Mehrstufige Indizierung
Einführung
Die Basiskonzepte von Pandas haben wir im vorherigen Kapitel gelernt. Dabei haben wir uns die Daten-Strukturen
- Series und
- DataFrame
angeschaut.
Ebenso haben wir gelernt, wie man Series- und DataFrame-Objekte in numerischen Python-Programmen erstellt und manipuliert.
Jetzt wollen wir weitere Aspekte dieser Datenstrukturen betrachen. Wir beginnen mit den fortgeschrittenen Indizierungsmöglichkeiten in Pandas.
Mehrstufig indizierte Series
Mehrstufige Indizierung ist sowohl für Series als auch für DataFrame verfügbar. Es ist eine faszinierende Möglichkeit, in höheren Daten-Dimensionen mit den Pandas-Datenstrukturen zu arbeiten. Ein effizienter Weg, um beliebig hoch dimensionierte Daten zu speichern und zu manipulieren und damit in 1-dimensionalen (Series) oder 2-dimensionalen (DataFrames) Strukturen zu arbeiten. Mit anderen Worten können wir mit höher-dimensionierten Daten in niedrigeren Dimensionen arbeiten. Es ist Zeit für ein Beispiel in Python:
import pandas as pd cities = ["Vienna", "Vienna", "Vienna", "Hamburg", "Hamburg", "Hamburg", "Berlin", "Berlin", "Berlin", "Zürich", "Zürich", "Zürich"] index = [cities, ["country", "area", "population", "country", "area", "population", "country", "area", "population", "country", "area", "population"]] data = ["Austria", 414.60, 1805681, "Germany", 755.00, 1760433, "Germany", 891.85, 3562166, "Switzerland", 87.88, 378884] city_series = pd.Series(data, index=index) print(city_series)
Vienna country Austria area 414.6 population 1805681 Hamburg country Germany area 755 population 1760433 Berlin country Germany area 891.85 population 3562166 Zürich country Switzerland area 87.88 population 378884 dtype: object
cities_data = { ("Vienna", "country"): "Austria", ("Vienna", "area"): 414.6, ("Vienna", "population"): 1805681, ("Hamburg", "country"): "Germany", ("Hamburg", "area"): 755, ("Hamburg", "population"): 1760433, ("Berlin", "country"): "Germany", ("Berlin", "area"): 891.85, ("Berlin", "population"): 3562166, ("Zürich", "country"): "Switzerland", ("Zürich", "area"): 87.88, ("Zürich", "population"): 378884 } city_series = pd.Series(cities_data) city_seriesWir können die folgende Ausgabe erwarten, wenn wir den obigen Python-Code ausführen:
Vienna country Austria area 414.6 population 1805681 Hamburg country Germany area 755 population 1760433 Berlin country Germany area 891.85 population 3562166 Zürich country Switzerland area 87.88 population 378884 dtype: object
Zugriffsmöglichkeiten
Wir können über folgenden Weg auf die Daten, die mit dem ersten Index bezeichnet sind, zugreifen:
print(city_series["Vienna"])
country Austria area 414.6 population 1805681 dtype: object
Ebenso kann auf die Information über das Land (country), Gebiet (area) oder Bevölkerung (population) einer Stadt zugegriffen werden. Dazu gibt es zwei Möglichkeiten:
print(city_series["Vienna"]["area"])
414.6
Zur Vervollständigung der zweite Weg:
print(city_series["Vienna", "area"])
414.6
Wenn der Index geordnet ist, kann auch die Slicing-Operation angewendet werden:
city_series = city_series.sort_index() print("city_series with sorted index:") print(city_series) print("\nSlicing the city_series:") print(city_series["Berlin":"Vienna"])
city_series with sorted index: Berlin area 891.85 country Germany population 3562166 Hamburg area 755 country Germany population 1760433 Vienna area 414.6 country Austria population 1805681 Zürich area 87.88 country Switzerland population 378884 dtype: object Slicing the city_series: Berlin area 891.85 country Germany population 3562166 Hamburg area 755 country Germany population 1760433 Vienna area 414.6 country Austria population 1805681 dtype: object
Ebenso können dann auch die Inhalte mehrerer Städte selektiv ausgegeben werden, indem man eine Liste der Stadtnamen als Schlüssel verwendet:
city_series[["Vienna", "Berlin"]]Führt man obigen Code aus, erhält man Folgendes:
Berlin area 891.85 country Germany population 3562166 Vienna area 414.6 country Austria population 1805681 dtype: object
Im nächsten Beispiel zeigen wir, wie mittels Slicing auf die inneren Schlüssel zugegriffen werden kann:
print(city_series[:, "area"])
Berlin 891.85 Hamburg 755 Vienna 414.6 Zürich 87.88 dtype: object
Mit city_series.index.levels
kann man auf die einzelnen Stufen des mehrstufigen Indexes zugreifen. Bei diesem Objekt handelt es sich um eine FrozenList, über die wir hier iterieren:
for i in range(len(city_series.index.levels)): if i == 0: print("Oberste Hierarchiestufe:") elif i == 1: print("Untere Hierarchiestufe:") print(city_series.index.levels[i])
Oberste Hierarchiestufe: Index(['Berlin', 'Hamburg', 'Vienna', 'Zürich'], dtype='object') Untere Hierarchiestufe: Index(['area', 'country', 'population'], dtype='object')
Zusammenhang zu DataFrames
Einige werden sicherlich bemerkt haben, dass man obige mehrstufige Series auch als DataFrame-Objekte darstellen könnte. Ein DataFrame ist ja bereits zweidimensional, während eine Series nur eindimensional ist, sofern man keinen mehrstufigen Index verwendet.
Nun stellt sich die Frage, wie man aus der Series city_series
ein DataFrame erzeigen kann. Man kann dies zwar mit folgendem Code erreichen, aber wir werden danach einen direkteren Weg zeigen.
city_df = pd.DataFrame([], index=index[0]) for key in index[1][:3]: city_df = pd.concat([city_df, city_series[:, key]], axis=1, sort=False) city_df.columns = ["country", "population", "area"] print(city_df)
country population area Vienna Austria 414.6 1805681 Hamburg Germany 755 1760433 Berlin Germany 891.85 3562166 Zürich Switzerland 87.88 378884
Setzt man sort
auf False
, erhält man einen unsortierten Index, in unserem Fall:
city_df = pd.DataFrame([], index=index[0]) for key in index[1][:3]: city_df = pd.concat([city_df, city_series[:, key]], axis=1, sort=False) city_df.columns = ["country", "population", "area"] print(city_df)
country population area Vienna Austria 414.6 1805681 Hamburg Germany 755 1760433 Berlin Germany 891.85 3562166 Zürich Switzerland 87.88 378884
Obiges können wir einfacher haben, indem wir die von der Series-Klasse zur Verfügung gestellte Methode unstack
benutzen. unstack
bietet zwei optionale Parameter:
level
, der per Default auf -1 gesetzt ist, bestimmt, welcher Teil des mehrstufigen Indexes als Spaltenbezeichner verwendet wird. -1 bedeutet, dass der innere Index verwendet wird. Das entspricht in unserem Beispiel city_series.index.levels[-1], also die Städtenamen. Setzen wirlevel
auf 0, so werden die Städtenamen zum Index des DataFrame.fill_value
ist per Default auf None gesetzt. Mit diesem Parameter kann man den Wert bestimmen, auf denNaN
-Werte umgesetzt werden, falls diese sich in den Daten befinden.
city_df = city_series.unstack() print("Für level wurde der Default-Wert -1 genutzt:") print(city_df) city_df = city_series.unstack(level=0) print("\nErgebnis für level=0:") print(city_df)
Für level wurde der Default-Wert -1 genutzt: area country population Berlin 891.85 Germany 3562166 Hamburg 755 Germany 1760433 Vienna 414.6 Austria 1805681 Zürich 87.88 Switzerland 378884 Ergebnis für level=0: Berlin Hamburg Vienna Zürich area 891.85 755 414.6 87.88 country Germany Germany Austria Switzerland population 3562166 1760433 1805681 378884
Die DataFrame-Methode stack
entspricht der Umkehrfunktion, d.h. aus einem DataFrame-Objekt erzeugt sie ein Series-Objekt mit mehrstufigem Index:
city_df.stack()Führt man obigen Code aus, erhält man folgende Ausgabe:
area Berlin 891.85 Hamburg 755 Vienna 414.6 Zürich 87.88 country Berlin Germany Hamburg Germany Vienna Austria Zürich Switzerland population Berlin 3562166 Hamburg 1760433 Vienna 1805681 Zürich 378884 dtype: object
Dreistufige Indizes
Zu Anfang dieses Kapitels haben wir gesehen, wie wir eine Series mit einem mehrstufigen Index direkt durch die Angabe einer Liste mit zwei oder mehr Index-Arrays oder Listen erzeugen können. Wir hatten ein Dictionary cities
und die verschachtelte Liste index
zu einer mehrstufigen Series
gewandelt. Genaugenommen erhielten wir eine zweistufige Series. Im folgenden Beispiel zeigen wir ein Beispiel mit einem dreistufigen Index:
import pandas as pd index = [ ["hot"] * 6 + ["cold"] * 6, (["red"] * 2 + ["green"] * 2 + ["blue"] * 2) * 2, ["right", "wrong"] * 6] data = np.random.randint(100, 100000, size=(12,)) S3_series = pd.Series(data, index=index) print(S3_series)
hot red right 16031 wrong 71724 green right 51514 wrong 56067 blue right 51977 wrong 28134 cold red right 8368 wrong 54434 green right 6894 wrong 82765 blue right 34937 wrong 58604 dtype: int64
Auch im Falle von dreistufigen Indizes können wir mit Hilfe der Methode unstack
ein DataFrame erzeugen. Wir zeigen die verschiedenen Möglichkeiten für den Parameter level
:
print(S3_series.unstack(level=-1)) # entspricht 'level=2'
right wrong cold blue 34937 58604 green 6894 82765 red 8368 54434 hot blue 51977 28134 green 51514 56067 red 16031 71724
print(S3_series.unstack(level=-0))
cold hot blue right 34937 51977 wrong 58604 28134 green right 6894 51514 wrong 82765 56067 red right 8368 16031 wrong 54434 71724
x = S3_series.unstack(level=[1, 2]) print(x)
red green blue right wrong right wrong right wrong cold 8368 54434 6894 82765 34937 58604 hot 16031 71724 51514 56067 51977 28134
print(x["red", "right"])
cold 8368 hot 16031 Name: (red, right), dtype: int64
x = S3_series.unstack(level=[2, 1]) print(x)
right wrong right wrong right wrong red red green green blue blue cold 8368 54434 6894 82765 34937 58604 hot 16031 71724 51514 56067 51977 28134
x["right"]Der obige Code führt zu folgendem Ergebnis:
red | green | blue | |
---|---|---|---|
cold | 8368 | 6894 | 34937 |
hot | 16031 | 51514 | 51977 |
Die Daten hätten aber auch wie in folgendem Dictionary organisiert gewesen sein können. Auch dann können wir diese direkt in ein mehrstufiges Dictionary wandeln:
Vertauschen mehrstufiger Indizes
Es ist möglich, die Ebenen eines mehrstufigen Index mit der Methode swaplevel
zu vertauschen:
S3_swapped = S3_series.swaplevel() S3_swapped.sort_index(inplace=True) S3_swappedWir können die folgende Ausgabe erwarten, wenn wir den obigen Python-Code ausführen:
cold right blue 34937 green 6894 red 8368 wrong blue 58604 green 82765 red 54434 hot right blue 51977 green 51514 red 16031 wrong blue 28134 green 56067 red 71724 dtype: int64
print(city_series) city_series = city_series.swaplevel() city_series.sort_index(inplace=True) print("\n--- vertauscht ---") city_series
Berlin area 891.85 country Germany population 3562166 Hamburg area 755 country Germany population 1760433 Vienna area 414.6 country Austria population 1805681 Zürich area 87.88 country Switzerland population 378884 dtype: object --- vertauscht ---Der obige Code führt zu folgendem Ergebnis:
area Berlin 891.85 Hamburg 755 Vienna 414.6 Zürich 87.88 country Berlin Germany Hamburg Germany Vienna Austria Zürich Switzerland population Berlin 3562166 Hamburg 1760433 Vienna 1805681 Zürich 378884 dtype: object