In [ ]:
 

Mehrstufige Indizierung

Einführung

Multi Level Indizierung

Die Basiskonzepte von Pandas haben wir im vorherigen Kapitel gelernt. Dabei haben wir uns die Daten-Strukturen

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_series
Der obige Python-Code führt zu folgender Ausgabe:
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"]]
Wir erhalten die folgende Ausgabe:
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

Auf die einzelnen Stufen des mehrstufigen Indexes kann man über city_series.index.levels zugreifen. Bei diesem Objekt handelt es sich über 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 citiy_series ein DataFrame erzeigen kann. Man kann dies zwar mit folgendem Code erreichen, aber wie 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:

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:
             Berlin  Hamburg   Vienna       Zürich
area         891.85      755    414.6        87.88
country     Germany  Germany  Austria  Switzerland
population  3562166  1760433  1805681       378884
Ergebnis für level=0:
           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=1:
             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()
Der obige Python-Code führt zu folgender 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    28626
             wrong    32422
      green  right    32309
             wrong    57389
      blue   right    42729
             wrong     5575
cold  red    right    87591
             wrong    75693
      green  right    24264
             wrong    58508
      blue   right    84909
             wrong    79819
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   84909  79819
     green  24264  58508
     red    87591  75693
hot  blue   42729   5575
     green  32309  57389
     red    28626  32422
print(S3_series.unstack(level=-0))
              cold    hot
blue  right  84909  42729
      wrong  79819   5575
green right  24264  32309
      wrong  58508  57389
red   right  87591  28626
      wrong  75693  32422
x = S3_series.unstack(level=[1, 2])
print(x)
        red         green          blue       
      right  wrong  right  wrong  right  wrong
cold  87591  75693  24264  58508  84909  79819
hot   28626  32422  32309  57389  42729   5575
print(x["red", "right"])
cold    87591
hot     28626
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  87591  75693  24264  58508  84909  79819
hot   28626  32422  32309  57389  42729   5575
x["right"]
Führt man obigen Code aus, erhält man folgendes Ergebnis:
red green blue
cold 87591 24264 84909
hot 28626 32309 42729

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_swapped
Der obige Code führt zu folgendem Ergebnis:
hot   right  red      28626
      wrong  red      32422
      right  green    32309
      wrong  green    57389
      right  blue     42729
      wrong  blue      5575
cold  right  red      87591
      wrong  red      75693
      right  green    24264
      wrong  green    58508
      right  blue     84909
      wrong  blue     79819
dtype: int64
print(city_series)
city_series = city_series.swaplevel()
city_series.sort_index(inplace=True)
print("\n--- vertauscht ---")
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
--- vertauscht ---
Führt man obigen Code aus, erhält man folgendes 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

In [ ]: