diff --git a/R/aktualisiere_karten.R b/R/aktualisiere_karten.R new file mode 100644 index 0000000000000000000000000000000000000000..28626b76e347cde275a6d73884451345badcdb84 --- /dev/null +++ b/R/aktualisiere_karten.R @@ -0,0 +1,49 @@ +#' aktualisiere_karten.R + + +aktualisiere_karten <- function(wl_url = stimmbezirke_url) { + # Lies Ortsteil-Daten ein und vergleiche + neue_orts_df <- lies_gebiet(wl_url) %>% + aggregiere_stadtteile() %>% + mutate(quorum = ifelse(wahlberechtigt == 0, + 0, + ja / wahlberechtigt * 100)) %>% + mutate(status = ifelse(meldungen_anz == 0, + "KEINE DATEN", + paste0(ifelse(meldungen_anz < meldungen_max, + "TREND ",""), + ifelse(ja < nein, + "NEIN", + ifelse(quorum < 30, + "JA", + "JA QUORUM"))) + )) + alte_orts_df <- hole_letztes_df("daten/ortsteile") + # Datenstand identisch? Dann brich ab. + if(vergleiche_stand(alte_orts_df,neue_orts_df)) { + return(FALSE) + } else { + # Zeitstempel holen + archiviere(neue_orts_df,"daten/ortsteile") + ts <- neue_orts_df %>% pull(zeitstempel) %>% last() + # Datentabelle übertragen + dw_data_to_chart(neue_orts_df,choropleth_id) + dw_data_to_chart(neue_orts_df,symbol_id) + dw_data_to_chart(neue_orts_df,tabelle_id) + # Anmerkungen aktualisieren + wahlberechtigt <- neue_orts_df %>% pull(wahlberechtigt) %>% sum() + # Prozentsatz ausgezählte Stimmen: abgerundet auf ganze Prozent + ausgezählt <- floor(wahlberechtigt / ffm_waehler *100) + annotate_str <- generiere_auszählungsbalken(ausgezählt, + anz = neue_orts_df %>% pull(meldungen_anz) %>% sum(), + max = neue_orts_df %>% pull(meldungen_max) %>% sum(), + ts = ts) + dw_edit_chart(symbol_id,annotate=annotate_str) + dw_edit_chart(choropleth_id,annotate=annotate_str) + dw_edit_chart(tabelle_id,annotate=annotate_str) + dw_publish_chart(symbol_id) + dw_publish_chart(choropleth_id) + dw_publish_chart(tabelle_id) + return(TRUE) + } +} \ No newline at end of file diff --git a/R/generiere_testdaten.R b/R/generiere_testdaten.R new file mode 100644 index 0000000000000000000000000000000000000000..3eed9587e7a82d8e51b2252b2ce46d563e810348 --- /dev/null +++ b/R/generiere_testdaten.R @@ -0,0 +1,140 @@ +#' generiere_testdaten.R +#' +#' Macht aus den Templates für Ortsteil- und Wahllokal-Ergebnisse +#' jeweils eine Serie von fiktiven Livedaten, um das Befüllen der +#' Grafiken testen zu können. +#' + +require(tidyr) +require(dplyr) +require(readr) + +# Alles weg, was noch im Speicher rumliegt +rm(list=ls()) + +source("R/lies_aktuellen_stand.R") + +#---- Funktion zum Testdaten-Löschen ---- +lösche_testdaten <- function(){ + q <- tolower(readline(prompt = "Testdaten löschen - sicher? ")) + if (!(q %in% c("j","y","ja"))) { return() } + # Datenarchiv weg + if (file.exists("daten/fom_df.rds")){ + file.remove("daten/fom_df.rds") + } + # Testdaten + testdaten_files <- list.files("testdaten", full.names=TRUE) + for (f in testdaten_files) { + # Grausam, I know. + if (str_detect(f,"ortsteile[0-9]+\\.csv") | + str_detect(f,"wahllokale[0-9]+\\.csv")) { + file.remove(f) + } + } +} + +# Vorlagen laden +vorlage_wahllokale_df <- read_delim("testdaten/Open-Data-06412000-Buergerentscheid-zur-Abwahl-des-Oberbuergermeisters-der-Stadt-Frankfurt-am-Main_-Herrn-Peter-Feldmann-Stimmbezirk.csv", + delim = ";", escape_double = FALSE, + locale = locale(date_names = "de", + decimal_mark = ",", + grouping_mark = "."), + trim_ws = TRUE) + +wahllokale_max <- sum(vorlage_wahllokale_df$`max-schnellmeldungen`) + +# Konstanten für die Simulation - werden jeweils um bis zu +/-25% variiert +c_wahlberechtigt = 510000 / wahllokale_max # Gleich große Wahlbezirke +c_wahlbeteiligung = 0.3 # Wahlbeteiligung um 30%, wird im Lauf der "Wahl" erhöht (kleinere WL sind schneller ausgezählt) +c_wahlschein = 0.25 # 25% Briefwähler +c_nv = 0.05 # 0,5% wählen "spontan" und sind nicht verzeichnet (nv) im Wählerverzeichnis +c_ungültig = 0.01 # 1% Ungültige +c_nein = 0.15 # unter den gültigen: 85% Ja-Stimmen (Varianz also von ca 81-89%) + +variiere <- function(x = 1) { + # Variiert den übergebenen Wert zufällig um -25% bis +25%: + # Zufallswerte zwischen 0,75 und 1,25 erstellen und multiplizieren + # + # Die Length-Funktion ist wichtig - sonst erstellt runif() nur einen + # Zufallswert, mit dem alle Werte von x multipliziert werden. + return(floor(x * (runif(length(x),0.75,1.25)))) +} + + + +i = 1 +# Schleife für die Wahllokale: Solange noch nicht alle "ausgezählt" sind... +while(sum(vorlage_wahllokale_df$`anz-schnellmeldungen`) < wahllokale_max) { + # ...splitte das df in die gemeldeten (meldungen_anz == 1) und nicht gemeldeten Zeilen + tmp_gemeldet_df <- vorlage_wahllokale_df %>% filter(`anz-schnellmeldungen` == 1) + # Die Variable rand wird als Anteil von 20 Meldungen an debn noch offenen Wahllokale berechnet + rand <- 20 / (nrow(vorlage_wahllokale_df) - nrow(tmp_gemeldet_df)) + tmp_sample_df <- vorlage_wahllokale_df %>% + filter(`anz-schnellmeldungen` == 0) %>% + # Bei den noch nicht ausgefüllten "Meldungen" mit einer Wahrscheinlichkeit + # von rand in die Gruppe sortieren, die neu "gemeldet" wird + mutate(sample = (runif(nrow(.)) < rand)) + tmp_offen_df <- tmp_sample_df %>% + filter(sample == 0) %>% + # sample-Variable wieder raus + select(-sample) + tmp_neu_df <- tmp_sample_df %>% + filter(sample == 1) %>% + select(-sample) %>% + # Alle als gemeldet markieren + mutate(`anz-schnellmeldungen` = 1) %>% + # Und jetzt der Reihe nach (weil die Werte z.T. aufeinander aufbauen) + # Wahlberechtigte + mutate(A = floor(c_wahlberechtigt * runif(nrow(.),0.75,1.25))) %>% + # Wahlschein + mutate(A2 = floor(A * c_wahlschein * runif(nrow(.),0.75,1.25))) %>% + # Nicht verzeichnet + mutate(A3 = floor(A * c_nv * runif(nrow(.),0.75,1.25))) %>% + # Regulär Wahlberechtigte (ohne Wahlschein oder nv) + mutate(A1 = A - A2 - A3) %>% + # Abgegebene Stimmen + mutate(B = floor(A * c_wahlbeteiligung * runif(nrow(.),0.75,1.25))) %>% + # davon mit Wahlschein + mutate(B1 = floor(B * c_wahlschein * runif(nrow(.),0.75,1.25))) %>% + # davon ungültig + mutate(C = floor(B * c_ungültig * runif(nrow(.),0.75,1.25))) %>% + # gültig + mutate(D = B - C) %>% + # davon ja + mutate(D2 = floor(D * c_nein *runif(nrow(.),0.75,1.25))) %>% + mutate(D1 = D - D2) + # Kurze Statusmeldung + cat("Neu gemeldet:",nrow(tmp_neu_df),"noch offen:",nrow(tmp_offen_df)) + # Phew. Aktualisierte Testdatei zusammenführen und anlegen. + vorlage_wahllokale_df <- tmp_gemeldet_df %>% + bind_rows(tmp_neu_df) %>% + bind_rows(tmp_offen_df) %>% + # wieder in die Reihenfolge nach Wahllokal-Nummer + arrange(`gebiet-nr`) + + write_csv2(vorlage_wahllokale_df, + paste0("testdaten/wahllokale", + sprintf("%02i",i), + ".csv"), + escape = "backslash") + # Generiere die passende Ortsteil-Meldung + # Geht aus irgeneindem Grund nicht, aber wir brauchens ja auch nicht. + # ortsteile_df <- zuordnung_wahllokale_df %>% + # select(`gebiet-name` = name,ortsteilnr) %>% + # left_join(vorlage_wahllokale_df,by="gebiet-name") %>% + # # Zuordnung der Wahllokale + # group_by(ortsteilnr) %>% + # # Das crasht - WTF??? + # summarize(across(7:18, ~ sum(.,na.rm = T))) %>% + # left_join(stadtteile_df %>% select(ortsteilnr = nr,name),by="ortsteilnr") %>% + # rename(`gebiet-nr` = ortsteilnr) %>% + # mutate(`gebiet-name` = name) %>% + # select(-ortsteilnr) + + i <- i+1 + # Wahlbeteiligung schrittweise ein wenig anheben - um zu simulieren, + # dass "kleinere" Wahllokale zuerst ausgezählt werden + c_wahlbeteiligung <- c_wahlbeteiligung + 0.002 +} + + diff --git a/R/lies_aktuellen_stand.R b/R/lies_aktuellen_stand.R new file mode 100644 index 0000000000000000000000000000000000000000..70b7bca9985413a10982c16b486276077c7c0b19 --- /dev/null +++ b/R/lies_aktuellen_stand.R @@ -0,0 +1,152 @@ +library(readr) +library(lubridate) +library(tidyr) +library(stringr) +library(dplyr) + +# lies_aktuellen_stand.R +# +# Enthält die Funktion zum Lesen der aktuellen Daten. + +#---- Vorbereitung ---- +# Statische Daten einlesen +# (das später durch ein schnelleres .rda ersetzen) + +# Enthält drei Datensätze: +# - opendata_wahllokale_df mit der Liste aller Stimmwahlbezirke nach Wahllokal +# - statteile_df: Stadtteil mit Namen und laufender Nummer, Geokoordinaten, Ergebnissen 2018 +# - zuordnung_stimmbezirke: Stimmbezirk-Nummer (als int und String) -> Stadtteilnr. + +load ("index/index.rda") + +# Konfiguration auslesen und in Variablen schreiben +config_df <- read_csv("index/config.csv") +for (i in c(1:nrow(config_df))) { + # Erzeuge neue Variablen mit den Namen und Werten aus der CSV + assign(config_df$name[i], + # Kann man den Wert auch als Zahl lesen? + # Fieses Regex sucht nach reiner Zahl oder Kommawerten. + # Keine Exponentialschreibweise! + ifelse(grepl("^[0-9]*\\.*[0-9]+$",config_df$value[i]), + # Ist eine Zahl - wandle um + as.numeric(config_df$value[i]), + # Keine Zahl - behalte den String + config_df$value[i])) +} + + +#---- Daten ins Archiv schreiben oder daraus lesen +archiviere <- function(df,a_directory = "daten/stimmbezirke") { + if (!dir.exists(a_directory)) { + dir.create(a_directory) + } + write_csv(df, + paste0(a_directory,"/", + # Zeitstempel isolieren und alle Doppelpunkte + # durch Bindestriche ersetzen + str_replace_all(df %>% pull(zeitstempel) %>% last(), + "\\:","_"), + ".csv")) +} + +hole_letztes_df <- function(a_directory = "daten/stimmbezirke") { + if (!dir.exists(a_directory)) return(tibble()) + neuester_file <- list.files(a_directory, full.names=TRUE) %>% + file.info() %>% + # Legt eine Spalte namens path an + tibble::rownames_to_column(var = "path") %>% + arrange(desc(ctime)) %>% + head(1) %>% + # Pfad wieder rausziehen + pull(path) + if (length(neuester_file)==0) { + # Falls keine Daten archiviert, gibt leeres df zurück + return(tibble()) + } else { + return(read_csv(neuester_file)) + } +} + + +#---- Lese-Funktionen ---- +lies_gebiet <- function(stand_url = stimmbezirke_url) { + ts <- now() + # Versuch Daten zu lesen - und gib ggf. Warnung oder Fehler zurück + check = tryCatch( + { stand_df <- read_delim(stand_url, + delim = ";", escape_double = FALSE, + locale = locale(date_names = "de", + decimal_mark = ",", + grouping_mark = "."), + trim_ws = TRUE) %>% + # Spalten umbenennen, Zeitstempel-Spalte einfügen + mutate(zeitstempel=ts) %>% + select(zeitstempel, + nr = `gebiet-nr`, + name = `gebiet-name`, + meldungen_anz = `anz-schnellmeldungen`, + meldungen_max = `max-schnellmeldungen`, + # Ergebniszellen + wahlberechtigt = A, + # Mehr zum Wahlschein hier: https://www.bundeswahlleiter.de/service/glossar/w/wahlscheinvermerk.html + waehler_regulaer = A1, + waehler_wahlschein = A2, + waehler_nv = A3, + stimmen = B, + stimmen_wahlschein = B1, + ungueltig = C, + gueltig = D, + ja = D1, + nein = D2) + }, + warning = function(w) {teams_warning(w,title="Feldmann: Datenakquise")}, + error = function(e) {teams_warning(e,title="Feldmann: Datenakquise")}) + # Spalten umbenennen, + return(stand_df) +} + + +# Sind die beiden df abgesehen vom Zeitstempel identisch? +# Funktion vergleicht die numerischen Werte - Spalte für Spalte. +vergleiche_stand <- function(alt_df, neu_df) { + neu_sum_df <- alt_df %>% summarize_if(is.numeric,sum,na.rm=T) + alt_sum_df <- neu_df %>% summarize_if(is.numeric,sum,na.rm=T) + # Unterschiedliche Spaltenzahlen? Dann können sie keine von Finns Männern sein. + if (length(neu_sum_df) != length(alt_sum_df)) return(FALSE) + # Differenzen? Dann können sie keine von Finns Männern sein. + return(sum(abs(neu_sum_df - alt_sum_df))==0) +} + +#' Liest Stimmbezirke, gibt nach Ortsteil aggregierte Daten zurück +#' (hier: kein Sicherheitscheck) +aggregiere_stadtteile <- function(stimmbezirke_df) { + ortsteile_df <- stimmbezirke_df %>% + left_join(zuordnung_stimmbezirke_df,by=c("nr","name")) %>% + group_by(ortsteilnr) %>% + summarize(zeitstempel = last(zeitstempel), + across(meldungen_anz:nein, ~ sum(.,na.rm = T))) %>% + rename(nr = ortsteilnr) %>% + # Stadtteilnamen, 2018er Ergebnisse, Geokoordinaten dazuholen + left_join(stadtteile_df, by="nr") %>% + # Nach Ortsteil sortieren + arrange(nr) %>% + # Wichtige Daten für bessere Lesbarkeit nach vorn + relocate(zeitstempel,nr,name,lon,lat) + + # Sicherheitscheck: Warnen, wenn nicht alle Ortsteile zugeordnet + if (nrow(ortsteile_df) != nrow(stadtteile_df)) teams_warnung("Nicht alle Ortsteile zugeordnet") + if (nrow(zuordnung_stimmbezirke_df) != length(unique(stimmbezirke_df$nr))) teams_warnung("Nicht alle Stimmbezirke zugeordnet") + return(ortsteile_df) +} + +lies_stadtteil_direkt <- function(stand_url = ortsteile_url) { + neu_df <- lies_gebiet(stand_url) %>% + # nr bei Ortsteil-Daten leer/ignorieren + select(!nr) %>% + # Stadtteilnr., Geodaten und Feldmann-2018-Daten reinholen: + left_join(stadtteile_df, by=c("name")) %>% + mutate(trend = (meldungen_anz < meldungen_max), + quorum_erreicht = (ja >= (wahlberechtigt * 0.3))) + return(neu_df) +} + diff --git a/R/messaging.R b/R/messaging.R new file mode 100644 index 0000000000000000000000000000000000000000..b2b93b0e1470153dc8f474648344f2546ad7dfc2 --- /dev/null +++ b/R/messaging.R @@ -0,0 +1,41 @@ +library(readr) +library(lubridate) +library(tidyr) +library(stringr) +library(dplyr) +library(teamr) + +#' messaging.R +#' +#' Kommunikation mit Teams +#' +#' Webhook wird als URL im Environment gespeichert. Wenn nicht dort, dann + +# Webhook schon im Environment? +if (Sys.getenv("WEBHOOK_REFERENDUM") == "") { + t_txt <- read_file("../key/webhook_referendum.key") + Sys.setenv(WEBHOOK_REFERENDUM = t_txt) +} + +teams_meldung <- function(...,title="Feldmann-Update") { + cc <- teamr::connector_card$new(hookurl = t_txt) + cc$title(paste0(title," - ",lubridate::with_tz(lubridate::now(), + "Europe/Berlin"))) + alert_str <- paste0(...) + cc$text(alert_str) + cc$print() + cc$send() +} + +teams_error <- function(...) { + alert_str <- paste0(...) + teams_meldung(title="Feldmann: FEHLER: ", ...) + stop(alert_str) +} + +teams_warning <- function(...) { + alert_str <- paste0(...) + teams_meldung("Feldmann: WARNUNG: ",...) + warning(alert_str) +} + diff --git a/R/update_all.R b/R/update_all.R new file mode 100644 index 0000000000000000000000000000000000000000..e0dd5d6d73505c1a8d37304dcf8eb2236688b0f4 --- /dev/null +++ b/R/update_all.R @@ -0,0 +1,203 @@ +library(pacman) + +# Laden und ggf. installieren +p_load(this.path) +p_load(readr) +p_load(lubridate) +p_load(tidyr) +p_load(stringr) +p_load(dplyr) +p_load(DatawRappr) + +rm(list=ls()) + +# Aktuelles Verzeichnis als workdir +setwd(this.path::this.dir()) +# Aus dem R-Verzeichnis eine Ebene rauf +setwd("..") + + +source("R/messaging.R") +source("R/lies_aktuellen_stand.R") +source("R/aktualisiere_karten.R") +source("R/generiere_balken.R") + + +#----aktualisiere_fom() ---- +# fom ist das "Feldmann-o-meter", die zentrale Grafik mit dem Stand der Auszählung. + +aktualisiere_fom <- function(wl_url = stimmbezirke_url) { + + # Einlesen: Feldmann-o-meter-Daten so far. + # Wenn die Daten noch nicht existieren, generiere ein leeres df. + if(file.exists("daten/fom_df.rds")) { + fom_df <- readRDS("daten/fom_df.rds") + } else { + # Leeres df mit einer Zeile + fom_df <- tibble(zeitstempel = as_datetime(startdatum), + meldungen_anz = 0, + meldungen_max = 575, + # Ergebniszellen + wahlberechtigt = 0, + # Mehr zum Wahlschein hier: https://www.bundeswahlleiter.de/service/glossar/w/wahlscheinvermerk.html + waehler_regulaer = 0, + waehler_wahlschein = 0, + waehler_nv = 0, + stimmen = 0, + stimmen_wahlschein = 0, + ungueltig = 0, + gueltig = 0, + ja = 0, + nein = 0) + # SAVE kann man sich schenken; df ist schneller neu erzeugt + # save(feldmann_df,"daten/feldmann_df.rda") + } + # Daten zur Sicherheit sortieren, dann die letzte Zeile rausziehen + letzte_fom_df <- fom_df %>% + arrange(zeitstempel) %>% + tail(1) + # Neue Daten holen (mit Fehlerbehandlung) + stimmbezirke_df <- lies_gebiet(wl_url) + neue_fom_df <- stimmbezirke_df %>% + # Namen raus + select(-name,-nr) %>% + # Daten aufsummieren + summarize(zeitstempel = last(zeitstempel), + across(2:ncol(.), ~ sum(.,na.rm=T))) + # Alte und neue Daten identisch? Dann brich ab. + if (vergleiche_stand(letzte_fom_df,neue_fom_df)) { + return(FALSE) + } else { + # Archiviere die Rohdaten + archiviere(stimmbezirke_df,"daten/stimmbezirke/") + # Ergänze das fom_df um die neuen Daten und sichere es + fom_df <- fom_df %>% bind_rows(neue_fom_df) + saveRDS(fom_df,"daten/fom_df.rds") + # Bilde das Dataframe + # Sende die Daten an Datawrapper und aktualisiere + fom_dw_df <- fom_df %>% + mutate(ausgezählt = meldungen_anz / meldungen_max *100) %>% + mutate(prozent30 = NA) %>% + mutate(quorum = ja / wahlberechtigt * 100) %>% + select(ausgezählt, wahlberechtigt, ungueltig, ja, nein, quorum, prozent30) %>% + # Noch den Endpunkt der 30-Prozent-Linie + bind_rows(tibble(ausgezählt = 100, prozent30 = ffm_waehler * 0.3)) + dw_data_to_chart(fom_dw_df,fom_id) + # Parameter setzen + alles_ausgezählt <- (neue_fom_df$meldungen_max == neue_fom_df$meldungen_anz) + if (neue_fom_df$meldungen_anz == 0) { + quorum = 0 + feldmann_str <- "Es liegen noch keine Auszählungsdaten des Bürgerentscheids vor." + } else { + quorum <- (neue_fom_df$ja / neue_fom_df$wahlberechtigt * 100) + if (quorum >= 30) { + if (alles_ausgezählt ) { + feldmann_str <- "Peter Feldmann ist als OB abgewählt." + } else { + feldmann_str <- "Nach dem derzeitigen Auszählungsstand wäre Peter Feldmann als OB abgewählt." + } + } else { + if (alles_ausgezählt ) { + feldmann_str <- "Peter Feldmann bleibt OB von Frankfurt." + } else { + feldmann_str <- "Nach dem derzeitigen Auszählungsstand bliebe Peter Feldmann OB von Frankfurt." + } + } + } + + # Breite des Balkens: Wenn das Quorum erreicht ist, hat er die volle Breite, + # wenn nicht, einen Anteil von 30%, um die Entfernung von der Markierung zu zeigen + + # Jetzt die Beschreibungstexte mit den Fake-Balkengrafiken generieren + beschreibung_str <- paste0( + "Die Abwahl ist beschlossen, wenn mindestens 30 Prozent aller Wahlberechtigten mit "Ja" stimmen.<br/><br>", + "<b style='font-weight:700;font-size:120%;'>", + # Erste dynamisch angepasste Textstelle: Bleibt Feldmann? + feldmann_str, + "</b><br/><br>", + generiere_balken(wb = neue_fom_df$wahlberechtigt, + ja = neue_fom_df$ja, + nein = neue_fom_df$nein, + auszählung_beendet = alles_ausgezählt)) + annotate_str <- generiere_auszählungsbalken( + ausgezählt = floor(neue_fom_df$wahlberechtigt / ffm_waehler * 100), + anz = neue_fom_df$meldungen_anz, + max = neue_fom_df$meldungen_max, + ts = neue_fom_df$zeitstempel) + briefwahl_anz <- stimmbezirke_df %>% filter(str_detect(nr,"^9")) %>% + pull(meldungen_anz) %>% sum() + briefwahl_max <- stimmbezirke_df %>% filter(str_detect(nr,"^9")) %>% + nrow() + annotate_str <- paste0("<strong>Derzeit sind ", + briefwahl_anz, + " von ", + briefwahl_max, + " Briefwahl-Stimmbezirken ausgezählt.</strong><br/><br/>", + annotate_str) + dw_edit_chart(fom_id,intro = beschreibung_str,annotate = annotate_str) + dw_publish_chart(fom_id) + return(TRUE) + } +} + + +#---- MAIN ---- +# Ruft aktualisiere_fom() auf +# (die dann wieder aktualisiere_karten() aufruft) +check = tryCatch( + { + neue_daten <- aktualisiere_fom(stimmbezirke_url) + }, + warning = function(w) {teams_warning(w,title="Feldmann: fom")}, + error = function(e) {teams_warning(e,title="Feldmann: fom")}) +# Neue Daten? Dann aktualisiere die Karten +if (neue_daten) { + check = tryCatch( + { + neue_daten <- aktualisiere_karten(stimmbezirke_url) + }, + warning = function(w) {teams_warning(w,title="Feldmann: Karten")}, + error = function(e) {teams_warning(e,title="Feldmann: Karten")}) + if (neue_daten) { + # Alles OK, letzte Daten nochmal holen und ausgeben + fom_df <- readRDS("daten/fom_df.rds") %>% + arrange(zeitstempel) %>% + tail(1) + if(fom_df$meldungen_anz > 0) { + stimmbezirke_df <- lies_gebiet(stimmbezirke_url) + briefwahl_anz <- stimmbezirke_df %>% filter(str_detect(nr,"^9")) %>% + pull(meldungen_anz) %>% sum() + briefwahl_max <- stimmbezirke_df %>% filter(str_detect(nr,"^9")) %>% + nrow() + fom_update_str <- paste0( + "<strong>Update OK</strong><br/><br/>", + fom_df$meldungen_anz, + " von ", + fom_df$meldungen_max," Stimmbezirke ausgezählt.<br> ", + "Derzeit sind ", + briefwahl_anz, + " von ", + briefwahl_max, + " Briefwahl-Stimmbezirken ausgezählt.<br/>", + "<ul><li><strong>Quorum zur Abwahl ist derzeit", + ifelse(fom_df$ja / fom_df$wahlberechtigt < 0.3, " nicht ", " "), + "erreicht</strong></li>", + "<li><strong>Anteil der Ja-Stimmen an den Wahlberechtigten: ", + format(fom_df$ja / fom_df$wahlberechtigt * 100,decimal.mark=",",big.mark=".",nsmall=1, digits=3),"%", + "</li><li>Ja-Stimmen: ", + format(fom_df$ja,decimal.mark=",",big.mark="."), + "</li><li>Nein-Stimmen: ", + format(fom_df$nein,decimal.mark=",",big.mark="."), + "</li><li>Verhältnis Ja:Nein: ", + format(fom_df$ja / (fom_df$ja + fom_df$nein) * 100,decimal.mark=",",big.mark=".",nsmall=1, digits=3),"% : ", + format(fom_df$nein / (fom_df$ja + fom_df$nein) *100,decimal.mark=",",big.mark=".",nsmall=1, digits=3),"%</li></ul>" + + ) + teams_meldung(fom_update_str,title="Feldmann-Referendum") + + } + } else { + teams_warning("Neue Stimmbezirk-Daten, aber keine neuen Ortsdaten?") + } +} +# Auch hier TRUE zurückbekommen;; alles OK? \ No newline at end of file diff --git a/README.md b/README.md index 4640ee6d11697585bb58c7f8fda7800fd439cfe6..b56eda9e4e778d815d162f3c68c3a50b71e370c7 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,55 @@ -# obwahlen -Code, um die Ergebnisse hessischer Bürgermeisterwahlen abzubilden +# obwahlen PRE + +**DIES IST IM AUGENBLICK NUR EINE NOCH NICHT ANGEPASSTE KOPIE DES REFERENDUMS-CODES** - bitte nicht nutzen und wundern! Anpassung spätestens zur [1. Runde der OB-Wahl in Frankfurt am 5. März 2023](https://frankfurt.de/aktuelle-meldung/meldungen/direktwahl-oberbuergermeisterin-oberbuergermeister-frankfurt/). + +R-Code, um den Auszählungsstand hessischer Bürgermeisterwahlen in Echtzeit abzurufen und mit Datawrapper darzustellen + +## Ordnerstruktur + +- **R** enthält den Code +- **index** enthält Index-, Konfigurations-, und Template-Dateien +- **daten** wird vom Code beschrieben und enthält den aktuellen Datenstand. + +## Daten aufarbeiten + +### Ziele + +Folgende Grafiken wären denkbar: +* Balkengrafik Ergebnis nach derzeitigem Auszählungsstand mit "Fortschrittsbalken" +* Choropleth Stadtteil-Sieger +* Choropleth Ergebnis nach Kandidat +* Choropleth Wahlbeteiligung +* Choropleth Briefwahl +* Tabelle nach Stadtteil +* Tabelle nach Kandidaten (Erste drei? fünf?) + +### Konfiguration + +- Konfigurationsdatei ```index/config.csv``` mit Link, Starttermin, Datawrapper-Zielen; Anzahl der eingegangenen Briefwahlstimmen +- ```index/index.rda``` mit Tabellen Zuordnung Stimmbezirk->Wahllokal und Stadtteilen + +### Aufarbeitung + +- Daten nach Stimmbezirk abfragen +- Zuordnung Stimmen zu Kandidaten, Umrechnung Prozente gültige Stimmen +Aggregation auf Stadtteilebene +- Zuordnung Stimmbezirk->Stadtteil +- Prozentanteile je Kandidat, Wahlbeteiligung +Aggregation auf Stadtebene +- Prozentanteile je Kandidat, Gewinner +- Fortschrittsbalken ausgezählte Wahllokale +- Fortschrittsbalken ausgezählte Stimmen (mit akt. Briefwahlstimmendaten) + + +## Struktur des Codes + +### Hauptroutinen + +- **update_all.R** ist das Skript für den CRON-Job. Es pollt nach Daten, ruft die Abruf-, Aggregations- und Auswertungsfunktionen auf und gibt Meldungen aus. +- **lies_aktuellen_stand.R** enthält Funktionen, die die Daten lesen, aggregieren und archivieren +- **aktualisiere_karten.R** enthält die Funktionen zur Datenausgabe +- **messaging.R** enthält Funktionen, die Teams-Updates und -Fehlermeldungen generieren + +### Hilfsfunktionen + +- **generiere_testdaten.R** ist ein Skript, das zufällige, aber plausible CSV-Daten auf Stimmbezirks-Ebene zum Testen generiert diff --git a/index/config.csv b/index/config.csv new file mode 100644 index 0000000000000000000000000000000000000000..04090781806af46ccd34af31eaf562a4a8cb9201 --- /dev/null +++ b/index/config.csv @@ -0,0 +1,8 @@ +name,value,comment +stimmbezirke_url,https://votemanager-ffm.ekom21cdn.de/2022-11-06/06412000/praesentation/Open-Data-06412000-Buergerentscheid-zur-Abwahl-des-Oberbuergermeisters-der-Stadt-Frankfurt-am-Main_-Herrn-Peter-Feldmann-Stimmbezirk.csv?ts=1667662273015,URL Daten-CSV Stimmbezirke +ffm_waehler,508182,Wahlamt +fom_id,bIm87,Datawrapper-ID Feldmann-o-meter +choropleth_id,UwKOO,Datawrapper-ID Stadtteile Choropleth-Karte +symbol_id,RWqrf,Datawrapper-ID Stadtteile Symbole (absolute Stimmen) +tabelle_id,hLqMi,Datawrapper-ID Tabelle Stadtteile +startdatum,2022-11-06 18:00:00 CET,Beginn der Auszählung diff --git a/index/stadtteile.csv b/index/stadtteile.csv new file mode 100644 index 0000000000000000000000000000000000000000..66dba9e3eb2e8c5a481e3be95da03d9da6e1352d --- /dev/null +++ b/index/stadtteile.csv @@ -0,0 +1,45 @@ +nr,name,lon,lat,wahlberechtigt_2018,waehler_2018,gueltig_2018,feldmann_2018 +1,Altstadt,8.682385346400634,50.11059669873516,2772,1035,1030,427 +2,Innenstadt,8.682664888207869,50.113790989177375,4343,1074,1066,435 +4,Westend-Süd,8.6594393465439925,50.11682467903111,13376,5835,5814,1814 +5,Westend-Nord,8.666488952498467,50.128769620533795,6887,2761,2751,1064 +6,Nordend-West,8.684596043386934,50.13022858244322,22988,10779,10743,4368 +7,Nordend-Ost,8.69761974358955,50.127318654892264,17390,7852,7817,3578 +8,Ostend,8.719218276147204,50.11554639352114,20946,8411,8358,3873 +9,Bornheim,8.712407551447747,50.13090801018288,22232,9682,9633,4954 +10,Gutleut- und Bahnhofsviertel,8.652137942960298,50.099479845695974,6802,2113,2103,831 +11,Gallus,8.636377745265355,50.10300477630784,23958,6529,6488,2926 +12,Bockenheim,8.632922516089874,50.12128753657858,27090,10377,10332,4546 +13,Sachsenhausen-Nord,8.684579993638577,50.10051804775371,23441,9940,9897,4128 +14,Sachsenhausen-Süd und Flughafen,8.629663957345496,50.0607635296547,20710,8739,8700,3702 +16,Oberrad,8.727138168461476,50.09922899999497,9206,3109,3093,1491 +17,Niederrad,8.636199605275262,50.081631798202295,16799,5412,5366,2712 +18,Schwanheim,8.572652070944704,50.081760019105545,13936,5261,5217,2524 +19,Griesheim,8.600109548586545,50.09781734654457,14910,3670,3632,1921 +20,Rödelheim,8.603076601631098,50.127692431637506,12446,4518,4487,2108 +21,Hausen,8.626134516198546,50.13524298077671,4518,1814,1796,889 +22,Praunheim,8.61444644716483,50.14547905112678,11093,4365,4337,2109 +24,Heddernheim,8.64020132453368,50.158128239125205,11867,4579,4546,2385 +25,Niederursel,8.616911198776547,50.16683966510584,10458,3843,3818,1797 +26,Ginnheim,8.648134546192246,50.14388748058928,11185,4310,4285,2101 +27,Dornbusch,8.670541998003081,50.14434313041997,13527,6140,6102,2671 +28,Eschersheim,8.659950213724542,50.16002839395001,11166,5005,4982,2159 +29,Eckenheim,8.683795236784233,50.148564823086005,9638,3457,3425,1764 +30,Preungesheim,8.697198159142667,50.15544843144313,10222,3983,3965,2025 +31,Bonames,8.665887880254154,50.18258113675648,4467,1566,1549,862 +32,Berkersheim,8.702941786636124,50.17015956481773,2607,1137,1129,515 +33,Riederwald,8.73274589886058,50.12667040584185,3209,1070,1063,669 +34,Seckbach,8.726644066440096,50.147246840458955,7419,2926,2909,1381 +35,Fechenheim,8.762275115113775,50.12551773891441,10381,2571,2551,1475 +36,Höchst,8.539657322936813,50.098523172532,9859,2527,2506,1251 +37,Nied,8.57676379509509,50.103479453362766,12659,3730,3708,1975 +38,Sindlingen,8.51273746688725,50.07800492013246,5763,1768,1748,1011 +39,Zeilsheim,8.495768400332896,50.097784690964886,8012,2328,2299,1199 +40,Unterliederbach,8.525490184772172,50.10992510336304,10485,3201,3176,1533 +41,Sossenheim,8.574019745075416,50.12010605434964,10126,2684,2652,1270 +42,Nieder-Erlenbach,8.709219115856458,50.20871646737095,3544,1808,1796,665 +43,Kalbach-Riedberg,8.639008245521376,50.1846309062546,12409,5359,5333,2349 +44,Harheim,8.689844680792895,50.18583895746724,3548,1846,1836,601 +45,Nieder-Eschbach,8.668094243977997,50.20106152063871,8041,2979,2950,1204 +46,Bergen-Enkheim,8.766772308170399,50.15913246211432,13431,5774,5742,2583 +47,Frankfurter Berg,8.673427563622,50.169778678198924,5409,2049,2030,978 diff --git a/index/zuordnung_wahllokale.csv b/index/zuordnung_wahllokale.csv new file mode 100644 index 0000000000000000000000000000000000000000..c673346f90a2723d028487a82298881b49dd4ceb --- /dev/null +++ b/index/zuordnung_wahllokale.csv @@ -0,0 +1,576 @@ +"nr","name","ortsteilnr" +"01001","010-01",1 +"01002","010-02",1 +"04001","040-01",2 +"07001","070-01",2 +"08001","080-01",2 +"09001","090-01",10 +"10001","100-01",4 +"10002","100-02",4 +"11001","110-01",4 +"11002","110-02",4 +"12001","120-01",6 +"12002","120-02",6 +"12003","120-03",6 +"12004","120-04",6 +"13001","130-01",7 +"13002","130-02",7 +"13003","130-03",7 +"14001","140-01",8 +"14002","140-02",8 +"14003","140-03",8 +"14004","140-04",8 +"14005","140-05",8 +"15101","151-01",10 +"15102","151-02",10 +"15103","151-03",10 +"15104","151-04",10 +"15301","153-01",11 +"15302","153-02",11 +"15401","154-01",11 +"15402","154-02",11 +"16101","161-01",11 +"16102","161-02",11 +"16103","161-03",11 +"16104","161-04",11 +"16201","162-01",11 +"16202","162-02",11 +"16203","162-03",11 +"16204","162-04",11 +"16205","162-05",11 +"16301","163-01",12 +"16302","163-02",12 +"16303","163-03",12 +"16401","164-01",11 +"16501","165-01",11 +"16502","165-02",11 +"16503","165-03",11 +"16504","165-04",11 +"17001","170-01",4 +"17002","170-02",4 +"17003","170-03",4 +"18001","180-01",4 +"18002","180-02",4 +"18003","180-03",4 +"19101","191-01",5 +"19102","191-02",5 +"19103","191-03",5 +"19201","192-01",5 +"19202","192-02",5 +"20101","201-01",6 +"20102","201-02",6 +"20201","202-01",6 +"20202","202-02",6 +"20301","203-01",6 +"20302","203-02",6 +"20303","203-03",6 +"20304","203-04",6 +"21101","211-01",6 +"21102","211-02",6 +"21103","211-03",6 +"21201","212-01",6 +"21202","212-02",6 +"21203","212-03",6 +"21301","213-01",6 +"22101","221-01",7 +"22102","221-02",7 +"22103","221-03",7 +"22201","222-01",7 +"22202","222-02",7 +"22203","222-03",7 +"22204","222-04",7 +"23001","230-01",7 +"23002","230-02",7 +"23003","230-03",7 +"24001","240-01",9 +"24002","240-02",9 +"25101","251-01",8 +"25102","251-02",8 +"25103","251-03",8 +"25104","251-04",8 +"25201","252-01",8 +"25202","252-02",8 +"25203","252-03",8 +"25204","252-04",8 +"25205","252-05",8 +"26101","261-01",8 +"26102","261-02",8 +"26103","261-03",8 +"26201","262-01",33 +"26202","262-02",33 +"27101","271-01",9 +"27102","271-02",9 +"27201","272-01",9 +"27202","272-02",9 +"27203","272-03",9 +"27204","272-04",9 +"27205","272-05",9 +"28101","281-01",9 +"28102","281-02",9 +"28103","281-03",9 +"28201","282-01",9 +"28202","282-02",9 +"28203","282-03",9 +"29001","290-01",9 +"29002","290-02",9 +"30001","300-01",13 +"30002","300-02",13 +"32101","321-01",13 +"32102","321-02",13 +"32103","321-03",13 +"32201","322-01",13 +"32202","322-02",13 +"32203","322-03",13 +"32204","322-04",13 +"32205","322-05",13 +"32301","323-01",14 +"32302","323-02",14 +"32303","323-03",14 +"32304","323-04",14 +"32305","323-05",14 +"32306","323-06",14 +"32401","324-01",13 +"32402","324-02",13 +"32403","324-03",13 +"32404","324-04",13 +"32601","326-01",14 +"32602","326-02",14 +"32603","326-03",14 +"33101","331-01",13 +"33102","331-02",13 +"33103","331-03",13 +"33201","332-01",14 +"33202","332-02",14 +"33203","332-03",14 +"33204","332-04",14 +"33205","332-05",14 +"33206","332-06",14 +"34101","341-01",12 +"34102","341-02",12 +"34103","341-03",12 +"34201","342-01",12 +"34202","342-02",12 +"34301","343-01",12 +"34401","344-01",12 +"35001","350-01",12 +"35002","350-02",12 +"35003","350-03",12 +"35004","350-04",12 +"36101","361-01",12 +"36102","361-02",12 +"36103","361-03",12 +"36201","362-01",12 +"36202","362-02",12 +"36203","362-03",12 +"36204","362-04",12 +"37101","371-01",17 +"37102","371-02",17 +"37103","371-03",17 +"37104","371-04",17 +"37105","371-05",17 +"37106","371-06",17 +"37201","372-01",17 +"37202","372-02",17 +"37203","372-03",17 +"37204","372-04",17 +"37205","372-05",17 +"37206","372-06",17 +"38001","380-01",16 +"38002","380-02",16 +"38003","380-03",16 +"38004","380-04",16 +"38005","380-05",16 +"38006","380-06",16 +"39001","390-01",34 +"39002","390-02",34 +"39003","390-03",34 +"39004","390-04",34 +"39005","390-05",34 +"39006","390-06",34 +"39007","390-07",34 +"40101","401-01",20 +"40102","401-02",20 +"40103","401-03",20 +"40104","401-04",20 +"40105","401-05",20 +"40201","402-01",20 +"40202","402-02",20 +"40203","402-03",20 +"40204","402-04",20 +"41001","410-01",21 +"41002","410-02",21 +"41003","410-03",21 +"42201","422-01",22 +"42202","422-02",22 +"42203","422-03",22 +"42301","423-01",22 +"42302","423-02",22 +"42401","424-01",22 +"42501","425-01",22 +"42601","426-01",22 +"42602","426-02",22 +"43101","431-01",24 +"43102","431-02",24 +"43103","431-03",24 +"43104","431-04",24 +"43105","431-05",24 +"43201","432-01",24 +"43202","432-02",24 +"43203","432-03",24 +"43204","432-04",24 +"44101","441-01",26 +"44102","441-02",26 +"44103","441-03",26 +"44104","441-04",26 +"44105","441-05",26 +"44106","441-06",26 +"44107","441-07",26 +"44108","441-08",26 +"44201","442-01",27 +"44202","442-02",27 +"44203","442-03",27 +"44204","442-04",27 +"45101","451-01",28 +"45102","451-02",28 +"45103","451-03",28 +"45104","451-04",28 +"45201","452-01",28 +"45202","452-02",28 +"45203","452-03",28 +"45204","452-04",28 +"46101","461-01",29 +"46102","461-02",29 +"46103","461-03",29 +"46104","461-04",29 +"46105","461-05",29 +"46106","461-06",29 +"46107","461-07",29 +"46201","462-01",27 +"46202","462-02",27 +"46203","462-03",27 +"46204","462-04",27 +"46205","462-05",27 +"46206","462-06",27 +"46301","463-01",27 +"46302","463-02",27 +"47001","470-01",30 +"47002","470-02",30 +"47003","470-03",30 +"47004","470-04",30 +"47005","470-05",30 +"47006","470-06",30 +"47007","470-07",30 +"48101","481-01",25 +"48102","481-02",25 +"48103","481-03",25 +"48201","482-01",25 +"48202","482-02",25 +"48203","482-03",25 +"48301","483-01",25 +"49101","491-01",31 +"49102","491-02",31 +"49103","491-03",31 +"49201","492-01",47 +"49202","492-02",47 +"49203","492-03",47 +"49204","492-04",47 +"50001","500-01",32 +"50002","500-02",32 +"51001","510-01",35 +"51002","510-02",35 +"51003","510-03",35 +"51004","510-04",35 +"52001","520-01",35 +"52002","520-02",35 +"52003","520-03",35 +"53101","531-01",18 +"53102","531-02",18 +"53103","531-03",18 +"53104","531-04",18 +"53105","531-05",18 +"53201","532-01",18 +"53202","532-02",18 +"53203","532-03",18 +"53204","532-04",18 +"53205","532-05",18 +"53206","532-06",18 +"54101","541-01",19 +"54102","541-02",19 +"54201","542-01",19 +"54202","542-02",19 +"54203","542-03",19 +"55101","551-01",19 +"55102","551-02",19 +"55201","552-01",19 +"55202","552-02",19 +"55203","552-03",19 +"56101","561-01",37 +"56102","561-02",37 +"56103","561-03",37 +"56104","561-04",37 +"56105","561-05",37 +"56201","562-01",37 +"56202","562-02",37 +"56203","562-03",37 +"56204","562-04",37 +"57001","570-01",36 +"57002","570-02",36 +"57003","570-03",36 +"58001","580-01",36 +"58002","580-02",36 +"59101","591-01",36 +"59102","591-02",36 +"60101","601-01",38 +"60102","601-02",38 +"60201","602-01",38 +"60202","602-02",38 +"60401","604-01",39 +"61101","611-01",39 +"61102","611-02",39 +"61201","612-01",39 +"61202","612-02",39 +"62101","621-01",40 +"62102","621-02",40 +"62103","621-03",40 +"62104","621-04",40 +"62201","622-01",40 +"62202","622-02",40 +"62203","622-03",40 +"63101","631-01",41 +"63102","631-02",41 +"63103","631-03",41 +"63104","631-04",41 +"63201","632-01",41 +"63202","632-02",41 +"63203","632-03",41 +"64001","640-01",42 +"64002","640-02",42 +"64003","640-03",42 +"65001","650-01",43 +"65002","650-02",43 +"65003","650-03",43 +"65101","651-01",43 +"65102","651-02",43 +"65103","651-03",43 +"65104","651-04",43 +"65105","651-05",43 +"65106","651-06",43 +"65107","651-07",43 +"66001","660-01",44 +"66002","660-02",44 +"66003","660-03",44 +"67001","670-01",45 +"67002","670-02",45 +"67003","670-03",45 +"67004","670-04",45 +"67005","670-05",45 +"67006","670-06",45 +"68001","680-01",46 +"68002","680-02",46 +"68003","680-03",46 +"68004","680-04",46 +"68005","680-05",46 +"68006","680-06",46 +"68007","680-07",46 +"68008","680-08",46 +"68009","680-09",46 +"68010","680-10",46 +90101,"901-01",1 +90201,"902-01",2 +90202,"902-02",2 +90401,"904-01",4 +90402,"904-02",4 +90403,"904-03",4 +90404,"904-04",4 +90405,"904-05",4 +90501,"905-01",5 +90502,"905-02",5 +90503,"905-03",5 +90601,"906-01",6 +90602,"906-02",6 +90603,"906-03",6 +90604,"906-04",6 +90605,"906-05",6 +90606,"906-06",6 +90607,"906-07",6 +90608,"906-08",6 +90609,"906-09",6 +90701,"907-01",7 +90702,"907-02",7 +90703,"907-03",7 +90704,"907-04",7 +90705,"907-05",7 +90706,"907-06",7 +90707,"907-07",7 +90801,"908-01",8 +90802,"908-02",8 +90803,"908-03",8 +90804,"908-04",8 +90805,"908-05",8 +90806,"908-06",8 +90807,"908-07",8 +90808,"908-08",8 +90809,"908-09",8 +90901,"909-01",9 +90902,"909-02",9 +90903,"909-03",9 +90904,"909-04",9 +90905,"909-05",9 +90906,"909-06",9 +90907,"909-07",9 +90908,"909-08",9 +90909,"909-09",9 +91001,"910-01",10 +91002,"910-02",10 +91003,"910-03",10 +91101,"911-01",11 +91102,"911-02",11 +91103,"911-03",11 +91104,"911-04",11 +91105,"911-05",11 +91106,"911-06",11 +91107,"911-07",11 +91108,"911-08",11 +91201,"912-01",12 +91202,"912-02",12 +91203,"912-03",12 +91204,"912-04",12 +91205,"912-05",12 +91206,"912-06",12 +91207,"912-07",12 +91208,"912-08",12 +91209,"912-09",12 +91210,"912-10",12 +91211,"912-11",12 +91301,"913-01",13 +91302,"913-02",13 +91303,"913-03",13 +91304,"913-04",13 +91305,"913-05",13 +91306,"913-06",13 +91307,"913-07",13 +91308,"913-08",13 +91309,"913-09",13 +91310,"913-10",13 +91401,"914-01",14 +91402,"914-02",14 +91403,"914-03",14 +91404,"914-04",14 +91405,"914-05",14 +91406,"914-06",14 +91407,"914-07",14 +91408,"914-08",14 +91409,"914-09",14 +91601,"916-01",16 +91602,"916-02",16 +91603,"916-03",16 +91701,"917-01",17 +91702,"917-02",17 +91703,"917-03",17 +91704,"917-04",17 +91705,"917-05",17 +91706,"917-06",17 +91801,"918-01",18 +91802,"918-02",18 +91803,"918-03",18 +91804,"918-04",18 +91805,"918-05",18 +91806,"918-06",18 +91901,"919-01",19 +91902,"919-02",19 +91903,"919-03",19 +91904,"919-04",19 +91905,"919-05",19 +92001,"920-01",20 +92002,"920-02",20 +92003,"920-03",20 +92004,"920-04",20 +92005,"920-05",20 +92101,"921-01",21 +92102,"921-02",21 +92201,"922-01",22 +92202,"922-02",22 +92203,"922-03",22 +92204,"922-04",22 +92205,"922-05",22 +92401,"924-01",24 +92402,"924-02",24 +92403,"924-03",24 +92404,"924-04",24 +92405,"924-05",24 +92501,"925-01",25 +92502,"925-02",25 +92503,"925-03",25 +92504,"925-04",25 +92601,"926-01",26 +92602,"926-02",26 +92603,"926-03",26 +92604,"926-04",26 +92701,"927-01",27 +92702,"927-02",27 +92703,"927-03",27 +92704,"927-04",27 +92705,"927-05",27 +92706,"927-06",27 +92801,"928-01",28 +92802,"928-02",28 +92803,"928-03",28 +92804,"928-04",28 +92901,"929-01",29 +92902,"929-02",29 +92903,"929-03",29 +92904,"929-04",29 +93001,"930-01",30 +93002,"930-02",30 +93003,"930-03",30 +93004,"930-04",30 +93101,"931-01",31 +93102,"931-02",31 +93201,"932-01",32 +93301,"933-01",33 +93401,"934-01",34 +93402,"934-02",34 +93403,"934-03",34 +93501,"935-01",35 +93502,"935-02",35 +93503,"935-03",35 +93504,"935-04",35 +93601,"936-01",36 +93602,"936-02",36 +93603,"936-03",36 +93701,"937-01",37 +93702,"937-02",37 +93703,"937-03",37 +93704,"937-04",37 +93801,"938-01",38 +93802,"938-02",38 +93901,"939-01",39 +93902,"939-02",39 +93903,"939-03",39 +94001,"940-01",40 +94002,"940-02",40 +94003,"940-03",40 +94004,"940-04",40 +94101,"941-01",41 +94102,"941-02",41 +94103,"941-03",41 +94104,"941-04",41 +94201,"942-01",42 +94202,"942-02",42 +94301,"943-01",43 +94302,"943-02",43 +94303,"943-03",43 +94304,"943-04",43 +94305,"943-05",43 +94401,"944-01",44 +94402,"944-02",44 +94501,"945-01",45 +94502,"945-02",45 +94503,"945-03",45 +94601,"946-01",46 +94602,"946-02",46 +94603,"946-03",46 +94604,"946-04",46 +94605,"946-05",46 +94701,"947-01",47 +94702,"947-02",47 diff --git a/obwahlen.Rproj b/obwahlen.Rproj new file mode 100644 index 0000000000000000000000000000000000000000..8e3c2ebc99e2e337f7d69948b93529a437590b27 --- /dev/null +++ b/obwahlen.Rproj @@ -0,0 +1,13 @@ +Version: 1.0 + +RestoreWorkspace: Default +SaveWorkspace: Default +AlwaysSaveHistory: Default + +EnableCodeIndexing: Yes +UseSpacesForTab: Yes +NumSpacesForTab: 2 +Encoding: UTF-8 + +RnwWeave: Sweave +LaTeX: pdfLaTeX