Dieses Dokument beschreibt die Vorprozessierung und explorative Analyse des Datensatzes, der Grundlage des auf srf.ch veröffentlichten Artikel Diese Telekom-Anbieter sorgen für den meisten Unmut 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):
reasons_top_four_providers.csv
cases_per_type.csv
cases_top_4_with_exit.csv
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-11-19 14:12:02. R version: 3.5.3 on x86_64-pc-linux-gnu. For this report, CRAN packages as of 2019-03-01 were used.
Der Code für die vorliegende Datenprozessierung ist auf https://github.com/srfdata/2019-11-ombudscom zur freien Verwendung verfügbar.
2019-11-ombudscom 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 https://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.
cases_per_type.csv
Attribut | Typ | Beschreibung |
---|---|---|
year | Numeric | Jahr der Beobachtung |
Fernmeldedienst | Numeric | Anzahl Fälle der Fernmeldedienstanbieter |
Mehrwertdienst | Numeric | Anzahl Fälle der Mehrwertdienstanbieter |
cases_top_4_with_exit.csv
Exportiert werden nur die drei aus unserer Sicht wichtigsten Ausgänge. Andere bezieht sich sowohl auf Fernmeldedienstanbieter wie auch auf Mehrwertdienstanbieter, auch hier sind die Jahre 2014-18 abgebildet.
Attribut | Typ | Beschreibung |
---|---|---|
category | String | Ausgang des Falls in Worten |
Salt | Numeric | Anzahl Fälle 2014-2018 mit diesem Ausgang bei Salt |
Sunrise | Numeric | Anzahl Fälle 2014-2018 mit diesem Ausgang bei Sunrise |
Swisscom | Numeric | Anzahl Fälle 2014-2018 mit diesem Ausgang bei Swisscom |
UPC | Numeric | Anzahl Fälle 2014-2018 mit diesem Ausgang bei UPC |
Andere | Numeric | Anzahl Fälle 2014-2018 mit diesem Ausgang bei allen anderen Providern |
reasons_top_four_providers.csv
Diese Zahlen beziehen sich nur auf die Fälle der 4 grössten Provider Salt, Sunrise, Swisscom und UPC, auch hier sind die Jahre 2014-18 abgebildet.
Attribut | Typ | Beschreibung |
---|---|---|
category | String | Beschwerdegrund |
value | Numeric | Anzahl Fälle 2014-2018 mit diesem Beschwerdegrund |
Originalquelle der Auswertungen sind Daten, die SRF im Rahmen des Öffentlichkeitsprinzips von der Ombudscom herausverlangt hat. Die betreffenden Excel-Dateien, die SRF zugeschickt worden waren, finden sich im Ordner input/data
.
Lesen Sie mehr über das Verfahren eines Ombudscom-Falls auf der betreffenden Website oder im letzten Bericht (2018). Wichtig ist uns die Unterscheidung zwischen einer Anfrage und einem Fall. Es wird wie folgt beschrieben: “Eine Anfrage wird statistisch erfasst, wenn sich eine Person schriftlich oder telefonisch an die Schlichtungsstelle wendet und die Voraussetzungen für eine Einleitung des Schlichtungsverfahrens gemäss Art. 8 Prozess- und Gebührenreglement noch nicht erfüllt sind.”
Das bedeutet, dass sich diese Analyse hauptsächlich auf die Fälle und nicht auf die Anfragen konzentriert. So wurden beispielsweise im Jahr 2018 die folgenden Untergruppen von Anfragen protokolliert:
Wie im Jahresbericht 2018 auf Seite 9 angegeben. Im Erklärungstext ist zu verstehen, dass die Anfragen allein vielleicht keine sehr zuverlässige Informationsquelle sind: “872 abgeschlossene Anfragen sind auf nicht eingereichte Dokumente zurückzuführen. Diese machten knapp 26% aller Anfragen aus. Gegenüber dem Vorjahr stieg dieser Wert leicht. Oft sind den Kundinnen und Kunden die Hürden zur Durchführung eines Schlichtungsverfahrens zu hoch. Bereits das Ausfüllen eines speziell für das Schlichtungsverfahren vorgesehenen Formulars bereitete einigen Personen Mühe. Oftmals gingen sie fälschlicherweise auch davon aus, dass der Ombudsmann ihre Interessen gegenüber den Anbietern vertritt oder die streitige Angelegenheit umgehend (z.B. mit einem Telefonat mit dem betroffenen Anbieter) lösen kann. Auch wurden sie von auf die Schlichtungsstelle verweisenden Stellen falsch über die Funktion und die Aufgaben der Schlichtungsstelle informiert. Die Mitarbeitenden der Schlichtungsstelle klärten die Kundinnen und Kunden über die Tätigkeit auf und informierten sie über die Eintretensvoraussetzungen: Bevor ein Schlichtungsverfahren eingeleitet werden kann, muss die begehrende Partei das Formular „Schlichtungsbegehren“ unter Angabe des Sachverhalts und Ziels ausfüllen sowie glaubhaft darlegen, dass sie sich innerhalb der letzten 12 Monate erfolglos um eine Lösung des Problems bemüht hatte. Ging aus der Anfrage nicht hervor, dass die Kundin oder der Kunde bereits eine Lösung mit dem Anbieter angestrebt hatte, empfahl die Schlichtungsstelle, sich schriftlich mit einer Beschwerde an den betroffenen Anbieter zu wenden. Dieser Aufwand war vielen Kundinnen und Kunden zu gross. Sie gingen – wie bereits erwähnt – davon aus, dass die Schlichtungsstelle ihre Interessen vertreten könne und sie in der Angelegenheit nichts weiter unternehmen müssen. Daher strebten sie kein Schlichtungsverfahren an und die Anfragen wurden infolge nicht eingereichter Dokumente abgeschlossen.”
## [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(tidyverse) # ggplot2, dplyr, tidyr, readr, purrr, tibble
library(glue) # string literals
library(magrittr) # pipes
library(readxl) # excel
library(scales) # scales for ggplot2
library(jsonlite) # json
library(lintr) # code linting
library(ggrepel) # repelling geom_text
library(rmarkdown)",
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 = F,
use.knitr = F,
R.version = R_version)
rm(package_date)
source("manifest.R")
unlink("manifest.R")
sessionInfo()
## R version 3.5.3 (2019-03-11)
## Platform: x86_64-pc-linux-gnu (64-bit)
## Running under: Ubuntu 18.04.3 LTS
##
## Matrix products: default
## BLAS: /opt/R/R-3.5.3/lib/R/lib/libRblas.so
## LAPACK: /opt/R/R-3.5.3/lib/R/lib/libRlapack.so
##
## locale:
## [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
## [3] LC_TIME=de_CH.UTF-8 LC_COLLATE=en_US.UTF-8
## [5] LC_MONETARY=de_CH.UTF-8 LC_MESSAGES=en_US.UTF-8
## [7] LC_PAPER=de_CH.UTF-8 LC_NAME=C
## [9] LC_ADDRESS=C LC_TELEPHONE=C
## [11] LC_MEASUREMENT=de_CH.UTF-8 LC_IDENTIFICATION=C
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] rmarkdown_1.11 ggrepel_0.8.0 lintr_1.0.3 jsonlite_1.6
## [5] scales_1.0.0 readxl_1.3.0 magrittr_1.5 glue_1.3.0
## [9] forcats_0.4.0 stringr_1.4.0 dplyr_0.8.0.1 purrr_0.3.0
## [13] readr_1.3.1 tidyr_0.8.2 tibble_2.0.1 ggplot2_3.1.0
## [17] tidyverse_1.2.1 checkpoint_0.4.0 rstudioapi_0.9.0 knitr_1.21
##
## loaded via a namespace (and not attached):
## [1] Rcpp_1.0.0 cellranger_1.1.0 plyr_1.8.4 pillar_1.3.1
## [5] compiler_3.5.3 tools_3.5.3 digest_0.6.18 lubridate_1.7.4
## [9] evaluate_0.13 nlme_3.1-137 gtable_0.2.0 lattice_0.20-38
## [13] pkgconfig_2.0.2 rlang_0.3.1 rex_1.1.2 cli_1.0.1
## [17] yaml_2.2.0 haven_2.1.0 xfun_0.5 withr_2.1.2
## [21] xml2_1.2.0 httr_1.4.0 hms_0.4.2 generics_0.0.2
## [25] grid_3.5.3 tidyselect_0.2.5 R6_2.4.0 modelr_0.1.4
## [29] backports_1.1.3 htmltools_0.3.6 rvest_0.3.2 assertthat_0.2.0
## [33] colorspace_1.4-0 stringi_1.3.1 lazyeval_0.2.1 munsell_0.5.0
## [37] broom_0.5.1 crayon_1.3.4
rm(list = ls(all = TRUE))
# prepare multiple tibbles that will be filled with data
status <- tibble() # Statistik
reasons_cases_requests <- tibble() # Beschwerdegründe (Fälle und Anfragen)
reasons_cases <- tibble() # Beschwerdegründe (nur Fälle)
reasons_requests <- tibble() # Beschwerdegründe (nur Anfragen)
languages <- tibble() # Sprache Beschwerdeführer
appellant_types <- tibble() # Art Beschwerdeführer
avg_sums <- tibble() # Durchschnittliche Streitsummen
avg_durations <- tibble() # Durchschnittlichen Behandlungsdauer
# the files have a consistent structure
c("FDA", "MWDA") %>%
walk(function(current_type) {
list.files(
"input/data",
pattern = glue("^\\d{{4}}_{current_type}.xlsx$"),
full.names = TRUE
) %>%
walk(function(current_file) {
# create helper function that gathers providers
# renames first column to category and appends info about year/file
add_info_and_gather <- function(df) {
df %>%
rename(category = 1) %>%
mutate(
year = as.numeric(str_extract(current_file, "\\d{4}")),
type = str_replace_all(current_type, c(
"FDA" = "Fernmeldedienst",
"MWDA" = "Mehrwertdienst"
))
) %>%
gather(
key = "provider",
value = "value",
-one_of("category", "year", "type", "file")
) %>%
# convert to numeric, but first remove dots (thousands) and
# replace commas (decimal) with point
mutate(
value = str_replace_all(value, "\\.", ""),
value = str_replace_all(value, ",", "."),
value = as.numeric(value)
)
}
# read ranges and bind to prepared tibbles
suppressMessages({
status <<- status %>%
bind_rows(
read_excel(current_file, skip = 1, n_max = 37) %>%
add_info_and_gather()
)
reasons_cases_requests <<- reasons_cases_requests %>%
bind_rows(
read_excel(current_file, skip = 40, n_max = 22) %>%
add_info_and_gather()
)
reasons_cases <<- reasons_cases %>%
bind_rows(
read_excel(current_file, skip = 65, n_max = 22) %>%
add_info_and_gather()
)
reasons_requests <<- reasons_requests %>%
bind_rows(
read_excel(current_file, skip = 90, n_max = 22) %>%
add_info_and_gather()
)
languages <<- languages %>%
bind_rows(
read_excel(current_file, skip = 115, n_max = 4) %>%
add_info_and_gather()
)
appellant_types <<- appellant_types %>%
bind_rows(
read_excel(current_file, skip = 122, n_max = 3) %>%
add_info_and_gather()
)
avg_sums <<- avg_sums %>%
bind_rows(
read_excel(current_file, skip = 128, n_max = 2) %>%
add_info_and_gather()
)
avg_durations <<- avg_durations %>%
bind_rows(
read_excel(current_file, skip = 133, n_max = 8) %>%
add_info_and_gather()
)
})
})
})
# In our tables we still have some totals. Not only in the column category
# where it is the sum of all other categories, but also in the column provider
# we remove all these, we can sum stuff on our own perfectly well.
# Also convert category, type, provider to factor in all tibbles, don't be
# confused by the syntax with walk / assign, it's really just a fancy way
# to apply the same mutation to all variables named in the vector.
c(
"status",
"reasons_cases_requests",
"reasons_cases",
"reasons_requests",
"languages",
"appellant_types",
"avg_sums",
"avg_durations"
) %>%
walk(function(current_tibble) {
# create new variables with names …_totals
assign(
glue("{current_tibble}_totals"),
get(current_tibble) %>%
# now remove totals from original tables
filter(
str_detect(category, "^Total") |
str_detect(provider, "^Total")
),
envir = .GlobalEnv
)
# apply mutation to all variables
assign(
current_tibble,
get(current_tibble) %>%
# now remove totals from original tables
filter(
!str_detect(category, "^Total") &
!str_detect(provider, "^Total")
) %>%
mutate_at(
vars(category, type, provider),
factor
),
envir = .GlobalEnv
)
})
Die Tabelle status
enthält zwei Gruppen: Fälle
und Anfragen abgeschlossen
. In der Spalte category
sind auch die Unterkategorien aufgeführt, aber diese beiden sind geeignet, um sich die Summen pro Jahr anzusehen, z.B:
top_four <- c("Salt Mobile SA", "Swisscom (Schweiz) AG",
"Sunrise Communications AG", "UPC Schweiz GmbH")
status %>%
filter(category == "Fälle" | category == "Anfragen abgeschlossen") %>%
group_by(category, year) %>%
summarise(sum = sum(value)) %>%
spread(key = category, value = sum) %>%
mutate(Total = `Anfragen abgeschlossen` + `Fälle`) %>%
knitr::kable()
year | Anfragen abgeschlossen | Fälle | Total |
---|---|---|---|
2014 | 5036 | 1184 | 6220 |
2015 | 4849 | 1420 | 6269 |
2016 | 5115 | 1333 | 6448 |
2017 | 4183 | 1125 | 5308 |
2018 | 3330 | 1078 | 4408 |
status %>%
filter(category == "Fälle" | category == "Anfragen abgeschlossen") %>%
group_by(category, year, type) %>%
summarise(sum = sum(value)) %>%
ggplot(
aes(
x = year,
y = sum,
fill = type
)
) +
geom_bar(stat = "identity") +
scale_fill_brewer(palette = "Set1", guide = FALSE) +
facet_grid(category ~ type, scales = "fixed") +
theme_minimal() +
labs(
title = "Anzahl Fälle und Anfragen pro Jahr",
x = NULL,
y = NULL,
fill = NULL
)