Kapitel 5 Letzte Vorbereitungen …

Jetzt ist es an der Zeit, sich zu vergewissern in welchem Verzeichnis ihr auf eurem Computer ihr euch befindet. Falls ihr ein RStudio Projekt nutzt, sollte diese Frage leicht zu beantworten sein. Falls ihr trotzdem unsicher seid, gebt getwd() in der Konsole ein, um das aktuelle Arbeitsverzeichnis zu sehen.

Alle nachfolgenden Code Beispiele wollen wir in einer .R Datei abspeichern, die dann im aktuellen Arbeitsverzeichnis gespeichert werden soll. Idealerweise geben wir dieser Datei noch einen Namen, wie etwa datenanalyse_teil_1.R, der uns schon viel über den möglichen Inhalt erzählt. Alternativ können wir alle Befehle natürlich auch in eine R Markdown Datei schreiben, siehe Test drive R Markdown.

Prinzipiell geht es in den folgenden Abschnitten um Datenmanagement. Es werden aber auch immer wieder ein paar Grafiken zu sehen sein. Dies ist aber kein Problem, da ihr ja bereits den Data Visualization Basics Primer.

besucht habt und somit den nötigen Background besitzt.

5.1 Data frames sind fantastisch

Das Standardformat für Daten ist ein data frame. Die meisten Funktionen zur Inferenz, Modellierung und graphischen Darstellung erwarten, dass ihnen über ein data = Argument ein data frame übergeben wird. Dies gilt für Funktion aus der R Basisdistribution schon seit langem.

Die als tidyverse bekannte Kollektion von Paketen geht noch einen Schritt weiter und priorisiert ausdrücklich die Verarbeitung von data frames. Tatsächlich priorisiert tidyverse eine besondere Art von data frames, die als “tibble” bezeichnet wird.

Data frames - im Gegensatz zu allgemeinen Arrays oder speziell Matrizen - können Variablen unterschiedlicher Typen enthalten, wie z.B. Textdaten (Subjekt-ID oder Name), quantitative Daten (Anzahl der weißen Blutkörperchen) und qualitative/kategoriale Informationen (behandelt vs. unbehandelt). Genauer gesagt können in data frames unterschiedliche Spalten aus unterschiedlichen Datentypen bestehen. Innerhalb einer Spalte müssen aber alle Einträge vom gleichen Typ sein.

Daten aus einer Datenanalyse bestehen immer aus mehr als einem Datentyp. Aus diesem Grund eigenen sich Matrizen oder Arrays nicht zur Datenanalyse, da man sonst mit verschiedenen, unverbundenen Objekten (Matrizen, Arrays) arbeiten müsste und diese nur schwer koordinieren kann.

5.2 Gapminder data

Wir werden mit einem Teil der Daten aus dem Gapminder-Projekt

arbeiten. Die Daten sind im gapminder Paket enthalten, welches wir über CRAN installieren können:

install.packages("gapminder")

Um die Daten zu verwenden, müssen wir das Paket natürlich auch noch laden

Gapminder wurde von Hans, Anna und Ola Rosling gegründet. Auf der Webseite des Projekts findet man nicht nur viel Informatives, sondern auch sehr Unterhaltsames

Leider ist Hans Rosling bereits 2017 verstorben.

5.3 Die gapminder Daten sind ein “tibble”

Durch das Laden des gapminder Pakets haben wir nun Zugriff auf einen Datenobjekt mit demselben Namen. Schauen wir uns mithilfe der Funktion str() mal die Struktur dieses Objekts an.

str(gapminder)
## tibble [1,704 × 6] (S3: tbl_df/tbl/data.frame)
##  $ country  : Factor w/ 142 levels "Afghanistan",..: 1 1 1 1 1 1 1 1 1 1 ...
##  $ continent: Factor w/ 5 levels "Africa","Americas",..: 3 3 3 3 3 3 3 3 3 3 ...
##  $ year     : int [1:1704] 1952 1957 1962 1967 1972 1977 1982 1987 1992 1997 ...
##  $ lifeExp  : num [1:1704] 28.8 30.3 32 34 36.1 ...
##  $ pop      : int [1:1704] 8425333 9240934 10267083 11537966 13079460 14880372..
##  $ gdpPercap: num [1:1704] 779 821 853 836 740 ...

str() liefert eine vernünftige Beschreibung von fast allem, und im schlimmsten Fall kann tatsächlich auch nichts “Schlimmes” passieren. Aus dem Output erkennen wir, dass der Datensatz 1704 Beobachtungen enthält.

Auf der anderen Seite hätten wir durch direkten Aufruf von gapminder den Inhalt auch direkt auf den Bildschirm schreiben können. Aber vielleicht habt ihr schon mal einen größeren Datensatz aufgerufen und zögert nun etwas, da große Datensätze tendenziell einfach die Konsole füllen und nur sehr wenig Einblick bieten.

Tidyverse bietet eine spezielle data frame Variante an: ein tibble. gapminder ist ein tibble. Das sieht man auch, wenn man sich die Klasse dieses Objekts anzeigen lässt

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

Es ist immer noch ein reguläres data frame, aber zusätzlich eben auch ein tibble.

Wenn wir nun gapminder einfach auf den Bildschirm ausgeben, sehen wir, dass die Konsole nicht voll läuft.

gapminder
## # A tibble: 1,704 × 6
##    country     continent  year lifeExp      pop gdpPercap
##    <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
##  1 Afghanistan Asia       1952    28.8  8425333      779.
##  2 Afghanistan Asia       1957    30.3  9240934      821.
##  3 Afghanistan Asia       1962    32.0 10267083      853.
##  4 Afghanistan Asia       1967    34.0 11537966      836.
##  5 Afghanistan Asia       1972    36.1 13079460      740.
##  6 Afghanistan Asia       1977    38.4 14880372      786.
##  7 Afghanistan Asia       1982    39.9 12881816      978.
##  8 Afghanistan Asia       1987    40.8 13867957      852.
##  9 Afghanistan Asia       1992    41.7 16317921      649.
## 10 Afghanistan Asia       1997    41.8 22227415      635.
## # ℹ 1,694 more rows

Dies ist der erste große Vorteil eines tibbles gegenüber einem data frame.

Wenn ihr mit einem reinen data frame arbeitet - und dieses Feature gefällt euch - so könnt ihr es mit as_tibble() in ein tibble transformieren.

library(tidyverse)
as_tibble(iris)
## # A tibble: 150 × 5
##    Sepal.Length Sepal.Width Petal.Length Petal.Width Species
##           <dbl>       <dbl>        <dbl>       <dbl> <fct>  
##  1          5.1         3.5          1.4         0.2 setosa 
##  2          4.9         3            1.4         0.2 setosa 
##  3          4.7         3.2          1.3         0.2 setosa 
##  4          4.6         3.1          1.5         0.2 setosa 
##  5          5           3.6          1.4         0.2 setosa 
##  6          5.4         3.9          1.7         0.4 setosa 
##  7          4.6         3.4          1.4         0.3 setosa 
##  8          5           3.4          1.5         0.2 setosa 
##  9          4.4         2.9          1.4         0.2 setosa 
## 10          4.9         3.1          1.5         0.1 setosa 
## # ℹ 140 more rows

Weitere Möglichkeiten, grundlegende Informationen zu einem data frame abzufragen:

names(gapminder)
## [1] "country"   "continent" "year"      "lifeExp"   "pop"       "gdpPercap"
ncol(gapminder)
## [1] 6
length(gapminder)
## [1] 6
dim(gapminder)
## [1] 1704    6
nrow(gapminder)
## [1] 1704

Ein bisschen deskriptive Statistik zum Inhalt eines data frames erhält man mit der summary() Funktion:

summary(gapminder)
##         country        continent        year         lifeExp    
##  Afghanistan:  12   Africa  :624   Min.   :1952   Min.   :23.6  
##  Albania    :  12   Americas:300   1st Qu.:1966   1st Qu.:48.2  
##  Algeria    :  12   Asia    :396   Median :1980   Median :60.7  
##  Angola     :  12   Europe  :360   Mean   :1980   Mean   :59.5  
##  Argentina  :  12   Oceania : 24   3rd Qu.:1993   3rd Qu.:70.8  
##  Australia  :  12                  Max.   :2007   Max.   :82.6  
##  (Other)    :1632                                               
##       pop             gdpPercap     
##  Min.   :6.00e+04   Min.   :   241  
##  1st Qu.:2.79e+06   1st Qu.:  1202  
##  Median :7.02e+06   Median :  3532  
##  Mean   :2.96e+07   Mean   :  7215  
##  3rd Qu.:1.96e+07   3rd Qu.:  9325  
##  Max.   :1.32e+09   Max.   :113523  
## 

Bemerkung: summary() ist eine generische Funktion. Für eine gegebene Klasse (des Inputs) bestimmt die generische Funktion die passende Methode. Die Funktion summary() besitzt z.B. die folgenden Methoden:

methods(summary)
##  [1] summary.aov                         summary.aovlist*                   
##  [3] summary.aspell*                     summary.check_packages_in_dir*     
##  [5] summary.connection                  summary.data.frame                 
##  [7] summary.Date                        summary.default                    
##  [9] summary.Duration*                   summary.ecdf*                      
## [11] summary.factor                      summary.ggplot*                    
## [13] summary.glm                         summary.hcl_palettes*              
## [15] summary.infl*                       summary.Interval*                  
## [17] summary.lm                          summary.loess*                     
## [19] summary.manova                      summary.matrix                     
## [21] summary.mlm*                        summary.nls*                       
## [23] summary.packageStatus*              summary.Period*                    
## [25] summary.POSIXct                     summary.POSIXlt                    
## [27] summary.ppr*                        summary.prcomp*                    
## [29] summary.princomp*                   summary.proc_time                  
## [31] summary.rlang_error*                summary.rlang_message*             
## [33] summary.rlang_trace*                summary.rlang_warning*             
## [35] summary.rlang:::list_of_conditions* summary.srcfile                    
## [37] summary.srcref                      summary.stepfun                    
## [39] summary.stl*                        summary.table                      
## [41] summary.tukeysmooth*                summary.vctrs_sclr*                
## [43] summary.vctrs_vctr*                 summary.warnings                   
## see '?methods' for accessing help and source code

Obwohl wir uns formell noch nicht eingehender mit der Visualisierung beschäftigt haben, ist es wichtig ein paar Grafiken zu erzeugen um einen ersten Eindruck über den Datensatz zu bekommen. Auf der anderen Seite werden die Grafiken aber auch nicht (viel) aufwendiger als die Beispiele im Visualisation Basics Primer.

Interessant ist z.B. der zeitliche Verlauf der Lebenserwartung

ggplot(gapminder, mapping = aes(x = year, y = lifeExp)) +
         geom_point()

oder der Zusammenhang zwischen dem GDP (gross domestic product) und der Lebenserwartung

ggplot(gapminder, mapping = aes(x = gdpPercap, y = lifeExp)) +
  geom_point()
ggplot(gapminder, mapping = aes(x = log(gdpPercap), y = lifeExp)) +
  geom_point()

Grafiken dieser Art werden wir zu einem späteren Zeitpunkt noch genauer behandeln. Dann sprechen wir natürlich auch intensiv über deren Inhalt.


Wir schauen uns nochmal die Ausgabe von str() an, um darüber zu sprechen, was ein data frame genau ist.

str(gapminder)
## tibble [1,704 × 6] (S3: tbl_df/tbl/data.frame)
##  $ country  : Factor w/ 142 levels "Afghanistan",..: 1 1 1 1 1 1 1 1 1 1 ...
##  $ continent: Factor w/ 5 levels "Africa","Americas",..: 3 3 3 3 3 3 3 3 3 3 ...
##  $ year     : int [1:1704] 1952 1957 1962 1967 1972 1977 1982 1987 1992 1997 ...
##  $ lifeExp  : num [1:1704] 28.8 30.3 32 34 36.1 ...
##  $ pop      : int [1:1704] 8425333 9240934 10267083 11537966 13079460 14880372..
##  $ gdpPercap: num [1:1704] 779 821 853 836 740 ...

Ein data frame ist ein Sonderfall einer Liste. R verwendet Listen um so gut wie alles in ein Objekt aufnehmen zu können. Data frames sind nun ein Spezialfall einer Liste, bei dem die Länge jedes Listenelements gleich ist.

Nehmen wir mal an, dass wir eine Beschreibung der Variablen

names(gapminder)
## [1] "country"   "continent" "year"      "lifeExp"   "pop"       "gdpPercap"

zusammen mit den Daten abspeichern wollen. Dazu könnten wir ein tibble

(desc <- tibble(variables = names(gapminder),
               desc = c("factor with 142 levels", "factor with 5 levels",
                        "ranges from 1952 to 2007 in increments of 5 years",
                        "life expectancy at birth, in years",
                        "population","GDP per capita (US$, inflation-adjusted)")))
## # A tibble: 6 × 2
##   variables desc                                             
##   <chr>     <chr>                                            
## 1 country   factor with 142 levels                           
## 2 continent factor with 5 levels                             
## 3 year      ranges from 1952 to 2007 in increments of 5 years
## 4 lifeExp   life expectancy at birth, in years               
## 5 pop       population                                       
## 6 gdpPercap GDP per capita (US$, inflation-adjusted)

erzeugen, das die Beschreibungen enthält. Dieses data frame hat nun 6 Zeilen und 2 Spalten. Trotzdem können wir es mit gapminder kombinieren, wenn wir beide tibbles in einer Liste abspeichern

gapminder_desc <- list(gapminder, desc)
str(gapminder_desc)
## List of 2
##  $ : tibble [1,704 × 6] (S3: tbl_df/tbl/data.frame)
##   ..$ country  : Factor w/ 142 levels "Afghanistan",..: 1 1 1 1 1 1 1 1 1 1 ...
##   ..$ continent: Factor w/ 5 levels "Africa","Americas",..: 3 3 3 3 3 3 3 3 3 ..
##   ..$ year     : int [1:1704] 1952 1957 1962 1967 1972 1977 1982 1987 1992 199..
##   ..$ lifeExp  : num [1:1704] 28.8 30.3 32 34 36.1 ...
##   ..$ pop      : int [1:1704] 8425333 9240934 10267083 11537966 13079460 14880..
##   ..$ gdpPercap: num [1:1704] 779 821 853 836 740 ...
##  $ : tibble [6 × 2] (S3: tbl_df/tbl/data.frame)
##   ..$ variables: chr [1:6] "country" "continent" "year" "lifeExp" ...
##   ..$ desc     : chr [1:6] "factor with 142 levels" "factor with 5 levels" "r"..

5.4 Variablen in einem data frame

Einzelne Variable in einem data frame kann man mit dem Dollarzeichen $ ansprechen. Als Beispiel schauen wir uns die numerische Variable lifeExp genauer an.

head(gapminder$lifeExp)
## [1] 28.8 30.3 32.0 34.0 36.1 38.4
summary(gapminder$lifeExp)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    23.6    48.2    60.7    59.5    70.8    82.6

Die summary() Funktion berechnet, neben dem empirischen Mittel, das Five-number summary, bestehend aus

  1. dem Minimum

  2. dem unteren Quartil (das empirische 0.25 Quantil)

  3. dem Median

  4. dem oberen Quartil (das empirische 0.75 Quantil)

  5. dem Maximum

Zusätzlich wollen wir noch die Verteilung von lifeExp visualisieren und plotten dazu ein Histogramm. Dazu verwenden wir wieder ggplot().

Beachtet, dass ihr innerhalb der ggplot2 Funktionen Variablen eines data frames direkt ansprechen könnt. Es ist hier also nicht nötig mit dem $ Operator zu arbeiten.

ggplot(gapminder, mapping = aes(x = lifeExp)) + 
  geom_histogram()
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Alternativ können wir die Einträge eines data frames aber auch über die eckigen Klammern [] indizieren.

summary(gapminder[,"lifeExp"])
##     lifeExp    
##  Min.   :23.6  
##  1st Qu.:48.2  
##  Median :60.7  
##  Mean   :59.5  
##  3rd Qu.:70.8  
##  Max.   :82.6

Dabei spezifiziert der Eintrag links vom Komma die Zeilen und der Wert rechts davon die Spalten. Dies ist hilfreich, wenn man auf einzelne Werte zugreifen will. Aber beim Aufruf nur einer Variable (Spalte), ist die $ Notation sicherlich vorteilhafter.

Die Variable year ist eine ganzzahlige Variable, aber da es so wenige unterschiedliche Werte gibt, funktioniert sie auch ein wenig wie eine kategoriale Variable und es macht Sinn die Häufigkeit der einzelnen Ausprägungen zu zählen.

summary(gapminder$year)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    1952    1966    1980    1980    1993    2007
table(gapminder$year)
## 
## 1952 1957 1962 1967 1972 1977 1982 1987 1992 1997 2002 2007 
##  142  142  142  142  142  142  142  142  142  142  142  142

Wir erkennen, dass in jedem Jahr die gleiche Anzahl an Beobachtungen vorhanden ist. Es handelt sich dabei stets um Beobachtungen aus den gleichen 142 Ländern. Dies erkennt man aus dieser Ausgabe aber natürlich noch nicht.

Die Variablen country und continent enthalten rein kategorische Informationen, die in R (häufig) als factor gespeichert werden.

class(gapminder$continent)
## [1] "factor"
summary(gapminder$continent)
##   Africa Americas     Asia   Europe  Oceania 
##      624      300      396      360       24
levels(gapminder$continent)
## [1] "Africa"   "Americas" "Asia"     "Europe"   "Oceania"
nlevels(gapminder$continent)
## [1] 5

Die Levels von continent sind "Afrika", "America" usw., also Beschreibungen, die man gut lesen und verstehen kann. So sollte es sein, wenn man in R eine Faktorvariable aufruft. Im Allgemeinen sind die Levels von Menschen lesbare Zeichenfolgen, wie "male/female" oder "control/treated".

Euch sollte aber auch klar sein, dass R diese Information in kodierter Form speichert. Als Beispiel könnt ihr euch das Ergebnis von str(gapminder$continent) anschauen

str(gapminder$continent)
##  Factor w/ 5 levels "Africa","Americas",..: 3 3 3 3 3 3 3 3 3 3 ...

Faktorvariablen haben Vor- und Nachteil. Im weiteren Verlauf zeigen wir sowohl die Vor- wie auch die Nachteile. Generell ist aber durchaus so, dass die Vorteile überwiegen.

Als Nächstes erstellen wir mit der Funktion table() eine Häufigkeitstabelle für die Variable count. Die berechneten Werte wollen wir anschließend noch mithilfe der Funktion geom_bar() visualisieren. Die zu plottenden Häufigkeiten müssen dabei aber nicht an die Funktion geom_bar() übergeben werden. Die Funktion berechnet selbst (über die statistische Transformation stat_count()) die entsprechenden Werte.

table(gapminder$continent)
## 
##   Africa Americas     Asia   Europe  Oceania 
##      624      300      396      360       24
ggplot(gapminder, aes(x = continent)) + geom_bar()

In den folgenden Abbildungen sehen wir, wie die kodierten Zahlenwerte einer Faktor Variable genutzt werden können. Der continent-Faktor lässt sich im ggplot2-Paket leicht in “Facetten” oder Farben abbilden.

Die Erstellung von Grafiken mit ggplot2 werden wir noch genauer besprechen. Daher könnt ihr euch also ruhig zurücklehnen und die Plots einfach genießen oder blind kopieren/einfügen.

Zunächst initialisieren wir eine Grafik ohne Inhalt, da nicht gesagt wird, wie die Daten geplottet werden sollen.

p <- ggplot(filter(gapminder, 
                   continent != "Oceania"), # Daten aus Oceania werden "herausgefiltert"
            aes(x = gdpPercap, y = lifeExp))  

Für die x-Achse wählen wir im nächsten Schritt eine log Skala (zur Basis 10).

p <- p + scale_x_log10() 

Zum Grafikobjekt p werden nun weitere Komponenten hinzugefügt und dann jeweils geplottet

# einen scatterplot
p + geom_point() 
# mit verschiedenen farben für die verschiedenen kontinente
p + geom_point(aes(color = continent))  
# punkte mit transparenz
p + geom_point(alpha = (1/3), size = 3) + 
  geom_smooth(lwd = 3, se = FALSE) # einen geglätteten zusammenhang
## `geom_smooth()` using method = 'gam' and formula = 'y ~ s(x, bs = "cs")'
# für jeden kontinent einen eigenen plot (innerhalb einer grafik) erzeugen
p + geom_point(alpha = (1/3), size = 3) + 
  facet_wrap(~ continent) + 
  geom_smooth(lwd = 1.5, se = FALSE)
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'

5.5 Aufgabe

Im nächsten Abschnitt starten wir mit dem dplyr Paket. Bearbeitet daher zur Vorbereitung die ersten beiden Abschnitte des Work with Data

Working with Tibbbles zeigt die Verwendung von Tibbles sowie die Verwaltung von tidyverse-Paketen.

learnr::run_tutorial("tibbles", package = "idsst.rtutorials")

Isolating Data with dplyr zeigt wie ihr Variablen auswählt, Beobachtungen filtert und Werte neu anordnet. Zudem wird der Pipe-Operator von R, |>, eingeführt.

learnr::run_tutorial("isolating", package = "idsst.rtutorials")