DataExploR: Typische Businessfragen mit R analysieren

In diesem Post untersuchen wir eine recht häufige Fragestellung im Bereich der Datenanalyse – die Auswertung von Umfragedaten. Umfragen sind eine gängige Angelegenheit in vielen Organisationen: man möchte wissen, ob die Kunden zufrieden sind oder was die Mitarbeiter vom Management denken. Wir werden nicht alle Aspekte der Analyse betrachten – da gibt es viel zu tun –, sondern ein paar zentrale Aspekte herausgreifen.

Laden wir zuerst ein paar nützliche Pakete:

library(tidyverse)
library(likert)
library(sjmisc)
library(sjPlot)

Dann laden wir Umfragedaten:

data(extra, package = "pradadata")

Das Paket pradadata muss vorab (einmalig) installiert werden (s. Installationshinweise).

Diese Daten untersuchen die Extraversion von Menschen (z.B. Mitarbeiter*innen); außerdem werden noch ein paar mit der Extraversion korrelierte Variablen erhoben (für unsere Zwecke im Detail nicht weiter von Belang).

glimpse(extra)
#> Observations: 826
#> Variables: 34
#> $ timestamp          <chr> "11.03.2015 19:17:48", "11.03.2015 19:18:05...
#> $ code               <chr> "HSC", "ERB", "ADP", "KHB", "PTG", "ABL", "...
#> $ i01                <int> 3, 2, 3, 3, 4, 3, 4, 3, 4, 4, 3, 3, 4, 4, 3...
#> $ i02r               <int> 3, 2, 4, 3, 3, 2, 4, 3, 4, 4, 3, 4, 3, 3, 3...
#> $ i03                <int> 3, 1, 1, 2, 1, 1, 1, 2, 1, 2, 1, 1, 1, 4, 1...
#> $ i04                <int> 3, 2, 4, 4, 4, 4, 3, 3, 4, 4, 3, 3, 2, 4, 3...
#> $ i05                <int> 4, 3, 4, 3, 4, 2, 3, 2, 3, 3, 3, 2, 3, 3, 3...
#> $ i06r               <int> 4, 2, 1, 3, 3, 3, 3, 2, 4, 3, 3, 3, 3, 3, 3...
#> $ i07                <int> 3, 2, 3, 3, 4, 4, 2, 3, 3, 3, 2, 4, 2, 3, 3...
#> $ i08                <int> 2, 3, 2, 3, 2, 3, 3, 2, 3, 3, 3, 2, 3, 3, 4...
#> $ i09                <int> 3, 3, 3, 3, 3, 3, 3, 4, 4, 3, 4, 2, 4, 4, 4...
#> $ i10                <int> 1, 1, 1, 2, 4, 3, 2, 1, 2, 3, 1, 3, 2, 3, 2...
#> $ n_facebook_friends <dbl> 250, 106, 215, 200, 100, 376, 180, 432, 200...
#> $ n_hangover         <dbl> 1, 0, 0, 15, 0, 1, 1, 2, 5, 0, 1, 2, 20, 2,...
#> $ age                <int> 24, 35, 25, 39, 29, 33, 24, 28, 29, 38, 25,...
#> $ sex                <chr> "Frau", "Frau", "Frau", "Frau", "Frau", "Ma...
#> $ extra_single_item  <int> 4, 3, 4, 3, 4, 4, 3, 3, 4, 4, 4, 4, 4, 4, 4...
#> $ time_conversation  <dbl> 10, 15, 15, 5, 5, 20, 2, 15, 10, 10, 1, 5, ...
#> $ presentation       <chr> "nein", "nein", "nein", "nein", "nein", "ja...
#> $ n_party            <dbl> 20, 5, 3, 25, 4, 4, 3, 6, 12, 5, 10, 5, 10,...
#> $ clients            <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
#> $ extra_vignette     <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
#> $ i21                <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
#> $ extra_vignette2    <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
#> $ major              <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
#> $ smoker             <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
#> $ sleep_week         <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
#> $ sleep_wend         <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
#> $ clients_freq       <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
#> $ extra_mean         <dbl> 2.9, 2.1, 2.6, 2.9, 3.2, 2.8, 2.8, 2.5, 3.2...
#> $ extra_md           <dbl> 3.0, 2.0, 3.0, 3.0, 3.5, 3.0, 3.0, 2.5, 3.5...
#> $ extra_aad          <dbl> 0.56, 0.54, 1.08, 0.36, 0.80, 0.68, 0.68, 0...
#> $ extra_mode         <dbl> 3, 2, 1, 3, 4, 3, 3, 2, 4, 3, 3, 3, 3, 3, 3...
#> $ extra_iqr          <dbl> 0.00, 0.75, 2.50, 0.00, 1.00, 0.75, 0.75, 1...

Zuerst wählen wir nur die 10 Items der Umfrage heraus, das ist komfortabler als der Datensatz mit 34 Variablen:

extra %>% 
  select(i01:i10) -> extra_items

Der Operator %>% bedeutet so viel wie “und dann”. Demnach liest sich die Syntax oben wie folgt:

Nimm den Datensatz “extra” UND DANN… wähle Spalten i01 bis i10 speichere das Ergebnis als “extra_items”

Werfen wir einen Blick hinein (engl. “to glimpse”):

glimpse(extra_items)
#> Observations: 826
#> Variables: 10
#> $ i01  <int> 3, 2, 3, 3, 4, 3, 4, 3, 4, 4, 3, 3, 4, 4, 3, 4, 4, 4, 3, ...
#> $ i02r <int> 3, 2, 4, 3, 3, 2, 4, 3, 4, 4, 3, 4, 3, 3, 3, 4, 4, 3, 3, ...
#> $ i03  <int> 3, 1, 1, 2, 1, 1, 1, 2, 1, 2, 1, 1, 1, 4, 1, 2, 3, 3, 1, ...
#> $ i04  <int> 3, 2, 4, 4, 4, 4, 3, 3, 4, 4, 3, 3, 2, 4, 3, 4, 4, 4, 4, ...
#> $ i05  <int> 4, 3, 4, 3, 4, 2, 3, 2, 3, 3, 3, 2, 3, 3, 3, 3, 3, 4, 3, ...
#> $ i06r <int> 4, 2, 1, 3, 3, 3, 3, 2, 4, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, ...
#> $ i07  <int> 3, 2, 3, 3, 4, 4, 2, 3, 3, 3, 2, 4, 2, 3, 3, 3, 3, 4, 3, ...
#> $ i08  <int> 2, 3, 2, 3, 2, 3, 3, 2, 3, 3, 3, 2, 3, 3, 4, 3, 2, 4, 3, ...
#> $ i09  <int> 3, 3, 3, 3, 3, 3, 3, 4, 4, 3, 4, 2, 4, 4, 4, 4, 4, 4, 3, ...
#> $ i10  <int> 1, 1, 1, 2, 4, 3, 2, 1, 2, 3, 1, 3, 2, 3, 2, 3, 3, 3, 2, ...

Visualisierung der Itemantworten

Mit dem Paket likert kann man sich komfortabel ein schönes Diagramm zu den Itemverteilungen ausgeben lassen. Allerdings “verdaut” likert nur klassische Dataframes, keine neumodischen Tibbles. extra_items ist aber ein Tibble, d.h. hat die Klasse tbl:

class(extra_items)
#> [1] "tbl_df"     "tbl"        "data.frame"

Wir wandeln daher in einen “normalen” Dataframe um:

extra_items %>%
  as.data.frame() -> extra_items

class(extra_items)
#> [1] "data.frame"

Nun wandeln wir den Dataframe in ein Objekt vom Typ likert um, das resultierende Objekt kann dann einfach geplottet werden. Ach ja, likert möchte die Items gerne vom Typ factor haben, also wandeln wir alle Items noch um (mit mutate_all()), und zwar in den Typ factor:

extra_items %>% 
  mutate_all(factor) -> extra_items_f
  
extra_items_f %>% 
  likert() %>% 
  plot()

Items umkodieren

Häufig kommt es vor, dass ein Item umkodiert werden muss. Was heißt umkodieren und wozu braucht man es? Normalerweise sind Items “richtig herum” formuliert, etwa “Ich gehe gerne unter Leute” wäre ein Beispiel für ein Extraversionsitem. Es ist in Richtung Extraversion formuliert (oder, synonym, “kodiert”). Aber das Item “Ich bleibe am liebsten alleine zuhause” ist nicht Richtung Extraversion kodiert, sondern im Gegenteil in Richtung Introversion. Leute, die diesem Item zustimmen, sind also nicht hoch sondern gering extrovertiert. Das Item muss zuerst “umgedreht werden”: Hat jemand die höchste Stufe (hier: 4) angekreuzt, so muss das auf die geringste Antwortstufe (hier: 1) umgedreht werden und so weiter.

Das kann man wie folgt leisten; nehmen wir an, Item 01 müsse umkodiert werden:

extra_items %>% 
  rec(i01, rec = "1=4; 2=3; 3=2; 4=1")
#> # A tibble: 826 x 11
#>      i01  i02r   i03   i04   i05  i06r   i07   i08   i09   i10 i01_r
#>    <int> <int> <int> <int> <int> <int> <int> <int> <int> <int> <dbl>
#>  1     3     3     3     3     4     4     3     2     3     1     2
#>  2     2     2     1     2     3     2     2     3     3     1     3
#>  3     3     4     1     4     4     1     3     2     3     1     2
#>  4     3     3     2     4     3     3     3     3     3     2     2
#>  5     4     3     1     4     4     3     4     2     3     4     1
#>  6     3     2     1     4     2     3     4     3     3     3     2
#>  7     4     4     1     3     3     3     2     3     3     2     1
#>  8     3     3     2     3     2     2     3     2     4     1     2
#>  9     4     4     1     4     3     4     3     3     4     2     1
#> 10     4     4     2     4     3     3     3     3     3     3     1
#> # ... with 816 more rows

Das umkodierte Item (ganz hinten als letzte Spalte im Datensatz) wird praktischerweise mit _r gekennzeichnet, so kann man einfach damit weiterarbeiten.

Eine weitere häufig benötigte Art des Umkodierens ist, statt einer Antwortzahl wie 1 oder 2 etc. die Beschreibung dieser Antwort aufzuführen, z.B. “ich stimme dieser Aussage überhaupt nicht zu”. Das geht z.B. so:

extra_items_f %>% 
  mutate_all(funs( case_when(
             . == "1" ~ "stimme nicht zu",
             . == "2" ~ "stimme eher nicht zu",
             . == "3" ~ "stimme eher zu",
             . == "4" ~ "stimme voll und ganz zu"))) -> extra_items_rec
glimpse(extra_items_rec)
#> Observations: 826
#> Variables: 10
#> $ i01  <chr> "stimme eher zu", "stimme eher nicht zu", "stimme eher zu...
#> $ i02r <chr> "stimme eher zu", "stimme eher nicht zu", "stimme voll un...
#> $ i03  <chr> "stimme eher zu", "stimme nicht zu", "stimme nicht zu", "...
#> $ i04  <chr> "stimme eher zu", "stimme eher nicht zu", "stimme voll un...
#> $ i05  <chr> "stimme voll und ganz zu", "stimme eher zu", "stimme voll...
#> $ i06r <chr> "stimme voll und ganz zu", "stimme eher nicht zu", "stimm...
#> $ i07  <chr> "stimme eher zu", "stimme eher nicht zu", "stimme eher zu...
#> $ i08  <chr> "stimme eher nicht zu", "stimme eher zu", "stimme eher ni...
#> $ i09  <chr> "stimme eher zu", "stimme eher zu", "stimme eher zu", "st...
#> $ i10  <chr> "stimme nicht zu", "stimme nicht zu", "stimme nicht zu", ...

Betrachten wir diese Syntax genauer:

  • Mit mutate_all weisen wir an, dass alle Spalten verändert (“mutiert”) werden sollen
  • Mit case_when definieren wir die Bedingungen für die Veränderung
  • Der Punkt . steht dabei als Platzhalter für jeweils eine Spalte, sozusagen die Spalte “i”

Man kann den case_when Befehl grob so ins Deutsche übersetzen:

Wenn eine Zelle in der aktuellen Spalte den Wert “1” hat, dann soll das in “stimme überhaupt nicht zu” übersetzt werden. Für übrige Werte (2,3,4) gilt das Gleiche.

Zeilenmittelwerte berechnen

Häufig möchte man Zeilenmittelwerte oder Zeilensummen berechnen. Das kann man so machen:

extra_items %>%
  row_means(i01:i10, n = 9) -> extra_items
extra_items
#> # A tibble: 826 x 11
#>      i01  i02r   i03   i04   i05  i06r   i07   i08   i09   i10 rowmeans
#>    <int> <int> <int> <int> <int> <int> <int> <int> <int> <int>    <dbl>
#>  1     3     3     3     3     4     4     3     2     3     1      2.9
#>  2     2     2     1     2     3     2     2     3     3     1      2.1
#>  3     3     4     1     4     4     1     3     2     3     1      2.6
#>  4     3     3     2     4     3     3     3     3     3     2      2.9
#>  5     4     3     1     4     4     3     4     2     3     4      3.2
#>  6     3     2     1     4     2     3     4     3     3     3      2.8
#>  7     4     4     1     3     3     3     2     3     3     2      2.8
#>  8     3     3     2     3     2     2     3     2     4     1      2.5
#>  9     4     4     1     4     3     4     3     3     4     2      3.2
#> 10     4     4     2     4     3     3     3     3     3     3      3.2
#> # ... with 816 more rows

Mit n = 9 legen wir fest, dass mindestens 9 (von 10) Items von der Person beantwortet worden sollen, sonst wird ein fehlender Wert zurückgeliefert.

Mittelwerte pro Person nach Gruppen aufgeteilt

Häufig ist man an summativen Statistiken pro Gruppe interessiert, etwa: Ist die Belegschaft in Abteilung A engagierter als in Abteilung B? Sind die Kunden mit Produkt X zufriedener als mit Produkt Y?

Diese Frage kann man so beantworten: Zuerst fügen wir eine Gruppierungsvariable hinzu, hier Geschlecht (sex):

extra_items %>% 
  mutate(sex = extra$sex) -> extra_items

mutate definiert eine neue Spalte, in diesem Fall mit dem Inhalt der Spalte sex aus dem ursprünglichen Datensatz `extra´.

Jetzt berechnen wir die mittlere Extraversion pro Gruppe:

extra_items %>% 
  row_means(i01:i10, n = 9) %>% 
  group_by(sex) %>% 
  summarise(group_mean = mean(rowmeans, na.rm = TRUE))
#> # A tibble: 3 x 2
#>   sex   group_mean
#>   <chr>      <dbl>
#> 1 Frau        2.91
#> 2 Mann        2.86
#> 3 <NA>        2.96

Häufigkeitsauswertung

Umfragedaten werden gerne nach Häufigkeiten ausgewertet, das kann so aussehen für das Item i10:

extra_items %>% 
  frq(i10)
#> 
#> # i10 <integer> 
#> # total N=826  valid N=817  mean=2.20  sd=0.88
#>  
#>   val frq raw.prc valid.prc cum.prc
#>     1 183   22.15     22.40   22.40
#>     2 354   42.86     43.33   65.73
#>     3 212   25.67     25.95   91.68
#>     4  68    8.23      8.32  100.00
#>  <NA>   9    1.09        NA      NA

frq steht dabei für “frequencies”, also Häufigkeiten. Einfach, oder?

Vielleicht möchte man diese Häufigkeiten noch visualisiert haben, z.B. so:

sjp.frq(extra_items$i10)

Oder in einer Variante:

sjp.frq(extra_items$i10, type = "histogram",
        show.mean = TRUE, normal.curve = TRUE)

Vielleicht auch als schön formatierte Kontingenztabelle - Item 10 in Zusammenhang mit dem Geschlecht:

sjt.xtab(extra_items$i10, extra_items$sex)
i10 sex Total
Frau Mann
1 140 41 181
2 223 130 353
3 132 78 210
4 32 36 68
Total 527 285 812
χ2=22.661 · df=3 · Cramer’s V=0.167 · p=0.000

Mehr davon?! - In unseren Seminaren

Sie möchten mehr über moderne Datenvisualisierung lernen?

In unseren Seminaren lernen Sie alles einiges über professionelle Datenvisualisierung.

Nähere Informationen zum Seminar: https://www.data-divers.com/leistungen/seminare/seminar-dataviser/

Zur Annmeldung: https://www.data-divers.com/leistungen/seminare/seminaranmeldung/

Dozenten:

Prof. Dr. Sebastian Sauer (zur Homepage, zum Blog)

Prof. Dr. Felix Bauer (zur Homepage)

Buch zur modernen Datenanalyse (und Datenvisualisierung) mit R

Dieses Buch erklärt ausführlich die Grundlagen der Datenanalyse mit R: