在 2 列上合并 pandas 数据帧,但以任意顺序

Posted

技术标签:

【中文标题】在 2 列上合并 pandas 数据帧,但以任意顺序【英文标题】:Merging pandas dataframes on 2 columns but in either order 【发布时间】:2018-11-01 17:34:36 【问题描述】:

问题:

我遇到了 2 个数据框的情况:

test1 = pd.DataFrame('id_A':['Ben', 'Julie', 'Jack', 'Jack'],
                  'id_B':['Julie', 'Ben', 'Nina', 'Julie'])

test2 = pd.DataFrame('id_a':['Ben', 'Ben', 'Ben', 'Julie', 'Julie', 'Nina'],
                      'id_b':['Julie', 'Nina', 'Jack', 'Nina', 'Jack', 'Jack'],
                      'value':[1,1,0,0,1,0])

>>> test1
    id_A   id_B
0    Ben  Julie
1  Julie    Ben
2   Jack   Nina
3   Jack  Julie

>>> test2
    id_a   id_b  value
0    Ben  Julie      1
1    Ben   Nina      1
2    Ben   Jack      0
3  Julie   Nina      0
4  Julie   Jack      1
5   Nina   Jack      0

我想做的是将test2test1 合并,其中id_A == id_aid_B == id_b OR 其中id_A == id_bid_B == id_a,产生以下数据框:

>>> final_df
    id_A   id_B  value
0    Ben  Julie      1
1  Julie    Ben      1
2   Jack   Nina      0
3   Jack  Julie      1

当前解决方案:

我的解决方案有效,但看起来很混乱,我想看看我是否忽略了一些更聪明的做事方式。它涉及将 test2 与其自身连接,但将感兴趣的 2 列反转(id_a 变为 id_b,反之亦然),然后从那里合并。

test3 = pd.concat([test2, test2.rename(columns = 'id_a':'id_b', 'id_b':'id_a')])

final_df = (test1.merge(test3, left_on = ['id_A', 'id_B'],
                        right_on = ['id_a', 'id_b'])
            .drop(['id_a', 'id_b'], axis=1))

问题:

有没有人知道一个更简洁的方法来做到这一点?我觉得我可能忽略了一些令人惊奇的、讨人喜欢的做事方式。

感谢您的帮助!

【问题讨论】:

【参考方案1】:

你可以试试np.sort

test1.assign(key=pd.DataFrame(np.sort(test1.values,axis=1)).sum(1)).merge(test2.assign(key=pd.DataFrame(np.sort(test2[['id_a','id_b']].values,axis=1)).sum(1))).drop('key',1)
Out[188]: 
    id_A   id_B   id_a   id_b  value
0    Ben  Julie    Ben  Julie      1
1  Julie    Ben    Ben  Julie      1
2   Jack   Nina   Nina   Jack      0
3   Jack  Julie  Julie   Jack      1

【讨论】:

这很聪明!我想我正在尝试做的事情没有神奇的pandas 功能:( @sacul 我不认为熊猫可以做到这一点,因为它需要重建数据框(列侧)以进行合并。【参考方案2】:

你可以做两个内连接,然后连接和去重复,比如:

merge_1 = test1.merge(test2, left_on = ['id_A', 'id_B'], right_on= ['id_a', 'id_b'])
merge_2 = test1.merge(test2, left_on = ['id_A', 'id_B'], right_on= ['id_b', 'id_a'])
final_df = pd.concat([merge_1, merge_2]).drop_duplicates()

或者您可以进行外部联接并手动计算条件:

final_df = test1.merge(test2, how='outer')
final_df = final_df[((final_df.id_A == final_df.id_a) &
                     (final_df.id_B == final_df.id_b)) |
                    ((final_df.id_A == final_df.id_b) &
                     (final_df.id_B == final_df.id_a))]

或者您可以创建一个始终按已知顺序连接的键:

test1['join_key'] = test1.apply(lambda row: tuple(sorted(row[['id_A', 'id_B']])), axis=1)
test2['join_key'] = test2.apply(lambda row: tuple(sorted(row[['id_a', 'id_b']])), axis=1)
final_df = test1.merge(test2, on='join_key').drop('join_key')

【讨论】:

是的,两次合并方法是我的第一次尝试,它也很有效。感谢您的选择!【参考方案3】:

frozenset

test1.assign(
    value=test1.apply(frozenset, 1).map(frozenset(a): b for *a, b in test2.values))

    id_A   id_B  value
0    Ben  Julie      1
1  Julie    Ben      1
2   Jack   Nina      0
3   Jack  Julie      1

不那么可爱,更健壮。之后删除您需要的内容。

t1 = test1.assign(ref=list(map(frozenset, zip(test1.id_A, test1.id_B))))
t2 = test2.assign(ref=list(map(frozenset, zip(test2.id_a, test2.id_b))))

t1.merge(t2, on='ref')

    id_A   id_B            ref   id_a   id_b  value
0    Ben  Julie   (Julie, Ben)    Ben  Julie      1
1  Julie    Ben   (Julie, Ben)    Ben  Julie      1
2   Jack   Nina   (Jack, Nina)   Nina   Jack      0
3   Jack  Julie  (Jack, Julie)  Julie   Jack      1

【讨论】:

以上是关于在 2 列上合并 pandas 数据帧,但以任意顺序的主要内容,如果未能解决你的问题,请参考以下文章

在多索引列上合并pandas数据帧

合并两个 Pandas 数据帧,在一个时间列上重新采样,插值

在 Pandas 中合并索引上的数据帧更有效

熊猫合并:合并同一列上的两个数据框,但保留不同的列

将大型 Dask 数据框与小型 Pandas 数据框合并

使用 Pandas query() 过滤时间戳列上的数据帧