Gruppenoperationen#
Mit groupby
ist ein Prozess gemeint, der einen oder mehrere der folgenden Schritte umfasst:
Split teilt die Daten in Gruppen nach bestimmten Kriterien auf
Apply wendet eine Funktion unabhängig auf jede Gruppe an
Combine kombiniert die Ergebnisse in einer Datenstruktur
In der ersten Phase des Prozesses werden die in einem pandas-Objekt enthaltenen Daten, sei es eine Series, ein DataFrame oder etwas anderes, in Gruppen aufgeteilt, die auf einem oder mehreren Schlüsseln basieren. Die Aufteilung wird auf einer bestimmten Achse eines Objekts durchgeführt. Ein DataFrame kann zum Beispiel nach seinen Zeilen (axis=0
) oder seinen Spalten (axis=1
) gruppiert werden. Danach wird auf jede Gruppe eine Funktion angewendet, die einen neuen Wert erzeugt. Schließlich
werden die Ergebnisse all dieser Funktionsanwendungen in einem Ergebnisobjekt kombiniert. Die Form des Ergebnisobjekts hängt normalerweise davon ab, was mit den Daten gemacht wird.
Jeder Gruppierungsschlüssel kann viele Formen annehmen, und die Schlüssel müssen nicht alle vom gleichen Typ sein: * Eine Liste oder ein Array von Werten, die die gleiche Länge wie die zu gruppierende Achse haben * Ein Wert, der einen Spaltennamen in einem DataFrame angibt * Ein Dict oder eine Series, die eine Entsprechung zwischen den Werten auf der Achse, die gruppiert wird, und den Gruppennamen darstellt * Eine Funktion, die auf dem Achsenindex oder den einzelnen Beschriftungen im Index aufgerufen wird
Hinweis:
Die drei letztgenannten Methoden sind Abkürzungen, um ein Array von Werten zu erzeugen, die für die Aufteilung des Objekts verwendet werden.
Macht euch keine Sorgen, wenn dies alles abstrakt erscheint. Im Laufe dieses Kapitels werde ich viele Beispiele für all diese Methoden geben. Für den Anfang hier ein kleiner Tabellendatensatz als DataFrame:
[1]:
import pandas as pd
import numpy as np
[2]:
df = pd.DataFrame({'Title' : ['Jupyter Tutorial',
'Jupyter Tutorial',
'PyViz Tutorial',
None,
'Python Basics',
'Python Basics'],
'Language' : ['de', 'en', 'de', None, 'de', 'en'],
'2021-12' : [19651,4722,2573,None,525,157],
'2022-01' : [30134,3497,4873,None,427,85],
'2022-02' : [33295,4009,3930,None,276,226]})
df
[2]:
Title | Language | 2021-12 | 2022-01 | 2022-02 | |
---|---|---|---|---|---|
0 | Jupyter Tutorial | de | 19651.0 | 30134.0 | 33295.0 |
1 | Jupyter Tutorial | en | 4722.0 | 3497.0 | 4009.0 |
2 | PyViz Tutorial | de | 2573.0 | 4873.0 | 3930.0 |
3 | None | None | NaN | NaN | NaN |
4 | Python Basics | de | 525.0 | 427.0 | 276.0 |
5 | Python Basics | en | 157.0 | 85.0 | 226.0 |
Angenommen, ihr möchtet den Summe der Spalte 02/2022
anhand der Beschriftungen von Title
berechnen. Es gibt mehrere Möglichkeiten, dies zu tun. Eine davon ist der Zugriff auf 02/2022
und der Aufruf von groupby
mit der Spalte (einer Series) in Title
:
[3]:
grouped = df['2022-02'].groupby(df['Title'])
grouped
[3]:
<pandas.core.groupby.generic.SeriesGroupBy object at 0x7fa6c1b453d0>
Diese grouped
-Variable ist nun ein spezielles SeriesGroupBy
-Objekt. Es hat noch nichts berechnet, außer einigen Zwischendaten über den Gruppenschlüssel df['Title']
. Die Idee ist, dass dieses Objekt über alle Informationen verfügt, die benötigt werden, um eine Operation auf jede der Gruppen anzuwenden. Zur Berechnung der Gruppenmittelwerte können wir beispielsweise die Methode sum
des GroupBy
-Objekts aufrufen:
[4]:
grouped.sum()
[4]:
Title
Jupyter Tutorial 37304.0
PyViz Tutorial 3930.0
Python Basics 502.0
Name: 2022-02, dtype: float64
Später werde ich mehr darüber erklären, was passiert, wenn ihr .sum()
aufruft. Wichtig ist hier, dass die Daten (eine Reihe) durch Aufteilung der Daten auf den Gruppenschlüssel aggregiert wurden, wodurch eine neue Reihe entsteht, die nun durch die eindeutigen Werte in der Spalte Title
indiziert ist. Der resultierende Index ist Title
, weil groupby(df['Title']
dies tat.
Hätten wir stattdessen mehrere Arrays als Liste übergeben, würden wir etwas anderes erhalten:
[5]:
sums = df['2021-12'].groupby([df['Language'], df['Title']]).sum()
sums
[5]:
Language Title
de Jupyter Tutorial 19651.0
PyViz Tutorial 2573.0
Python Basics 525.0
en Jupyter Tutorial 4722.0
Python Basics 157.0
Name: 2021-12, dtype: float64
Hier haben wir die Daten anhand von zwei Schlüsseln gruppiert, und die resultierende Reihe hat nun einen hierarchischen Index, der aus den beobachteten eindeutigen Schlüsselpaaren besteht:
[6]:
sums.unstack()
[6]:
Title | Jupyter Tutorial | PyViz Tutorial | Python Basics |
---|---|---|---|
Language | |||
de | 19651.0 | 2573.0 | 525.0 |
en | 4722.0 | NaN | 157.0 |
Häufig befinden sich die Gruppierungsinformationen in demselben DataFrame wie die Daten, die ihr bearbeiten möchtet. In diesem Fall könnt ihr Spaltennamen (egal ob es sich um Zeichenketten, Zahlen oder andere Python-Objekte handelt) als Gruppenschlüssel übergeben:
[7]:
df.groupby('Title').sum()
[7]:
2021-12 | 2022-01 | 2022-02 | |
---|---|---|---|
Title | |||
Jupyter Tutorial | 24373.0 | 33631.0 | 37304.0 |
PyViz Tutorial | 2573.0 | 4873.0 | 3930.0 |
Python Basics | 682.0 | 512.0 | 502.0 |
Hierbei fällt auf, dass das Ergebnis keine Spalte Language
enthält. Da es sich bei df['Language']
nicht um numerische Daten handelt, stört sie im Tabellenlayout und wird daher automatisch aus dem Ergebnis ausgeschlossen. Standardmäßig werden alle numerischen Spalten aggregiert.
[8]:
df.groupby(['Title','Language']).sum()
[8]:
2021-12 | 2022-01 | 2022-02 | ||
---|---|---|---|---|
Title | Language | |||
Jupyter Tutorial | de | 19651.0 | 30134.0 | 33295.0 |
en | 4722.0 | 3497.0 | 4009.0 | |
PyViz Tutorial | de | 2573.0 | 4873.0 | 3930.0 |
Python Basics | de | 525.0 | 427.0 | 276.0 |
en | 157.0 | 85.0 | 226.0 |
Unabhängig vom Ziel der Verwendung von groupby
ist eine allgemein nützliche groupby
-Methode size
, die eine Serie mit den Gruppengrößen zurückgibt:
[9]:
df.groupby(['Language']).size()
[9]:
Language
de 3
en 2
dtype: int64
Hinweis:
Alle fehlenden Werte in einem Gruppenschlüssel werden standardmäßig aus dem Ergebnis ausgeschlossen. Dieses Verhalten kann deaktiviert werden, indem dropna=False
an groupby
übergeben wird.
[10]:
df.groupby('Language', dropna=False).size()
[10]:
Language
de 3
en 2
NaN 1
dtype: int64
[11]:
df.groupby(['Title', 'Language'], dropna=False).size()
[11]:
Title Language
Jupyter Tutorial de 1
en 1
PyViz Tutorial de 1
Python Basics de 1
en 1
NaN NaN 1
dtype: int64
Iteration über Gruppen#
Das von groupby
zurückgegebene Objekt unterstützt Iteration und erzeugt eine Folge von 2-Tupeln, die den Gruppennamen zusammen mit dem Datenpaket enthalten. Betrachten wir das Folgende:
[12]:
for name, group in df.groupby('Title'):
print(name)
print(group)
Jupyter Tutorial
Title Language 2021-12 2022-01 2022-02
0 Jupyter Tutorial de 19651.0 30134.0 33295.0
1 Jupyter Tutorial en 4722.0 3497.0 4009.0
PyViz Tutorial
Title Language 2021-12 2022-01 2022-02
2 PyViz Tutorial de 2573.0 4873.0 3930.0
Python Basics
Title Language 2021-12 2022-01 2022-02
4 Python Basics de 525.0 427.0 276.0
5 Python Basics en 157.0 85.0 226.0
Bei mehreren Schlüsseln ist das erste Element des Tupels ein Tupel von Schlüsselwerten:
[13]:
for (i1, i2), group in df.groupby(['Title', 'Language']):
print((i1, i2))
print(group)
('Jupyter Tutorial', 'de')
Title Language 2021-12 2022-01 2022-02
0 Jupyter Tutorial de 19651.0 30134.0 33295.0
('Jupyter Tutorial', 'en')
Title Language 2021-12 2022-01 2022-02
1 Jupyter Tutorial en 4722.0 3497.0 4009.0
('PyViz Tutorial', 'de')
Title Language 2021-12 2022-01 2022-02
2 PyViz Tutorial de 2573.0 4873.0 3930.0
('Python Basics', 'de')
Title Language 2021-12 2022-01 2022-02
4 Python Basics de 525.0 427.0 276.0
('Python Basics', 'en')
Title Language 2021-12 2022-01 2022-02
5 Python Basics en 157.0 85.0 226.0
Als nächstes wollen wir ein dict
der Daten als Einzeiler ausgeben:
[14]:
books = dict(list(df.groupby('Title')))
books
[14]:
{'Jupyter Tutorial': Title Language 2021-12 2022-01 2022-02
0 Jupyter Tutorial de 19651.0 30134.0 33295.0
1 Jupyter Tutorial en 4722.0 3497.0 4009.0,
'PyViz Tutorial': Title Language 2021-12 2022-01 2022-02
2 PyViz Tutorial de 2573.0 4873.0 3930.0,
'Python Basics': Title Language 2021-12 2022-01 2022-02
4 Python Basics de 525.0 427.0 276.0
5 Python Basics en 157.0 85.0 226.0}
Standardmäßig gruppiert groupby
auf axis=0
, aber ihr könnt auch auf jeder der anderen Achsen gruppieren. Zum Beispiel könnten wir die Spalten unseres Beispiels df
hier nach dtype
gruppieren wie folgt:
[15]:
df.dtypes
[15]:
Title object
Language object
2021-12 float64
2022-01 float64
2022-02 float64
dtype: object
[16]:
grouped = df.groupby(df.dtypes, axis=1)
[17]:
for dtype, group in grouped:
print(dtype)
print(group)
float64
2021-12 2022-01 2022-02
0 19651.0 30134.0 33295.0
1 4722.0 3497.0 4009.0
2 2573.0 4873.0 3930.0
3 NaN NaN NaN
4 525.0 427.0 276.0
5 157.0 85.0 226.0
object
Title Language
0 Jupyter Tutorial de
1 Jupyter Tutorial en
2 PyViz Tutorial de
3 None None
4 Python Basics de
5 Python Basics en
Auswählen einer Spalte oder Untergruppe von Spalten#
Die Indizierung eines GroupBy
-Objekts, das aus einem DataFrame mit einem Spaltennamen oder einem Array von Spaltennamen erstellt wurde, hat den Effekt einer Spaltenunterteilung für die Aggregation. Dies bedeutet, dass:
[18]:
df.groupby('Title')['2021-12']
df.groupby('Title')[['2022-01']]
[18]:
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x7fa6c1ab4100>
sind vereinfachte Schreibweisen für:
[19]:
df['2021-12'].groupby(df['Title'])
df[['2022-01']].groupby(df['Title'])
[19]:
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x7fa6c1ab4790>
Insbesondere bei großen Datensätzen kann es wünschenswert sein, nur einige Spalten zu aggregieren. Um zum Beispiel im vorhergehenden Datensatz die Summe nur für die Spalte 01/2022
zu berechnen und das Ergebnis als DataFrame zu erhalten, könnten wir schreiben:
[20]:
df.groupby(['Title', 'Language'])[['2022-01']].sum()
[20]:
2022-01 | ||
---|---|---|
Title | Language | |
Jupyter Tutorial | de | 30134.0 |
en | 3497.0 | |
PyViz Tutorial | de | 4873.0 |
Python Basics | de | 427.0 |
en | 85.0 |
Das von dieser Indizierungsoperation zurückgegebene Objekt ist ein gruppierter DataFrame, wenn eine Liste oder ein Array übergeben wird, oder eine gruppierte Serie, wenn nur ein einzelner Spaltenname als Skalar übergeben wird:
[21]:
series_grouped = df.groupby(['Title', 'Language'])['2022-01']
series_grouped
[21]:
<pandas.core.groupby.generic.SeriesGroupBy object at 0x7fa6c1aaabe0>
[22]:
series_grouped.sum()
[22]:
Title Language
Jupyter Tutorial de 30134.0
en 3497.0
PyViz Tutorial de 4873.0
Python Basics de 427.0
en 85.0
Name: 2022-01, dtype: float64
Gruppierung mit dicts
und Series
#
Gruppierungsinformationen können auch in einer anderen Form als einem Array vorliegen:
[23]:
df.iloc[2:3, [2, 3]] = np.nan
Angenommen, ich habe eine Gruppenkorrespondenz für die Spalten und möchte die Spalten nach Gruppen zusammenfassen:
[24]:
mapping = {'2021-12': 'Dec 2021',
'2022-01': 'Jan 2022',
'2022-02': 'Feb 2022'}
Nun könnte aus diesem dict
ein Array konstruiert werden, um es an groupby
zu übergeben, aber stattdessen können wir auch einfach das dict
übergeben:
[25]:
by_column = df.groupby(mapping, axis=1)
by_column.sum()
[25]:
Dec 2021 | Feb 2022 | Jan 2022 | |
---|---|---|---|
0 | 19651.0 | 33295.0 | 30134.0 |
1 | 4722.0 | 4009.0 | 3497.0 |
2 | 0.0 | 3930.0 | 0.0 |
3 | 0.0 | 0.0 | 0.0 |
4 | 525.0 | 276.0 | 427.0 |
5 | 157.0 | 226.0 | 85.0 |
Die gleiche Funktionalität gilt für Series
, die als eine Abbildung mit fester Größe betrachtet werden können:
[26]:
map_series = pd.Series(mapping)
map_series
[26]:
2021-12 Dec 2021
2022-01 Jan 2022
2022-02 Feb 2022
dtype: object
[27]:
df.groupby(map_series, axis=1).count()
[27]:
Dec 2021 | Feb 2022 | Jan 2022 | |
---|---|---|---|
0 | 1 | 1 | 1 |
1 | 1 | 1 | 1 |
2 | 0 | 1 | 0 |
3 | 0 | 0 | 0 |
4 | 1 | 1 | 1 |
5 | 1 | 1 | 1 |
Gruppieren mit Funktionen#
Die Verwendung von Python-Funktionen ist im Vergleich zu einem Dict
oder einer Series
eine allgemeinere Methode zur Definition einer Gruppenzuordnung. Jede Funktion, die als Gruppenschlüssel übergeben wird, wird einmal pro Indexwert aufgerufen, wobei die Rückgabewerte als Gruppennamen verwendet werden. Betrachtet konkret den Beispiel-DataFrame aus dem vorherigen Abschnitt, das die Titel als Indexwerte enthält. Angenommen, Wenn ihr nach der Länge der Namen gruppieren wollt, könnt ihr zwar
ein Array mit den Längen der Strings berechnen, aber es ist einfacher, die Funktion len
zu übergeben:
[28]:
df = pd.DataFrame([[19651,30134,33295],
[4722,3497,4009],
[2573,4873,3930],
[525,427,276],
[157,85,226]],
index=['Jupyter Tutorial',
'Jupyter Tutorial',
'PyViz Tutorial',
'Python Basics',
'Python Basics'],
columns=['2021-12', '2022-01', '2022-02'])
[29]:
df.groupby(len).count()
[29]:
2021-12 | 2022-01 | 2022-02 | |
---|---|---|---|
13 | 2 | 2 | 2 |
14 | 1 | 1 | 1 |
16 | 2 | 2 | 2 |
Das Mischen von Funktionen mit Arrays, Dicts oder Series ist kein Problem, da alles intern in Arrays umgewandelt wird:
[30]:
languages = ['de', 'en', 'de', 'de', 'en']
[31]:
df.groupby([len, languages]).count()
[31]:
2021-12 | 2022-01 | 2022-02 | ||
---|---|---|---|---|
13 | de | 1 | 1 | 1 |
en | 1 | 1 | 1 | |
14 | de | 1 | 1 | 1 |
16 | de | 1 | 1 | 1 |
en | 1 | 1 | 1 |
Gruppierung nach Indexebenen#
Eine letzte praktische Funktion für hierarchisch indizierte Datensätze ist die Möglichkeit der Aggregation anhand einer der Indexebenen einer Achse. Schauen wir uns ein Beispiel an:
[32]:
version_hits = [[19651,0,30134,0,33295,0],
[4722,1825,3497,2576,4009,3707],
[2573,0,4873,0,3930,0],
[None,None,None,None,None,None],
[525,0,427,0,276,0],
[157,0,85,0,226,0]]
df = pd.DataFrame(version_hits,
index=[['Jupyter Tutorial',
'Jupyter Tutorial',
'PyViz Tutorial',
None,
'Python Basics',
'Python Basics'],
['de', 'en', 'de', None, 'de', 'en']],
columns=[['2021-12', '2021-12',
'2022-01', '2022-01',
'2022-02', '2022-02'],
['latest', 'stable',
'latest', 'stable',
'latest', 'stable']])
df.columns.names = ['Month', 'Version']
df
[32]:
Month | 2021-12 | 2022-01 | 2022-02 | ||||
---|---|---|---|---|---|---|---|
Version | latest | stable | latest | stable | latest | stable | |
Jupyter Tutorial | de | 19651.0 | 0.0 | 30134.0 | 0.0 | 33295.0 | 0.0 |
en | 4722.0 | 1825.0 | 3497.0 | 2576.0 | 4009.0 | 3707.0 | |
PyViz Tutorial | de | 2573.0 | 0.0 | 4873.0 | 0.0 | 3930.0 | 0.0 |
NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
Python Basics | de | 525.0 | 0.0 | 427.0 | 0.0 | 276.0 | 0.0 |
en | 157.0 | 0.0 | 85.0 | 0.0 | 226.0 | 0.0 |
[33]:
df.groupby(level='Month', axis=1).sum()
[33]:
Month | 2021-12 | 2022-01 | 2022-02 | |
---|---|---|---|---|
Jupyter Tutorial | de | 19651.0 | 30134.0 | 33295.0 |
en | 6547.0 | 6073.0 | 7716.0 | |
PyViz Tutorial | de | 2573.0 | 4873.0 | 3930.0 |
NaN | NaN | 0.0 | 0.0 | 0.0 |
Python Basics | de | 525.0 | 427.0 | 276.0 |
en | 157.0 | 85.0 | 226.0 |