如何为Pandas DataFrame每行提取首个非空值?求更优方案
高效实现DataFrame每行取首个非空列值的优化方案
嘿,你现在用嵌套np.where的方式虽然能实现需求,但当列数变多的时候会变得非常繁琐,而且代码可读性也差。这里给你几个更简洁高效的方案,适配你这种列名无固定规律的场景:
首先先还原你的示例DataFrame:
import pandas as pd import numpy as np df = pd.DataFrame({ "ID": [1, 2, 3, 4], "c1": ["a", np.nan, np.nan, np.nan], "c2": ["b", "cc", np.nan, np.nan], "c3": [np.nan, "dd", "ee", np.nan], "c4": ["a", np.nan, "ff", "gg"], })
方案1:用bfill实现一行搞定(最简洁)
这是我最推荐的方法,利用pandas的向后填充(bfill)功能,按行方向操作:
# 先筛选出需要检查的目标列(这里是c1到c4,你可以根据实际列名调整筛选逻辑) target_cols = [col for col in df.columns if col.startswith('c')] # 按行向后填充,取第一列就是每行的首个非空值 df['result'] = df[target_cols].bfill(axis=1).iloc[:, 0]
原理很简单:bfill(axis=1)会沿着每行从左到右,用后面的非空值填充前面的空值,所以填充后的第一列就正好是每行最左边的非空值。如果你的列是要从右往左找首个非空值,换成ffill(axis=1).iloc[:, -1]就行。
方案2:向量化的idxmax+lookup(高效适配大数据)
如果你的数据集很大,需要极致的效率,可以用这种纯向量化的操作,避免循环或apply:
non_null_mask = df[target_cols].notnull() # 找到每行第一个非空值对应的列名 first_non_null_col = non_null_mask.idxmax(axis=1) # 根据行索引和列名取值 df['result'] = df.lookup(df.index, first_non_null_col)
idxmax(axis=1)会返回每行第一个True值的列名,配合lookup就能快速取出对应的值,全程都是pandas的底层向量操作,性能拉满。
方案3:apply+first_valid_index(可读性拉满)
如果需要代码逻辑非常清晰,容易理解,可以用apply逐行处理:
def get_first_non_null(row): # 获取该行第一个非空值的列索引 first_valid_col = row.first_valid_index() return row[first_valid_col] if first_valid_col is not None else np.nan df['result'] = df[target_cols].apply(get_first_non_null, axis=1)
这个方法的好处是逻辑一目了然,但要注意:apply本质是逐行循环,在数据量很大的时候性能会比前两种向量化方法差一些,适合小数据集或者需要自定义额外逻辑的场景。
方案对比
| 方案 | 优点 | 缺点 |
|---|---|---|
bfill | 代码最简洁,性能优 | 逻辑相对不直观(需要理解填充机制) |
idxmax+lookup | 性能最优,纯向量操作 | 代码比bfill稍长 |
apply | 可读性最强,易扩展 | 大数据量下性能较差 |
内容的提问来源于stack exchange,提问作者qqqwww
相关产品推荐
相关产品推荐

