Stimmbeteiligung der Schweizerbevölkerung mit Verwendung von Plotly

Allgemeine Vorbereitungen

library(readr)
library(tidyverse)
library(dplyr)
library(geojsonio)
library(ggplot2)
library(plotly)
library(broom)
library(sp)

Einlesen Dataset

Im Datensatz werden verschiedene Symbole zur Markierung von NA-Werten verwendet. Diese mussten als solche erkennbar gemacht werden. Ebenfalls mussten die Abstimmungs-Tage als Datum erkannt werden.

swissvotes <- read_delim("DATASET_swissvotes.csv", 
    delim = ";", escape_double = FALSE, na = "NA", locale = locale(decimal_mark = ",", grouping_mark = "'"), trim_ws = TRUE, show_col_types = FALSE)
swissvotes[, 1:834][swissvotes[,1:834] == "."] <- NA
swissvotes[, 1:834][swissvotes[,1:834] == ""] <- NA
swissvotes$datum <- as.Date(swissvotes$datum, format = "%d.%m.%Y")

Um potentielle Fehler durch speziell formatierte Werte vorzubeugen, wurden diese umbenannt.

names(swissvotes)[names(swissvotes) == "kt-nein"] <- "kt_nein"
names(swissvotes)[names(swissvotes) == "kt-ja"] <- "kt_ja"
names(swissvotes)[names(swissvotes) == "volkja-proz"] <- "volkja_proz"
names(swissvotes)[names(swissvotes) == "sr-pos"] <- "sr_pos"
names(swissvotes)[names(swissvotes) == "nr-pos"] <- "nr_pos"
names(swissvotes)[names(swissvotes) == "bv-pos"] <- "bv_pos"
names(swissvotes)[names(swissvotes) == "br-pos"] <- "br_pos"

Wie verändert sich die Stimmbeteiligung in der Schweiz über die Jahre. Unterscheiden sich dabei die kantonalen Stimmbeteiligungen?

Darstellung der Stimmbeteiligung mit einer Choroplethen-Karte

Umbenennung Datensatz

Um die Stimmbeteiligung der Kantone auf die Kantone in den Geo-Daten zu mappen, wurden die jeweiligen Spalten umbenannt.

swissvotes <- swissvotes %>% rename ("Zuerich" = "zh-bet",
                                      "Bern" = "be-bet",
                                      "Luzern" = "lu-bet",
                                      "Uri" = "ur-bet",
                                      "Schwyz" = "sz-bet",
                                      "Obwalden" = "ow-bet",
                                      "Nidwalden" = "nw-bet", 
                                      "Glarus" = "gl-bet",
                                      "Zug" = "zg-bet",
                                      "Fribourg" = "fr-bet", 
                                      "Solothurn" = "so-bet", 
                                      "Basel_Stadt" = "bs-bet", 
                                      "Basel_Landschaft" = "bl-bet",
                                      "Schaffhausen" = "sh-bet",
                                      "Appenzell_Ausserrhoden" = "ar-bet",
                                      "Appenzell_Innerrhoden" = "ai-bet",
                                      "St_Gallen" = "sg-bet",
                                      "Graubuenden" = "gr-bet",
                                      "Aargau" = "ag-bet",
                                      "Thurgau" = "tg-bet",
                                      "Ticino" = "ti-bet",
                                      "Vaud" = "vd-bet",
                                      "Valais" = "vs-bet",
                                      "Neuchâtel" = "ne-bet",
                                      "Genève" = "ge-bet",
                                      "Jura" = "ju-bet")

Beim Einlesen des Datensatzes wurden einige Werte nicht als numerisch erkannt, diese wurden umformatiert.

cols.num <- c( "Zuerich", "Bern", "Luzern", "Uri", "Schwyz", "Obwalden", "Nidwalden", "Glarus", "Zug", "Fribourg", "Solothurn","Basel_Landschaft", "Basel_Stadt", "Schaffhausen", "Appenzell_Ausserrhoden", "Appenzell_Innerrhoden", "St_Gallen", "Graubuenden", "Aargau", "Thurgau", "Ticino", "Vaud","Valais", "Neuchâtel", "Genève", "Jura")

swissvotes[cols.num] <- sapply(swissvotes[cols.num],as.numeric)
swissvotes <- transform(swissvotes,
                             bet = as.numeric(bet))

Datenbereinigung

Für die Choroplethen-Karte war nur ein Bruchteil der Daten relevant, es wurde daher ein neues Datenset erstellt.

slim_swissvotes <- subset(swissvotes, select = c(anr, datum, titel_kurz_d, Zuerich, Bern, Luzern, Uri, Schwyz, Obwalden, Nidwalden, Glarus, Zug, Fribourg, Solothurn, Basel_Landschaft, Basel_Stadt, Schaffhausen, Appenzell_Ausserrhoden, Appenzell_Innerrhoden, St_Gallen, Graubuenden, Aargau, Thurgau, Ticino, Vaud, Valais, Neuchâtel, Genève, Jura))

Reduzierung der Spalten, da die Kantons-Beteiligungen nicht jeweils eine eigene Spalte bilden sollen. Dafür gab es eine Erweiterung der Zeilen von 680 zu 17680.

swissvotes_beteiligung <- pivot_longer(slim_swissvotes, cols = cols.num, names_to = "Beteiligung_Kantone")

Pro Abstimmungs-Datum werden mehrere Abstimmungen gehalten. Die Stimmbeteiligung der verschiedenen Vorlagen unterscheidet sich dabei minim, weshalb entschieden wurde, den jeweiligen Durchschnitts-Wert pro Datum zu verwenden. So wird eine doppel- oder gar dreifach-Belegung der Daten in der Choroplethen-Karte vermieden.

swissvotes_mean <- swissvotes_beteiligung %>%                                 # Group data
  group_by(datum, Beteiligung_Kantone) %>%
  dplyr::summarize(mean_bet = mean(value)) %>% 
  as.data.frame()

Beim ersten Versuch die Karte zu plotten, wurde bemerkt, dass Kantone ohne Daten jeweils nicht gezeichnet werden, das heisst, vor der Eigenständigkeit des Kanton Juras wird die Landesgrenze nicht korrekt dargestellt. Um dies zu vermeiden wird daher jeder NA-Wert im für die Darstellung relevanten Datensatz gleich 0 gesetzt.

swissvotes_mean[is.na(swissvotes_mean)] <- 0

Herausforderung Datenmenge

Mit über 17000 Observationen wird eine Berechung aller Daten und vor allem ein Rendering der gesamten Grafiken extrem rechenaufwändig. Mit den zur Verfügung stehenden Ressourcen war dies nicht in einer sinnvollen Zeit berechenbar (memory fail nach 4 Stunden Rechenzeit). Um dennoch zu zeigen, dass (und wie) die Karte funktioniert, wurde ein minimalistischer Datensatz erstellt, der nur das Jahr 2020 und das Jahr 2019 betrachtet.

tiny <- swissvotes_mean[swissvotes_mean$datum < "2021-03-07" & swissvotes_mean$datum > "2018-11-25",]

Geodaten und Projektion

Mit einem geojson-File werden die Kantonsgrenzen aller Schweizer Kantone generiert und in einem json-Format gespeichert. Über «g» wird die Projektion der Karte definiert.

kan <- rjson::fromJSON(file="kantone.geojson")

g <- list(

  fitbounds = "locations",
  
  projection = list(type = 'mercator'),

  visible = FALSE

)

Die Karte

Mit plotly wird eine Choroplethen-Karte, verknüpft auf die oben eingelesenen Geo-Daten erstellt. Um die Differenz der verschiedenen Stimmbeteiligungen besser zu erkennen, wird für die Demonstration die Range der Farbskale von 20 % bis 80% Beteiligung beschränkt. Als Farbskala wird “Virdis” verwendet, da diese farbenblind sicher ist.

Erklärung einiger wichtiger Parameter:

type = Plot-Art, in unserem Fall eine Choroplethenkarte dieser Plot-Typ braucht Geodaten in Form einer geojson Datei
locations = Verknüpfung der Geodaten mit den jeweiligen Kantonsdaten
z = Füllwert der einzelnen locations (Kantone), in unserem Fall die mittlere Stimmbeteiligung und damit auch die Werte für die Farbskala
zmin = minimal Wert der Fabskala
zmax = maximal Wert der Farbskala
colorscale = Farbwahl der Farbskala
frame = Erweiterung des Plots um einen dynamischen Zeitstrahl, die Plots werden in einzelne Frames/Plots aufgeteilt
featureidkey = Festlegung, über welche Werte die einzelnen Verknüpfungen gesucht werden sollen (aus beiden Datensätzen, sowohl Swissvotes als auch die Geodaten)

plot_karte <- plot_ly()

plot_karte <- plot_karte %>% add_trace(

    type="choropleth",

    geojson=kan,

    locations=~tiny$Beteiligung_Kantone,

    z=~tiny$mean_bet,
    
    zmin = 20,
    
    zmax = 80,

    colorscale="Viridis",
  
    frame=~tiny$datum,
    
    featureidkey="properties.NAME"

  )

Kosmetik für die einzelnen Frames, Titel, Farbskalabezeichung und Position, sowie die Verzerrung der Karte wurden korrigiert (mit geo = g).

plot_karte <- plot_karte %>% colorbar(title = "Stimmbeteiligung (%)",x = 1, y = 0.5, len = 0.5, which = c(1:5))

plot_karte <- plot_karte %>% layout(

    title = "Stimmbeteiligung"

)

#Berücksichtigung von geographischen Parameter (Projektion etc)
plot_karte <- plot_karte %>% layout(

    geo = g

  )

Ausgabe des Plots

plot_karte

Stimmbeteiligung pro Politikbereich

Datenbereinigung

Die gesamtschweizerische Stimmbeteiligung wird im Datensatz standardmässig nicht als numerisch erkannt, dies wurde angepasst.

pol_swissvotes <- transform(swissvotes,
                             bet = as.numeric(bet))

Ebenfalls werden einige Spalten als Faktoren benötigt, dies wurde hier umgestellt.

f_cols <- c("rechtsform", "d1e1", "d1e2", "d1e3", "d2e1", "d2e2", "d2e3", "d3e1", "d3e2", "d3e3", "dep", "br_pos", "bv_pos", "nr_pos", "sr_pos", "volk", "stand", "annahme")
pol_swissvotes[f_cols] <- lapply(pol_swissvotes[f_cols], factor)

Erstellung eines neuen Datensatzes, mit nur den relevanten Spalten.

small_pol_swissvotes <- subset(pol_swissvotes, select = c(anr, datum, titel_kurz_d, d1e1, bet))

Die einzelnen Politikbereiche sind im Codebook des Swissvotes-Datensatzes mit Zahlen benannt. Zur einfacheren Verständlichkeit wurden diese durch die im Codebook erwähnten Bezeichnungen ersetzt.

small_pol_swissvotes$d1e1 <- factor(small_pol_swissvotes$d1e1, labels = c("Staatsordnung", "Aussenpolitik", "Sicherheitspolitik", "Wirtschaft", "Landwirtschaft", "Öffentliche Finanzen", "Energie", "Verkehr und Infrastruktur", "Umwelt und Lebensraum", "Sozialpolitik", "Bildung und Forschung", "Kultur, Religion, Medien"))

Reduktion der Datenmenge

680 Abstimmungen mit Durchführungen an gleichen Tagen machen die Darstellung einer Veränderung unübersichtlich. Daher werden die durchschnittlichen Daten pro Jahrzehnt geplottet.

Einfügen einer neuen Spalte, die das Jahr aus dem Datum extrahiert.

small_pol_swissvotes <- small_pol_swissvotes %>%
    mutate(year = format(datum, "%Y")) %>% 
      group_by(year)

Die Spalte «Jahr» wurde als numerische Spalte definiert.

small_pol_swissvotes <- transform(small_pol_swissvotes,
                             year = as.numeric(year))

Einfügen einer neuen Spalte, um das Jahrzehnt für jede Abstimmung zu definieren.

small_pol_swissvotes <- small_pol_swissvotes %>% 
  mutate(decade = floor(year/10)*10) %>% 
    group_by(decade)

Erstellung des finalisierten Datensatzes mit einer mittleren Stimmbeteiligung pro Jahrzehnt.

pol_mean <- small_pol_swissvotes %>%                                
  group_by(decade, d1e1) %>%
  dplyr::summarize(mean_bet_dec = mean(bet)) %>% 
  as.data.frame()

Der Plot

Erklärung einiger wichtiger Parameter:

x = X-Achsen-Werte
y = Y-Achsen-Werte
color = Aufteilung der verschiedenen Politikbereiche in einzelne Farben
type = Bestimmung, dass wir einen Scatter-Plot erhalten möchten
mode = Erstellung von «Verbindungslinien» zwischen den einzelnen Punkten, um einen Linien-Plot zu erhalten

poli_plot <- plot_ly(pol_mean,
                      x = ~decade, 
                      y = ~mean_bet_dec, 
                      color = ~d1e1,
                      type = "scatter",
                      mode = "lines",
                      colors = c("#fde725", "#c2df23", "#86d549", "#52c569", "#2ab07f", "#1e9b8a", "#25858e", "#2d708e", "#38588c", "#433e85", "#482173", "#440154")) %>%
layout(title = 'Stimmbeteiligung pro Politikbereich',xaxis = list(title = "Veränderung über die Jahre"), yaxis = list(title = "Mittlere Stimmbeteiligung"))

Ausgabe des Plots

Der Vorteil von Plotly ist hier, dass für eine bessere Übersicht die einzelnen Bereiche alleinstehend ausgewählt werden können oder nur eine bestimmte Auswahl angezeigt werden kann.

poli_plot

Quellen

Swissvotes-Datensatz: Swissvotes (2022): Codebuch für Swissvotes – die Datenbank der eidgenössischen Volksabstimmun- gen. Année Politique Suisse, Universität Bern. Online: https://www.swissvotes.ch. Abgerufen am [21.10.22]

swiss-boundaries-geojson: Bundesamt für Landestopografie swisstopo (2022), Online: https://labs.karavia.ch/swiss-boundaries-geojson/, Abgerufen am [24.11.22]