#!/usr/bin/env python # coding: utf-8 # # Hintergrund # # Siehe den [Blogeintrag](http://datenspieler.com/Karten-NOE) zu Idee und Hintergrund dieses Notebooks # # Benötigte Pakete laden # In[1]: import pandas as pd import numpy as np import folium import json # # Daten zusammentragen # ## Bevölkerungsdaten # # Daten zur Bevölkerung in Niederösterreich, [hier](https://www.data.gv.at/katalog/dataset/land-noe-bevolkerung-nach-alter-und-geschlecht) beschrieben. Konkret wird diese [CSV Datein](http://open-data.noe.gv.at/RU2/noe_pop_age_sex_2012_2015_lau2.csv) verwendet. # In[2]: FILE_BEVOELKERUNG = 'noe_pop_age_sex_2012_2015_lau2.csv' # In[3]: bev = pd.read_csv(FILE_BEVOELKERUNG, encoding='latin-1', delimiter=';', decimal=',', skiprows=1) bev[:3] # Formatieren des DatenFrames: Löschen von unbenötigten Spalten, Umbenennen von Spalten, Umformatieren und Berechnen des durchschnittlichen Alters pro Altersgruppe. # In[4]: bev = bev.drop(['NUTS1', 'NUTS2', 'NUTS3'], axis=1) bev = bev.rename(columns={'LAU2_CODE':'iso_gemeinde', 'LAU2_NAME':'name_gemeinde'}) bev['iso_gemeinde'] = bev.iso_gemeinde.astype('str') bev['iso_bezirk'] = bev.iso_gemeinde.str[0:3] bev['alter_durchschnitt'] = bev.AGE_GROUP.str.split('_', expand=True).replace('', np.nan).astype('float').mean(axis=1) bev[:3] # Erzeugen einer Liste aller Gemeinden und Bezirke (jeweils ISO code), für die Daten vorhanden sind. # In[5]: iso_gemeinde = bev.iso_gemeinde.unique().astype('str').tolist() iso_bezirk = bev.iso_bezirk.unique().astype('str').tolist() # ## GeoJSON Daten # # Daten zur graphischen Darstellung von Bezirken und Gemeinden finden sich [hier](http://www.strategieanalysen.at/wahlen/geojson/). Konkret verwende ich aus [dieser Datei](http://www.strategieanalysen.at/wahlen/geojson/json_94.7z) die Dateien `gemeinden.json` und `bezirke.json`. # In[6]: FILE_GEOJSON_GEMEINDE = 'gemeinden.json' FILE_GEOJSON_BEZIRK = 'bezirke.json' # In[7]: geo_json_data_gemeinde = json.load(open(FILE_GEOJSON_GEMEINDE, encoding='latin-1')) geo_json_data_bezirk = json.load(open(FILE_GEOJSON_BEZIRK, encoding='latin-1')) # Die GeoJSON Files enthalten Informationen zu allen Gemeinden und Bezirken in Österreich. Da ich mich hier nur Niederösterreich interessiere, erstelle ich neue Geojson Files, die nur diese Information enthalten. # # Zuerst wird der Eintrag `type` kopiert, dann die Einträge `features` auf Niederösterreich eingeschränkt. # In[8]: geo_json_data_gemeinde['type'] # In[9]: geo_gemeinde = dict() geo_bezirk = dict() geo_gemeinde['type'] = geo_json_data_gemeinde['type'] geo_bezirk['type'] = geo_json_data_bezirk['type'] # In[10]: geo_json_data_gemeinde['features'][1]['properties'] # In[11]: geo_gemeinde['features'] = [data for data in geo_json_data_gemeinde['features'] if data['properties']['iso'] in iso_gemeinde] geo_bezirk['features'] = [data for data in geo_json_data_bezirk['features'] if data['properties']['iso'] in iso_bezirk] # Um in unserem DataFrame `bev` auch den Namen des Bezirks zu haben, wird zuerst, basierend auf der Information in `geo_bezirk` ein Mapping zwischen Iso-Code und Bezirksname erstellt. Dieses wird dann auf den Iso-Code des Bezirks in `bev` angewendet. # In[12]: map_iso_bezirk = {data['properties']['iso']: data['properties']['name'] for data in geo_bezirk['features']} map_iso_bezirk # In[13]: bev['name_bezirk'] = bev.iso_bezirk.map(map_iso_bezirk) bev[:3] # # Karten erstellen # # Dank `folium` ist das recht einfach. Zuerst wird eine Karte erstellt, hier muss man die Koordinaten und den Zoomlevel angeben. Dann wird die Bezirksinformation aus dem GeoJSON File hinzugefügt. Und dann noch die Gemeindeinformation, wobei dort noch eine Formatierung (Farbe, Strichbreite, ..) angegeben wird. Zum Schluss zeige die Karte im Notebook an. Die Karte ist interaktiv, sprich man kann darin zoomen, verschieben, ... # In[14]: m = folium.Map(location=[48.2,15.8], zoom_start=8) folium.GeoJson(geo_bezirk).add_to(m) folium.GeoJson( geo_gemeinde, style_function=lambda feature:{ 'fillColor': 'red', 'color' : 'black', 'weight' : 2, 'dashArray' : '5, 5' } ).add_to(m) m # # Daten hinzufügen # # ## Einwohner pro Bezirk # # Nun sollen Daten zu den Karten hinzugefügt werden. Als erstes Beispiel sollen die Bezirke basierend auf der Einwohneranzahl eingefärbt werden. Auch das ist mit `folium` recht einfach. Zuerst erzeuge ich einen Datenframe mit der entsprechenden Information pro Bezirk. Mit dem Befehl `choropleth` lassen sich die Daten dann mit der Karte verbinden. Am Schluss speichere ich die Karte noch in einer extra html-Datei für meinen Blog. # In[15]: df = bev[bev.YEAR == 2015].groupby(['iso_bezirk', 'name_bezirk'])[['POP_TOTAL']].sum().reset_index() df = df.sort_values(by='POP_TOTAL') df[:3] # In[16]: df[-3:] # In[17]: m = folium.Map(location=[48.2, 15.8], zoom_start=8) m.choropleth(geo_str=geo_bezirk, data=df, columns=['iso_bezirk', 'POP_TOTAL'], key_on='feature.properties.iso', fill_color='YlOrRd', fill_opacity=0.7, line_opacity=0.3 ) m.save('01.html') m # ## Frauen vs Männer # # In welchem Bezirk wohnen prozentuell am meisten Frauen, wo am wenigsten? # In[18]: bev[:3] # In[19]: df = bev[bev.YEAR == 2015].groupby(['iso_bezirk', 'name_bezirk'])[['POP_MALE', 'POP_FEMALE']].sum().reset_index() df['FEMALE_RATIO'] = df.POP_FEMALE / df.POP_MALE * 100 df = df.sort_values('FEMALE_RATIO') df[:3] # In[20]: df[-3:] # Man sieht also, dass in Zwettl auf 100 Männer nur 99.5 Frauen kommen, während das in Mödling 108 sind. Auf einer Landkarte sieht das wie folgt aus. # In[21]: m = folium.Map(location=[48.2, 15.8], zoom_start=8) m.choropleth(geo_str=geo_bezirk, data=df, columns=['iso_bezirk', 'FEMALE_RATIO'], key_on='feature.properties.iso', fill_color='YlOrRd', fill_opacity=0.7, line_opacity=0.3) m.save('02.html') m # ## Durchschnittliches Alter # # In welcher Gemeinde ist das durchschnittliche Alter am höchsten, wo leben die durchschnittlich jüngsten Leute? # In[22]: bev[:3] # In[23]: df = bev[bev.YEAR == 2015].groupby(['iso_gemeinde', 'name_gemeinde'])[['POP_TOTAL', 'alter_durchschnitt']].\ apply(lambda x: sum(x['POP_TOTAL'] * x['alter_durchschnitt']) / sum(x['POP_TOTAL'])) df = pd.DataFrame(df, columns=['alter_durchschnitt']).reset_index().sort_values('alter_durchschnitt') df[:3] # In[24]: df[-3:] # In[25]: m = folium.Map(location=[48.2, 15.8], zoom_start=8) m.choropleth(geo_str=geo_gemeinde, data=df, columns=['iso_gemeinde', 'alter_durchschnitt'], key_on='feature.properties.iso', fill_color='YlOrRd', fill_opacity=0.7, line_opacity=0.3) m.save('03.html') m # ## Kinder # # In welchem Bezirk leben prozentuell die meisten Kinder (jünger als 10 Jahre)? # In[26]: bev[:3] # In[27]: # Die Altersgruppe 5-9 hat das durchschnittliche Alter 7. Daher filtere ich für die Anzahl der Kinder, # nach dem durchschnittlichen Alter <= Variable jung = 7 jung = 7 df = bev[bev.YEAR == 2015].groupby(['iso_bezirk', 'name_bezirk'])[['POP_TOTAL', 'alter_durchschnitt']].\ apply(lambda x: sum(x['POP_TOTAL'] * (x['alter_durchschnitt'] <= jung)) / sum(x['POP_TOTAL']) * 100) df = pd.DataFrame(df, columns=['prozent_jung']).reset_index().sort_values('prozent_jung') # In[28]: m = folium.Map(location=[48.2, 15.8], zoom_start=8) m.choropleth(geo_str=geo_bezirk, data=df, columns=['iso_bezirk', 'prozent_jung'], key_on='feature.properties.iso', fill_color='YlOrRd', fill_opacity=0.7, line_opacity=0.3) m.save('04.html') m # ## Wachstum # # Welche Gemeinden sind am meisten gewachsten bzw. geschrumpft? # In[29]: bev[:3] # In[30]: df = bev.groupby(['iso_gemeinde', 'name_gemeinde'])[['POP_TOTAL', 'YEAR']].\ apply(lambda x: (sum(x['POP_TOTAL'] * (x['YEAR'] == 2015)) / sum(x['POP_TOTAL'] * (x['YEAR'] == 2012)) - 1 ) * 100) df = pd.DataFrame(df, columns=['delta_pop_total']).reset_index().sort_values('delta_pop_total') df[:3] # In[31]: df[-3:] # Mit `folium` kann man die Farbpalette genau angeben. Hier verwenden wir einen Verlauf von rot (starker Rückgang) über weiß hin zu grün (starkes Wachstum). # In[32]: colormap = folium.colormap.LinearColormap(('red', 'white', 'green'), index=(df.delta_pop_total.min(), 0, df.delta_pop_total.max()), vmin = df.delta_pop_total.min(), vmax=df.delta_pop_total.max()) colormap.caption = 'Bevölkerungsentwicklung in %, 2012 vs 2015' colormap # Um die Karte zu erstellen, benötigt man eine Funktion, die basierend auf dem ISO-Code der Gemeinde die entsprechende Farbe ausgibt. # In[33]: def colormap_iso(iso): delta_pop_total = float(df.loc[df.iso_gemeinde == iso, 'delta_pop_total']) return colormap(delta_pop_total) # In[34]: colormap_iso('30506') # In[35]: geo_gemeinde['features'][0]['properties']['iso'] # Damit sich das Grün der Farbpalette nicht mit dem Grün in der Karte verwende ich diesmal einen anderen Kartentyp. # In[36]: m = folium.Map(location=[48.2, 15.8], zoom_start=8, tiles='cartodbpositron') folium.GeoJson(geo_gemeinde, style_function=lambda feature: { 'fillColor': colormap_iso(feature['properties']['iso']), 'color' : 'black', 'weight' : 2, 'dashArray' : '5, 5' }).add_to(m) m.add_children(colormap) m.save('05.html') m # In[ ]: