Odpowiadając sobie na to pytanie, nauczyłem się wielu rzeczy i chciałem zebrać katalog przykładów i wyjaśnienia.
Konkretna odpowiedź do punktu levels sporu nadejdzie na końcu.
pandas.concat: Brakujący podręcznik
Link do aktualnej dokumentacji
Importuje i definiuje obiekty
import pandas as pd
d1 = pd.DataFrame(dict(A=.1, B=.2, C=.3), index=[2, 3])
d2 = pd.DataFrame(dict(B=.4, C=.5, D=.6), index=[1, 2])
d3 = pd.DataFrame(dict(A=.7, B=.8, D=.9), index=[1, 3])
s1 = pd.Series([1, 2], index=[2, 3])
s2 = pd.Series([3, 4], index=[1, 2])
s3 = pd.Series([5, 6], index=[1, 3])
Argumenty
objs
Pierwszy argument, z którym się spotykamy, to objs:
objs : sekwencja lub mapowanie obiektów Series, DataFrame lub Panel Jeśli dict jest przekazany, posortowane klucze będą używane jako argument keys, chyba że zostanie przekazany, w którym to przypadku wartości zostaną wybrane (patrz poniżej). Wszystkie obiekty None zostaną po cichu porzucone, chyba że wszystkie są None, w którym to przypadku zostanie zgłoszony błąd ValueError
- Zwykle widzimy, że jest to używane z listą obiektów
Serieslub DataFrame.
- Pokażę, że to też
dictmoże być bardzo przydatne.
- Generatory mogą być również stosowane i mogą być przydatne podczas używania
map, jak wmap(f, list_of_df)
Na razie będziemy trzymać się listy niektórych obiektów DataFramei Serieszdefiniowanych powyżej. Pokażę MultiIndexpóźniej, jak można wykorzystać słowniki, aby uzyskać bardzo przydatne wyniki.
pd.concat([d1, d2])
A B C D
2 0.1 0.2 0.3 NaN
3 0.1 0.2 0.3 NaN
1 NaN 0.4 0.5 0.6
2 NaN 0.4 0.5 0.6
axis
Drugi argument, który napotkamy, to axiswartość domyślna 0:
oś : {0 / 'indeks', 1 / 'kolumny'}, domyślnie 0 Oś do połączenia.
Dwa DataFramezaxis=0 (ułożone)
Dla wartości 0lubindex mamy na myśli: „Wyrównaj wzdłuż kolumn i dodaj do indeksu”.
Jak pokazano powyżej, gdzie użyliśmy axis=0, ponieważ 0jest to wartość domyślna, i widzimy, że indeks d2rozszerza indeks o d1pomimo nakładania się wartości 2:
pd.concat([d1, d2], axis=0)
A B C D
2 0.1 0.2 0.3 NaN
3 0.1 0.2 0.3 NaN
1 NaN 0.4 0.5 0.6
2 NaN 0.4 0.5 0.6
Dwa DataFramez axis=1(obok siebie)
W przypadku wartości 1lub columnsmamy na myśli: „Wyrównaj wzdłuż indeksu i dodaj do kolumn”,
pd.concat([d1, d2], axis=1)
A B C B C D
1 NaN NaN NaN 0.4 0.5 0.6
2 0.1 0.2 0.3 0.4 0.5 0.6
3 0.1 0.2 0.3 NaN NaN NaN
Widzimy, że wynikowy indeks jest sumą indeksów, a wynikowe kolumny są rozszerzeniem kolumn z d1przez kolumny d2.
Dwa (lub trzy) Seriesz axis=0(ułożone)
Przy łączeniu pandas.Seriesrazem axis=0, otrzymujemy z powrotem pandas.Series. Nazwa wynikowa Seriesbędzie, Nonechyba że wszystkie Seriespołączone mają tę samą nazwę. Zwróć uwagę na to, 'Name: A'kiedy drukujemy wynik Series. Kiedy go nie ma, możemy założyć, że Seriestak None.
| | | pd.concat(
| pd.concat( | pd.concat( | [s1.rename('A'),
pd.concat( | [s1.rename('A'), | [s1.rename('A'), | s2.rename('B'),
[s1, s2]) | s2]) | s2.rename('A')]) | s3.rename('A')])
-------------- | --------------------- | ---------------------- | ----------------------
2 1 | 2 1 | 2 1 | 2 1
3 2 | 3 2 | 3 2 | 3 2
1 3 | 1 3 | 1 3 | 1 3
2 4 | 2 4 | 2 4 | 2 4
dtype: int64 | dtype: int64 | Name: A, dtype: int64 | 1 5
| | | 3 6
| | | dtype: int64
Dwa (lub trzy) Serieszaxis=1 (obok siebie)
Podczas łączenia pandas.Serieswzdłuż axis=1jest to nameatrybut, do którego odwołujemy się, aby wywnioskować nazwę kolumny w wyniku pandas.DataFrame.
| | pd.concat(
| pd.concat( | [s1.rename('X'),
pd.concat( | [s1.rename('X'), | s2.rename('Y'),
[s1, s2], axis=1) | s2], axis=1) | s3.rename('Z')], axis=1)
---------------------- | --------------------- | ------------------------------
0 1 | X 0 | X Y Z
1 NaN 3.0 | 1 NaN 3.0 | 1 NaN 3.0 5.0
2 1.0 4.0 | 2 1.0 4.0 | 2 1.0 4.0 NaN
3 2.0 NaN | 3 2.0 NaN | 3 2.0 NaN 6.0
Miesza się Seriesi DataFramez axis=0(w stos)
Wykonując konkatenację a Seriesi DataFramewzdłuż axis=0, konwertujemy wszystko Seriesna pojedyncze kolumny DataFrame.
Zwróć szczególną uwagę, że jest to konkatenacja axis=0; oznacza to rozszerzenie indeksu (wierszy) podczas wyrównywania kolumn. W poniższych przykładach widzimy, że indeks staje się, [2, 3, 2, 3]który jest masowym dołączaniem indeksów. Kolumny nie nakładają się, chyba że wymuszę nazewnictwo Serieskolumny z argumentem to_frame:
pd.concat( |
[s1.to_frame(), d1]) | pd.concat([s1, d1])
------------------------- | ---------------------
0 A B C | 0 A B C
2 1.0 NaN NaN NaN | 2 1.0 NaN NaN NaN
3 2.0 NaN NaN NaN | 3 2.0 NaN NaN NaN
2 NaN 0.1 0.2 0.3 | 2 NaN 0.1 0.2 0.3
3 NaN 0.1 0.2 0.3 | 3 NaN 0.1 0.2 0.3
Możesz zobaczyć, że wyniki pd.concat([s1, d1])są takie same, jak gdybym wykonałto_frame siebie.
Jednak mogę kontrolować nazwę kolumny wynikowej za pomocą parametru do to_frame. Zmiana nazwy za Seriespomocą renamemetody nie kontroluje nazwy kolumny w wynikowym DataFrame.
pd.concat( | pd.concat( | pd.concat(
[s1.to_frame('X'), d1]) | [s1.rename('X'), d1]) | [s1.to_frame('B'), d1])
---------------------------- | -------------------------- | ----------------------------
A B C X | 0 A B C | A B C
2 NaN NaN NaN 1.0 | 2 1.0 NaN NaN NaN | 2 NaN 1.0 NaN
3 NaN NaN NaN 2.0 | 3 2.0 NaN NaN NaN | 3 NaN 2.0 NaN
2 0.1 0.2 0.3 NaN | 2 NaN 0.1 0.2 0.3 | 2 0.1 0.2 0.3
3 0.1 0.2 0.3 NaN | 3 NaN 0.1 0.2 0.3 | 3 0.1 0.2 0.3
Mieszane Seriesi DataFramez axis=1(obok siebie)
Jest to dość intuicyjne. Seriesnazwa kolumny domyślnie zawiera wyliczenie takich Seriesobiektów, gdy nameatrybut nie jest dostępny.
| pd.concat(
pd.concat( | [s1.rename('X'),
[s1, d1], | s2, s3, d1],
axis=1) | axis=1)
------------------- | -------------------------------
0 A B C | X 0 1 A B C
2 1 0.1 0.2 0.3 | 1 NaN 3.0 5.0 NaN NaN NaN
3 2 0.1 0.2 0.3 | 2 1.0 4.0 NaN 0.1 0.2 0.3
| 3 2.0 NaN 6.0 0.1 0.2 0.3
join
Trzeci argument joinopisuje, czy wynikowe scalenie powinno być scaleniem zewnętrznym (domyślne), czy scaleniem wewnętrznym.
join : {'internal', 'external'}, default 'external'
Jak obsługiwać indeksy na innych osiach.
Okazuje się, że nie ma leftani rightopcji jakpd.concat może obsłużyć więcej niż tylko dwa obiekty do scalenia.
W przypadku d1i d2opcje wyglądają następująco:
outer
pd.concat([d1, d2], axis=1, join='outer')
A B C B C D
1 NaN NaN NaN 0.4 0.5 0.6
2 0.1 0.2 0.3 0.4 0.5 0.6
3 0.1 0.2 0.3 NaN NaN NaN
inner
pd.concat([d1, d2], axis=1, join='inner')
A B C B C D
2 0.1 0.2 0.3 0.4 0.5 0.6
join_axes
Czwarty argument to rzecz, która pozwala nam dokonać leftpołączenia i nie tylko.
join_axes : lista obiektów indeksu
Określone indeksy do użycia dla pozostałych n - 1 osi zamiast wykonywania wewnętrznej / zewnętrznej logiki zbioru.
Lewe scalanie
pd.concat([d1, d2, d3], axis=1, join_axes=[d1.index])
A B C B C D A B D
2 0.1 0.2 0.3 0.4 0.5 0.6 NaN NaN NaN
3 0.1 0.2 0.3 NaN NaN NaN 0.7 0.8 0.9
Right Merge
pd.concat([d1, d2, d3], axis=1, join_axes=[d3.index])
A B C B C D A B D
1 NaN NaN NaN 0.4 0.5 0.6 0.7 0.8 0.9
3 0.1 0.2 0.3 NaN NaN NaN 0.7 0.8 0.9
ignore_index
ignore_index : boolean, default False
Jeśli True, nie używaj wartości indeksu wzdłuż osi konkatenacji. Wynikowa oś będzie oznaczona jako 0, ..., n - 1. Jest to przydatne, jeśli łączysz obiekty, w których oś konkatenacji nie ma znaczących informacji o indeksowaniu. Zwróć uwagę, że wartości indeksu na innych osiach są nadal przestrzegane w połączeniu.
Na przykład, gdy układam się d1na wierzchu d2, jeśli nie dbam o wartości indeksu, mogę je zresetować lub zignorować.
| pd.concat( | pd.concat(
| [d1, d2], | [d1, d2]
pd.concat([d1, d2]) | ignore_index=True) | ).reset_index(drop=True)
--------------------- | ----------------------- | -------------------------
A B C D | A B C D | A B C D
2 0.1 0.2 0.3 NaN | 0 0.1 0.2 0.3 NaN | 0 0.1 0.2 0.3 NaN
3 0.1 0.2 0.3 NaN | 1 0.1 0.2 0.3 NaN | 1 0.1 0.2 0.3 NaN
1 NaN 0.4 0.5 0.6 | 2 NaN 0.4 0.5 0.6 | 2 NaN 0.4 0.5 0.6
2 NaN 0.4 0.5 0.6 | 3 NaN 0.4 0.5 0.6 | 3 NaN 0.4 0.5 0.6
A gdy używasz axis=1:
| pd.concat(
| [d1, d2], axis=1,
pd.concat([d1, d2], axis=1) | ignore_index=True)
------------------------------- | -------------------------------
A B C B C D | 0 1 2 3 4 5
1 NaN NaN NaN 0.4 0.5 0.6 | 1 NaN NaN NaN 0.4 0.5 0.6
2 0.1 0.2 0.3 0.4 0.5 0.6 | 2 0.1 0.2 0.3 0.4 0.5 0.6
3 0.1 0.2 0.3 NaN NaN NaN | 3 0.1 0.2 0.3 NaN NaN NaN
keys
Możemy przekazać listę wartości skalarnych lub krotek w celu przypisania krotek lub wartości skalarnych do odpowiedniego MultiIndexu. Długość przekazanej listy musi być taka sama, jak liczba łączonych elementów.
klucze : sekwencja, wartość domyślna Brak
Jeśli minęło wiele poziomów, powinno zawierać krotki. Skonstruuj indeks hierarchiczny, używając przekazanych kluczy jako najbardziej zewnętrznego poziomu
axis=0
Podczas łączenia Seriesobiektów wzdłużaxis=0 (rozszerzania indeksu).
Te klucze stają się nowym początkowym poziomem MultiIndexobiektu w atrybucie indeksu.
pd.concat([s1, s2, s3], keys=['A', 'B', 'C']) pd.concat([s1, s2], keys=['A', 'B'])
---------------------------------------------- -------------------------------------
A 2 1 A 2 1
3 2 3 2
B 1 3 B 1 3
2 4 2 4
C 1 5 dtype: int64
3 6
dtype: int64
Możemy jednak użyć więcej niż wartości skalarnych w keysargumencie, aby stworzyć jeszcze głębszą MultiIndex. Tutaj przekazujemy tuplesdługość 2 przed dodaniem dwóch nowych poziomów a MultiIndex:
pd.concat(
[s1, s2, s3],
keys=[('A', 'X'), ('A', 'Y'), ('B', 'X')])
-----------------------------------------------
A X 2 1
3 2
Y 1 3
2 4
B X 1 5
3 6
dtype: int64
axis=1
Trochę inaczej wygląda to w przypadku rozciągania wzdłuż kolumn. Kiedy używaliśmy axis=0(patrz powyżej), nasz keysdziałał jako MultiIndexpoziomy oprócz istniejącego indeksu. Ponieważ axis=1odnosimy się do osi, której Seriesobiekty nie mają, a mianowicie do columnsatrybutu.
Wariacje dwóch
Serieswtih
axis=1
Zauważ, że nazywanie s1i ma s2znaczenie, o ile nie keyssą przekazywane, ale jest zastępowane, jeśli keyssą przekazywane.
| | | pd.concat(
| pd.concat( | pd.concat( | [s1.rename('U'),
pd.concat( | [s1, s2], | [s1.rename('U'), | s2.rename('V')],
[s1, s2], | axis=1, | s2.rename('V')], | axis=1,
axis=1) | keys=['X', 'Y']) | axis=1) | keys=['X', 'Y'])
-------------- | --------------------- | ---------------------- | ----------------------
0 1 | X Y | U V | X Y
1 NaN 3.0 | 1 NaN 3.0 | 1 NaN 3.0 | 1 NaN 3.0
2 1.0 4.0 | 2 1.0 4.0 | 2 1.0 4.0 | 2 1.0 4.0
3 2.0 NaN | 3 2.0 NaN | 3 2.0 NaN | 3 2.0 NaN
MultiIndexz
Seriesi
axis=1
pd.concat(
[s1, s2],
axis=1,
keys=[('W', 'X'), ('W', 'Y')])
-----------------------------------
W
X Y
1 NaN 3.0
2 1.0 4.0
3 2.0 NaN
Dwa
DataFramez
axis=1
Podobnie jak w axis=0przykładach, keysdodaj poziomy do a MultiIndex, ale tym razem do obiektu przechowywanego w columnsatrybucie.
pd.concat( | pd.concat(
[d1, d2], | [d1, d2],
axis=1, | axis=1,
keys=['X', 'Y']) | keys=[('First', 'X'), ('Second', 'X')])
------------------------------- | --------------------------------------------
X Y | First Second
A B C B C D | X X
1 NaN NaN NaN 0.4 0.5 0.6 | A B C B C D
2 0.1 0.2 0.3 0.4 0.5 0.6 | 1 NaN NaN NaN 0.4 0.5 0.6
3 0.1 0.2 0.3 NaN NaN NaN | 2 0.1 0.2 0.3 0.4 0.5 0.6
| 3 0.1 0.2 0.3 NaN NaN NaN
Seriesi
DataFramez
axis=1
To jest trudne. W takim przypadku wartość klucza skalarnego nie może działać jako jedyny poziom indeksu dla Seriesobiektu, gdy staje się on kolumną, a jednocześnie działa jako pierwszy poziom a MultiIndexdla DataFrame. Pandy ponownie użyją nameatrybutu Seriesobiektu jako źródła nazwy kolumny.
pd.concat( | pd.concat(
[s1, d1], | [s1.rename('Z'), d1],
axis=1, | axis=1,
keys=['X', 'Y']) | keys=['X', 'Y'])
--------------------- | --------------------------
X Y | X Y
0 A B C | Z A B C
2 1 0.1 0.2 0.3 | 2 1 0.1 0.2 0.3
3 2 0.1 0.2 0.3 | 3 2 0.1 0.2 0.3
Ograniczenia
keysi
MultiIndexwnioskowanie.
Wydaje się, że pandy tylko wydają się wywnioskować nazwy kolumn z Seriesnazwy, ale nie będą wypełniać luk podczas wykonywania analogicznej konkatenacji między ramkami danych o różnej liczbie poziomów kolumn.
d1_ = pd.concat(
[d1], axis=1,
keys=['One'])
d1_
One
A B C
2 0.1 0.2 0.3
3 0.1 0.2 0.3
Następnie połącz to z inną ramką danych z tylko jednym poziomem w obiekcie kolumny, a Pandy odmówią próby stworzenia krotek MultiIndexobiektu i połączenia wszystkich ramek danych, jakby to był pojedynczy poziom obiektów, skalarów i krotek.
pd.concat([d1_, d2], axis=1)
(One, A) (One, B) (One, C) B C D
1 NaN NaN NaN 0.4 0.5 0.6
2 0.1 0.2 0.3 0.4 0.5 0.6
3 0.1 0.2 0.3 NaN NaN NaN
Przekazywanie dictzamiast alist
Podczas przekazywania słownika pandas.concatużyje kluczy ze słownika jako keysparametru.
pd.concat( | pd.concat(
{0: d1, 1: d2}) | {0: d1, 1: d2}, axis=1)
----------------------- | -------------------------------
A B C D | 0 1
0 2 0.1 0.2 0.3 NaN | A B C B C D
3 0.1 0.2 0.3 NaN | 1 NaN NaN NaN 0.4 0.5 0.6
1 1 NaN 0.4 0.5 0.6 | 2 0.1 0.2 0.3 0.4 0.5 0.6
2 NaN 0.4 0.5 0.6 | 3 0.1 0.2 0.3 NaN NaN NaN
levels
Jest to używane w połączeniu z keysargumentem. levelsGdy pozostawi się wartość domyślną None, Pandy przyjmą unikalne wartości każdego poziomu wyniku MultiIndexi wykorzystają je jako obiekt używany w wynikowym index.levelsatrybucie.
poziomy : lista sekwencji, wartość domyślna Brak
Określone poziomy (unikalne wartości) do użycia przy konstruowaniu MultiIndex. W przeciwnym razie zostaną wywnioskowane z kluczy.
Jeśli Pandy już wywnioskują, jakie powinny być te poziomy, jaką korzyść daje samodzielne określenie tego? Pokażę jeden przykład i pozostawię ci wymyślenie innych powodów, dla których może to być przydatne.
Przykład
Zgodnie z dokumentacją levelsargumentem jest lista sekwencji. Oznacza to, że możemy użyć innej pandas.Indexjako jednej z tych sekwencji.
Rozważmy ramki danych df, który jest połączeniem d1, d2i d3:
df = pd.concat(
[d1, d2, d3], axis=1,
keys=['First', 'Second', 'Fourth'])
df
First Second Fourth
A B C B C D A B D
1 NaN NaN NaN 0.4 0.5 0.6 0.7 0.8 0.9
2 0.1 0.2 0.3 0.4 0.5 0.6 NaN NaN NaN
3 0.1 0.2 0.3 NaN NaN NaN 0.7 0.8 0.9
Poziomy obiektu kolumny to:
print(df, *df.columns.levels, sep='\n')
Index(['First', 'Second', 'Fourth'], dtype='object')
Index(['A', 'B', 'C', 'D'], dtype='object')
Jeśli użyjemy sumw a groupbyotrzymamy:
df.groupby(axis=1, level=0).sum()
First Fourth Second
1 0.0 2.4 1.5
2 0.6 0.0 1.5
3 0.6 2.4 0.0
Ale co by było, gdyby zamiast ['First', 'Second', 'Fourth']innych brakujących kategorii nazwano Thirdi Fifth? I chciałem, żeby zostały uwzględnione w wynikach groupbyagregacji? Możemy to zrobić, gdybyśmy mieli plik pandas.CategoricalIndex. I możemy to określić z wyprzedzeniem za pomocą levelsargumentu.
Zamiast tego zdefiniujmy dfjako:
cats = ['First', 'Second', 'Third', 'Fourth', 'Fifth']
lvl = pd.CategoricalIndex(cats, categories=cats, ordered=True)
df = pd.concat(
[d1, d2, d3], axis=1,
keys=['First', 'Second', 'Fourth'],
levels=[lvl]
)
df
First Fourth Second
1 0.0 2.4 1.5
2 0.6 0.0 1.5
3 0.6 2.4 0.0
Ale pierwszy poziom obiektu kolumny to:
df.columns.levels[0]
CategoricalIndex(
['First', 'Second', 'Third', 'Fourth', 'Fifth'],
categories=['First', 'Second', 'Third', 'Fourth', 'Fifth'],
ordered=True, dtype='category')
A nasze groupbypodsumowanie wygląda następująco:
df.groupby(axis=1, level=0).sum()
First Second Third Fourth Fifth
1 0.0 1.5 0.0 2.4 0.0
2 0.6 1.5 0.0 0.0 0.0
3 0.6 0.0 0.0 2.4 0.0
names
Służy do nazwania poziomów wyniku MultiIndex. Długość nameslisty powinna odpowiadać liczbie poziomów w wynikowym MultiIndex.
nazwy : lista, wartość domyślna Brak
Nazwy poziomów w wynikowym indeksie hierarchicznym
pd.concat( | pd.concat(
[d1, d2], | [d1, d2],
keys=[0, 1], | axis=1, keys=[0, 1],
names=['lvl0', 'lvl1']) | names=['lvl0', 'lvl1'])
----------------------------- | ----------------------------------
A B C D | lvl0 0 1
lvl0 lvl1 | lvl1 A B C B C D
0 2 0.1 0.2 0.3 NaN | 1 NaN NaN NaN 0.4 0.5 0.6
3 0.1 0.2 0.3 NaN | 2 0.1 0.2 0.3 0.4 0.5 0.6
1 1 NaN 0.4 0.5 0.6 | 3 0.1 0.2 0.3 NaN NaN NaN
2 NaN 0.4 0.5 0.6 |
verify_integrity
Dokumentacja nie wymagająca objaśnienia
verify_integrity : boolean, default False
Sprawdź, czy nowa połączona oś zawiera duplikaty. Może to być bardzo kosztowne w porównaniu z rzeczywistą konkatenacją danych.
Ponieważ wynikowy indeks jest wynikiem konkatenacji d1i d2nie jest unikalny, nie przejdzie testu integralności.
pd.concat([d1, d2])
A B C D
2 0.1 0.2 0.3 NaN
3 0.1 0.2 0.3 NaN
1 NaN 0.4 0.5 0.6
2 NaN 0.4 0.5 0.6
I
pd.concat([d1, d2], verify_integrity=True)
> ValueError: Indeksy mają nakładające się wartości: [2]