# invisible
import pandas as pd
pd.set_option('display.max_colwidth', 65)
pd.set_option('display.max_columns', 65)
import numpy as np
np.core.arrayprint._line_width = 65
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)
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_series
print(city_series["Vienna"])
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"])
Zur Vervollständigung der zweite Weg:
print(city_series["Vienna", "area"])
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"])
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"]]
Im nächsten Beispiel zeigen wir, wie mittels Slicing auf die inneren Schlüssel zugegriffen werden kann:
print(city_series[:, "area"])
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])
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][::3])
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)
Setzt man sort
auf False
, erhält man einen unsortierten Index, in unserem Fall:
city_df = pd.DataFrame([], index=index[0][::3])
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)
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)
Die DataFrame-Methode stack
entspricht der Umkehrfunktion, d.h. aus einem DataFrame-Objekt erzeugt sie ein Series-Objekt mit mehrstufigem Index:
city_df.stack()
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)
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'
print(S3_series.unstack(level=-0))
x = S3_series.unstack(level=[1, 2])
print(x)
print(x["red", "right"])
x = S3_series.unstack(level=[2, 1])
print(x)
x["right"]
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:
S3_swapped = S3_series.swaplevel()
S3_swapped.sort_index(inplace=True)
S3_swapped
print(city_series)
city_series = city_series.swaplevel()
city_series.sort_index(inplace=True)
print("\n--- vertauscht ---")
city_series
growth_rates = {("Afghanistan", 2015): 1.31,
("Afghanistan", 2016): 2.37,
("Afghanistan", 2017): 2.60,
("Ägypten", 2015): 4.37,
("Ägypten", 2016): 4.35,
("Ägypten", 2017): 4.18,
("Albanien", 2015): 2.22,
("Albanien", 2016): 3.35,
("Albanien", 2017): 3.84}
growth_rates_series = pd.Series(growth_rates)
growth_rates_series
growth_rates_series = growth_rates_series.swaplevel()
growth_rates_series.sort_index(inplace=True)
growth_rates_series