在 pandas DataFrame 的每一行中保留前 n 个非 NaN 单元格

use*_*035 4 python nan dataframe pandas

我有一个 Pandas Dataframe,每行至少有 4 个非 NaN 值,但位于不同的列:

Index       Col1     Col2      Col3         Col4     Col5  Col6  Col7  Col8 
1991-12-31  100.000 100.000    100.000     89.123   NaN    NaN   NaN   NaN                     
1992-01-31  98.300  101.530    100.000     NaN      92.342 NaN   NaN   NaN                     
1992-02-29  NaN     100.230    98.713      97.602   NaN    NaN   NaN   NaN                     
1992-03-31  NaN     NaN        102.060     93.473   98.123 NaN   NaN   NaN                     
1992-04-30  NaN     102.205    107.755     94.529   94.529 NaN   NaN   NaN
Run Code Online (Sandbox Code Playgroud)

(我只显示前 8 列)我想将其转换为每行 4 列的数据框。这些行应仅包含该日期的前四个(从左到右读取)非 NaN 值。

编辑:

每行的顺序很重要。

Div*_*kar 5

方法#1:这是一个 NumPy 解决方案,使用justify-

pd.DataFrame(justify(df.values, invalid_val=np.nan, axis=1, side='left')[:,:4])
Run Code Online (Sandbox Code Playgroud)

样本运行 -

In [211]: df
Out[211]: 
             Col1     Col2     Col3    Col4    Col5  Col6  Col7  Col8
Index                                                                
1991-12-31  100.0  100.000  100.000  89.123     NaN   NaN   NaN   NaN
1992-01-31   98.3  101.530  100.000     NaN  92.342   NaN   NaN   NaN
1992-02-29    NaN  100.230   98.713  97.602     NaN   NaN   NaN   NaN
1992-03-31    NaN      NaN  102.060  93.473  98.123   NaN   NaN   NaN
1992-04-30    NaN  102.205  107.755  94.529  94.529   NaN   NaN   NaN

In [212]: pd.DataFrame(justify(df.values, invalid_val=np.nan, axis=1, side='left')[:,:4])
Out[212]: 
         0        1        2       3
0  100.000  100.000  100.000  89.123
1   98.300  101.530  100.000  92.342
2  100.230   98.713   97.602     NaN
3  102.060   93.473   98.123     NaN
4  102.205  107.755   94.529  94.529
Run Code Online (Sandbox Code Playgroud)

方法#2:使用掩模的定制功能 -

def app2(df, N=4):
    a = df.values
    out = np.empty_like(a)
    mask = df.isnull().values
    mask_sorted = np.sort(mask,1)
    out[~mask_sorted] = a[~mask]
    return pd.DataFrame(out[:,:N])
Run Code Online (Sandbox Code Playgroud)

保持秩序的工作解决方案的运行时测试 -

# Using df from posted question to recreate a bigger one :
df = df.set_index('Index')
df = pd.concat([df] * 10000, ignore_index=1)

In [298]: %timeit app2(df)
100 loops, best of 3: 4.06 ms per loop

In [299]: %timeit pd.DataFrame(justify(df.values, invalid_val=np.nan, axis=1, side='left')[:,:4])
100 loops, best of 3: 4.78 ms per loop

In [300]: %timeit df.apply(sorted, key=np.isnan, axis=1).iloc[:, :4]
1 loop, best of 3: 4.05 s per loop
Run Code Online (Sandbox Code Playgroud)