在数据处理中,我们经常需要根据特定条件从dataframe的多个列中选择一个值,并同时记录该值的来源列。例如,给定一个dataframe,其中包含多列可能的数据源,我们希望创建一个新列来存储首个有效(非空)值,并创建另一个新列来记录该值的原始列名。
考虑以下DataFrame示例:
import pandas as pd import numpy as np data = {'A': [1.0, 2.0, np.nan], 'B': [4, 5, 6]} df = pd.DataFrame(data) print("原始DataFrame:") print(df)
期望的输出是这样的:
A B val val_source 0 1.0 4 1.0 A 1 2.0 5 2.0 A 2 NaN 6 6.0 B
初次尝试时,开发者可能会倾向于使用numpy.select,因为它允许根据条件数组选择不同的“选择”数组。然而,np.select的一个限制是,它期望每个“选择”项都是一个单一的数组或Series,而不能直接返回一个包含多列的DataFrame。
例如,以下尝试会引发错误:
# 这种尝试会报错,因为np.select不支持返回多列 # conds = [df['A'].notna(), True] # choices = [df[['A']].assign(val_source='A'), df[['B']].assign(val_source='B')] # df[['val', 'val_source']] = np.select(conds, choices)
为了规避这一限制,常见的做法是执行两次独立的np.select操作,即使它们的条件逻辑完全相同:
# 常见的双次np.select workaround conds = [df['A'].notna(), True] _choices_val_src = [ (df['A'], 'A'), (df['B'], 'B'), ] choices_val, choices_src = zip(*_choices_val_src) df_copy = df.copy() # 使用副本避免修改原始df df_copy['val'] = np.select(conds, choices_val, default=np.nan) df_copy['val_source'] = np.select(conds, choices_src, default=np.nan) print("\n使用两次np.select的结果:") print(df_copy)
虽然这种方法能够实现目标,但当需要处理的候选列数量很多时,代码会显得冗长且存在重复计算,不够优雅和高效。
对于“选择第一个非空值及其来源”这类特定场景,Pandas和NumPy提供了一种更为简洁和高效的解决方案,它利用了DataFrame.notna().to_numpy().argmax(axis=1)来找到每行中第一个非空值的列索引,然后结合NumPy的高级索引特性来提取值和列名。
# 原始DataFrame data = {'A': [1.0, 2.0, np.nan], 'B': [4, 5, 6]} df = pd.DataFrame(data) # 1. 识别每行第一个非空值的列索引 # df.notna() 返回一个布尔型DataFrame,指示每个元素是否为非空 # .to_numpy() 将布尔型DataFrame转换为NumPy数组 # .argmax(1) 沿着行方向(axis=1)找到第一个True(即第一个非空值)的索引 # 如果一行全是False(即全是NaN),argmax会返回0(第一个列的索引),这需要注意后续处理 idx = df.notna().to_numpy().argmax(1) print("\n每行第一个非空值的列索引 (idx):") print(idx) # 2. 提取对应的值 # df.to_numpy() 将整个DataFrame转换为NumPy数组 # (df.index, idx) 创建一个元组,用于NumPy的高级索引 # df.index 提供了行的索引(0, 1, 2...) # idx 提供了列的索引(0代表A,1代表B...) # 这样,对于每一行,我们都精确地取到了其在idx中指定的列的值 df['val'] = df.to_numpy()[(df.index, idx)] # 3. 提取对应的列名作为来源 # df.columns 是DataFrame的列名列表 # df.columns[idx] 使用之前计算的列索引idx来直接获取对应的列名 df['val_source'] = df.columns[idx] print("\n使用argmax和高级索引的结果:") print(df)
代码解析:
idx = df.notna().to_numpy().argmax(1):
df['val'] = df.to_numpy()[(df.index, idx)]:
df['val_source'] = df.columns[idx]:
适用场景:
此方法特别适用于以下场景:
注意事项:
在Pandas中进行多列条件赋值并追踪来源是一个常见的需求。尽管numpy.select在直接返回多列方面存在局限性,但通过巧妙地结合DataFrame.notna().to_numpy().argmax(1)和NumPy的高级索引,我们可以为“选择第一个非空值及其来源”这一特定问题提供一个极其高效和简洁的解决方案。掌握这种技巧将有助于编写更优雅、更具性能的Pandas数据处理代码。
以上就是Pandas与NumPy:高效实现多列条件赋值与来源追踪的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号