本文将介绍一种利用 Pandas DataFrame 根据条件更新列值的高效方法,核心思想是通过重塑数据、分组操作以及前向和后向填充,避免了低效的逐行迭代。
问题描述
假设我们有一个 DataFrame,记录了针对特定 Issue ID 在不同日期所做的更改。DataFrame 中包含以下列:Issue_Id、Due_Date、status、estimation_hour、changed_date、changed_parameter、old_value 和 new_value。我们的目标是基于这些数据,为每个 Issue ID 在每次更改日期创建一个快照。这意味着我们需要根据 changed_date,将 new_value 应用于对应的 changed_parameter,从而更新 DataFrame 中其他行的值。
解决方案
直接迭代 DataFrame 的行并更新值效率较低,尤其是当数据量很大时。一种更有效的方法是使用 Pandas 的 pivot_table 函数来重塑数据,然后使用 groupby 函数按 Issue_Id 进行分组,最后使用 ffill (forward fill) 和 bfill (backward fill) 函数来更新值。
以下是具体的实现步骤:
- 定义更新函数:
import pandas as pd def update_values(df): return df['new_value'].ffill().fillna(df['old_value'].bfill())
这个函数使用 ffill() 将 new_value 列中的缺失值用前面的有效值填充,然后使用 fillna(df[‘old_value’].bfill()) 将剩余的缺失值用 old_value 列的后向填充值填充。这确保了即使某个参数在特定日期没有更改,也能使用最近的值。
- 重塑数据:
upd_values = (df.pivot_table(index=df.index, columns='changed_parameter', values=['old_value', 'new_value'], aggfunc='first') .groupby(df['Issue_Id']).apply(update_values) .droplevel('Issue_Id').fillna(df))
pivot_table 函数将 changed_parameter 列转换为新的列,并将 old_value 和 new_value 作为这些列的值。aggfunc=’first’ 确保对于每个 changed_parameter,只保留第一个值。然后,我们使用 groupby(df[‘Issue_Id’]).apply(update_values) 按 Issue_Id 对数据进行分组,并将 update_values 函数应用于每个组。droplevel(‘Issue_Id’) 移除多余的索引层级。最后,使用 fillna(df) 来填充任何剩余的缺失值,确保所有原始数据都被保留。
- 更新 DataFrame:
df[upd_values.columns] = upd_values
这行代码将更新后的值赋回原始 DataFrame。
完整代码示例
import pandas as pd # 示例数据 data = {'Issue_Id': [101, 101, 101, 101, 101, 101, 101, 102, 102, 102, 102, 102], 'Due_Date': ['1/31/2023', '1/31/2023', '1/31/2023', '1/31/2023', '1/31/2023', '1/31/2023', '1/31/2023', '2/28/2023', '2/28/2023', '2/28/2023', '2/28/2023', '2/28/2023'], 'status': ['closed', 'closed', 'closed', 'closed', 'closed', 'closed', 'closed', 'closed', 'closed', 'closed', 'closed', 'closed'], 'estimation_hour': [40, 40, 40, 40, 40, 40, 40, 50, 50, 50, 50, 50], 'changed_date': ['1/10/2023', '1/15/2023', '1/16/2023', '1/16/2023', '1/20/2023', '1/25/2023', '1/30/2023', '1/10/2023', '1/15/2023', '1/20/2023', '1/25/2023', '1/30/2023'], 'changed_parameter': ['status', 'estimation_hour', 'estimation_hour', 'Due_Date', 'status', 'estimation_hour', 'status', 'status', 'estimation_hour', 'status', 'estimation_hour', 'status'], 'old_value': ['Defined', '0', '20', '1/20/2023', 'Accepted', '30', 'InProgress', 'Defined', '0', 'Accepted', '30', 'InProgress'], 'new_value': ['Accepted', '20', '30', '1/31/2023', 'InProgress', '40', 'Closed', 'Accepted', '30', 'InProgress', '50', 'Closed']} df = pd.DataFrame(data) def update_values(df): return df['new_value'].ffill().fillna(df['old_value'].bfill()) upd_values = (df.pivot_table(index=df.index, columns='changed_parameter', values=['old_value', 'new_value'], aggfunc='first') .groupby(df['Issue_Id']).apply(update_values) .droplevel('Issue_Id').fillna(df)) df[upd_values.columns] = upd_values print(df)
注意事项
- 确保 DataFrame 按照 changed_date 排序,以保证 ffill 和 bfill 的正确性。
- 此方法假设 changed_parameter 列中的值是有限的,并且可以作为列名使用。
- 如果数据量非常大,可以考虑使用更高效的数据结构或分布式计算框架。
总结
通过使用 Pandas 的 pivot_table、groupby、ffill 和 bfill 函数,我们可以高效地根据条件更新 DataFrame 中的列值,避免了低效的逐行迭代。这种方法可以显著提高处理大型数据集的性能。