Nächstes Kapitel: Pandas Tutorial Continuation: multi-level indexing
# invisible
import numpy as np
import pandas as pd
np.core.arrayprint._line_width = 60
pd.set_option('display.max_colwidth', 65)
pd.set_option('display.max_columns', 5)
Binning in Python und Pandas
Einführung
Binning ist eine Technik, die in der Datenverarbeitung und Statistik verwendet wird. Unter Binning versteht man eine Klassenbildung in der Vorverarbeitung bei der Datenanalyse. Eine gegebene Menge von Werten, die sortiert sind, werden in Intervalle aufgeteilt. Diese Intervalle bezeichnet man im Englischen als "bins" (deutsch "Behälter"). Jedes dieser Intervalle (Bins) wird dann durch einen Repräsentanten bezeichnet. Man bezeichnet diese auch als Intervallabels. Binning wird häufig angewendet, wenn es mehr mögliche Daten gibt, als man eigentlich braucht. So können bspw. Körpergrössen von Menschen in Intervallen oder Kategorien eingeteilt werden.
Nehmen wir an, wir messen die Körpergrößen von 30 Menschen. Die Größenwerte können, grob geschätzt, zwischen 1,30 Meter und 2,50 Meter liegen. Theoretisch gibt es nun 120 verschiedene cm-Werte, jedoch werden wir meistens nur 30 verschiedene Werte aus der Test-Gruppe beobachten.
Eine Möglichkeit um sie zu gruppieren wäre die Einteilung in die Bereiche von 1,30 - 1,50 Meter, 1,50 - 1,70 Meter, 1,70 - 1,90 Meter, usw. Die Original-Daten werden also zu den passenden "Eimern" (Buckets) zugeordnet. Weiterhin werden die Originaldaten durch die korrespondierenden Intervalle ersetzt. Binning ist also eine Form der Quantifizierung.
Einteilungen (Bins) müssen nicht unbedingt numerisch sein. Die Kategorisierung kann beispielsweise von der Art "Hunde", "Katzen", "Hamster" usw. sein, also von jeder erdenklichen Art.
Binning wird auch in der Bildverarbeitung verwendet, um die Datenmengen zu reduzieren, indem benachbarte Pixel zu einzelnen Pixeln kombiniert werden. Man nennt das Verfahren auch kxk-binning, weil Bereiche von k x k Pixel auf einen Pixel reduziert werden.
Pandas bietet einfache Wege, um Bins zu erstellen und so Daten einzuteilen. Bevor wir auf die Pandas-Funktionalität eingehen, möchten wir noch die Basisfunktionen von Python vorstellen, um Bins zu verwenden. Im folgenden Beispiel kommen Listen und Tupel zum Einsatz:
def create_bins(lower_bound, width, quantity):
""" create_bins gibt eine Partitionierung mit gleichen Abständen
zurück. Es handelt sich um eine aufsteigende Liste von Tupeln,
die die Werte der Intervallgrenzen darstellen.
A tuple bins[i], i.e. (bins[i][0], bins[i][1]) with i > 0
and i < quantity, satisfies the following conditions:
(1) bins[i][0] + width == bins[i][1]
(2) bins[i-1][0] + width == bins[i][0] and
bins[i-1][1] + width == bins[i][1]
"""
bins = []
for low in range(lower_bound,
lower_bound + quantity * width + 1, width):
bins.append((low, low+width))
return bins
Wir erstellen nun 5 bins (quantity=5) mit einer Breite von 10 (width=10) und beginnen bei 10 (lower_bound=10):
bins = create_bins(lower_bound=10,
width=10,
quantity=5)
print(bins)
Die nächste Funktion find_bin()
wird mit einer Liste oder einem Tupel bins
aufgerufen, welches 2er-Tupel oder Listen mit zwei Elementen enthalten muss. Die Funktion ermittelt den Index des Intervalls, der zu dem Wert value
gehört:
def find_bin(value, bins):
""" 'bins' ist ist eine List von Tupeln, wie beispielsweise
[(0,20), (20, 40), (40, 60)]
binning gibt den kleinsten Index i von 'bins' zurück,
sodass gilt:
bin[i][0] <= value < bin[i][1]
"""
for i in range(0, len(bins)):
if bins[i][0] <= value < bins[i][1]:
return i
return -1
from collections import Counter
bins = create_bins(lower_bound=50,
width=4,
quantity=10)
print(bins)
weights_of_persons = [73.4, 69.3, 64.9, 75.6, 74.9, 80.3,
78.6, 84.1, 88.9, 90.3, 83.4, 69.3,
52.4, 58.3, 67.4, 74.0, 89.3, 63.4]
binned_weights = []
for value in weights_of_persons:
bin_index = find_bin(value, bins)
print(value, bin_index, bins[bin_index])
binned_weights.append(bin_index)
frequencies = Counter(binned_weights)
print(frequencies)
Binning mit Pandas
Das Pandas-Modul bietet starke Funktionalitäten für die Einteilung von Daten. Wir demonstrieren dies anhand der vorigen Daten.
Von Pandas verwendete Bins
Als Bins haben wir im vorigen Beispiel eine Liste von Tupeln verwendet. Diese Liste müssen wir nun in eine Datenstruktur wandeln, welche von der Pandas-Funktion cut()
verwendet werden kann. Diese Datenstruktur ist ein IntervalIndex
. Wir können das mit dem Befehl pd.IntervalIndex.from_tuples()
tun:
import pandas as pd
bins2 = pd.IntervalIndex.from_tuples(bins)
cut
ist der Name der Pandas-Funktion, die wir brauchen, um Daten in Bins einzuteilen. cut
erwartet einige Parameter. Die wichtigsten sind aber x
für die aktuellen Werte und bins
, welcher den IntervalIndex definiert. x
kann jede eindimensionale Array-ähnliche Sruktur sein, wie z.B. Listen, Tupel, nd-arrays usw.:
categorical_object = pd.cut(weights_of_persons, bins2)
print(categorical_object)
Das Ergebnis der Funktion cut()
ist ein sogenanntes "Kategorisches Objekt" (Categorical object). Jedes "bin" entspricht einer Kategorie. Die Kategorien sind in einer mathematischen Notation beschrieben. (70, 74]
bedeutet, dass dieses "bin" Werte beinhaltet zwischen 70 (exklusive) und 74 (inklusive).
Mathematisch handelt es sich dabei um ein halb-offenes Intervall. Das heißt, dass ein Endpunkt des Intervalls inklusive ist, der anderes Endpunkt dagegen nicht. Manchmal wird es auch halb-geschlossenes Intervall genannt.
In unserem vorherigen Kapitel haben wir auch ein halb-offenes Intervall definiert, jedoch anders herum, d.h. dass die linke Seite offen und die rechte geschlossen war.
Wenn wir pd.IntervalIndex.from_tuples
verwenden, können wir die Öffnung der "bins" definieren, indem wir den Parameter closed
auf einen der folgenden Werte setzen:
- 'left': linke Seite geschlossen und rechte Seite geöffnet
- 'right': (Default) linke Seite geöffnet und rechte Seite geschlossen
- 'both': beide Seiten geschlossen
- 'neither': beide Seiten geöffnet
Für das gleiche Verhalten wie im vorherigen Kapitel, setzen wir den Parameter closed = 'left'
:
bins2 = pd.IntervalIndex.from_tuples(bins, closed="left")
categorical_object = pd.cut(weights_of_persons, bins2)
print(categorical_object)
Andere Wege um Bins zu definieren
Wir haben IntervalIndex
verwendet, um die Gewichtsdaten in "bins" einzuteilen. Die Funktion cut()
kann noch mit zwei weiteren Arten der bin-Repräsentation umgehen:
- Integer-Werte:
Angabe eines Integer-Wertes für die gleiche Breite aller "Bins" im Bereich der Wertex
. Die Range vonx
wird auf jeder Seite um 0.1% erweitert, um die Minimum- und Maximum-Werte zu inkludieren. - Skalare Sequenzen:
Definiert die "bin"-Kanten, was eine ungleiche Breite der "bins" erlaubt. Die Range vonx
wird nicht erweitert.
categorical_object = pd.cut(weights_of_persons, 18)
print(categorical_object[:5])
sequence_of_scalars = [ x[0] for x in bins]
sequence_of_scalars.append(bins[-1][1])
print(sequence_of_scalars)
categorical_object = pd.cut(weights_of_persons,
sequence_of_scalars,
right=False)
for i in range(len(categorical_object)):
print(categorical_object[i])
print(pd.value_counts(categorical_object))
categorical_object.codes
bietet eine Beschriftung der Eingangswerte in die "binning"-Kategorien:
labels = categorical_object.codes
print(labels)
categories
ist der IntervalIndex der Kategorien der Label-Indizes:
categories = categorical_object.categories
categories
Zusammenhang zwischen Gewichtsdaten und "bins":
for index in range(len(weights_of_persons)):
label_index = labels[index]
print(weights_of_persons[index],
label_index,
categories[label_index] )
Bins benennen
Stellen wir uns vor, wir hätten eine Universität, die in Abhängigkeit der Durchschnittsnote (GPA - Grade point average) drei Stufen der "Latin honors" verleiht:
- "summa cum laude" setzt einen GPA über 3.9 voraus
- "magna cum laude", wenn der GPA über 3.8 ist
- "cum laude", wenn der GPA 3.6 oder höher ist
degrees = ["none",
"cum laude",
"magna cum laude",
"summa cum laude"]
student_results = [3.93, 3.24, 2.80,
2.83, 3.91, 3.698,
3.731, 3.25, 3.24,
3.82, 3.22]
student_results_degrees = pd.cut(student_results,
[0, 3.6, 3.8, 3.9, 4.0],
labels=degrees)
print(pd.value_counts(student_results_degrees))
Schauen wir uns die einzelnen Bewertungen/Benotungen der Studenten an:
labels = student_results_degrees.codes
categories = student_results_degrees.categories
for index in range(len(student_results)):
label_index = labels[index]
print(student_results[index],
label_index,
categories[label_index] )
Nächstes Kapitel: Pandas Tutorial Continuation: multi-level indexing