
引言:识别Pandas DataFrame中的差异
在数据分析和处理的日常工作中,我们经常需要比较两个结构相似的Pandas DataFrame,以找出它们之间的不同之处。例如,比较不同时间点的数据快照,或验证数据处理前后的变化。理想情况下,我们希望得到的输出不仅能指出哪些行存在差异,还能明确显示这些差异具体发生在哪些列上,并且只保留这些差异化的信息,剔除完全相同的部分。
考虑以下两个DataFrame df1 和 df2:
import pandas as pd
data1 = {
'pet_name': ['Patrick', 'Patrick', 'Patrick', 'Patrick'],
'exam_day': ['2023-01-01', '2023-01-02', '2023-01-03', '2023-01-04'],
'result_1': [1, 2, 3, 4],
'result_2': [10, 20, 30, 40],
'pre_result_1': [123, 123, 123, 123]
}
df1 = pd.DataFrame(data1)
data2 = {
'pet_name': ['Patrick', 'Patrick', 'Patrick', 'Patrick'],
'exam_day': ['2023-01-01', '2023-01-02', '2023-01-03', '2023-01-04'],
'result_1': [1, 99, 3, 4], # Difference here: df1 has 2, df2 has 99
'result_2': [10, 20, 30, 100], # Another difference for demonstration
'pre_result_1': [123, 123, 123, 123]
}
df2 = pd.DataFrame(data2)
print("df1:")
print(df1)
print("\ndf2:")
print(df2)df1:
pet_name exam_day result_1 result_2 pre_result_1 0 Patrick 2023-01-01 1 10 123 1 Patrick 2023-01-02 2 20 123 2 Patrick 2023-01-03 3 30 123 3 Patrick 2023-01-04 4 40 123
df2:
pet_name exam_day result_1 result_2 pre_result_1 0 Patrick 2023-01-01 1 10 123 1 Patrick 2023-01-02 99 20 123 2 Patrick 2023-01-03 3 30 123 3 Patrick 2023-01-04 4 100 123
我们注意到 df1 和 df2 在以下位置存在差异:
- pet_name='Patrick', exam_day='2023-01-02' 行的 result_1 列:df1 为 2,df2 为 99。
- pet_name='Patrick', exam_day='2023-01-04' 行的 result_2 列:df1 为 40,df2 为 100。
如果仅仅使用 merge(..., indicator=True, how="outer") 并过滤 _merge != "both",虽然能识别出有差异的行,但会保留所有列,并且对同一差异行会分别显示 left_only 和 right_only 两条记录,无法直接突出差异所在的具体列。我们的目标是获得一个更精炼的视图,仅包含差异行和差异列,同时保留关键的标识列。
核心解决方案:使用DataFrame.compare()
Pandas 1.1.0 版本引入的 DataFrame.compare() 方法是专门为解决这类问题而设计的。它能够进行元素级别的比较,并以一种结构化的方式展示差异。
步骤一:准备数据——设置索引
为了让 compare() 方法能够正确地对齐和比较行,我们需要将DataFrame中的关键标识列(例如 pet_name 和 exam_day)设置为索引。这些列通常被称为“维度”列或“主键”列,它们在比较过程中不应被视为可变的值,而是作为行的唯一标识符。
df1_indexed = df1.set_index(['pet_name', 'exam_day'])
df2_indexed = df2.set_index(['pet_name', 'exam_day'])
print("df1_indexed (partial view):")
print(df1_indexed.head(2))输出示例:
df1_indexed (partial view):
result_1 result_2 pre_result_1
pet_name exam_day
Patrick 2023-01-01 1 10 123
2023-01-02 2 20 123通过设置索引,compare() 方法将基于这些索引值来匹配行。
步骤二:执行比较——compare(align_axis=0)
接下来,我们使用 compare() 方法对两个已设置索引的DataFrame进行比较。关键参数是 align_axis=0,它指示 compare() 在行级别进行对齐。
diff_df_raw = df1_indexed.compare(df2_indexed, align_axis=0)
print("Raw comparison output (diff_df_raw):")
print(diff_df_raw)输出示例:
Raw comparison output (diff_df_raw):
result_1 result_2
pet_name exam_day
Patrick 2023-01-02 self 2.0 NaN
other 99.0 NaN
2023-01-04 self NaN 40.0
other NaN 100.0compare() 方法的输出特点:
- 行索引: compare() 会在原始索引的基础上增加一个内部级别 (self 和 other),用于区分 df1 (self) 和 df2 (other) 中的值。
- 列过滤: compare() 会自动过滤掉所有值都完全相同的列。在我们的例子中,pre_result_1 列在两个DataFrame中完全相同,因此它没有出现在输出中。
- NaN 填充: 对于在特定行中没有差异的列,compare() 会用 NaN 填充。例如,在 2023-01-02 的差异行中,result_2 并没有差异,因此显示为 NaN。
步骤三:重塑输出——droplevel()与reset_index()
为了将 compare() 的输出重塑成我们期望的简洁格式(即每行显示一个差异值,且包含原始的关键标识列),我们需要进行额外的后处理。
- droplevel(-1): 这一步用于删除行索引中表示 self/other 的最内层级别。这样,对于同一个 (pet_name, exam_day) 组合,来自 self 和 other 的差异值将作为独立的










