熊猫合并101

  • 我怎么能执行(INNER|(LEFT|RIGHT|FULLOUTERJOIN熊猫?
  • 如何在合并后为缺失的行添加NaN?
  • 合并后如何摆脱NaN?
  • 我可以在索引上合并吗?
  • 如何合并多个DataFrames?
  • 与熊猫的交叉连接
  • mergejoinconcatupdate?谁?什么?为什么?!

…等等。我看到这些反复出现的问题询问了熊猫合并功能的各个方面。今天,关于合并及其各种用例的大多数信息都分散在几十个措辞糟糕、无法搜索的帖子中。这里的目的是为后代整理一些更重要的观点。

这个问答是关于常见熊猫习语的一系列有用的用户指南的下一部分(参见这篇文章关于旋转这篇文章关于连接,我稍后会谈到)。

请注意,这篇文章是没有,是为了取代留档,所以也请阅读!一些例子取自那里。


目录

为了方便访问。

343503 次浏览

这篇文章的目的是给读者一个入门SQL口味的熊猫合并,如何使用它,什么时候不使用它。

特别是,这篇文章将经历以下内容:

  • 基础-连接类型(左、右、外、内)

    • 合并不同的列名
    • 与多列合并
    • 避免输出中重复的合并键列

这个帖子(以及我在这个线程上的其他帖子)不会经历什么:

  • 与绩效相关的讨论和时间安排(目前)。在适当的情况下,大多值得注意地提到更好的替代品。
  • 处理后缀、删除额外列、重命名输出和其他特定用例。还有其他(阅读:更好)的帖子可以处理这些问题,所以请弄清楚!

注释大多数示例在演示各种功能时默认为INNER JOIN操作,除非另有说明。

此外,这里的所有数据帧都可以复制和复制你可以和他们一起玩。另外,请参阅如何从剪贴板中读取DataFrames。

最后,JOIN操作的所有视觉表示都是使用Google Drawing手绘的。来自这里的灵感。



说够了-告诉我如何使用merge

设置和基础

np.random.seed(0)left = pd.DataFrame({'key': ['A', 'B', 'C', 'D'], 'value': np.random.randn(4)})right = pd.DataFrame({'key': ['B', 'D', 'E', 'F'], 'value': np.random.randn(4)})
left
key     value0   A  1.7640521   B  0.4001572   C  0.9787383   D  2.240893
right
key     value0   B  1.8675581   D -0.9772782   E  0.9500883   F -0.151357

为简单起见,键列具有相同的名称(目前)。

内连接表示为

注释这个,随着即将到来的数字都遵循这个惯例:

  • 蓝色表示合并结果中存在的行
  • 红色表示从结果中排除的行(即删除)
  • 绿色表示结果中替换为NaN的缺失值

要执行INNER JOIN,请调用左侧DataFrame上的merge,指定右侧DataFrame和连接键(至少)作为参数。

left.merge(right, on='key')# Or, if you want to be explicit# left.merge(right, on='key', how='inner')
key   value_x   value_y0   B  0.400157  1.8675581   D  2.240893 -0.977278

这仅返回leftright中共享公共键(在本例中为“B”和“D”)的行。

左外加入或LEFT JOIN表示为

这可以通过指定how='left'来执行。

left.merge(right, on='key', how='left')
key   value_x   value_y0   A  1.764052       NaN1   B  0.400157  1.8675582   C  0.978738       NaN3   D  2.240893 -0.977278

请仔细注意此处NaN的位置。如果您指定how='left',则仅使用left中的键,而right中缺少的数据将被NaN替换。

类似地,对于右外加入或右连接,这是…

…指定how='right'

left.merge(right, on='key', how='right')
key   value_x   value_y0   B  0.400157  1.8675581   D  2.240893 -0.9772782   E       NaN  0.9500883   F       NaN -0.151357

这里使用了right中的键,left中缺失的数据被NaN替换。

最后,对于完全外部连接,由

指定how='outer'

left.merge(right, on='key', how='outer')
key   value_x   value_y0   A  1.764052       NaN1   B  0.400157  1.8675582   C  0.978738       NaN3   D  2.240893 -0.9772784   E       NaN  0.9500885   F       NaN -0.151357

这使用了两个帧中的键,并且为两个帧中缺少的行插入NaN。

留档很好地总结了这些不同的合并:

在此处输入图片描述


其他连接-左排除、右排除和全排除/反连接

如果你需要左排除连接排除右连接两个步骤。

对于左排除JOIN,表示为

首先执行LEFT OUTER JOIN,然后过滤到仅来自left的行(排除右侧的所有内容),

(left.merge(right, on='key', how='left', indicator=True).query('_merge == "left_only"').drop('_merge', 1))
key   value_x  value_y0   A  1.764052      NaN2   C  0.978738      NaN

哪里,

left.merge(right, on='key', how='left', indicator=True)
key   value_x   value_y     _merge0   A  1.764052       NaN  left_only1   B  0.400157  1.867558       both2   C  0.978738       NaN  left_only3   D  2.240893 -0.977278       both

类似地,对于排除右的JOIN,

(left.merge(right, on='key', how='right', indicator=True).query('_merge == "right_only"').drop('_merge', 1))
key  value_x   value_y2   E      NaN  0.9500883   F      NaN -0.151357

最后,如果您需要执行仅保留左侧或右侧键的合并,而不是同时保留两个键(IOW,执行反加入),

你可以用类似的方式来做——

(left.merge(right, on='key', how='outer', indicator=True).query('_merge != "both"').drop('_merge', 1))
key   value_x   value_y0   A  1.764052       NaN2   C  0.978738       NaN4   E       NaN  0.9500885   F       NaN -0.151357

键列的不同名称

如果键列的命名方式不同-例如,leftkeyLeftrightkeyRight而不是key-那么您必须指定left_onright_on作为参数而不是on

left2 = left.rename({'key':'keyLeft'}, axis=1)right2 = right.rename({'key':'keyRight'}, axis=1)
left2
keyLeft     value0       A  1.7640521       B  0.4001572       C  0.9787383       D  2.240893
right2
keyRight     value0        B  1.8675581        D -0.9772782        E  0.9500883        F -0.151357
left2.merge(right2, left_on='keyLeft', right_on='keyRight', how='inner')
keyLeft   value_x keyRight   value_y0       B  0.400157        B  1.8675581       D  2.240893        D -0.977278

避免输出中重复的键列

当合并left中的keyLeftright中的keyRight时,如果您只想要输出中的keyLeftkeyRight中的任何一个(但不是两个),您可以首先将索引设置为初步步骤。

left3 = left2.set_index('keyLeft')left3.merge(right2, left_index=True, right_on='keyRight')
value_x keyRight   value_y0  0.400157        B  1.8675581  2.240893        D -0.977278

将此与之前命令的输出(即left2.merge(right2, left_on='keyLeft', right_on='keyRight', how='inner')的输出)进行对比,您会注意到keyLeft不见了。您可以根据将哪一帧的索引设置为键来确定要保留哪一列。当执行某些OUTER JOIN操作时,这可能很重要。


仅合并来自DataFrames之一的单个列

例如,考虑

right3 = right.assign(newcol=np.arange(len(right)))right3key     value  newcol0   B  1.867558       01   D -0.977278       12   E  0.950088       23   F -0.151357       3

如果您只需要合并“newcols”(没有任何其他列),您通常可以在合并之前只对列进行子集:

left.merge(right3[['key', 'newcol']], on='key')
key     value  newcol0   B  0.400157       01   D  2.240893       1

如果您正在执行LEFT OUTER JOIN,则更高性能的解决方案将涉及map

# left['newcol'] = left['key'].map(right3.set_index('key')['newcol']))left.assign(newcol=left['key'].map(right3.set_index('key')['newcol']))
key     value  newcol0   A  1.764052     NaN1   B  0.400157     0.02   C  0.978738     NaN3   D  2.240893     1.0

如前所述,这类似于,但比

left.merge(right3[['key', 'newcol']], on='key', how='left')
key     value  newcol0   A  1.764052     NaN1   B  0.400157     0.02   C  0.978738     NaN3   D  2.240893     1.0

在多个列上合并

要加入多个列,请为on(或left_onright_on,视情况而定)指定一个列表。

left.merge(right, on=['key1', 'key2'] ...)

或者,如果名字不同,

left.merge(right, left_on=['lkey1', 'lkey2'], right_on=['rkey1', 'rkey2'])

其他有用的merge*操作和功能

本节仅介绍最基本的内容,旨在满足您的需求。有关更多示例和案例,请参阅mergejoinconcat上的留档以及函数规范的链接。



继续阅读

跳转到熊猫合并101中的其他主题以继续学习:

*你在这里。

我认为你应该在你的解释中包含这一点,因为它是我经常看到的相关合并,我相信它被称为cross-join。这是当唯一的df不共享任何列时发生的合并,它只是并排合并2个dfs:

设置:

names1 = [{'A':'Jack', 'B':'Jill'}]
names2 = [{'C':'Tommy', 'D':'Tammy'}]
df1=pd.DataFrame(names1)df2=pd.DataFrame(names2)df_merged= pd.merge(df1.assign(X=1), df2.assign(X=1), on='X').drop('X', 1)

这将创建一个虚拟X列,合并到X上,然后丢弃它以生成

df_merged:

      A     B      C      D0  Jack  Jill  Tommy  Tammy

pd.concat([df0, df1], kwargs)的补充视觉视图。请注意,kwargaxis=0axis=1的含义并不像df.mean()df.apply(func)那样直观


onpd.concat([df0, df1])

在这个答案中,我将考虑以下实际例子:

  1. pandas.concat

  2. pandas.DataFrame.merge合并一个索引的数据帧和另一个索引的列。

我们将为每个案例使用不同的数据框架。


1.pandas.concat

考虑以下具有相同列名的DataFrames

  • 价格2018,大小(8784, 5)

       Year  Month  Day  Hour  Price0  2018      1    1     1   6.741  2018      1    1     2   4.742  2018      1    1     3   3.663  2018      1    1     4   2.304  2018      1    1     5   2.305  2018      1    1     6   2.066  2018      1    1     7   2.067  2018      1    1     8   2.068  2018      1    1     9   2.309  2018      1    1    10   2.30
  • 价格2019,大小(8760, 5)

       Year  Month  Day  Hour  Price0  2019      1    1     1  66.881  2019      1    1     2  66.882  2019      1    1     3  66.003  2019      1    1     4  63.644  2019      1    1     5  58.855  2019      1    1     6  55.476  2019      1    1     7  56.007  2019      1    1     8  61.098  2019      1    1     9  61.019  2019      1    1    10  61.00

可以使用pandas.concat组合它们,只需

import pandas as pd
frames = [Price2018, Price2019]
df_merged = pd.concat(frames)

这导致大小为(17544, 5)的DataFrame

如果一个人想清楚地了解发生了什么,它是这样工作的

如何工作

来源


2.pandas.DataFrame.merge

在本节中,我们将考虑一个特定的案例:合并一个数据帧的索引和另一个数据帧的列

假设一个具有54列的数据帧Geo,是Date的列之一,类型为datetime64[ns]

                 Date         1         2  ...        51        52        530 2010-01-01 00:00:00  0.565919  0.892376  ...  0.593049  0.775082  0.6806211 2010-01-01 01:00:00  0.358960  0.531418  ...  0.734619  0.480450  0.9267352 2010-01-01 02:00:00  0.531870  0.221768  ...  0.902369  0.027840  0.3988643 2010-01-01 03:00:00  0.475463  0.245810  ...  0.306405  0.645762  0.5418824 2010-01-01 04:00:00  0.954546  0.867960  ...  0.912257  0.039772  0.627696

而dataframePrice有一列价格为Price,索引对应于日期(Date

                     PriceDate2010-01-01 00:00:00  29.102010-01-01 01:00:00   9.572010-01-01 02:00:00   0.002010-01-01 03:00:00   0.002010-01-01 04:00:00   0.00

为了合并它们,可以使用pandas.DataFrame.merge如下

df_merged = pd.merge(Price, Geo, left_index=True, right_on='Date')

其中GeoPrice是前面的数据帧。

这导致了以下数据帧

   Price                Date         1  ...        51        52        530  29.10 2010-01-01 00:00:00  0.565919  ...  0.593049  0.775082  0.6806211   9.57 2010-01-01 01:00:00  0.358960  ...  0.734619  0.480450  0.9267352   0.00 2010-01-01 02:00:00  0.531870  ...  0.902369  0.027840  0.3988643   0.00 2010-01-01 03:00:00  0.475463  ...  0.306405  0.645762  0.5418824   0.00 2010-01-01 04:00:00  0.954546  ...  0.912257  0.039772  0.627696

这篇文章将经历以下主题:

  • 如何正确地泛化到多个DataFrames(以及为什么merge在这里有缺点)
  • 在唯一密钥上合并
  • 在非unqiue键上合并

返回首页



泛化到多个数据帧

通常,当多个DataFrames要合并在一起时会出现这种情况。天真地,这可以通过链接merge调用来完成:

df1.merge(df2, ...).merge(df3, ...)

然而,对于许多数据帧来说,这很快就失控了。此外,可能有必要对未知数量的数据帧进行泛化。

在这里,我为独特键上的多路连接介绍pd.concat,为非唯一键上的多路连接介绍DataFrame.join。首先,设置。

# Setup.np.random.seed(0)A = pd.DataFrame({'key': ['A', 'B', 'C', 'D'], 'valueA': np.random.randn(4)})B = pd.DataFrame({'key': ['B', 'D', 'E', 'F'], 'valueB': np.random.randn(4)})C = pd.DataFrame({'key': ['D', 'E', 'J', 'C'], 'valueC': np.ones(4)})dfs = [A, B, C]
# Note: the "key" column values are unique, so the index is unique.A2 = A.set_index('key')B2 = B.set_index('key')C2 = C.set_index('key')
dfs2 = [A2, B2, C2]

唯一密钥上的多向合并

如果您的键(在这里,键可以是列或索引)是唯一的,那么您可以使用pd.concat。请注意#0在索引上加入DataFrames

# Merge on `key` column. You'll need to set the index before concatenatingpd.concat([df.set_index('key') for df in dfs], axis=1, join='inner').reset_index()
key    valueA    valueB  valueC0   D  2.240893 -0.977278     1.0
# Merge on `key` index.pd.concat(dfs2, axis=1, sort=False, join='inner')
valueA    valueB  valueCkeyD    2.240893 -0.977278     1.0

对于FULL OUTER JOIN,省略join='inner'。请注意,您不能指定LEFT或右OUTER连接(如果您需要这些连接,请使用join,如下所述)。


重复键上的多路合并

concat速度很快,但也有缺点。它无法处理重复项。

A3 = pd.DataFrame({'key': ['A', 'B', 'C', 'D', 'D'], 'valueA': np.random.randn(5)})pd.concat([df.set_index('key') for df in [A3, B, C]], axis=1, join='inner')
ValueError: Shape of passed values is (3, 4), indices imply (3, 2)

在这种情况下,我们可以使用join,因为它可以处理非唯一键(请注意,join在其索引上加入DataFrames;除非另有说明,否则它会在引擎盖下调用merge并执行LEFT OUTER JOIN)。

# Join on `key` column. Set as the index first.# For inner join. For left join, omit the "how" argument.A.set_index('key').join([B2, C2], how='inner').reset_index()
key    valueA    valueB  valueC0   D  2.240893 -0.977278     1.0
# Join on `key` index.A3.set_index('key').join([B2, C2], how='inner')
valueA    valueB  valueCkeyD    1.454274 -0.977278     1.0D    0.761038 -0.977278     1.0


继续阅读

跳转到熊猫合并101中的其他主题以继续学习:

*你在这里

这篇文章将经历以下主题:

  • 在不同条件下与索引合并
    • 基于索引的连接选项:mergejoinconcat
    • 索引合并
    • 合并一个的索引,另一个的列
  • 有效地使用命名索引来简化合并语法

返回首页



基于索引的连接

太长别读

有几个选项,有些比其他更简单,具体取决于使用案例。

  1. #0left_indexright_index(或left_onright_on使用命名索引)
    • 支持内/左/右/满
    • 一次只能加入两个
    • 支持列-列、索引-列、索引-索引连接
  2. #0(加入索引)
    • 支持内/左(默认)/右/满
    • 可以一次加入多个数据帧
    • 支持索引-索引连接
  3. #0(加入索引)
    • 支持内部/完整(默认)
    • 可以一次加入多个数据帧
    • 支持索引-索引连接

索引到索引连接

设置和基础

import pandas as pdimport numpy as np
np.random.seed([3, 14])left = pd.DataFrame(data={'value': np.random.randn(4)},index=['A', 'B', 'C', 'D'])right = pd.DataFrame(data={'value': np.random.randn(4)},index=['B', 'D', 'E', 'F'])left.index.name = right.index.name = 'idxkey'
leftvalueidxkeyA      -0.602923B      -0.402655C       0.302329D      -0.524349
right 
valueidxkeyB       0.543843D       0.013135E      -0.326498F       1.385076

通常,索引内连接看起来像这样:

left.merge(right, left_index=True, right_index=True)
value_x   value_yidxkeyB      -0.402655  0.543843D      -0.524349  0.013135

其他连接遵循类似的语法。

重要替代品

  1. #0默认为索引上的连接。DataFrame.join默认执行LEFT OUTER JOIN,因此此处需要how='inner'

     left.join(right, how='inner', lsuffix='_x', rsuffix='_y')
    value_x   value_yidxkeyB      -0.402655  0.543843D      -0.524349  0.013135

    请注意,我需要指定lsuffixrsuffix参数,否则join会出错:

     left.join(right)ValueError: columns overlap but no suffix specified: Index(['value'], dtype='object')

    因为列名是相同的。如果它们的名称不同,这不会是一个问题。

     left.rename(columns={'value':'leftvalue'}).join(right, how='inner')
    leftvalue     valueidxkeyB       -0.402655  0.543843D       -0.524349  0.013135
  2. #0在索引上连接,可以一次连接两个或多个DataFrames。默认情况下,它会进行完整的外连接,因此此处需要how='inner'

     pd.concat([left, right], axis=1, sort=False, join='inner')
    value     valueidxkeyB      -0.402655  0.543843D      -0.524349  0.013135

    有关concat的更多信息,请参阅这篇文章


列连接的索引

要使用左索引,右列执行内连接,您将使用DataFrame.mergeleft_index=Trueright_on=...的组合。

right2 = right.reset_index().rename({'idxkey' : 'colkey'}, axis=1)right2 
colkey     value0      B  0.5438431      D  0.0131352      E -0.3264983      F  1.385076
left.merge(right2, left_index=True, right_on='colkey')
value_x colkey   value_y0 -0.402655      B  0.5438431 -0.524349      D  0.013135

其他连接遵循类似的结构。请注意,只有merge可以执行索引到列连接。您可以在多个列上进行连接,前提是左侧的索引级别数等于右侧的列数。

joinconcat不能混合合并。您需要使用DataFrame.set_index将索引设置为预步骤。


有效地使用命名索引[熊猫>=0.23]

如果您的索引已命名,则从熊猫>=0.23开始,DataFrame.merge允许您将索引名称指定为on(或根据需要指定left_onright_on)。

left.merge(right, on='idxkey')
value_x   value_yidxkeyB      -0.402655  0.543843D      -0.524349  0.013135

对于前面的合并左索引,右列的示例,您可以使用left_on与左索引名:

left.merge(right2, left_on='idxkey', right_on='colkey')
value_x colkey   value_y0 -0.402655      B  0.5438431 -0.524349      D  0.013135


继续阅读

跳转到熊猫合并101中的其他主题以继续学习:

*你在这里

加入101

这些动画可能会更好地在视觉上解释你。图片来源:Garrick Aden-Buie整理解释回购

内连接

在此处输入图片描述

外连接或全连接

在此处输入图片描述

右加入

在此处输入图片描述

左加入

在此处输入图片描述

Pandas目前不支持合并语法中的不等式连接;一个选项是使用pyjanitor中的conditional_join函数-我是这个库的贡献者:

# pip install pyjanitorimport pandas as pdimport janitor
left.conditional_join(right, ('value', 'value', '>'))
left           rightkey     value   key     value0     A  1.764052     D -0.9772781     A  1.764052     F -0.1513572     A  1.764052     E  0.9500883     B  0.400157     D -0.9772784     B  0.400157     F -0.1513575     C  0.978738     D -0.9772786     C  0.978738     F -0.1513577     C  0.978738     E  0.9500888     D  2.240893     D -0.9772789     D  2.240893     F -0.15135710    D  2.240893     E  0.95008811    D  2.240893     B  1.867558
left.conditional_join(right, ('value', 'value', '<'))
left           rightkey     value   key     value0    A  1.764052     B  1.8675581    B  0.400157     E  0.9500882    B  0.400157     B  1.8675583    C  0.978738     B  1.867558

列作为元组的变量参数传递,每个元组包括左侧数据帧中的列、右侧数据帧中的列和连接运算符,可以是(>, <, >=, <=, !=)中的任何一个。在上面的示例中,由于列名重叠,返回一个MultiIndex列。

性能方面,这比简单的交叉连接更好:

np.random.seed(0)dd = pd.DataFrame({'value':np.random.randint(100000, size=50_000)})df = pd.DataFrame({'start':np.random.randint(100000, size=1_000),'end':np.random.randint(100000, size=1_000)})
dd.head()
value0  682681  435672  426133  458914  21243
df.head()
start    end0  71915  470051  64284  449132  13377  966263  75823  386734  29151    575

%%timeitout = df.merge(dd, how='cross')out.loc[(out.start < out.value) & (out.end > out.value)]5.12 s ± 19 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit df.conditional_join(dd, ('start', 'value' ,'<'), ('end', 'value' ,'>'))280 ms ± 5.56 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit df.conditional_join(dd, ('start', 'value' ,'<'), ('end', 'value' ,'>'), use_numba=True)124 ms ± 12.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
out = df.merge(dd, how='cross')out = out.loc[(out.start < out.value) & (out.end > out.value)]A = df.conditional_join(dd, ('start', 'value' ,'<'), ('end', 'value' ,'>'))columns = A.columns.tolist()A = A.sort_values(columns, ignore_index = True)out = out.sort_values(columns, ignore_index = True)
A.equals(out)True