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ädteverbandscantons.csv
: Ausgabenstruktur der Kantonemunicipalities_by_canton.csv
: Ausgabenstruktur der Gemeinden pro Kantoncantons_and_municipalities.csv
: Ausgabenstruktur der Kantone und all ihrer Gemeindenfederation.csv
: Ausgabenstruktur des Bundesdeltas.csv
: Abweichungen der Ausgaben von Schweizer Klein-, Mittel- und Grossstädte vom DurchschnittDie 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.
Der Code für die vorliegende Datenprozessierung ist auf https://github.com/srfdata/2019-02-steuern zur freien Verwendung verfügbar.
2019-02-steuern von SRF Data ist lizenziert unter einer Creative Commons Namensnennung - Weitergabe unter gleichen Bedingungen 4.0 International Lizenz.
Code & Daten von SRF Data sind unter http://srfdata.github.io verfügbar.
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.
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 |
→ 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.
→ 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.
→ 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.
→ 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.
→ 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.
→ 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:
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)
→ 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.
→ 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.
→ 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.
→ input/Raumgliederungen.xlsx
→ klein_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.
→ 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.
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
## [1] "package package:rmarkdown detached"
## Loading required package: knitr
## Loading required package: rstudioapi
# 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")
# 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)
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)
)
}
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)
Folgende Fusionen fanden innerhalb der Städte des Städteverbands statt:
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.
# 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.
# 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%}
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%}
# 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.
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)
)
# 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
)
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 ——- —– ——– ——
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()
## )
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
# 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)
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")
# 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))
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
)
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
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"
)
# 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"
)
# 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).
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()
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.
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.
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}"))
## ^~~~~~~~~~~~~~~~~~~~~