Daten manipulieren

Datensätze in R

Wie alle anderen Programme zur statistischen Auswertung hat R natürlich neben den Vektoren auch rechteckige Datenformate.

Das typische rechteckige Datenformat in base R ist der data.frame. Im Prinzip nichts anderes, als spaltenweise zusammengeklebte Vektoren. Der Konstruktor für ein solches Objekt ist die gleichnamige Funktion, die die Spalten als benannte Argumente nimmt:

df <- data.frame(a = 1:3,
                 b = c(TRUE, FALSE, TRUE),
                 c = c('a','b','c'))
df
  a     b c
1 1  TRUE a
2 2 FALSE b
3 3  TRUE c

Das Indizieren im Datensatz geht dann am lesbarsten, durch das Angeben der gewünschten Spalte mit dem $-Operator und der Auswahl der Zeile durch den schon bekannten []-Operator.

df$c[2] ## 2. Wert in der 'c'-Spalte.
[1] "b"

Wie könnte ich den 3. Wert in der b-Spalte indizieren?

df$b[3]

Antwort aufdecken

Der iris-Datensatz ist ein im Grundumfang von R mitgelieferter Datensatz, der historische botanische Daten nach Anderson (1935) enthält.

iris %>% 
  head()
  Sepal.Length Sepal.Width Petal.Length
1          5.1         3.5          1.4
2          4.9         3.0          1.4
3          4.7         3.2          1.3
4          4.6         3.1          1.5
5          5.0         3.6          1.4
6          5.4         3.9          1.7
  Petal.Width Species
1         0.2  setosa
2         0.2  setosa
3         0.2  setosa
4         0.2  setosa
5         0.2  setosa
6         0.4  setosa

Übersicht über Datensatz verschaffen

Das ist natürlich ein bisschen unübersichtlich, wie kann man damit umgehen?

1. Möglichkeit:

Wenn man iris explizit in das Environment nimmt, kann man die Oberfläche von RStudio nutze, um sich einen Überblick zu verschaffen 1

1 Dabei nutzt die RStudio-IDE aber nur die str()(für structure)-Funktion.

iris <- iris

2. Möglichkeit:

Die summary-Funktion, die genau das macht, was ihr Name suggeriert:

summary(iris)
  Sepal.Length    Sepal.Width     Petal.Length  
 Min.   :4.300   Min.   :2.000   Min.   :1.000  
 1st Qu.:5.100   1st Qu.:2.800   1st Qu.:1.600  
 Median :5.800   Median :3.000   Median :4.350  
 Mean   :5.843   Mean   :3.057   Mean   :3.758  
 3rd Qu.:6.400   3rd Qu.:3.300   3rd Qu.:5.100  
 Max.   :7.900   Max.   :4.400   Max.   :6.900  
  Petal.Width          Species  
 Min.   :0.100   setosa    :50  
 1st Qu.:0.300   versicolor:50  
 Median :1.300   virginica :50  
 Mean   :1.199                  
 3rd Qu.:1.800                  
 Max.   :2.500                  

Aufgabe: Deskriptive Kennwerte berechnen

Wir wollen für diesen Datensatz jetzt die folgenden Schritte der Auswertung vollziehen:

  1. Ausschluss der Blumen, die breitere Blütenblätter als das 1.5-fache der mittleren Blütenblätter haben und Kelche, die kürzer als das Mittel der Kelchlänge sind

  2. Darstellung der Mittelwerte und Streuungen der Blütenblattlänge und -breite pro verbleibende Spezies als Tabelle

Aufgabe: Base-R Lösung

df <- iris[iris$Petal.Width <= 1.5 * mean(iris$Petal.Width) &
             iris$Sepal.Length >= mean(iris$Sepal.Length),]
means <- aggregate(cbind(df$Petal.Length,df$Petal.Width),
          by = list(Species = df$Species),
          FUN = mean)
sds <- aggregate(cbind(df$Petal.Length,df$Petal.Width),
          by = list(Species = df$Species),
          FUN = sd)
tab <- data.frame(means, sds[,2:3])
names(tab)[2:5] = c('m_Length', 'm_Width', 'sd_Length', 'sd_Width')
tab
     Species m_Length m_Width sd_Length
1 versicolor    4.560   1.424 0.2783882
2  virginica    5.375   1.500 0.3862210
    sd_Width
1 0.14798649
2 0.08164966

Auftritt tidyverse

Die selbe Aufgabe wie gerade, jetzt mit dem tidyverse:

library(tidyverse)
iris %>% 
  filter(Petal.Width <= 1.5 * mean(Petal.Width) &
           Sepal.Length >= mean(Sepal.Length)) %>% 
  group_by(Species) %>% 
  summarise(m_Length = mean(Petal.Length),
            sd_Length = sd(Petal.Length),
            m_Width = mean(Petal.Width), 
            sd_Width = sd(Petal.Width))
# A tibble: 2 × 5
  Species    m_Length sd_Length m_Width sd_Width
  <fct>         <dbl>     <dbl>   <dbl>    <dbl>
1 versicolor     4.56     0.278    1.42   0.148 
2 virginica      5.38     0.386    1.5    0.0816

tidy aggregation

Das tidyverse (Wickham et al., 2019) ist eine Sammlung von Paketen, deren Hauptziel es ist, Datenaufbereitung in R intuitiver und leichter lesbar zu machen.

Ein zentrales Element dabei ist der %>%-Operator, die sogenannte Pipeline2. Beim Skript-Lesen und -Schreiben kann man sich diese am Besten als ‘dann’ vorstellen

2 base-R hat mit Version 4.1 auch eine native pipe eingeführt (|>), da wir aber eh das tidyverse nutzen bleiben wir bei der magrittr-pipe

Mit ihrer Hilfe werden Aufbereitungsschritte in einer stringenten Reihe an Operationen formuliert, die sich am Besten als Satz verstehen lassen.

Da die Funktionen im tidyverse alle mit einfachen Verben benannt sind, lässt sich die Operation von eben auch so lesen.

1library(tidyverse)
2iris %>%
3  filter(Petal.Width <= 1.5 * mean(Petal.Width) &
           Sepal.Length >= mean(Sepal.Length)) %>%
4  group_by(Species) %>%
5  summarise(m_Length = mean(Petal.Length),
            sd_Length = sd(Petal.Length),
            m_Width = mean(Petal.Width),
            sd_Width = sd(Petal.Width))
1
Zuerst muss das tidyverse geladen werden
2
Nimm iris, dann …
3
filter Zeilenweise nach den gesetzten Regeln, dann…
4
gruppiere nach der Spezies, dann…
5
berechne die angegebenen Kenngrößen über die Gruppen.

Zweite Beispielaufgabe:

Wir möchten für den iris-Datensatz:

  1. Eine Spalte hinzufügen, die die z-transformierte Blattlänge enthält

  2. Eine Spalte hinzufügen, die als character das Ergebnis eines Mediansplits der gerade erstellten Variable enthält

  3. Einen Datensatz erstellen, der nur die Spezies, die z-Transformierte und die Mediansplit-Variable enthält

  4. Die Häufigkeiten der Kombinationen von Mediansplit-Gruppe und Spezies auszählen

1df <- iris %>%
2  mutate(
3        z_length = (Petal.Length-mean(Petal.Length))/sd(Petal.Length),
4        med_split = case_when(
5                           z_length >= median(z_length) ~ 'upper',
6                           T ~ 'lower')) %>%
7  select(Species, z_length, med_split)
1
Erstelle ein Objekt df. Nimm dazu iris, dann …
2
verändere den Datensatz indem Du …
3
die z-Werte pro Blatt-Länge berechnest und als z_length dem Datensatz hinzufügst, …
4
mit der Funktion case_when eine Spalte anlegst, die …
5
z_length-Werten kleiner/gleich dem Median ‘upper’ zuweist …
6
und allen anderen Werten ‘lower’. Dann…
7
wähle die Spalten Species, z_length und med_split aus.

Hat das geklappt?

Wie könnte ich das überprüfen?

summary(df)
       Species      z_length      
 setosa    :50   Min.   :-1.5623  
 versicolor:50   1st Qu.:-1.2225  
 virginica :50   Median : 0.3354  
                 Mean   : 0.0000  
                 3rd Qu.: 0.7602  
                 Max.   : 1.7799  
  med_split        
 Length:150        
 Class :character  
 Mode  :character  
                   
                   
                   

Antwort aufdecken

Jetzt noch Häufigkeiten auszählen:

1df %>%
2  group_by(Species, med_split) %>%
3  summarise(n = n())
1
Nimm df, dann …
2
gruppiere nach Species und med_split, dann…
3
Zähle die absoluten Häufigkeiten aus.
`summarise()` has grouped output by 'Species'.
You can override using the `.groups` argument.
# A tibble: 4 × 3
# Groups:   Species [3]
  Species    med_split     n
  <fct>      <chr>     <int>
1 setosa     lower        50
2 versicolor lower        25
3 versicolor upper        25
4 virginica  upper        50

Aufgabe

Machen Sie sich mit dem swiss-Datensatz vertraut. Lesen Sie dazu auch die Hilfeseite zu dem Datensatz, diese können Sie mit ?swiss aufrufen. Erstellen Sie mit Hilfe einer pipeline einen Datensatz, der…

  1. nur Provinzen enthält, deren Einwohner zu mehr als 10% und weniger als 35% Bestnoten bei der Armee-Untersuchung erhalten haben

  2. nur den Anteil der männlichen Population in der Landwirtschaft, die Kindersterblichkeit, das Bildungsniveau und den Anteil der katholischen Familien enthält

  3. eine numerische Variable enthält, die für die so ausgewählten Fälle einen Mediansplit der Kindersterblichkeit codiert.

  4. eine Variable enthält, die angibt, ob der Anteil der männlichen Population an der Landwirtschaft über oder unter dem Mittelwert (mean) liegt

Lassen Sie sich die absoluten Häufigkeiten der Kombination der beiden gerade erstellten Variablen ausgeben.

Zusatz: Erstellen Sie anschließend eine kurze pipeline, die den gerade erstellten Datensatz mit dem Absteigenden Bildungsniveau als ersten Sortierschlüssel und dem aufsteigenden Anteil katholischer Familien als zweitem Schlüssel sortiert. Nutzen Sie dafür die Hilfeseite der arrange-Funktion.

library(tidyverse)
df <- swiss %>%
  filter(Education > 10,
         Education < 35) %>%
  select(Agriculture,
         Infant.Mortality,
         Education,
         Catholic) %>%
  mutate(
    mediansplit_mortality = case_when(
      Infant.Mortality >= median(Infant.Mortality) ~ 1,
      T ~ -1),
    meansplit_agriculture = case_when(
      Agriculture > mean(Agriculture) ~ 'high',
      Agriculture < mean(Agriculture) ~ 'low',
      T ~ 'mean'
    )
  )

df %>% 
  count(mediansplit_mortality,meansplit_agriculture)
  mediansplit_mortality meansplit_agriculture n
1                    -1                  high 4
2                    -1                   low 4
3                     1                  high 4
4                     1                   low 4
df2 <- df %>% 
  arrange(-Education,
          Catholic)

Antwort aufdecken

Literatur

Anderson, E. (1935). The irises of the Gaspe Peninsula. Bull. Am. Iris Soc., 59, 2–5.
Wickham, H., Averick, M., Bryan, J., Chang, W., McGowan, L. D., François, R., Grolemund, G., Hayes, A., Henry, L., Hester, J., Kuhn, M., Pedersen, T. L., Miller, E., Bache, S. M., Müller, K., Ooms, J., Robinson, D., Seidel, D. P., Spinu, V., … Yutani, H. (2019). Welcome to the tidyverse. Journal of Open Source Software, 4(43), 1686. https://doi.org/10.21105/joss.01686