代码之家  ›  专栏  ›  技术社区  ›  cs95 abhishek58g

大熊猫合并101

  •  131
  • cs95 abhishek58g  · 技术社区  · 6 年前
    • 如何执行( LEFT | RIGHT γ FULL )( INNER γ OUTER )和熊猫一起?
    • 合并后如何为缺少的行添加nan?
    • 合并后我如何摆脱奶奶?
    • 我可以合并索引吗?
    • 如何合并多个数据帧?
    • merge ? join ? concat ? update ?谁?什么?为什么?!

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

    本QNA是一系列关于熊猫习语的有用用户指南中的下一期(请参见 this post on pivoting this post on concatenation ,稍后我将讨论)。

    请注意这篇文章是 旨在取代 documentation ,所以也请阅读!其中一些例子是从那里获得的。

    1 回复  |  直到 6 年前
        1
  •  146
  •   cs95 abhishek58g    6 年前

    本文旨在向读者介绍SQL风格的与熊猫的融合,如何使用它,以及何时不使用它。

    特别是,这篇文章将介绍以下内容:

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

      • 合并不同的列名
      • 避免输出中出现重复的合并键列
    • 不同条件下与索引合并
      • 有效地使用命名索引
      • 合并键作为一个和另一个列的索引
    • 列和索引上的多路合并(唯一和非唯一)
    • 值得注意的替代方案 merge join

    这篇文章不会涉及的内容:

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

    注释
    除非另有规定,大多数示例在演示各种特性时默认为内部联接操作。

    此外,这里的所有数据帧都可以复制,因此 你可以和他们一起玩。此外,参见 this post 关于如何从剪贴板中读取数据帧。

    最后,所有连接操作的可视化表示都借用了 谢谢这篇文章 https://www.codeproject.com/Articles/33052/Visual-Representation-of-SQL-Joins .

    说够了,给我演示一下怎么用 合并 你说什么?

    安装程序

    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     value
    0   A  1.494079
    1   B -0.205158
    2   C  0.313068
    3   D -0.854096
    
    right
    
      key     value
    0   B -2.552990
    1   D  0.653619
    2   E  0.864436
    3   F -0.742165
    

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

    一个 内连接 代表为

    enter image description here

    注释
    A 这里引用的键来自 left DataFrame B 引用中联接列的键 right 数据帧和交叉点 表示两个键的公共键 左边 正确的 . 阴影区域表示联接结果中存在的键。这项公约将贯穿始终。请记住,venn图并不是连接操作的100%准确表示,因此请用少量的盐来表示它们。

    要执行内部联接,请调用 pd.merge 指定左数据帧、右数据帧和联接键。

    pd.merge(left, right, on='key')
    
      key   value_x   value_y
    0   B  0.400157  1.867558
    1   D  2.240893 -0.977278
    

    这只返回来自 左边 正确的 共享一个公共密钥(在本例中为“b”和“d”)。

    在熊猫的最新版本中(v0.21左右), 合并 现在是一阶函数,因此可以调用 DataFrame.merge .

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

    左外连接 或左联接由表示

    enter image description here

    这可以通过指定 how='left' .

    left.merge(right, on='key', how='left')
    
      key   value_x   value_y
    0   A  1.764052       NaN
    1   B  0.400157  1.867558
    2   C  0.978738       NaN
    3   D  2.240893 -0.977278
    

    请注意此处的nan位置。如果您指定 如何“左” ,然后只有来自的键 左边 已使用,并且缺少来自 正确的 被NaN取代。

    同样地,对于 右外部联接 或右联接…

    enter image description here

    …指定 how='right' :

    left.merge(right, on='key', how='right')
    
      key   value_x   value_y
    0   B  0.400157  1.867558
    1   D  2.240893 -0.977278
    2   E       NaN  0.950088
    3   F       NaN -0.151357
    

    这里,钥匙来自 正确的 已使用,并且缺少来自 左边 被NaN取代。

    最后,为了 全外部连接 ,由

    enter image description here

    指定 how='outer' .

    left.merge(right, on='key', how='outer')
    
      key   value_x   value_y
    0   A  1.764052       NaN
    1   B  0.400157  1.867558
    2   C  0.978738       NaN
    3   D  2.240893 -0.977278
    4   E       NaN  0.950088
    5   F       NaN -0.151357
    

    这将使用来自两个帧的键,并且为两个帧中丢失的行插入nan。

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

    enter image description here

    其他联接-左不包括、右不包括和完全不包括/反联接

    如果你需要 左侧不包括连接 不包括连接的权限 分两步进行。

    对于左侧,不包括连接,表示为

    enter image description here

    首先执行左外部联接,然后进行筛选(不包括!)行来自 左边 只有

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

    在哪里?

    left.merge(right, on='key', how='left', indicator=True)
    
      key   value_x   value_y     _merge
    0   A  1.764052       NaN  left_only
    1   B  0.400157  1.867558       both
    2   C  0.978738       NaN  left_only
    3   D  2.240893 -0.977278       both

    同样地,对于排除连接的权利,

    enter image description here

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

    最后,如果需要进行合并,只保留来自左侧或右侧的键,而不同时保留这两个键(iow,执行 抗连接 )

    enter image description here

    你也可以用类似的方式来做。

    (left.merge(right, on='key', how='outer', indicator=True)
         .query('_merge != "both"')
         .drop('_merge', 1))
    
      key   value_x   value_y
    0   A  1.764052       NaN
    2   C  0.978738       NaN
    4   E       NaN  0.950088
    5   F       NaN -0.151357
    

    键列的名称不同

    例如,如果键列的名称不同, 左边 keyLeft 正确的 keyRight 而不是 key _ left_on right_on 作为参数而不是 on :

    left2 = left.rename({'key':'keyLeft'}, axis=1)
    right2 = right.rename({'key':'keyRight'}, axis=1)
    
    left2
    
      keyLeft     value
    0       A  0.706573
    1       B  0.010500
    2       C  1.785870
    3       D  0.126912
    
    right2
    
      keyRight     value
    0        B  0.401989
    1        D  1.883151
    2        E -1.347759
    3        F -1.270485
    

    left2.merge(right2, left_on='keyLeft', right_on='keyRight', how='inner')
    
      keyLeft   value_x keyRight   value_y
    0       B  0.010500        B  0.401989
    1       D  0.126912        D  1.883151
    

    避免输出中出现重复的键列

    合并时 左键 左边 右键 正确的 ,如果您只需要 左键 右键 (但不是两者)在输出中,可以将索引设置为一个初步步骤。

    left3 = left2.set_index('keyLeft')
    left3.merge(right2, left_index=True, right_on='keyRight')
    
        value_x keyRight   value_y
    0  0.010500        B  0.401989
    1  0.126912        D  1.883151
    

    将其与之前的命令输出(thst是 left2.merge(right2, left_on='keyLeft', right_on='keyRight', how='inner') )你会注意到 左键 丢失。您可以根据将哪个帧的索引设置为键来确定要保留的列。例如,当执行一些外部联接操作时,这可能很重要。

    仅合并其中一列 DataFrames

    例如,考虑

    right3 = right.assign(newcol=np.arange(len(right)))
    right3
      key     value  newcol
    0   B  1.867558       0
    1   D -0.977278       1
    2   E  0.950088       2
    3   F -0.151357       3
    

    如果只需要合并“新值”(不包括任何其他列),则通常可以在合并前只对列进行子集:

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

    如果您正在执行左外部联接,则更高性能的解决方案将涉及 map :

    # left['newcol'] = left['key'].map(right3.set_index('key')['newcol']))
    left.assign(newcol=left['key'].map(right3.set_index('key')['newcol']))
    
      key     value  newcol
    0   A  1.764052     NaN
    1   B  0.400157     0.0
    2   C  0.978738     NaN
    3   D  2.240893     1.0
    

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

    left.merge(right3[['key', 'newcol']], on='key', how='left')
    
      key     value  newcol
    0   A  1.764052     NaN
    1   B  0.400157     0.0
    2   C  0.978738     NaN
    3   D  2.240893     1.0
    

    合并多列

    若要在多个列上联接,请为指定一个列表 (或) 左撇子 右上 ,视情况而定)。

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

    或者,如果名称不同,

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

    其他有用 merge* 操作和功能

    这一部分只介绍了最基本的内容,并且是为满足你的胃口而设计的。有关更多示例和案例,请参见 documentation on merge , join , and concat 以及到功能规范的链接。


    基于索引的*-联接(+索引列) 合并 s)

    安装程序

    left = pd.DataFrame({'value': np.random.randn(4)}, index=['A', 'B', 'C', 'D'])    
    right = pd.DataFrame({'value': np.random.randn(4)}, index=['B', 'D', 'E', 'F'])
    left.index.name = right.index.name = 'idxkey'
    
    left
               value
    idxkey          
    A       2.269755
    B      -1.454366
    C       0.045759
    D      -0.187184
    
    right
               value
    idxkey          
    B       1.532779
    D       1.469359
    E       0.154947
    F       0.378163
    

    通常,索引上的合并如下所示:

    left.merge(right, left_index=True, right_index=True)
    
             value_x   value_y
    idxkey                    
    B       0.410599  0.761038
    D       1.454274  0.121675
    

    支持索引名称

    如果索引是命名的,那么v0.23用户还可以将级别名称指定为 (或) 左撇子 右上 必要时)。

    left.merge(right, on='idxkey')
    
             value_x   value_y
    idxkey                    
    B       0.410599  0.761038
    D       1.454274  0.121675
    

    合并一个的索引,另一个的列

    使用一个索引和另一个列的索引来执行合并是可能的(而且非常简单)。例如,

    left.merge(right, left_on='key1', right_index=True)
    

    反之亦然(反之亦然) right_on=... left_index=True )

    right2 = right.reset_index().rename({'idxkey' : 'colkey'}, axis=1)
    right2
    
      colkey     value
    0      B  1.222445
    1      D  0.208275
    2      E  0.976639
    3      F  0.356366
    
    left.merge(right2, left_index=True, right_on='colkey')
    
        value_x colkey   value_y
    0 -1.070753      B  1.222445
    1 -0.403177      D  0.208275
    

    在这种特殊情况下, 左边 是命名的,因此您也可以将索引名用于 左撇子 ,像这样:

    left.merge(right2, left_on='idxkey', right_on='colkey')
    
        value_x colkey   value_y
    0 -1.070753      B  1.222445
    1 -0.403177      D  0.208275
    

    DataFrame.join
    除此之外,还有一个简洁的选择。你可以使用 数据流连接 默认为索引上的联接。 数据帧.join 默认情况下是左外部联接吗,因此 how='inner' 在这里是必要的。

    left.join(right, how='inner', lsuffix='_x', rsuffix='_y')
    
             value_x   value_y
    idxkey                    
    B       0.410599  0.761038
    D       1.454274  0.121675
    

    请注意,我需要指定 lsuffix rsuffix 自辩 参加 否则会出错:

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

    因为列名是相同的。如果他们的名字不同,这不会是问题。

    left.rename(columns={'value':'leftvalue'}).join(right, how='inner')
    
            leftvalue     value
    idxkey                     
    B       -1.454366  1.532779
    D       -0.187184  1.469359
    

    pd.concat
    最后,作为基于索引的联接的替代方法,可以使用 顺铂 :

    pd.concat([left, right], axis=1, sort=False, join='inner')
    
               value     value
    idxkey                    
    B      -1.980796  1.230291
    D       0.156349  1.202380
    

    省略 join='inner' 如果需要完整的外部联接(默认):

    pd.concat([left, right], axis=1, sort=False)
    
          value     value
    A -0.887786       NaN
    B -1.980796  1.230291
    C -0.347912       NaN
    D  0.156349  1.202380
    E       NaN -0.387327
    F       NaN -0.302303
    

    有关详细信息,请参阅 this canonical post on pd.concat by @piRSquared .


    概括: 合并 ing多个数据帧

    安装程序

    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]   
    

    通常,当多个数据帧合并在一起时会出现这种情况。天真地说,这可以通过链接来实现。 合并 电话:

    A.merge(B, on='key').merge(C, on='key')
    
      key    valueA    valueB  valueC
    0   D  0.922207 -1.099401     1.0
    

    然而,对于许多数据帧来说,这很快就失去了控制。此外,可能需要对未知数量的数据帧进行概括。要做到这一点,一个常用的简单技巧是 functools.reduce ,您可以使用它来实现内部联接,如下所示:

    from functools import reduce
    reduce(pd.merge, dfs)
    
      key    valueA    valueB  valueC
    0   D  0.465662  1.488252     1.0
    

    请注意,除了“键”列之外的每一列都应该以不同的名称进行命名,这样就可以开箱即用了。否则,您可能需要使用 lambda .

    对于完全外部联接,可以 curry PD.合并 使用 functools.partial :

    from functools import partial
    outer_merge = partial(pd.merge, how='outer')
    reduce(outer_merge, dfs)
    
      key    valueA    valueB  valueC
    0   A  0.056165       NaN     NaN
    1   B -1.165150 -1.536244     NaN
    2   C  0.900826       NaN     1.0
    3   D  0.465662  1.488252     1.0
    4   E       NaN  1.895889     1.0
    5   F       NaN  1.178780     NaN
    6   J       NaN       NaN     1.0
    

    正如您可能注意到的,这是非常强大的,您也可以使用它来控制合并期间的列名。只需根据需要添加更多关键字参数:

    partial(pd.merge, how='outer', left_index=True, right_on=...)
    

    备选方案: 顺铂
    如果列值是唯一的,那么使用它是有意义的 顺铂 这比一次两次多路径合并更快。

    pd.concat([
        df.set_index('key') for df in dfs], axis=1, join='inner'
    ).reset_index()
    
      key    valueA    valueB  valueC
    0   D  0.465662  1.488252     1.0
    

    唯一索引上的多路合并

    如果要在唯一索引上合并多个数据帧,则应再次选择 局部混凝土 以获得更好的性能。

    # 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(dfs2, axis=1, sort=False, join='inner')
    
           valueA    valueB  valueC
    key                            
    D    0.922207 -1.099401     1.0
    

    一如既往,省略 加入=“内部” 完全外部连接。

    具有重复项的索引的多路合并

    海螺 速度快,但也有缺点。它不能处理重复项。

    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)
    

    在这种情况下, 参加 是最佳选项,因为它可以处理非唯一索引( 参加 电话 合并 在引擎盖下面)。

    # For inner join. For left join, pass `pd.DataFrame.join` directly to `reduce`. 
    inner_join = partial(pd.DataFrame.join, how='inner')
    reduce(inner_join, [A3.set_index('key'), B2, C2])
    
           valueA    valueB  valueC
    key                            
    D   -0.674333 -1.099401     1.0
    D    0.031831 -1.099401     1.0