Leider muss ich zugeben, dass meine bisherige Beteiligung am TidyTuesday weit hinter meinen Erwartungen zurück geblieben ist. Bisher habe ich lediglich einmal mitgemacht und das ist auch schon Monate her.

Natürlich überfliege ich trotzdem jede Woche unzählige Tweets und erfreue mich an tollen Grafiken zu verschiedenen Themen. So wie beispielsweise an dieser super-coole Visualisierung, die wirklich Lust darauf gemacht hat, mal wieder was mit Karten zu machen.

Und da ich mit ein bisschen Glück demnächst wieder Vollzeit-Baden-Württemberger werde, werden wir uns nicht Pizza-Restaurants in NYC sondern was aus dem lokaleren Datenbestand anschauen.

In diesem Blog-Post möchte ich zeigen, wie sich Visualisierungen in meinem Workflow entwickeln, wie ich von ggplot() + geom_point() zu etwas halbwegs ansehnlichem komme. Dass manche Schritte dabei ein Fall für @accidential aRt sind, versteht sich vermutlich von selbst 😅

Daten sammeln

Lange Rede - kurzer Sinn. Ich habe also auf die Webseite des Statistischen Landesamtes aufgerufen und mir einfach den erstbesten Datensatz runtergeladen, der Informationen auf Kreis-Ebene zur Verfügung stellt. Wir werden heute also etwas zur Bevölkerungsdichte in Baden-Württemberg lernen.

Damit wir die Daten auch hübsch visualisieren könne, habe ich mir darüber hinaus vom Landesamt für Geoinformation und Landentwicklung das entsprechende Shapefile heruntergeladen.

Da die Daten sauber vorbereitet sind, können wir sie mit wenigen Zeilen einlesen und in das gewünschte Format bringen.

 1# --------------------------------
 2# notwendige Packete laden
 3# --------------------------------
 4library(tidyverse)
 5library(sf)
 6
 7# --------------------------------
 8# Dateipfade definieren
 9# --------------------------------
10file_bevoelkerung <- "link/to/Bevoelk_I_Flaeche_j.csv"
11file_shapes <- "link/to/AX_Gebiet_Kreis.shp"
12
13# --------------------------------
14# Daten einlesen und aufbereiten
15# --------------------------------
16EINWOHNER.Kreise <- 
17  read_delim(
18    file = file_bevoelkerung, 
19    locale = locale(encoding = readr::guess_encoding(file_bevoelkerung) %>% select(encoding) %>% unlist()),
20    delim = ";", 
21    skip = 20,
22    col_names = c(
23      "ID.Regionaleinheit",
24      "AmtlicherGemeindeschluessel",
25      "PLZ",
26      "Regionalname",
27      "Stichtag",
28      "Bevoelkerung_insgesamt",
29      "Gemeindegebiet_ha",
30      "Bevoelkerungsdichte_EW_km2"
31    )
32  ) %>% 
33  filter(
34    Stichtag == "31.12.2018",
35    ID.Regionaleinheit == "KR"
36  ) %>% 
37  left_join(
38    y = read_sf(file_shapes),
39    by = c("AmtlicherGemeindeschluessel" = "Schlüssel")
40  )

Basis-Plot erstellen

Nachdem wir die Daten eingelesen und aufbereitet haben, sollten wir uns anschauen was ggplot() vorschlägt. Ein klarer Vorteil ist, dass selbst dieser erste Versuch schon relativ ansehnlich ist. Aber da geht natürlich noch mehr …

1# --------------------------------
2# Basisplot erstellen
3# --------------------------------
4p <- ggplot(data = EINWOHNER.Kreise) + 
5  geom_sf(aes(fill = Bevoelkerungsdichte_EW_km2))
6p

All hail the Spätzle!

Theoretisch könnten wir hier aufhören. Wir haben eine Karte auf der die Landkreise entsprechend ihrer Bevölkerungsdichte eingefärbt sind. Es wird auf einen Blick deutlich, dass in Stuttgart sehr viele Menschen auf wenig Raum wohnen, während es im Nord- und Südosten eher ruhig zugeht.

Nun wollten wir ja aber einen etwas cooleren Plot erstellen. Dazu ändern wir als erstes die Farben. Und weil die Landesfarbe halt spätzle-gelb ist, nehmen wir das als Basis. Von coolors.co lasse ich mir einen dazu passenden Rot-Ton erzeugen und definiere so die beiden Extremwerte meiner Farbskala.

 1# --------------------------------
 2# Farben definieren
 3# --------------------------------
 4colour_spaetzlegelb <- "#FFFEF9"
 5colour_rangemax <- "#AA4465"
 6
 7# --------------------------------
 8# Plot aktualisieren
 9# --------------------------------
10p <- p + 
11  scale_fill_gradient(low = colour_spaetzlegelb, high = colour_rangemax)
12p

Make the theme great again!

Das sieht schon besser (aber noch nicht gut) aus. Als nächstes passen wir das Theme an, entfernen die Legende, den grauen Hintergrund und die Achsen, deren Zahlenwerte aktuell eher verwirrend als hilfreich sind.

 1# --------------------------------
 2# Plot aktualisieren
 3# --------------------------------
 4p <- p + 
 5  theme(
 6    
 7    # Achsen entfernen -----------
 8    axis.line = element_blank(), 
 9    axis.title = element_blank(),
10    axis.ticks = element_blank(),
11    axis.text = element_blank(),
12
13    # Hintergrund anpassen -------
14    panel.background = element_rect(fill = colour_spaetzlegelb),
15    plot.background = element_rect(fill = colour_spaetzlegelb),
16    
17    # Legende entfernen ----------
18    legend.position = "none"
19  )
20p

Reden ist Silber, Annotierung ist Gold!

So langsam wird es doch. Es fehlt aber noch ein bisschen Kontext. Also fügen wir neben der eigentlich Grafik einen Titel und eine kurze Zusammenfassung der Ergebnisse ein. Das hilft die Hauptaussage der Visualisierung noch einmal (in prosa) hervorzuheben.

 1# --------------------------------
 2# Untertitel verfassen
 3# --------------------------------
 4plot_subtitle <- str_c(
 5  "Die Karte zeigt die Bevölkerungsdichte nach Kreisen in Baden-Württemberg.",
 6  "Am dünnsten besiedelt ist der Main-Tauber-Kreis. Hier wohnen 101 Personen pro km².",
 7  "Mehr als 30x mehr Personen pro Quadratkilomenter wohnen in der Landeshauptstadt.",
 8  sep = " "
 9)
10
11# --------------------------------
12# Quellen definieren
13# --------------------------------
14plot_caption <- str_c(
15  "Daten: Statistisches Landesamt Baden-Württemberg;",
16  "Karten: Landesamt für Geoinformation und Landentwicklung",
17  "Grafik: https://gluecko.se",
18  sep = " "
19)
20
21# --------------------------------
22# Notwendige Koordinaten speichern
23# --------------------------------
24coord_xmin <- sf::st_bbox(EINWOHNER.Kreise$geometry)$xmin
25coord_xmax <- sf::st_bbox(EINWOHNER.Kreise$geometry)$xmax
26coord_ymin <- sf::st_bbox(EINWOHNER.Kreise$geometry)$ymin
27coord_ymax <- sf::st_bbox(EINWOHNER.Kreise$geometry)$ymax
28
29# --------------------------------
30# Plot aktualisieren
31# --------------------------------
32p <- p + 
33  annotate("text", label = "Bevölkerungsdichte nach Kreisen", x = .7 * coord_xmin, y = 1.01 * coord_ymax, size = 6, hjust = 0) +
34  annotate("text", label = str_wrap(plot_subtitle, 40), x = .7 * coord_xmin, y = coord_ymax, size = 3, hjust = 0) +
35  annotate("text", label = str_wrap(plot_caption, 40), x = .7 * coord_xmin, y = coord_ymax, size = 3, hjust = 0, vjust = 2.5)
36p

Look here!

Na das sieht doch jetzt echt nach was aus! Wir sollten allerdings noch ein paar Kreise explizit hervorheben. Extremfälle bieten sich an - fügen wir also noch Hinweise auf die Kreise mit der höchsten bzw. niedrigsten Bevölkerungsdichte ein.

 1# --------------------------------
 2# Centroid berechnen
 3# --------------------------------
 4get_centroid <- function(name){
 5  coords <- 
 6    EINWOHNER.Kreise %>% 
 7    filter(Name %in% name) %>% 
 8    select(geometry) %>% 
 9    st_as_sf() %>% 
10    st_centroid() %>% 
11    unlist()
12  
13  if(length(coords)>2){
14    warning("More than one centroid found!")
15  }
16  
17  returnData <- 
18    tribble(
19      ~"Name", ~"x", ~"y",
20      name, coords[1], coords[2]
21    )
22  
23  return(returnData)
24}
25
26# --------------------------------
27# Notwendige Koordinaten speichern
28# --------------------------------
29coord_Stuttgart_xlabel <- 1.05 * coord_xmin
30coord_Stuttgart_ylabel <- 1.0045 * get_centroid("Stuttgart")$y
31coord_Mannheim_xlabel <- 1.1 * coord_xmin
32coord_Mannheim_ylabel <- 1.005 * get_centroid("Mannheim")$y
33coord_Karlsruhe_xlabel <- 1.1 * coord_xmin
34coord_Karlsruhe_ylabel <- 1.005 * get_centroid("Karlsruhe")$y
35coord_MainTauberKreis_xlabel <- 0.95 * coord_xmax
36coord_MainTauberKreis_ylabel <- 1.005 * get_centroid("Main-Tauber-Kreis")$y
37coord_Sigmaringen_xlabel <- 0.98 * coord_xmax
38coord_Sigmaringen_ylabel <- 1.005 * get_centroid("Sigmaringen")$y
39coord_NeckarOdenwaldKreis_xlabel <- 0.82 * coord_xmax
40coord_NeckarOdenwaldKreis_ylabel <- 1.0115 * get_centroid("Neckar-Odenwald-Kreis")$y
41
42# --------------------------------
43# Plot aktualisieren
44# --------------------------------
45p <- p + 
46  # Top 1: Stuttgart -------------  
47  annotate("segment", x = coord_Stuttgart_xlabel, y = coord_Stuttgart_ylabel, xend = get_centroid("Stuttgart")$x,  yend = get_centroid("Stuttgart")$y) + 
48  annotate("label", label = "Stuttgart\n3.062 EW/km²", x = coord_Stuttgart_xlabel, y = coord_Stuttgart_ylabel) + 
49  
50  # Top 2: Mannheim --------------
51  annotate("segment", x = coord_Mannheim_xlabel, y = coord_Mannheim_ylabel, xend = get_centroid("Mannheim")$x, yend = get_centroid("Mannheim")$y) + 
52  annotate("label", label = "Mannheim\n2.134 EW/km²", x = coord_Mannheim_xlabel, y = coord_Mannheim_ylabel) + 
53  
54  # Top 3: Karlsruhe -------------
55  annotate("segment", x = coord_Karlsruhe_xlabel, y = coord_Karlsruhe_ylabel, xend = get_centroid("Karlsruhe")$x, yend = get_centroid("Karlsruhe")$y) + 
56  annotate("label", label = "Karlsruhe\n1.805 EW/km²", x = coord_Karlsruhe_xlabel, y = coord_Karlsruhe_ylabel) + 
57
58  # Bottom 1: Main-Tauber-Kreis --
59  annotate("segment", x = coord_MainTauberKreis_xlabel, y = coord_MainTauberKreis_ylabel, xend = get_centroid("Main-Tauber-Kreis")$x, yend = get_centroid("Main-Tauber-Kreis")$y) +
60  annotate("label", label = "Main-Tauber-Kreis\n101 EW/km²", x = coord_MainTauberKreis_xlabel, y = coord_MainTauberKreis_ylabel) + 
61
62  # Bottom 2: Sigmaringen --------
63  annotate("segment", x = coord_Sigmaringen_xlabel, y = coord_Sigmaringen_ylabel, xend = get_centroid("Sigmaringen")$x, yend = get_centroid("Sigmaringen")$y) +
64  annotate("label", label = "Sigmaringen\n109 EW/km²", x = coord_Sigmaringen_xlabel, y = coord_Sigmaringen_ylabel) +
65
66  # bottom 3: Neckar-Odenwald-Kreis --
67  annotate("segment", x = coord_NeckarOdenwaldKreis_xlabel, y = coord_NeckarOdenwaldKreis_ylabel, xend = get_centroid("Neckar-Odenwald-Kreis")$x, yend = get_centroid("Neckar-Odenwald-Kreis")$y) +
68  annotate("label", label = "Neckar-Odenwald-Kreis\n127 EW/km²", x = coord_NeckarOdenwaldKreis_xlabel, y = coord_NeckarOdenwaldKreis_ylabel)
69p

Eine große Version der Grafik und den vollständigen Code am Stück gibt es - wie immer - bei GitHub.


I love to hear your opinion. If you want to exchange on what I wrote in this article, please write me an email ✉️ and we can start a conversation.