Titel: Mietpreise München Description: Interaktive Mietpreis-Heatmap der 25 Stadtbezirke Dataset: https://datengartln.de/datasets/detail/cef955ef-7242-4e5d-bb5f-697df6bbe224/ Landing: true
Mietpreise München - Eine interaktive Heatmap¶
München ist eine der teuersten Städte Deutschlands, wenn es um Wohnraum geht. Aber wie stark unterscheiden sich die Mietpreise eigentlich zwischen den verschiedenen Stadtbezirken? Und wo lohnt sich der Erstbezug einer Neubauwohnung im Vergleich zur Wiedervermietung?
Diese Visualisierung zeigt die durchschnittlichen Nettokaltmieten (€/m²) für alle 25 Münchner Stadtbezirke. Mit dem Slider können Sie zwischen den Jahren wechseln und die Entwicklung der Mietpreise beobachten.
Hier finden Sie:
- Zwei Karten nebeneinander: Erstvermietung (Neubau) vs. Wiedervermietung (Bestand)
- Rot-Heatmap: Je dunkler die Farbe, desto teurer der Stadtbezirk
- Interaktiver Slider zum Wechseln zwischen den Jahren
- Beim Hovern: Exakte Mietpreise für jeden Stadtbezirk
Die Daten stammen vom Statistischen Amt der Landeshauptstadt München (link zum Dataset) und basieren auf Immobilienangeboten von Immobilien Scout GmbH.
import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import geopandas as gpd
import requests
import json
import warnings
import plotly.io as pio
pio.renderers.default = 'notebook'
warnings.filterwarnings('ignore')
primary_color = "#07A299"
text_color = "#282a36"
# Lade Mietpreise (enthält Stadtbezirk-Namen)
df_wohnen = pd.read_excel("https://mstatistik.muenchen.de/indikatorenatlas/export/xlsx/export_wo.xlsx", sheet_name='WOHNEN')
def prepare_mieten(indikator):
df_temp = df_wohnen[
(df_wohnen['Indikator'] == indikator) &
(df_wohnen['Ausprägung'] == 'absolut') &
(df_wohnen['Raumbezug'] != 'Stadt München')
].copy()
df_temp['sb_nummer'] = df_temp['Raumbezug'].str.split(' ').str[0].astype(int).astype(str)
df_temp['Indikatorwert'] = df_temp['Indikatorwert'].astype(str).str.replace(',', '.').astype(float)
return df_temp
df_mieten_erst = prepare_mieten('Mieten Erstvermietung')
df_mieten_wieder = prepare_mieten('Mieten Wiedervermietung')
# Extrahiere Namen aus Wohndaten
stadtbezirke = dict(zip(
df_mieten_wieder['sb_nummer'],
df_mieten_wieder['Raumbezug'].str.split(' ', n=1).str[1]
))
# Lade Geodaten
response = requests.get("https://opendata.muenchen.de/api/3/action/package_search", params={"q": "Bezirksteile"})
geojson_url = next(r['url'] for r in response.json()["result"]["results"][0]['resources'] if r['format'].upper() == 'GEOJSON')
gdf = gpd.read_file(geojson_url).to_crs(epsg=4326)
gdf['sb_nummer'] = gdf['bt_nummer'].str.split('.').str[0].astype(int).astype(str)
gdf_stadtbezirke = gdf.dissolve(by='sb_nummer', aggfunc={'flaeche_qm': 'sum'}).reset_index()
gdf_stadtbezirke['name'] = gdf_stadtbezirke['sb_nummer'].map(stadtbezirke)
geojson_data = json.loads(gdf_stadtbezirke.to_json())
df = pd.DataFrame([f['properties'] for f in geojson_data['features']])
df['display_name'] = df['name']
# Zwei Karten mit Slider
jahre = sorted(df_mieten_erst['Jahr'].unique())
initial_jahr = max(jahre)
zmin = pd.concat([df_mieten_erst['Indikatorwert'], df_mieten_wieder['Indikatorwert']]).min()
zmax = pd.concat([df_mieten_erst['Indikatorwert'], df_mieten_wieder['Indikatorwert']]).max()
def create_trace(df_mieten_data, jahr, show_colorbar=False):
df_jahr = df.merge(df_mieten_data[df_mieten_data['Jahr'] == jahr][['sb_nummer', 'Indikatorwert']], on='sb_nummer', how='left')
return go.Choroplethmapbox(
geojson=geojson_data, locations=df_jahr['sb_nummer'], z=df_jahr['Indikatorwert'],
featureidkey="properties.sb_nummer", colorscale='Reds', zmin=zmin, zmax=zmax,
marker_opacity=0.7, marker_line_width=2, marker_line_color='white',
text=df_jahr['display_name'], customdata=df_jahr['Indikatorwert'],
hovertemplate='<b>%{text}</b><br>Miete: %{customdata:.2f} €/m²<extra></extra>',
showscale=show_colorbar, colorbar=dict(title='€/m²', x=1.02, len=0.7) if show_colorbar else None
)
fig = make_subplots(rows=1, cols=2, subplot_titles=('Erstvermietung (Neubau)', 'Wiedervermietung (Bestand)'),
specs=[[{'type': 'mapbox'}, {'type': 'mapbox'}]], horizontal_spacing=0.02)
fig.add_trace(create_trace(df_mieten_erst, initial_jahr), row=1, col=1)
fig.add_trace(create_trace(df_mieten_wieder, initial_jahr, show_colorbar=True), row=1, col=2)
frames = [
go.Frame(data=[create_trace(df_mieten_erst, j), create_trace(df_mieten_wieder, j, show_colorbar=True)],
name=str(j), layout=go.Layout(title={'text': f'Mietpreise München {j}', 'x': 0.5, 'xanchor': 'center', 'font': {'size': 24, 'color': text_color}}))
for j in jahre
]
fig.update_layout(
title={'text': f'Mietpreise München {initial_jahr}', 'x': 0.5, 'xanchor': 'center', 'font': {'size': 24, 'color': text_color}},
height=600, margin={"r": 0, "t": 80, "l": 0, "b": 100}, paper_bgcolor='rgba(0,0,0,0)',
sliders=[{'active': jahre.index(initial_jahr), 'y': 0, 'x': 0.5, 'xanchor': 'center', 'currentvalue': {'visible': False},
'len': 0.8, 'bgcolor': '#f9fafb', 'bordercolor': '#d1d5db', 'borderwidth': 1,
'steps': [{'args': [[str(j)], {'frame': {'duration': 300, 'redraw': True}, 'mode': 'immediate'}], 'label': str(j), 'method': 'animate'} for j in jahre]}]
)
fig.update_mapboxes(style="carto-positron", center=dict(lat=48.15, lon=11.57), zoom=9.5)
fig.frames = frames
fig.show()