Pandas ist die De-facto-Toolbox für Python-Datenwissenschaftler, um Datenanalysen zu vereinfachen: Du kannst sie zum Beispiel vor der eigentlichen Analyse nutzen, um Daten zu sammeln, zu erkunden und zu formatieren. Dank zahlreicher I/O- und praktischer Datenmanipulationsfunktionen gehen diese Schritte mit Pandas spielend leicht von der Hand. Außerdem – wie du später in diesem Tutorial lernst – bietet es auch einfache Visualisierungsmöglichkeiten (über matplotlib).
Auch wenn die Pandas-Datenstrukturen sehr leistungsfähig und flexibel sind, bringen sie gewisse Eigenheiten mit, die deine Analyse gerade am Anfang eher kniffliger als einfacher machen können. In diesem Tutorial konzentrierst du dich darauf, Kopfschmerzen zu vermeiden und idiomatischeren Pandas-Code zu schreiben. Neben dem Laden, Erkunden und Bereinigen der Daten lernst du diese fünf Themen kennen:
Natürlich ist dieses Tutorial nicht vollständig; Das Pandas-Paket ist extrem umfangreich und es gibt zweifellos noch viele weitere Wege, deinen Pandas-Code idiomatischer zu machen.
Also los geht’s!
Erster Schritt: Daten laden und erkunden
Wie immer beginnt deine Datenanalyse mit dem Laden und Erkunden der Daten. Diese Sektion ist nur ein Warm-up: Du nutzt die Funktion read_csv(), um weltweite Universitätsrankings zu laden, und erkundest die Daten anschließend mit head() und describe(). Wenn du eine vollständige Pandas-Einführung suchst, wirf einen Blick auf DataCamps dreiteiligen Pandas-Kurs, beginnend mit Pandas Foundations.
Wenn dir das alles schon vertraut ist, kannst du diese Sektion überspringen und direkt lernen, wie du Indexing in Pandas idiomatischer nutzt.
Pakete importieren
Zum Start importieren wir die gängigen Python-Pakete für Data Science. Wie du dir denken kannst, gehört Pandas dazu.
# If you're working with a notebook, don't forget to use Matplotlib magic! %matplotlib inline
# Import pandas under the alias pd import pandas as pd # Import seaborn under the alias sns import seaborn as sns # Set the Seaborn theme if desired sns.set_style('darkgrid')Bereitgestellt von
Außerdem kannst du das Paket watermark nutzen: Ein hilfreiches Tool, um dein Notebook reproduzierbarer zu machen. Reproduzierbare Arbeit ist entscheidend für die Zusammenarbeit im Team – und dein zukünftiges Ich wird es dir danken! Mehr zu reproduzierbarer Data Science findest du hier.
Die Daten
Für dieses Tutorial brauchst du „echte“ Daten aus der Welt. Wir arbeiten mit einem Datensatz von Kaggle. Falls dir Kaggle neu ist: Das ist eine der größten Communities für Data Science und Machine Learning – und ein guter Ort, um nach den Grundlagen weiterzulernen.
Wie oben erwähnt, hat Pandas viele bequeme Methoden, um Daten aus unterschiedlichen Quellen zu lesen (mehr dazu hier). Da die Daten im CSV-Format vorliegen, verwenden wir die Methode .read_csv. Bevor es losgeht, musst du die Daten von Kaggle herunterladen (oder aus dem Code-Repository im Ordner data nehmen, wenn du keinen Account anlegen willst).
Nach dem Download solltest du einen Ordner data mit mehreren CSV-Dateien haben.
# Import Times Higher Education World University Rankings data
times_df = pd.read_csv('data/timesData.csv', thousands=",")# Import Academic Ranking of World Universities data
shanghai_df = pd.read_csv('data/shanghaiData.csv')Bereitgestellt von
Daten schnell inspizieren
Wie du im DataCamp-Exploratory Data Analysis Tutorial gelernt hast, bietet Pandas Methoden, um DataFrames schnell zu prüfen – namentlich .head() für die ersten n Zeilen (n ist standardmäßig 5) und .describe() für eine schnelle statistische Zusammenfassung.
Frische dein Wissen auf, indem du die folgenden Codezeilen ausführst. Die Daten sind bereits in den Variablen times_df und shanghai_df geladen:
# Return the first rows of `times_df`
times_df.____()
# Describe `times_df`
times_df.describe()
# Return the first rows of `shanghai_df`
shanghai_df.head()
# Describe `shanghai_df`
shanghai_df.________()
Jetzt, da die Daten geladen sind, kannst du überlegen, wie du sie mit Pandas idiomatischer manipulierst!
Unsere Tipps für idiomatischen Pandas-Code
1. Indexing
Idiomatischer Pandas-Code nutzt die Stärke des Indexings. Indexing bedeutet, dass du Teilmengen deines DataFrames auswählst.
Wenn du neu mit Pandas startest, brauchst du etwas Zeit, um dich an Indizes zu gewöhnen. Wenn du mit (Python-)Listen gearbeitet hast, kennst du wahrscheinlich schon eckige Klammern [] in Kombination mit dem Doppelpunkt :, um Elemente auszuwählen. Das funktioniert auch bei DataFrames.
Daneben gibt es zwei weitere, idiomatischere Wege, eine Teilmenge zu wählen: iloc und loc:
loc ist labelbasiert. Wenn du loc[2] schreibst, suchst du die Werte mit dem Index-Label 2.
iloc ist positionsbasiert. Wenn du iloc[2] schreibst, suchst du die Werte an der Indexposition 2.
Probiere das unten im DataCamp-Light-Snippet aus, indem du times_df.loc[2] und times_df.iloc[2] in die IPython-Konsole eingibst. Löse anschließend die Übung mithilfe von loc, iloc und den klassischen eckigen Klammern!
# Retrieve the total score of the first row
print(times_df.loc[0, '___________'])
# Retrieve rows 0 and 1
print(times_df[_:_])
# Retrieve the values at columns and rows 1-3
print(times_df.iloc[1:4,1:4])
# Retrieve the column `total_score`
print(times_df['__________'])
Natürlich kannst du viel weiter gehen, als nur Zeilen und Spalten auszuwählen. Besonders spannend wird es, wenn du loc und iloc mit booleschen Arrays kombinierst.
Das geht schon in Richtung „Abfragen“ deiner Daten!
# Are the last entries after 2006?
print(shanghai_df.loc[:-10, 'year'] > 2006)
# Was the alumni count higher than 90 for the first ten universities?
print(shanghai_df.loc[0:11, 'alumni'] > 90)
loc und iloc sind großartig zum Indexieren – aber Vorsicht, wenn du zwei Sätze eckiger Klammern hintereinander nutzt! Pandas kann dann eine Kopie einer Ansicht zurückgeben, und du weißt nicht mehr sicher, worauf du arbeitest. Denke also zweimal nach, wenn du doppelte eckige Klammern mit loc oder iloc kombinierst.
Hinweis: Für Abfragen stellt Pandas auch die Funktion query() bereit (seit Version 0.13), mit der du Daten schnell inspizieren kannst. Beispiel: Welche Universitäten haben einen total_score knapp unter 50?
# Query `shanghai_df` for universities with total score between 40 and 50
averge_schools = shanghai_df.query('total_score > __ and total_score < __')
# Print the result
print(average_schools)
Es gibt natürlich unzählige weitere Möglichkeiten zum Abfragen. Probier doch Anfragen, die Universitäten mit nationalem Rang 1 und mit weltweitem Rang 1 zurückgeben. Du kannst dir auch die Alumni-Zahlen ansehen.
Unsicher, wie du die Abfragen formulierst? Hier ein paar Beispiele:
shanghai_df.query("national_rank == 1 and world_rank == 1")
shanghai_df.query("alumni < 20")Bereitgestellt von
2. Method Chaining
Method Chaining ist typisch für Pandas, kann aber anfangs verwirren. Im Kern rufst du Methoden nacheinander auf demselben Objekt auf.
In diesem Abschnitt vertiefst du Method Chaining, indem du Datenpipelines mit pipe() erstellst.
# Extract info
def extract_info(input_df, name):
df = input_df.copy()
info_df = pd.DataFrame({'nb_rows': df.shape[0], 'nb_cols': df.shape[1], 'name': name}, index=range(1))
return info_df
# Gather all info
all_info = pd.concat([times_df.pipe(extract_info, 'times'), shanghai_df.pipe(extract_info, 'shanghai')])
Wie du siehst, unterscheiden sich die Datensätze deutlich. Ein paar Unterschiede:
Der Times-Datensatz hat 14 Spalten, der Shanghai-Datensatz 11.
Times hat 2603 Zeilen, Shanghai 4897.
Beim Shanghai-Datensatz fehlt u. a. das Land der Universität. Diese Info kannst du später bei Bedarf aus Times holen.
Damit wir beide DataFrames gemeinsam nutzen können, wählen wir nur die gemeinsamen Spalten – idealerweise mit vergleichbarem Inhalt.
total_score: Score zur Bestimmung des Rangs. Laut Kaggle-Beschreibung enthält diese Spalte Bereichsränge (z. B. zwischen zwei Werten) und Gleichstände (mit = davor)
university_name: Name der Universität
world_rank: Weltrang der Universität
year: Jahr des Rankings
Bevor wir beide Daten zusammenführen, bereinigen wir sie. Wende die komplette Pipeline an, um die Daten zu säubern:
# Clean up the `world_rank`
def clean_world_rank(input_df):
df = input_df.copy()
df.world_rank = df.world_rank.str.split('-').str[0].str.split('=').str[0]
return df
# Assign the common years of `shanghai_df` and `times_df` to `common_years`
common_years = set(shanghai_df.year) & set(times_df.year)
# Print `common_years`
print(common_years)
# Filter years
def filter_year(input_df, years):
df = input_df.copy()
return df.query('year in {}'.format(list(years)))
# Clean `times_df` and `shanghai_df`
cleaned_times_df = (times_df.loc[:, common_columns]
.pipe(filter_year, common_years)
.pipe(clean_world_rank)
.assign(name='times'))
cleaned_shanghai_df = (shanghai_df.loc[:, common_columns]
.pipe(filter_year, common_years)
.pipe(clean_world_rank)
.assign(name='shanghai'))
Wie du gemerkt hast, nutze ich in diesem Tutorial häufig die Methode .pipe. Wenn du aus der R-Welt kommst, kommt sie dir vermutlich bekannt vor.
Sie ist seit Pandas 0.16.2 verfügbar und erlaubt dir, Operationen zu verketten und so Zwischen-DataFrames zu vermeiden. Der Code wird deutlich lesbarer.
Ohne diesen Operator würdest du statt df.pipe(f).pipe(g).pipe(h) so etwas wie h(g(f(df))) schreiben – je mehr Verschachtelungen, desto unübersichtlicher.
Wenn du tiefer einsteigen willst, empfehle ich diesen Blog-Post.
Nachdem beide DataFrames bereinigt sind, können wir sie zu einem DataFrame konkatenieren. Vergiss nicht, fehlende Daten zu bereinigen:
# Compose `ranking_df` with `cleaned_times_df` and `cleaned_shanghai_df`
ranking_df = pd.concat([cleaned_times_df, cleaned_shanghai_df])
# Calculate the percentage of missing data
missing_data = 100 * pd.isnull(ranking_df.total_score).sum() / len(ranking_df)
# Drop the `total_score` column of `ranking_df`
ranking_df = ranking_df.drop('total_score', axis=1)
Da in der Spalte total_score rund 38% der Werte fehlen, solltest du sie besser mit der Methode .drop entfernen.
3. Memory Optimization
Ein dritter Weg zu idiomatischerem – sprich performanterem – Pandas-Code ist die Optimierung der Speichernutzung. Besonders bei Pipelines mit Method Chaining spielt Speicher schnell eine große Rolle für die Laufzeit. Hier siehst du mehr dazu.
Bei diesem kleinen Datensatz ist es vielleicht überdimensioniert, aber oft lohnt es sich, Spalten auf passendere Typen zu casten, um Speicher zu sparen. Finde zuerst mit der Methode .info heraus, wie viel Speicher ranking_df belegt.
# Print the memory usage of `ranking_df`
ranking_df.____()
# Print the deep memory usage of `ranking_df`
ranking_df.info(memory_usage="deep")
Die erste Ausgabe zeigt 144 KB. Inspiziert man die Argumente von .info, findet man das optionale memory_usage, standardmäßig None. Was passiert bei memory_usage = deep?
Der neue Speicherbedarf ist rund 5,6-mal höher als zuvor.
Woran liegt das?
Ohne das „deep“-Flag schätzt Pandas den Speicher für den dtype object nicht. Dieser (Python-)Typ braucht deutlich mehr Platz als optimierte numpy-dtypes. Daher empfiehlt es sich, object-Spalten in passendere Typen zu casten (z. B. category bei kategorialen Daten).
Lass es uns tun!
defmemory_change(input_df, column, dtype):
df = input_df.copy()
old =round(df[column].memory_usage(deep=True)/1024,2)# In KB
new =round(df[column].astype(dtype).memory_usage(deep=True)/1024,2)# In KB
change =round(100*(old - new)/(old),2)
report =("The inital memory footprint for {column} is: {old}KB.\n""The casted {column} now takes: {new}KB.\n""A change of {change} %.").format(**locals())return report
print(memory_change(ranking_df,'world_rank','int16'))print(memory_change(ranking_df,'university_name','category'))print(memory_change(ranking_df,'name','category'))Bereitgestellt von
Da du nun weißt, dass z. B. world_rank als int16 Speicher spart, wendest du die Änderungen an. Nutze dafür astype() und prüfe abschließend erneut die Speichernutzung:
# Cast `world_rank` as type `int16`
ranking_df.world_rank = ranking_df.world_rank._______('int16')
# Cast `unversity_name` as type `category`
ranking_df.university_name = ranking_df.university_name.astype('category')
# Cast `name` as type `category`
ranking_df.name = ranking_df.____.astype('category')
# Double check the memory usage after type casting
ranking_df.info(memory_usage='deep')
Schon deutlich besser. Noch weiter optimierst du, indem du year auf int32 heruntercastest.
4. GroupBy
Jetzt haben wir einen sauberen Datensatz (sprich: tidy) mit optimiertem Speicher – die Analyse kann starten.
Spannende Fragen sind zum Beispiel:
Welche 5 Unis sind in jedem Jahr laut jedem Rankingsystem ganz vorne?
Wie stark unterscheiden sich die Rankings pro Jahr?
Beantworten wir zuerst Frage 1 – natürlich mit den idiomatischen Pandas-Ansätzen aus den vorherigen Abschnitten!
Vorab: „Massachusetts Institute of Technology (MIT)“ und „Massachusetts Institute of Technology“ sind doppelte Einträge derselben Uni. Wir vereinheitlichen den Namen.
In solchen Fällen ist loc, das du bereits kennst, besonders nützlich!
# Query for the rows with university name 'Massachusetts Institute of Technology (MIT)'
print(ranking_df._____("university_name == 'Massachusetts Institute of Technology (MIT)'"))
# Localize the rows with the MIT university name and replace value
ranking_df.loc[lambda df: df.university_name == 'Massachusetts Institute of Technology (MIT)', 'university_name'] = 'Massachusetts Institute of Technology'
# Print `ranking_df`
print(__________)
Um die 5 (allgemeiner n) Top-Unis je Jahr und Ranking zu finden, sieht die Pseudologik so aus:
Für jedes Jahr (year) und jedes Rankingsystem (name):
Teilmenge für Jahr und Rankingsystem auswählen
Top 5 Unis ermitteln und in einer Liste speichern
Ergebnis in einem Dictionary unter (year, name) als Schlüssel ablegen, Wert ist die Liste der Unis (absteigend)
Wenden wir das an.
# Load in `itertools`
import itertools
# Initialize `ranking`
ranking = {}
for year, name in itertools.product(common_years, ["times", "shanghai"]):
s = (ranking_df.loc[lambda df: ((df.year == year) & (df.name == name)
& (df.world_rank.isin(range(1,6)))), :]
.sort_values('world_rank', ascending=False)
.university_name)
ranking[(year, name)] = list(s)
# Print `ranking`
print(ranking)
Jetzt, da wir dieses Ranking-Dictionary haben, wollen wir wissen, wie stark (in Prozent) sich beide Rankings über die Jahre unterscheiden: Sie sind zu 100% set-ähnlich, wenn die ausgewählten Top-5-Unis identisch sind – auch wenn die Reihenfolge unterschiedlich ist.
# Import `defaultdict`
from collections import defaultdict
# Initialize `compare`
compare = defaultdict(list)
# Initialize `exact_similarity` and `set_similarity`
exact_similarity = {}
set_similarity = {}
for (year, method), universities in ranking.items():
compare[year].append(universities)
for year, ranks in compare.items():
set_similarity[year] = 100 * len(set(ranks[0]) & set(ranks[1])) / 5.0
# Print `set_similarity`
print(set_similarity)
Ganz schön aufwendig. Geht das idiomatischer?
Klar! Filtere den Ranking-DataFrame zuerst so, dass je Jahr und Rankingmethode nur die Top 5 übrigbleiben. Prüfe das Ergebnis mit head():
# Construct a DataFrame with the top 5 universities
top_5_df = ranking_df.loc[lambda df: df.world_rank.isin(range(1, 6)), :]
# Print the first rows of `top_5_df`
top_5_df.____()
Jetzt, da du den passenden DataFrame hast, nutze .groupby, um beide Rankingmethoden über die Set-Ähnlichkeit zu vergleichen.
# Compute the similarity
def compute_set_similarity(s):
pivoted = s.pivot(values='world_rank', columns='name', index='university_name').dropna()
set_simlarity = 100 * len((set(pivoted['shanghai'].index) & set(pivoted['times'].index))) / 5
return set_simlarity
# Group `top_5_df` by `year`
grouped_df = top_5_df._______('year')
# Use `compute_set_similarity` to construct a DataFrame
setsimilarity_df = pd.DataFrame({'set_similarity': grouped_df.apply(compute_set_similarity)}).reset_index()
# Print the first rows of `setsimilarity_df`
setsimilarity_df.____()
Du erhältst mit weniger Aufwand dasselbe Ergebnis wie oben.
5. Visualization
Nach der ersten Analyse ist es Zeit für visuelle Exploration. Idiomatischer Pandas-Code nutzt die Plot-Integration mit Matplotlib – so erstellst du im Handumdrehen starke Plots!
Für diesen Abschnitt kehren wir zu times_df und shanghai_df zurück und erstellen einfache Visualisierungen mit Matplotlib und Seaborn.
# Plot a scatterplot with `total_score` and `alumni`
shanghai_df.plot.scatter('total_score', 'alumni', c='year', colormap='viridis')
plt.show()
Der Plot zeigt sofort: In der Spalte alumni gibt es einige 0-Werte. Genau solche Auffälligkeiten deckt Datenprofiling auf, damit du sie behandeln kannst. Mehr dazu im DataCamp-Data Profiling Tutorial.
Wenn Werte in deinem Datensatz nichts verloren haben, kannst du sie durch NaN ersetzen und anschließend diese NaN-Zeilen für betroffene Spalten entfernen – so wird das Plotten einfacher. Hinweis: Das ist nur ein schneller Fix; für echte Datenqualität brauchst du ein gründlicheres Profiling. Unten siehst du z. B. immer noch 0-Werte in num_students:
Natürlich musst du dich nicht auf Matplotlib beschränken. Es gibt weitere Bibliotheken, mit denen du schnell visualisieren kannst, etwa Seaborn.
Seaborn eignet sich besonders für ansprechende statistische Plots (siehe Website). Vor Matplotlib 2.0 war es deutlich mühsamer, schöne Plots zu erstellen.
Die Kombination aus Pandas und Seaborn erlaubt dir schnelle visuelle Iterationen über deine Daten.
Schauen wir uns Beispiele an!
Im folgenden Plot zählst du, wie oft ein Land auftaucht, nimmst die Top 10 und sortierst die Balken von hoch nach niedrig:
# Abbreviate country names of United States and United Kingdom
times_df['country'] = times_df['country'].replace("United States of America", "USA").replace("United Kingdom", "UK")
# Count the frequency of countries
count = times_df['country'].value_counts()[:10]
# Convert the top 10 countries to a DataFrame
df = count.to_frame()
# Reset the index
df.reset_index(level=0, inplace=True)
# Rename the columns
df.columns = ['country', 'count']
# Plot a barplot with `country` and `count`
sns.barplot(x='country', y='count', data=df)
sns.despine()
plt.show()
Anschließend kannst du diese Länder mit ihren durchschnittlichen Scores plotten:
# Barplot with `country` and `total_score`
sns.barplot(x='country', y='total_score', data=times_df_filtered)
sns.despine()
plt.show()
Plots können auch recht komplex werden. Schau dir den folgenden Pairplot an, der paarweise Beziehungen im Datensatz sichtbar macht:
import numpy as np
np.seterr(invalid='ignore')
sns.pairplot(times_df, hue='country')
plt.show()Bereitgestellt von
Ebenso hilfreich ist die Kombination aus FacetGrid und regplot, die dir die Entwicklung des total_score über die Jahre für die Top-5-Länder zeigt:
# Filter out rows with NaN entry for `total_score`
times_df_filtered = times_df_filtered.dropna(subset=['total_score'], how='all')
g = sns.FacetGrid(times_df_filtered, col='country', hue='country')
g.map(sns.regplot, 'year', 'total_score').set(xlim=(2010, 2015), ylim=(0,100))
g.fig.subplots_adjust(wspace=.2)
Zum Schluss ist auch eine Korrelationsmatrix praktisch. Mit Seaborns heatmap() klappt das im Handumdrehen:
import matplotlib.pyplot as plt
sns.set(style="white")
# Compute the correlation matrix
corr = times_df.corr()
# Generate a mask for the upper triangle
mask = np.zeros_like(corr, dtype=np.bool)
mask[np.triu_indices_from(mask)] = True
# Set up the matplotlib figure
f, ax = plt.subplots(figsize=(11, 9))
# Generate a custom diverging colormap
cmap = sns.diverging_palette(220, 10, as_cmap=True)
# Draw the heatmap with the mask and correct aspect ratio
sns.heatmap(corr, mask=mask, cmap=cmap, vmax=.3,
square=True, linewidths=.5, ax=ax)
plt.show()
Mit Visualisierungen könntest du ewig weitermachen – wir belassen es hier erst einmal :)
Nächste Schritte
Das war’s für den Moment. Ich hoffe, du hattest Spaß mit diesen Techniken rund um Pandas – und speziell damit, idiomatischeren Pandas-Code zu schreiben.
Wenn dir dieser Beitrag gefallen hat und du mehr über Performance in Pandas lernen willst, empfehle ich folgendes Zusatzmaterial:
Mach dich mit Python-Datenstrukturen vertraut: Lerne mehr über Datentypen und primitive sowie nicht-primitive Datenstrukturen wie Strings, Listen, Stapel usw.
Pandas ist die De-facto-Toolbox für Python-Datenwissenschaftler, um Datenanalysen zu vereinfachen: Du kannst sie zum Beispiel vor der eigentlichen Analyse nutzen, um Daten zu sammeln, zu erkunden und zu formatieren. Dank zahlreicher I/O- und praktischer Datenmanipulationsfunktionen gehen diese Schritte mit Pandas spielend leicht von der Hand. Außerdem – wie du später in diesem Tutorial lernst – bietet es auch einfache Visualisierungsmöglichkeiten (über
matplotlib).Auch wenn die Pandas-Datenstrukturen sehr leistungsfähig und flexibel sind, bringen sie gewisse Eigenheiten mit, die deine Analyse gerade am Anfang eher kniffliger als einfacher machen können. In diesem Tutorial konzentrierst du dich darauf, Kopfschmerzen zu vermeiden und idiomatischeren Pandas-Code zu schreiben. Neben dem Laden, Erkunden und Bereinigen der Daten lernst du diese fünf Themen kennen:
Natürlich ist dieses Tutorial nicht vollständig; Das Pandas-Paket ist extrem umfangreich und es gibt zweifellos noch viele weitere Wege, deinen Pandas-Code idiomatischer zu machen.
Also los geht’s!
Erster Schritt: Daten laden und erkunden
Wie immer beginnt deine Datenanalyse mit dem Laden und Erkunden der Daten. Diese Sektion ist nur ein Warm-up: Du nutzt die Funktion
read_csv(), um weltweite Universitätsrankings zu laden, und erkundest die Daten anschließend mithead()unddescribe(). Wenn du eine vollständige Pandas-Einführung suchst, wirf einen Blick auf DataCamps dreiteiligen Pandas-Kurs, beginnend mit Pandas Foundations.Wenn dir das alles schon vertraut ist, kannst du diese Sektion überspringen und direkt lernen, wie du Indexing in Pandas idiomatischer nutzt.
Pakete importieren
Zum Start importieren wir die gängigen Python-Pakete für Data Science. Wie du dir denken kannst, gehört Pandas dazu.
Außerdem kannst du das Paket
watermarknutzen: Ein hilfreiches Tool, um dein Notebook reproduzierbarer zu machen. Reproduzierbare Arbeit ist entscheidend für die Zusammenarbeit im Team – und dein zukünftiges Ich wird es dir danken! Mehr zu reproduzierbarer Data Science findest du hier.Die Daten
Für dieses Tutorial brauchst du „echte“ Daten aus der Welt. Wir arbeiten mit einem Datensatz von Kaggle. Falls dir Kaggle neu ist: Das ist eine der größten Communities für Data Science und Machine Learning – und ein guter Ort, um nach den Grundlagen weiterzulernen.
Der Datensatz, den du nutzt, sind die World University Rankings.
Wie oben erwähnt, hat Pandas viele bequeme Methoden, um Daten aus unterschiedlichen Quellen zu lesen (mehr dazu hier). Da die Daten im
CSV-Format vorliegen, verwenden wir die Methode.read_csv. Bevor es losgeht, musst du die Daten von Kaggle herunterladen (oder aus dem Code-Repository im Ordnerdatanehmen, wenn du keinen Account anlegen willst).Nach dem Download solltest du einen Ordner
datamit mehreren CSV-Dateien haben.Als Nächstes arbeitest du mit den Daten des Times Higher Education World University Rankings (kurz Times) und dem Academic Ranking of World Universities (kurz Shanghai). Im Ordner
dataliegt auch noch ein drittes Ranking CWUR, das aber weniger bekannt ist – du kannst es vorerst ignorieren.Daten schnell inspizieren
Wie du im DataCamp-Exploratory Data Analysis Tutorial gelernt hast, bietet Pandas Methoden, um DataFrames schnell zu prüfen – namentlich
.head()für die erstennZeilen (nist standardmäßig 5) und.describe()für eine schnelle statistische Zusammenfassung.Frische dein Wissen auf, indem du die folgenden Codezeilen ausführst. Die Daten sind bereits in den Variablen
times_dfundshanghai_dfgeladen:Jetzt, da die Daten geladen sind, kannst du überlegen, wie du sie mit Pandas idiomatischer manipulierst!
Unsere Tipps für idiomatischen Pandas-Code
1. Indexing
Idiomatischer Pandas-Code nutzt die Stärke des Indexings. Indexing bedeutet, dass du Teilmengen deines DataFrames auswählst.
Wenn du neu mit Pandas startest, brauchst du etwas Zeit, um dich an Indizes zu gewöhnen. Wenn du mit (Python-)Listen gearbeitet hast, kennst du wahrscheinlich schon eckige Klammern
[]in Kombination mit dem Doppelpunkt:, um Elemente auszuwählen. Das funktioniert auch bei DataFrames.Daneben gibt es zwei weitere, idiomatischere Wege, eine Teilmenge zu wählen:
ilocundloc:locist labelbasiert. Wenn duloc[2]schreibst, suchst du die Werte mit dem Index-Label2.ilocist positionsbasiert. Wenn duiloc[2]schreibst, suchst du die Werte an der Indexposition2.Probiere das unten im DataCamp-Light-Snippet aus, indem du
times_df.loc[2]undtimes_df.iloc[2]in die IPython-Konsole eingibst. Löse anschließend die Übung mithilfe vonloc,ilocund den klassischen eckigen Klammern!Natürlich kannst du viel weiter gehen, als nur Zeilen und Spalten auszuwählen. Besonders spannend wird es, wenn du
locundilocmit booleschen Arrays kombinierst.Das geht schon in Richtung „Abfragen“ deiner Daten!
locundilocsind großartig zum Indexieren – aber Vorsicht, wenn du zwei Sätze eckiger Klammern hintereinander nutzt! Pandas kann dann eine Kopie einer Ansicht zurückgeben, und du weißt nicht mehr sicher, worauf du arbeitest. Denke also zweimal nach, wenn du doppelte eckige Klammern mitlocoderilockombinierst.Hinweis: Für Abfragen stellt Pandas auch die Funktion
query()bereit (seit Version 0.13), mit der du Daten schnell inspizieren kannst. Beispiel: Welche Universitäten haben einentotal_scoreknapp unter 50?Es gibt natürlich unzählige weitere Möglichkeiten zum Abfragen. Probier doch Anfragen, die Universitäten mit nationalem Rang 1 und mit weltweitem Rang 1 zurückgeben. Du kannst dir auch die Alumni-Zahlen ansehen.
Unsicher, wie du die Abfragen formulierst? Hier ein paar Beispiele:
2. Method Chaining
Method Chaining ist typisch für Pandas, kann aber anfangs verwirren. Im Kern rufst du Methoden nacheinander auf demselben Objekt auf.
In diesem Abschnitt vertiefst du Method Chaining, indem du Datenpipelines mit
pipe()erstellst.Wie du siehst, unterscheiden sich die Datensätze deutlich. Ein paar Unterschiede:
Damit wir beide DataFrames gemeinsam nutzen können, wählen wir nur die gemeinsamen Spalten – idealerweise mit vergleichbarem Inhalt.
Die gemeinsamen Spalten sind also:
total_score: Score zur Bestimmung des Rangs. Laut Kaggle-Beschreibung enthält diese Spalte Bereichsränge (z. B. zwischen zwei Werten) und Gleichstände (mit=davor)university_name: Name der Universitätworld_rank: Weltrang der Universitätyear: Jahr des RankingsBevor wir beide Daten zusammenführen, bereinigen wir sie. Wende die komplette Pipeline an, um die Daten zu säubern:
Wie du gemerkt hast, nutze ich in diesem Tutorial häufig die Methode
.pipe. Wenn du aus der R-Welt kommst, kommt sie dir vermutlich bekannt vor.Sie ist seit Pandas 0.16.2 verfügbar und erlaubt dir, Operationen zu verketten und so Zwischen-DataFrames zu vermeiden. Der Code wird deutlich lesbarer.
Ohne diesen Operator würdest du statt
df.pipe(f).pipe(g).pipe(h)so etwas wieh(g(f(df)))schreiben – je mehr Verschachtelungen, desto unübersichtlicher.Wenn du tiefer einsteigen willst, empfehle ich diesen Blog-Post.
Nachdem beide DataFrames bereinigt sind, können wir sie zu einem DataFrame konkatenieren. Vergiss nicht, fehlende Daten zu bereinigen:
Da in der Spalte
total_scorerund 38% der Werte fehlen, solltest du sie besser mit der Methode.dropentfernen.3. Memory Optimization
Ein dritter Weg zu idiomatischerem – sprich performanterem – Pandas-Code ist die Optimierung der Speichernutzung. Besonders bei Pipelines mit Method Chaining spielt Speicher schnell eine große Rolle für die Laufzeit. Hier siehst du mehr dazu.
Bei diesem kleinen Datensatz ist es vielleicht überdimensioniert, aber oft lohnt es sich, Spalten auf passendere Typen zu casten, um Speicher zu sparen. Finde zuerst mit der Methode
.infoheraus, wie viel Speicherranking_dfbelegt.Die erste Ausgabe zeigt 144 KB. Inspiziert man die Argumente von
.info, findet man das optionalememory_usage, standardmäßig None. Was passiert beimemory_usage=deep?Der neue Speicherbedarf ist rund 5,6-mal höher als zuvor.
Woran liegt das?
Ohne das „deep“-Flag schätzt Pandas den Speicher für den dtype
objectnicht. Dieser (Python-)Typ braucht deutlich mehr Platz als optimiertenumpy-dtypes. Daher empfiehlt es sich,object-Spalten in passendere Typen zu casten (z. B.categorybei kategorialen Daten).Lass es uns tun!
Da du nun weißt, dass z. B.
world_rankalsint16Speicher spart, wendest du die Änderungen an. Nutze dafürastype()und prüfe abschließend erneut die Speichernutzung:Schon deutlich besser. Noch weiter optimierst du, indem du
yearaufint32heruntercastest.4. GroupBy
Jetzt haben wir einen sauberen Datensatz (sprich: tidy) mit optimiertem Speicher – die Analyse kann starten.
Spannende Fragen sind zum Beispiel:
Beantworten wir zuerst Frage 1 – natürlich mit den idiomatischen Pandas-Ansätzen aus den vorherigen Abschnitten!
Vorab: „Massachusetts Institute of Technology (MIT)“ und „Massachusetts Institute of Technology“ sind doppelte Einträge derselben Uni. Wir vereinheitlichen den Namen.
In solchen Fällen ist
loc, das du bereits kennst, besonders nützlich!Um die 5 (allgemeiner
n) Top-Unis je Jahr und Ranking zu finden, sieht die Pseudologik so aus:year) und jedes Rankingsystem (name):Wenden wir das an.
Jetzt, da wir dieses Ranking-Dictionary haben, wollen wir wissen, wie stark (in Prozent) sich beide Rankings über die Jahre unterscheiden: Sie sind zu 100% set-ähnlich, wenn die ausgewählten Top-5-Unis identisch sind – auch wenn die Reihenfolge unterschiedlich ist.
Ganz schön aufwendig. Geht das idiomatischer?
Klar! Filtere den Ranking-DataFrame zuerst so, dass je Jahr und Rankingmethode nur die Top 5 übrigbleiben. Prüfe das Ergebnis mit
head():Jetzt, da du den passenden DataFrame hast, nutze
.groupby, um beide Rankingmethoden über die Set-Ähnlichkeit zu vergleichen.Du erhältst mit weniger Aufwand dasselbe Ergebnis wie oben.
5. Visualization
Nach der ersten Analyse ist es Zeit für visuelle Exploration. Idiomatischer Pandas-Code nutzt die Plot-Integration mit Matplotlib – so erstellst du im Handumdrehen starke Plots!
Für diesen Abschnitt kehren wir zu
times_dfundshanghai_dfzurück und erstellen einfache Visualisierungen mit Matplotlib und Seaborn.Der Plot zeigt sofort: In der Spalte
alumnigibt es einige 0-Werte. Genau solche Auffälligkeiten deckt Datenprofiling auf, damit du sie behandeln kannst. Mehr dazu im DataCamp-Data Profiling Tutorial.Wenn Werte in deinem Datensatz nichts verloren haben, kannst du sie durch
NaNersetzen und anschließend dieseNaN-Zeilen für betroffene Spalten entfernen – so wird das Plotten einfacher. Hinweis: Das ist nur ein schneller Fix; für echte Datenqualität brauchst du ein gründlicheres Profiling. Unten siehst du z. B. immer noch 0-Werte innum_students:Natürlich musst du dich nicht auf Matplotlib beschränken. Es gibt weitere Bibliotheken, mit denen du schnell visualisieren kannst, etwa Seaborn.
Seaborn eignet sich besonders für ansprechende statistische Plots (siehe Website). Vor Matplotlib 2.0 war es deutlich mühsamer, schöne Plots zu erstellen.
Die Kombination aus Pandas und Seaborn erlaubt dir schnelle visuelle Iterationen über deine Daten.
Schauen wir uns Beispiele an!
Im folgenden Plot zählst du, wie oft ein Land auftaucht, nimmst die Top 10 und sortierst die Balken von hoch nach niedrig:
Anschließend kannst du diese Länder mit ihren durchschnittlichen Scores plotten:
Plots können auch recht komplex werden. Schau dir den folgenden Pairplot an, der paarweise Beziehungen im Datensatz sichtbar macht:
Ebenso hilfreich ist die Kombination aus
FacetGridundregplot, die dir die Entwicklung destotal_scoreüber die Jahre für die Top-5-Länder zeigt:Zum Schluss ist auch eine Korrelationsmatrix praktisch. Mit Seaborns
heatmap()klappt das im Handumdrehen:Mit Visualisierungen könntest du ewig weitermachen – wir belassen es hier erst einmal :)
Nächste Schritte
Das war’s für den Moment. Ich hoffe, du hattest Spaß mit diesen Techniken rund um Pandas – und speziell damit, idiomatischeren Pandas-Code zu schreiben.
Wenn dir dieser Beitrag gefallen hat und du mehr über Performance in Pandas lernen willst, empfehle ich folgendes Zusatzmaterial: