在pandas数据框中获取每行的最后一个非空值

6

我有一个形状为(40,500)的数据框。数据框中的每一行都有一些数值,直到某个变量列号k,之后的所有条目都是NaN。

我想要获取每行中最后一个非NaN列的值。是否有一种方法可以在不循环遍历数据框的所有行的情况下完成此操作?

样本数据框:

2016-06-02 7.080 7.079 7.079 7.079 7.079 7.079   nan   nan   nan
2016-06-08 7.053 7.053 7.053 7.053 7.053 7.054   nan   nan   nan  
2016-06-09 7.061 7.061 7.060 7.060 7.060 7.060   nan   nan   nan   
2016-06-14   nan   nan   nan   nan   nan   nan   nan   nan   nan  
2016-06-15 7.066 7.066 7.066 7.066   nan   nan   nan   nan   nan  
2016-06-16 7.067 7.067 7.067 7.067 7.067 7.067 7.068 7.068   nan  
2016-06-21 7.053 7.053 7.052   nan   nan   nan   nan   nan   nan  
2016-06-22 7.049 7.049   nan   nan   nan   nan   nan   nan   nan  
2016-06-28 7.058 7.058 7.059 7.059 7.059 7.059 7.059 7.059 7.059  

要求的输出

2016-06-02 7.079 
2016-06-08 7.054
2016-06-09 7.060
2016-06-14   nan 
2016-06-15 7.066
2016-06-16 7.068 
2016-06-21 7.052 
2016-06-22 7.049
2016-06-28 7.059  
3个回答

15
您需要使用自定义函数与 last_valid_index,因为如果所有值都是NaN,它会返回KeyError
def f(x):
    if x.last_valid_index() is None:
        return np.nan
    else:
        return x[x.last_valid_index()]

df['status'] = df.apply(f, axis=1)
print (df)
                1      2      3      4      5      6      7      8      9  \
0                                                                           
2016-06-02  7.080  7.079  7.079  7.079  7.079  7.079    NaN    NaN    NaN   
2016-06-08  7.053  7.053  7.053  7.053  7.053  7.054    NaN    NaN    NaN   
2016-06-09  7.061  7.061  7.060  7.060  7.060  7.060    NaN    NaN    NaN   
2016-06-14    NaN    NaN    NaN    NaN    NaN    NaN    NaN    NaN    NaN   
2016-06-15  7.066  7.066  7.066  7.066    NaN    NaN    NaN    NaN    NaN   
2016-06-16  7.067  7.067  7.067  7.067  7.067  7.067  7.068  7.068    NaN   
2016-06-21  7.053  7.053  7.052    NaN    NaN    NaN    NaN    NaN    NaN   
2016-06-22  7.049  7.049    NaN    NaN    NaN    NaN    NaN    NaN    NaN   
2016-06-28  7.058  7.058  7.059  7.059  7.059  7.059  7.059  7.059  7.059   

            status  
0                   
2016-06-02   7.079  
2016-06-08   7.054  
2016-06-09   7.060  
2016-06-14     NaN  
2016-06-15   7.066  
2016-06-16   7.068  
2016-06-21   7.052  
2016-06-22   7.049  
2016-06-28   7.059  

另一种解决方案-使用fillna函数和ffill方法,并通过iloc选择最后一列:

df['status'] = df.ffill(axis=1).iloc[:, -1]
print (df)
            status  
0                   
2016-06-02   7.079  
2016-06-08   7.054  
2016-06-09   7.060  
2016-06-14     NaN  
2016-06-15   7.066  
2016-06-16   7.068  
2016-06-21   7.052  
2016-06-22   7.049  
2016-06-28   7.059  

8

使用agg('last')函数

df.groupby(['status'] * df.shape[1], 1).agg('last')

enter image description here


'agg'函数中的'last'会在分组内产生最后一个有效值。我传递了一个与列数相等长度的列表,该列表的每个值都是'status'。这意味着我正在按一组进行分组。结果是一个名为'status'的数据框。


1
谢谢,这解决了问题,但我只能接受一个答案。鉴于jezrael方法的易懂和简单直观,我接受他的回答,并为你们两个点赞。谢谢piRSquared! - dayum
我认为这个解决方案比应用lambda更快。然而,我无法将其应用于数据框的子集。 - Eduardo EPF

4
这里提供一个基于NumPy的解决方案 -
In [113]: a
Out[113]: 
array([[ 17.,  53.,  nan,  63.,  66.,  nan,  nan,  nan,  nan,  nan],
       [ 54.,  96.,  71.,  20.,  70.,  58.,  91.,  nan,  nan,  nan],
       [ 58.,  26.,  72.,  93.,  58.,  29.,  44.,  28.,  36.,  88.],
       [ nan,  nan,  nan,  nan,  nan,  nan,  nan,  nan,  nan,  nan],
       [ 94.,  23.,  nan,  nan,  92.,  81.,  40.,  30.,  84.,  nan]])

In [114]: m = ~np.isnan(a)

In [115]: a[np.arange(m.shape[0]), m.shape[1]-m[:,::-1].argmax(1)-1]
Out[115]: array([ 66.,  91.,  88.,  nan,  84.])

要将此转换为数据框,首先我们可以将值提取为一个数组:a = df.values,最后生成输出数据框:
vals = a[np.arange(m.shape[0]), m.shape[1]-m[:,::-1].argmax(1)-1]
df_out = pd.DataFrame(vals,index=df.index)

谢谢,这个完美地解决了问题,但是我只能接受一个答案。鉴于jezrael的方法易于理解和直接,我接受他的答案,并为你们俩投票。谢谢Divakar! - dayum

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接