【问题标题】:pandas three-way joining multiple dataframes on columns熊猫三向连接列上的多个数据框
【发布时间】:2014-07-03 07:43:06
【问题描述】:

我有 3 个 CSV 文件。每个都有第一列作为人的(字符串)名称,而每个数据框中的所有其他列都是该人的属性。

如何将所有三个 CSV 文档“连接”在一起以创建单个 CSV,其中每一行都包含人员字符串名称的每个唯一值的所有属性?

pandas 中的join() 函数指定我需要一个多索引,但我对分层索引方案与基于单个索引进行连接有什么关系感到困惑。

【问题讨论】:

  • 您不需要多索引。它在连接文档中指出,当传递多个列以连接时,您没有多重索引,然后它将处理。
  • 在我的试验中,df1.join([df2, df3], on=[df2_col1, df3_col1]) 不起作用。
  • 您需要像给出的答案一样将它们链接在一起。合并 df1 和 df2 然后将结果与 df​​3 合并

标签: python pandas join merge


【解决方案1】:

假定进口:

import pandas as pd

John Galt's answer 基本上是一个reduce 操作。如果我有多个数据框,我会将它们放在这样的列表中(通过列表推导或循环等生成):

dfs = [df0, df1, df2, dfN]

假设他们有一些共同的列,例如您的示例中的name,我会执行以下操作:

df_final = reduce(lambda left,right: pd.merge(left,right,on='name'), dfs)

这样,您的代码应该适用于您要合并的任意数量的数据帧。

2016 年 8 月 1 日编辑:对于使用 Python 3 的用户:reduce 已移至 functools。因此,要使用此功能,您首先需要导入该模块:

from functools import reduce

【讨论】:

  • 我刚刚尝试使用它,但它失败了,因为reduce 被替换为functools.reduce 所以import functools functools.reduce(.......)
  • 如果我要加入的字段名称不同,此解决方案将如何工作?例如,在三个数据框中,我可以分别有name1name2name3
  • 这不是说我们有n-1 调用合并函数吗?我想在这种数据帧数量很少的情况下,这并不重要,但我想知道是否有更可扩展的解决方案。
  • 这对我的dfs 的列多索引不太有效(它正在注入“on”作为第一次合并的列,但随后的合并失败),而是我让它工作:df = reduce(lambda left, right: left.join(right, how='outer', on='Date'), dfs)
  • +1 到 ps0604。如果连接列不同怎么办,这行得通吗?如果连接列不同,我们应该使用 pd.merge 吗?谢谢
【解决方案2】:

如果你有 3 个数据框,你可以试试这个

# Merge multiple dataframes
df1 = pd.DataFrame(np.array([
    ['a', 5, 9],
    ['b', 4, 61],
    ['c', 24, 9]]),
    columns=['name', 'attr11', 'attr12'])
df2 = pd.DataFrame(np.array([
    ['a', 5, 19],
    ['b', 14, 16],
    ['c', 4, 9]]),
    columns=['name', 'attr21', 'attr22'])
df3 = pd.DataFrame(np.array([
    ['a', 15, 49],
    ['b', 4, 36],
    ['c', 14, 9]]),
    columns=['name', 'attr31', 'attr32'])

pd.merge(pd.merge(df1,df2,on='name'),df3,on='name')

或者,如 cwharland 所述

df1.merge(df2,on='name').merge(df3,on='name')

【讨论】:

  • 为了更干净的外观,您可以将它们链接起来df1.merge(df2,on='name').merge(df3,on='name')
  • 如果我要加入的字段名称不同,此解决方案将如何工作?例如,在三个数据框中,我可以分别拥有name1name2name3
  • @ps0604 df1.merge(df2,left_on='name1', right_on='name2').merge(df3,left_on='name1', right_on='name3').drop(columns=['name2', 'name3']).rename(columns={'name1':'name'})
  • 以及如何使用索引来做到这一点。如果 'name' 是索引而不是列名,则似乎不起作用。
【解决方案3】:

这是join 方法的理想情况

join 方法正是为这些类型的情况而构建的。您可以将任意数量的 DataFrame 与其连接在一起。调用 DataFrame 与传递的 DataFrame 集合的索引连接。要使用多个 DataFrame,您必须将连接列放在索引中。

代码如下所示:

filenames = ['fn1', 'fn2', 'fn3', 'fn4',....]
dfs = [pd.read_csv(filename, index_col=index_col) for filename in filenames)]
dfs[0].join(dfs[1:])

使用@zero 的数据,您可以这样做:

df1 = pd.DataFrame(np.array([
    ['a', 5, 9],
    ['b', 4, 61],
    ['c', 24, 9]]),
    columns=['name', 'attr11', 'attr12'])
df2 = pd.DataFrame(np.array([
    ['a', 5, 19],
    ['b', 14, 16],
    ['c', 4, 9]]),
    columns=['name', 'attr21', 'attr22'])
df3 = pd.DataFrame(np.array([
    ['a', 15, 49],
    ['b', 4, 36],
    ['c', 14, 9]]),
    columns=['name', 'attr31', 'attr32'])

dfs = [df1, df2, df3]
dfs = [df.set_index('name') for df in dfs]
dfs[0].join(dfs[1:])

     attr11 attr12 attr21 attr22 attr31 attr32
name                                          
a         5      9      5     19     15     49
b         4     61     14     16      4     36
c        24      9      4      9     14      9

【讨论】:

  • 将所有 dfs 加入一个空数据框也可以:pd.DataFrame().join(dfs, how="outer")。这在某些情况下可能更干净。
  • 这是一个不错的建议,现在已被纳入pandas merging 101(参见合并多个数据帧的部分)。值得注意的是,如果您的连接键是唯一的,使用pd.concat 将导致更简单的语法:pd.concat([df.set_index('name') for df in dfs], axis=1, join='inner').reset_index()concat 在处理跨多个 dfs 的重复列名时也更加通用(join 不擅长于此),尽管您只能使用它执行内部或外部联接。
  • dfs[0].join(dfs[1:]) 应编辑为dfs[0].join(dfs[1:], sort=False),否则会弹出FutureWarning。谢谢你的好例子。
  • 我在尝试时遇到错误:ValueError: Indexes have overlapping values,尽管通过检查列表中的各个数据框,它们似乎没有重叠的值。
【解决方案4】:

python 3.6.3 和pandas 0.22.0 中,您也可以使用concat,只要您将要用于连接的列设置为索引

pd.concat(
    (iDF.set_index('name') for iDF in [df1, df2, df3]),
    axis=1, join='inner'
).reset_index()

其中df1df2df3 定义为John Galt's answer

import pandas as pd
df1 = pd.DataFrame(np.array([
    ['a', 5, 9],
    ['b', 4, 61],
    ['c', 24, 9]]),
    columns=['name', 'attr11', 'attr12']
)
df2 = pd.DataFrame(np.array([
    ['a', 5, 19],
    ['b', 14, 16],
    ['c', 4, 9]]),
    columns=['name', 'attr21', 'attr22']
)
df3 = pd.DataFrame(np.array([
    ['a', 15, 49],
    ['b', 4, 36],
    ['c', 14, 9]]),
    columns=['name', 'attr31', 'attr32']
)

【讨论】:

  • 这应该是公认的答案。这是最快的。
  • 如果数据框形状不同怎么办?
  • @AbhilashRamteke 如果您的意思是它们具有不同的数量或行数(因此 name 列在所有数据帧中都不相同),那么 join='outer' 应该保留它们,但您会丢失价值观。不同的列集没有问题,只要它们都共享用于索引的name
【解决方案5】:

对于数据框列表df_list,也可以按如下方式完成:

df = df_list[0]
for df_ in df_list[1:]:
    df = df.merge(df_, on='join_col_name')

或者如果数据帧在生成器对象中(例如,为了减少内存消耗):

df = next(df_list)
for df_ in df_list:
    df = df.merge(df_, on='join_col_name')

【讨论】:

    【解决方案6】:

    简单的解决方案:

    如果列名相似:

     df1.merge(df2,on='col_name').merge(df3,on='col_name')
    

    如果列名不同:

    df1.merge(df2,left_on='col_name1', right_on='col_name2').merge(df3,left_on='col_name1', right_on='col_name3').drop(columns=['col_name2', 'col_name3']).rename(columns={'col_name1':'col_name'})
    

    【讨论】:

      【解决方案7】:

      这是一种合并数据框字典同时保持列名与字典同步的方法。如果需要,它还会填充缺失值:

      这是合并数据帧字典的函数

      def MergeDfDict(dfDict, onCols, how='outer', naFill=None):
        keys = dfDict.keys()
        for i in range(len(keys)):
          key = keys[i]
          df0 = dfDict[key]
          cols = list(df0.columns)
          valueCols = list(filter(lambda x: x not in (onCols), cols))
          df0 = df0[onCols + valueCols]
          df0.columns = onCols + [(s + '_' + key) for s in valueCols] 
      
          if (i == 0):
            outDf = df0
          else:
            outDf = pd.merge(outDf, df0, how=how, on=onCols)   
      
        if (naFill != None):
          outDf = outDf.fillna(naFill)
      
        return(outDf)
      

      好的,让我们生成数据并测试一下:

      def GenDf(size):
        df = pd.DataFrame({'categ1':np.random.choice(a=['a', 'b', 'c', 'd', 'e'], size=size, replace=True),
                            'categ2':np.random.choice(a=['A', 'B'], size=size, replace=True), 
                            'col1':np.random.uniform(low=0.0, high=100.0, size=size), 
                            'col2':np.random.uniform(low=0.0, high=100.0, size=size)
                            })
        df = df.sort_values(['categ2', 'categ1', 'col1', 'col2'])
        return(df)
      
      
      size = 5
      dfDict = {'US':GenDf(size), 'IN':GenDf(size), 'GER':GenDf(size)}   
      MergeDfDict(dfDict=dfDict, onCols=['categ1', 'categ2'], how='outer', naFill=0)
      

      【讨论】:

      • 不错的方法。请参阅下面 MergeDfDict 中的更正:keys = dfDict.keys();我 = 0;输入键:
      【解决方案8】:

      不需要多索引来执行join 操作。 只需正确设置执行连接操作的索引列(例如哪个命令df.set_index('Name')

      join 操作默认在索引上执行。 在您的情况下,您只需指定 Name 列对应于您的索引。 下面是一个例子

      tutorial 可能有用。

      # Simple example where dataframes index are the name on which to perform
      # the join operations
      import pandas as pd
      import numpy as np
      name = ['Sophia' ,'Emma' ,'Isabella' ,'Olivia' ,'Ava' ,'Emily' ,'Abigail' ,'Mia']
      df1 = pd.DataFrame(np.random.randn(8, 3), columns=['A','B','C'], index=name)
      df2 = pd.DataFrame(np.random.randn(8, 1), columns=['D'],         index=name)
      df3 = pd.DataFrame(np.random.randn(8, 2), columns=['E','F'],     index=name)
      df = df1.join(df2)
      df = df.join(df3)
      
      # If you have a 'Name' column that is not the index of your dataframe,
      # one can set this column to be the index
      # 1) Create a column 'Name' based on the previous index
      df1['Name'] = df1.index
      # 1) Select the index from column 'Name'
      df1 = df1.set_index('Name')
      
      # If indexes are different, one may have to play with parameter how
      gf1 = pd.DataFrame(np.random.randn(8, 3), columns=['A','B','C'], index=range(8))
      gf2 = pd.DataFrame(np.random.randn(8, 1), columns=['D'], index=range(2,10))
      gf3 = pd.DataFrame(np.random.randn(8, 2), columns=['E','F'], index=range(4,12))
      
      gf = gf1.join(gf2, how='outer')
      gf = gf.join(gf3, how='outer')
      

      【讨论】:

        【解决方案9】:

        pandas documentation 提供了另一种解决方案(我在这里没有看到),

        使用.append

        >>> df = pd.DataFrame([[1, 2], [3, 4]], columns=list('AB'))
           A  B
        0  1  2
        1  3  4
        >>> df2 = pd.DataFrame([[5, 6], [7, 8]], columns=list('AB'))
           A  B
        0  5  6
        1  7  8
        >>> df.append(df2, ignore_index=True)
           A  B
        0  1  2
        1  3  4
        2  5  6
        3  7  8
        

        ignore_index=True 用于忽略附加数据帧的索引,将其替换为源数据帧中可用的下一个索引。

        如果有不同的列名,会引入Nan

        【讨论】:

        • 这是语义上的,对于使用“join”这个词来表示将两个数据框放在一起的人。 (不一定是 SQL 连接操作)
        【解决方案10】:

        我调整了接受的答案,以使用 reduce 对不同的 suffix 参数执行多个数据帧的操作,我猜它也可以扩展到不同的 on 参数。

        from functools import reduce 
        
        dfs_with_suffixes = [(df2,suffix2), (df3,suffix3), 
                             (df4,suffix4)]
        
        merge_one = lambda x,y,sfx:pd.merge(x,y,on=['col1','col2'..], suffixes=sfx)
        
        merged = reduce(lambda left,right:merge_one(left,*right), dfs_with_suffixes, df1)
        

        【讨论】:

        • 调整方法很棒;但是,必须添加一个小修复以避免 ValueError: too many values to unpack (expected 2),一个 left 就足够了空字符串""。最终的合并函数如下:merge_one = lambda x,y,sfx:pd.merge(x,y,on=['col1','col2'..], suffixes=('', sfx)) # Left gets no suffix, right gets something identifiable
        猜你喜欢
        • 2014-03-14
        • 2021-07-02
        • 2023-02-10
        • 1970-01-01
        • 2019-09-09
        • 1970-01-01
        相关资源
        最近更新 更多