在数据分析和数据清洗过程中,我们经常需要比较两个结构相似的pandas dataframe,找出它们之间的差异,并了解这些差异行分别来自哪个原始dataframe。例如,比较当前估计数据与历史估计数据,以识别发生变化的条目。pandas提供了强大的合并(merge)功能,结合特定的参数,可以高效地完成这项任务。
核心方法:外连接与指示器要识别两个DataFrame的差异行并追踪其来源,最有效的方法是使用pd.merge函数进行outer(外连接)操作,并设置indicator=True参数。
- how='outer': 外连接会保留左侧DataFrame中独有的行、右侧DataFrame中独有的行,以及两个DataFrame中都存在的匹配行。
- indicator=True: 当设置为True时,pd.merge会在结果DataFrame中添加一个名为_merge的特殊列。这个列会指示每行是来源于左侧DataFrame(left_only)、右侧DataFrame(right_only),还是同时来源于两个DataFrame(both)。
通过过滤_merge列,我们可以轻松地提取出差异行(即_merge不等于'both'的行),并直接获取它们的来源信息。
示例:识别并标记差异行假设我们有两个DataFrame,df_current_est(当前估计)和df_previous_est(先前估计),它们可能包含相同的列,我们希望找出两者之间不同的行,并标记这些行来自哪个原始DataFrame。
import pandas as pd # 示例数据 df_current_est = pd.DataFrame({ 'EstimateID': [10061, 10062, 10063], 'Season': [2023, 2023, 2023], 'RPIN': ['R1000', 'R2000', 'R3000'], 'GrowMethodCode': ['FO', 'FO', 'FO'], 'Variety_code': ['002', '068', '001'], 'Packout_pc': [0.60, 0.76, 0.80], 'QtyBins': [320, 1000, 500] }) df_previous_est = pd.DataFrame({ 'EstimateID': [10061, 10062, 10064], 'Season': [2023, 2023, 2023], 'RPIN': ['R1000', 'R2000', 'R4000'], 'GrowMethodCode': ['FO', 'FO', 'FO'], 'Variety_code': ['002', '068', '003'], 'Packout_pc': [0.60, 0.76, 0.90], 'QtyBins': [9000, 90000, 600] # 注意 QtyBins 差异 }) # 执行外连接并添加指示器 merged_df = pd.merge(df_current_est, df_previous_est, how='outer', indicator=True) # 筛选出差异行(_merge不为'both'的行) changed_df = merged_df[merged_df['_merge'] != 'both'].copy() # 对结果进行排序以便观察 changed_df = changed_df.sort_values(by=['EstimateID', '_merge'], ascending=True) print("识别出的差异行及其来源:") print(changed_df)
代码解释:
- pd.merge(df_current_est, df_previous_est, how='outer', indicator=True): 这一步是核心。它将两个DataFrame进行外连接,并生成_merge列。
- changed_df = merged_df[merged_df['_merge'] != 'both'].copy(): 我们通过筛选_merge列来获取所有不完全匹配的行。copy()操作是为了避免SettingWithCopyWarning,确保我们操作的是一个独立的DataFrame。
- changed_df.sort_values(...): 对结果进行排序,便于分析。
输出示例:
识别出的差异行及其来源: EstimateID Season RPIN GrowMethodCode Variety_code Packout_pc QtyBins _merge 0 10061 2023 R1000 FO 002 0.60 320 left_only 3 10061 2023 R1000 FO 002 0.60 9000 right_only 1 10062 2023 R2000 FO 068 0.76 1000 left_only 4 10062 2023 R2000 FO 068 0.76 90000 right_only 2 10063 2023 R3000 FO 001 0.80 500 left_only 5 10064 2023 R4000 FO 003 0.90 600 right_only
从输出可以看出,_merge列清晰地指示了每行是来自df_current_est(left_only)还是df_previous_est(right_only)。例如,EstimateID为10061的行在两个DataFrame中都有,但QtyBins值不同,因此它们都被标记为差异行,分别指示了各自的来源。
注意事项:不要随意删除_merge列在获取到包含_merge列的结果后,如果你需要利用该列的信息来判断行的来源,请务必不要将其删除。原始问题中的代码片段就犯了这个错误:
# 错误示例:不应删除_merge列 changed_df.drop('_merge', axis=1, inplace=True)
如果删除了_merge列,你将失去行的来源信息,这与你最初的目标相悖。
处理原始索引的需求上述方法能够很好地追踪行的来源,但它不会保留原始DataFrame的索引。在某些场景下,你可能不仅关心行来自哪个DataFrame,还需要知道它在原始DataFrame中的具体索引。_merge列并不能保留原始索引信息。
解决方案:将索引转换为普通列如果需要保留原始索引,你需要在进行merge操作之前,先使用reset_index()方法将每个DataFrame的索引转换为一个普通的数据列。这样,在合并之后,原始索引信息就会作为新的列存在于结果DataFrame中。
import pandas as pd # 示例数据,包含自定义索引 df1 = pd.DataFrame({'key': ['one', 'two', 'three'], 'value1': [1, 2, 3]}, index=[101, 102, 103]) df2 = pd.DataFrame({'key': ['two', 'four', 'three'], 'value2': [20, 40, 30]}, index=[201, 202, 203]) print("df1:") print(df1) print("\ndf2:") print(df2) # 将索引重置为列,然后进行外连接 merged_df_with_index = ( pd.merge(df1.reset_index(), df2.reset_index(), on='key', # 基于'key'列合并 suffixes=('_df1', '_df2'), # 为重复列添加后缀 how='outer', indicator=True) .query('_merge != "both"') # 筛选差异行 ) print("\n带有原始索引的差异行:") print(merged_df_with_index)
代码解释:
- df1.reset_index() 和 df2.reset_index(): 这一步将每个DataFrame的当前索引转换为一个名为index的列。
- suffixes=('_df1', '_df2'): 由于两个DataFrame都将有一个名为index的列,suffixes参数用于为这些同名列添加后缀,以区分它们来自哪个原始DataFrame(例如,index_df1和index_df2)。
- .query('_merge != "both"'): 这是一个简洁的筛选方式,等同于merged_df_with_index[merged_df_with_index['_merge'] != 'both']。
输出示例:
df1: key value1 101 one 1 102 two 2 103 three 3 df2: key value2 201 two 20 202 four 40 203 three 30 带有原始索引的差异行: index_df1 key value1 index_df2 value2 _merge 0 101.0 one 1.0 NaN NaN left_only 3 NaN four NaN 202.0 40.0 right_only
现在,结果DataFrame中包含了index_df1和index_df2列,它们分别存储了差异行在df1和df2中的原始索引。对于left_only的行,index_df2为NaN;对于right_only的行,index_df1为NaN。
扩展应用:查找匹配行的最大索引在某些分析场景中,你可能还需要找出那些在两个DataFrame中都匹配的行(即_merge == "both"),并获取它们在原始DataFrame中的最大索引。这在需要追踪最新版本或最高ID的匹配记录时非常有用。
# 查找匹配行(_merge == "both")的最大索引 max_id_same = ( pd.merge(df1.reset_index(), df2.reset_index(), on='key', suffixes=('_df1', '_df2'), how='outer', indicator=True) .query('_merge == "both"') # 筛选匹配行 )[['index_df1', 'index_df2']].max() # 选择索引列并取最大值 print("\n匹配行(_merge == \"both\")的最大索引:") print(max_id_same)
输出示例:
匹配行(_merge == "both")的最大索引: index_df1 103.0 index_df2 203.0 dtype: float64
这表明在df1中,匹配行的最大索引是103,在df2中是203。
总结本教程详细阐述了在Pandas中比较两个DataFrame并识别差异行的两种主要场景:
- 仅追踪行来源: 使用pd.merge(how='outer', indicator=True),然后筛选_merge列中不为'both'的行。_merge列将直接指示行是left_only还是right_only。关键是不要删除_merge列。
- 同时追踪行来源和原始索引: 在pd.merge之前,对每个DataFrame使用reset_index()将索引转换为普通列。合并时使用suffixes参数处理同名索引列,然后进行筛选。
掌握这些技术,可以帮助你在数据分析工作中更精确地理解和管理DataFrame之间的变化,从而提高数据处理的效率和准确性。
以上就是Pandas DataFrame差异行识别与来源追踪教程的详细内容,更多请关注知识资源分享宝库其它相关文章!
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。