我希望创建一个程序来比较两个数据帧并检测对每个数据帧所做的更改。这适用于版本控制应用程序,其中原始文件已被用户操作,我正在尝试提取他们对原始文件所做的更改。
尝试的幼稚解决方案:
import pandas as pd
# Initalize the data
data_original = [['4', 'NYC','New York'], ['3', 'BOS','Boston'], ['2', 'CHI','Chicago']]
data_new = [['4', 'NYC','New York','50'], ['3', 'Boston','Boston','100'], ['2', 'CHI','Chicago','20'], ['8', 'LA','Los Angeles','30']]
# Create the dataframes
df_original = pd.DataFrame(data_original, columns = ['Office Number', 'Office Name','Office Location'])
df_new = pd.DataFrame(data_new, columns = ['Office Number', 'Office Name','Office Location','Money'])
df_changes = df_new[ ~df_new.isin(df_original)].dropna()
生成的数据帧:
['8', 'LA','Los Angeles','30']
这个天真的解决方案不是我想要的,因为它不会检测到数据帧第二项中"BOS"到"Boston"的变化。我正在寻找即使连续更改了单个项目也能指示的东西。列的加减我可以单独计算,但是如何检测元素的变化,例如"波士顿"到"波士顿"?
全面检查
def compare(old, new):
new_cols = new.columns.difference(old.columns)
del_cols = old.columns.difference(new.columns)
new_indx = new.index.difference(old.index)
del_indx = old.index.difference(new.index)
# Now that we've checked new and deleted rows and columns
# `align` the dataframes and check the values
old, new = old.align(new, 'inner')
I, J = np.where(old.ne(new))
c = old.columns
r = old.index
changes = pd.DataFrame([
[r[i], c[j], old.iat[i, j], new.iat[i, j]]
for i, j in zip(I, J)
], columns=['Row', 'Column', 'Old', 'New'])
return changes, new_cols, del_cols, new_indx, del_indx
示范
获取变更数据
changes, new_cols, del_cols, new_indx, del_indx = compare(df_original, df_new)
打印一份漂亮的报告
print(f"""
New Columns:
{' '.join(new_cols.astype(str))}
Deleted Columns:
{' '.join(del_cols.astype(str))}
New Rows:
{' '.join(new_indx.astype(str))}
Deleted Rows:
{' '.join(del_indx.astype(str))}
Changes:
{changes}
""")
New Columns:
Money
Deleted Columns:
New Rows:
3
Deleted Rows:
Changes:
Row Column Old New
0 1 Office Name BOS Boston
____
更简单的解决方案
我们可以放弃查找添加和删除的列和行的细分,而只是适当地解释changes
数据框中的空值。
def compare(old, new):
old, new = old.align(new) # Notice I don't use `'inner'` as I did before
I, J = np.where(old.ne(new))
c = old.columns
r = old.index
changes = pd.DataFrame([
[r[i], c[j], old.iat[i, j], new.iat[i, j]]
for i, j in zip(I, J)
], columns=['Row', 'Column', 'Old', 'New'])
return changes
compare(df_original, df_new)
Row Column Old New
0 0 Money NaN 50
1 1 Money NaN 100
2 1 Office Name BOS Boston
3 2 Money NaN 20
4 3 Money NaN 30
5 3 Office Location NaN Los Angeles
6 3 Office Name NaN LA
7 3 Office Number NaN 8
在这种情况下,唯一的更改由'Old'
列中的非 null 值表示。 所有其他的都是新的。
__
更安全的解决方案
如果同时在新旧数据帧中np.nan
,则计算结果将不相等。 这个版本,考虑到了这一点。
但是,如果一个数据帧None
而另一个数据帧np.nan
,它仍然无法捕获。 我将把它留给未来的读者练习。
def compare(old, new):
old, new = old.align(new)
I, J = np.where(old.ne(new))
c = old.columns
r = old.index
data = []
for i, j in zip(I, J):
n = new.iat[i, j]
o = old.iat[i, j]
if pd.notna(n) or pd.notna(o):
data.append([r[i], c[j], o, n])
return pd.DataFrame(data, columns=['Row', 'Column', 'Old', 'New'])
也许这就是你需要的?
df_changes = df_new[ ~df_new[["Office Number","Office Name","Office Location"]].apply(tuple,1).isin(df_original[["Office Number","Office Name","Office Location"]].apply(tuple,1))].dropna()
不知道现在是否仍然需要 dropna((。您可以将列设置为元组,使其不可变,并且可以跨键进行比较。使用您的测试数据进行测试,我认为它可以工作。
如果比较是沿着索引进行的,那么我们需要两个reindexlike
调用。第一个允许将原始数据帧与新数据帧进行比较,而不考虑其他行或列。第二个将所有其他行和列标记为True
。生成的数据帧True
df_new
与df_original
不同的地方。
m = (df_new.reindex_like(df_original)
.ne(df_original)
.reindex_like(df_new)
.fillna(True))
Office Number Office Name Office Location Money
0 False False False True
1 False True False True
2 False False False True
3 True True True True
# Can slice to see changes
df_new[m]
Office Number Office Name Office Location Money
0 NaN NaN NaN 50
1 NaN Boston NaN 100
2 NaN NaN NaN 20
3 8 LA Los Angeles 30