pandy trójdrożne łączenie wielu ramek danych na kolumnach

191

Mam 3 pliki CSV. Każda z nich ma pierwszą kolumnę jako (ciąg) nazwisk osób, podczas gdy wszystkie pozostałe kolumny w każdej ramce danych są atrybutami tej osoby.

Jak mogę „połączyć” razem wszystkie trzy dokumenty CSV, aby utworzyć pojedynczy plik CSV, w którym każdy wiersz ma wszystkie atrybuty dla każdej unikalnej wartości nazwy ciągu danej osoby?

join()Funkcja w Pandy Określa, że muszę multiindex, ale jestem mylić o co hierarchiczny system indeksujący ma do czynienia z podejmowania przystąpić na podstawie pojedynczego wskaźnika.

Lollercoaster
źródło
2
Nie potrzebujesz multiindeksu. W dokumentach dołączania stwierdza, że ​​nie masz multiindeksu, gdy przekazujesz wiele kolumn do przyłączenia, wtedy to obsłuży.
cwharland
1
W moich próbach df1.join([df2, df3], on=[df2_col1, df3_col1])nie działało.
Lollercoaster
Musisz połączyć je razem, jak w podanej odpowiedzi. Scal df1 i df2, a następnie połącz wynik z df3
cwharland

Odpowiedzi:

474

Zakładany import:

import pandas as pd

Odpowiedź Johna Galta jest w zasadzie reduceoperacją. Jeśli mam więcej niż garść ramek danych, umieściłbym je na takiej liście (wygenerowanej za pomocą wyrażeń lub pętli listowych itp.):

dfs = [df0, df1, df2, dfN]

Zakładając, że mają one wspólną kolumnę, jak namew twoim przykładzie, zrobiłbym następujące:

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

W ten sposób kod powinien działać z dowolną liczbą ramek danych, które chcesz scalić.

Edytuj 1 sierpnia 2016 r . : Dla osób używających Python 3: reducezostał przeniesiony do functools. Aby użyć tej funkcji, musisz najpierw zaimportować ten moduł:

from functools import reduce
Zestaw
źródło
11
Właśnie próbowałem tego użyć i nie udało się, ponieważ reducezostał zastąpiony przez functools.reduceSoimport functools functools.reduce(.......)
MattR
3
Jak to rozwiązanie będzie działać, jeśli nazwy pól do przyłączenia będą różne? Na przykład, w trzech ramek danych mogę mieć name1, name2i name3odpowiednio.
ps0604
2
Czy to nie znaczy, że mamy n-1wywołania funkcji scalania? Wydaje mi się, że w tym przypadku, gdy liczba ramek danych jest niewielka, nie ma to znaczenia, ale zastanawiam się, czy istnieje bardziej skalowalne rozwiązanie.
eapolinario
1
Nie działało to całkiem dobrze dla moich dfindeksów kolumnowych (wstawiał „on” jako kolumnę, która działała dla pierwszego scalenia, ale kolejne scalenia nie powiodły się), zamiast tego udało mi się pracować z:df = reduce(lambda left, right: left.join(right, how='outer', on='Date'), dfs)
Adrian Torrie
+1 do ps0604. co jeśli kolumny łączenia są różne, czy to działa? czy powinniśmy stosować pd.merge, ponieważ kolumny łączenia są inne? dzięki
Steve
106

Możesz spróbować, jeśli masz 3 ramki danych

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

alternatywnie, jak wspomniano przez cwharland

df1.merge(df2,on='name').merge(df3,on='name')
Zero
źródło
34
Aby uzyskać czystszy wygląd, możesz je df1.merge(df2,on='name').merge(df3,on='name')
połączyć
1
Jak to rozwiązanie będzie działać, jeśli nazwy pól do przyłączenia będą różne? Na przykład w trzech ramkach danych mógłbym mieć name1, name2i name3odpowiednio
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.
i dalej, jak to zrobić za pomocą indeksu. Nie działa, jeśli „nazwa” jest indeksem, a nie nazwą kolumny.
Brian D
85

Jest to idealna sytuacja dla tej joinmetody

joinMetoda jest zbudowana właśnie dla takich sytuacjach. Możesz dołączyć do niego dowolną liczbę ramek danych. Wywoływająca DataFrame łączy się z indeksem kolekcji przekazanych DataFrames. Aby pracować z wieloma ramkami danych, należy umieścić kolumny łączące w indeksie.

Kod wyglądałby mniej więcej tak:

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

Z danymi @ zero możesz to zrobić:

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
Ted Petrou
źródło
4
Dołączenie wszystkich DFS do pustej dataframe również działa: pd.DataFrame().join(dfs, how="outer"). W niektórych sytuacjach może to być czystsze.
Dominik
4
Jest to przyzwoita rada i została teraz włączona do pand łączących 101 (patrz sekcja na temat łączenia wielu ramek danych). Warto zauważyć, że jeśli Twój klucze są unikalne, używając pd.concatspowoduje prostszej składni: pd.concat([df.set_index('name') for df in dfs], axis=1, join='inner').reset_index(). concatjest również bardziej wszechstronny, gdy ma do czynienia ze zduplikowanymi nazwami kolumn w wielu plikach dfs ( joinnie jest w tym tak dobry), chociaż można za jego pomocą wykonywać tylko połączenia wewnętrzne lub zewnętrzne.
cs95
dfs[0].join(dfs[1:])należy edytować, dfs[0].join(dfs[1:], sort=False) ponieważ w przeciwnym razie FutureWarningpojawi się testament. Dzięki za miły przykład.
gies0r
ValueError: Indexes have overlapping valuesWystępuje błąd przy próbie, że: chociaż po sprawdzeniu poszczególnych ramek danych na liście nie wydają się one nakładać na siebie wartości.
SomJura
17

Można to również zrobić w następujący sposób dla listy ramek danych df_list:

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

lub jeśli ramki danych znajdują się w obiekcie generatora (np. w celu zmniejszenia zużycia pamięci):

df = next(df_list)
for df_ in df_list:
    df = df.merge(df_, on='join_col_name')
AlexG
źródło
11

W wersji python3.6.3 z pandas0.22.0 możesz także używać concattak długo, jak ustawisz jako indeks kolumny, których chcesz użyć do łączenia

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

gdzie df1, df2i df3są zdefiniowane jak w odpowiedzi Johna Galta

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']
)
Igor Fobia
źródło
2
To powinna być zaakceptowana odpowiedź. To najszybszy.
R. Zhu
4

Do wykonywania operacji łączenia nie jest potrzebny multiindeks . Trzeba tylko poprawnie ustawić kolumnę indeksu, na której mają być wykonywane operacje łączenia (które polecenie df.set_index('Name')na przykład)

joinOperacja jest domyślnie wykonywane na indeksie. W twoim przypadku wystarczy określić, że Namekolumna odpowiada Twojemu indeksowi. Poniżej znajduje się przykład

Poradnik mogą być użyteczne.

# 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')
Guillaume Jacquenot
źródło
4

Oto metoda scalenia słownika ramek danych przy jednoczesnym zachowaniu synchronizacji nazw kolumn ze słownikiem. W razie potrzeby uzupełnia brakujące wartości:

Jest to funkcja scalania dict ramek danych

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)

OK, pozwala generować dane i przetestować to:

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)
rz1317
źródło
3

Proste rozwiązanie:

Jeśli nazwy kolumn są podobne:

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

Jeśli nazwy kolumn są różne:

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'})
Gil Baggio
źródło
2

Istnieje inne rozwiązanie z dokumentacji pand (której tu nie widzę),

używając .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=TrueSłuży do ignorowania indeks załączonym dataframe, zastępując ją do następnego indeksu dostępnych w jedno źródło.

Jeśli istnieją różne nazwy kolumn, Nanzostaną wprowadzone.

Sylhare
źródło
jest semantyczny, dla kogoś, kto używa słowa „dołącz”, aby powiedzieć, że zestawia dwie ramki danych. (niekoniecznie jako operacja łączenia SQL)
Sylhare
1

Trzy ramki danych to

wprowadź opis zdjęcia tutaj

wprowadź opis zdjęcia tutaj

Połączmy te ramki za pomocą zagnieżdżonego pd.merge

wprowadź opis zdjęcia tutaj

Proszę bardzo, mamy scaloną ramkę danych.

Szczęśliwa analiza !!!

decyzja_naukowiec_nie
źródło