熊猫三向联接列上的多个数据框


191

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

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

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


2
您不需要多索引。它在连接文档中指出,当传递多个列以进行连接时,您没有多索引,则它将处理该问题。
cwharland

1
在我的试用中,df1.join([df2, df3], on=[df2_col1, df3_col1])没有用。
lollercoaster 2014年

您需要像给出的答案一样将它们链接在一起。合并DF1和DF2然后用DF3合并的结果
cwharland

Answers:


472

假设进口:

import pandas as pd

John Galt的答案基本上是一项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

11
我刚刚尝试使用它,但是失败了,因为它reduce被替换为functools.reduceSoimport functools functools.reduce(.......)
MattR

3
如果要加入的字段名称不同,此解决方案将如何工作?例如,在3个数据帧我可以有name1name2name3分别。
ps0604

2
这不是说我们已经n-1调用了merge函数吗?我想在这种情况下,数据帧的数量很小并不重要,但是我想知道是否有更可扩展的解决方案。
eapolinario

1
这对我df的具有列多索引的s 不太有效(它将“ on”作为对第一次合并有效的列进行注入,但是随后的合并失败),而是让它与以下对象一起使用:df = reduce(lambda left, right: left.join(right, how='outer', on='Date'), dfs)
Adrian Torrie

+1至ps0604。如果连接列不同,该怎么办?如果连接列不同,我们应该使用pd.merge吗?谢谢
史蒂夫

106

如果您有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')

34
为了使外观更清洁,您可以将它们链接起来 df1.merge(df2,on='name').merge(df3,on='name')
cwharland 2014年

1
如果要加入的字段名称不同,此解决方案将如何工作?例如,在三个数据帧我可以有name1name2name3分别
ps0604

4
@ ps0604df1.merge(df2,left_on='name1', right_on='name2').merge(df3,left_on='name1', right_on='name3').drop(columns=['name2', 'name3']).rename(columns={'name1':'name'})
Michael H.

进一步,如何使用索引来做到这一点。如果“名称”是索引而不是列名,则似乎不起作用。
Brian D

85

这是该join方法的理想情况

join方法正是针对这些类型的情况而构建的。您可以将任意数量的DataFrame与其一起加入。调用DataFrame与传递的DataFrames集合的索引连接。要使用多个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

4
加入所有的DFS将一个空的数据帧也可以工作:pd.DataFrame().join(dfs, how="outer")。在某些情况下这可以更清洁。
多米尼克

4
这是一个体面的建议,现在已合并到熊猫合并101中(请参阅有关合并多个数据框的部分)。值得注意的是,如果您的联接键是唯一的,则使用pd.concat会导致语法更简单:pd.concat([df.set_index('name') for df in dfs], axis=1, join='inner').reset_index()concat在多个df上处理重复的列名时,它也更加通用(join虽然不那么出色),尽管您只能对其执行内部或外部联接。
cs95

dfs[0].join(dfs[1:])应该被编辑为dfs[0].join(dfs[1:], sort=False) ,否则FutureWarning会弹出一个。感谢您的好例子。
gies0r

我在尝试该操作时遇到错误:ValueError: Indexes have overlapping values尽管通过检查列表中的各个数据框,它们似乎没有重叠的值。
SomJura

17

对于数据帧列表,也可以按以下步骤进行操作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')

11

python3.6.3和pandas0.22.0中concat,只要将要用于联接的列设置为索引,也可以使用

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

其中df1df2df3定义为John Galt的答案

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']
)

2
这应该是公认的答案。这是最快的。
朱R

4

一个并不需要一个多指标进行连接操作。只需正确设置要在其上执行联接操作的索引列(df.set_index('Name')例如,该命令)

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

教程可能是有用的。

# 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 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')

4

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

这是合并数据帧字典的功能

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)

3

简单的解决方案:

如果列名相似:

 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'})

2

pandas文档中还有另一种解决方案(我在这里看不到),

使用 .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连接操作)
Sylhare

1

这三个数据帧是

在此处输入图片说明

在此处输入图片说明

让我们使用嵌套的pd.merge合并这些框架

在此处输入图片说明

在这里,我们有了合并的数据框。

快乐的分析!!!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.