11  Boole’sche Operationen

In R stehen die logischen Operationen als binäre Operatoren zur Verfügung, bzw. als Funktionen mit genau zwei Parametern. Diese logischen Operatoren sind vektorisiert. Es ist deshalb unnötig, logische Ausdrücke durch die Boole’sche Arithmetik zu ersetzen. Lediglich die Reihenfolge der Ausführung dieser Operatoren folgt der gleichen Regel wie die Arithmetik.

Merke
  • Das logische Und entspricht der Multiplikation.
  • Das logische Oder entspricht der Addition.

Daraus folgt, dass immer zuerst das logische Und und erst danach das logische Oder ausgewertet wird. Dieser Regel folgt auch R.

Die Tabelle 11.1 stellt die logischen Operationen und die verschiedenen Schreibweisen gegenüber.

Tabelle 11.1: Die wichtigsten logischen Operatoren und ihre Entsprechung in R
Operation neutrales Element Mathematisch R arithmetische Operation
Nicht - \lnot ! 1 - a
Und WAHR \land & a \cdot b
Oder FALSCH \lor | a + b
Exklusiv-Oder/Antivalenz - \oplus xor() (a - b)^2
Achtung

Es gibt neben den beiden Operatoren & und | auch die gedoppelte Varianten && und ||. Diese Varianten arbeiten auf den Binärwerten von Ganzzahlen und werden normalerweise nicht im Zusammenhang mit logischen Ausdrücken verwendet.

Das Logisches Nicht wird in R durch den Nicht-Operator (!) ausgedrückt. Dieser Operator wird auf jeden Wert eines Vektors einzeln angewandt.

Beispiel 11.1 (Logisches Nicht)  

logischer_vektor = c(TRUE, FALSE, FALSE, TRUE, TRUE)

! logischer_vektor 
[1] FALSE  TRUE  TRUE FALSE FALSE

R wandelt numerische Werte automatisch in Wahrheitswerte um, wenn sie mit logischen Operationen verwendet werden. Dabei gilt:

Beispiel 11.2  

! c(1, 2, 0, 4, 0) 
[1] FALSE FALSE  TRUE FALSE  TRUE

Wenn Sie in R zwei Vektoren mit dem Und- (&), dem Oder-Operator (|) oder der Antivalenz (xor()) verknüpfen, dann werden die Werte immer paarweise miteinander verglichen. Ein einzelner Vektor kann nicht an die Funktion des jeweiligen Operators übergeben werden.

Beispiel paarweise Verknüpfung

vektor_a = c(TRUE, FALSE, FALSE, TRUE, TRUE)
vektor_b = c(TRUE,  TRUE, FALSE, FALSE, TRUE)

vektor_a & vektor_b 
[1]  TRUE FALSE FALSE FALSE  TRUE

11.1 Logische Aggregationen mit reduce()

Merke

Um logische Vektoren in R zu aggregieren, muss der Vektor reduziert (engl. reduce) werden. Das Reduzieren ist eine besondere Aggregation über eine Reihe von Werten, bei der jeder Wert gemeinsam mit dem Ergebnis der Vorgängerwerte an eine Funktion übergeben wird.

Beispiel 11.3 (Aggregation logischer Vektoren)  

beispielWerte = c(TRUE, TRUE, FALSE, TRUE)

beispielWerte |> reduce(`&`)   
[1] FALSE
beispielWerte |> reduce(`|`)   
[1] TRUE
beispielWerte |> reduce(`xor`) 
[1] TRUE
Wichtig

Beim Reduzieren muss beachtet werden, dass eine Funktion und nicht den Operator übergeben wird. Deshalb muss der jeweilige logische Operator in Backticks (`) gesetzt und so als Funktionsbezeichner markiert werden.

11.2 Vergleiche

Neben den logischen Operationen sind Vergleiche ein wichtiges Konzept, das wir in logischen Ausdrücken regelmässig anwenden.

Es gibt genau sechs (6) Vergleichsoperatoren:

  • Gleich (==)
  • Ungleich (!=)
  • Grösser als (>)
  • Grösser gleich (>=)
  • Kleiner als (<)
  • Kleiner gleich (<=)
Warnung

Vergleiche erfordern, dass beide Werte vom gleichen Datentyp sind.

Die Vergleiche funktionieren für alle fundamentalen Datentypen.

Bei Zeichenketten wertet R die alphabetische Reihenfolge der Symbole vom Beginn einer Zeichenkette aus, um grösser oder kleiner Vergleiche durchzuführen.

11.2.1 Die Existenz eines Werts in einem Vektor überprüfen

Häufig müssen Sie überprüfen, ob ein Wert in einer Liste vorkommt. Grundsätzlich können Sie das mit komplizierten logischen Verknüpfungen in der Art von Beispiel 11.4 schreiben.

Beispiel 11.4 (Existstenzprüfung ohne %in%)  

meinWert = 3
wertVektor = c(8, 2, 3)

meinWert == wertVektor[1] | meinWert == wertVektor[2] | meinWert == wertVektor[3]
[1] TRUE

Einfacher ist aber ein sogenannter Existenztest. Dabei wird überprüft, ob ein Wert in einem Vektor vorkommt. Ein solcher Test lässt sich wie in Beispiel 11.5 schreiben:

Beispiel 11.5 (Existstenzprüfung mit %in%)  

meinWert = 3
wertVektor = c(8, 2, 3)

meinWert %in% wertVektor
[1] TRUE

Entsprechend der Definition des Existenzvergleichs \in funktioniert R’s %in%-Operator auch für Vektoren als linker Operand.

11.3 Fälle unterscheiden

11.3.1 Bedingte Operationen

R kennt die beiden Schlüsselworte if und else, um die Ausführung Operationsblöcken an Bedingungen zu knüpfen. Das Schlüsselwort if erwartet einen logischen Ausdruck, der genau einen Wahrheitswert zurückgibt. Logische Ausdrücke mit Vektoren sind damit nicht möglich.

Soll sowohl die Bedingung als auch die Alternative behandelt werden, dann muss das Schlüsselwort else in der gleichen Zeile stehen, wie das Ende des Blocks für die Bedingung.

Beispiel 11.6 (Ungültige Vektorbedingung mit if)  

werte = c(-1, 2, 0, 1)

if (werte > 1) {
    werte = werte - 1
} else {
    werte = 0
}
Error in if (werte > 1) {: Bedingung hat Länge > 1
Praxis

Bedingte Operationen sind in R nur selten notwendig. Die einzige relevandte Anwendung ist Datentypkontrolle für Parameter bevor die eigentliche Operation durchgeführt wird.

Beispiel 11.7 (Datentypprüfung mit if)  

if (!is.list(werte)) {
    stop("Variable enthält keine Liste")
}
Error in eval(expr, envir, enclos): Variable enthält keine Liste

11.3.2 Vektorisierte Unterscheidungen

Häufiger als Bedingungen kommen in R vektorisierte Unterscheidungen vor. Dafür stehen zwei Funktionen zur Verfügung:

  • ifelse()
  • case_when()

Die Funktion ifelse() hat drei Parameter und immer einen Vektor als Ergebnis. Die Parameter sind:

  1. Einen vektorisierten logischen Ausdruck.
  2. Eine Operation für den Fall, dass der logische Ausdruck Wahr (TRUE) ergibt.
  3. Eine Operation für den Fall, dass der logische Ausdruck Falsch (FALSE) ergibt.

Die Ergebnisse der beiden Operationen stehen im Ergebnisvektor an den Positionen, an denen der logische Ausdruck Wahr oder Falsch ergab.

Beispiel 11.8 (Vektorisierte Unterscheidung mit ifelse())  

ifelse(werte > 1, werte * 2, 0)
[1] 0 4 0 0

Die Funktion case_when() erlaubt es, mehrere miteinander verbundene vektorisierte Unterscheidungen in einer Operation durchzuführen. Dazu werden logische Ausdrücke mit Ergebnisoperationen bzw. -Werten verknüpft. Eine Ergebnisoperation wird dann ausgeführt, wenn der zugehörige logische Ausdruck Wahr (TRUE) ergibt. Die logischen Ausdrücke werden in der angegebenen Reihenfolge geprüft, wobei die Operation abbricht, sobald ein logischer Ausdruck Wahr ergibt.

Beispiel 11.9 (case_when() über einen Vektor)  

case_when(
    werte > 0 ~ "positiv",
    werte == 0 ~ "null",
    werte < 0 ~ "negativ" 
)
[1] "negativ" "positiv" "null"    "positiv"

Für den Fall, dass für einen Wert kein logischer Ausdruck Wahr ergibt, kann ein Rückfallergebnis angegeben werden. Dieses Rückfallergebnis muss mit .default = eingeleitet werden.

Beispiel 11.10 (case_when() mit Rückfallergebnis)  

case_when(
    werte > 0 ~ "positiv",
    werte < 0 ~ "negativ",
    .default = "null"
)
[1] "negativ" "positiv" "null"    "positiv"

11.4 Filtern

Das Filtern von Werten in Vektoren und Stichproben ist ein zentrales Element von R. Dafür stehen viele Funktionen bereit. Es ist auch möglich über die Index-Operatoren zu filtern.

In der Praxis wird meistens die Funktion filter() zum Auswählen von Datensätzen verwendet. Diese Funktion ermöglicht es, einen Datenrahmen mittels eines logischen Ausdrucks einzuschränken. Die Funktion filter() hat zwei Parameter:

  1. Den Datenrahmen und
  2. Den logischen Ausdruck für die Auswahl der Datensätze.

Das Ergebnis ist ein Datenrahmen, der nur Datensätze enthält, für die der logische Ausdruck Wahr (TRUE) ergibt.

Beispiel 11.11 (Filtern)  

A B C
1 Name Sprache Einwohner:innen
2 Basel deutsch 173863
3 Genf  französisch 203856
4 Lugano italienisch 62315
5 Zug deutsch  30934
6 Zürich deutsch  421878

Für diese Stichprobe möchten wir wissen, wie viele Einwohner in Städten mit mehr als 100000 Einwohnenden leben?

Diese Frage beantworten wir mit der folgenden Logik:

  1. Alle Städte mit mehr als 100000 Einwohner:innen filtern.
  2. Die Einwohner:innen der gefilterten Städte zusammenzählen.

Der logische Ausdruck zum Filtern ist `Einwohner:innen` > 100000, weil dieser Ausdruck nur für die Datensätze Wahr wird, wenn im Vektor Einwohner:innen der Wert grösser als 100000 ist. Nach dem Filtern im ersten Schritt liegt nur noch die folgende Stichprobe vor:

A B C
1 Name Sprache Einwohner:innen
2 Basel deutsch 173863
3 Genf  französisch 203856
6 Zürich deutsch  421878

Für diese Teilstichprobe muss im zweiten Schritt nur noch die Summe über den Vektor Einwohner:innen gebildet werden.

Daraus ergibt sich die folgende Funktionskette:

1read_delim("daten/einwohnende.psv",
           delim = "|",
           trim_ws = T,
           show_col_types = F) |>
2    filter(`Einwohner:innen` > 100000) |>
    summarise(
        Gesamteinwohnende = sum(`Einwohner:innen`)
    )
1
read_delim() muss verwendet werden, weil ein besonderes Trennzeichen benutzt wird.
2
Filter operation.
# A tibble: 1 × 1
  Gesamteinwohnende
              <dbl>
1            799597

11.4.1 NA-Werte filtern

Die Funktion drop_na() ist eine spezielle Filterfunktion, deren Ergebnis nur Datensätze enthält, in denen bei keinem Vektor den Wert NA vorkommt. Die Funktion hat keinen Effekt, wenn ein Datenrahmen nur gültige Werte enthält (Beispiel 11.12).

Beispiel 11.12 (drop_na() ohne Effekt)  

11.5 Selektieren

Praxis

Die tidyverse Bibliothek umfasst die tidyselect-Funktionen. Dabei handelt es sich um eine Reihe von Hilfsfunktionen, die die Vektorenauswahl nachvollziehbarer macht. Auf der tidyselect-Homepage finden sich ausführliche Code-Beispiele.

In R können Vektoren mit der Funktion select() selektiert werden. Dieser Funktion werden Regeln übergeben, nach denen die Vektoren auswählt werden sollen. Die einfachste Regel ist die direkte Eingabe der Vektorennamen. Ein typischer Anwendungsfall ist die Datenbereinigung, damit die Funktion drop_na() nicht zu viele Datensätze löscht. Diese Situation kommt vor, wenn ein Datenrahmen viele fehlende Werte enthält, die ungleichmässig in den Vektoren vorkommen. Die Analyse muss deshalb auf die gewünschten Vektoren beschränkt werden.

Für die folgenden Beispiele verwenden wir die Daten der Befragung zum digitalen Umfeld, die mit der read_csv()-Funktion eingelesen wird.

stichprobe = read_csv("daten/befragung_digitales_umfeld/deviceuse.csv") 
Rows: 76 Columns: 4
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (4): q00_demo_gen, q00_demo_studyload, q01_mob_typ, q12_fav_apps

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

11.5.1 Vektoren direkt selektieren

Wir wollen die Vektoren q00_demo_gen (Gender), q00_demo_studyload (Studienmodell) und q01_mob_typ (Mobile OS des Smartphones) auswählen.

Beispiel 11.13 (Direktes selektieren)  

stichprobe |>
    select(q00_demo_gen, q00_demo_studyload, q01_mob_typ) |>
    head()
# A tibble: 6 × 3
  q00_demo_gen q00_demo_studyload q01_mob_typ       
  <chr>        <chr>              <chr>             
1 Weiblich     Vollzeit           iPhone            
2 Weiblich     Vollzeit           iPhone            
3 Weiblich     Teilzeit           iPhone            
4 Weiblich     Teilzeit           iPhone            
5 Männlich     <NA>               Android Smartphone
6 Weiblich     Vollzeit           iPhone            

Durch diesen Aufruf von select() wird der Datenrahmen auf die drei ausgewählten Vektoren reduziert.

11.5.2 Alle ausser die benannten Vektoren selektieren

Vektoren direkt zu benennen ist eine einfache direkte Methode. Wenn man sehr viele Vektoren auswählen möchte, dann ist es manchmal einfacher, nur die Vektoren anzugeben, die nicht in der Ergebnisstichprobe enthalten sein sollen. Mit select() erreichen wir das, indem wir ein - den ungewollten Vektoren voranstellen.

Das folgende Beispiel selektiert alle Vektoren ausser q00_demo_gen aus der Stichprobe.

Beispiel 11.14 (Selektieren durch Ausschliessen)  

stichprobe |>
    select(-q00_demo_gen) |>
    head()

Wenn mehrere Vektoren ausgeschlossen werden sollen, dann müssen diese zu einem Vektor zusammengefasst werden.

Beispiel 11.15 (Selektieren durch mehrfaches Ausschliessen)  

stichprobe |>
    select(- c(q00_demo_gen, q00_demo_studyload)) |>
    head()

Diese Vektorenauswahl wählt alle Vektoren ausser das Geschlecht und das Studienmodell.

11.5.3 Vektoren mit ähnlichen Namen auswählen

Drei leistungsfähige Hilfsfunktionen für select() sind:

  • starts_with(),
  • ends_with() sowie
  • contains()

Diesen Funktionen akzeptieren einen Teilnamen, über den mehrere Vektoren ausgewählt werden, in denen der angegebene Teil im Vektornamen vorkommt.

Diese Funktionen lassen sich mittels der iris-Stichprobe veranschaulichen.

Beispiel 11.16 (Selektieren mit starts_with())  

iris |>
    select(starts_with("Sepal")) |>  # wählt die Vektoren Sepal.Width und Sepal.Length aus
    head()
  Sepal.Length Sepal.Width
1          5.1         3.5
2          4.9         3.0
3          4.7         3.2
4          4.6         3.1
5          5.0         3.6
6          5.4         3.9

Beispiel 11.17 (Selektieren mit ends_with())  

iris |>
    select(ends_with("Length")) |> # wählt die Vektoren Petal.Length und Sepal.Length aus
    head()
  Sepal.Length Petal.Length
1          5.1          1.4
2          4.9          1.4
3          4.7          1.3
4          4.6          1.5
5          5.0          1.4
6          5.4          1.7

11.5.4 Alle Vektoren zwischen zwei benannten Vektoren auswählen

Eine weitere Möglichkeit schneller viele Vektoren auszuwählen ist der :-Operator. Damit können wir alle Vektoren zwischen zwei Vektoren inklusive der benannten Vektoren auswählen.

Der folgende Aufruf veranschaulicht dies:

Beispiel 11.18 (Vektorenbereich selektieren)  

stichprobe |>
    select(q00_demo_gen:q01_mob_typ) |>
    head()
# A tibble: 6 × 3
  q00_demo_gen q00_demo_studyload q01_mob_typ       
  <chr>        <chr>              <chr>             
1 Weiblich     Vollzeit           iPhone            
2 Weiblich     Vollzeit           iPhone            
3 Weiblich     Teilzeit           iPhone            
4 Weiblich     Teilzeit           iPhone            
5 Männlich     <NA>               Android Smartphone
6 Weiblich     Vollzeit           iPhone            

Diese Vektorenauswahl wählt die Vektoren q00_demo_gen, q00_demo_studyload und q01_mob_typ für das Ergebnis aus.

Warnung

Die Reihenfolge von Vektoren kann durch andere Transformationen geändert werden. Deshalb sollte das Selektieren mit Vektorbereichen vermieden werden.

11.6 Sortieren

R erlaubt kein Sortieren über logische Ausdrücke. Es ist nur möglich, Werte nach vorgegebenen grösser-kleiner Beziehungen zu sortieren. Deshalb muss für komplexe Sortierungen ein numerischer Hilfsvektor erzeugt werden, der anschliessend sortiert werden kann.

Die Werte eines Vektors werden mit sort() sortiert.

Beispiel 11.19 (Vektorsortierung für zufällige Ganzzahlen)  

set.seed(10)
runif(10, min = 1, max = 10) |> trunc() |> sort()
 [1] 1 3 3 3 3 4 4 5 6 7

Die sort()-Funktion kann nur einzelne Vektoren sortieren. Das ist unpraktisch, wenn Daten in einem Datenrahmen vorliegen. In diesem Fall lassen sich die Datensätze mithilfe der Funktion arrange() sortieren. Der Funktion werden die Vektoren übergeben, über die eine neue Reihenfolge festgelegt werden soll. Standardmässig sortiert arrange() aufsteigend. Mit der Hilfsfunktion desc() (engl. descending = absteigen) werden Datensätze entsprechend der Werte im Sortiervektor absteigend sortiert.

Beispiel 11.20 (Absteigende Datenrahmensortierung mit arrange())  

mtcars |>
    as_tibble(rownames = "model") |> 
    select(model, hp, disp, mpg, am) |> 
    arrange(desc(hp)) |> 
    head()
# A tibble: 6 × 5
  model                  hp  disp   mpg    am
  <chr>               <dbl> <dbl> <dbl> <dbl>
1 Maserati Bora         335   301  15       1
2 Ford Pantera L        264   351  15.8     1
3 Duster 360            245   360  14.3     0
4 Camaro Z28            245   350  13.3     0
5 Chrysler Imperial     230   440  14.7     0
6 Lincoln Continental   215   460  10.4     0