12  Faktoren

Faktoren sind die Datenstruktur in R für kategoriale, das heißt nominale (<fct>) und ordinale (<ord>) Merkmale, also Merkmale, die endlich viele bekannte und feste Ausprägungen haben. Die offensichtlichen Vorteile sind

Zum Bearbeiten von Faktoren nutzt man das Paket forcats, welches fester Bestandteil des tidyverse ist. Es muss daher nicht zusätzlich geladen werden.

Bemerkungen:

  • Der Artikel Wrangling categorical data in R liefert einen ersten Einblick, sowie eine kurze geschichtliche Entwicklung der Faktoren in R.

  • Der Name forcats ist nicht nur ein Anagramm von Factors, sondern auch eine Kurzform von for categorical variables.

Abbildung 12.1: Logo Forcats

12.1 Erstellen von Faktoren

Im folgenden Beispiel haben wir zwei Objekte x1 und x2. Das Merkmale sind die Monate (abgekürzt auf drei Buchstaben), wobei der Vektor x2 einen Tippfehler enthält.

x1 <- c("Dez", "Apr", "Jul", "Aug")
x2 <- c("Dez", "Apr", "Jum", "Aug")    # Tippfehler

Sortiert man dieses Merkmal, so erhält man eine (wenig sinnvolle) alphabetische Ordnung.

sort(x1)
[1] "Apr" "Aug" "Dez" "Jul"

Was man an der Stelle haben möchte ist eine Ordnung wie die Monate im Jahr vorkommen. Dies erreicht man, wenn man aus den Vektoren jeweils einen Faktor macht. Um einen Faktor zu erstellen, benötigt man die Levels, das heißt die möglichen Ausprägungen, die ein Faktor annehmen kann, wobei die Reihenfolge wichtig ist.

# Levels / mögliche Ausprägungen:

monate_levels <- c("Jan", "Feb", "Mär", "Apr", "Mai", "Jun", 
                   "Jul", "Aug", "Sep", "Okt", "Nov", "Dez")
# Erstellen des Faktors:
y1 <- factor(x1, levels = monate_levels)

# sinnvolle Ordnung
sort(y1)
[1] Apr Jul Aug Dez
Levels: Jan Feb Mär Apr Mai Jun Jul Aug Sep Okt Nov Dez

Am obigen Beispiel können wir sehen, dass nicht nur die im Vektor vorkommenden Ausprägungen des Vektors y1 angezeigt werden, sondern auch alle möglichen Ausprägungen, die Levels, die ein Vektor annehmen kann. Bei Tippfehlern (oder allgemein nicht vorkommenden Ausprägungen) werden diese zu NAs umgewandelt.

# Tippfehler werden zu NA:
y2 <- factor(x2, levels = monate_levels)
y2
[1] Dez  Apr  <NA> Aug 
Levels: Jan Feb Mär Apr Mai Jun Jul Aug Sep Okt Nov Dez
# mit Warnung (aus dem readr-Paket!):
y2 <- parse_factor(x2, levels = monate_levels)
Warning: 1 parsing failure.
row col           expected actual
  3  -- value in level set    Jum

Bemerkungen

  • Geben wir das Argument levels= nicht an, so wird die alphabetische Reihenfolge genommen.

  • Manchmal möchte man, dass die Reihenfolge der Ausprägungen in der diese im Vektor oder später dann in der Datentabelle genommen wird. Dies kann man mit unique() oder fct_inorder() erreichen. Letzteres wird später nocheinmal erklärt.

x3 <- c("B", "A", "D", "A", "A", "B", "E", "E", "N")
unique(x3)
[1] "B" "A" "D" "E" "N"
(y3 <- factor(x3, levels = unique(x3)))
[1] B A D A A B E E N
Levels: B A D E N
  • Eine Möglichkeit auf die Menge aller zulässigen Levels zuzugreifen ist die Funktion levels() die einen Zeichenketten-Vektor liefert.
levels(y3)
[1] "B" "A" "D" "E" "N"

12.1.1 Der Datensatz gss_cat

gss_cat
# A tibble: 21,483 × 9
    year marital         age race  rincome        partyid    relig denom tvhours
   <int> <fct>         <int> <fct> <fct>          <fct>      <fct> <fct>   <int>
 1  2000 Never married    26 White $8000 to 9999  Ind,near … Prot… Sout…      12
 2  2000 Divorced         48 White $8000 to 9999  Not str r… Prot… Bapt…      NA
 3  2000 Widowed          67 White Not applicable Independe… Prot… No d…       2
 4  2000 Never married    39 White Not applicable Ind,near … Orth… Not …       4
 5  2000 Divorced         25 White Not applicable Not str d… None  Not …       1
 6  2000 Married          25 White $20000 - 24999 Strong de… Prot… Sout…      NA
 7  2000 Never married    36 White $25000 or more Not str r… Chri… Not …       3
 8  2000 Divorced         44 White $7000 to 7999  Ind,near … Prot… Luth…      NA
 9  2000 Married          44 White $25000 or more Not str d… Prot… Other       0
10  2000 Married          47 White $25000 or more Strong re… Prot… Sout…       3
# ℹ 21,473 more rows
  • gss steht für General Social Survey, eine US-Langzeitstudie der unabhängigen Organisation NORC an der University of Chicago.

  • Weitere Informationen unter ?gss_cat.

  • Mit der Funktion count() kann man sich die Levels des Faktors anschauen:

gss_cat |>  count(race)
# A tibble: 3 × 2
  race      n
  <fct> <int>
1 Other  1959
2 Black  3129
3 White 16395
  • Alternativ mit einem Säulen- oder Balkendiagramm. Der Standard ist, dass Levels, die keine Werte haben nicht dargestellt werden. Es ist allerdings möglich sich diese trotzdem anzeigen zu lassen mit der Funktion scale_x_discrete(drop = FALSE).
gss_cat  |> 
  ggplot(aes(race)) +
  geom_bar()
gss_cat |> 
  ggplot(aes(race)) +
  geom_bar() +
  scale_x_discrete(drop = FALSE)
Abbildung 12.2: Levels, die nicht vorkommen werden nicht angezeigt.
Abbildung 12.3: Mit der Funktion scale_x_discrete() werden alle Levels angezeigt, auch wenn diese keine Ausprägungen haben.

12.2 Ordnung der Levels

  • Bei der Visualisierung ist es oft sinnvoll die Reihenfolge der Levels zu ändern, um die Daten besser deuten zu können. Die Graphik oben rechts ist mühsam.

  • Mit der Funktion fct_reorder() können die Faktoren geordnet werden. Im Beispiel wird das Merkmal f=relig nach dem arithmetischen Mittel von x=tvhours sortiert. Kommen Ausprägungen mehrfach vor, kann eine Funktion mit dem Argument fun= angegeben werden (z.B: mean oder sum, etc.) wie sortiert werden soll. Der Standard ist der Median.

relig_tv <- gss_cat |>
  group_by(relig) |>
  summarise(tvhours = mean(tvhours, na.rm = TRUE)  
  )
relig_tv |> 
  ggplot(aes(tvhours, relig)) + 
  geom_point()
Abbildung 12.4: Unsortiert
relig_tv |> 
  ggplot(aes(tvhours, fct_reorder(relig, tvhours))) +
  geom_point()
Abbildung 12.5: Sortiert nach der Größe der Werte. Die Funktion dazu ist fct_reorder().
  • Wenn die Transformationen komplizierter werden, ist es ratsam, diese aus der Funktion aes() herauszunehmen, und den Faktor in der Datentabelle zu transformieren.
relig_summary <- gss_cat  |> 
  group_by(relig) |>
  summarise(
    age = mean(age, na.rm = TRUE),
    tvhours = mean(tvhours, na.rm = TRUE),
    n = n()
  )

relig_tv |> 
  mutate(relig = fct_reorder(relig, tvhours)) |> 
  ggplot(aes(tvhours, relig)) +
  geom_point()

Dies liefert die gleiche Grafik wie Abbildung 12.5, allerdings ist die Syntax übersichtlicher.

  • Es ist nicht immer ratsam Faktoren umzuordnen, da sie oft bereits eine Ordnung haben. Allerdings kommt es vor, dass man einzelne Ausprägungen an den Anfang der Ordnung setzen möchte.

  • Dies geschieht mit der Funktion fct_relevel(). Das folgende Beispiel zeigt, wie die Funktion funktioniert: Der erste Eintrag ist der Faktor, der neu geordnet werden soll, dahinter steht eine beliebige Anzahl von Ausprägungen, die nach vorne geschrieben werden sollen.

rincome_age <- gss_cat |>
 group_by(rincome) |>
 summarise(age = mean(age, na.rm = TRUE))
rincome_age |> 
  ggplot(aes(age, fct_relevel(rincome, "Not applicable"))) +
  geom_point()
Abbildung 12.6: Der erste Eintrag der Funktion fct_relevel() ist der Faktor, der neu geordnet werden soll, dahinter steht eine beliebige Anzahl von Ausprägungen, die nach vorne geschrieben werden sollen.
  • Eine weitere Anwendung wäre die Färbung der Linien eines Plots zu ändern.

  • Die Funktion fct_reorder2() ordnet den Faktor nach den y-Werten, der mit den höchsten x-Werten assoziiert wird. Dies macht die Graphik einfacher zu lesen, da Graphik und Legende besser zusammenpassen.

by_age <- gss_cat |>
  filter(!is.na(age)) |>
  count(age, marital) |>
  group_by(age) |>
  mutate(prop = n / sum(n))
by_age |>  
  ggplot(aes(age, prop, colour = marital)) +
  geom_line(na.rm = TRUE)
Abbildung 12.7: Die Reihenfolge und Färbung ist unabhängig von den angenommenen Werten.
by_age |> 
  ggplot(aes(age, prop, colour = fct_reorder2(marital, age, prop))) +
  geom_line() +
  labs(colour = "marital")
Abbildung 12.8: Mit der Funktion fct_reorder2() kann die Reihenfolge geändert werden, so dass das resultierende Diagramm besser gelesen werden kann.
  • Für Säulen- oder Balkendiagramme gibt es die Funktionen fct_infreq() um die Levels in steigender Häufigkeit zu ordnen. Dazu benötigt man keine weiteren Argumente.

  • Die Funktion fct_rev() invertiert die Reihenfolge der Levels.

gss_cat |>
  mutate(marital = marital |> 
                   fct_infreq()) |>
  ggplot(aes(marital)) +
  geom_bar()
Abbildung 12.9: Manchmal ist es wünschenswert die Reihenfolge der Faktoren umzudrehen.
gss_cat |>
  mutate(marital = marital |> 
                   fct_infreq() |> 
                   fct_rev()) |>
  ggplot(aes(marital)) +
    geom_bar()
Abbildung 12.10: Mit der Funktion fct_rev() kann die Reihenfolge der Faktoren umgedreht werden.

12.3 Ändern der Levels

12.3.1 Levels Umbenennen

  • Neben dem Ändern der Ordnung ist eine Änderung der Levels selbst oft wünschenswert.
  • Mit der Funktion fct_recode() können Levels umbenannt werden.
  • Im folgenden Beispiel sind die Level schlecht und nicht konsistent:
gss_cat |> count(partyid)
# A tibble: 10 × 2
   partyid                n
   <fct>              <int>
 1 No answer            154
 2 Don't know             1
 3 Other party          393
 4 Strong republican   2314
 5 Not str republican  3032
 6 Ind,near rep        1791
 7 Independent         4119
 8 Ind,near dem        2499
 9 Not str democrat    3690
10 Strong democrat     3490
gss_cat |>
  mutate(partyid = fct_recode(partyid,
    "Republican, strong"    = "Strong republican",
    "Republican, weak"      = "Not str republican",
    "Democrat, weak"        = "Not str democrat",
    "Democrat, strong"      = "Strong democrat",
    "Independent, near rep" = "Ind,near rep",
    "Independent, near dem" = "Ind,near dem"
  )) |>
  count(partyid)
# A tibble: 10 × 2
   partyid                   n
   <fct>                 <int>
 1 No answer               154
 2 Don't know                1
 3 Other party             393
 4 Republican, strong     2314
 5 Republican, weak       3032
 6 Independent, near rep  1791
 7 Independent            4119
 8 Independent, near dem  2499
 9 Democrat, weak         3690
10 Democrat, strong       3490

12.3.2 Levels Zusammenfassen

Sollen Levels zusammengefasst werden gibt es mehrere Möglichkeiten dies zu tun:

  • Mit der Funktion fct_recode() kann ein neues Level für verschiedene alte Level angegeben werden.

  • Bei der Funktion fct_collapse() werden jeweils diverse alte Levels zu einem ‘kollabiert’ (siehe nächste Folie).

  • Bei der Funktion fct_lump(), werden die n= größten Levels behalten, wobei n= ein (optionales) Argument ist. Die restlichen Levels werden zusammengefasst und bekommen alle die Ausprägung other.

Wird das Argument n= nicht angegeben, so werden so viele Level wie möglich zusammengefasst, unter der Bedingung, dass die Ausprägung other immer noch diejenige ist, die am wenigsten häufig auftritt.

gss_cat |>
  mutate(partyid = fct_recode(partyid,
    "Republican"    = "Strong republican",
    "Republican"    = "Not str republican",
    "Independent"   = "Ind,near rep",
    "Independent"   = "Ind,near dem",
    "Democrat"      = "Not str democrat",
    "Democrat"      = "Strong democrat",
    "Other"         = "No answer",
    "Other"         = "Don't know",
    "Other"         = "Other party"
    )) |>
  count(partyid)
# A tibble: 4 × 2
  partyid         n
  <fct>       <int>
1 Other         548
2 Republican   5346
3 Independent  8409
4 Democrat     7180
# Beispiel fct_collapse():

gss_cat |>
  mutate(partyid = fct_collapse(partyid,
         other   = c("No answer", "Don't know", "Other party"),
         rep     = c("Strong republican", "Not str republican"),
         ind     = c("Ind,near rep", "Independent", "Ind,near dem"),
         dem     = c("Not str democrat", "Strong democrat")
  )) |>
  count(partyid)
# A tibble: 4 × 2
  partyid     n
  <fct>   <int>
1 other     548
2 rep      5346
3 ind      8409
4 dem      7180
# Beispiele für fct_lump():

gss_cat |>
  mutate(relig = fct_lump(relig)) |>
  count(relig, sort = TRUE) 
# A tibble: 2 × 2
  relig          n
  <fct>      <int>
1 Protestant 10846
2 Other      10637
gss_cat |>
  mutate(relig = fct_lump(relig, n = 5)) |>
  count(relig, sort = TRUE) 
# A tibble: 6 × 2
  relig          n
  <fct>      <int>
1 Protestant 10846
2 Catholic    5124
3 None        3523
4 Other        913
5 Christian    689
6 Jewish       388