# === DO NOT PLOT ===
import matplotlib.pyplot as plt
import seaborn as sns
# === Farbschema ===
primary_color = "#089289"
background_color = "#f2f2f2"
text_color = "#282a36"
grid_color = "#44475a"
# === Matplotlib Style global anpassen ===
plt.rcParams.update({
# Transparenz für gespeicherte Plots
'savefig.transparent': True,
# Transparenz im Plot selbst
'figure.facecolor': 'none', # GANZES Bild transparent
'axes.facecolor': 'none', # Plot-Hintergrund transparent
# Farben und Achsendesign
'axes.edgecolor': text_color,
'axes.labelcolor': text_color,
'xtick.color': text_color,
'ytick.color': text_color,
'text.color': text_color,
# Schriftgrößen & Typografie
'axes.titleweight': 'bold',
'axes.titlesize': 14,
'axes.labelsize': 12,
'xtick.labelsize': 10,
'ytick.labelsize': 10,
# Gitter
'grid.color': grid_color,
'grid.linestyle': '--',
'grid.alpha': 0.5,
# Legende
'legend.edgecolor': 'none',
'legend.facecolor': 'none' # Auch Legendenhintergrund transparent
})
# === Seaborn-Design setzen (für Konsistenz) ===
sns.set_style("whitegrid", {
'axes.facecolor': 'none',
'grid.color': grid_color,
'grid.linestyle': '--',
'axes.edgecolor': background_color, # Optional: weniger visuell störend
'axes.labelcolor': text_color,
'xtick.color': text_color,
'ytick.color': text_color,
'text.color': text_color
})
# === Farbpalette definieren ===
custom_palette = [primary_color]
secondary_colors = ["#ffa600", "#ff6361", "#bc5090", "#58508d", "#003f5c"]
sns.set_palette([primary_color] + secondary_colors)
import matplotlib_inline
matplotlib_inline.backend_inline.set_matplotlib_formats('svg')
Nomen est Omen? - Ein datenjournalistischer Abenteuerroman in mehreren Codeblöcken¶
Philipp und ich, zwei werdende Väter, sitzen an einem sonnigen Mittwochabend im Biergarten und stellen uns eine der vielleicht schwierigsten Frage unseres bisherigen Lebens: Wie nennen wir unsere Kinder? Nicht zu selten, nicht zu abgefahren, keine falschen Assoziationen, bitte leicht auszusprechen – und natürlich soll das Kind damit später Bundeskanzlerin, Gitarrist, Professorin, Bürgermeister oder wenigstens glücklich werden können. Kein Druck also.
Münchner Kindl - Holen wir uns die Namen!¶
Wir starten unsere Mission dort, wo jede gute Geschichte beginnt: im Datenportal der Stadt München. Hier werden die gemeldeten Vornamen der letzten Jahre aller neuen Münchner Kindl gesammelt. Warum sich also nicht etwas inspierieren lassen? Mit dem Suchparameter "vornamen" bekommen wir eine Liste aller verfügbaren Datensets, welche in ihrem Dateinamen "vornamen" beinhalten.
import requests
search_url = "https://opendata.muenchen.de/api/3/action/package_search"
search_params = {"q": "vornamen"}
response = requests.get(search_url, params=search_params)
package = response.json()["result"]["results"][0]
for resource in package['resources']:
print(resource['url'])
https://opendata.muenchen.de/dataset/99ad40ec-9d7b-4a2e-87eb-9bac783fb57a/resource/02ab322e-d33e-4447-a9ab-23df63dfa7e1/download/vornamen-muenchen-2023.csv https://opendata.muenchen.de/dataset/99ad40ec-9d7b-4a2e-87eb-9bac783fb57a/resource/5f11d3a0-4779-4f64-b113-b59326e6d839/download/open_data_portal_2022.csv https://opendata.muenchen.de/dataset/99ad40ec-9d7b-4a2e-87eb-9bac783fb57a/resource/dc6170db-f7f4-4bdc-b790-13df55f0cf64/download/vornamen_2021.csv https://opendata.muenchen.de/dataset/99ad40ec-9d7b-4a2e-87eb-9bac783fb57a/resource/48a4c5fd-f5f2-4c0c-bcb3-222fd9ebda67/download/vornamen_2020.csv https://opendata.muenchen.de/dataset/99ad40ec-9d7b-4a2e-87eb-9bac783fb57a/resource/523d5860-25eb-4bea-96e3-193d1dacfb8f/download/vornamen2019.csv https://opendata.muenchen.de/dataset/99ad40ec-9d7b-4a2e-87eb-9bac783fb57a/resource/23fdd53e-485a-46bd-abb6-d7a51b2ebb18/download/vornamen_2018.csv https://opendata.muenchen.de/dataset/99ad40ec-9d7b-4a2e-87eb-9bac783fb57a/resource/2c70d289-8b1a-4071-9739-866c5e532b77/download/vornamen2017.csv https://opendata.muenchen.de/dataset/99ad40ec-9d7b-4a2e-87eb-9bac783fb57a/resource/6655ec3c-251a-4a5e-9a57-d60c4ad59ca3/download/vornamen2016.csv https://opendata.muenchen.de/dataset/99ad40ec-9d7b-4a2e-87eb-9bac783fb57a/resource/c11e8fef-94c8-4e16-a769-70dc6b98b69b/download/vornamen_2015.csv https://opendata.muenchen.de/dataset/99ad40ec-9d7b-4a2e-87eb-9bac783fb57a/resource/5d581ba3-4c7e-4057-8be1-04d56bb0d8d6/download/vornamen_2014.csv https://opendata.muenchen.de/dataset/99ad40ec-9d7b-4a2e-87eb-9bac783fb57a/resource/17c4915d-6b8b-470f-a6b6-31f61b10a285/download/vornamen_2013.csv
Sieht doch schon mal super aus, jetzt also ab damit in eine einzelne, saubere Tabelle. Datenanalyse kann so schön sein!
import pandas as pd
df = pd.DataFrame()
for resource in package['resources']:
df_jahr = pd.read_csv(resource['url'], delimiter=',')
Jahreszahl = int(resource['url'][-8:-4]) # hier holen wir uns die Jahreszahl aus der URL, das 8.letzte bis 5.letzte Zeichen aus dem String
df_jahr['jahr'] = Jahreszahl
df = pd.concat([df, df_jahr], ignore_index=True)
df
Vorname | Anzahl | Geschlecht | jahr | vorname | anzahl | geschlecht | vornamen;anzahl;geschlecht | vorname;anzahl;geschlecht | |
---|---|---|---|---|---|---|---|---|---|
0 | Leon | 107 | m | 2023 | NaN | NaN | NaN | NaN | NaN |
1 | Maximilian | 105 | m | 2023 | NaN | NaN | NaN | NaN | NaN |
2 | Emilia | 89 | w | 2023 | NaN | NaN | NaN | NaN | NaN |
3 | Paul | 89 | m | 2023 | NaN | NaN | NaN | NaN | NaN |
4 | Noah | 83 | m | 2023 | NaN | NaN | NaN | NaN | NaN |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
46369 | NaN | NaN | NaN | 2013 | NaN | NaN | NaN | NaN | Zora;4 oder weniger;w |
46370 | NaN | NaN | NaN | 2013 | NaN | NaN | NaN | NaN | Zoran;4 oder weniger;m |
46371 | NaN | NaN | NaN | 2013 | NaN | NaN | NaN | NaN | Zorica;4 oder weniger;w |
46372 | NaN | NaN | NaN | 2013 | NaN | NaN | NaN | NaN | Zoubair;4 oder weniger;m |
46373 | NaN | NaN | NaN | 2013 | NaN | NaN | NaN | NaN | Züleyha;4 oder weniger;w |
46374 rows × 9 columns
Ergebnis: Wir haben ein paar CSV-Dateien mit Babynamen, die jährlich veröffentlicht werden. Leider nicht immer gleich formatiert. Aber hey – was ist schon ein bisschen Chaos unter Freunden?
Daten putzen – aka das IKEA-Regal der Datenanalyse¶
Also standardisieren wir Spaltennamen, erkennen mehr oder weniger obskure Trennzeichen, verschiedene Schreibweisen von Spalten, und überhaupt: Wer unterscheidet bitte heute noch zwischen Groß- und Kleinschreibung? – kurz; wir kämpfen uns durch den Datendschungel.
df = pd.DataFrame()
# Mehrere Spalten standardisieren
mapping = {
'vorname': 'vorname',
'vornamen': 'vorname',
'anzahl': 'anzahl',
'häufigkeit': 'anzahl',
'geschlecht': 'geschlecht',
'gender': 'geschlecht'
}
for resource in package['resources']:
df_jahr = pd.read_csv(resource['url'], sep=',|;', engine='python') # versuche ; oder (das oder wird durch | ausgedrückt) ,
Jahreszahl = int(resource['url'][-8:-4]) # hier holen wir uns die Jahreszahl aus der URL, das 8.letzte bis 5.letzte Zeichen aus dem String
df_jahr['jahr'] = Jahreszahl
df_jahr = df_jahr.rename(columns=lambda x: mapping.get(x.lower(), x)) # Spaltennamen standardisieren, unabhängig von Groß-/Kleinschreibung
df = pd.concat([df, df_jahr], ignore_index=True) # Zusammenführen der Daten
df
vorname | anzahl | geschlecht | jahr | |
---|---|---|---|---|
0 | Leon | 107 | m | 2023 |
1 | Maximilian | 105 | m | 2023 |
2 | Emilia | 89 | w | 2023 |
3 | Paul | 89 | m | 2023 |
4 | Noah | 83 | m | 2023 |
... | ... | ... | ... | ... |
46369 | Zora | 4 oder weniger | w | 2013 |
46370 | Zoran | 4 oder weniger | m | 2013 |
46371 | Zorica | 4 oder weniger | w | 2013 |
46372 | Zoubair | 4 oder weniger | m | 2013 |
46373 | Züleyha | 4 oder weniger | w | 2013 |
46374 rows × 4 columns
Erster Blick auf die Daten:
- Spaltenchaos beseitigt ✓
- Trennzeichen überlistet ✓
- Jahreszahl extrahiert ✓
Jetzt haben wir noch das Problem dass wir in der spalte anzahl
Zahlen erwarten, aber manche Namen wurden "4 oder weniger" mal vergeben und kommen im String Format vor. Schön anonymisiert, aber halt für die Analyse ein bisschen nervig. Für uns heißt das: Wir setzen sie einfach auf 1. Sonst wird das nix mit dem Plotten.
df['anzahl'] = df['anzahl'].replace('4 oder weniger', 1)
df['anzahl'] = df['anzahl'].replace('4 oder wenniger', 1)
df['anzahl'] = pd.to_numeric(df['anzahl'])
df['anzahl'] = df['anzahl'].astype('Int64')
df
vorname | anzahl | geschlecht | jahr | |
---|---|---|---|---|
0 | Leon | 107 | m | 2023 |
1 | Maximilian | 105 | m | 2023 |
2 | Emilia | 89 | w | 2023 |
3 | Paul | 89 | m | 2023 |
4 | Noah | 83 | m | 2023 |
... | ... | ... | ... | ... |
46369 | Zora | 1 | w | 2013 |
46370 | Zoran | 1 | m | 2013 |
46371 | Zorica | 1 | w | 2013 |
46372 | Zoubair | 1 | m | 2013 |
46373 | Züleyha | 1 | w | 2013 |
46374 rows × 4 columns
Es gibt noch Leerzeilen ohne Wert. Interessiert uns nicht, also weg damit.
df[df['anzahl'].isna()]
vorname | anzahl | geschlecht | jahr | |
---|---|---|---|---|
21662 | <NA> | NaN | 2019 | |
25845 | <NA> | NaN | 2018 | |
30087 | <NA> | NaN | 2017 | |
34396 | <NA> | NaN | 2016 |
Es gibt, duppletten
# Finde doppelte Einträge
duplicates = df[df.duplicated(subset=['jahr', 'vorname', 'geschlecht'], keep=False)]
# Zeige die doppelten Einträge an
print(f"Anzahl der doppelten Einträge: {len(duplicates)}")
print("\nDoppelte Einträge:")
duplicates_sorted = duplicates.sort_values(['jahr', 'vorname', 'geschlecht'])
display(duplicates_sorted)
Anzahl der doppelten Einträge: 26 Doppelte Einträge:
vorname | anzahl | geschlecht | jahr | |
---|---|---|---|---|
15329 | Ilyas | 15 | m | 2020 |
16578 | Ilyas | 1 | m | 2020 |
13785 | Stefania | 1 | w | 2020 |
15068 | Stefania | 1 | w | 2020 |
17327 | Yagiz | 1 | m | 2020 |
17328 | Yagiz | 1 | m | 2020 |
17349 | Yigit | 1 | m | 2020 |
17350 | Yigit | 1 | m | 2020 |
15960 | Yilmaz | 1 | m | 2020 |
17353 | Yilmaz | 1 | m | 2020 |
9634 | Ayse | 1 | w | 2021 |
9635 | Ayse | 1 | w | 2021 |
12219 | Hizir | 1 | m | 2021 |
12220 | Hizir | 1 | m | 2021 |
11015 | Ilyas | 11 | m | 2021 |
12241 | Ilyas | 1 | m | 2021 |
8958 | Isra | 5 | w | 2021 |
10048 | Isra | 1 | w | 2021 |
11536 | Matej | 1 | m | 2021 |
12528 | Matej | 1 | m | 2021 |
10418 | Nara | 1 | w | 2021 |
10419 | Nara | 1 | w | 2021 |
10661 | Seyma | 1 | w | 2021 |
10662 | Seyma | 1 | w | 2021 |
11361 | Yigit | 1 | m | 2021 |
13019 | Yigit | 1 | m | 2021 |
Wir summieren sie
# 2. Summiere die Anzahlen für doppelte Einträge
df = df.groupby(['jahr', 'vorname', 'geschlecht'])['anzahl'].sum().reset_index()
import seaborn as sns
import matplotlib.pyplot as plt
geburten_pro_jahr_geschlecht = df.groupby(['jahr', 'geschlecht'])['anzahl'].sum().reset_index()
plt.figure(figsize=(10, 6))
sns.lineplot(data=geburten_pro_jahr_geschlecht, x='jahr', y='anzahl', hue='geschlecht', marker='o')
plt.title('Anzahl der Geburten pro Jahr nach Geschlecht')
plt.xlabel('Jahr')
plt.ylabel('Anzahl der Geburten')
plt.legend(title='Geschlecht')
plt.grid(True, linestyle='--')
sns.despine()
plt.tight_layout()
plt.show()
Erkenntnis: Es gibt leichte Schwankungen über die Jahre. Fun Fact: seit 2018 liegen die Jungs jedes Jahr vorne
Top 20 Namen – interaktiv und bunt¶
Jetzt wird’s fancy: Wähle ein Jahr, wir zeigen dir die beliebtesten Namen!
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import plotly.io as pio
pio.renderers.default='notebook'
# Dataframe für Jungen
df_boys = df[df['geschlecht'] == 'm'].pivot(
index='jahr',
columns='vorname',
values='anzahl'
).fillna(0)
# Dataframe für Mädchen
df_girls = df[df['geschlecht'] == 'w'].pivot(
index='jahr',
columns='vorname',
values='anzahl'
).fillna(0)
# Sortiere die Spalten nach der Gesamtsumme
df_boys = df_boys[df_boys.sum().sort_values(ascending=False).index]
df_girls = df_girls[df_girls.sum().sort_values(ascending=False).index]
# Erstelle die Figuren
fig = make_subplots(rows=1, cols=2,
subplot_titles=('Top 10 Jungennamen', 'Top 10 Mädchennamen'),
horizontal_spacing=0.15)
available_years = sorted(df['jahr'].unique())
# Erstelle die Frames für die Animation
frames = []
for year in available_years:
df_year = df[df['jahr'] == year]
top_m = df_year[df_year['geschlecht'] == 'm'].nlargest(10, 'anzahl').sort_values('anzahl')
top_w = df_year[df_year['geschlecht'] == 'w'].nlargest(10, 'anzahl').sort_values('anzahl')
frame = go.Frame(
data=[
go.Bar(x=top_m['anzahl'], y=top_m['vorname'], orientation='h', marker_color=secondary_colors[4]),
go.Bar(x=top_w['anzahl'], y=top_w['vorname'], orientation='h', marker_color=secondary_colors[2])
],
name=str(year)
)
frames.append(frame)
# Füge die Frames hinzu
fig.frames = frames
# Initiale Daten
initial_year = available_years[0]
df_initial = df[df['jahr'] == initial_year]
top_m_initial = df_initial[df_initial['geschlecht'] == 'm'].nlargest(10, 'anzahl').sort_values('anzahl')
top_w_initial = df_initial[df_initial['geschlecht'] == 'w'].nlargest(10, 'anzahl').sort_values('anzahl')
# Füge die initialen Daten hinzu
fig.add_trace(
go.Bar(x=top_m_initial['anzahl'], y=top_m_initial['vorname'], orientation='h', marker_color=secondary_colors[4]),
row=1, col=1
)
fig.add_trace(
go.Bar(x=top_w_initial['anzahl'], y=top_w_initial['vorname'],orientation='h', marker_color=secondary_colors[2]),
row=1, col=2
)
# Erstelle die Slider-Animation
sliders = [{
'transition': {'duration': 300, 'easing': 'cubic-in-out'},
'pad': {'b': 10, 't': 50},
'len': 0.9,
'x': 0.1,
'y': 0,
'steps': [{
'args': [[f.name], {
'frame': {'duration': 300, 'redraw': True},
'mode': 'immediate',
'transition': {'duration': 300}
}],
'label': str(year),
'method': 'animate'
} for f, year in zip(frames, available_years)]
}]
# Aktualisiere das Layout
fig.update_layout(
sliders=sliders,
height=600,
showlegend=False,
margin=dict(l=100, r=100, t=50, b=50),
updatemenus=[{
'type': 'buttons',
'x': 0.05,
'y': -0.17,
'showactive': False,
'buttons': [{
'label': 'Abspielen',
'method': 'animate',
'args': [None, {
'frame': {'duration': 1000, 'redraw': True},
'fromcurrent': True,
'transition': {'duration': 500}
}]
}]
}]
)
# Zeige die Figur an
fig.show()
Vergleich zweier Namen über die Zeit¶
Wir vergleichen Leon vs. Maximilian – das Duell der Klassiker vs. New Kid on the Block?
name1 = "Leon"
name2 = "Maximilian"
name1_data = df[df['vorname'] == name1]
name2_data = df[df['vorname'] == name2]
name1_by_year = name1_data.groupby('jahr')['anzahl'].sum().reset_index()
name2_by_year = name2_data.groupby('jahr')['anzahl'].sum().reset_index()
plt.plot(name1_by_year['jahr'], name1_by_year['anzahl'], marker='o', label=name1)
plt.plot(name2_by_year['jahr'], name2_by_year['anzahl'], marker='s', label=name2)
plt.title(f'Häufigkeit: {name1} vs. {name2} über die Jahre')
plt.xlabel('Jahr')
plt.ylabel('Anzahl')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
Leon holt auf und siegt! 2023 hat er Maximilian als Spitzenreiter abgelöst. Aber der Max hält sich tapfer – aber will man sein Kind wirklich nach einem 2005er Fußballstar benennen?
Auf- und Absteiger: Welche Namen sind gerade im Trend?¶
Jeder kennt sie: Die Mode-Namen, die plötzlich überall auftauchen (Hi „Levi“) – und solche, die irgendwann einfach verschwinden (rip „Kevin“?).
Wir messen die Trendstärke anhand der Differenz der Nennungen im Vergleich zum Vorjahr.
# Wir gruppieren nach Vorname und Jahr, summieren und sortieren
trend_data = df.groupby(['vorname', 'jahr'])['anzahl'].sum().reset_index()
# Umwandeln zu Zeitreihe für jeden Namen
trend_pivot = trend_data.pivot(index='jahr', columns='vorname', values='anzahl').fillna(0)
# Differenz zum Vorjahr berechnen
trend_diff = trend_pivot.diff().fillna(0)
# Mittelwert der letzten 3 Jahre vergleichen mit den vorherigen 3
trend_score = (trend_pivot.tail(3).mean() - trend_pivot.iloc[-6:-3].mean()).sort_values(ascending=False)
# Erstelle zwei Subplots nebeneinander
plt.figure(figsize=(15, 6))
# Subplot für die häufigsten Jungennamen
plt.subplot(1, 2, 1)
sns.barplot(x=trend_score.head(10).index, y=trend_score.head(10).values)
plt.title('Top 10 Aufsteiger der letzten Jahre')
plt.ylim(-trend_score.abs().max(), trend_score.abs().max()) # Setze y-Achsengrenzen
plt.ylabel('Trend Score')
plt.xlabel('')
# Subplot für die häufigsten Mädchennamen
plt.subplot(1, 2, 2)
sns.barplot(x=trend_score.tail(10).index, y=trend_score.tail(10).values)
plt.ylim(-trend_score.abs().max(), trend_score.abs().max())
plt.title('Top 10 Absteiger der letzten Jahre')
plt.xlabel('')
ax = plt.gca()
ax.set_yticklabels([])
plt.tight_layout()
plt.show()
Unisex oder eindeutig? Analyse nach Geschlechterverteilung¶
Wie viele Namen werden für beide Geschlechter verwendet – und welche?
# Gruppieren nach Vorname und Geschlecht und Summieren
geschlecht_counts = df.groupby(['vorname', 'geschlecht'])['anzahl'].sum().unstack(fill_value=0)
# Umbenennen der Spalten
geschlecht_counts.columns = ['Anzahl (männlich)', 'Anzahl (weiblich)'] if 'm' in geschlecht_counts.columns and 'w' in geschlecht_counts.columns else geschlecht_counts.columns
# Gesamtsumme berechnen
geschlecht_counts['Anzahl (gesamt)'] = geschlecht_counts['Anzahl (männlich)'] + geschlecht_counts['Anzahl (weiblich)']
sns.scatterplot(data=geschlecht_counts, x="Anzahl (männlich)", y="Anzahl (weiblich)")
plt.show()
Nachdem wir nun einen Blick auf die häufigsten Vornamen geworfen haben – und dabei festgestellt haben, dass die meisten davon recht klar einem Geschlecht zugeordnet sind – stellt sich die Frage:
Gibt es eigentlich Namen, die wirklich beide Geschlechter fast gleich häufig tragen?
Also nicht nur die üblichen Verdächtigen, bei denen mal ein „Luca“ auf der Mädchenliste auftaucht oder ein „Mika“ bei den Jungs, sondern Vornamen, bei denen das Verhältnis von männlich zu weiblich nahezu ausgeglichen ist und die zugleich nicht nur ein oder zwei Mal vergeben wurden?
Genau solche Namen haben wir im nächsten Schritt gesucht: „echte“ Unisexnamen, mit möglichst ausgewogenem Verhältnis und ausreichender Verbreitung, um statistisch ernst genommen zu werden.
# Voraussetzung: geschlecht_counts aus vorherigem Schritt (siehe vorherige Antwort)
# Nur Namen mit beiden Geschlechtern
unisex = geschlecht_counts[(geschlecht_counts['Anzahl (männlich)'] > 0) & (geschlecht_counts['Anzahl (weiblich)'] > 0)].copy()
# Verhältnis berechnen
unisex['Verhältnis m/w'] = unisex['Anzahl (männlich)'] / unisex['Anzahl (weiblich)']
unisex['Verhältnis w/m'] = unisex['Anzahl (weiblich)'] / unisex['Anzahl (männlich)']
unisex['Anteil m'] = unisex['Anzahl (männlich)'] / unisex['Anzahl (gesamt)']
unisex['Anteil w'] = unisex['Anzahl (weiblich)'] / unisex['Anzahl (gesamt)']
unisex['Ratio_Diff'] = unisex[['Verhältnis m/w', 'Verhältnis w/m']].min(axis=1) # je näher an 1, desto ausgeglichener
# Optional: Nur relevante Namen behalten (z. B. mind. 50 Nennungen)
unisex = unisex[unisex['Anzahl (gesamt)'] >= 50]
# Sortieren: zuerst nach Gleichverteilung, dann nach Häufigkeit
unisex_sorted = unisex.sort_values(by=['Ratio_Diff', 'Anzahl (gesamt)'], ascending=[False, False]).reset_index()
# Erstelle Figure und Achse
plt.figure(figsize=(15, 6))
# Stacked Bar Plot
bars = plt.bar(range(20), unisex_sorted['Anteil m'].head(20), color=secondary_colors[4], label='Männlich')
plt.bar(range(20), unisex_sorted['Anteil w'].head(20), bottom=unisex_sorted['Anteil m'].head(20),
color=secondary_colors[2], label='Weiblich')
# Line Plot auf derselben Achse
plt.plot(range(20), unisex_sorted['Ratio_Diff'].head(20), color=secondary_colors[0], linewidth=2,
marker='o', label='Ratio', zorder=3) # zorder=3 bringt die Linie in den Vordergrund
# Formatierung
plt.xlabel('')
plt.xticks(range(20), unisex_sorted['vorname'].head(20), rotation=45, ha='right')
plt.ylabel('Anteil / Ratio')
plt.ylim(0, 1)
plt.grid(True, alpha=0.3)
plt.legend(bbox_to_anchor=(1, 1), loc='upper left')
plt.title('Geschlechterverteilung und Balance-Ratio der Top 20 Unisex-Namen')
plt.tight_layout()
plt.show()
Was bedeutet Ratio_Diff? Das ist ein Maß für die Balance zwischen den Geschlechtern:
- Werte nahe 1.0 = sehr gleich verteilt.
- Werte nahe 0.0 = stark unausgeglichen.
Wie viele Namen kommen überhaupt in beiden Geschlechtern vor? Also unabhängig davon, ob sie gleich verteilt sind oder nicht – Hauptsache, sie wurden mindestens einmal für männlich und weiblich vergeben.
Das gibt uns ein gutes Bild davon, wie durchlässig die Vornamenswelt über die Jahre eigentlich ist.
def berechne_duale_namen_anteil(df):
duale_namen_stat = []
for jahr in sorted(df['jahr'].unique()):
df_jahr = df[df['jahr'] == jahr]
# Gruppierung nach Vorname und Geschlecht
geschlecht_pro_name = df_jahr.groupby(['vorname'])['geschlecht'].nunique()
# Namen mit mehr als einem Geschlecht
duale_namen = geschlecht_pro_name[geschlecht_pro_name > 1].index
anzahl_duale = len(duale_namen)
# Gesamtanzahl verschiedener Namen im Jahr
gesamt_namen = df_jahr['vorname'].nunique()
duale_namen_stat.append({
'jahr': jahr,
'duale_namen': anzahl_duale,
'gesamt_namen': gesamt_namen,
'anteil_dual': anzahl_duale / gesamt_namen if gesamt_namen > 0 else 0
})
return pd.DataFrame(duale_namen_stat)
# Berechnen
duale_namen_df = berechne_duale_namen_anteil(df)
# Plotten
plt.figure(figsize=(10, 6))
sns.lineplot(data=duale_namen_df, x='jahr', y='anteil_dual', marker='o', color='teal')
plt.title('Anteil aller Namen, die für beide Geschlechter vergeben wurden', fontsize=14)
plt.xlabel('Jahr')
plt.ylabel('Anteil dual verwendeter Namen')
plt.grid(True, linestyle='--', alpha=0.5)
plt.tight_layout()
plt.show()
Trend zur Einzigartigkeit?¶
Wie häufig werden Namen über die Jahre im Schnitt vergeben?
Je weniger Babys einen Namen bekommen, desto individueller.
# Für jedes Jahr: durchschnittliche Anzahl pro Name
seltenheit = df.groupby(['jahr', 'vorname'])['anzahl'].sum().reset_index()
seltenheit_avg = seltenheit.groupby('jahr')['anzahl'].mean().reset_index()
# Plot
plt.figure(figsize=(10, 6))
sns.lineplot(data=seltenheit_avg, x='jahr', y='anzahl', marker='o')
plt.title('Durchschnittliche Nennungen pro Name (je niedriger, desto individueller)')
plt.xlabel('Jahr')
plt.ylabel('Ø Anzahl pro Name')
plt.grid(True)
plt.tight_layout()
plt.show()
Politiker-Vornamen im Trend?¶
Wir untersuchen, wie häufig die Vornamen der aktuellen bayerischen Ministerpräsidenten und Münchner Oberbürgermeister in den Geburtsstatistiken Münchens vertreten sind.
import matplotlib.pyplot as plt
import seaborn as sns
# Liste der Politiker-Vornamen
politiker_namen = ['Markus', 'Horst', 'Edmund', 'Dieter', 'Christian']
# Filtern der Daten für die ausgewählten Namen
df_politiker = df[df['vorname'].isin(politiker_namen)]
# Gruppieren nach Jahr und Vorname
df_politiker_grouped = df_politiker.groupby(['jahr', 'vorname'])['anzahl'].sum().reset_index()
# Pivotieren für die Darstellung
df_pivot = df_politiker_grouped.pivot(index='jahr', columns='vorname', values='anzahl').fillna(0)
# Plot erstellen
plt.figure(figsize=(14, 8))
for name in politiker_namen:
if name in df_pivot.columns:
plt.plot(df_pivot.index, df_pivot[name], marker='o', label=name)
plt.title('Häufigkeit der Politiker-Vornamen in München über die Jahre')
plt.xlabel('Jahr')
plt.ylabel('Anzahl der Neugeborenen mit diesem Vornamen')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
Fazit¶
Die Namenssuche ist schwer – aber Daten helfen. Sie beruhigen, inspirieren und geben euch das gute Gefühl, dass zumindest statistisch gesehen schon viele Eltern denselben Stress überlebt haben.