使用 Pandas,找到每行第一个非空值所在的列名

6

我希望知道各种项目的第一年收入情况。

给出以下数据框:

ID  Y1      Y2      Y3
0   NaN     8       4
1   NaN     NaN     1
2   NaN     NaN     NaN
3   5       3       NaN

我希望能够按行返回第一列非空值的名称。

在这种情况下,我想要返回:

['Y2','Y3',NaN,'Y1']

我的目标是将此列添加到原始数据框中。

以下代码基本上可以工作,但非常笨重。

import pandas as pd
import numpy as np

df = pd.DataFrame({'Y1':[np.nan, np.nan, np.nan, 5],'Y2':[8, np.nan, np.nan, 3], 'Y3':[4, 1, np.nan, np.nan]})
df['first'] = np.nan

for ID in df.index:
row = df.loc[ID,]
for i in range(0,len(row)):
    if (~pd.isnull(row[i])):
        df.loc[ID,'first'] = row.index[i]
        break

返回:

   Y1  Y2  Y3  first
0 NaN  8   4   Y2   
1 NaN NaN  1   Y3   
2 NaN NaN NaN  first
3  5   3  NaN  Y1   

有没有人知道更优雅的解决方案?

3个回答

14
你可以使用 lambda 表达式,通过 axis=1 指定行,在数据帧的每一行上应用 first_valid_index 方法。

你可以使用 lambda 表达式,通过 axis=1 指定行,在数据帧的每一行上应用 first_valid_index 方法。

>>> df.apply(lambda row: row.first_valid_index(), axis=1)
ID
0      Y2
1      Y3
2    None
3      Y1
dtype: object

将其应用于您的数据框:

df = df.assign(first = df.apply(lambda row: row.first_valid_index(), axis=1))

>>> df
    Y1  Y2  Y3 first
ID                  
0  NaN   8   4    Y2
1  NaN NaN   1    Y3
2  NaN NaN NaN  None
3    5   3 NaN    Y1

1
避免使用apply,因为它不是矢量化的。以下是矢量化的方法。它已经在Pandas 1.1中进行了测试。

设置

import numpy as np
import pandas as pd

df = pd.DataFrame({'Y1':[np.nan, np.nan, np.nan, 5],'Y2':[8, np.nan, np.nan, 3], 'Y3':[4, 1, np.nan, np.nan]})

# df.dropna(how='all', inplace=True)  # Optional but cleaner

# For ranking only:
col_ranks = pd.DataFrame(index=df.columns, data=np.arange(1, 1 + len(df.columns)), columns=['first_notna_rank'], dtype='UInt8') # UInt8 supports max value of 255.

找到第一个非空列的名称。
df['first_notna_name'] = df.dropna(how='all').notna().idxmax(axis=1).astype('string')

如果保证df没有全为null的行,则可以选择删除上面的.dropna操作。

然后找到第一个非null值

bfill
df['first_notna_value'] = df[df.columns.difference(['first_notna_name'])].bfill(axis=1).iloc[:, 0]
melt
df['first_notna_value'] = df.melt(id_vars='first_notna_name', value_vars=df.columns.difference(['first_notna_name']), ignore_index=False).query('first_notna_name == variable').merge(df[[]], how='right', left_index=True, right_index=True).loc[df.index, 'value']

如果保证df没有所有行都为null的情况,则上述.merge操作可以选择删除。

对名称进行排名

df = df.merge(col_ranks, how='left', left_on='first_notna_name', right_index=True)

有更好的方式吗?

输出

    Y1   Y2   Y3 first_notna_name  first_notna_value  first_notna_rank
0  NaN  8.0  4.0               Y2                8.0                 2
1  NaN  NaN  1.0               Y3                1.0                 3
2  NaN  NaN  NaN             <NA>                NaN              <NA>
3  5.0  3.0  NaN               Y1                5.0                 1

部分得分:piRSquaredAndy 给出了答案。


-1
将此代码应用于仅有一行的数据框,以返回包含空值的行中的第一列。

row.columns[~(row.loc[:].isna()).all()][-1]


2
这不是这个问题所要求的。 - Harmon758

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