Vorbemerkungen

Dieses Dokument beschreibt die Vorprozessierung und explorative Analyse des Datensatzes, der Grundlage des auf srf.ch veröffentlichten Artikel Wofür zahle ich Steuern? ist.

SRF Data legt Wert darauf, dass die Datenvorprozessierung und -Analyse nachvollzogen und überprüft werden kann. SRF Data glaubt an das Prinzip offener Daten, aber auch offener und nachvollziehbarer Methoden. Zum anderen soll es Dritten ermöglicht werden, auf dieser Vorarbeit aufzubauen und damit weitere Auswertungen oder Applikationen zu generieren.

Die Endprodukte des vorliegenden Scripts, neben der vorliegenden explorativen Analyse, sind (Datenbeschreibung siehe unten):

  • cities.csv: Ausgabenstruktur der Mitglieder des Schweizer Städteverbands
  • cantons.csv: Ausgabenstruktur der Kantone
  • municipalities_by_canton.csv: Ausgabenstruktur der Gemeinden pro Kanton
  • cantons_and_municipalities.csv: Ausgabenstruktur der Kantone und all ihrer Gemeinden
  • federation.csv: Ausgabenstruktur des Bundes
  • deltas.csv: Abweichungen der Ausgaben von Schweizer Klein-, Mittel- und Grossstädte vom Durchschnitt

R-Script & Daten

Die Vorprozessierung und Analyse wurde im Statistikprogramm R vorgenommen. Das zugrunde liegende Script sowie die prozessierten Daten können unter diesem Link heruntergeladen werden. Durch Ausführen von main.Rmd kann der hier beschriebene Prozess nachvollzogen und der für den Artikel verwendete Datensatz generiert werden. Dabei werden Daten aus dem Ordner input eingelesen und Ergebnisse in den Ordner output geschrieben.

SRF Data verwendet das rddj-template von Timo Grossenbacher als Grundlage für seine R-Scripts. Entstehen bei der Ausführung dieses Scripts Probleme, kann es helfen, die Anleitung von rddj-template zu studieren.

Debug-Informationen: This report was generated on 2019-02-28 08:44:06. R version: 3.5.0 on x86_64-apple-darwin15.6.0. For this report, CRAN packages as of 2018-10-01 were used.

GitHub

Der Code für die vorliegende Datenprozessierung ist auf https://github.com/srfdata/2019-02-steuern zur freien Verwendung verfügbar.

Weitere Projekte

Code & Daten von SRF Data sind unter http://srfdata.github.io verfügbar.

Haftungsausschluss

Die veröffentlichten Informationen sind sorgfältig zusammengestellt, erheben aber keinen Anspruch auf Aktualität, Vollständigkeit oder Richtigkeit. Es wird keine Haftung übernommen für Schäden, die durch die Verwendung dieses Scripts oder der daraus gezogenen Informationen entstehen. Dies gilt ebenfalls für Inhalte Dritter, die über dieses Angebot zugänglich sind.

Datenbeschreibung

cities.csv, cantons.csv, cantons_and_municipalities.csv, federation.csv, municipalities_by_canton.csv

Attribut Typ Beschreibung
name String Name der Körperschaft (Kantonskürzel, Gemeindename)
id String Identifikationsnummer des Ausgabenbereichs (Vorsicht: 01 und 1 sind nicht dasselbe)
category String Name des Ausgabenbereichs
is_main Boolean Angabe: Ist diese Kategorie eine Hauptkategorie oder nicht
share Numeric Anteil dieses Ausgabenpunkts an den Gesamtausgaben als Dezimalzahl
bfs_id Numeric BFS ID der Gemeinde (nur in cities.csv)

deltas.csv

Attribut Typ Beschreibung
name String Name der Körperschaft (Kantonskürzel, Gemeindename)
id String Identifikationsnummer des Ausgabenbereichs (Vorsicht: 01 und 1 sind nicht dasselbe)
category String Name des Ausgabenbereichs
amount Numeric Angabe: Ist diese Kategorie eine Hauptkategorie oder nicht
type Factor Einteilung in drei Gruppen: Kleinstadt, Mittelstadt, Grossstadt
share Numeric Anteil dieses Ausgabenbereichs an den Gesamtausgaben als Dezimalzahl
average Numeric Durchschnittlicher Anteil dieses Bereichs über alle Gemeinden in dieser Gruppe
delta Numeric Abweichung dieser Stadt bzw. dieses Bereichs vom obigen Durchschnitt

Originalquelle

Steuerbelastung pro Person

input/SB-NP-alle-Gden_2017.xlsx

Die Eidgenössische Steuerverwaltung ESTV veröffentlicht für jedes Jahr eine Tabelle mit der Steuerbelastung verschiedener Personen-Modelle für jede Gemeinde der Schweiz. Aufgrund dieser Prozentwerte berechnen wir für eineN Website-BenutzerIn, wie viele Franken Steuern er/sie insgesamt zu bezahlen hat.

Darin enthalten ist auch die Steuerbelastung durch die Direkte Bundessteuer. Diese ist schweizweit gleich für alle natürlichen Personen.

Steuerbelastung pro Einkommensgruppe

input/NP_2015_mitnull.xlsx

Die dazugehörigen Tabellen der ESTV können auf deren Website heruntergeladen werden. Kapitel: Statistische Kennzahlen > Natürliche Personen > Mit einer Belastung durch die direkte Bundessteuer. Sie liegen erst für bis und mit 2015 vor.

Effektive Steuerbelastung (nicht selbst berechnet)

input/Progression_Export_SRF.xlsx

Von Kurt Schmidheiny haben wir die genannte Tabelle erhalten, um die Grafik in dem Working Paper Effective Tax Rates and Effective Progressivity in aFiscally Decentralized Country Seite 18 nachzubilden. Sie zeigt, dass Reiche (Einkommen über 3 Mio CHF / Jahr) systeamtisch ihren Wohnsitz in Tiefsteuergemeinden verlegen und dass deshalb schweizweit gesehen, das Steuersystem nicht mehr unbedingt progressiv ist.

Steuerfüsse

input/steuerfuesse.csv

Um zu eruieren, wie gross der Kantons- bzw. Gemeinde-Anteil an der Steuerbelastung ist, verwenden wir die Tabelle Steuerfüsse der natürlichen Personen, die Teil des Steuerrechners der ESTV ist. Wenn die Gemeinde darin z.B. einen Steuerfuss besitzt von 119 und der Kanton einen Steuerfuss von 100, so beträgt der Anteil der Gemeinde 100 / (100 + 119) = 45.6% und der Anteil des Kantons entsprechend 54.4%.

Da der Kanton jedoch 17% der Direkten Bundessteuer behalten darf, da er für die Erhebung der Steuern der natürlichen Personen verantwortlich ist, addieren wir 17% der direkten Bundessteuer zum Kantonsanteil dazu.

Sonderall Wallis

input/tarifs/VS_bareme_Hilfstabellen/…

In Wallis ist leider alles etwas komplizierter. Der Kanton und jede Gemeinde haben nicht einfach einen Steuerfuss, sondern einen Multiplikations-Faktor und einen Index. Der Index gibt an, nach welcher Progression die Steuern erhoben werden. Das führt dazu, dass je nach Höhe des Einkommens unterschiedlich viel Geld an die Gemeinde bzw. an den Kanton fliessen. Beide Werte werden anschliessend wird der definierte Wert mit dem Faktor multipliziert. Der Kanton hat im Moment einen Index von 160%.

Von der Website des Kantons Wallis können wir die Tabellen als Textfiles herunterladen, in denen steht, mit wie viel Einkommen man bei welcher Indexierung wie viel Steuern bezahlt. Die Werte gelten «von 2008 bis heute» bzw. die Gemeindesteuer Tabellen gelten von «2012 bis 2017».

Der Ordner tarifs enthält ausserdem noch PDFs zu der Ausgestaltung der Steuern in allen Kantonen. Diese PDFs werden in der Analyse nicht verwendet, aber können ev. von Hilfe sein, um kantonale Besonderheiten nachzuvollziehen.

Ausgaben nach funktionaler Gliederung

→ Tabellen im Ordner input/zip_d_2016

Die ESTV publiziert ausserdem für den Bund, alle Kantone und alle Städte im Schweizerischen Städteverband (über 150 Gemeinden) eine detaillierte Berichterstattung über deren Finanzen. Darin befinden sich bei allen Ebenen die beiden Tabellenblätter ord_ausgaben_funk und ord_einnahmen_funk. Dies ist die sogenannte «funktionale Gliederung». Sie besteht aus 10 Haupt und ca. 70 Unterkategorien. Wir haben uns entschieden, mit dieser Einteilung zu arbeiten, da sie mit für Laien leicht verständlichen Kategorien arbeitet. Es ist nicht immer einfach, die Kosten des Staates in diese Kategorien zu unterteilen. Wir sind uns bewusst, dass nicht jede Gemeinde / jeder Kanton die funktionale Gliederung genau gleich vornimmt. Sie ist jedoch die zuverlässigste und vollständigste Sammlung der föderalen Schweizer Staatsfinanzen.

Haben in einem Punkt die Einnahmen die Ausgaben überstiegen (für den Staat also ein Plus resultierte), haben wir dieses Plus nicht beachtet und die Einnahmen durch null ersetzt, da wir ja nur an den Ausgaben interessiert waren.

Die Abkürzungen bedeuten folgendes:

  • fs_bund: Zahlen des Bundes (inkl. Vergangenheit)
  • fs_staat: Summierte Zahlen Gemeinden plus Kantone plus Bund (inkl. Vergangenheit)
  • fs_ktn: Zahlen der Kantone (inkl. Vergangenheit)
  • fs_ktn_gdn: Summierte Zahlen Gemeinde plus Kanton (inkl. Vergangenheit)
  • fs_gdn: Zahlen der Gemeinden

Im Ordner zu den Gemeinden gibt es einerseits summierte Zahlen aller Gemeinden in einem Kanton (inkl. Vergangenheit). Dazu kommt jedoch eine weitere Tabelle mit allen Kantonshauptorten bzw. allen Städten im Städteverband. (ohne Vergangenheit)

Städte im Städteverband

→ Tabellen im Ordner input/stdt_vgl

Da wir mit den Zahlen der Städte Analysen machen, haben wir vom ESTV zusätzlich die Zahlen der Jahre 2010-16 erhalten.

Einwohnerzahlen

px-x-0102020000_201.csv

Wir exportieren alle Gemeinden und alle Kantone und die Schweiz von allen verfügbaren Jahren als CSV aus STATPOP: Bilanz der Ständigen Wohnbevölkerung. (Speichern unter «Textdatei, kommagetrennt (ohne Kopfzeilen)»)

Das BFS gibt folgendes zu bedenken: «Die Daten weisen den Gemeindestand per 31. Dezember des letzten produzierten Jahres aus. Zu beachten ist, dass die nicht mehr bestehenden Thurgauer Gemeinden 4505 Neukirch an der Thur, 4670 Illighausen und 4695 Scherzingen immer noch ausgewiesen werden, da eine rückwirkende Verteilung der Wohnbevölkerung für die Jahre vor 1994 nicht möglich war.»

Wir arbeiten für die Berichterstattung mit dem neusten verfügbaren Stand der Daten. Dies hat Vor- und Nachteile. Zum Beispiel sind im Jahr 2017 16 kleinere Gemeinden zur Gemeinde Bellinzona fusioniert. Diese kleineren Gemeinden können nicht mehr gefunden werden vom Benutzer, bzw. Bellinzona kann gefunden werden, war aber zum Zeitpunkt der Datensammlung 2010-16 noch kleiner als heute.

Alles in allem ist es aber durchaus sinnvoll, dass wir mit dem neusten Stand der Gemeinden arbeiten, auch wenn die Zahlen zu den Ausgaben wie erwähnt von 2010 bis 2016 gehen. Fusionen betreffen, mit Ausnahme von Illnau-Effretikon, Wil, Glarus und Bellinzona vor allem kleinere Gemeinden, zu denen wir ohnehin keine Gemeindespezifischen Zahlen haben.

Gemeindenamen auf italienisch und französisch

be-b-00.04-agv-01.xls

Namen für Gemeinden auf Italienisch und Französisch entnehmen wir dem Amtlichen Gemeindeverzeichnis der Schweiz. Die Zuordnungen zu Gemeindenummern (BFS ID) wurde von Hand gemacht und in Tabelle input/gemeindenamen_de_fr_it.csv gespeichert.

Den Inhalt der Tabellen kann auch in diesem öffentlichen Google Spreadsheet betrachtet werden.

Raumgliederung

input/Raumgliederungen.xlsxklein_mittel_grossstadt.csv

Die Einteilung der Städte in die Gruppen klein, mittel und gross haben wir der Raumgliederung des BFS entnommen (Gemeindetypologie 1980-2000, 22 Typen). Die Tabelle haben wir aus der Applikation der Schweizer Gemeinden des BFS exportiert.

BIP

input/je-d-04.02.01.08.xlsx

In der einen Grafik im Skript (nicht jedoch in der öffentlichen Berichterstattung) haben wir das Brutto-Inlandsprodukt als Vergleich dazugezogen. Die Werte haben wir beim Bundesamt für Statistik heruntergeladen.

Funktionale Gliederung

Um besser nachvollziehen zu können, was sich hinter den Begriffen der funktionalen Gliederung verbirgt, haben wir drei Word-Dokumente (de, fr, it) zu Rate gezogen, die von der Konferenz der kantonalen Finanzdirektorinnen und -direktoren stammen:

  • input/funktionale_gliederung/srs-cspcp_kontenrahmen_und_funktionale_gliederung_hrm2_v10_2017_12_14_aend_3.docx
  • input/funktionale_gliederung/srs-cspcp_piano_contabile_ed_articolazione_funzionale_mpca2_v10_2017_12_14_modif_3.docx
  • input/funktionale_gliederung/srs-cspcp_plan_comptable_et_classification_fonctionnelle_mch2_v10_2017_12_14_modif_1.docx

Und deren Inhalt bezüglich der funktionalen Gliederung manuell in diese sechs Tabellen extrahiert:

  • input/funktionale_gliederung/unterkategorien_de.csv
  • input/funktionale_gliederung/unterkategorien_texte_de.csv
  • input/funktionale_gliederung/unterkategorien_fr.csv
  • input/funktionale_gliederung/unterkategorien_texte_fr.csv
  • input/funktionale_gliederung/unterkategorien_it.csv
  • input/funktionale_gliederung/unterkategorien_texte_it.csv

Vorbereitungen

## [1] "package package:rmarkdown detached"
## Loading required package: knitr
## Loading required package: rstudioapi

Packages definieren

# from https://mran.revolutionanalytics.com/web/packages/checkpoint/vignettes/using-checkpoint-with-knitr.html
# if you don't need a package, remove it from here (commenting is probably not sufficient)
# tidyverse: see https://blog.rstudio.org/2016/09/15/tidyverse-1-0-0/
cat("
library(rstudioapi)
library(raster) # raster handling (needed for relief)
library(tidyverse) # ggplot2, dplyr, tidyr, readr, purrr, tibble
library(ggrepel) # repelling texts for ggplot
library(glue) # easier templating output
library(magrittr) # pipes
library(scales) # scales for ggplot2
library(jsonlite) # json
library(svglite) # svg export
library(lintr) # code linting
library(styler) # code formatting
library(rmarkdown) # needed for automated knitting
library(sf) # simple features for R
library(rmapshaper) # simplify shapes to reduce file size",
file = "manifest.R")

Packages installieren

# if checkpoint is not yet installed, install it (for people using this
# system for the first time)
if (!require(checkpoint)) {
  if (!require(devtools)) {
    install.packages("devtools", repos = "http://cran.us.r-project.org")
    require(devtools)
  }
  devtools::install_github("RevolutionAnalytics/checkpoint",
                           ref = "v0.3.2", # could be adapted later,
                           # as of now (beginning of July 2017
                           # this is the current release on CRAN)
                           repos = "http://cran.us.r-project.org")
  require(checkpoint)
}
# nolint start
if (!dir.exists("~/.checkpoint")) {
  dir.create("~/.checkpoint")
}
# nolint end
# install packages for the specified CRAN snapshot date
checkpoint(snapshotDate = package_date,
           project = path_to_wd,
           verbose = T,
           scanForPackages = T,
           use.knitr = F,
           R.version = R_version)
rm(package_date)

Packages laden

source("manifest.R")
unlink("manifest.R")
sessionInfo()
## R version 3.5.0 (2018-04-23)
## Platform: x86_64-apple-darwin15.6.0 (64-bit)
## Running under: macOS Sierra 10.12.6
## 
## Matrix products: default
## BLAS: /Library/Frameworks/R.framework/Versions/3.5/Resources/lib/libRblas.0.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/3.5/Resources/lib/libRlapack.dylib
## 
## locale:
## [1] de_CH.UTF-8/de_CH.UTF-8/de_CH.UTF-8/C/de_CH.UTF-8/de_CH.UTF-8
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
##  [1] rmapshaper_0.4.1 sf_0.6-3         rmarkdown_1.10   styler_1.0.2    
##  [5] lintr_1.0.2      svglite_1.2.1    jsonlite_1.5     scales_1.0.0    
##  [9] magrittr_1.5     glue_1.3.0       ggrepel_0.8.0    forcats_0.3.0   
## [13] stringr_1.3.1    dplyr_0.7.6      purrr_0.2.5      readr_1.1.1     
## [17] tidyr_0.8.1      tibble_1.4.2     ggplot2_3.0.0    tidyverse_1.2.1 
## [21] raster_2.8-4     sp_1.3-1         checkpoint_0.4.0 rstudioapi_0.7  
## [25] knitr_1.20      
## 
## loaded via a namespace (and not attached):
##  [1] Rcpp_0.12.18       lubridate_1.7.4    lattice_0.20-35   
##  [4] class_7.3-14       assertthat_0.2.0   rprojroot_1.3-2   
##  [7] digest_0.6.17      V8_1.5             R6_2.2.2          
## [10] cellranger_1.1.0   plyr_1.8.4         backports_1.1.2   
## [13] evaluate_0.11      e1071_1.7-0        httr_1.3.1        
## [16] pillar_1.3.0       gdtools_0.1.7      rlang_0.2.2       
## [19] geojsonlint_0.2.0  curl_3.2           lazyeval_0.2.1    
## [22] readxl_1.1.0       munsell_0.5.0      broom_0.5.0       
## [25] compiler_3.5.0     modelr_0.1.2       pkgconfig_2.0.2   
## [28] htmltools_0.3.6    tidyselect_0.2.4   codetools_0.2-15  
## [31] jsonvalidate_1.0.0 crayon_1.3.4       withr_2.1.2       
## [34] grid_3.5.0         nlme_3.1-137       spData_0.2.9.4    
## [37] gtable_0.2.0       DBI_1.0.0          units_0.6-1       
## [40] cli_1.0.1          stringi_1.2.4      rex_1.1.2         
## [43] bindrcpp_0.2.2     xml2_1.2.0         tools_3.5.0       
## [46] hms_0.4.2          yaml_2.2.0         colorspace_1.3-2  
## [49] classInt_0.2-3     rvest_0.3.2        bindr_0.1.1       
## [52] haven_1.1.2

# latest year of interest
years_of_interest <- 2010:2016
latest_municipality_data_available <- 2017

# define biggest 10 names with name and bfs_id
big_10_names <- c(
  "Zürich" = 261,
  "Genève" = 6621,
  "Bern" = 351,
  "Basel" = 2701,
  "Lausanne" = 5586,
  "Winterthur" = 230,
  "Luzern" = 1061,
  "St. Gallen" = 3203,
  "Lugano" = 5192,
  "Biel/Bienne" = 371
)

# define principal towns (Hauptorte) for each canton
principal_towns <- c(
  "ZH" = 261,  # Zürich
  "BE" = 351,  # Bern
  "LU" = 1061, # Luzern
  "UR" = 1201, # Altdorf (UR)
  "SZ" = 1372, # Schwyz
  "OW" = 1407, # Sarnen
  "NW" = 1509, # Stans
  "GL" = 1632, # Glarus
  "ZG" = 1711, # Zug
  "FR" = 2196, # Fribourg
  "SO" = 2601, # Solothurn
  "BS" = 2701, # Basel
  "BL" = 2829, # Liestal
  "SH" = 2939, # Schaffhausen
  "AR" = 3001, # Herisau
  "AI" = 3101, # Appenzell
  "SG" = 3203, # St. Gallen
  "GR" = 3901, # Chur
  "AG" = 4001, # Aarau
  "TG" = 4566, # Frauenfeld
  "TI" = 5002, # Bellinzona
  "VD" = 5586, # Lausanne
  "VS" = 6266, # Sion
  "NE" = 6458, # Neuchâtel
  "GE" = 6621, # Genève
  "JU" = 6711  # Delémont
)

# get list of cantons and its abbreviations for automated read-in of data
canton_abbrs <- read_csv("input/canton_abbrs_regis.csv")
## Parsed with column specification:
## cols(
##   name = col_character(),
##   abbr = col_character(),
##   regi = col_character()
## )
# define categorical color scale with 10 values
categorical <- c(
  "#e31f2b",
  "#f7a600",
  "#a8b51c",
  "#61b13e",
  "#1cb373",
  "#1cb0b5",
  "#1e8ce3",
  "#a359c0",
  "#ca51af",
  "#9f9c90"
)

# prepare green color palette defined in design phase
green_palette <- c(
  "federal" = "#c2eedc",
  "cantonal" = "#1cb373",
  "municipal" = "#168c5a",
  "sum" = "grey"
)

slugify <- function(string) {
  string %>%
    tolower() %>%
    str_replace_all(c(
      "ü" = "ue", "[öœ]" = "oe", "[äæ]" = "ae", "ç" = "c", "[ôò]" = "o",
      "[éèêë]" = "e", "[àáâ]" = "a", "[îïì]" = "i", "[ùû]" = "u"
    )) %>%
    str_replace_all("[^\\w]+", "-") %>%
    str_replace_all("[^\\w]$", "")
}

Ausgaben liegen vor zu den Jahren 2010 bis max(years_of_interest)}Der Stand der Gemeinden stammt aus dem Jahr 2017

# function that clamps negative values (more income than expenditures) to zero!
# after doing this, the main categories get newly calculated so they are again
# the sum of all their subcategories
clamp_subs_to_zero_and_sum_again <- function(df) {
  # main categories and sub categories both add up to 100 percent each
  # so we have to treat them separately when calculating percentages
  # main categories are 0 to 9, sub categories have two digits
  result <- df %>%
    mutate(is_main = nchar(id) == 1)

  # save for later, but keep throw away the value
  main_categories <- result %>%
    filter(is_main) %>%
    select(-value)

  # do the clamping
  result %<>%
    # we calculate the shares based only on the values of the subcategories
    filter(!is_main) %>%
    mutate(
      # add indicator to show which main category the subcategory belongs to
      main_category = str_sub(id, 0, 1),
      # clamp negative values to zero
      value = ifelse(value < 0, 0, value)
    )

  # by clamping values to zero the value in the main category is no longer
  # equal with the sum of it's subcategories, as they can get clamped to zero
  # but the parent does not. For this reason we have to replace the values for
  # the main categories with the sum of it's subcategories. That's the correct
  # way to do it becaus subcategories do not empty each other out in one main
  # category
  main_categories %<>%
    left_join(
      result %>%
        # sum the shares of the subcategories by main category
        group_by(name, main_category, year) %>%
        summarise(value = sum(value)),
      by = c("id" = "main_category", "name", "year")
    )

  # bind together and sort by id
  result %<>%
    bind_rows(main_categories) %>%
    arrange(year, name, id) %>%
    # throw away unneeded columns
    select(-main_category)

  return (result)
}

# function that subtracts income from expenditures (for use below)
# it executes the function that is passed to it as an argument
calculate_costs <- function(reading_function) {
  reading_function("ord_ausgaben_funk") %>%
    # rename columns accordingly
    rename(expenditure = value) %>%
    left_join(
      reading_function("ord_einnahmen_funk") %>%
        select(name, id, category, year, income = value),
      by = c("name", "id", "category", "year")
    ) %>%
    # leave away category 91 (taxes)
    # by doing this we can focus on those categories that cost the state money
    filter(id != "91") %>%
    # subtract the income from the expenditures to get the netto costs
    # but replace NAs with zero
    mutate(
      value = ifelse(is.na(expenditure), 0, expenditure) -
        ifelse(is.na(income), 0, income)
    ) %>%
    # clean up
    select(-expenditure, -income) %>%
    clamp_subs_to_zero_and_sum_again()
}

# takes a long string and shortens it to the given length
# I use this for the long category names in ggplot
shorten <- function(str, length = 20, appendix = "…") {
  ifelse(
    nchar(str) < length,
    str,
    paste0(substr(str, 0, length), appendix)
  )
}

Daten einlesen

Einwohnerzahlen

inhabitants_yearly <- read_csv(
  "input/px-x-0102020000_201.csv",
  skip = 1,
  col_names = FALSE
)
## Parsed with column specification:
## cols(
##   X1 = col_integer(),
##   X2 = col_character(),
##   X3 = col_character(),
##   X4 = col_character(),
##   X5 = col_integer()
## )
# define pattern to exclude everything that does not match one of these
regex <- c(
  "municipality" = "^\\.\\.\\.\\.\\.\\.",
  "canton" = "-\\s",
  "total" = "^Schweiz$"
)
regex_any <- paste(regex, collapse = "|")

inhabitants_yearly %<>%
  # keep only name of geography and number of inhabitants (first two columns)
  select(geography = X2, inhabitants = X5, year = X1) %>%
  # filter out everything but municipalities and cantons
  filter(str_detect(geography, regex_any)) %>%
  # create new column for canton and district
  separate(
    geography,
    into = c("geography", "canton"),
    sep = regex[["canton"]]
  ) %>%
  # fill empty cells for each municipality
  fill(canton)
## Warning: Expected 2 pieces. Missing pieces filled with `NA` in 83028
## rows [1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
## 21, ...].
# extract municipalities in its own data frame
inhabitants_yearly_municipalities <- inhabitants_yearly %>%
  filter(str_detect(geography, regex[["municipality"]])) %>%
  # remove dots
  mutate(
    geography = str_replace_all(geography, regex[["municipality"]], "")
  ) %>%
  # split municipality_id into separate column
  separate(geography, into = c("bfs_id", "municipality"), sep = 5) %>%
  # convert bfs_id to numeric
  mutate(bfs_id = as.numeric(bfs_id))

# extract cantons in its own data frame
inhabitants_yearly_cantons <- inhabitants_yearly %>%
  filter(nzchar(geography) == 0) %>%
  select(-geography, name = canton)

# reduce main data frame to totals
inhabitants_yearly %<>%
  filter(str_detect(geography, regex[["total"]])) %>%
  select(-canton, -geography)

# select the relevant year for easier joins below
inhabitants_municipalities <- inhabitants_yearly_municipalities %>%
  filter(year == latest_municipality_data_available) %>%
  select(-year) %>%
  # throw away three municipalities in Thurgau that don't exist anymore
  filter(!bfs_id %in% c(4505, 4670, 4695))

# prepare simplified version for exporting (used in dropdown search)
export_municipalities <- inhabitants_municipalities %>%
  left_join(canton_abbrs, by = c("canton" = "name")) %>%
  select(label = municipality, value = bfs_id, canton = abbr)

# clean up
rm(regex, regex_any, export_municipalities)

Städte

Folgende Fusionen fanden innerhalb der Städte des Städteverbands statt:

  • ab 2011 gehören Ennenda, Netstal und Riedern zu Glarus
  • ab 2013 gehört Bronschhofen zu Wil
  • ab 2016 gehört Kyburg zu Illnau-Effretikon
  • ab 2017 gehören Camorino, Claro, Giubiasco, Gnosca, Gorduno, Gudo, Moleno, Monte Carasso, Pianezzo, Preonzo, Sant’Antonio und Sementina zu Bellinzona

Kyburg ist die einzige Gemeinde, die vor der Fusion auch Mitglied des Städteverbands war. Da wir mit dem Gemeindestand von 2017 arbeiten, fragt sich, ob wir z.B. Glarus nach der Fusion mit Glarus vor der Fusion vergleichen können. Wir sind der Meinung: Ja, das ist legitim. Da wir die Ausgaben pro Kategorie als Prozentwerte der gesamten Ausgaben betrachten, fällt die Höhe der Gesamtausgaben nicht so stark ins Gewicht.

Bestimmt gibt es relativ starke Veränderungen durch eine Fusion, gleichzeitig möchten wir nicht auf die historischen Daten, z.B. von Illnau-Effretikon bis 2016 verzichten.

Der Benutzer wird die Gemeinde Kyburg im Frontend nicht mehr auswählen können, da diese 2017 nicht mehr existiert. Wir ignorieren Sie deshalb an dieser Stelle.

# read in years 2010 to 2016 and bind together
read_cities <- function(sheet_name) {
  map_df(years_of_interest, function(year) {
    readxl::read_excel(
      glue("input/stdt_vgl/stdt_vgl.{year}.xlsx"),
      sheet = sheet_name,
      skip = 6
    ) %>%
      rename(id = X__1, category = `1 000 CHF`) %>%
      # make tidy and select only necessary columns
      gather(name, value, -one_of("id", "category")) %>%
      mutate(year = year) %>%
      # add flag for whether any value is present in the whole column
      group_by(name) %>%
      mutate(has_values = sum(!is.na(value)) > 0) %>%
      ungroup() %>%
      # throw away the rest and remove the flag again
      filter(has_values) %>%
      select(-has_values) %>%
      # separate e.g. ort_132762_allschwil into bfs id and name
      mutate(
        bfs_id = as.numeric(str_sub(name, 7, 10)),
        name = str_sub(name, 12) # has underscores in it and is not unique
      ) %>%
      mutate(
        # by manual inspection we see: basel has the weird bfs_id 0, replace it
        bfs_id = ifelse(name == "basel_stadt", 2701, bfs_id),
        # correct municipalities that were merged (see explanation above)
        # municipalities Glarus + Ennenda + Netstal + Riedern are now Glarus
        bfs_id = ifelse(bfs_id == 1609, 1632, bfs_id),
        # Wil 3427 + Bronschofen are now Wil 3427
        bfs_id = ifelse(bfs_id == 3425, 3427, bfs_id),
        # Illnau-Effretikon 174 + Kyburg 175 are now Illnau-Effretikon
        bfs_id = ifelse(bfs_id == 174, 296, bfs_id)
      ) %>%
      filter(bfs_id != 175) %>%
      # now replace the ugly name with the unique one
      select(-name) %>%
      left_join(
        inhabitants_municipalities %>%
          select(bfs_id, name = municipality),
        by = "bfs_id"
      )
  })
}

cities_yearly <- calculate_costs(read_cities)

# check whether the big cities are complete
cities_missing <- cities_yearly %>%
  # filter(bfs_id %in% big_10_names | bfs_id %in% principal_towns) %>%
  filter(category == "Allgemeine Verwaltung") %>%
  # add flag to evaluate whether values are present
  mutate(has_value = !is.na(value)) %>%
  select(name, year, has_value) %>%
  group_by(name) %>%
  # show only the ones that have values missing
  mutate(is_complete = sum(has_value) == length(years_of_interest)) %>%
  filter(!is_complete)

ggplot(
  data = cities_missing,
  aes(
    x = year,
    y = name
  )
) +
  geom_point(color = "#25a567") +
  xlim(c(min(years_of_interest), max(years_of_interest))) +
  theme_minimal() +
  labs(
    title = "Die folgenden Städte haben Lücken",
    subtitle = "Grün bedeutet Daten sind vorhanden",
    x = NULL,
    y = NULL
  )

# clean up
rm(cities_missing)

Einige Gemeinden weisen gewisse Lücken auf. Diese können entstehen, weil die Stadt erst im Laufe der Zeit zum Städteverband dazugestossen ist. Da wir im späteren Prozess Durchschnitswerte berechnen, verzichten wir darauf, diese herauszufiltern. Die Datengrundlage ist dann einfach etwas weniger stabil, da weniger Jahre in den Durchschnitt einfliessen. Das ist ok.

Um zu evaluieren, ob wir mit dem aktuellsten Jahr (2016) arbeiten sollen, oder mit einem Durchschnitt aller verfügbaren Jahre, untersuchen wir alle eingelesenen Daten auf ihre Volatilität:

evaluate_extreme_changes <- function(df, delta = 0.2) {
  df %>% group_by(category, name) %>%
    mutate(
      median = median(value, na.rm = TRUE),
      # set flat is_extreme for values that differ more than 20% from median
      is_extreme = abs( (value - median) / median) > delta,
      # apply flag to whole group if one element is extreme
      is_extreme = sum(is_extreme) > 0
    )
}

cities_extreme_changes <- cities_yearly %>%
  evaluate_extreme_changes()

glue(
  "{percent(share_is_extreme)} of all main categories have changes +/- 20%}",
  share_is_extreme =
    cities_extreme_changes %>% filter(is_main, is_extreme) %>% nrow() /
    cities_extreme_changes %>% filter(is_main) %>% nrow()
)
## 79.6% of all main categories have changes +/- 20%}
plot_extreme_changes <- function (df, delta = 0.2) {
  ggplot(
    data = df %>%
      filter(is_main, is_extreme),
    aes(
      x = year,
      y = value,
      group = name
    )
  ) +
    geom_line(alpha = 0.5) +
    facet_wrap(~ category, scales = "free_y") +
    theme_minimal() +
    labs(
      title = glue("Diese Kategorien haben Veränderungen +/- {percent(delta)}")
    ) %>%
    print()
}

cities_extreme_changes %>%
  plot_extreme_changes()
## $title
## Diese Kategorien haben Veränderungen +/- 20.0%
## 
## attr(,"class")
## [1] "labels"

Wir sehen: Vor allem auf Gemeinde-Ebene sind die Schwankungen sehr stark. Es macht deshalb Sinn, mit Durchschnittswerten zu arbeiten.

Kantone

# save data mangling as a function to minimize repition
read_cantons <- function(sheet_name) {
  canton_abbrs$abbr %>%
    map_df(function(abbr) {
      readxl::read_excel(
        glue(
          "input/zip_d_{max(years_of_interest)}/fs_ktn/ktn_{tolower(abbr)}.xlsx"
        ),
        sheet = sheet_name,
        skip = 6
      ) %>%
        mutate(name = abbr) %>%
        rename(id = X__1, category = `1 000 CHF`) %>%
        gather(year, value, -one_of("id", "category", "name")) %>%
        # convert to correct data type
        mutate(year = as.numeric(year))
    })
}

# read income and expenditures and calculate net costs
cantons_yearly <- calculate_costs(read_cantons)

# filter to relevant years
cantons_yearly %<>% filter(year %in% years_of_interest)
cantons_extreme_changes <- cantons_yearly %>%
  evaluate_extreme_changes()

glue(
  "{percent(share_is_extreme)} of all main categories have changes +/- 20%}",
  share_is_extreme =
    cantons_extreme_changes %>% filter(is_main, is_extreme) %>% nrow() /
    cantons_extreme_changes %>% filter(is_main) %>% nrow()
)
## 50.4% of all main categories have changes +/- 20%}
cantons_extreme_changes %>%
  plot_extreme_changes()
## $title
## Diese Kategorien haben Veränderungen +/- 20.0%
## 
## attr(,"class")
## [1] "labels"

Bei den Kantonen schwanken etwas weniger Ausgabenbereiche als bei den Gemeinden. Das ist soweit nachvollziehbar, da es sich um grössere Körperschaften handelt. Aber auch hier gibt es immer noch genug Schwankungen, was für das Arbeiten mit dem Durchschnitt spricht.

Kantone + Gemeinden

# save data mangling as a function to minimize repition
read_cantons_and_municipalities <- function(sheet_name) {
  canton_abbrs$abbr %>%
    map_df(function(abbr) {
      readxl::read_excel(
        glue(
          "input/zip_d_{max(years_of_interest)}/fs_ktn_gdn/ktn_gdn_{tolower(abbr)}.xlsx"
        ),
        sheet = sheet_name,
        skip = 6
      ) %>%
        mutate(name = abbr) %>%
        rename(id = X__1, category = `1 000 CHF`) %>%
        gather(year, value, -one_of("id", "category", "name")) %>%
        # convert to correct data type
        mutate(year = as.numeric(year))
    })
}

# read income and expenditures and calculate net costs
cantons_and_municipalities_yearly <-
  calculate_costs(read_cantons_and_municipalities)

# filter to relevant years
cantons_and_municipalities_yearly %<>% filter(year %in% years_of_interest)
# as the changes at the federal level are smaller than at the other levels
# we set the threshold for extreme changes to 15%
cantons_and_municipalities_extreme_changes <-
  cantons_and_municipalities_yearly %>%
    evaluate_extreme_changes(delta = 0.15)

glue(
  "{percent(share_is_extreme)} of all main categories have changes +/- 15%}",
  share_is_extreme =
    cantons_and_municipalities_extreme_changes %>%
      filter(is_main, is_extreme) %>%
      nrow() /
    cantons_and_municipalities_extreme_changes %>%
      filter(is_main) %>%
      nrow()
)
## 51.2% of all main categories have changes +/- 15%}

Bund

read_federation <- function(sheet_name) {
  readxl::read_excel(
    glue("input/zip_d_{max(years_of_interest)}/fs_bund/bund.xlsx"),
    sheet = sheet_name,
    skip = 6
  ) %>%
    # add name for consistency with cantonal and municipal data
    mutate(name = "CH") %>%
    rename(id = X__1, category = `1 000 CHF`) %>%
    gather(year, value, -one_of("id", "category", "name")) %>%
    # convert to correct data type
    mutate(year = as.numeric(year))
}

# read income and expenditures and calculate net costs
federation_yearly <- calculate_costs(read_federation)

# filter to relevant years
federation_yearly %<>% filter(year %in% years_of_interest)

Beim Bund ist es wichtig, dass wir die zweckgebundenen Fiskaleinnahmen berücksichtigen. Der Bund schreibt in der Staatsrechnung auf Seite 150:

In folgenden Fällen besteht eine Zweckbindung der Fiskalerträge. Die nicht verwendeten Erträge sind in den zweckgebundenen Mitteln bilanziert (vgl. Ziffer 82/34 «zweckgebundene Mittel im Fremd- und Eigenkapital»): - Mehrwertsteuer: Eine Zweckbindung besteht für die AHV, für die Krankenversicherung, für die IV sowie für die Finanzierung der Bahninfrastruktur (vgl. Ziffer 81/8, 81/10 sowie 81/12). - Mineralölsteuer: Die Hälfte der Grundsteuer und der gesamte Zuschlag sind für den Strassenverkehr zweckgebunden (Spezialfinanzierung Strassenverkehr). Der Zuschlag auf Flugtreibstoffen ist zweckgebunden für die Spezialfinanzierung Luftverkehr. - Verkehrsabgaben: Die Nationalstrassenabgabe wird zweckgebunden für den Strassenverkehr eingesetzt (Spezialfinanzierung Strassenverkehr). - Spielbankenabgabe: Die Spielbankenabgabe wird zweckgebunden für die AHV eingesetzt (Spezialfinanzierung Spielbankenabgabe). - Lenkungsabgaben: Die Lenkungsabgaben umfassen die CO2-Emissionen, die Abgabe auf flüchtigen organischen Verbindungen und auf schwefelhaltigem Heizöl (VOC/HEL) sowie die Altlastenabgabe auf der Ablagerung von Abfällen. Die Lenkungsabgaben werden an die Bevölkerung zurückerstattet oder zweckgebunden eingesetzt. Für jede Lenkungsabgabe wird eine Spezialfinanzierung geführt.

Zur Mehrwertsteuer wird in Unterlagen der ESTV genauer definiert :

Gemäss den gegenwärtig geltenden Verfassungsbestimmungen (Art. 130 BV, Art. 196 Ziff. 3 Abs. 2 Bst. e und Ziff. 14 Abs. 2 und 3 Übergangsbestimmung BV) sind knapp 23% des MWST-Ertrags zweckgebunden für die AHV, für die IV, für grosse Eisenbahnprojekte und für die Prämienverbilligung in der Krankenversicherung und iessen daher nicht in die allgemeine Bundeskasse: - Der Ertrag aus einem Steuerprozentpunkt (1% des Normalsatzes, 0,5% des Sondersatzes für Beherbergungsleistungen und 0,3% des reduzierten Satzes) ist für die AHV bestimmt. - Der Ertrag aus 0,4 Steuerprozentpunkten (0,4% des Normalsatzes, 0,2% des Sondersatzes für Beherbergungsleistungen und 0,1% des reduzierten Steuersatzes) wird zur befristeten Finanzierung der IV (1. Januar 2011 bis 31. Dezember 2017) verwendet. - Der Ertrag aus 0,1 Prozentpunkten wird zur Finanzierung von grossen Eisenbahnprojekten eingesetzt. - Vom verbleibenden Betrag werden 5% für die Prämienverbilligung in der Krankenversicherung zugunsten unterer Einkommensschichten verwendet.

# as the changes at the federal level are smaller than at the other levels
# we set the threshold for extreme changes to 15%
federation_extreme_changes <- federation_yearly %>%
  evaluate_extreme_changes(delta = 0.15)

glue(
  "{percent(share_is_extreme)} of all main categories have changes +/- 15%}",
  share_is_extreme =
    federation_extreme_changes %>% filter(is_main, is_extreme) %>% nrow() /
    federation_extreme_changes %>% filter(is_main) %>% nrow()
)
## 30.0% of all main categories have changes +/- 15%}

Gemeinden pro Kanton

# read the sum of all the municipalities to later calculate values for
# all municipalities that are no cities
read_municipalities_by_canton <- function(sheet_name) {
  canton_abbrs$abbr %>%
    map_df(function(abbr) {
      readxl::read_excel(
        glue(
          "input/zip_d_{max(years_of_interest)}/fs_gdn/gdn_{tolower(abbr)}.xlsx"
        ),
        sheet = sheet_name,
        skip = 6
      ) %>%
        mutate(name = abbr) %>%
        rename(id = X__1, category = `1 000 CHF`) %>%
        gather(year, value, -one_of("id", "category", "name")) %>%
        # convert to correct data type
        mutate(year = as.numeric(year))
    })
}

# read income and expenditures and calculate net costs
municipalities_by_canton_yearly <-
  calculate_costs(read_municipalities_by_canton)

# filter to relevant years
municipalities_by_canton_yearly %<>% filter(year %in% years_of_interest)
municipalities_by_canton_extreme_changes <- municipalities_by_canton_yearly %>%
  evaluate_extreme_changes()

glue(
  "{percent(share_is_extreme)} of all main categories have changes +/- 20%}",
  share_is_extreme =
    municipalities_by_canton_extreme_changes %>%
      filter(is_main, is_extreme) %>%
      nrow() /
    municipalities_by_canton_extreme_changes %>% filter(is_main) %>% nrow()
)
## 57.7% of all main categories have changes +/- 20%}
municipalities_by_canton_extreme_changes %>%
  plot_extreme_changes()
## $title
## Diese Kategorien haben Veränderungen +/- 20.0%
## 
## attr(,"class")
## [1] "labels"

# clean up
rm(
  evaluate_extreme_changes,
  plot_extreme_changes,
  cities_extreme_changes,
  cantons_extreme_changes,
  cantons_and_municipalities_extreme_changes,
  federation_extreme_changes,
  municipalities_by_canton_extreme_changes
)

Am wenisten Schwanunken weisen also die Zahlen des Bundes aus. Alle anderen Ebenen (Kantone, Städte, Gemeinden) schwanken doch recht stark. Es könnte in der Folge interessant sein, die Kategorien zu identifizieren, die stark gewachsen sind. Dafür hatten wir noch keine Zeit.

Gesamtstaat (konsolidiert)

Auch die folgende Grafik hat es letzten Endes nicht in die Berichterstattung geschafft. Sie sollte dazu dienen, die Entwicklung pro Bereich seit 1990 aufzeigen - anhand Zahlen des Gesamtsaates. Die Entwicklungen waren aber zu wenig überraschend, um einen Artikel darüber zu schreiben.

# read consolidated values of the state (municipal + cantonal + federal)
read_state <- function(sheet_name) {
  readxl::read_excel(
    glue("input/zip_d_{max(years_of_interest)}/fs_staat/staat.xlsx"),
    sheet = sheet_name,
    skip = 6
  ) %>%
    # add name for consistency with cantonal and municipal data
    mutate(name = "State") %>%
    rename(id = X__1, category = `1 000 CHF`) %>%
    gather(year, value, -one_of("id", "category", "name")) %>%
    # convert to correct data type
    mutate(year = as.numeric(year))
}

# read income and expenditures and calculate net costs
state_yearly <- calculate_costs(read_state) %>%
  # add share per year and category
  group_by(year, is_main) %>%
  mutate(share = value / sum(value, na.rm = TRUE)) %>%
  ungroup()

# normalize values to francs per inhabitant (value is in thousands)
state_yearly %<>%
  left_join(inhabitants_yearly, by = "year") %>%
  mutate(chf_per_inhabitant = value / inhabitants * 1000)

# read in data about the bip for a better contextualisation
bip_per_inhabitant <- readxl::read_excel(
  "input/je-d-04.02.01.08.xlsx",
  sheet = "BIP pro Einwohner, lange Serie",
  range = "A48:B74",
  col_names = FALSE
) %>%
  rename(
    year = `X__1`,
    value = `X__2`
  ) %>%
  # we start at year 1990 (same as for tax data)
  # and add a column change for the relative change to the first year
  # plus we divide the value with 1000 so we have the same units (TCHF)
  mutate(
    category = "BIP pro Einwohner",
    value = value / 1000,
    change = value / first(value, order_by = year)
  )

Vergleich mit BIP

# plot change in main categories since 1990 regarding chf per inhabitant
# hide smallest two categories
ggplot(
  state_yearly %>%
    filter(is_main) %>%
    group_by(category) %>%
    mutate(
      change = chf_per_inhabitant /
        first(chf_per_inhabitant, order_by = year)
    ) %>%
    filter(!str_detect(category, "Umweltschutz|Volkswirtschaft|Steuern")),
  aes(
    x = year,
    y = change,
    color = shorten(category),
    label = ifelse(
      year == max(year),
      shorten(category),
      ""
    )
  )
) +
  geom_line() +
  geom_text_repel(
    segment.size = 0.25,
    segment.color = "black",
    nudge_x = 0.5,
    hjust = 0,
    direction = "y"
  ) +
  geom_line(
    data = bip_per_inhabitant,
    color = "black",
    size = 1
  ) +
  geom_text(
    data = bip_per_inhabitant %>% filter(year == max(year)),
    aes(label = category, hjust = 0),
    color = "black",
    nudge_x = 0.5
  ) +
  scale_color_manual(values = categorical) +
  scale_y_continuous(labels = percent) +
  xlim(c(NA, max(state_yearly$year) + 8)) +
  theme_minimal() +
  theme(legend.position = "none") +
  labs(
    title = "Gesamtstaat: Veränderung pro Kategorie in Prozent",
    subtitle = "100% = Franken pro Einwohner im Jahr 1990",
    x = NULL,
    y = NULL
  )

Durchschnitswerte berechnen

Wir berechnen den Durchschnitt pro Kategorie und Gemeinde indem wir zuerst den Prozentwert einer Kategorie gemessen an den gesamten Nettoausgaben berechnen. Von diesem Prozentwert (share) bilden wir anschliessend den Durchschnitt über alle verfügbaren Jahre.

Dabei kappen wir negative Werte (das bedeutet ein positives Saldo bei einem Punkt, sprich mehr Einnahmen als Ausgaben) auf null. Wir sind nur an den Ausgaben interessiert, da die Einnahmen gleich wie die Steuereinnahmen nicht-zweckgebunden ins Staatsbudget miteinfliessen.

# function that calculates percentage shares from raw values. You must pass
# dataframes with columns: name, year, value
calculate_shares_from_numbers <- function(df) {
  df %>%
    # calculate yearly shares first, so group by year
    group_by(name, year, is_main) %>%
    # change from absolute numbers to percentage values
    mutate(share = value / sum(value, na.rm = TRUE)) %>%
    select(-value)
}

# works witih columns name, id, category, share
calculate_means_over_years <- function(df) {
  df %>%
    # now calculate average of yearly shares
    group_by(name, id, category, is_main) %>%
    summarise(share = mean(share, na.rm = TRUE)) %>%
    ungroup()
}

# calculate shares on all data frames
cities <- cities_yearly %>%
  calculate_shares_from_numbers() %>%
  calculate_means_over_years() %>%
  # we lost the bfs_id, join it in again
  left_join(
    cities_yearly %>%
      filter(year == max(years_of_interest)) %>%
      distinct(bfs_id, name),
    by = "name"
  )

cantons <- cantons_yearly %>%
  calculate_shares_from_numbers() %>%
  calculate_means_over_years()

cantons_and_municipalities <- cantons_and_municipalities_yearly %>%
  calculate_shares_from_numbers() %>%
  calculate_means_over_years()

federation <- federation_yearly %>%
  calculate_shares_from_numbers() %>%
  calculate_means_over_years()

municipalities_by_canton <- municipalities_by_canton_yearly %>%
  calculate_shares_from_numbers() %>%
  calculate_means_over_years()


print(paste("The following table should be empty. If it's not, that means,",
            "that the sum of all shares does not equal to one:"))
## [1] "The following table should be empty. If it's not, that means, that the sum of all shares does not equal to one:"
knitr::kable(
  bind_rows(
    cities %>% mutate(source = "cities"),
    cantons %>% mutate(source = "cantons"),
    federation %>% mutate(source = "federation"),
    municipalities_by_canton %>% mutate(source = "municipalities_by_canton")
  ) %>%
    group_by(source, name, is_main = nchar(id) == 1) %>%
    # sum all subcategories and main categories
    summarise(total = sum(share)) %>%
    # display those that do not add up to 100%
    # ignore rounding errors
    filter(total > 1.00001 | total < 0.99999)
)

source name is_main total ——- —– ——– ——

Steuerfüsse

tax_rates <- read_csv("input/steuerfuesse.csv") %>%
  # rename and drop church taxes
  select(
    canton = `Kanton`,
    municipality = `Gemeinde`,
    cantonal_factor = `Kanton_1`,
    municipal_factor = `Gemeinde_1`,
    kath_factor = `kath. Kirchgemeinde`,
    year = Jahr
  ) %>%
  # reduce tax rates to year of data of municipalities
  filter(year == latest_municipality_data_available) %>%
  # remove duplicates
  distinct(municipality, .keep_all = TRUE) %>%
  # throw away municipalities that do not exist anymore by inner joining bfs_id
  inner_join(
    inhabitants_municipalities %>%
      select(municipality, bfs_id),
    by = "municipality"
  )
## Warning: Duplicated column names deduplicated: 'Kanton' => 'Kanton_1' [4],
## 'Gemeinde' => 'Gemeinde_1' [5]
## Parsed with column specification:
## cols(
##   Kanton = col_character(),
##   Jahr = col_integer(),
##   Gemeinde = col_character(),
##   Kanton_1 = col_double(),
##   Gemeinde_1 = col_double(),
##   `ref. Kirchgemeinde` = col_double(),
##   `kath. Kirchgemeinde` = col_double()
## )

Sonderfall Wallis

In diesem Abschnitt lesen wir die Daten zum Kanton Wallis ein, sodass wir weiter unten auch für die Wallisser gemeinden den Gemeinde- bzw. Kantonsanteil korrekt berechnen können.

Die Berechnungen in dieser Datei wurden punktuell mit jenen des Walliser Online-Steuerrechners kontrolliert.

# read in every text file for every index (from 100% to 170%)
calculated_taxes_wallis <- seq(100, 170, 5) %>%
  map_df(function(index) {
    read_delim(
      paste0("input/tarifs/VS_bareme_Hilfstabellen/IC-BAREME-", index, ".txt"),
      delim = ";",
      col_names = FALSE,
      col_types = cols() # suppress col type guesses
    ) %>%
      # give meaningful names
      select(
        bareme = X1,
        income = X2,
        percentage = X5
      ) %>%
      # convert to numbers and divide by divisor where needed
      mutate(
        income = as.numeric(income),
        percentage = as.numeric(percentage) / 1000000
      )
  }) %>%
  # reduce to steps of 2000 CHF to reduce file size
  filter(income %% 2000 == 0)

# incomes above 300'000 are not listed, they are also taxed with 10%
# we'll handle that in the frontend

# read in cantonal data and convert to json
cantonal_taxes_wallis <- read_delim(
  "input/tarifs/VS_bareme_Hilfstabellen/canton.csv",
  delim = ";"
) %>%
  # drop tax, we'll calculate that ourselves in the frontend
  select(-tax) %>%
  mutate(percentage = percentage / 100) %>%
  # reduce to steps of 2000 CHF to reduce file size
  filter(income %% 2000 == 0)
## Parsed with column specification:
## cols(
##   income = col_integer(),
##   percentage = col_double(),
##   tax = col_double()
## )
# prepare unified municipality names for later matching
matching_names_wallis <- c(
  "^Brig/Glis$" = "Brig-Glis",
  "^Bourg-St-Pierre$" = "Bourg-Saint-Pierre",
  "^Münster-Geschinen$" = "Münster-Geschinen", # now part of Goms
  "^Agettes$" = "Les Agettes",
  "^St-Martin$" = "Saint-Martin (VS)",
  "^Turtmann - Unterems$" = "Turtmann-Unterems",
  "^St-Gingolph$" = "Saint-Gingolph",
  "^Val d'Illiez$" = "Val-d'Illiez",
  "^Wiler$" = "Wiler (Lötschen)",
  "^St-Maurice$" = "Saint-Maurice",
  "^Mollens$" = "Mollens (VS)",
  "^Randogne$" = "Randogne", # now part of Crans-Montana, added to csv manually
  "^St-Léonard$" = "Saint-Léonard",
  "^Stalden$" = "Stalden (VS)"
)

tax_rates_wallis <- read_csv(
  "input/tarifs/VS_Coefficients_Indexations_Communes_2012-2017.csv"
) %>%
  gather(key, value, -one_of("id", "municipality")) %>%
  mutate(
    # separate year and category (index and factor)
    year = as.numeric(str_sub(key, 0, 4)),
    key = str_sub(key, 6),
    # remove percentage sign of indeces (baremes) and convert factors to numeric
    value = ifelse(
      key == "index",
      str_sub(value, 0, 3),
      as.numeric(value)
    ),
    # remove additional info between brackets for easier matching
    municipality = str_replace_all(municipality, " \\(.*\\)", ""),
    # replace names with those from bfs
    municipality = str_replace_all(municipality, matching_names_wallis)
  ) %>%
  # keep only year of interest
  filter(year == max(years_of_interest)) %>%
  # spread to one row per year and municipality
  spread(key, value) %>%
  # filter out NAs as these municipalities do no longer exist
  filter(!is.na(factor)) %>%
  left_join(
    inhabitants_municipalities %>%
      select(bfs_id, municipality),
    by = "municipality"
  ) %>%
  # convert to correct data type
  mutate(
    index = as.numeric(index),
    factor = as.numeric(factor)
  ) %>%
  # throw out municipalities that do not exist anymore
  filter(!is.na(bfs_id))
## Parsed with column specification:
## cols(
##   id = col_integer(),
##   municipality = col_character(),
##   `2012_factor` = col_double(),
##   `2012_index` = col_character(),
##   `2013_factor` = col_double(),
##   `2013_index` = col_character(),
##   `2014_factor` = col_double(),
##   `2014_index` = col_character(),
##   `2015_factor` = col_double(),
##   `2015_index` = col_character(),
##   `2016_factor` = col_double(),
##   `2016_index` = col_character(),
##   `2017_factor` = col_double(),
##   `2017_index` = col_character()
## )
## Warning in ifelse(key == "index", str_sub(value, 0, 3), as.numeric(value)):
## NAs durch Umwandlung erzeugt

Steuerbelastung pro Person

# define spreadsheet names and corresponding description
personas <- c(
  "Ledig" = "Lediger, unselbständig Erwerbender mit eigenem Haushalt",
  "VOK"   = "Verheirateter Alleinverdiener ohne Kinder",
  "VMK"   = "Verheirateter Alleinverdiener mit 2 Kindern",
  "DOPMK" = "Verheirateter Doppelverdiener mit 2 Kindern",
  "REN"   = "Verheirateter Rentner, beide Ehegatten über 65 Jahre alt"
)

# write reading function to decrease repetition and
# read personas in different sheets
read_persona <- function(sheet_name) {
  readxl::read_excel(
    glue("input/SB-NP-alle-Gden_{latest_municipality_data_available}.xlsx"),
    sheet = sheet_name,
    skip = 4
  ) %>%
    # throw away first line (containig only labels)
    filter(`X__1` != "Kanton / canton") %>%
    # rename first three columns
    rename(
      canton = `X__1`,
      bfs_id = `X__2`,
      municipality = `X__3`
    ) %>%
    # make narrow tidy
    gather(income, percentage, -one_of("canton", "bfs_id", "municipality")) %>%
    # add column to indicate what persona it is about
    # convert columns to correct data type
    mutate(
      persona = sheet_name,
      bfs_id = as.numeric(bfs_id),
      income = as.numeric(income)
    )
}

# iterate over personas defined at top of chunk and apply function to each entry
tax_weights <- names(personas) %>%
  map_df(read_persona) %>%
  # divide by 100 because they are provided in percentage numbers
  mutate(percentage = percentage / 100)

# save direct federal tax into its own data frame
dbst_tax_weights <- tax_weights %>%
  filter(canton == "DBSt")

# filter out federal tax (export that one below) and
# throw away municipalities that do not exist anymore
tax_weights %<>%
  filter(bfs_id %in% inhabitants_municipalities$bfs_id)

# clean up
rm(read_persona)

Export

Hier werden die CSV Dateien geschrieben, welche im Kapitel Datenbeschreibung genauer spezifiziert werden.

# TO DO
write_csv(cities, "output/cities.csv")
write_csv(cantons, "output/cantons.csv")
write_csv(cantons_and_municipalities, "output/cantons_and_municipalities.csv")
write_csv(federation, "output/federation.csv")
write_csv(municipalities_by_canton, "output/municipalities_by_canton.csv")

Analyse

10 grösste Gemeinden

Kategorien in CHF

# calculate share of municipal, cantonal and federal tax for example person
example <- tibble(
  "persona" = "Ledig",
  "income" = "100000"
)

# apply special calculation for the municipalities in the canton Wallis
# there, every municipality as it's own progression. That's why we make a
# simplified deduction to the gross income and find out what factors to apply
# to the net income
simplified_deductions <- function (brutto, persona) {
  if (persona == "Ledig") {
    return(brutto * 0.8875 - 4600)
  } else if (persona == "VOK") {
    return(brutto * 0.8875 - 4800)
  } else if (persona == "VMK") {
    return(brutto * 0.8875 - 21075)
  } else if (persona == "DOPMK") {
    return(brutto * 0.814 - 25300)
  } else if (persona == "REN") {
    return(brutto - 6200)
  }
  browser()
  return(NA)
}

replace_rates_for_vs <- function(bfs_id, income, persona, level) {
  # calculate simplified net income, to evaluate what index and factor to apply
  net_income <- simplified_deductions(income, persona)
  # round to 2000 the vsTax contains the same rounded incomes
  net_income <- round(net_income / 2000, digits = 0) * 2000
  # get entry for municipality given. dplyrs filter would return whole
  # data frame because bfs_id == bfs_id is always true
  tax_rate <- tax_rates_wallis[tax_rates_wallis$bfs_id == bfs_id, ]
  # sanity check
  if (tax_rate %>% nrow() != 1) {
    browser()
    return(NA)
  }
  # return for the given level the percentage value that people have to pay
  if (level == "municipal") {
    result <- calculated_taxes_wallis %>%
      # get the percentage value for the correct "barème" (defined per munic.)
      filter(bareme == tax_rate$index, income == net_income) %>%
      select(percentage) %>%
      as.numeric()
    # return value or 10% if is above that (that's the maximum)
    return(min(result, 0.1) * tax_rate$factor)
  } else if (level == "cantonal") {
    result <- cantonal_taxes_wallis %>%
      filter(income == net_income) %>%
      select(percentage) %>%
      as.numeric()
    # return value or 14% if is above that (that's the maximum)
    return(min(result, 0.14))
  }
}

# remove kath tax and convert municipal and cantonal rates to percentage values
convert_rates_to_shares <- function(municipal, cantonal, kath, canton, level) {
  if (canton == "UR") {
    # if we divide the catholic tax weight with 7.1 we end up with values
    # that match the electronic tax calculator by the ESTV
    kath_tmp <- kath / 7.1
    municipal_tmp <- municipal / (municipal + cantonal + kath_tmp)
    cantonal_tmp <- cantonal / (municipal + cantonal + kath_tmp)
  } else if (canton %in% c("BL", "BS", "JU")) {
    # here we add the kath multiplied with the cantonal tax weight
    municipal_tmp <- municipal / (municipal + cantonal + cantonal * kath / 100)
    cantonal_tmp <- cantonal / (municipal + cantonal + cantonal * kath / 100)
  } else {
    # standard procedure for other cantons
    municipal_tmp <- municipal / (municipal + cantonal + kath)
    cantonal_tmp <- cantonal / (municipal + cantonal + kath)
  }
  # return for the given level the percentage value that people have to pay
  if (level == "municipal") {
    return(municipal_tmp)
  } else if (level == "cantonal") {
    return(cantonal_tmp)
  } else if(level == "kath") {
    return(kath_tmp)
  }
}

# calculate how much taxes the person in the example has to pay (m, c, f)
tax_francs_example <- tax_weights %>%
  filter(
    persona == example$persona,
    income == example$income
  ) %>%
  left_join(
    dbst_tax_weights %>%
      select(persona, income, federal = percentage),
    by = c("persona", "income")
  ) %>%
  left_join(
    tax_rates %>%
      select(
        municipality,
        municipal = municipal_factor,
        cantonal = cantonal_factor,
        kath = kath_factor
      ),
    by = "municipality"
  ) %>%
  rowwise() %>%
  mutate(municipal = ifelse(
    canton != "VS",
    municipal,
    replace_rates_for_vs(bfs_id, income, persona, "municipal")
  ),
  cantonal = ifelse(
    canton != "VS",
    cantonal,
    replace_rates_for_vs(bfs_id, income, persona, "cantonal")
  )) %>%
  # convert the tax rates (Steuerfüsse) to decimals (as parts of a whole)
  mutate(
    # introduce temporary variable because otherwise the cantonal number
    # could not be computed correctly (divided by invalid sum)
    municipal_tmp = convert_rates_to_shares(municipal, cantonal, kath, canton, "municipal"),
    cantonal = convert_rates_to_shares(municipal, cantonal, kath, canton, "cantonal"),
    municipal = municipal_tmp
  ) %>%
  ungroup() %>%
  select(-municipal_tmp, -kath) %>%
  # convert municipal and cantonal to percent of net income
  mutate(
    municipal = municipal * percentage,
    cantonal = cantonal * percentage
  ) %>%
  # convert all to Swiss Francs
  mutate(
    municipal = municipal * income,
    cantonal = cantonal * income,
    federal = federal * income
  ) %>%
  # move 17% of the federal tax to the cantonal tax
  # (read more about why in chapter data sources)
  mutate(
    cantonal = cantonal + federal * 0.17,
    federal = federal * 0.83
  )

# calculate francs of expenditures per city for example person
francs_per_cat_example <- cities %>%
  # we join more values down below, so make clear what value it is
  rename(municipal_share = share) %>%
  # join total number of tax francs for example persona
  left_join(
    tax_francs_example %>%
      select(name = municipality, canton, municipal, cantonal, federal),
    by = "name"
  ) %>%
  # now join total cantonal expenditures per category
  left_join(
    cantons %>%
      select(id, canton = name, cantonal_share = share),
    by = c("id", "canton")
  ) %>%
  # now join total federal expenditures per category
  left_join(
    federation %>%
      select(id, federal_share = share),
    by = "id"
  ) %>%
  # multiply percentage share with total tax francs
  mutate(
    municipal = municipal * municipal_share,
    cantonal = cantonal * cantonal_share,
    federal = federal * federal_share
  ) %>%
  # remove percentage share and totals
  select(
    -municipal_share,
    -cantonal_share,
    -federal_share
  ) %>%
  ungroup() %>%
  mutate(
    sum = municipal + cantonal + federal
  )

# calculate francs per cat and level for the example ledig, 100k income
francs_per_cat_and_level <- francs_per_cat_example %>%
  filter(is_main) %>%
  select(name, id, category, municipal:federal, sum) %>%
  gather(level, amount, municipal:sum) %>%
  mutate(level = factor(level, levels = names(green_palette))) %>%
  # set negative values to zero
  mutate(amount = ifelse(amount < 0, 0, amount))

# extract out data for 10 biggest cities
big_10_cities <- cities %>%
  filter(name %in% names(big_10_names))

big_10_tax_rates <- tax_rates %>%
  filter(municipality %in% names(big_10_names))

big_10_tax_weights <- tax_weights %>%
  filter(bfs_id %in% big_10_names)

big_10_francs_per_cat_example <- francs_per_cat_example %>%
  filter(name %in% names(big_10_names))

big_10_francs_per_cat_and_level <- francs_per_cat_and_level %>%
  filter(name %in% names(big_10_names))
Hauptkategorien

Szenario: Ledig, Netto-Einkommen: CHF 100000

ggplot(
  big_10_francs_per_cat_and_level %>%
    filter(level != "sum"),
  aes(
    x = name,
    y = amount,
    fill = level
  )
) +
  facet_wrap(~ category, ncol = 2) +
  geom_bar(stat = "identity") +
  scale_fill_manual(values = green_palette) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  labs(
    title = glue("Ausgaben pro Stadt für Status {example$persona} und \\
                 Einkommen {example$income}"),
    x = "Franken pro Jahr pro funktionaler Einnahme/Ausgabe",
    y = NULL,
    fill = NULL
  )

ggplot(
  big_10_francs_per_cat_and_level %>%
    arrange(name, amount) %>%
    filter(level != "sum") %>%
    mutate(category = shorten(category)),
  aes(
    x = category,
    y = amount,
    fill = level
  )
) +
  facet_wrap(~ name, ncol = 2, scales = "free_y") +
  geom_bar(stat = "identity") +
  coord_flip() +
  theme_minimal() +
  scale_fill_manual(values = green_palette) +
  labs(
    title = glue("Ausgaben pro Stadt für Status {example$persona} und \\
                 Einkommen {example$income}"),
    x = "Franken pro Jahr pro funktionaler Einnahme/Ausgabe",
    y = NULL,
    fill = NULL
  )

Kategorien prozentual

ggplot(
  big_10_francs_per_cat_and_level %>%
    filter(level == "sum"),
  aes(
    x = name,
    y = amount,
    fill = category
  )
) +
  geom_bar(stat = "identity") +
  coord_flip() +
  facet_wrap(~ name, ncol = 1, scales = "free") +
  theme_minimal() +
  scale_fill_brewer(palette = "Set1") +
  theme(strip.text.x = element_blank()) +
  labs(
    title = "Anteil der Hauptkategorien an den Gesamtausgaben pro Stadt",
    subtitle =
      "Die Einheit ist CHF / Jahr, aber die Balken bilden die Verhältnisse ab",
    x = NULL,
    y = NULL,
    fill = NULL
  )
## Warning in RColorBrewer::brewer.pal(n, pal): n too large, allowed maximum for palette Set1 is 9
## Returning the palette you asked for with that many colors

Abweichungen vom Schnitt

Die Abweichungen nehmen wir anhand der Klassifizierung in Gross-, Mittel- und Kleinstädte des BFS vor. Das heisst, wir schauen die folgenden Städte genauer an:

# read in classification into small, medium and big cities
sm_md_lg_cities <- read_csv("input/klein_mittel_grossstadt.csv") %>%
  # sort by size (by converting to factor with levels sorted manually)
  mutate(
    groesse = factor(
      groesse,
      levels = c("gross", "mittel", "klein")
    )
  )
## Parsed with column specification:
## cols(
##   bfs_id = col_integer(),
##   name = col_character(),
##   kanton = col_character(),
##   groesse = col_character(),
##   sprache = col_character()
## )
knitr::kable(
  sm_md_lg_cities %>%
    select(name, groesse)
)
name groesse
Zürich gross
Bern gross
Basel gross
Lausanne gross
Genève gross
Winterthur mittel
Biel/Bienne mittel
Thun mittel
Luzern mittel
Zug mittel
Fribourg mittel
Olten mittel
Solothurn mittel
Schaffhausen mittel
St. Gallen mittel
Wil (SG) mittel
Chur mittel
Aarau mittel
Baden mittel
Bellinzona mittel
Locarno mittel
Lugano mittel
Montreux mittel
Vevey mittel
Sion mittel
La Chaux-de-Fonds mittel
Neuchâtel mittel
Wetzikon (ZH) klein
Herisau klein
Rorschach klein
Altstätten klein
Brugg klein
Zofingen klein
Arbon klein
Chiasso klein
Mendrisio klein
Le Locle klein
Lyss klein
Langenthal klein
Burgdorf klein
Sursee klein
Altdorf (UR) klein
Einsiedeln klein
Lachen klein
Schwyz klein
Sarnen klein
Stans klein
Glarus klein
Bulle klein
Grenchen klein
Buchs (SG) klein
Rapperswil-Jona klein
Wattwil klein
Wohlen (AG) klein
Reinach (AG) klein
Lenzburg klein
Romanshorn klein
Frauenfeld klein
Kreuzlingen klein
Aigle klein
Payerne klein
Yverdon-les-Bains klein
Brig-Glis klein
Martigny klein
Monthey klein
Sierre klein
Visp klein
Delémont klein
Moutier klein
Langnau im Emmental klein
Val-de-Travers klein

Wir berechnen einen Durchschnittswert pro Agglo-Typ (gross/mittel/klein) und Ausgabenkategorie.

Leider sind die folgenden Kleinstädte nicht in den Daten vorhanden, wir müssen sie aus dieser Analyse ausschliessen:

# output
knitr::kable(
  sm_md_lg_cities %>%
    anti_join(
      cities_yearly %>%
        distinct(bfs_id),
      by = "bfs_id"
    )
)
bfs_id name kanton groesse sprache
1344 Lachen SZ klein D
3379 Wattwil SG klein D
4141 Reinach (AG) AG klein D
902 Langnau im Emmental BE klein D
# remove missings by inner joining
sm_md_lg_cities %<>%
  inner_join(
    cities_yearly %>%
      distinct(bfs_id),
    by = "bfs_id"
  )
# calculate percentage shares by city (main categories)
share_per_category <- francs_per_cat_and_level %>%
  # join classification into small / medium / big
  left_join(
    sm_md_lg_cities %>%
      select(name, type = groesse),
    by = "name"
  ) %>%
  filter(level == "sum" & !is.na(type)) %>%
  group_by(name) %>%
  mutate(share = amount / sum(amount)) %>%
  ungroup()

# calculate averages per main category
average_share_per_category_and_type <- share_per_category %>%
  group_by(id, category, type) %>%
  summarise(share = mean(share, na.rm = TRUE))

# compare
ggplot(
  average_share_per_category_and_type,
  aes(
    x = shorten(category),
    y = share,
    fill = type
  )
) +
  geom_bar(stat = "identity", position = position_dodge()) +
  scale_y_continuous(labels = percent) +
  scale_fill_brewer(palette = "Set1") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  labs(
    title = "Unterschiedliche Durchschnittswerte (Gemeinde + Kanton + Bund)",
    subtitle = glue(
      "Die {count} Gross-, Mittel- und Kleinstädte unterscheiden \\
      sich bei den Ausgaben pro Kategorie",
      count = sm_md_lg_cities %>% nrow()),
    y = "Anteil an den Ausgaben",
    x = NULL,
    fill = "Grösse"
  )

category_levels <- share_per_category %>%
  distinct(category) %>%
  unlist()

# calculate deltas per city by comparing it to other cities of a similar size
share_per_cat_delta <- share_per_category %>%
  # calculate difference from average (delta)
  left_join(
    average_share_per_category_and_type %>%
      rename(average = share),
    by = c("id", "category", "type")
  ) %>%
  # calculate percent difference with average = 100%
  mutate(delta = (share - average) / average) %>%
  # the categories Volkswirtschaft and Umweltschutz and Steuern contain huge
  # differences but small total values, hide to clearer analyse of the others
  filter(!str_detect(category, "Volkswirtschaft|Umweltschutz|Steuern")) %>%
  # bring categories and cities in right order for plot
  mutate(
    category = factor(category, levels = category_levels)
  ) %>%
  # remove level = sum (only sums left at this point)
  select(-level)

# export
write_csv(share_per_cat_delta, "output/deltas.csv")

ggplot(
  data = share_per_cat_delta %>%
    filter(type %in% c("gross", "mittel")),
  aes(
    x = category,
    y = delta,
    label = percent(delta, accuracy = 1, prefix = ifelse(delta > 0, "+", "")),
    fill = type
  )
) +
  geom_bar(stat = "identity") +
  geom_text() +
  scale_fill_manual(values = c("#1cb373", "#1cb0b5")) +
  facet_wrap(~ name, ncol = 4) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  theme(
    panel.grid.major = element_blank(),
    axis.text.y = element_blank(),
    legend.position = "none"
  ) +
  labs(
    title = "Abweichungen vom Durchschnitt der vergleichbaren Städte",
    subtitle =
      "Dargestellt sind aus Platgründen nur grosse (grün) und mittlere (blau)",
    x = NULL,
    y = NULL,
    fill = "Grösse"
  )

Steuerbelastung pro Einkommensgruppe

# read in file provided by ESTV
dbst_2015_federation <- readxl::read_excel(
  "input/NP_2015_mitnull.xlsx",
  sheet = "Schweiz - Suisse"
)

# take total and make tidy
tax_payers_per_income <- dbst_2015_federation %>%
  filter(Einheit == "Total") %>%
  select(starts_with("stpf_")) %>%
  gather("lower", "n", starts_with("stpf_")) %>%
  mutate(lower = as.numeric(str_replace(lower, "stpf_u?", "")))


dbst_2015_fed_percentiles <- readxl::read_excel(
  "input/00_Prozentuale Verteilung_VP15_nach Zivcd_d.xlsx",
  sheet = "Einzelwerte in Prozentanteilen",
  range = "B7:P22"
)

# rename first three columns
names(dbst_2015_fed_percentiles)[1:3] <- c("lower", "bis", "upper")

# define bigger groups for easier consumption in plot
breaks <- c(0, 50, 95, 99, 100)

tax_load_per_income <- dbst_2015_fed_percentiles %>%
  select(lower, upper, starts_with("Alle")) %>%
  rename(
    reink = `Alle`,
    steink = `Alle__1`,
    tax_load = `Alle__2`
  ) %>%
  mutate(
    lower = round(lower),
    size = upper - lower,
    new_upper = cut(
      upper,
      breaks = breaks,
      include.lowest = TRUE
    )
  ) %>%
  group_by(new_upper) %>%
  summarise(
    size = sum(size),
    steink = sum(steink),
    tax_load = sum(tax_load)
  )

ggplot(
  data = tax_load_per_income %>%
    select(new_upper, size, tax_load) %>%
    gather("key", "value", size:tax_load),
  aes(
    x = key,
    y = value,
    fill = factor(new_upper)
  )
) +
  geom_bar(
    stat = "identity",
    width = 0.6
  ) +
  scale_fill_viridis_d(direction = -1) +
  theme_minimal() +
  theme(legend.position = "none") +
  labs(
    title = "Wer bezahlt die Direkte Bundessteuer?",
    subtitle = glue(
      "Links sieht man die Bevölkerung, rechts den Anteil der Steuer, die \\
diese bezahlt, man sieht:\n1 Prozent der Bevölkerung bezahlt ca. 40% \\
der Bundessteuer"
    ),
    x = NULL,
    y = "Anteil"
  )

Progression

# select a number of cantons with extreme value as to how progressive they tax incomes
extreme_municipalities <- c(
  6621, # Genève (canton with more progressive income tax)
  2701, # Basel (canton with more progressive income tax)
  5586, # Lausanne (canton with more progressive income tax)
  1711, # Zug (canton with less progressive income tax)
  1372, # Schwyz (canton with less progressive income tax)
  1201, # Altdorf (canton with flat rate tax)
  1407, # Sarnen (canton with flat rate tax)
  2939 # Schaffhausen (canton with flat rate tax)
)

ggplot(
  tax_weights %>%
    # show only one municipality per canton
    filter(bfs_id %in% principal_towns),
  aes(
    x = income,
    y = percentage,
    color = municipality,
    alpha = bfs_id %in% extreme_municipalities
  )
) +
  geom_line() +
  theme_minimal() +
  facet_wrap(~ persona) +
  scale_y_continuous(labels = percent) +
  scale_alpha_manual(values = c(0.3, 0.9), guide = FALSE) +
  labs(
    title = "Wie progressiv sind die Kantonshauptorte?",
    x = "Bruttoeinkommen",
    y = "Steuern in Prozent des Bruttoeinkommens",
    color = "Kantonshauptorte"
  )

Die folgende Grafik zeigt die Steuerprogression für die verschiedenen Modelle (Ledig, Verheiratet, etc.). Die Prozentsätze variieren von Modell zu Modell, die Reihenfolge der Kantone bleibt aber in etwa gleich, deshalb zeigen wir in der Berichterstattung nur ein Modell (ledig) und nur bis Einkommen 500’000, sodass man die unteren Einkommen etwas besser sieht:

ggplot(
  tax_weights %>%
    # show only one municipality per canton
    filter(bfs_id %in% principal_towns) %>%
    # only look at Ledige for the moment
    filter(persona == "Ledig"),
  aes(
    x = income,
    y = percentage,
    color = municipality
  )
) +
  geom_line() +
  theme_minimal() +
  facet_wrap(~ persona, scales = "free_x") +
  scale_y_continuous(labels = percent) +
  xlim(0, 500000) +
  labs(
    title = "Wie progressiv sind die Kantonshauptorte bei Einkommen bis 500k?",
    x = "Bruttoeinkommen",
    y = "Steuern in Prozent des Bruttoeinkommens",
    color = "Kantonshauptorte"
  )
## Warning: Removed 26 rows containing missing values (geom_path).

Wirkliche Progression (Schweizweit)

Um aufzuzeigen, dass über die ganze Schweiz gesehen, die Steuern gar nicht progressiv sondern degressiv sind, haben zeigen wir ausserdem folgende Visualisierung. Wir sind nicht Urheber dieser Zahlen, wir geben sie nur wieder. Mehr über die Studie und einen Link dazu, finden Sie im Abschnitt Originalquelle.

# read values calculated by Schmidheiny/
degression <- readxl::read_excel(
  "input/Progression_Export_SRF.xlsx",
  col_names = FALSE,
  skip = 2
) %>%
  # select relevant columns for graphic
  select(
    income = X__1,
    min = X__2,
    max = X__3,
    real = X__5
  ) %>%
  # make tidy
  gather(key, value, min:real) %>%
  # convert to decimal
  mutate(value = value / 100) %>%
  # convert to millions
  mutate(income = income / 1000000)

# plot
ggplot(
  degression,
  aes(
    x = income,
    y = value,
    color = key
  )
) +
  geom_line() +
  scale_x_log10() +
  scale_y_continuous(labels = percent) +
  theme_minimal()

Regionaljournale

Nun folgen die selben 4 Grafiken mit unterschiedlich hervorgehobenen Gemeinden, nämlich für die verscheidenen Regionaljournale von SRF.

# iterate over every regi there is by grouping and walking over it
# plus: concatenate the cantonal abbreviations to a string, and then
# split it up again with str_split. This way, we get one iteration per regi
# and still have the cantons as a vector and can us it as a filter
canton_abbrs %>%
  group_by(regi) %>%
  summarise(
    cantons = paste(abbr, collapse = ";"),
    canton_names = paste(name, collapse = ";")
  ) %>%
  # rename because otherwise we run into naming collisions
  rename(current_regi = regi) %>%
  pwalk(function(current_regi, cantons, canton_names) {
    current_cantons <- str_split(cantons, ";", simplify = TRUE)
    current_canton_names <- str_split(canton_names, ";", simplify = TRUE)

    cat(" \n\n ")

    print(glue("#### {current_regi}"))

    cat(" \n\n ")

    print(glue("##### Kantone und ihre Gemeinden"))

    cat(" \n\n ")

    cat(paste("**Was sehe ich hier?** \n\n",
             "Die eidgenössische Finanzverwaltung EFV veröffentlicht für alle",
             "Kantone und ihre Gemeinden die Einnahmen und Ausgaben in der so",
             "genannten funktionalen Gliederung (Bezeichnungen über den",
             "Grafiken). Wir haben von den Ausgaben die Einnahmen abgezogen",
             "und zeigen in der folgenden Grafik, wie viel Prozent der",
             "Gesamtausgaben für den jeweiligen Punkt ausgegeben werden.\n\n"))

    levels <- cantons_and_municipalities %>%
      mutate(tmp_name = glue("{id}{name}")) %>%
      filter(is_main) %>%
      arrange(desc(share))

    cantons_and_municipalities_plot <- ggplot(
      cantons_and_municipalities %>%
        filter(is_main) %>%
        mutate(
          tmp_name = factor(glue("{id}{name}"), levels = levels$tmp_name),
          category = factor(
            category,
            levels = levels %>%
              distinct(category) %>%
              simplify()
          )
        ),
      aes(
        x = tmp_name,
        y = share,
        label = name,
        fill = name %in% current_cantons
      )
    ) +
      geom_bar(stat = "identity") +
      geom_text(vjust = 1.6, color = "white", size = 1.2) +
      facet_wrap(~ category, scales = "free_x", ncol = 2) +
      scale_y_continuous(labels = percent) +
      scale_fill_manual(values = c("grey", "#d9230f")) +
      theme_minimal() +
      theme(
        axis.text.x = element_blank(),
        axis.ticks.x = element_blank(),
        legend.position = "none"
      ) +
      labs(
        title = "Wofür gibt welcher Kanton anteilsmässig viel aus?",
        subtitle = "Ausgaben betreffen den Kanton und alle seine Gemeinden",
        x = NULL,
        y = "Prozent an den Gesamtausgaben",
        fill = NULL
      )

    # save plot as image
    ggsave(
      glue("output/{slugify(current_regi)}_c_and_m.png"),
      cantons_and_municipalities_plot,
      width = 6,
      height = 10
    )

    # output image from file
    cat(" \n\n ")

    print(glue(
      "![](output/{slugify(current_regi)}_c_and_m.png)"
    ))

    cat(" \n\n ")

    print(glue("##### Städte im Vergleich\n\n"))

    cat(paste("**Was sehe ich hier?** \n\n",
             "Wir haben ausgerechnet, wie viele Steuern eine ledige Person",
             "mit einem Einkommen von CHF 100'000 bezahlen muss, wie hoch",
             "der Anteil der Gemeinde, des Kantons und des Bundes ist und",
             "wofür diese drei Staatsebenen wiederum wie viel Geld",
             "ausgeben. Die Zahlen sind Durchschnittswerte der Jahre **2010",
             "bis 2016**. Wir haben sie von der ESTV.\n\n",
             "Hier dargestellt sind die **Abweichungen vom Durchschnitt**",
             "aller Städte von vergleichbarer Grösse (Klein-, Mittel-,",
             "Grossstadt) für den jeweiligen Ausgabenpunkt.\n\n"))

    # small medium and large cities
    c("klein", "mittel", "gross") %>%
      walk(function(current_type) {

        # define sort overall (so every facet is displayed sorted)
        levels <- share_per_cat_delta %>%
          mutate(tmp_name = glue("{id}{name}")) %>%
          arrange(desc(delta))

        average <- share_per_cat_delta %>%
          filter(type == current_type)

        cities_plot <- ggplot(
          share_per_cat_delta %>%
            # join canton names
            left_join(
              inhabitants_municipalities %>%
                select(name = municipality, canton),
                by = "name"
            ) %>%
            # show only small or medium or large
            filter(type == current_type) %>%
            # this sorts bars in every facet according to sort prepared above
            mutate(
              tmp_name = factor(glue("{id}{name}"), levels = levels$tmp_name)
            ),
          aes(
            x = tmp_name,
            y = delta,
            label = name,
            fill = canton %in% current_canton_names
          )
        ) +
          geom_bar(stat = "identity") +
          geom_text(
            aes(size = type),
            vjust = 0.5,
            hjust = 0,
            angle = 90,
            nudge_x = 0.05
          ) +
          facet_wrap(
            ~ glue("{category}, Schnitt: {percent(average)}"),
            scales = "free_x",
            ncol = ifelse(current_type == "klein", 1, 2)
          ) +
          scale_size_manual(
            values = c("klein" = 2, "mittel" = 2, "gross" = 3)
          ) +
          scale_fill_manual(values = c("grey", "#d9230f")) +
          scale_y_continuous(labels = percent) +
          theme_minimal() +
          theme(
            axis.text.x = element_blank(),
            axis.ticks.x = element_blank(),
            legend.position = "none"
          ) +
          labs(
            title =
              glue("Wofür gibt welche Stadt ({current_type}) wie viel aus?"),
            subtitle =
              glue("Abweichungen vom Schnitt in %. Gerechnet mit Modell \\
                    {example$persona} mit Einkommen {example$income}."),
            x = NULL,
            y = glue("Abweichungen vom Schnitt aller Städte {current_type}"),
            fill = NULL
          )

        # save plot as image
        ggsave(
          glue("output/{slugify(current_regi)}_{current_type}.png"),
          cities_plot,
          width = 6,
          height = 12
        )

        # output image from file
        cat(" \n\n ")

        print(glue(
          "![](output/{slugify(current_regi)}_{current_type}.png)"
        ))

        cat(" \n\n ")

      })
  })

#### Aargau Solothurn

##### Kantone und ihre Gemeinden

Was sehe ich hier?

Die eidgenössische Finanzverwaltung EFV veröffentlicht für alle Kantone und ihre Gemeinden die Einnahmen und Ausgaben in der so genannten funktionalen Gliederung (Bezeichnungen über den Grafiken). Wir haben von den Ausgaben die Einnahmen abgezogen und zeigen in der folgenden Grafik, wie viel Prozent der Gesamtausgaben für den jeweiligen Punkt ausgegeben werden.

##### Städte im Vergleich

Was sehe ich hier?

Wir haben ausgerechnet, wie viele Steuern eine ledige Person mit einem Einkommen von CHF 100’000 bezahlen muss, wie hoch der Anteil der Gemeinde, des Kantons und des Bundes ist und wofür diese drei Staatsebenen wiederum wie viel Geld ausgeben. Die Zahlen sind Durchschnittswerte der Jahre 2010 bis 2016. Wir haben sie von der ESTV.

Hier dargestellt sind die Abweichungen vom Durchschnitt aller Städte von vergleichbarer Grösse (Klein-, Mittel-, Grossstadt) für den jeweiligen Ausgabenpunkt.

#### Basel Baselland

##### Kantone und ihre Gemeinden

Was sehe ich hier?

Die eidgenössische Finanzverwaltung EFV veröffentlicht für alle Kantone und ihre Gemeinden die Einnahmen und Ausgaben in der so genannten funktionalen Gliederung (Bezeichnungen über den Grafiken). Wir haben von den Ausgaben die Einnahmen abgezogen und zeigen in der folgenden Grafik, wie viel Prozent der Gesamtausgaben für den jeweiligen Punkt ausgegeben werden.

##### Städte im Vergleich

Was sehe ich hier?

Wir haben ausgerechnet, wie viele Steuern eine ledige Person mit einem Einkommen von CHF 100’000 bezahlen muss, wie hoch der Anteil der Gemeinde, des Kantons und des Bundes ist und wofür diese drei Staatsebenen wiederum wie viel Geld ausgeben. Die Zahlen sind Durchschnittswerte der Jahre 2010 bis 2016. Wir haben sie von der ESTV.

Hier dargestellt sind die Abweichungen vom Durchschnitt aller Städte von vergleichbarer Grösse (Klein-, Mittel-, Grossstadt) für den jeweiligen Ausgabenpunkt.

#### Bern Freiburg Wallis

##### Kantone und ihre Gemeinden

Was sehe ich hier?

Die eidgenössische Finanzverwaltung EFV veröffentlicht für alle Kantone und ihre Gemeinden die Einnahmen und Ausgaben in der so genannten funktionalen Gliederung (Bezeichnungen über den Grafiken). Wir haben von den Ausgaben die Einnahmen abgezogen und zeigen in der folgenden Grafik, wie viel Prozent der Gesamtausgaben für den jeweiligen Punkt ausgegeben werden.

##### Städte im Vergleich

Was sehe ich hier?

Wir haben ausgerechnet, wie viele Steuern eine ledige Person mit einem Einkommen von CHF 100’000 bezahlen muss, wie hoch der Anteil der Gemeinde, des Kantons und des Bundes ist und wofür diese drei Staatsebenen wiederum wie viel Geld ausgeben. Die Zahlen sind Durchschnittswerte der Jahre 2010 bis 2016. Wir haben sie von der ESTV.

Hier dargestellt sind die Abweichungen vom Durchschnitt aller Städte von vergleichbarer Grösse (Klein-, Mittel-, Grossstadt) für den jeweiligen Ausgabenpunkt.

#### Graubünden

##### Kantone und ihre Gemeinden

Was sehe ich hier?

Die eidgenössische Finanzverwaltung EFV veröffentlicht für alle Kantone und ihre Gemeinden die Einnahmen und Ausgaben in der so genannten funktionalen Gliederung (Bezeichnungen über den Grafiken). Wir haben von den Ausgaben die Einnahmen abgezogen und zeigen in der folgenden Grafik, wie viel Prozent der Gesamtausgaben für den jeweiligen Punkt ausgegeben werden.

##### Städte im Vergleich

Was sehe ich hier?

Wir haben ausgerechnet, wie viele Steuern eine ledige Person mit einem Einkommen von CHF 100’000 bezahlen muss, wie hoch der Anteil der Gemeinde, des Kantons und des Bundes ist und wofür diese drei Staatsebenen wiederum wie viel Geld ausgeben. Die Zahlen sind Durchschnittswerte der Jahre 2010 bis 2016. Wir haben sie von der ESTV.

Hier dargestellt sind die Abweichungen vom Durchschnitt aller Städte von vergleichbarer Grösse (Klein-, Mittel-, Grossstadt) für den jeweiligen Ausgabenpunkt.

#### Ostschweiz

##### Kantone und ihre Gemeinden

Was sehe ich hier?

Die eidgenössische Finanzverwaltung EFV veröffentlicht für alle Kantone und ihre Gemeinden die Einnahmen und Ausgaben in der so genannten funktionalen Gliederung (Bezeichnungen über den Grafiken). Wir haben von den Ausgaben die Einnahmen abgezogen und zeigen in der folgenden Grafik, wie viel Prozent der Gesamtausgaben für den jeweiligen Punkt ausgegeben werden.

##### Städte im Vergleich

Was sehe ich hier?

Wir haben ausgerechnet, wie viele Steuern eine ledige Person mit einem Einkommen von CHF 100’000 bezahlen muss, wie hoch der Anteil der Gemeinde, des Kantons und des Bundes ist und wofür diese drei Staatsebenen wiederum wie viel Geld ausgeben. Die Zahlen sind Durchschnittswerte der Jahre 2010 bis 2016. Wir haben sie von der ESTV.

Hier dargestellt sind die Abweichungen vom Durchschnitt aller Städte von vergleichbarer Grösse (Klein-, Mittel-, Grossstadt) für den jeweiligen Ausgabenpunkt.

#### RSI

##### Kantone und ihre Gemeinden

Was sehe ich hier?

Die eidgenössische Finanzverwaltung EFV veröffentlicht für alle Kantone und ihre Gemeinden die Einnahmen und Ausgaben in der so genannten funktionalen Gliederung (Bezeichnungen über den Grafiken). Wir haben von den Ausgaben die Einnahmen abgezogen und zeigen in der folgenden Grafik, wie viel Prozent der Gesamtausgaben für den jeweiligen Punkt ausgegeben werden.

##### Städte im Vergleich

Was sehe ich hier?

Wir haben ausgerechnet, wie viele Steuern eine ledige Person mit einem Einkommen von CHF 100’000 bezahlen muss, wie hoch der Anteil der Gemeinde, des Kantons und des Bundes ist und wofür diese drei Staatsebenen wiederum wie viel Geld ausgeben. Die Zahlen sind Durchschnittswerte der Jahre 2010 bis 2016. Wir haben sie von der ESTV.

Hier dargestellt sind die Abweichungen vom Durchschnitt aller Städte von vergleichbarer Grösse (Klein-, Mittel-, Grossstadt) für den jeweiligen Ausgabenpunkt.

#### RTS

##### Kantone und ihre Gemeinden

Was sehe ich hier?

Die eidgenössische Finanzverwaltung EFV veröffentlicht für alle Kantone und ihre Gemeinden die Einnahmen und Ausgaben in der so genannten funktionalen Gliederung (Bezeichnungen über den Grafiken). Wir haben von den Ausgaben die Einnahmen abgezogen und zeigen in der folgenden Grafik, wie viel Prozent der Gesamtausgaben für den jeweiligen Punkt ausgegeben werden.

##### Städte im Vergleich

Was sehe ich hier?

Wir haben ausgerechnet, wie viele Steuern eine ledige Person mit einem Einkommen von CHF 100’000 bezahlen muss, wie hoch der Anteil der Gemeinde, des Kantons und des Bundes ist und wofür diese drei Staatsebenen wiederum wie viel Geld ausgeben. Die Zahlen sind Durchschnittswerte der Jahre 2010 bis 2016. Wir haben sie von der ESTV.

Hier dargestellt sind die Abweichungen vom Durchschnitt aller Städte von vergleichbarer Grösse (Klein-, Mittel-, Grossstadt) für den jeweiligen Ausgabenpunkt.

#### Zentralschweiz

##### Kantone und ihre Gemeinden

Was sehe ich hier?

Die eidgenössische Finanzverwaltung EFV veröffentlicht für alle Kantone und ihre Gemeinden die Einnahmen und Ausgaben in der so genannten funktionalen Gliederung (Bezeichnungen über den Grafiken). Wir haben von den Ausgaben die Einnahmen abgezogen und zeigen in der folgenden Grafik, wie viel Prozent der Gesamtausgaben für den jeweiligen Punkt ausgegeben werden.

##### Städte im Vergleich

Was sehe ich hier?

Wir haben ausgerechnet, wie viele Steuern eine ledige Person mit einem Einkommen von CHF 100’000 bezahlen muss, wie hoch der Anteil der Gemeinde, des Kantons und des Bundes ist und wofür diese drei Staatsebenen wiederum wie viel Geld ausgeben. Die Zahlen sind Durchschnittswerte der Jahre 2010 bis 2016. Wir haben sie von der ESTV.

Hier dargestellt sind die Abweichungen vom Durchschnitt aller Städte von vergleichbarer Grösse (Klein-, Mittel-, Grossstadt) für den jeweiligen Ausgabenpunkt.

#### Zürich Schaffhausen

##### Kantone und ihre Gemeinden

Was sehe ich hier?

Die eidgenössische Finanzverwaltung EFV veröffentlicht für alle Kantone und ihre Gemeinden die Einnahmen und Ausgaben in der so genannten funktionalen Gliederung (Bezeichnungen über den Grafiken). Wir haben von den Ausgaben die Einnahmen abgezogen und zeigen in der folgenden Grafik, wie viel Prozent der Gesamtausgaben für den jeweiligen Punkt ausgegeben werden.

##### Städte im Vergleich

Was sehe ich hier?

Wir haben ausgerechnet, wie viele Steuern eine ledige Person mit einem Einkommen von CHF 100’000 bezahlen muss, wie hoch der Anteil der Gemeinde, des Kantons und des Bundes ist und wofür diese drei Staatsebenen wiederum wie viel Geld ausgeben. Die Zahlen sind Durchschnittswerte der Jahre 2010 bis 2016. Wir haben sie von der ESTV.

Hier dargestellt sind die Abweichungen vom Durchschnitt aller Städte von vergleichbarer Grösse (Klein-, Mittel-, Grossstadt) für den jeweiligen Ausgabenpunkt.

Franken pro Kopf

Bisher haben wir alles als Prozenten an den Gesamtausgaben ausgedrückt. Eine zweite Möglichkeit ist es, mit Franken pro Einwohner zu rechnen. Dies haben wir im folgenden Code gemacht, verzichten aber auf eine Weiterverfolgung dieser Ebene, da es für das Publikum noch einmal komplizierter wird, die Zahlen nachzuvollziehen. Genf hat beispielsweise ein hohes Staatsbudget und ist finanzstark, das führt zu hohen Ausgaben pro Kopf.

# join inhabitants per year and municipality
cantons_and_municipalities_per_capita <- cantons_and_municipalities_yearly %>%
  rename(abbr = name) %>%
  left_join(
    canton_abbrs %>%
      select(abbr, name),
    by = "abbr"
  ) %>%
  left_join(
    inhabitants_yearly_cantons %>%
      select(name, year, inhabitants),
    by = c("name", "year")
  ) %>%
  # look only at main categories
  filter(is_main) %>%
  # convert to chf per inhabitant
  mutate(value = value * 1000 / inhabitants) %>%
  select(-inhabitants) %>%
  group_by(id, category, name, abbr, is_main) %>%
  summarise(value = mean(value)) %>%
  ungroup()

levels <- cantons_and_municipalities_per_capita %>%
  arrange(desc(value)) %>%
  mutate(sorting_var = glue("{id}{abbr}"))

ggplot(
  cantons_and_municipalities_per_capita %>%
    mutate(
      sorting_var = factor(glue("{id}{abbr}"), levels = levels$sorting_var),
      category = factor(
        category,
        levels = levels %>%
          distinct(category) %>%
          simplify()
      )
    ),
  aes(
    label = abbr,
    x = sorting_var,
    y = value
  )
) +
  geom_bar(stat = "identity") +
  geom_text(vjust = 1.6, color = "white", size = 1.2) +
  facet_wrap(~ category, scales = "free_x", ncol = 2) +
  theme_minimal() +
  theme(
    axis.text.x = element_blank(),
    axis.ticks.x = element_blank(),
    legend.position = "none"
  )

# clean up
rm(levels)

Der Fokus unserer Analyse liegt aber weniger auf der absoluten Höhe der Steuern und Ausgaben, als auf der Priorisierung in den Gemeinden und Kantonen, deshalb gehen wir nicht vertieft auf diese Zahlen ein.

Linting

Der Code in diesem RMarkdown wird mit lintr automatisch auf den Wickham’schen tidyverse style guide überprüft.

lintr::lint(
  "main.Rmd",
  linters = lintr::with_defaults(
    object_length_linter(45)
  )
)
## main.Rmd:810:1: style: lines should not be more than 80 characters.
##           "input/zip_d_{max(years_of_interest)}/fs_ktn_gdn/ktn_gdn_{tolower(abbr)}.xlsx"
## ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
## main.Rmd:1475:12: style: Place a space before left parenthesis, except in a function call.
##   } else if(level == "kath") {
##            ^
## main.Rmd:1516:1: style: lines should not be more than 80 characters.
##     municipal_tmp = convert_rates_to_shares(municipal, cantonal, kath, canton, "municipal"),
## ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
## main.Rmd:1517:1: style: lines should not be more than 80 characters.
##     cantonal = convert_rates_to_shares(municipal, cantonal, kath, canton, "cantonal"),
## ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
## main.Rmd:2083:18: style: Commented code should be removed.
##     print(glue("#### {current_regi}"))
##                  ^~~~~~~~~~~~~~~~~~~~~