比较字典以检查它们是否相等,或者它们是否从空变为具有值

Compare dictionaries to check if they are equal or if they go from empty to having a value

提问人:Angie 提问时间:3/10/2023 最后编辑:Angie 更新时间:3/11/2023 访问量:93

问:

我有两个数据帧,我在其中遍历每一行,并为我正在查看的行创建一个字典以相互比较。

我通过做以下操作来做到这一点:

ids = []
for row in range(len(df1)-1):
    df1_row = dict(df1.iloc[row])
    df2_row = dict(df2.iloc[row])
    if df1_row == df2_row:
        ids.append(df1_row['ID'])

我正在检查我一次比较的两行是否相等,如果相等,我将该行的 id 附加到列表以在最后返回。

但是,我还想检查以下条件:如果 df2 中的行包含给定键的空字符串,而 df1 中的行包含同一键的值,但其余的键值对在它们之间相等,那么我也想将该 id 附加到列表中。

例如,如果我像这样查看两行

df1_row = {'NAME': 'Kelly', 'AGE': '15', 'CITY': 'London', 'GENDER': 'F', 'ID': 15}
df2_row = {'NAME': 'Kelly', 'AGE': '15', 'CITY': '', 'GENDER': 'F', 'ID': '15'}

然后我想将 ID 15 附加到我的列表中,因为 CITY 从 df2_row 中的 EMPTY 变为df1_row中的值。

如果这对看起来像这样

df1_row = {'NAME': 'Kelly', 'AGE': '15', 'CITY': 'London', 'GENDER':''  'ID': 15}
df2_row = {'NAME': 'Kelly', 'AGE': '15', 'CITY': '', 'GENDER': 'F', 'ID': '15'}

我不想将 id 15 附加到我的结果列表中,因为即使 CITY 从 EMPTY 变为 df2_row 到 df1_row,GENDER 的值从 df2 到 df1 变为 EMPTY。

(基本上我的检查是:行完全相等,或者它们的值从空到非空(从 df2 到 df1),其余值相等)

我试过了

ids = []
for row in range(len(df1)-1):
    df1_row = dict(df1.iloc[row])
    df2_row = dict(df2.iloc[row])
    if df1_row == df2_row:
        ids.append(df1_row['ID'])
    else:
        for key in df1_row:
            if df1_row[key] == df2_row[key] or (df2_row[key] == '' and df1_row[key] != ''):

但是我不确定如何编写第二个条件,以便它只在检查整行后附加 id,而不是只检查当前键值上的条件并在那里附加 id......有没有办法一次检查整行的这个条件/另一种写这个的方法?谢谢!(或者,也许有一种更好的方法可以使用这些条件将同一 ID 的 DataFrame 中的两行相互比较,而不必将行转换为字典进行比较?

测试台

DF1:

名字 年龄 城市 编号
凯利 15 伦敦 F 15
千斤顶 12 M 98
乔希 30 奥斯汀 M 12

DF2:

名字 年龄 城市 编号
凯利 15 F 15
千斤顶 慕尼黑 M 98
乔希 30 奥斯汀 M 12

我想取回 ID 15 和 12,因为 12 完全匹配,并且在 15 中它完全匹配,或者它在 df2 中有一个列值在 df1 中变为非空。

Python Pandas DataFrame 字典 比较

评论

0赞 RomanPerekhrest 3/10/2023
发布可测试的示例 DataFrame
0赞 Angie 3/10/2023
发布了两个测试数据帧
0赞 wwii 3/10/2023
has a column value in df2 that goes to non empty in df1- 任何列?如果 df2 中有 2 个空列,但两个列在 df1 中都有值,它是否符合您的要求?你为什么选择先把每一行变成一个字典?系列是字典式的。缺失值是空字符串还是 null 或 nan?
0赞 Angie 3/10/2023
如果 df2 中的任何列为空并在 df1 中变为非空,并且其他列与满足我要求的值匹配(所以是的,如果 df2 中有两个空列,但它们在 df1 中具有值),我不想保留此 id 的情况是,如果至少有一列从 df2 中的非空变为 df1 中的空(即使其他值匹配)或行都有值但根本不匹配
0赞 Angie 3/10/2023
缺少的值为空字符串。我把它变成了字典,因为这是我能想到的比较行的唯一方法,所以不确定是否有更好的方法来实现这一目标

答:

0赞 Driftr95 3/11/2023 #1

设置:

# import pandas as pd 

## [ can just read tables from your question with: ]
## df1, df2 = pd.read_html('https://stackoverflow.com/questions/75688651')[:2]

df1 = pd.DataFrame([{'NAME': 'Kelly', 'AGE': 15, 'CITY': 'London', 'GENDER': 'F', 'ID': 15}, {'NAME': 'Jack', 'AGE': 12, 'CITY': '', 'GENDER': 'M', 'ID': 98}, {'NAME': 'Josh', 'AGE': 30, 'CITY': 'Austin', 'GENDER': 'M', 'ID': 12}])
df2 = pd.DataFrame([{'NAME': 'Kelly', 'AGE': 15.0, 'CITY': '', 'GENDER': 'F', 'ID': 15}, {'NAME': 'Jack', 'AGE': '', 'CITY': 'Munich', 'GENDER': 'M', 'ID': 98}, {'NAME': 'Josh', 'AGE': 30.0, 'CITY': 'Austin', 'GENDER': 'M', 'ID': 12}])

有没有办法一次检查整行的这个条件/另一种写这个的方法?

您可以将 zip.iterrowsfor...其他例如:

ids = []
for (i1,df1_row),(i2,df2_row) in zip(df1.fillna('').iterrows(),df2.fillna('').iterrows()): 
    for df1_val, df2_val in zip(df1_row, df2_row): 
        if not (df1_val==df2_val or df2_val==''): break
    else: ids.append(df2_row['ID'])

[有了这个,如果有两个不相等的值,其中值不为空(并且 .fillna('') 确保所有值都替换为空字符串),则它会中断;如果它永远不会中断(即,所有值都相等具有空值),则该块将被执行并将添加到 。for...elsedf2nan''df2elseIDids

代替 ,您还可以使用 list comprehension with all 来检查每对值,就像您提到的:for...else

checkPair = lambda df1_val, df2_val: df1_val==df2_val or df2_val=='' 
ids = [df2_row['ID'] for (i_1, df1_row), (i_2, df2_row) in zip(
    df1.fillna('').iterrows(), df2.fillna('').iterrows()
) if all(checkPair(v1, v2) for v1, v2 in zip(df1_row, df2_row))]

建议的解决方案:

或者,也许有一种更好的方法可以使用这些条件将同一 ID 的 DataFrame 中的两行相互比较,而不必将行转换为字典进行比较?

是的,pandas 有 .compare 方法,我认为这对于这种情况非常方便。

comp_df = df1.set_index('ID').compare(df2.set_index('ID'), keep_shape=True)

比较示例中可以看出(比较):df1df2

  • self和列将 [分别] 包含 和 值otherdf1df2
  • 如果两者具有相同的值,并且都将包含df1df2selfothernan
    • (除非您设置 keep_equal=True
  • 因此,如果你用 填充 s ,你只需要检查值nan''other

因此,使用列表推导式和任何

ids = [i for i, r in comp_df.fillna('').iterrows() if not any(list(r!='')[1::2])]

或与:for...else

comp_df = df1.set_index('ID').compare(df2.set_index('ID'), keep_shape=True)
colNames = {c[0] for c in comp_df.columns} # --> {'GENDER', 'AGE', 'CITY', 'NAME'}

ids = []
for i, row in comp_df.fillna('').iterrows():
    for c in colNames:
        if row[c]['other'] != '': break
    else: ids.append(i)

上述 4 种方法中的任何一种都应该返回 。id[15, 12]

请注意,.compare 要求两个 DataFrame 具有完全相同的形状和标签 [对于列和索引]。[除非所有列和行也以相同的顺序排序,否则前半部分的方法将无法可靠地工作(例如:你不能让 Jack 先进入 Kelly,因为 Kelly 排在第一位)。for...zip...df2df1