Statistik mit R und RStudio

Ein Nachschlagewerk für Gesundheitsberufe

Jörg große Schlarmann

2024-05-07

Zitationsvorschlag

Willkommen zu diesem freien Lehrbuch.

Dieses Buch widmet sich an Studierende und Beschäftigte im Gesundheitswesen und soll als Nachschlagewerk für statistische Fragestellungen bezüglich der freien Software R dienen. Es ist nicht dafür geeignet, Einsteigern Statistik zu erklären, sondern es richtet sich an jene, die ihre statistischen Auswertungen mit R fahren möchten. Das heisst, Sie müssen schon wissen was Sie eigentlich tun wollen. Dieses Buch möchte Ihnen zeigen, wie Sie Ihre Vorhaben in R umsetzen können.

Das Nachschlagewerk wird - im Sinne eines rolling releases - ständig aktualisiert und erweitert.

Eine aktuelle epub-Version finden Sie unter: https://www.produnis.de/R/rbuch.epub

Eine aktuelle PDF-Version finden Sie unter: https://www.produnis.de/R/rbuch.pdf

Kritik und Diskussion sind per Mastodon möglich: https://mastodon.social/@rbuch

große Schlarmann, J (2024): “Statistik mit R und RStudio - Ein Nachschlagewerk für Gesundheitsberufe”, Hochschule Niederrhein, Krefeld, https://www.produnis.de/R/

@book{grSchlR,
  author = {{große Schlarmann}, Jörg},
  title = {Statistik mit R und RStudio - Ein Nachschlagewerk für Gesundheitsberufe},
  publisher = {Hochschule Niederrhein},
  address = {Krefeld},
  url = {https://www.produnis.de/R},
  copyright = {CC BY-NC-SA 4.0},
  language = {de},
  year = {2024},
}

Lizenz

Das Logo der Hochschule Niederrhein sowie die Logos von R, RStudio und quarto sind Eigentum der jeweiligen Rechteinhaber. Für alles weitere gelten die folgenden Bedingungen.

Dieses Werk ist unter der Creative Commons Lizenz CC BY-NC-SA 4.0 verfügbar, siehe https://creativecommons.org/licenses/by-nc-sa/4.0/deed.de

Sie dürfen

das Material in jedwedem Format oder Medium vervielfältigen und weiterverbreiten.

das Material remixen, verändern und darauf aufbauen.

Unter folgenden Bedingungen

Namensnennung: Sie müssen angemessene Urheber- und Rechteangaben machen, einen Link zur Lizenz beifügen und angeben, ob Änderungen vorgenommen wurden. Diese Angaben dürfen in jeder angemessenen Art und Weise gemacht werden, allerdings nicht so, dass der Eindruck entsteht, der Lizenzgeber unterstütze gerade Sie oder Ihre Nutzung besonders.

Nicht kommerziell: Sie dürfen das Material nicht für kommerzielle Zwecke nutzen.

Weitergabe unter gleichen Bedingungen: Wenn Sie das Material remixen, verändern oder anderweitig direkt darauf aufbauen, dürfen Sie Ihre Beiträge nur unter derselben Lizenz wie das Original verbreiten.

Keine weiteren Einschränkungen: Sie dürfen keine zusätzlichen Klauseln oder technische Verfahren einsetzen, die anderen rechtlich irgendetwas untersagen, was die Lizenz erlaubt.

Einleitung

Willkommen zu diesem Nachschlagewerk.

Dieses Buch widmet sich an Studierende und Beschäftigte im Gesundheitswesen und soll als Nachschlagewerk für statistische Fragestellungen bezüglich der freien Software R dienen. Es ist nicht dafür geeignet, Einsteigern Statistik zu erklären1, sondern es richtet sich an jene, die ihre statistischen Auswertungen mit R fahren möchten. Das heisst, Sie müssen schon wissen was Sie eigentlich tun wollen. Dieses Buch möchte Ihnen zeigen, wie Sie Ihre Vorhaben in R umsetzen können.

Dieses Buch ist in drei Teile gegliedert:

Im ersten Teil (ab Kapitel 2 ) geben wir eine kurze Einführung ins klassische R und RStudio. Anschließend behandeln wir den modernen R-Dialekt Tidyverse.

Im zweiten Teil (ab Kapitel 32 ) betreiben wir Statistik mit R.

Der dritte Teil (ab Kapitel 36) zeigt konkrete Anwedungsbeispiele. Hierbei folgen wir der Ordnung “Was willst du machen?” und zeigen an “echten” Fragen, wie wir das erlernte R-KungFu in der Praxis anwenden können.

1. hierzu empfehle ich die Bücher von Kuckartz et al. (2013), Müller (2011), Koller (2017) oder das CleoScript.

1 Installation von R und RStudio

R ist eine quellfreie Software und kann daher weltweit kostenfrei auf vielen Betriebssystemen, z.B. Windows, Apple und Linux, installiert werden. Gehen Sie hierzu auf die Webseite des Projektes unter https://r-project.org und laden sich das passende Installationspaket für Ihr Betriebssystemen herunter und führen Sie die Installation durch.

Für Windows lautet der Link auf das Installationspaket
https://cran.r-project.org/bin/windows/base/

Für MacOSX lautet der Link
https://cran.r-project.org/bin/macosx/

Bei fast allen Linuxdistributionen liegt R im Repository, so dass es z.B. unter Ubuntu mit apt-get install r-base installiert werden kann.

Von RStudio steht ebenfalls eine quelloffene (sowie eine kommerzielle) Version für diese Systeme zur Verfügung. Gehen Sie auch hier auf die Projektseite https://rstudio.com/products/rstudio/download/ und laden sich die kostenfreie Variante für Ihr Betriebssystemen herunter. Sie können RStudio erst installieren, wenn R bereits installiert ist.

Falls Sie nicht wissen, wie sich Software auf Ihrem PC installieren lässt, empfehlen wir dieses Youtube-Video:

https://youtu.be/X_Mxya2Fis0

Es zeigt den gesamten Installtionsprozess von R und RStudio am Beispiel “Windows” (weitere Youtube-Videos sammeln wir in Abschnitt 30.2).

2 Kurze Einführung in R und RStudio

2.1 R

R ist eine Programmiersprache für Statistik (R Core Team, 2023). Mit ihr lassen sich statistische Berechnung durchführen und graphisch darstellen. Die klassische R-Software läuft in einem Terminalfenster (Abbildung 2.1) über welches die Befehle abgesetzt werden.

Abb. 2.1: R Konsole

Dies kann am Anfang abschreckend wirken, da wir es eher gewöhnt sind, Programme über eine graphische Nutzeroberfläche zu steuern.

Seit 2012 wird dafür RStudio entwickelt, welches eine funktionsreiche Entwicklerumgebung für R bereitstellt. Mittlerweile hat sich RStudio als Open Source Standardanwendung für statistische Analysen durchgesetzt.

Sie werden nicht drum herumkommen, Ihre Befehle weiterhin aufzuschreiben und auszuführen, aber RStudio bietet Ihnen eine sehr hilfreiche und komfortable Arbeitsumgebung, die viele Arbeitsschritte erleichtern wird.

Auch in R hat sich einiges getan, vor allem in den letzten Jahren. Mit der Paketesammlung Tidyverse (siehe Kapitel 25) und dessen strenger Umsetzung der Tidy Data sowie kosequenter Nutzung der Pipe (siehe Abschnitt 25.2) hat sich ein eigenständiger Dialekt entwickelt, der immer mehr Anhänger findet. Die grundlegenden Schritte der Datenmanipulation und graphischen Darstellung sind im Tidyverse viel einfacher als mit klassischen R-Hausmitteln. Die Funktionalität der Pipe wurde mit Version 4.1 ins klassische R übernommen (siehe Kapitel 11).

In diesem Buch lernen Sie zuerst das “klassische” R, denn die grundlegenden Konzepte sind immer noch gültig, und auch die Funktionen für statistische Analysen sind immer noch die selben. Die Abschnitte zum Umgang mit Datensätzen sowie zur plot()-Funktion sind aber nur rudimentär gehalten, da dies mit den modernen Paketen dplyr und ggplot2 wesentlich einfacher funktioniert.

2.2 RStudio

Die Nutzeroberfläche von RStudio besteht aus vier Bereichen (siehe Abbildung 2.2), die nach ihrer hauptsächlichen Funktion benannte sind. Jeder Bereich bietet aber über seine Reiter weitere Anwendungsmöglichkeiten (die Sie jetzt evtl. noch nicht verstehen).

Abb. 2.2: RStudio Fenster

Oben links befindet sich das “Scriptfenster”. In diesem Bereich werden Ihre Scriptdateien angezeigt und Sie können sie dort editieren. In den Scriptdateien schreiben Sie Ihre R-Befehle nacheinander auf, je nachdem, welche Auswertungen oder Datentransformationen Sie fahren, oder welche Diagramme Sie plotten möchten (siehe Kaptiel Statistik mit R). Ihr Code wird dabei von RStudio farblich aufgehübscht, was ihn wesentlich übersichtlicher macht.

Rechts daneben ist das “Datenfenster”. Hier werden alle Variablen und Datenframes angezeigt, die derzeit im Speicher geladen sind. Durch Doppelklick auf einen Datensatz werden die Daten im “Scriptfenster” angezeigt. Über den Reiter History können Sie alle Befehle, die Sie in der R-Konsole abgesetzt haben, einsehen. Über den Reiter Tutorials haben Sie Zugriff auf zahlreiche Anleitungsangebote zu RStudio.

Unten links befindet sich die R-Konsole. Dieser Bereich entspricht der “normalen” R-Installation. Hier können Sie von Hand Befehle eingeben, oder aber Befehle aus dem Scriptfenster ausführen lassen. Die Konsole unterscheidet farblich zwischen Eingabe und Ausgabe, was sehr zur Übersicht beiträgt. Der Reiter Terminal öffnet eine Systemkonsole (unter Linux sehr hilfreich), und unter Jobs werden z.B. Installationsprozesse von Zusatzpaketen dargestellt.

Unten rechts ist das “Ausgabefenster”. Hier werden unter anderem Plots und Hilfetexte angezeigt. Das Fenster verfügt zudem über einen Dateibrowser (Reiter Files), von dem aus Sie bequem Dateien oder Datensätze aus Ihrem Arbeitsvereichnis laden können. Über den Reiter Packages können Sie Zusatzpakete aktivieren und deaktivieren.

Für die ersten Schritte in RStudio empfehlen wir folgendes Youtube-Video:

https://youtu.be/tyvEHQszZJs

2.2.1 RStudio anpassen

RStudio lässt eine Menge an Anpassungen zu, so dass Sie die Software möglichst gut an Ihren Workflow anpassen können. Folgende zwei Einstellungen empfehlen wir nach der Installation vorzunehmen.

Arbeitsverzeichnis setzen: Unter Tools →\rightarrow Global Options gelangen Sie ins allgemeine Einstellungsmenü. Unter dem Reiter General →\rightarrow Basic lässt sich das Arbeitsverzeichnis (working directory) festlegen (Abbildung 2.3). In dieses Verzeichnis legt standardmäßig alle Diagramme, Scripte sowie den aktuellen Workspace (also alle Variablen und Datensätze, die derzeit geladen sind).

Abb. 2.3: Arbeitsverzeichnis

Es bietet sich an, ein eigenes Verzeichnis für zu erstellen und hier in RStudio auszuwählen.

Tastaturkürzel: Unter Tools →\rightarrow Modify Keayboard Shortcuts öffnet sich das Dialogfenster zum Festlegen der Tastaturkürzel. Hier geben wir im Feld Filter “exec” ein (Abbildung 2.4).

Abb. 2.4: Tastaturkürzel

Bei der Aktion Execute Current Line setzen wir das Tastaturkürzel [STRG]+[R] (Abbildung 2.5). Dies ist ziemlich hilfreich.

Abb. 2.5: Tastaturkürzel

Die Einstellung bewirkt, dass im Scriptfenster die aktuelle Zeile per [STRG]+[R] ausgeführt wird.

An dieser Stelle sei darauf hingewiesen, dass das Aussehen von RStudio angepasst werden kann. Vielleicht möchten Sie auf einen “augenfreundlicheren” Modus umschalten, in welchem der Hintergrund dunkler ist. Klicken Sie hierfür in der Menüleiste auf Tools →\rightarrow Global Options klicken. Wählen Sie hier den Bereich Appearance aus (Abbildung 2.6).

Abb. 2.6: heme wählen

Es werden Ihnen zahlreiche “Themes” zur Auswahl gestellt. Probieren Sie alle einmal aus. Als dunkles Theme verwende ich persönlich gerne Dracula (Abbildung 2.7).

Abb. 2.7: Theme Dracula

2.2.2 Eine Sitzung starten

Starten Sie RStudio. Es begrüßt Sie die RStudio Arbeitsfläche. Das Scriptfenster oben links ist noch geschlossen, so dass der gesamte linke Bereich von der R-Konsole eingenommen wird (Abb. Abbildung 2.8).

Abb. 2.8: Neue Sitzung gestartet

Als erstes legen Sie eine neue Scriptdatei an. Klicken Sie oben links auf das grüne + Zeichen und wählen Sie R Script (Abbildung 2.9).

Abb. 2.9: neue Scriptdatei anlegen

Wie Abbildung 2.10 zeigt, können Sie auch die Tastenkombination [STRG] + [SHIFT] + [N] verwenden, um eine neue Scriptdatei zu erzeugen.

Abb. 2.10: Tastenkombination

In dieser Scriptdatei werden wir alle Befehle hinterlegen. Für gewöhnlich beginnt man bei Programmierübungen mit einem “Hallo Welt” Beispiel. Schreiben Sie in die Scriptdatei den Befehl:

print("Hallo Welt")

Abb. 2.11: Befehl in Scripdatei schreiben

Diesen Befehl wollen wir nun in der R-Konsole ausführen. Das kann auf mehrere Arten geschehen:

gehen Sie mit dem Cursor in die Zeile des Befehls. Drücken Sie die (vorhin angelegte) Tastenkombination [STRG] + [R].

oder gehen Sie mit dem Cursor in die Zeile des Befehls und klicken Sie auf den →\rightarrowRun-Knopf (siehe Abbildung 2.12).

oder gehen Sie mit dem Cursor in die Zeile des Befehls und drücken Sie die (voreingestellte) Tastenkombination [STRG] + [ENTER].

Abb. 2.12: run-Knopf

Alle drei Aktionen bewirken, dass der Befehl print("Hallo Welt") in der R-Konsole unten links ausgeführt wird (siehe Abbildung 2.13).

Abb. 2.13: Ausgabe in der Konsole unten links

Und R antwortet entsprechend in der Konsole mit

[1] "Hallo Welt"

Sie können die Befehle auch jederzeit direkt unten in die R-Konsole eingeben und ausführen.

Prinzipiell sollten Sie aber alles in der Scriptdatei aufschreiben und von dort ausführen. So können Sie leicht Ihre Befehle anpassen oder auch komplizierte R-Sprüche nach und nach aufbauen. Manchmal kann es aber auch nützlich sein, ein kurzes ?print (dazu später mehr) direkt unten in der Konsole einzugeben. Sie werden “den Dreh” sehr schnell heraus haben.

Wir sollten so häufig wie möglich unsere Scriptdatei abspeichern. Hierzu drücken Sie die Tastenkombination [STRG] + [S], oder Sie klicken auf das Diskettensymbol (siehe Abbildung 2.14).

Abb. 2.14: Scriptdatei speichern

Derzeit heisst unsere Scriptdatei noch Untitled1. Geben Sie Ihrer Scriptdatei einen schönen Dateinamen mit der Dateiendung .R, also z.B. ErsteSchritte.R
Der neue Dateiname wird nach dem Speichern über dem Script angezeigt (Abbildung 2.15).

Abb. 2.15: Dateiname

Mit dem #-Zeichen können Zeilen auskommentiert werden. Das bedeutet, dass alles, was hinter einem # steht, von R ignoriert wird (siehe Abbildung 2.16).

Abb. 2.16: Auskommentieren mit #

RStudio hebt auskommentierte Stelle farblich hervor (Abbildung 2.16), alles Auskommentierte ist hellgrün dargestellt), so dass sie leicht erkannt werden können.

Das Auskommentieren mit # ist hilfreich, um den Code zu kommentieren, also zu beschreiben, was die nachfolgenden Zeilen machen sollen. So bleibt Ihr Code verständlich und wird auch für andere nachvollziehbar. Zum anderen kann es hilfreich sein, einzelne Befehlszeilen mittels # zu “dekativieren”, z.B. wenn man auf Fehlersuche ist.

In Abbildung 2.16 sehen Sie auch, dass der Dateiname der Scriptdatei ErsteSchritte.R* in rot dargestellt ist und mit einem * endet. Auf diese Weise teilt RStudio Ihnen mit, dass es ungespeicherte Änderungen an der Scriptdatei gibt, die Sie evtl. speichern möchten. Das verwundert nicht, denn wir haben ja gerade jede Menge Kommentare in unser Script geschrieben. Mit der Tastenkombination [STRG] + [S] speichern wir die Scriptdatei. Wie Abbildung 2.17 zeigt, ist der Dateiname nun nicht mehr rot und hat auch kein * mehr.

Abb. 2.17: alles ist gespeichert

2.2.3 Projekte in RStudio

RStudio erlaubt es Ihnen, so genannte “Projekte” zu erstellen.

Ein neues Projekt startet mit einer leeren R-Konsole und einem (fast) leeren Arbeitsverzeichnis. Sie können wie gewohnt Daten laden, bearbeiten und Ihre Auswertungen fahren. Wenn Sie RStudio schließen, wird Ihr Arbeitsstand im Projektordner gespeichert. Beim nächsten Öffnen steht dann alles wieder so zur Verfügung, wie Sie es verlassen haben.

Es empfiehlt sich sehr, für jedes “Projekt”, das Sie mit R durchführen möchten, ein eigenes RStudio-Projekt zu erstellen. Dies erleichtert die Arbeit ungemein. Da jedes Projekt in seinem eigenen Dateiordner “lebt”, behalten Sie die Übersicht über Daten, Dateien und Diagramme. Sie können zwischen den Projekten wechseln, ohne dass Ihre Arbeitsspeicher durcheinandergeraten, oder Variablen überschrieben werden. Sofern Sie konsequent alle Dateien in den jeweiligen Ordnern gespeichert lassen, können Sie sich lästige Dateipfadangaben sparen, und Sie können den Projektordner einfach mit Kolleginnen und Kollegen teilen (indem Sie ihn kopieren), und in deren RStudio wird alles so funktionieren wie bei Ihnen.

Ich persönlich habe in meinem Dokumenteordner einen Unterordner R, in welchem sich alle Projekte befinden. Es ist aber natürlich auch möglich, Ihre einzelnen Projektordner an beliebigen Stellen des Dateisystems zu platzieren. Projektordner können auch “im Nachhinein” beliebig verschoben werden.

Legen Sie nun ein neues Projekt in RStudio an. Klicken Sie in der Menüleiste auf File →\rightarrow New Project....

Es öffnet sich ein neus Fenster, in welchem Sie gefragt werden, ob Sie einen neuen Projektordner im Dateisystem anlegen möchten, oder ob Sie einen bestehenden Ordner nutzen möchten. Da Sie (wahrscheilich) noch keine R-Projekte durgeführt haben, klicken Sie auf New Directory (Abbildung 2.18).

Abb. 2.18: Neues Projekt in neuem Verzeichnis

Abb. 2.19: neues Projekt

In RStudio können Sie viel machen, deshalb ist die Auswahlliste des Project Type so lang. Wählen Sie den ersten Eintrag New Project aus. Geben Sie dem Projekt einen Namen. In Abbildung 2.20 haben ich “ErsteSchritte” genannt. Darunter wird der Dateipfad angegeben. Ignorieren Sie die weitern Häkchenoptionen und klicken Sie unten auf den Create Project-Knopf.

Abb. 2.20: neues Projekt

Es begrüßt Sie ein “frisches” RStudio-Fenster (Abbildung 2.21).

Abb. 2.21: neu erstelltes Projekt

Wiederholen Sie nun die Schritte aus dem “Hallo Welt”-Beispiel. Das heisst, legen Sie eine neue Scriptdatei an, indem Sie auf das grüne + klicken (siehe Mauszeiger in Abbildung 2.21), und schreiben Sie den Befehl hinein:

print("Hallo Projekt-Welt")

Führen Sie den Befehl mittel Run-Knopf oder der Tastenkombination [STRG]+[R] aus, und speichern Sie Ihre Scriptdatei anschließend unter dem Namen
ErsteSchritte.R ab, indem Sie auf das Diskettensymbol klicken oder [STRG]+[S] drücken.

Abb. 2.22: Neues Projekt mit Scriptdatei

Wie Sie sehen, wird Ihre Scriptdatei nun im Ausgabefenster angezeigt (Abbildung 2.22). Daneben sehen Sie die Projektdatei ErsteSchritte.Rproj. Wenn Sie aus Ihrem Dateibrowser auf diese Datei klicken, öffnet sich automatisch RStudio und es wird der Arbeitsstand des Projektes geladen.

Rechts über dem Datenfenster wird Ihr derzeit aktives Projekt angezeigt (in Abbildung 2.23) ErsteSchritte).

Abb. 2.23: Projekte verwalten

Wenn Sie hier klicken, können Sie neue Projekte anlegen, und ihre zuletzt geöffneten Projekte werden Ihnen angezeigt. So können Sie bequem zwischen Ihren Projekten wechseln (Abbildung 2.24).

Abb. 2.24: letzte Projekte

3 Grundlegende Befehle

Zu jedem R-Befehl lässt sich eine Hilfeseite anzeigen. Diese gibt grundlegende Informationen über Funktionsweisen des Befehls sowie die zugelassenen Befehlsoptionen. Die meisten Hilfeseiten enden mit ein paar Beispielen. Die Hilfeseiten erreicht man mit dem Befehl help() oder kurz ?, wobei der Befehl, dessen Hilfsseite wir aufrufen möchten, innerhalb der Befehlsklammern stehen muss.

# gib Hilfeseite für Funktion "mean" aus
help(mean)

# oder einfach
?mean

Wenn Sie den Befehl help(mean) z.B. mit [STRG] + [R] ausführen, oder direkt unten in die R-Konsole eingeben, zeigt RStudio die Hilfeseiten im unteren rechten Viertel an (siehe Abbildung 3.1).

Abb. 3.1: Hilfeseite wird unten rechts angezeigt

In diesem Buch werden die R-Befehle nie vollständig mit all ihren Optionen und Einstellungsmöglichkeiten vorgestellt. Daher bietet es sich an, bei jedem “neuen” Befehl, den man lernt oder verwendet, per help() oder ? die Anleitungsseite zumindest zu überfliegen.

# bei help() steht die Funktion in Klammern
# gib Hilfeseite für Funktion "c()" aus
help(c)

# Bei ? wird sie ohne Klammern direkt drangeschrieben
?c
?mean
?median

Mit quit() oder kurz q() wird die R-Sitzung beendet.

# Beende die R-Sitzung
quit()

# Beende die R-Sitzung
q()

# Rufe Hilfeseite auf
?quit

Wenn Sie den Befehl quit() in der R-Konsole von RStudio ausführen, wird auch RStudio beendet.

4 Rechnen mit R

R funktioniert unter anderem wie ein mächtiger Taschenrechner. Folgende grundlegende mathematische Operationen können mit R ausgeführt werden:

Operation | Symbol
Addition | +
Subtraktion | -
Multiplikation | *
Division | /
Potenzieren | ^ oder **
Wurzel | sqrt(x)

Hier ein paar Beispiele:

# Addition
2+8

## [1] 10

# Subtraktion
9-7

## [1] 2

# Multiplikation
4*4

## [1] 16

# Division
6/2

## [1] 3

# Potenzieren
2^3

## [1] 8

# Exponent anders notiert
2**3

## [1] 8

# Quadratwurzel
sqrt(25)

## [1] 5

Vielleicht haben Sie sich schon gefragt, wieso vor jeder Ausgabe die Zeichenkette ## [1] steht. Die Rauten ## sehen Sie in Ihrer R Konsole nicht. Sie wurden von quarto (siehe Kapitel 23) für diese Webseitenversion des Buches erzeugt um anzugeben, dass es sich um eine R-Ausgabe handelt, und nicht um R-Befehle. So kann auch nichts schief gehen, falls Sie Code-Chunks von dieser Seite markieren, kopieren und in Ihre Scriptdatei übertragen, denn dort kommentieren die ## die Zeile aus, so dass sie nicht ausgeführt werden.

Die [1] erklärt sich folgendermaßen. In R beginnt jede Ausgabezeile mit der Position des Wertes, der als nächstes ausgegeben wird. Da wir derzeit nur Rechenoperationen durchführen, die genau ein Ergebnis haben, ist die Position unseres Ergebniswertes immer [1]. Hat man längere Datenreihen, z.B. das Gewicht von 10 Probanden in kg, dann könnte die Ausgabe so aussehen:

## [1] 102.20  76.83  98.50  86.55  121.93
## [6]  89.21 123.22  78.23  98.33   83.37

Die zweite Ausgabezeile beginnt mit einer [6], was bedeutet, dass nachfolgend die 6. Position der Wertereihe angezeigt wird.

Das Ziehen einer Wurzel erfolgt über die Funktion sqrt(). Damit Funktionen arbeiten können, benötigen sie ein “Argument”. Das Argument ist in unserem Fall die Zahl 25, da wir die Wurzel aus 25 ziehen wollen.

# Wurzel ziehen
sqrt(25)

## [1] 5

Argumente werden der Funktion in runden Klammern übergeben. So erklärt sich der Befehl sqrt(25).

# Lange Formel
sqrt( (2*10) / (20/2) ) - 9

## [1] -7.585786

# Befehl schlägt fehl!
2,3 - 9

## Error: <text>:2:2: Unerwartete(s) ','
## 1: # Befehl schlägt fehl!
## 2: 2,
##     ^

An der letzten Ausgabe zeigt sich ein wesentliches Merkmal von R:

Dezimalstellen werden mit einem Punkt UND NICHT mit einem Komma dargestellt!

Dies ist ein häufiger Anfängerfehler und meist der Grund, warum am Anfang etwas nicht funktioniert.

# Dezimalstellen mit .
4.25 * 6.25

## [1] 26.5625

# nicht mit Komma!
# schlägt fehl
4,25 * 6,78

## Error: <text>:3:2: Unerwartete(s) ','
## 2: # schlägt fehl
## 3: 4,
##     ^

Weitere mathematische Funktionen sind:

Funktion | Befehl | Beispiel | Ausgabe
Exponentialfunktion | exp() | exp(2) | [1] 7.389056
natürlicher Logarithmus | log() | log(2) | [1] 0.6931472
Logarithmus zur Basis 2 | log2() | log2(3) | [1] 1.584963
Logarithmus zur Basis 10 | log10() | log10(3) | [1] 0.4771213
Sinus | sin() | sin(2) | [1] 0.9092974
Kosinus | cos() | cos(2) | [1] -0.4161468
Tangens | tan() | tan(2) | [1] -2.18504
Absolutwert | abs() | abs(-7) | [1] 7

4.1 Nachkommastellen

R zeigt standardmäßig 6 Nachkommastellen an. Intern rechnet R mit “doppelter Genauigkeit”. Damit sind Nummern mit ca. 17 Stellen (1 Ziffer + 16 Nachkommastellen oder eben eine 17 stellige Zahl ohne Nachkommastellen) repräsentierbar. Möchte man die Nachkommastellen entsprechend angezeigt bekommen, so kann man dies mit der options-Funktion einstellen:

# gib ab jetzt 16 Nachkommastellen aus
options(digits=17)
# Wurzel aus 3
sqrt(3)

## [1] 1.7320508075688772

Nach dieser Eingabe liefert R Ergebnisse mit bis zu 16 Nachkommastellen.

Wir stellen den Wert wieder zurück auf 6 Nachkommastellen.

# gib ab jetzt wieder "nur" 6 Nachkommastellen aus
options(digits=7)

4.2 Runden

Ziehen wir die Wurzel aus 3, erhalten wir ein Ergebnis mit 6 Nachkommastellen.

# Wurzel aus 3
sqrt(3)

## [1] 1.732051

Dieses Ergebnis kann mit der Funktion round() gerundet werden.

# Runde auf 2 Nachkommastellen
round(sqrt(3), digits=2)

## [1] 1.73

# Runde auf ganze Zahl
round(sqrt(3))

## [1] 2

Bitte beachten Sie, dass R mathematisch rundet (gemäß dem IEEE 754 Standard), und nicht kaufmännisch. Beim mathematischen Runden wird eine 55 am Ende immer auf die nächste gerade Zahl gerundet.

Dies soll folgendes Beispiel verdeutlichen:

# mathematisches Runden
round(3.5) # ergibt 4

## [1] 4

round(2.5) # ergibt 2

## [1] 2

Neben round() existieren noch die Spezialfunktionen ceiling() und floor(). Die Funktion ceiling() rundet immer zur nächsten ganzen Zahl auf, und die Funktion floor() rundet immer zur nächsten ganzen Zahl ab. Man kann sich das so vorstellen:

Sie pflücken Äpfel und legen diese in Körbe. So erhalten Sie 10 volle und einen halbvollen Korb, oder andersgesagt 10,5 Körbe. Leider können Sie nur volle Körbe verkaufen. In diesem Fall würden Sie mittels floor() auf die ganze Zahl abrunden, um zu ermitteln, wieviele Körbe Sie verkaufen können.

Aus den restlichen Äpfeln pressen Sie Saft, den Sie in Flaschen füllen. In jede Flasche passen 100ml, und Sie pressen insgesamt 565ml Saft aus den Äpfeln. Um zu ermitteln, wieviele Flaschen Sie benötigen, runden Sie mittels ceiling() auf die nächste Zahl auf, damit nach 5 vollen Flaschen auch die restlichen 65ml aufgefangen werden können (auch wenn die letzte Flasche nicht voll wird).

# ceiling rundet immer auf
ceiling(sqrt(3))

## [1] 2

# ceiling rundet immer auf
ceiling(1.0008)

## [1] 2

# floor rundet immer ab
floor(sqrt(3))

## [1] 1

# floor rundet immer ab
floor(1.9999)

## [1] 1

R nutzt die wissenschaftliche Schreibweise sehr großer und kleiner Zahlen (so genannte Zehnerpotenzen).

# erzeuge eine sehr große Zahl
987654321 * 987654321

## [1] 9.754611e+17

Um die Ergebniszahl vor der Zehnerpotenz zu runden, kann die Funktion format() genutzt werden.

# runde vor der Zehnerpotenz
format(987654321 * 987654321, digits=2)

## [1] "9.8e+17"

5 Variablen

Variablen dienen der Speicherung von Werten oder komplexen Daten (streng genommen spricht man in R aber immer von “Objekten”). Variablen bestehen aus einem Namen und dem zugewiesenen Wert (bzw. Werten). Der Name einer Variable in R darf nicht mit einer Zahl beginnen, das heisst Variablennamen wie 1stTry sind nicht erlaubt. Ein Wert kann einer Variable auf zwei Weisen zugewiesen werden:

# weise Variable "x" den Wert 8 zu
x <- 8

Die in R gängige Variante ist das Zuweise mittels “Pfeil”, den man mit < und - nachahmt.

Die Zuweisung per Gleichheitszeichen funktioniert aber ebenso.

# weise Variable "x" den Wert 8 zu
x <- 8

# funktioniert auch mit "="
x = 8

Wir haben nun eine Variable x mit dem Wert 8. Beachten Sie, dass R zwischen Groß- und Kleinschreibung unterscheidet. Unsere Variable heisst x und nicht X!

In RStudio wird uns nun im Datenfenster (oberes rechtes Viertel) die Variable x mit ihrem Wert 8 angezeigt (Abbildung 5.1).

Abb. 5.1: Variablen werden oben rechts angezeigt

Den Wert einer Variable können wir ausgeben lassen, indem wir einfach ihren Namen in die R-Konsole schreiben

# gib Wert von x aus
x

## [1] 8

Wie erwähnt unterscheidet R zwischen Groß- und Kleinschreibung, und so erhalten wir einen Fehler wenn wir eingeben:

# großes "X", kein kleines "x"
X

## Error in eval(expr, envir, enclos): Objekt 'X' nicht gefunden

Mit Variablen kann auch gerechnet werden:

# erzeuge Variable "y" mit Wert 10
y <- 10
# erzeuge Variable "z" mit Wert 5
z <- 5
# rechne ein bisschen
x+y+z

## [1] 23

# noch ein bisschen rechnen
x/(y+z)

## [1] 0.5333333

# speichere Ergebnis in weitere Variable "Dummy"
Dummy <-x+(y^z)
# Gib Wert(e) von Dummy aus
Dummy

## [1] 100008

5.1 Wertereihen

Wir können einer Variable auch eine Wertereihe (also mehrere Werte) zuweisen - spätestens jetzt sollten wir nicht mehr von einer “Variable” sprechen, sondern von einem “Objekt”. Dies erfolgt für gewöhnlich mit der Funktion c() (für concatenate, “aneinanderreihen”), indem man die einzelnen Werte mit einem Komma getrennt der Funktion übergibt.

# weise Wertereihe 1 bis 10 mit c() zu
a <- c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
# Gib Werte von "a" aus
a

##  [1]  1  2  3  4  5  6  7  8  9 10

Mit c() kann auch Text, also z.B. Wörter, übergeben werden.

# es müssen keine Zahlen sein
b <- c("eins", "zwei", "drei", "vier", "fünf", "sechs", "sieben", "acht", "neun", "zehn")
# Gib Werte von "b" aus
b

##  [1] "eins"   "zwei"   "drei"   "vier"   "fünf"   "sechs"  "sieben" "acht"
##  [9] "neun"   "zehn"

Der Text muss in Anführungszeichen gesetzt werden, da R ansonsten nicht das Wort "fünf" versteht, sondern nach einem Objekt namens fünf sucht. Denn Variablen und Objekte können ebenfalls aneinandergereiht werden.

# Variablen aneinanderreihen
c(x, y, z)

## [1]  8 10  5

Wichtig ist, dass innerhalb der c()-Kette nur Werte des selben Typs (entweder Zahlen oder Text) angegeben werden. Mischt man Text mit Zahlen, wandelt c() die Wertereihe auf den “kleinsten gemeinsamen Nenner” um, und das ist immer “Text”.

# c() fällt auf den "kleinsten gemeinsamen Nenner" zurück
c(x, y, z, b)

##  [1] "8"      "10"     "5"      "eins"   "zwei"   "drei"   "vier"   "fünf"
##  [9] "sechs"  "sieben" "acht"   "neun"   "zehn"

Aus den Zahlen 8, 10 und 5 sind die Wörter "8", "10" und "5" geworden, und mit denen kann man nun keine Rechenoperationen mehr durchführen.

Das Mischen der Wertetypen ist ein weiterer häufiger Anfängerfehler.

Mit der Funktion length() kann die Länge der Wertereihe bestimmt werden

# länge von Objekt "a"
length(a)

## [1] 10

Objekt a besteht aus 10 Werten.

# länge von Objekt "Dummy"
length(Dummy)

## [1] 1

Objekt Dummy besteht aus 1 Wert.

5.2 Objekte löschen

Im Datenfenster von RStudio (rechtes oberes Viertel) werden alle Objekte (und somit auch Variablen) angezeigt (Abbildung 5.2).

Abb. 5.2: Objekte werden oben rechts angezeigt

Wir sehen dort unsere Objekte a, b, Dummy, x, y und z. Rechts daneben zeigt RStudio zusammenfassende Informationen zu den Objekten an. So sieht man direkt, dass der Wert von Dummy = 100008 ist, oder dass a zehn Werte enthält.

In der R-Konsole können alle vorhandenen Objekte mit der Funktion ls() angezeigt werden.

# zeige alle Objekte an
ls()

## [1] "a"     "b"     "Dummy" "x"     "y"     "z"

Objekte lassen sich mit dem Befehl rm() löschen.

# lösche Objekt "Dummy"
rm(Dummy)
# zeige alle Objekte an
ls()

## [1] "a" "b" "x" "y" "z"

Um alle Objekte zu löschen, verknüpfen wir den ls()-Befehl mit rm(). So änder sich der Befehl in:

# lösche ALLE Objekte
rm(list=ls())

# zeige alle Objekte an
ls()

## character(0)

In RStudio löschen Sie alle Objekte aus dem Arbeitsspeicher, indem Sie im Datenfenster auf den kleinen Besen klicken (Abbildung Abbildung 5.3).

Abb. 5.3: alle Variablen löschen

6 Wertesequenzen erzeugen

Bislang haben wir unsere Daten von Hand eingeben. Für die Zahlenreihe von 1 bis 10 sah das so aus:

# Zahlenreihe von 1 bis 10
zahlen <- c(1, 2, 3, 4, 5, 6, 7, 8, 9 ,10)
# ausgeben
zahlen

##  [1]  1  2  3  4  5  6  7  8  9 10

In R lassen sich solch kontinuierliche Reihen mit einem : erzeugen:

# auch Zahlenreihe von 1 bis 10
zahlen <- 1:10

# ausgeben
zahlen

##  [1]  1  2  3  4  5  6  7  8  9 10

In Kombination mit c() geht auch:

# neuer Vektor aus 2 Zahlenreihen
zahlen2 <- c(1:10, 100:110)

# ausgeben
zahlen2

##  [1]   1   2   3   4   5   6   7   8   9  10 100 101 102 103 104 105 106 107 108
## [20] 109 110

Mit der Funktion seq() lassen sich Sequenzen erstellen. Wir übergeben der Funktion Start- und Endpunkt, und in welchen Schritten gezählt werden soll. Eine Zahlenreihe von 1 bis 10 in den Schritten 0.5 erzeugt man so:

# eine Sequenz erstellen
sequenz <- seq(from=1, to=10, by=0.5)

# ausgeben
sequenz

##  [1]  1.0  1.5  2.0  2.5  3.0  3.5  4.0  4.5  5.0  5.5  6.0  6.5  7.0  7.5  8.0
## [16]  8.5  9.0  9.5 10.0

oder schlicht

# erstellt die selbe Sequenz
sequenz <- seq(1, 10, 0.5)

# ausgeben
sequenz

##  [1]  1.0  1.5  2.0  2.5  3.0  3.5  4.0  4.5  5.0  5.5  6.0  6.5  7.0  7.5  8.0
## [16]  8.5  9.0  9.5 10.0

Mit der Funktion rep() (für repeat) lassen sich wiederholte Werte erzeugen

# erzeuge 20 wiederholte Werte,
# 20 mal "weiblich"
geschlecht <- rep("weiblich", 20)

# ausgeben
geschlecht

##  [1] "weiblich" "weiblich" "weiblich" "weiblich" "weiblich" "weiblich"
##  [7] "weiblich" "weiblich" "weiblich" "weiblich" "weiblich" "weiblich"
## [13] "weiblich" "weiblich" "weiblich" "weiblich" "weiblich" "weiblich"
## [19] "weiblich" "weiblich"

In Kombination mit c() geht auch:

# erzeuge 20 wiederholte Werte,
# 10 mal "weiblich" und 10 mal "maennlich"
geschlecht <- c(rep("weiblich", 10), rep("maennlich", 10))
# ausgeben
geschlecht

##  [1] "weiblich"  "weiblich"  "weiblich"  "weiblich"  "weiblich"  "weiblich"
##  [7] "weiblich"  "weiblich"  "weiblich"  "weiblich"  "maennlich" "maennlich"
## [13] "maennlich" "maennlich" "maennlich" "maennlich" "maennlich" "maennlich"
## [19] "maennlich" "maennlich"

Oder anders verschachtelt:

# erzeuge 20 wiederholte Werte, anders verschachtelt
# 10 mal das Paar "weiblich" "maennlich"
geschlecht <- rep(c("weiblich", "maennlich"), 10)
# ausgeben
geschlecht

##  [1] "weiblich"  "maennlich" "weiblich"  "maennlich" "weiblich"  "maennlich"
##  [7] "weiblich"  "maennlich" "weiblich"  "maennlich" "weiblich"  "maennlich"
## [13] "weiblich"  "maennlich" "weiblich"  "maennlich" "weiblich"  "maennlich"
## [19] "weiblich"  "maennlich"

6.1 Zufallszahlen

In R lassen sich auch Zufallszahlen generieren. Hierfür steht die Funktion sample() zur Verfügung.

# erzeuge 20 Zufallswerte
sample(20)

##  [1] 13 12  5  6 15 16  4 17  9 18 11  8  7 10  2  3 20 19  1 14

Der Bereich, aus welchem die Zahlen gezogen werden sollen, kann ebenfalls angegeben werden.

# erzeuge 20 Zufallswerte zwischen 150 und 250
sample(150:250, 20)

##  [1] 193 210 183 219 212 153 248 184 238 235 192 156 181 202 247 158 171 159 242
## [20] 244

Über die Option replace kann angegeben werden, ob Werte auch doppelt vorkommen können (so genanntes zurücklegen).

# erzeuge 20 Zufallswerte zwischen 150 und 250 # MIT zurücklegen
sample(150:250, 20, replace=TRUE)

##  [1] 166 201 205 171 244 150 219 183 200 195 214 174 186 157 182 245 232 241 177
## [20] 197

Zudem bieten die in R implementierten Wahrscheinlichkeitsverteilungen

über ihre r-Funktionen (für random, siehe Kapitel 18) die Möglichkeit, zufällige Werte zu erzeugen.

Die Funktion runif() erzeugt Zufallswerte aus der “stetigen Gleichverteilung”. Standardmäßig liegen die Zufallswerte zwischen 0 und 1.

# erzeuge 20 Zufallswerte zwischen 0 und 1
runif(20)

##  [1] 0.46598719 0.39003139 0.02006522 0.37697093 0.55991284 0.85708359
##  [7] 0.38480971 0.52791704 0.60063752 0.26137136 0.29005016 0.48007517
## [13] 0.92000555 0.40072018 0.21317271 0.67176682 0.05861411 0.99706914
## [19] 0.14903547 0.51855664

Man kann aber (ähnlich wie bei sample()) den Bereich festlegen, aus welchem die Werte gezogen werden sollen.

# erzeuge 20 Zufallswerte zwischen 30 und 150
runif(20, min=30, max=150)

##  [1] 131.53441 116.19237  58.95768  95.64520 130.17622  33.35472  86.32612
##  [8] 126.68160 127.68616  78.46932  56.21172  80.20337 110.26449  90.91803
## [15] 109.24312  91.41496 130.26629 115.05374 134.90471  31.37754

Für ganzzahlige Werte können die Rundungsfunktionen round(), floor() und ceiling() eingeschoben werden.

# erzeuge 20 GANZZAHLIGE Zufallswerte zwischen 30 und 150
floor(runif(20, min=30, max=150))

##  [1] 136 149  90  73 122 100 106 133  98  60 140 134  59  78 122  44  53  49 109
## [20] 132

Um Zufallszahlen aus der Standardnormalverteilung zu generieren eignet sich die Funktion rnorm().

# erzeuge 40 zufällige Werte aus der \# Standardnormalverteilung
normal <- rnorm(40)

# ausgeben
normal

##  [1]  1.4505432  0.1943924 -0.6912054  1.3398599  2.7361084 -0.9441017
##  [7] -1.7810619 -0.7160587  0.9110785 -0.7721921 -0.7820777 -0.4321952
## [13] -0.6675648  1.3895059  0.9118739  0.2053894  2.5844322 -0.7893881
## [19]  0.5880771 -0.7112873  1.5849968  0.6763896 -0.2327618  0.6374729
## [25] -1.3707612 -1.4256595 -1.2461920 -0.6832669 -0.9796754 -0.4625191
## [31]  1.2145097 -1.2778199  0.7478688  3.3915088  1.6193896 -1.8508898
## [37]  1.0554223 -0.8053435  1.5961720  0.7759900

Somit erzeugt rt() zufällige Werte aus der t-Verteilung.

# erzeuge 40 zufällige Werte aus der \# t-Verteilung bei 10 Freiheitsgraden
t <- rt(40, df=10)

# ausgeben
t

##  [1]  0.80816081  0.63464272 -1.04867067  0.28763708  1.46970112 -1.67681763
##  [7]  0.76374895  0.35673699  0.43852070  0.02057365 -0.31623317 -0.60273538
## [13]  0.71802180 -0.26756381  1.17957855  0.62269651 -0.21301326 -0.75593568
## [19]  0.21687205 -1.16856330  0.43970628 -1.38123836 -0.20136631  1.64734295
## [25] -1.64091019 -0.30674880 -2.31814854  0.57115467  0.55923590  0.16815230
## [31]  0.26530835 -0.71421268 -1.60675413  0.07215224 -0.72087588  2.21050387
## [37]  2.16195314  0.95226057 -0.51923006  0.84492165

7 Datentypen

R ist eine objektorientierte Sprache und kann verschiedene Formen von Datenklassen und -typen verarbeiten. Die für uns wichtigsten Datentypen sind:

numerisch (Zahlen, mit denen man rechnen kann)

character (Zeichenkette, also Text und Wörter)

logisch (Wahr/Falsch, bzw. TRUE und FALSE)

Wir haben in Kapitel 5 schon zwei Datentypen kennengelernt, zum einen den Typ numerisch, denn wir haben den Variablen x, y und z jeweils Zahlenwerte zugeordnet, mit denen wir rechnen konnten.

# numerischer Datetyp
v_num <- c(0, 8, 15)

# rechnen ist möglich
v_num * 100

## [1]    0  800 1500

Zum anderen hatten wir der Variable b Wörter zugewiesen. Dies entspricht dem Datentyp “character”.

# Datetyp "Character"
v_char <- c("Hallo", "ihr", "lieben", "Leute")

# rechnen ist nicht möglich
v_char * 100

## Fehler in v_char * 100 : nicht-numerisches Argument für binären Operator

Variablen vom Typ logisch enthalten die logischen Aussagen TRUE und FALSE bzw. deren Abkürzungen T und F.

Eine logische Aussage kann zum Beispiel durch einen Vergleich erzeugt werden

# ist 50 = 100
50==100

## [1] FALSE

Da 50 eben nicht gleich 100 ist, erhalten wir die logische Aussage FALSE zurück.

# ist 50 kleiner als 100
50<100

## [1] TRUE

Die Zahl 50 ist kleiner als 100, darum erhalten wir die logische Antwort TRUE zurück.

Das kann man auch auf Wertereihen anwenden.

# erzeuge 20 werte
reihe <- seq(1, 100, 5)
# ausgeben
reihe

##  [1]  1  6 11 16 21 26 31 36 41 46 51 56 61 66 71 76 81 86 91 96

# Prüfe jeden Wert in "reihe", ob er kleiner als 75 ist
reihe<75

##  [1]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
## [13]  TRUE  TRUE  TRUE FALSE FALSE FALSE FALSE FALSE

# Prüfe jeden Wert in "reihe", ob er kleiner als 75 ist
# und speichere das Ergebnis in Variable "logical"
logical <- reihe<75

# zeige den 7. und 18. Wert von "logical" an
logical[c(7,18)]

## [1]  TRUE FALSE

Mit der Funktion class() kann der Datentyp (und auch die Datenklasse, dazu später mehr) angezeigt werden.

# welcher Datentyp ist Variable "logical"
class(logical)

## [1] "logical"

# welcher Datentyp ist Variable "v_char"
class(v_char)

## [1] "character"

# welcher Datentyp ist Variable "v_num"
class(v_num)

## [1] "numeric"

Mit den Funktionen is.numeric(), is.logical() und is.character() können wir den Datentyp ebenfalls überprüfen. Als Antwort erhalten wir ein logisches TRUE oder FALSE.

# ist Variable "v_num" numerisch?
is.numeric(v_num)

## [1] TRUE

# ist Variable "v_num" logical?
is.logical(v_num)

## [1] FALSE

# ist Variable "v_num" character?
is.character(v_num)

## [1] FALSE

# ist Variable "logical" character?
is.character(logical)

## [1] FALSE

# ist Variable "v_char" numerisch?
is.numeric(v_char)

## [1] FALSE

# ist Variable "v_char" logical?
is.logical(v_char)

## [1] FALSE

# ist Variable "v_char" character?
is.character(v_char)

## [1] TRUE

# ist Variable "logical" logical?
is.logical(logical)

## [1] TRUE

8 Datenklassen

Die Datentypen können wiederum in Datenklassen gespeichert werden. Für uns wichtige Datenklassen sind:

Vektoren

Matrizen

Faktoren (Gruppen, Rangfolge)

Datenframes

Listen

8.1 Vektoren

Vektoren haben wir schon kennengelernt. Ein Vektor ist eine einfache Wertereihe vom selben Datentyp. So erzeugt die Funktion c() einen Datenvektor. Alle Werte der Datenklasse Vektor müssen Werte des selben Wertetyps enthalen. Kombinieren wir numerische, character und logische Werte in einem Vektor, so wandeln sich alle Werte in den kleinsten gemeinsamen Datentyp (nämlich character) um.

# c() fällt auf den "kleinsten gemeinsamen Nenner" zurück
c(1, 2, 3, 4, "fünf", "sechs", TRUE, TRUE, FALSE)

## [1] "1"     "2"     "3"     "4"     "fünf"  "sechs" "TRUE"  "TRUE"  "FALSE"

Alle Werte sind nun vom Datentyp character, erkennbar an den Anführungszeichen.

Auf die Werte kann man zugreifen, indem man den Variablennamen eingibt:

 # erzeuge einen Vektor
vektor <- seq(1, 20, 1)

# anzeigen
vektor

##  [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20

Auf die einzelnen Werte des Vektors kann man Zugreifen, indem man die gewünschte Position in eckigen Klammern an den Variablennamen anhgängt.

# Zeige den ersten Wert von "vektor"
vektor[1]

## [1] 1

Auch hier können wir Positionsbereiche mit einem : angeben

# Zeige die Werte an Position 4 bis 15
vektor[4:15]

##  [1]  4  5  6  7  8  9 10 11 12 13 14 15

Mit einem Minuszeichen können auch bestimmte Werte oder Wertbereiche ausgelassen werden.

# Zeige "vektor" OHNE den ersten Wert
vektor[-1]

##  [1]  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20

# Zeige "vektor" OHNE die Werte 10 bis 14
vektor[-(10:14)]

##  [1]  1  2  3  4  5  6  7  8  9 15 16 17 18 19 20

Wieviele Werte ein Vektor enthält erfährt man mit der Funktion lenght().

# wieviele Werte hat "vektor"?
length(vektor)

## [1] 20

Der Vektor vektor beinhaltet 20 Werte.

Mit der Funktion is.vector() kann geprüft werden, ob ein Objekt ein Vektor ist.

# ist "vektor" ein Vektor?
is.vector(vektor)

## [1] TRUE

8.2 Matrizen

Matrizen sind zweidimensionale Strukturen (Tabellen) und werden von R-intern durch Vektoren dargestellt. Dies impliziert, dass alle Werte der Matrix vom selben Datentyp (z.B. numerisch) sein müssen, genau so wie bei Vektoren.

Um besser zu erklären, wie Matrizen funktionieren, erzeugen wir zunächst ein paar Beispielvektoren.

# Erzeuge Testwertereihen
a <- c(11, 12, 13, 14, 15)
b <- c(21, 22, 23, 24, 25)
c <- c(31, 32, 33, 34, 35)
d <- c(41, 42, 43, 44, 45)
e <- c(51, 52, 53, 54, 55)
f <- c("eins", "zwei", "drei", "vier", "fünf")

# Füge alle Zahlen zu einem Vektor zusammen
alle <- c(a, b, c, d, e)

# anzeigen
alle

##  [1] 11 12 13 14 15 21 22 23 24 25 31 32 33 34 35 41 42 43 44 45 51 52 53 54 55

Die Funktoin martix() setzt aus einem Vektor eine Matrix zusammen. Lässt man alle Parameter im Funktionsaufruf matrix() leer, wird eine Matrix mit 1 Spalte erzeugt.

# Erzeuge eine Matrix aus Vektor "a"
matrix(a)

##      [,1]
## [1,]   11
## [2,]   12
## [3,]   13
## [4,]   14
## [5,]   15

Mit dem Parameter ncol kann die gewünschte Anzahl an Spalten übergeben werden:

# Erzeuge eine Matrix aus Vektoren "a" und "b"
# mit 2 Spalten
matrix(c(a,b), ncol=2)

##      [,1] [,2]
## [1,]   11   21
## [2,]   12   22
## [3,]   13   23
## [4,]   14   24
## [5,]   15   25

Mit dem Parameter nrow kann die gewünschte Anzahl an Zeilen übergeben werden:

# Erzeuge eine Matrix aus Vektoren "a" und "b"
# mit 2 Zeilen
matrix(c(a,b), nrow=2)

##      [,1] [,2] [,3] [,4] [,5]
## [1,]   11   13   15   22   24
## [2,]   12   14   21   23   25

Achten Sie auf die Reihenfolge, in der die Werte in der Matrix angelegt wurden. Das ist wahrscheinlich nicht das Ergebnis, das Sie erwartet haben. Die Funktion matrix() arbeitet standardmäßig die Werte pro Spalte (spaltenorientiert) ab. Wir können das mit dem Parameter byrow ändern:

# Erzeuge eine Matrix aus Vektoren "a" und "b"
# mit 2 Zeilen, diesmal zeilenorientiert
matrix(c(a,b), nrow=2, byrow=TRUE)

##      [,1] [,2] [,3] [,4] [,5]
## [1,]   11   12   13   14   15
## [2,]   21   22   23   24   25

Sobald eine Matrix mit 2 oder mehr Spalten angelegt werden soll, muss der Datenvektor so viele Werte enthalten, dass die gewünschte Matrix vollständig erstellt werden kann. Sollten zu wenige Werte vorhanden sein, gibt R eine Warnmeldung aus. Für eine Matrix mit 2 Spalten muss also eine gerade Anzahl an Werten vorhanden sein.

# Matrix mit 2 Spalten benötigt gerade Anzahl an Werten
# daher gibt R (mit nur 9 Werten) eine Warnmeldung aus
matrix(1:9, nrow=2)

## Warning in matrix(1:9, nrow = 2): Datenlänge [9] ist kein Teiler oder
## Vielfaches der Anzahl der Zeilen [2]

##      [,1] [,2] [,3] [,4] [,5]
## [1,]    1    3    5    7    9
## [2,]    2    4    6    8    1

Wie Sie sehen versucht R selbstständig die Matrix zu vervollständigen. Hierfür wiederholt R die angegebenen Werte so lange, bis die Matrix “voll” ist. Im obigen Beispiel wurde nach der 9 wieder eine 1 eingetragen.

Wie bereits erwähnt müssen die Werte vom selben Datentyp sein. Mischt man numeric mit character, fällt auch Matrix auf den kleinsten gemeinsamen Datentyp (character) zurück.

# Erzeuge eine Matrix aus Vektoren "a" und "f"
# fällt auch Typ "char" zurück
matrix(c(a,f), nrow=2, byrow=TRUE)

##      [,1]   [,2]   [,3]   [,4]   [,5]
## [1,] "11"   "12"   "13"   "14"   "15"
## [2,] "eins" "zwei" "drei" "vier" "fünf"

Neben matrix() können auch die Befehle rbind() (für rowbind) und cbind() (für columnbind) verwendet werden, um eine Matrix zu erzeugen.

Der Befehl cbind() fügt die übergebenen Vektoren spaltenorientiert zu einer Matrix zusammen.

# Erzeuge eine Matrix aus Vektoren "c" und "d"
# spaltenorientiert
cbind(c, d)

##       c  d
## [1,] 31 41
## [2,] 32 42
## [3,] 33 43
## [4,] 34 44
## [5,] 35 45

Der Befehl rbind() fügt die übergebenen Vektoren zeilenorientiert zu einer Matrix zusammen.

# Erzeuge eine Matrix aus Vektoren "c" und "d"
# zeilenorientiert
rbind(c, d)

##   [,1] [,2] [,3] [,4] [,5]
## c   31   32   33   34   35
## d   41   42   43   44   45

Wir speichern eine Matrix in einer Variable:

# Erzeuge eine Matrix aus Vektor "alle"
# mit 5 Spalten
mymatrix <- matrix(alle, ncol=5)

# ausgeben
mymatrix

##      [,1] [,2] [,3] [,4] [,5]
## [1,]   11   21   31   41   51
## [2,]   12   22   32   42   52
## [3,]   13   23   33   43   53
## [4,]   14   24   34   44   54
## [5,]   15   25   35   45   55

An der Ausgabe der Spalten- und Zeilentitel lässt sich erahnen, wie die einzelnen Werte einer Matrix refenziert werden können. Bei Vektoren können die einzelnen Werte abgerufen werden, indem in eckigen Klammern die gewünschte Position angegeben wird. Dies funktioniert bei Matrizen ähnlich, jedoch muss innerhalb der eckigen Klammer zwischen Zeilen und Spalten unterschieden werden. Dies geschieht mit einem Komma, wobei vor dem Komma die Zeilen, und nach dem Komma die Spalten referenziert werden.

# zeige die 1. Zeile der Matrix
# die Zahl vor dem Komma repräsentiert die Zeilen
mymatrix[1,]

## [1] 11 21 31 41 51

# zeige die 1. Spalte der Matrix
# die Zahl nach dem Komma repräsentiert die Spalten
mymatrix[,1]

## [1] 11 12 13 14 15

Wie in einem Koordinatensystem können nun gezielt einzelne Werte oder Wertbereiche referenziert werden.

# zeige den Wert in Zeile 3 und Spalte 2
mymatrix[3,2]

## [1] 23

# zeige die Werte aus Zeile 2 bis 4
# und Spalte 1 bis 3
mymatrix[2:4,1:3]

##      [,1] [,2] [,3]
## [1,]   12   22   32
## [2,]   13   23   33
## [3,]   14   24   34

Mit der Funktion t() kann die Matrix transponiert werden, das beduetet, es werden Zeilen und Spalten diagonal gespiegelt.

# zeige meine Matrix
mymatrix

##      [,1] [,2] [,3] [,4] [,5]
## [1,]   11   21   31   41   51
## [2,]   12   22   32   42   52
## [3,]   13   23   33   43   53
## [4,]   14   24   34   44   54
## [5,]   15   25   35   45   55

# transponiere meine Matrix
t(mymatrix)

##      [,1] [,2] [,3] [,4] [,5]
## [1,]   11   12   13   14   15
## [2,]   21   22   23   24   25
## [3,]   31   32   33   34   35
## [4,]   41   42   43   44   45
## [5,]   51   52   53   54   55

Mit den Funktionen colnames() und rownames() können Spalten und Zeilen noch betitelt werden.

Wir benennen unsere Spalten von “a” bis “e”:

# benenne Spalten der Matrix
colnames(mymatrix) <- c("a", "b", "c", "d", "e")

# anzeigen
mymatrix

##       a  b  c  d  e
## [1,] 11 21 31 41 51
## [2,] 12 22 32 42 52
## [3,] 13 23 33 43 53
## [4,] 14 24 34 44 54
## [5,] 15 25 35 45 55

Wir benennen unsere Zeilen von römisch I bis V:

# benenne Spalten der Matrix
rownames(mymatrix) <- c("I", "II", "III", "IV", "V")

# anzeigen
mymatrix

##      a  b  c  d  e
## I   11 21 31 41 51
## II  12 22 32 42 52
## III 13 23 33 43 53
## IV  14 24 34 44 54
## V   15 25 35 45 55

Mit der Funktion class() kann die Datenklasse angezeigt werden.

# welcher Datentyp ist Variable "mymatrix"
class(mymatrix)

## [1] "matrix" "array"

Mit der Funktion is.matrix() kann zudem geprüft werden, ob ein Objekt eine Matrix ist.

# ist "mymatrix" eine Matrix?
is.matrix(mymatrix)

## [1] TRUE

# ist "vector" eine Matrix?
is.matrix(vector)

## [1] FALSE

Als Übung können wir nun beispielsweise die Anzahl der Beschäftigten in Pflegeberufen aus dem “Pflegethermometer 2018” (Isfort et al., 2018) (siehe Abbildung 8.1), als Matrix in R übertragen.

Abb. 8.1: Anzahl Beschäftigter in Pflegeberufen (Isfort et al. 2018)

# Schreibe die Zahlen reihenweise aus der Grafik ab
Pflegeberufe <- c(16624, 19061, 19478, 21537, 27731, 36481, 46517, 54371, 64127,
                  55770,52710, 49727, 45776, 48326, 47903, 47978, 48363, 49507,
                  47779, 48203, 48822, 48519, 49080, 49307, 48291, 48937, 48913,
                  430983, 436767, 444783, 449355, 457322, 465446, 468192, 472580, 476416,
                  109161, 124879, 141965, 158817, 178902, 194195, 208304, 227154, 246412 )
# überführe in Matrix mit 9 Spalten
# Die Werte kommen reihenweise
Pflegeberufe <- matrix(Pflegeberufe, byrow=T,ncol=9)

# benenne die Spalten
colnames(Pflegeberufe) <- c(1999, 2001, 2003, 2005, 2007, 2009, 2011, 2013, 2015)

# benenne die Reihen
rownames(Pflegeberufe) <- c("Krankenpflegeassistenz", "Altenpflegehilfe", "Kinderkrankenpflege", "Krankenpflege", "Altenpflege")

# zeige Tabelle
Pflegeberufe

##                          1999   2001   2003   2005   2007   2009   2011   2013
## Krankenpflegeassistenz  16624  19061  19478  21537  27731  36481  46517  54371
## Altenpflegehilfe        55770  52710  49727  45776  48326  47903  47978  48363
## Kinderkrankenpflege     47779  48203  48822  48519  49080  49307  48291  48937
## Krankenpflege          430983 436767 444783 449355 457322 465446 468192 472580
## Altenpflege            109161 124879 141965 158817 178902 194195 208304 227154
##                          2015
## Krankenpflegeassistenz  64127
## Altenpflegehilfe        49507
## Kinderkrankenpflege     48913
## Krankenpflege          476416
## Altenpflege            246412

Wir werden auf diese Matrix später noch zurückkommen.

8.3 Faktoren

Die Datenklasse factor beschreibt gruppierte (nominale) oder ranggeordnete (ordinale) Werte. Gruppierte Werte sind beispielsweise “Beruf”, “Konfession”, “Familienstand” oder “Geschlecht”. Es liegt keine Reihenfolge unter den Gruppen vor. Sie werden mit der Funktion factor() erstellt.

# nominalen Faktoren erstellen
v_fac1 <- factor(c("maennlich", "weiblich", "divers"))
# ausgeben
v_fac1

## [1] maennlich weiblich  divers
## Levels: divers maennlich weiblich

Unsere Variable v_fac1 besteht aus 3 Gruppen, nämlich divers, maennlich und weiblich mit je einem Wert. Erzeugen wir die Daten erneut, diesmal mit mehr Werten und einer abgekürzten Schreibweise für das Geschlecht.

# Faktoren neu erstellen, mit mehr Werten
v_fac1 <- factor(c("m", "w", "d","m", "w", "d","m", "w", "d","m", "w", "d"))

# ausgeben
v_fac1

##  [1] m w d m w d m w d m w d
## Levels: d m w

In der ersten Zeile sehen wir unsere Datenreihe, in der zweiten Zeile alle Gruppen (Levels), in diesem Falle d, m und w.

Für ordninale (also ranggeordnete) Werte nehmen wir ebenfalls die factor()-Funktion. Versuchen wir also, klassiche Schulnoten abzubilden. Wir stellen uns eine virtuelle Liste mit Schulnoten von 100 SchülerInnen vor. Wir übertragen die Noten von der virtuellen Liste in R, und zwar in der Reihenfolge, wie sie auf unserer virtuellen Notenliste stehen könnten (sprich: unsortiert).

# Faktoren aus Notenliste erstellen
v_fac2 <- factor(c("gut", "ausreichend", "sehr gut", "ausreichend", "befriedigend", "magelhaft", "ungenügend", "gut", "gut", "sehr gut"))

# ausgeben
v_fac2

##  [1] gut          ausreichend  sehr gut     ausreichend  befriedigend
##  [6] magelhaft    ungenügend   gut          gut          sehr gut
## Levels: ausreichend befriedigend gut magelhaft sehr gut ungenügend

In der ersten Zeile sehen wir unsere Datenreihe, in der letzten Zeile alle Ränge (Levels). Ein Blick auf die Levels zeigt aber auch, dass diese in einer falschen Reihehnfolge angelegt wurden. Der erste Rang ist hier ausreichend, und der zweite befriedigend (es sollte ja eigentlich so sein, dass sehr gut der erste Rang ist, und gut der zweite, usw). Das liegt daran, dass wir die Noten unsortiert aneinandergereiht haben.

R erstellt die Reihenfolge der Levels anhand der Reihenfolge, in der sie eintreffen.

Dies ist ein häufiger Anfängerfehler bei der Erstellung von Faktoren!

Um unsere Schulnoten in der richtigen Levelreihenfolge anzulegen, müssen wir dem Befehl factor() eben diese Reihenfolge über die Option levels mitgeben (mehr Informationen erhalten Sie über die Hilfeseite ?factor). Konkret übergeben wir per levels=c() die Levelnamen von sehr gut bis ungenügend.

# Faktoren aus Notenliste erstellen # diesmal Levelreihenfolge mit "levels=" vorgeben
v_fac2 <- factor(c("gut", "ausreichend", "sehr gut", "ausreichend", "befriedigend", "mangelhaft", "ungenügend", "gut", "gut", "sehr gut"),
                 levels=c("sehr gut", "gut", "befriedigend", "ausreichend","mangelhaft", "ungenügend"))

# ausgeben
v_fac2

##  [1] gut          ausreichend  sehr gut     ausreichend  befriedigend
##  [6] mangelhaft   ungenügend   gut          gut          sehr gut
## Levels: sehr gut gut befriedigend ausreichend mangelhaft ungenügend

Die Levels sind nun in der korrekten Reihenfolge.

Wir hätten die Daten aber gar nicht neu eingeben müssen. Der Befehl lässt sich verkürzen, indem man einfach die bestehende Variable als Input nutzt und neu überschreibt:

# Levelreihenfolge in "v_fac2" reparieren und überschreiben
v_fac2 <- factor(v_fac2, levels=c("sehr gut", "gut", "befriedigend", "ausreichend","mangelhaft", "ungenügend"))

# ausgeben
v_fac2

##  [1] gut          ausreichend  sehr gut     ausreichend  befriedigend
##  [6] mangelhaft   ungenügend   gut          gut          sehr gut
## Levels: sehr gut gut befriedigend ausreichend mangelhaft ungenügend

So kann man auch nachträglich die Levelreihenfolge korrigieren.

Mit dem Befehl revalue() aus dem plyr-Zusatzpaket lassen sich die Werte und Levels von Faktoren umändern. Wir ändern unsere Schulnoten von den ausgeschriebenen Noten hin zu Zahlenwerten. Hierfür erzeugen wir eine neue Variable v_fac3.

# Lade Zusatzpaket "plyr"
library(plyr)
# Ändere Levelnamen
v_fac3 <- revalue(v_fac2, c("sehr gut"="1", "gut"="2","befriedigend"="3","ausreichend"="4","mangelhaft"="5","ungenügend"="6"))

# Werte ausgeben
v_fac3

##  [1] 2 4 1 4 3 5 6 2 2 1
## Levels: 1 2 3 4 5 6

Beachten Sie, dass die “Zahlen”werte nur nominaler Natur sind. Wir können mit ihnen nicht rechnen!

# rechnen ist mit factor nicht möglich!
v_fac3 * 100

## Warning in Ops.factor(v_fac3, 100): '*' ist nicht sinnvoll für Faktoren

##  [1] NA NA NA NA NA NA NA NA NA NA

Die Werte innerhalb einer Faktorenreihe refenziert man so wie bei Vektoren, indem man die gewünschte Position in eckigen Klammern an den Variablennamen anhgängt.

# zeige die Werte von 3 bis 7 von "v_fac3"
v_fac3[3:7]

## [1] 1 4 3 5 6
## Levels: 1 2 3 4 5 6

Welche Levels in einem Faktor existieren erfährt man mit der Funktion levels().

# welche Levels hat "v_fac3"?
levels(v_fac3)

## [1] "1" "2" "3" "4" "5" "6"

Durch Kombimation mit der Funktion length() können wir die Anzahl der Levels erfahren.

# wieviele Levels hat "v_fac3"?
length(levels(v_fac3))

## [1] 6

Die Variable hat 6 Levels.

Welche Level welche Häufigkeit hat erfahren wir mit der Funktion table().

# welche Level hat welche Häufigkeit?
table(v_fac2)

## v_fac2
##     sehr gut          gut befriedigend  ausreichend   mangelhaft   ungenügend
##            2            3            1            2            1            1

Mit der Funktion class() kann die Datenklasse angezeigt werden.

# welcher Datentyp ist Variable "v_fac2"
class(v_fac2)

## [1] "factor"

Mit der Funktion is.factor() kann zudem geprüft werden, ob ein Objekt ein Faktor ist.

# ist "v_fac2" ein Faktor?
is.factor(v_fac2)

## [1] TRUE

# ist "mymatrix" ein Faktor?
is.factor(mymatrix)

## [1] FALSE

8.3.1 ordinale Faktoren

Die bislang von uns erzeugten Faktoren haben zwar “scheinbar” eine korrekte Levelreihenfolge. Für R handelt es sich dabei aber weiterhin um “einfache” Faktoren, also nominale Daten. Um die Faktoren in ordinale Faktoren umzuwandeln kann die Funktion ordered() verwendet werden. Nutzen wir hierfür unsere Variable v_fac2 mit den Schulnoten.

# wandle in ordinalen Faktor um
ordered(v_fac2)

##  [1] gut          ausreichend  sehr gut     ausreichend  befriedigend
##  [6] mangelhaft   ungenügend   gut          gut          sehr gut
## 6 Levels: sehr gut < gut < befriedigend < ausreichend < ... < ungenügend

Die Levels haben nun eine Rangfolge, welche durch das Kleinerzeichen < dargestellt wird. Leider sind die Levelränge genau verkehrt herum, denn im obigen Falle ist “sehr gut” die kleinste und “ungenügend” die größte Note. Wir ändern also nochmal die Levelreihenfolge. Hierzu übergeben wir die Levelnamen in einen Vektor, und kehren mit der Funktion rev() dessen Reihenfolge um. (die Funktion fct_rev() aus dem forcats-Paket dreht die Levelreihenfolge ebenfalls um, aber derzeit wissen wir ja noch nicht, wie man Zusatzpakete installiert (siehe Kapitel 16)).

# zeige Levelnamen an
levels(v_fac2)

## [1] "sehr gut"     "gut"          "befriedigend" "ausreichend"  "mangelhaft"
## [6] "ungenügend"

# kehre den Vektor um
rev(levels(v_fac2))

## [1] "ungenügend"   "mangelhaft"   "ausreichend"  "befriedigend" "gut"
## [6] "sehr gut"

In Kombination mit ordered() erhalten wir so die korrekte ordinale Darstellung.

# überführe die ordinale Reihenfolge in Variable 'v_ord'
v_ord <- ordered(factor(v_fac2,
                        levels= rev(levels(v_fac2))))

v_ord

##  [1] gut          ausreichend  sehr gut     ausreichend  befriedigend
##  [6] mangelhaft   ungenügend   gut          gut          sehr gut
## 6 Levels: ungenügend < mangelhaft < ausreichend < befriedigend < ... < sehr gut

Die Funktion factor() nimmt zudem den Parameter ordered=TRUE entgegen, der direkt ein ordinales Objekt erzeugt:

# erzeuge direkt einen ordinalen factor
# mit Parameter 'ordered=TRUE'
v_ord <- factor(v_fac2,
                levels=rev(levels(v_fac2)),
                ordered=TRUE)

v_ord

##  [1] gut          ausreichend  sehr gut     ausreichend  befriedigend
##  [6] mangelhaft   ungenügend   gut          gut          sehr gut
## 6 Levels: ungenügend < mangelhaft < ausreichend < befriedigend < ... < sehr gut

8.4 Datenframes

Die Datenklasse Datenframe (Datensatz) ist wohl die wichtigste in R. Datenframes sind ebenso wie Matrizen zweidimensional. Im Unterschied zu einer Matrix können in einem Datenframe unterschiedliche Datentypen, also z.B. numeric, character und factor, zusammengeführt werden. Das Datenframe folgt dabei der Logik “ein Fall pro Zeile” (so genanntes tidy data Format, siehe Kapitel 25). Das bedeutet, dass jede Beobachtung (auch Wiederholungen) in einer eigenen Zeile stehen und die jeweiligen Variablen durch die Spalten repräsentiert werden.

Erzeugen wir uns ein paar Beispielvektoren unterschiedlichen Typs mit je 12 Werten.

# erzeuge Testvektoren "factor", "char", "numeric", "logical"
geschlecht <- factor(rep(c("m", "w", "d"), 4))
spitzname <- c("Hasi", "Ide", "Momsi", "Ryu", "Dave", "Zoid", "Adu", "Efi", "Ole", "Ray", "Sam", "Emi")
hausnummer <- 1:12
angemeldet <- c(TRUE, TRUE, FALSE, T, F, F, F, T, T, T, F, T)

Aus den Variablen setzen wir nun mit der Funktion data.frame() ein Datenframe zusammen.

# erzeuge ein Datenframe aus den Testvektoren
data.frame(geschlecht, spitzname, hausnummer, angemeldet)

##    geschlecht spitzname hausnummer angemeldet
## 1           m      Hasi          1       TRUE
## 2           w       Ide          2       TRUE
## 3           d     Momsi          3      FALSE
## 4           m       Ryu          4       TRUE
## 5           w      Dave          5      FALSE
## 6           d      Zoid          6      FALSE
## 7           m       Adu          7      FALSE
## 8           w       Efi          8       TRUE
## 9           d       Ole          9       TRUE
## 10          m       Ray         10       TRUE
## 11          w       Sam         11      FALSE
## 12          d       Emi         12       TRUE

Alternativ können die Werte auch direkt dem Datenframe übergeben werden.

## Wir schreiben die Werte direkt ins Datenframe
## Das Ergebnis ist das selbe
data.frame(geschlecht = factor(rep(c("m", "w", "d"), 4)),
           spitzname  = c("Hasi", "Ide", "Momsi", "Ryu", "Dave", "Zoid", "Adu", "Efi", "Ole", "Ray", "Sam", "Emi"),
           hausnummer = 1:12,
           angemeldet = c(TRUE, TRUE, FALSE, T, F, F, F, T, T, T, F, T)
)

##    geschlecht spitzname hausnummer angemeldet
## 1           m      Hasi          1       TRUE
## 2           w       Ide          2       TRUE
## 3           d     Momsi          3      FALSE
## 4           m       Ryu          4       TRUE
## 5           w      Dave          5      FALSE
## 6           d      Zoid          6      FALSE
## 7           m       Adu          7      FALSE
## 8           w       Efi          8       TRUE
## 9           d       Ole          9       TRUE
## 10          m       Ray         10       TRUE
## 11          w       Sam         11      FALSE
## 12          d       Emi         12       TRUE

Das Datenframe speichern wir in die Variable MeinDatenframe.

# speicher Datenframe in Variable
MeinDatenframe <- data.frame(geschlecht, spitzname, hausnummer, angemeldet)

# zeige Datenklasse an
class(MeinDatenframe)

## [1] "data.frame"

Die Funktion class() weist unsere Variable als Datenframe aus.

Ähnlich wie bei Matrizen müssen die Vektoren jeweils die selbe Anzahl an Werten (die selbe Länge) besitzen, damit das Datenframe vollständig aufgebaut werden kann. Entfernen wir z.B. einen Wert aus der Reihe hausnummer, schlägt der Befehl fehl.

# Datenframe, "hausnummer" ist einen Wert kürzer
data.frame(geschlecht, spitzname, hausnummer[-1], angemeldet)

Fehler in data.frame(geschlecht, spitzname, hausnummer[-1], angemeldet) :
  Argumente implizieren unterschiedliche Anzahl Zeilen: 12, 11

8.4.1 Fälle (Reihen) hinzufügen

Wenn zwei Datenframes mit den selben Spaltennamen exisiteren, können diese per rbind() zusammengefasst werden. In unserem Beispiel verdoppeln wir einfach unser Datenframe.

# füge 2 Datenframes mittels rbind() zusammen
rbind(MeinDatenframe, MeinDatenframe)

##    geschlecht spitzname hausnummer angemeldet
## 1           m      Hasi          1       TRUE
## 2           w       Ide          2       TRUE
## 3           d     Momsi          3      FALSE
## 4           m       Ryu          4       TRUE
## 5           w      Dave          5      FALSE
## 6           d      Zoid          6      FALSE
## 7           m       Adu          7      FALSE
## 8           w       Efi          8       TRUE
## 9           d       Ole          9       TRUE
## 10          m       Ray         10       TRUE
## 11          w       Sam         11      FALSE
## 12          d       Emi         12       TRUE
## 13          m      Hasi          1       TRUE
## 14          w       Ide          2       TRUE
## 15          d     Momsi          3      FALSE
## 16          m       Ryu          4       TRUE
## 17          w      Dave          5      FALSE
## 18          d      Zoid          6      FALSE
## 19          m       Adu          7      FALSE
## 20          w       Efi          8       TRUE
## 21          d       Ole          9       TRUE
## 22          m       Ray         10       TRUE
## 23          w       Sam         11      FALSE
## 24          d       Emi         12       TRUE

Es funktioniert nicht, wenn eine neue Zeile mit einem Datenvektor übergeben wird, denn in einem Vektor können nur Werte des selben Datentyps vorkommen.

# füge einzelne Zeile mit rbind() hinzu
rbind(MeinDatenframe, c("m", "Joe", 99, TRUE))

##    geschlecht spitzname hausnummer angemeldet
## 1           m      Hasi          1       TRUE
## 2           w       Ide          2       TRUE
## 3           d     Momsi          3      FALSE
## 4           m       Ryu          4       TRUE
## 5           w      Dave          5      FALSE
## 6           d      Zoid          6      FALSE
## 7           m       Adu          7      FALSE
## 8           w       Efi          8       TRUE
## 9           d       Ole          9       TRUE
## 10          m       Ray         10       TRUE
## 11          w       Sam         11      FALSE
## 12          d       Emi         12       TRUE
## 13          m       Joe         99       TRUE

Achtung

Zwar sieht es so aus, als sei die neue Zeile korrekt eingetragen worden, wenn wir jedoch das neue Datenframe in einer Variable abspeichern und die Datenklassen überprüfen, stellen wir fest, was falsch gelaufen ist.

# füge einzelne Zeile mit rbind() hinzu
new <- rbind(MeinDatenframe, c("m", "Joe", 99, TRUE))

# überprüfe Datentyp für Spalte "hausnummer"
class(new$hausnummer)

## [1] "character"

Der Datentyp in Spalte hausnummer ist auf den “kleinsten gemeinsamen Nenner” (character) zurückgefallen. Das liegt daran, dass zunächst der Vektor in c() auf character zurückfällt. Somit sind alle Werte in der c()-Funktion vom Typ character. Bei hausnummer zieht nun dieser neue Wert die gesamte Spalte auf den Typ character zurück. Ebenso verhält es sich bei Variable angemeldet, die eigentlich mal vom Typ logical war.

# überprüfe Datentyp für Spalte "geschlecht"
class(new$angemeldet)

## [1] "character"

Wenn wir mit der Spalte hausnummer rechnen wollen, schlägt dies fehl.

# multipliziere Spalte "hausnummer" mit 2
new$hausnummer * 2

Fehler in new$hausnummer * 2 : nicht-numerisches Argument für binären Operator

Ein falscher rbind()-Befehl kann Ihnen also das gesamte Datenframe “zerschießen”.

Dies ist ein häufiger Anfängerfehler, seien Sie sorgsam, wenn Sie einem Datenframe neue Zeilen hinzufügen!

Um also eine neue Zeile korrekt dem Datenframe hinzuzufügen, muss diese neue Zeile ebenfalls als Datenframe in der selben Struktur (also mit den selben Variablen (Spalten) vorliegen. Die

# neue Zeile
neuezeile <- data.frame( factor("m"), "Joe", 99, TRUE)

# übergebe die Spaltennamen an die neue Zeile
colnames(neuezeile) <- colnames(MeinDatenframe)

# füge zu Datenframe hinzu
new <- rbind(MeinDatenframe, neuezeile)

# anzeigen
new

##    geschlecht spitzname hausnummer angemeldet
## 1           m      Hasi          1       TRUE
## 2           w       Ide          2       TRUE
## 3           d     Momsi          3      FALSE
## 4           m       Ryu          4       TRUE
## 5           w      Dave          5      FALSE
## 6           d      Zoid          6      FALSE
## 7           m       Adu          7      FALSE
## 8           w       Efi          8       TRUE
## 9           d       Ole          9       TRUE
## 10          m       Ray         10       TRUE
## 11          w       Sam         11      FALSE
## 12          d       Emi         12       TRUE
## 13          m       Joe         99       TRUE

8.4.2 Variablen (Spalten) hinzufügen

Mit dem Befehl cbind() können dem Datenframe neue Spalten hinzugefügt werden. Hierbei ist wichtig, dass die neue Spalte die selbe Anzahl an Werten aufweist wie die anderen Spalten des Datenframes. Wir erzeugen eine neue Variable und fügen diese als neue Spalte dem Datenframe hinzu.

# neue Variable mit 12 Werten
kinder <- c(1, 4, 3, 1, 2, 3, 2, 1, 4, 2, 3, 4)

# neues Datenframe mit dieser Spalte
new <- cbind(MeinDatenframe, kinder)

# anzeigen
new

##    geschlecht spitzname hausnummer angemeldet kinder
## 1           m      Hasi          1       TRUE      1
## 2           w       Ide          2       TRUE      4
## 3           d     Momsi          3      FALSE      3
## 4           m       Ryu          4       TRUE      1
## 5           w      Dave          5      FALSE      2
## 6           d      Zoid          6      FALSE      3
## 7           m       Adu          7      FALSE      2
## 8           w       Efi          8       TRUE      1
## 9           d       Ole          9       TRUE      4
## 10          m       Ray         10       TRUE      2
## 11          w       Sam         11      FALSE      3
## 12          d       Emi         12       TRUE      4

Wir könnten aber auch einfach schreiben:

MeinDatenframe$kinder <- kinder

8.4.3 Datenframes zusammenführen

Wenn die Variablen (Spalten) von zwei Datensätzen zusammengefügt werden sollen, kann alternativ auch die Funktion merge() verwendet werden. Die Funktion schaut in beiden Datenframes nach einer ID-Variable, anhand derer sie die Daten einander zuordnen kann. In unserem Beispiel kann das die Variable spitzname sein. Ein weiteres Test-Datenframe könnte so aussehen:

## Wir schreiben die Werte direkt ins Datenframe
## Das Ergebnis ist das selbe
test <- data.frame(kinder = c(1, 4, 3, 1, 2, 3, 2, 1, 4, 2, 3, 4),
           spitzname  = c( "Emi", "Dave", "Sam", "Hasi", "Zoid", "Ray", "Ide", "Momsi", "Ryu", "Adu", "Efi", "Ole")
)
# anzeigen
test

##    kinder spitzname
## 1       1       Emi
## 2       4      Dave
## 3       3       Sam
## 4       1      Hasi
## 5       2      Zoid
## 6       3       Ray
## 7       2       Ide
## 8       1     Momsi
## 9       4       Ryu
## 10      2       Adu
## 11      3       Efi
## 12      4       Ole

Wie Sie sehen können, ist die Reihenfolge der Spitznamen eine andere als in MeinDatenframe. Mittels merge() können die beiden Datenframes nun dennoch zusammengefügt werden. Über die Parameter by.x und by.y legen wir fest, anhand welcher Spalten das Zusammenführen ausgerichtet werden soll. In beiden Datensätzen ist dies die Variable spitzname. Daher lautet der Funktionsaufruf:

## Vereine MeinDatenframe und test
## anhand der Spalte spitzname
merge(MeinDatenframe, test,
      by.x = "spitzname", by.y = "spitzname")

##    spitzname geschlecht hausnummer angemeldet kinder
## 1        Adu          m          7      FALSE      2
## 2       Dave          w          5      FALSE      4
## 3        Efi          w          8       TRUE      3
## 4        Emi          d         12       TRUE      1
## 5       Hasi          m          1       TRUE      1
## 6        Ide          w          2       TRUE      2
## 7      Momsi          d          3      FALSE      1
## 8        Ole          d          9       TRUE      4
## 9        Ray          m         10       TRUE      3
## 10       Ryu          m          4       TRUE      4
## 11       Sam          w         11      FALSE      3
## 12      Zoid          d          6      FALSE      2

Die Daten wurden korrekt anhand der Spitznamen zusammengefügt.

8.4.4 Fälle und Variablen auswählen

Ebenso wie bei der Matrix lassen sich die einzelnen Spalten und Zeilen referenzieren, indem wir in eckigen Klammern die gewünschte Position angeben.

# Zeige nur die erste Spalte
MeinDatenframe[,1]

##  [1] m w d m w d m w d m w d
## Levels: d m w

Die einzelnen Spalten des Datenframes lassen sich auch über ihren Namen referenzieren. Hierfür schreiben wir ein Dollarzeichen $ und hängen den Spaltennamen daran.

# Zeige nur Spalte "geschlecht"
MeinDatenframe$geschlecht

##  [1] m w d m w d m w d m w d
## Levels: d m w

# Zeige nur Spalte "angemeldet"
MeinDatenframe$angemeldet

##  [1]  TRUE  TRUE FALSE  TRUE FALSE FALSE FALSE  TRUE  TRUE  TRUE FALSE  TRUE

So können wir auch die jeweiligen Datentypen der Spaltenwerte überprüfen.

# welcher Datentyp liegt in Spalte "angemeldet" vor?
class(MeinDatenframe$angemeldet)

## [1] "logical"

# welcher Datentyp liegt in Spalte "spitzname" vor?
class(MeinDatenframe$spitzname)

## [1] "character"

# welcher Datentyp liegt in der 1. Spalte vor?
class(MeinDatenframe[, 1])

## [1] "factor"

Möchten wir uns die Fälle (also die Zeilen) ausgeben lassen, erfolgt dies mit

# Zeige Fall Nummer 4
MeinDatenframe[4,]

##   geschlecht spitzname hausnummer angemeldet
## 4          m       Ryu          4       TRUE

oder für Fallserien per

# Zeige Fälle Nummer 2 bis 5
MeinDatenframe[2:5,]

##   geschlecht spitzname hausnummer angemeldet
## 2          w       Ide          2       TRUE
## 3          d     Momsi          3      FALSE
## 4          m       Ryu          4       TRUE
## 5          w      Dave          5      FALSE

Wir können auch bedingte Ausgaben erzeugen.

# Zeige nur die Fälle mit "hausnummer" kleiner als 5
MeinDatenframe[MeinDatenframe$hausnummer<5, ]

##   geschlecht spitzname hausnummer angemeldet
## 1          m      Hasi          1       TRUE
## 2          w       Ide          2       TRUE
## 3          d     Momsi          3      FALSE
## 4          m       Ryu          4       TRUE

# Zeige nur die Fälle mit "angemeldet" = TRUE
MeinDatenframe[MeinDatenframe$angemeldet==T, ]

##    geschlecht spitzname hausnummer angemeldet
## 1           m      Hasi          1       TRUE
## 2           w       Ide          2       TRUE
## 4           m       Ryu          4       TRUE
## 8           w       Efi          8       TRUE
## 9           d       Ole          9       TRUE
## 10          m       Ray         10       TRUE
## 12          d       Emi         12       TRUE

Dies kann auch verknüpft werden:

# Zeige nur die Fälle mit "angemeldet" = TRUE # und "hausnummer" größer 6
MeinDatenframe[(MeinDatenframe$angemeldet==T) & (MeinDatenframe$hausnummer>6),]

##    geschlecht spitzname hausnummer angemeldet
## 8           w       Efi          8       TRUE
## 9           d       Ole          9       TRUE
## 10          m       Ray         10       TRUE
## 12          d       Emi         12       TRUE

Beachten Sie, dass wir jedes Mal, wenn wir auf eine Spalte des Datenframes referenzieren möchten, den Datenframe-Namen mit einem Dollarzeichen $ schreiben müssen. Tun wir das nicht, sucht R nach einer Variable im Workspace, und nutzt dann deren Werte. Dies ist ein häufiger Anfängerfehler!

Die ständige Angabe des Datenframes macht die Befehle recht lang.

Mit der Funktion with() können wir uns die Referenzierung mit $ sparen. Wir übergeben der Funktion with() unser Datenframe, und sagen dann, was damit getan werden soll.

# mit Funktion with() wird es leichter
with(MeinDatenframe, MeinDatenframe[hausnummer>4 & angemeldet==F, ])

##    geschlecht spitzname hausnummer angemeldet
## 5           w      Dave          5      FALSE
## 6           d      Zoid          6      FALSE
## 7           m       Adu          7      FALSE
## 11          w       Sam         11      FALSE

# mit Funktion with() wird es leichter
with(MeinDatenframe, spitzname[hausnummer>4 & angemeldet==F])

## [1] "Dave" "Zoid" "Adu"  "Sam"

Das funktioniert in Kombination mit jeder anderen Funktion.

# Summe der Hausnummern
with(MeinDatenframe, sum(hausnummer))

## [1] 78

# Häufigkeit von geschlecht
with(MeinDatenframe, table(geschlecht))

## geschlecht
## d m w
## 4 4 4

Ähnlich wie bei Matrizen können wir die Zeilen- und Spaltentitel anpassen. Mit colnames() können wir die Spalten umbenennen.

# benenne Spalten des Datenframes
colnames(MeinDatenframe) <- c("Sex", "Nickname", "House", "confirmed")

# anzeigen
MeinDatenframe

##    Sex Nickname House confirmed
## 1    m     Hasi     1      TRUE
## 2    w      Ide     2      TRUE
## 3    d    Momsi     3     FALSE
## 4    m      Ryu     4      TRUE
## 5    w     Dave     5     FALSE
## 6    d     Zoid     6     FALSE
## 7    m      Adu     7     FALSE
## 8    w      Efi     8      TRUE
## 9    d      Ole     9      TRUE
## 10   m      Ray    10      TRUE
## 11   w      Sam    11     FALSE
## 12   d      Emi    12      TRUE

Dementsprechend können mit rownames() die Zeilen umbenannt werden.

# benenne Spalten des Datenframes
rownames(MeinDatenframe) <- c("Eins", "Zwei", "Drei", "Vier", "Fünf", "Sechs", "Sieben", "Acht", "Neun", "Zehn", "Elf", "Zwölf")

# anzeigen
MeinDatenframe

##        Sex Nickname House confirmed
## Eins     m     Hasi     1      TRUE
## Zwei     w      Ide     2      TRUE
## Drei     d    Momsi     3     FALSE
## Vier     m      Ryu     4      TRUE
## Fünf     w     Dave     5     FALSE
## Sechs    d     Zoid     6     FALSE
## Sieben   m      Adu     7     FALSE
## Acht     w      Efi     8      TRUE
## Neun     d      Ole     9      TRUE
## Zehn     m      Ray    10      TRUE
## Elf      w      Sam    11     FALSE
## Zwölf    d      Emi    12      TRUE

Das sollte bei einem Datenframe nach dem Tidy Data Prinzip (siehe Kapitel 25))) aber niemals notwendig sein.

Mit der Funktion class() kann die Datenklasse angezeigt werden.

# welcher Datentyp ist "MeinDatenframe"
class(MeinDatenframe)

## [1] "data.frame"

Mit der Funktion is.data.frame() kann zudem geprüft werden, ob ein Objekt ein Faktor ist.

# ist "MeinDatenframe" ein Datenframe?
is.data.frame(MeinDatenframe)

## [1] TRUE

# ist "mymatrix" ein Datenframe?
is.data.frame(mymatrix)

## [1] FALSE

In RStudio werden die Variablen und Datensätze im Datenfenster oben rechts angezeigt (Abbildung 8.2).

Abb. 8.2: Datenfenster rechts oben

Wenn Sie hier auf einen Datensatz klicken, z.B. auf MeinDatenframe, so werden Ihnen die Inhalte (also die Werte) des Datensatzes im Scriptfenster angezeigt (Abbildung 8.3).

Abb. 8.3: Datensatz ansehen

Wir können mit der Funktion as.data.frame() Objekte in ein Datenframe umwandeln. Schauen wir uns erneut die auf Seite erzeugte Matrix der Beschäftigten in den Pflegeberufen an.

# zeige Matrix
Pflegeberufe

##                          1999   2001   2003   2005   2007   2009   2011   2013
## Krankenpflegeassistenz  16624  19061  19478  21537  27731  36481  46517  54371
## Altenpflegehilfe        55770  52710  49727  45776  48326  47903  47978  48363
## Kinderkrankenpflege     47779  48203  48822  48519  49080  49307  48291  48937
## Krankenpflege          430983 436767 444783 449355 457322 465446 468192 472580
## Altenpflege            109161 124879 141965 158817 178902 194195 208304 227154
##                          2015
## Krankenpflegeassistenz  64127
## Altenpflegehilfe        49507
## Kinderkrankenpflege     48913
## Krankenpflege          476416
## Altenpflege            246412

Mit der Funktion as.data.frame() wandeln wir die Matrix in ein Datenframe um.

# wandle Matrix in Datenframe um
Pflegeframe <- as.data.frame(Pflegeberufe)

# anzeigen
Pflegeframe

##                          1999   2001   2003   2005   2007   2009   2011   2013
## Krankenpflegeassistenz  16624  19061  19478  21537  27731  36481  46517  54371
## Altenpflegehilfe        55770  52710  49727  45776  48326  47903  47978  48363
## Kinderkrankenpflege     47779  48203  48822  48519  49080  49307  48291  48937
## Krankenpflege          430983 436767 444783 449355 457322 465446 468192 472580
## Altenpflege            109161 124879 141965 158817 178902 194195 208304 227154
##                          2015
## Krankenpflegeassistenz  64127
## Altenpflegehilfe        49507
## Kinderkrankenpflege     48913
## Krankenpflege          476416
## Altenpflege            246412

Kommen wir noch einmal auf das Konzept Tidy Data zurück (siehe Kapitel 25)). Ein Datenframe sollte möglichst so aufgebaut sein, dass jeweils ein Fall pro Zeile abgebildet wird. Das ist bei unserem Datenframe Pflegeframe aber nicht der Fall.

Ein Datenframe würde sich aus der Matrix jede Beschäftigtenzahl einzeln vornehmen, um dann zu fragen “aus welchem Jahr stammt diese Zahl?” und “aus welcher Berufsgruppe stammt diese Zahl?”. Das heisst, es würde hinterher dieser Struktur folgen:

    Jahr    Berufsgruppe     Wert
1   1999    Krankenpflege    430983
2   2001    Krankenpflege    436767
3   2001    Altenpflege      124879
4   2003    Altenpflegehilfe 49727
            ( . . . )

Man spricht in diesem Zusammenhang von long table und wide table. Die Matrix der Pflegeberufe stellt dabei die wide table, die breite Tabelle dar. “Breit” bedeutet, dass die Tabelle, wenn wir ihr nun 10 weitere Jahrgänge hinzufügen würden, immer breiter und breiter werden würde.

Unser angestrebtes Tidy-Data-Datenframe ist vom Typ long table, da die Tabelle, wenn wir ihr Daten hinzufügen würden, immer länger und länger werden würde.

Wie formen wir unsere Matrix in ein Tidy Data-Datenframe, also in eine long table, um?

Mit der Funktion expand.grid() kann ein Datenframe mit Wertepaaren erzeugt werden. Für unser Beispiel mit den Pflegeberufen brauchen wir im Datenframe je eine Zeile für alle möglichen Kombinationen aus Jahr und Berufsgruppe. Die Funktion expand.grid() erzeugt genau solche Paarungen. Idealerweise sind die benötigten Werte (alle Jahre und alle Berufsgruppen) als Zeilen- und Spaltennamen in der Matrix Pflegeberufe vorhanden.

# erzeuge ein Tidy-Data-Dataframe
# mit allen möglichen Kombinationen
# aus Jahren und Berufsgruppen
new <- expand.grid( colnames(Pflegeberufe), rownames(Pflegeberufe))

# anzeigen
new

##    Var1                   Var2
## 1  1999 Krankenpflegeassistenz
## 2  2001 Krankenpflegeassistenz
## 3  2003 Krankenpflegeassistenz
## 4  2005 Krankenpflegeassistenz
## 5  2007 Krankenpflegeassistenz
## 6  2009 Krankenpflegeassistenz
## 7  2011 Krankenpflegeassistenz
## 8  2013 Krankenpflegeassistenz
## 9  2015 Krankenpflegeassistenz
## 10 1999       Altenpflegehilfe
## 11 2001       Altenpflegehilfe
## 12 2003       Altenpflegehilfe
## 13 2005       Altenpflegehilfe
## 14 2007       Altenpflegehilfe
## 15 2009       Altenpflegehilfe
## 16 2011       Altenpflegehilfe
## 17 2013       Altenpflegehilfe
## 18 2015       Altenpflegehilfe
## 19 1999    Kinderkrankenpflege
## 20 2001    Kinderkrankenpflege
## 21 2003    Kinderkrankenpflege
## 22 2005    Kinderkrankenpflege
## 23 2007    Kinderkrankenpflege
## 24 2009    Kinderkrankenpflege
## 25 2011    Kinderkrankenpflege
## 26 2013    Kinderkrankenpflege
## 27 2015    Kinderkrankenpflege
## 28 1999          Krankenpflege
## 29 2001          Krankenpflege
## 30 2003          Krankenpflege
## 31 2005          Krankenpflege
## 32 2007          Krankenpflege
## 33 2009          Krankenpflege
## 34 2011          Krankenpflege
## 35 2013          Krankenpflege
## 36 2015          Krankenpflege
## 37 1999            Altenpflege
## 38 2001            Altenpflege
## 39 2003            Altenpflege
## 40 2005            Altenpflege
## 41 2007            Altenpflege
## 42 2009            Altenpflege
## 43 2011            Altenpflege
## 44 2013            Altenpflege
## 45 2015            Altenpflege

Mit der Funktion cbind() können wir nun die Zahlenwerte aus der Matrix als neue Spalte an das Datenframe anhängen. Dafür müssen die Werte in Form eines Vektors vorliegen.

Um die Matrix als Vektor auszugeben nutzen wir die Funktion as.vector()

# stelle die Matrix als Vektor dar
as.vector(Pflegeberufe)

##  [1]  16624  55770  47779 430983 109161  19061  52710  48203 436767 124879
## [11]  19478  49727  48822 444783 141965  21537  45776  48519 449355 158817
## [21]  27731  48326  49080 457322 178902  36481  47903  49307 465446 194195
## [31]  46517  47978  48291 468192 208304  54371  48363  48937 472580 227154
## [41]  64127  49507  48913 476416 246412

Wie Sie sehen, überführt R die Matrix spaltenweise in den Vektor. Für unser neues Datenframe bräuchten wir aber einen zeilenorientierten Vektor, damit er mit der Reihenfolge der Einträge (Paarung aus Jahr und Berufsgruppe) übereinstimmt. Um einen reihenorientierten Vektor zu erzeugen muss die Matrix mit der Funktion t() transpoiniert werden.

# stelle die Matrix als Vektor dar
# zeilenorientiert
as.vector(t(Pflegeberufe))

##  [1]  16624  19061  19478  21537  27731  36481  46517  54371  64127  55770
## [11]  52710  49727  45776  48326  47903  47978  48363  49507  47779  48203
## [21]  48822  48519  49080  49307  48291  48937  48913 430983 436767 444783
## [31] 449355 457322 465446 468192 472580 476416 109161 124879 141965 158817
## [41] 178902 194195 208304 227154 246412

Diesen Vektor fügen wir nun per cbind() dem Datenframe als neue Spalte hinzu

# füge Spalte hinzu
Pflegeframe <- cbind(new, as.vector(t(Pflegeberufe)))

# benenne die Spalten neu
colnames(Pflegeframe) <- c("Jahr", "Berufsgruppe", "Anzahl")
# zeige an
Pflegeframe

##    Jahr           Berufsgruppe Anzahl
## 1  1999 Krankenpflegeassistenz  16624
## 2  2001 Krankenpflegeassistenz  19061
## 3  2003 Krankenpflegeassistenz  19478
## 4  2005 Krankenpflegeassistenz  21537
## 5  2007 Krankenpflegeassistenz  27731
## 6  2009 Krankenpflegeassistenz  36481
## 7  2011 Krankenpflegeassistenz  46517
## 8  2013 Krankenpflegeassistenz  54371
## 9  2015 Krankenpflegeassistenz  64127
## 10 1999       Altenpflegehilfe  55770
## 11 2001       Altenpflegehilfe  52710
## 12 2003       Altenpflegehilfe  49727
## 13 2005       Altenpflegehilfe  45776
## 14 2007       Altenpflegehilfe  48326
## 15 2009       Altenpflegehilfe  47903
## 16 2011       Altenpflegehilfe  47978
## 17 2013       Altenpflegehilfe  48363
## 18 2015       Altenpflegehilfe  49507
## 19 1999    Kinderkrankenpflege  47779
## 20 2001    Kinderkrankenpflege  48203
## 21 2003    Kinderkrankenpflege  48822
## 22 2005    Kinderkrankenpflege  48519
## 23 2007    Kinderkrankenpflege  49080
## 24 2009    Kinderkrankenpflege  49307
## 25 2011    Kinderkrankenpflege  48291
## 26 2013    Kinderkrankenpflege  48937
## 27 2015    Kinderkrankenpflege  48913
## 28 1999          Krankenpflege 430983
## 29 2001          Krankenpflege 436767
## 30 2003          Krankenpflege 444783
## 31 2005          Krankenpflege 449355
## 32 2007          Krankenpflege 457322
## 33 2009          Krankenpflege 465446
## 34 2011          Krankenpflege 468192
## 35 2013          Krankenpflege 472580
## 36 2015          Krankenpflege 476416
## 37 1999            Altenpflege 109161
## 38 2001            Altenpflege 124879
## 39 2003            Altenpflege 141965
## 40 2005            Altenpflege 158817
## 41 2007            Altenpflege 178902
## 42 2009            Altenpflege 194195
## 43 2011            Altenpflege 208304
## 44 2013            Altenpflege 227154
## 45 2015            Altenpflege 246412

Der vollständige Code, ohne Hilfsdatenframe new, zur Überführung der Matrix Pflegeberufe in das Tidy-Data-Datenframe Pflegeframe sieht also so aus:

# füge Spalte hinzu
Pflegeframe <- cbind(expand.grid( colnames(Pflegeberufe), rownames(Pflegeberufe)), as.vector(t(Pflegeberufe)))

# benenne die Spalten neu
colnames(Pflegeframe) <- c("Jahr", "Berufsgruppe", "Anzahl")
# zeige erste 12 Fälle an
head(Pflegeframe, 12)

##    Jahr           Berufsgruppe Anzahl
## 1  1999 Krankenpflegeassistenz  16624
## 2  2001 Krankenpflegeassistenz  19061
## 3  2003 Krankenpflegeassistenz  19478
## 4  2005 Krankenpflegeassistenz  21537
## 5  2007 Krankenpflegeassistenz  27731
## 6  2009 Krankenpflegeassistenz  36481
## 7  2011 Krankenpflegeassistenz  46517
## 8  2013 Krankenpflegeassistenz  54371
## 9  2015 Krankenpflegeassistenz  64127
## 10 1999       Altenpflegehilfe  55770
## 11 2001       Altenpflegehilfe  52710
## 12 2003       Altenpflegehilfe  49727

8.5 Listen

In der Datenklasse Listen können beliebige Datenobjekte (Vektor, Faktor, Datenframe, Matrizen) zusammengefasst werden. Listen sind also eine Ansammlung an Datenobjekten, ähnlich wie ein Schrank oder ein Koffer, in welchem man “sein Zeug” ablegt. Wir generieren testweise eine Liste aus den Datenobjekten, die wir bislang erzeugt haben. Dies erfolgt in mit der Funktion list().

# erzeuge eine Liste aus den Datenobjekten
# "MeinDatenframe", "mymatrix", "geschlecht" und "logical"
MeineListe <- list(MeinDatenframe, mymatrix, geschlecht, logical)

# anzeigen
MeineListe

## [[1]]
##        geschlecht spitzname hausnummer angemeldet
## Eins            m      Hasi          1       TRUE
## Zwei            w       Ide          2       TRUE
## Drei            d     Momsi          3      FALSE
## Vier            m       Ryu          4       TRUE
## Fünf            w      Dave          5      FALSE
## Sechs           d      Zoid          6      FALSE
## Sieben          m       Adu          7      FALSE
## Acht            w       Efi          8       TRUE
## Neun            d       Ole          9       TRUE
## Zehn            m       Ray         10       TRUE
## Elf             w       Sam         11      FALSE
## Zwölf           d       Emi         12       TRUE
##
## [[2]]
##      a  b  c  d  e
## I   11 21 31 41 51
## II  12 22 32 42 52
## III 13 23 33 43 53
## IV  14 24 34 44 54
## V   15 25 35 45 55
##
## [[3]]
##  [1] m w d m w d m w d m w d
## Levels: d m w
##
## [[4]]
## function (length = 0L)
## .Internal(vector("logical", length))
## <bytecode: 0x562b4e59c598>
## <environment: namespace:base>

Wie Sie sehen, werden die einzelnen Positionen der Datenobjekte durch doppelte eckige Klammer angezeigt ([[1]] ist unser Datenframe, [[2]] unsere Matrix, usw.) und können über diese auch referenziert werden.

# zeige Objekt 1 (= unser Datenframe)
MeineListe[[1]]

##        geschlecht spitzname hausnummer angemeldet
## Eins            m      Hasi          1       TRUE
## Zwei            w       Ide          2       TRUE
## Drei            d     Momsi          3      FALSE
## Vier            m       Ryu          4       TRUE
## Fünf            w      Dave          5      FALSE
## Sechs           d      Zoid          6      FALSE
## Sieben          m       Adu          7      FALSE
## Acht            w       Efi          8       TRUE
## Neun            d       Ole          9       TRUE
## Zehn            m       Ray         10       TRUE
## Elf             w       Sam         11      FALSE
## Zwölf           d       Emi         12       TRUE

# zeige Objekt 3 (= variable "geschlecht")
MeineListe[[3]]

##  [1] m w d m w d m w d m w d
## Levels: d m w

Die Werte der jeweiligen Objekte können anschließend wie gewohnt referenziert werden.

# zeige Objekt 3, aber nur den 5. Wert
MeineListe[[3]][5]

## [1] w
## Levels: d m w

# zeige Objekt 2, aber nur die 3. Spalte
MeineListe[[2]][, 3]

##   I  II III  IV   V
##  31  32  33  34  35

9 Fehlende Daten

Fehlende Daten werden in R mit dem Ausdruck NA (für “not available”) dargestellt. Angenommen Sie erfragen von zehn Personen das Alter, und zwei Personen geben keine Auskunft, dann könnten man das wie folgt in einem Vektor darstellen.

# Alter von 10 Personen
alter <- c(28, 30, NA, 21, 27, 43, NA, 35, 22, 18)
# anzeigen
alter

##  [1] 28 30 NA 21 27 43 NA 35 22 18

Möchten wir nun die Summe der Alterswerte bestimmen, meldet R uns NA zurück

# Summe der Altersangaben
sum(alter)

## [1] NA

Die Berechnung wird nicht durchgeführt, da fehlende Werte in der Reihe vorkommen. Möchte man die Berechnung erzwingen, kann (bei fast jeder -Funktion) der Parameter na.rm (für NA remove; entferne NA) auf TRUE gesetzt werden. So werden alle fehlenden Daten ignoriert und die Berechnungen nur mit der Restmenge durchgeführt.

# Summe der Altersangaben OHNE NA
sum(alter, na.rm=TRUE)

## [1] 224

Mit der Funktion is.na() kann geprüft werden, ob fehlende Werte enthalten sind

# sind Werte in "alter" NA?
is.na(alter)

##  [1] FALSE FALSE  TRUE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE

Möchten wir wissen, an welcher Position sich die NA-Einträge befinden können wir die Funktion which() mit is.na() kombinieren

# welche Position haben die NAs in "alter"
which(is.na(alter))

## [1] 3 7

Sind keine NAs enthalten, meldet R ein integer(0) zurück

# keine NAs in "Pflegeframe"
which(is.na(Pflegeframe))

## integer(0)

10 Umgang mit Datensätzen

Mit der Funktion str() (für structure) erhalten wir eine erste Beschreibung des Datensatzes.

# Übersicht von "MeinDatenframe"
str(MeinDatenframe)

## 'data.frame':    12 obs. of  4 variables:
##  $ geschlecht: Factor w/ 3 levels "d","m","w": 2 3 1 2 3 1 2 3 1 2 ...
##  $ spitzname : chr  "Hasi" "Ide" "Momsi" "Ryu" ...
##  $ hausnummer: int  1 2 3 4 5 6 7 8 9 10 ...
##  $ angemeldet: logi  TRUE TRUE FALSE TRUE FALSE FALSE ...

Das Datenframe MeinDatenframe enthält 12 Beobachtungen von 4 Variablen. Varibale geschlecht ist ein Faktor mit 3 Ausprägungen, usw.

In RStudio werden diese Informationen im Datenfenster angezeigt (Abbildung 10.1).

Abb. 10.1: str() im Datenfenster

Mit der Funktion head() können Sie sich einen weiteren Überblick über den Datensatz verschaffen. Die Funktion head() gibt die ersten 6 Zeilen des Datensatzes aus.

# zeige die ersten 6 Reihen von "MeinDatenframe"
head(MeinDatenframe)

##       geschlecht spitzname hausnummer angemeldet
## Eins           m      Hasi          1       TRUE
## Zwei           w       Ide          2       TRUE
## Drei           d     Momsi          3      FALSE
## Vier           m       Ryu          4       TRUE
## Fünf           w      Dave          5      FALSE
## Sechs          d      Zoid          6      FALSE

Möchten wir mehr als 6 Einträge sehen, können wir dies an head() übergeben:

# zeige die ersten 10 Reihen von "MeinDatenframe"
head(MeinDatenframe, 10)

##        geschlecht spitzname hausnummer angemeldet
## Eins            m      Hasi          1       TRUE
## Zwei            w       Ide          2       TRUE
## Drei            d     Momsi          3      FALSE
## Vier            m       Ryu          4       TRUE
## Fünf            w      Dave          5      FALSE
## Sechs           d      Zoid          6      FALSE
## Sieben          m       Adu          7      FALSE
## Acht            w       Efi          8       TRUE
## Neun            d       Ole          9       TRUE
## Zehn            m       Ray         10       TRUE

Im Gegensatz dazu zeigt tail() die letzten Reihen des Datenframes an.

# zeige die letzten 5 Reihen von "MeinDatenframe"
tail(MeinDatenframe, 5)

##       geschlecht spitzname hausnummer angemeldet
## Acht           w       Efi          8       TRUE
## Neun           d       Ole          9       TRUE
## Zehn           m       Ray         10       TRUE
## Elf            w       Sam         11      FALSE
## Zwölf          d       Emi         12       TRUE

10.1 Sortieren

Mit der Funktion sort() können Datenreihen auf- und absteigend sortiert werden. Standardmäßig wird aufsteigend sortiert

# sortiere "hausnummer" von "MeinDatenframe"
# aufsteigend
sort(MeinDatenframe$hausnummer)

##  [1]  1  2  3  4  5  6  7  8  9 10 11 12

Um absteigend zu sortieren, muss der Parameter decreasing auf TRUE gesetzt werden

# sortiere "spitzname" von "MeinDatenframe"
# absteigend
sort(MeinDatenframe$spitzname, decreasing=T)

##  [1] "Zoid"  "Sam"   "Ryu"   "Ray"   "Ole"   "Momsi" "Ide"   "Hasi"  "Emi"
## [10] "Efi"   "Dave"  "Adu"

Mit der Funktion order() werden die Zeilenpositionen ausgegeben, an deren Stelle sich die von sort() sortierten Daten befinden.

# sortiere "spitznamen" von "MeinDatenframe"
# aufsteigend, und zeige Positionen an
order(MeinDatenframe$spitzname)

##  [1]  7  5  8 12  1  2  3  9 10  4 11  6

Das kann man nun verwenden, um den kompletten Datensatz in dieser Ordnung anzuzeigen. Wir können ja durch Anhängen von eckigen Klammern die gewünschten Zeilen und Spalten des Datenframes referenzieren. Also übergeben wir die Positionsreihenfolge von order() als Argument in die eckige Klammer.

# sortiere "spitznamen" von "MeinDatenframe"
# aufsteigend, zeige ganzes Datenframe an
MeinDatenframe[ order(MeinDatenframe$spitzname), ]

##        geschlecht spitzname hausnummer angemeldet
## Sieben          m       Adu          7      FALSE
## Fünf            w      Dave          5      FALSE
## Acht            w       Efi          8       TRUE
## Zwölf           d       Emi         12       TRUE
## Eins            m      Hasi          1       TRUE
## Zwei            w       Ide          2       TRUE
## Drei            d     Momsi          3      FALSE
## Neun            d       Ole          9       TRUE
## Zehn            m       Ray         10       TRUE
## Vier            m       Ryu          4       TRUE
## Elf             w       Sam         11      FALSE
## Sechs           d      Zoid          6      FALSE

Oder andersherum

# sortiere "spitznamen" von "MeinDatenframe"
# absteigend, zeige ganzes Datenframe an
MeinDatenframe[order(MeinDatenframe$spitzname, decreasing=T), ]

##        geschlecht spitzname hausnummer angemeldet
## Sechs           d      Zoid          6      FALSE
## Elf             w       Sam         11      FALSE
## Vier            m       Ryu          4       TRUE
## Zehn            m       Ray         10       TRUE
## Neun            d       Ole          9       TRUE
## Drei            d     Momsi          3      FALSE
## Zwei            w       Ide          2       TRUE
## Eins            m      Hasi          1       TRUE
## Zwölf           d       Emi         12       TRUE
## Acht            w       Efi          8       TRUE
## Fünf            w      Dave          5      FALSE
## Sieben          m       Adu          7      FALSE

10.2 Teilgruppen erstellen

Mit der Funktion subset() können Teilgruppen aus einem Datenframe erstellt werden.

# erstelle eine Teilgruppe aus MeinDatenframe
# für "geschlecht" = d
subset(MeinDatenframe, geschlecht=="d")

##       geschlecht spitzname hausnummer angemeldet
## Drei           d     Momsi          3      FALSE
## Sechs          d      Zoid          6      FALSE
## Neun           d       Ole          9       TRUE
## Zwölf          d       Emi         12       TRUE

Über den Parameter select kann angegeben werden, welche Spalten übernommen werden sollen.

# erstelle eine Teilgruppe aus MeinDatenframe
# für "geschlecht" = d
# aber nur Spalte "spitzname" und "angemeldet"
subset(MeinDatenframe, geschlecht=="d", select = c(spitzname, angemeldet))

##       spitzname angemeldet
## Drei      Momsi      FALSE
## Sechs      Zoid      FALSE
## Neun        Ole       TRUE
## Zwölf       Emi       TRUE

Eine andere Möglichkeit zur Erstellung von Teilgruppen kann über die Referenzierung der Zeilen und Spalten des Datenframes (eckige Klammern) erfolgen

# erstelle eine Teilgruppe aus MeinDatenframe
# für "geschlecht" = w
MeinDatenframe[ MeinDatenframe$geschlecht == "w", ]

##      geschlecht spitzname hausnummer angemeldet
## Zwei          w       Ide          2       TRUE
## Fünf          w      Dave          5      FALSE
## Acht          w       Efi          8       TRUE
## Elf           w       Sam         11      FALSE

Auf diese Weise können die Bedingungen auch mit einem & kombiniert werden

# erstelle eine Teilgruppe aus MeinDatenframe
# für "geschlecht" = w und "hausnummer" größer 5
MeinDatenframe[(MeinDatenframe$geschlecht == "w") & (MeinDatenframe$hausnummer > 5) ,]

##      geschlecht spitzname hausnummer angemeldet
## Acht          w       Efi          8       TRUE
## Elf           w       Sam         11      FALSE

Über Angabe der Spaltennummern können wir das Teilset noch weiter zusammenschrumpfen lassen

# erstelle eine Teilgruppe aus MeinDatenframe
# für "geschlecht" = w und "angemeldet" = TRUE
# aber nur Spalte "spitzname" und "hausnummer"
MeinDatenframe[(MeinDatenframe$geschlecht == "w") & (MeinDatenframe$angemeldet== T), c(2,3)]

##      spitzname hausnummer
## Zwei       Ide          2
## Acht       Efi          8

Möchte man auf die Referenzierung per Dollarzeichen $ verzichten, kann die Funktion with() verwendet werden.

# with() macht es leichter
with(MeinDatenframe, MeinDatenframe[(geschlecht == "w") & (angemeldet == T), c(2,3)])

##      spitzname hausnummer
## Zwei       Ide          2
## Acht       Efi          8

11 Pipe

Ein wichtiger Operator für den Umgang mit Datensätzen ist die Pipe. Diese ist fester Bestandteil des Tidyverse (siehe Abschnitt 25.2) und hat sich dort so bewährt, dass sie seit R Version 4.1 ebenfalls im “Standard”-R implementiert ist.

Die Pipe erlaubt es, Datenströme weiterzuleiten.

Hierfür wurde ein eigener Operator implementiert, die Zeichenkette |>

Sie bedeutet so viel wie “und dann”.

Zur Eklärung sei der “klassische Weg” der Arbeitsschritte in R aufgezeigt:

# wir machen etwas, und speichern es in ein Objekt
habe.getan <- mache.etwas(Datensatz)

# dann machen wir etwas weiteres,
# und speichern wieder in ein Objekt
habe.weiteres.getan <- mache.weiteres(habe.getan)

# und kommen schließlich zum Endergebnis
endergebnis <- mache.noch.letzte.Sache(habe.weiteres.getan)

Mit Einsatz der Pipe wird dieser Prozess quasi umgekehrt in

# Speichere Endergebnis
endergebnis <- Datensatz |>
                gruppiere.nach.geschlecht() |>
                  sortiere Alter aufsteigend() |>
                    nimm die letzten 4 Werte()

Die liest sich in etwa so:

“Speichere etwas ins Objekt endergebnis, und das geht so…

nimm den Datensatz und dann

gruppiere die Daten nach geschlecht und dann

sortiere nach Alter aufsteigend und dann

nimm die letzten 4 Werte. ”

Die Pipe reicht das jeweilige Ergebnis (den Datenstrom) an die nächste Code-Zeile weiter. Erst wenn die letzte Zeile durchgelaufen ist, wird das Resultat in endergebnis gespeichert.

Dies liest sich zu Beginn evtl. etwas ungewohnt, aber Sie können erkennen, dass die Befehle so wesentlich übersichtlicher und die einzelnen Manipulationsschritte nachvollziehbarer geworden sind. Auch kann man sich diese Art der “Grammatik” relativ leicht merken.

Standardmäßig übergibt die Pipe das Objekt auf der linken Seite an das erste Argument der Funktion auf der rechten Seite, so dass x |> f(y) äquivalent zu f(x, y) ist, und x |> f(y) |> g(z) äquivalent zu g(f(x, y), z).

Mehr über die “Original”-Pipe %>% des Tidyverse sowie zu den Unterschieden zur Standard-Pipe |> finden Sie in Abschnitt 25.2.

In RStudio können Sie einstellen, ob Sie mit der Tastenkombination [STRG] + [SHFIT] + [M] die Tidyverse-Pipe oder die Standard-Pipe erzeugen möchten. Wählen Sie hierzu im Menü “Tools” →\rightarrow “Global Option” →\rightarrow “Code” aus, siehe Abbildung 11.1

Abb. 11.1: Tastenkombination für Pipe

Sie sollten die Standard-Pipe nutzen, weil sie Teil von Base R ist und Ihnen immer zur Verfügung steht, auch wenn Sie Tidyverse nicht verwenden. Zudem ist |> um einiges schneller als %>%.

12 Daten laden und speichern

In R können Daten leicht geladen und gespeichert werden. Eine R-Datei hat die Dateiendung .RData. In der Datei können “beliebig” viele Objekte (Variablen, Datenframes, usw.) enthalten sein.

Mit der Funktion save.image() werden alle Objekte im Speicher, also die aktuelle “Worksession”, als “unsichtbare” Datei .RData ins aktuelle Arbeitsverzeichnis geschrieben.

# speichere die aktuelle Sitzung
save.image()

In RStudio können Sie im Ausgabefenster (unten rechts) auf den Reiter Files klicken. Sie sehen Ihr aktuelles Arbeitsverzeichnis, und darin die R-Datei .RData (siehe Abbildung 12.1).

Abb. 12.1: Datei .RData im Arbeitsverzeichnis

Das ist übrigens die selbe Datei, die angelegt wird, wenn Sie RStudio beenden, und “speichere Arbeitsspeicher” klicken. Das heisst, dass diese Datei unter Umständen bald wieder überschrieben wird.

Der Funktion save.image() kann auch ein gewünschter Dateiname übergeben werden.

# speichere die aktuelle Sitzung in Datei "MeineSitzung.RData"
save.image("MeineSitzung.RData")

Die Datei MeineSitzung.RData wird in RStudio im Ausgabefenster angezeigt (siehe Abbildung 12.2).

Abb. 12.2: Datei MeineSitzung.RData im Arbeitsverzeichnis

Wenn nur einzelne Objekte gespeichert werden sollen, wird die save()-Funktion angewandt. Mit dem file-Parameter wird die gewünschte Zieldatei angegeben.

# speichere nur die Variable "sequenz"
# in Datei "Sequenz.RData"
save(sequenz, file="Sequenz.RData")

# speichere die Objekte "sequenz" "normal" und "geschlecht"
# in Datei "MeineVariablen.RData"
save(sequenz, normal, geschlecht, file="MeineVariablen.RData")

Daten können mit dem load()-Befehl geladen werden.

# lade Datei "MeineVariablen.RData"
load("MeineVariablen.RData")

In RStudio kann im Ausgabefenster unter dem Reiter Files einfach auf eine R-Datei geklickt werden, um diese zu laden (Abbildung 12.3).

Abb. 12.3: Dateien im Arbeitsverzeichnis

Wenn die Datei im Internet liegt, muss sie mit Hilfe der url()-Funktion geladen werden

# Test-Datensatz aus dem Internet laden # (dieser enthält das Datenframe "ordinalSample")
load(url("https://www.produnis.de/R/data/OrdinalSample.RData"))

# Daten anschauen mit head(ordinalSample)
head(ordinalSample)

13 Arbeitsverzeichnis

Das aktuelle Arbeitsverzeichnis kann per getwd() angezeigt werden.

# Arbeitsverzeichnis ausgeben
getwd()

## [1] "/home/produnis/Dokumente/Programmierung/R/R-Buch/R-buch-quarto"

Mit dem Befehl dir() werden die Dateien des aktuellen Arbeitsverzeichnisses angezeigt.

# Inhalt des Arbeitsverzeichnis ausgeben
dir()

##  [1] "_book"                            "_cache"
##  [3] "_common.R"                        "_freeze"
##  [5] "_quarto.yml"                      "apa-single-spaced.csl"
##  [7] "apply.qmd"                        "arbeitsverzeichnis_files"
##  [9] "arbeitsverzeichnis.html"          "arbeitsverzeichnis.qmd"
## [11] "arbeitsverzeichnis.rmarkdown"     "ausgabe-in-datei.qmd"
## [13] "book.bib"                         "cover.png"
## [15] "coverhsnr.png"                    "credits.qmd"
## [17] "data"                             "daten-exportieren.qmd"
## [19] "daten-importieren.qmd"            "daten-laden-speichern.html"
## [21] "daten-laden-speichern.qmd"        "datenklassen.html"
## [23] "datenklassen.qmd"                 "datentypen.html"
## [25] "datentypen.qmd"                   "deskriptive-statistik_files"
## [27] "deskriptive-statistik.qmd"        "diagramme-plotten_files"
## [29] "diagramme-plotten.qmd"            "eigene-funktionen.qmd"
## [31] "einleitung.html"                  "einleitung.qmd"
## [33] "favicon.ico"                      "fehlende-daten.html"
## [35] "fehlende-daten.qmd"               "generate.sitemap.sh"
## [37] "ggplot_files"                     "ggplot.qmd"
## [39] "google140aa36c32deeebd.html"      "grundelgende-r-befehle.html"
## [41] "grundelgende-r-befehle.qmd"       "images"
## [43] "index.html"                       "index.qmd"
## [45] "installation.html"                "installation.qmd"
## [47] "literaturverzeichnis.qmd"         "markdown.qmd"
## [49] "pf8sub.RData"                     "produnis.scss"
## [51] "quarto_files"                     "quarto.qmd"
## [53] "R-buch-quarto.Rproj"              "r-und-rstudio.html"
## [55] "r-und-rstudio.qmd"                "rbase-pipe.html"
## [57] "rbase-pipe.qmd"                   "rechnen-mit-r.html"
## [59] "rechnen-mit-r.qmd"                "references.bib"
## [61] "references.qmd"                   "resources"
## [63] "Rmarkdown.qmd"                    "robots.txt"
## [65] "schliessende-statistik_files"     "schliessende-statistik.qmd"
## [67] "site_libs"                        "sitemap.xml"
## [69] "statistik-mit-R.qmd"              "tidy-dataimport.qmd"
## [71] "tidy-datentidy.qmd"               "tidy-datenumgang_files"
## [73] "tidy-datenumgang.qmd"             "tidy-ergebnissekommunizieren.qmd"
## [75] "tidyverse_files"                  "tidyverse.qmd"
## [77] "umgang-mit-datensaetzen.html"     "umgang-mit-datensaetzen.qmd"
## [79] "variablen.html"                   "variablen.qmd"
## [81] "verteilungsfunktionen_files"      "verteilungsfunktionen.qmd"
## [83] "vgwort"                           "vgwort.html"
## [85] "weitere-hilfsmittel.qmd"          "wertesequenzen.html"
## [87] "wertesequenzen.qmd"               "will-covid19_files"
## [89] "will-covid19.qmd"                 "will-daten-manipulieren.qmd"
## [91] "will-diagramme-plotten_files"     "will-diagramme-plotten.qmd"
## [93] "will-pakete-verwalten.qmd"        "will-quarto.qmd"
## [95] "will-referenztabellen_files"      "will-referenztabellen.qmd"
## [97] "will-Rmarkdown.qmd"               "zusatzpakete.qmd"

Mit dem Befehl setwd() kann das aktuelle Arbeitsverzeichnis gewechselt werden.

# Arbeitsverzeichnis wechseln
setwd("/tmp/R/")

Auf Dauer kann es einfacher sein, ein eigenes Arbeitsverzeichnis beim Startvorgang einzustellen (siehe Abschnitt 2.2.1).

14 Daten importieren

In R können Daten auf vielfältige Weise importiert werden. Das Grundproblem beim “Tausch” von Daten zwischen verschiedenen Anwendungen (z.B. SPSS oder Statistica) liegt im verwendeten Format. So können native SPSS-Dateien nur mit einem Zusatzpaket importiert werden, für Statistica gibt es ein solches Zusatzpaket (derzeit) nicht. In beiden Fällen ist es aber möglich, die Daten über das CSV-Format zu tauschen. Hierfür speichert man in SPSS den Datensatz als CSV-Datei ab und liest diese in R ein.

Bedenken Sie ebenfalls, dass R Dezimalstellen mit einem Punkt angibt, und nicht mit einem Komma. So kann es zu Fehlern kommen, wenn die zu importierenden Werte nicht ebenfalls die Dezimalstellen mit einem Punkt angeben! Dies ist ein weiterer häufiger Anfängerfehler!

14.1 Import aus Textdateien

In einer Textdatei (mit Dateiendung .txt) liegen unsere Daten wie folgt vor:

m 28 80 170
w 18 55 174
w 25 74 183
m 29 101 190
m 21 84 185
w 19 74 178
w 27 65 169
w 26 56 163
m 31 88 189
m 22 78 184

Angenommen, diese Textdatei hieße Datentabelle.txt, und läge in unserem Arbeitsverzeichnis, dann können wir die Daten mit der Funktion read.table() in R einlesen.

# importiere Daten aus "Datentabelle.txt"
MeineTabelle <- read.table("Datentabelle.txt", header=TRUE)

## Warning in file(file, "rt"): kann Datei 'Datentabelle.txt' nicht öffnen: Datei
## oder Verzeichnis nicht gefunden

## Error in file(file, "rt"): kann Verbindung nicht öffnen

# ausgeben
MeineTabelle

## Error in eval(expr, envir, enclos): Objekt 'MeineTabelle' nicht gefunden

Liegt die Datei im Internet, kann die Adresse mit der Funktion url() übergeben werden.

# importiere Daten aus "Datentabelle.txt"
MeineTabelle <- read.table(url("https://www.produnis.de/R/data/Datentabelle.txt"), header=TRUE)

# ausgeben
MeineTabelle

##    Geschlecht Alter Gewicht Groesse
## 1           m    28      80     170
## 2           w    18      55     174
## 3           w    25      74     183
## 4           m    29     101     190
## 5           m    21      84     185
## 6           w    19      74     178
## 7           w    27      65     169
## 8           w    26      56     163
## 9           m    31      88     189
## 10          m    22      78     184

Der Parameter header=TRUE gibt an, dass in der ersten Zeile der Textdatei die Spaltennamen (Variablennamen) angegeben sind.

Da wir sonst keine Parameter übergeben haben, geht R davon aus, dass die Werte durch ein Leerzeichen getrennt sind, was in unserem Beispiel auch stimmt. Sollte die Tabelle in diesem Format vorliegen (die Daten sind mit einem Komma getrennt) …

m,28,80,170
w,18,55,174
w,25,74,183
m,29,101,190
m,21,84,185
w,19,74,178
w,27,65,169
w,26,56,163
m,31,88,189
m,22,78,184

…dann müssen wir mit der Option sep (für Datenseparator) angeben, dass die Daten mit einem Komma getrennt sind.

# importiere Daten aus "Datentabelle.txt"
# Daten sind mit Komma getrennt
MeineTabelle <- read.table("Datentabelle.txt", sep=",", header=TRUE)

In beiden Fällen wurde die Texttabelle als Datenframe in R importiert. Die Funktion class() bestätigt uns dies.

# welche Datenklasse wurde erzeugt?
class(MeineTabelle)

## [1] "data.frame"

Aber welche Datentypen wurden in den Spalten erzeugt?

# welcher Datentyp liegt in Spalte 1 vor?
class(MeineTabelle[,1])

## [1] "character"

# welcher Datentyp liegt in Spalte "Alter" vor?
class(MeineTabelle$Alter)

## [1] "integer"

Die Spalte Geschlecht wurde als character angelegt, die Variable Alter ist numerisch.

Möchten wir Geschlecht in einen Faktor umwandeln, ``überschreiben’’ wir einfach die Spalte entsprechend:

# "Geschlecht" soll factor werden
MeineTabelle$Geschlecht <- factor(MeineTabelle$Geschlecht)

# überprüfen, ob es geklappt hat
class(MeineTabelle$Geschlecht)

## [1] "factor"

14.2 Import aus CSV-Datei

Das CSV-Format (für comma-separated-values) wird von vielen Anwendungen unterstützt. So können auch Tabellen, die in LibreOffice oder Excel angelegt wurden, als CSV-Datei gespeichert werden. Wenn Sie eine Datei im CSV-Format speichern, fragt Sie Ihre Anwendung nach dem gewünschten Datenseparator, also nach dem Zeichen, durch welches die Werte voneinander getrennt sind. Standardmäßig wird hier ein Komma “,” verwendet (wie der Name CSV vermuten lässt, und was zusätzlich sicherstellt, dass die zu importierenden Daten ihre Dezimalstellen mit einem Punkt abbilden, denn sonst würde nach jedem Dezimalkomma ein neuer Wert beginnen!) oder ein Semikolon “;” . Ich persönlich verwende gerne das Semikolon.

Der Import in R erfolgt ebenfalls über die Funktion read.table(). Neben dem Parameter header muss ihr der eingestellte Datenseparator übergeben werden.

Angenommen, die CSV-Datei hieße DieDaten.csv, läge in unserem Arbeitsverzeichnis und hätte den Datenseparator “;”, dann erfolgt der Import per read.table() mit dem Befehl:

# importiere Daten aus "DieDaten.csv"
NeueTabelle <- read.table("DieDaten.csv", sep=";", header=TRUE)

## Warning in file(file, "rt"): kann Datei 'DieDaten.csv' nicht öffnen: Datei oder
## Verzeichnis nicht gefunden

## Error in file(file, "rt"): kann Verbindung nicht öffnen

Liegt die Datei im Internet, kann die Adresse per url() Funktion übergeben werden.

# importiere Daten aus dem Internet
NeueTabelle <- read.table(url("http://www.produnis.de/R/data/DieDaten.csv"), sep=";", header=TRUE)

# anzeigen
NeueTabelle

##     Name   Geschlecht Lieblingsfarbe Einkommen
## 1   Hans    maennlich          gruen      1233
## 2   Caro     weiblich           blau       800
## 3   Lars intersexuell           gelb      2400
## 4   Ines     weiblich        schwarz      4000
## 5 Samira     weiblich           gelb       899
## 6  Peter    maennlich          gruen      1100
## 7  Sarah     weiblich           blau      1900

Sollte die CSV-Datei mit dem Datenseparator TAB (für Tabulator) erstellt worden sein, lautet der Befehl:

# importiere Daten # Daten sind mit TAB separiert!
NeueTabelle <- read.table("DieDaten.csv", sep=""͡, header=TRUE)

(Separieren Sie Ihre Daten möglichst niemals mittels Tabulator, da es hier zu ungewollten Effekten kommen kann.)

Sollte read.table)() keine brauchbaren Daten erzeugen (z.B. wegen Sonderzeichen in den Daten oder wenn die Daten per TAB separiert wurden), kann auf die Funktion read.csv() zurückgegriffen werden. Letztendlich ruft diese Funktion intern auch “nur” read.table() auf, jedoch mit jeder Menge voreingestellter Parameter.

# importiere Daten mit read.csv() # Daten sind mit TAB separiert!
NeueTabelle <- read.csv("DieDaten.csv", sep=""͡, header=TRUE)

14.3 Import aus SPSS-Datei

Um native SPSS-Dateien in R zu importieren, müssen zunächste Zusatzpakete installiert werden, siehe hierzu Kapitel 16.

Zum Import von SPSS-Dateien installieren und aktivieren wir das Paket haven.

# installiere das Zusatzpaket "haven"
install.packages("haven")

# aktiviere das Paket "haven"
library("haven")

Das Paket haven bietet für jeden SPSS-Dateityp die passende Funktion an. Angenommen, die SPSS-Datei hieße “alteDaten.sav” und läge in unserem Arbeitsverzeichnis, dann kann sie mit der Funktion read_sav() in R importiert werden.

# importiere SPSS-Datei "alteDaten.sav"
alteDaten <- read_sav("alteDaten.sav")

## Error: 'alteDaten.sav' does not exist in current working directory ('/home/produnis/Dokumente/Programmierung/R/R-Buch/R-buch-quarto').

In SPSS werden kategoriale Daten häufig mit Labels versehen.

# Lade Test-SPSS-Datei mit Labels
spss <- haven::read_sav(url("https://www.produnis.de/R/data/alteDaten-lang.sav"))
head(spss)

## # A tibble: 6 × 6
##   v1                 v2                 v3            v4       v5       thetruth
##   <dbl+lbl>          <dbl+lbl>          <dbl+lbl>     <dbl+lb> <dbl+lb> <dbl+lb>
## 1 4 [strongly agree] 4 [strongly agree] 4 [strongly … NA       NA       4 [stro…
## 2 4 [strongly agree] 4 [strongly agree] 4 [strongly … NA       NA       4 [stro…
## 3 4 [strongly agree] 4 [strongly agree] 4 [strongly … NA       NA       4 [stro…
## 4 4 [strongly agree] 3 [agree]          3 [agree]      4 [str…  4 [str… 4 [stro…
## 5 3 [agree]          3 [agree]          2 [disagree]   2 [dis…  4 [str… 4 [stro…
## 6 2 [disagree]       3 [agree]          3 [agree]      2 [dis…  4 [str… 4 [stro…

Wir sehen, dass die Werte der Spalten gelabelt sind (4=strongly agree, 3=agree, usw.). Die Labels stehen in eckigen Klammern neben dem eingentlichen Wert der Variable.

Die enthaltenen Labels kann man sich mit der Funktion attr() anzeigen lassen.

# Namen der Variablen
attr(spss, "names")

## [1] "v1"       "v2"       "v3"       "v4"       "v5"       "thetruth"

Das Label einer Variable wird angezeigt mit

# Label der Variablen
attr(spss$v1, "label")

## [1] "Blue Question"

Die Labels innerhalb einer Variable mit

# Label der Variablenwerte für "v1"
attr(spss$v1, "labels")

## strongly disagree          disagree             agree    strongly agree
##                 1                 2                 3                 4
##         no answer
##                 9

In R ist die Verwendung von Wertelabels eher untypisch. Es empfiehlt sich, die Ausprägungsstufen so wie sie sind als Werte einzutragen, also z.B. direkt “männlich” - “weiblich” - “divers” anstatt 0 - 1 - 2 und anschließender Labelung. So werden die Ausprägungen auch auf den Grafiken entsprechend “aussagekräftig” angezeigt.

Wir wandeln die Variablen in Faktoren um, welche die Labels als Levelnamen nutzen (die Funktion mutate() wird später im Abschnitt Tidyverse (siehe Kapitel 25) genauer erläutert. Der Punkt innerhalb der as_factor()-Funktion bedeutet, dass der Datenstrom der Pipe verwendet werden soll).

library(tidyverse)
spss %>%
  mutate(haven::as_factor(.))

## # A tibble: 45,278 × 6
##   v1             v2             v3             v4             v5        thetruth
##   <fct>          <fct>          <fct>          <fct>          <fct>     <fct>
## 1 strongly agree strongly agree strongly agree <NA>           <NA>      strongl…
## 2 strongly agree strongly agree strongly agree <NA>           <NA>      strongl…
## 3 strongly agree strongly agree strongly agree <NA>           <NA>      strongl…
## 4 strongly agree agree          agree          strongly agree strongly… strongl…
## 5 agree          agree          disagree       disagree       strongly… strongl…
## 6 disagree       agree          agree          disagree       strongly… strongl…
## # ℹ 45,272 more rows

Weitere Informationen zum Umgang mit gelabelten Datensätzen erhalten Sie im Tidyverse-Abschnitt zum Datenimport (siehe Abschnitt 26.1).

14.4 Import aus Excel-Datei

Um xlsx-Dateien in R zu importieren, muss das Zusatzpakete readxl installiert werden. Der Datenimport erfolgt dann mit der Funktion read_excel():

# installiere das Zusatzpaket "readxl"
install.packages("readxl")

# aktiviere das Paket "readxl"
library("readxl")

# importiere Excel-Datei "alteDaten.xlsx"
alteDaten <- read_excel("alteDaten.xlsx")

Soll ein bestimmtes Tabellenblatt der xlsx-Datei importiert werden, kann diese mit dem Parameter sheet übergeben werden.

# importiere Tabelle "Tabelle1" aus Excel-Datei "alteDaten.xlsx"
alteDaten <- read_excel("alteDaten.xlsx", sheet="Tabelle1")

Soll nur ein bestimmter Bereich der Tabelle importiert werden, kann diese mit dem Parameter range übergeben werden.

# importiere aus Excel-Datei "alteDaten.xlsx"
# Tabellenblatt "Tabelle1"
# im Bereich A2 bis D3
alteDaten <- read_excel("alteDaten.xlsx", sheet="Tabelle1", range="A2:D3")

14.5 Import aus .ods-Datei

Um ods-Dateien (z.B. aus Libreoffice oder OpenOffice) in R zu importieren, muss das Zusatzpakete readODS installiert werden. Der Datenimport erfolgt dann mit der Funktion read_ods():

# installiere das Zusatzpaket "readODS"
install.packages("readODS")

# aktiviere das Paket "readODS"
library("readODS")

# importiere ods-Datei "tolleDaten.ods"
tolleDaten <- read_ods("tolleDaten.ods")

Soll ein bestimmtes Tabellenblatt der ods-Datei importiert werden, kann diese mit dem Parameter sheet übergeben werden.

# importiere Tabelle "Tabelle1" aus ods-Datei "cooleDaten.ods"
cooleDaten <- read_ods("cooleDaten.ods", sheet="Tabelle1")

Soll nur ein bestimmter Bereich der Tabelle importiert werden, kann diese mit dem Parameter range übergeben werden.

# importiere aus ods-Datei "cooleDaten.ods"
# Tabellenblatt "Tabelle1"
# im Bereich A2 bis D3
alteDaten <- read.ods("cooleDaten.ods", sheet="Tabelle1", range="A2:D3")

14.6 Import mit RStudio

Mit RStudio können Daten sehr leicht importiert werden. Im Datenfenster klicken Sie oben auf Import Dataset.

Es öffnet sich ein kleines Fenster, in welchem Sie das gewünschte Import-Format auswählen können (Abbildung 14.1). Wie Sie sehen, werden native Dateien aus Excel, SPSS, SAS und Stata unterstützt.

Abb. 14.1: importiere Daten

Wir nehmen in diesem Beispiel From SPSS.

Da der Datenimport das Zusatzpaket haven erfordert, wird es zur Installation vorgeschlagen, sofern es noch nicht installiert ist (Abbildung 14.2). Wir bestätigen in diesem Fall mit Yes.

Abb. 14.2: Zusatzpakete installieren

Nun öffnet sich das eigentliche Importierfenster (Abbildung 14.3).

Abb. 14.3: Importfenster

Klicken Sie oben rechts auf den Browse-Knopf und wählen Sie die SPSS-Datei aus, die Sie importieren möchten. Für Abbildung 14.3 habe ich die Datei alteDaten-I.sav in meinem Arbeitsverzeichnis ausgewählt. Sobald Sie eine Datei ausgewählt haben, wird unter Data Preview eine Vorschau des zu importierenden Datensatzes angezeigt. Wie Sie sehen hat mein alter Datensatz die Variablen v1, v2, v3 , usw.

Darunter sehen Sie die Ausprägungen der Variablen.

Unter dem Vorschaufenster können Sie im linken Abschnitt (Import Options) den Objektnamen eintragen, den der Datensatz in R haben soll. Ich habe in Abbildung 14.3 den Namen alteDaten_l gewählt. Das Format ist schon automatisch auf SAV eingestellt (denn der Dateiname endet mit .sav). Der Haken bei Open Data Viewer bewirkt, dass der Datensatz nach dem Import im Scritpfenster angezeigt wird.

Im rechten Abschnitt (Code Preview) sehen Sie die R-Befehle, die in der Konosle ausgeführt werden, um den Import durchzuführen.

Klicken Sie unten rechts auf Import.

14.7 Importierte Daten ins richtige Format bringen

In R werden Dezimalstellen mit einem Punkt angegeben. Dies kann beim Importieren von Daten Probleme bereiten, insbesondere dann, wenn die zu importierenden Daten ihre Dezimalstelle mit einem Komma angeben.

Als Beispiel soll folgende CSV-Tabelle dienen. Sie enthält die beiden Variablen Anwesenheit und Note (mehr Informationen gibt es hier im Blogpost).

Mittels read.table() in Kombination mit url() lesen wir die CSV-Datei in unsere R-Session.

# einlesen
df <- read.table(url("http://www.produnis.de/R/data/anwesenheitnoten.csv"),
            sep=";",
            header=TRUE)

# anzeigen
df

##    Anwesenheit Note
## 1         90,9  1,7
## 2         40,9    5
## 3          100    1
## 4         81,8
## 5            0    5
## 6          100    2
## 7         27,3  3,7
## 8         27,3    5
## 9         54,5  3,3
## 10        54,5
## 11        45,5    4
## 12         100  1,3
## 13          50  3,3
## 14        27,3    4
## 15        68,2  2,3
## 16         100
## 17         100    1
## 18          50    2
## 19        63,6  2,7
## 20        16,7
## 21        31,8    4
## 22        72,7  3,3
## 23        27,3    4
## 24        31,8    3
## 25        18,2    4
## 26          80  1,7
## 27        90,9  1,3

# Datentyp
str(df)

## 'data.frame':    27 obs. of  2 variables:
##  $ Anwesenheit: chr  "90,9" "40,9" "100" "81,8" ...
##  $ Note       : chr  "1,7" "5" "1" "" ...

Die Daten sind als chr eingelesen worden. Mit der Funktion as.numeric() können wir solche Daten in numeric umwandlen, eigentlich…

dummy <-c("1", "5", "9", "18")
# anzeigen
dummy

## [1] "1"  "5"  "9"  "18"

# wandle chr in numeric um:
as.numeric(dummy)

## [1]  1  5  9 18

Bei den zuvor importierten Daten funktioniert dies aber nicht:

as.numeric(df$Anwesenheit)

## Warning: NAs durch Umwandlung erzeugt

##  [1]  NA  NA 100  NA   0 100  NA  NA  NA  NA  NA 100  50  NA  NA 100 100  50  NA
## [20]  NA  NA  NA  NA  NA  NA  80  NA

Das liegt an der Dezimaltrennung per Kommata. Wir müssen zunächst alle Kommata in Punkte umwandeln. Dies erfolgt mit der Funktion gsub(), die als Parameter den zu suchenden Wert gefolgt vom zu ersetzenden Wert entgegennimmt.

# ersetze alle Kommata durch Punkte
# in df$Note
gsub(",", ".", df$Note)

##  [1] "1.7" "5"   "1"   ""    "5"   "2"   "3.7" "5"   "3.3" ""    "4"   "1.3"
## [13] "3.3" "4"   "2.3" ""    "1"   "2"   "2.7" ""    "4"   "3.3" "4"   "3"
## [25] "4"   "1.7" "1.3"

Jetzt bauen wir die beiden Funktionen zusammen:

as.numeric(gsub(",", ".", df$Note))

##  [1] 1.7 5.0 1.0  NA 5.0 2.0 3.7 5.0 3.3  NA 4.0 1.3 3.3 4.0 2.3  NA 1.0 2.0 2.7
## [20]  NA 4.0 3.3 4.0 3.0 4.0 1.7 1.3

Genau so wollten wir es haben. Ändern wir nun das Datenframe entsprechend:

df$Anwesenheit <- as.numeric(gsub(",", ".", df$Anwesenheit))
df$Note <- as.numeric(gsub(",", ".", df$Note))
#anzeigen
df

##    Anwesenheit Note
## 1         90.9  1.7
## 2         40.9  5.0
## 3        100.0  1.0
## 4         81.8   NA
## 5          0.0  5.0
## 6        100.0  2.0
## 7         27.3  3.7
## 8         27.3  5.0
## 9         54.5  3.3
## 10        54.5   NA
## 11        45.5  4.0
## 12       100.0  1.3
## 13        50.0  3.3
## 14        27.3  4.0
## 15        68.2  2.3
## 16       100.0   NA
## 17       100.0  1.0
## 18        50.0  2.0
## 19        63.6  2.7
## 20        16.7   NA
## 21        31.8  4.0
## 22        72.7  3.3
## 23        27.3  4.0
## 24        31.8  3.0
## 25        18.2  4.0
## 26        80.0  1.7
## 27        90.9  1.3

# Datentyp
str(df)

## 'data.frame':    27 obs. of  2 variables:
##  $ Anwesenheit: num  90.9 40.9 100 81.8 0 100 27.3 27.3 54.5 54.5 ...
##  $ Note       : num  1.7 5 1 NA 5 2 3.7 5 3.3 NA ...

Jetzt ist alles richtig eingelesen.

15 Daten exportieren

In Kapitel 12 wird beschrieben, wie Sie Ihre R-Objekte im .RData-Format abspeichern können.

Es ist aber natürlich ebenso möglich, die Daten in anderen Formaten zu exportieren.

15.1 Export als CSV-Datei

Mit dem CSV-Format kommen die meisten Anwendungen zurecht. Daher empfehlen wir, dieses zum Tausch zwischen Anwendungen zu verwenden. Wir benutzen hierfür die Funktion write.csv() und speichern das Datenframe MeinDatenframe in die Datei MeinDatenframe.csv. Als Datenseparator wird standardmäßig ein Leerzeichen verwendet. Soll ein anderer Separator verwendet werden, kann dies über den Parameter sep angegeben werden.

# exportiere "MeinDatenframe" als CSV-Datei # mit "," als Separator
write.csv(MeinDatenframe, "MeinDatenframe.csv", sep=",")

15.2 Export als Textdatei-Datei

Mit der Funktion write.table() können die Daten als simple Textdatei gespeichert werden. Als Datenseparator wird auch hier standardmäßig ein Leerzeichen verwendet. Soll ein anderer Separator verwendet werden, kann dies über den Parameter sep angegeben werden.

# exportiere "MeinDatenframe" als Text-Datei # mit "," als Separator
write.table(MeinDatenframe, "MeinDatenframe.txt", sep=",")

Sollen die Daten an eine bestehende Datei angehängt werden, muss der Parameter append auf TRUE gesetzt werden.

15.3 Export als SPSS-Datei

Mit dem Zusatzpaket haven können R-Objekte als SPSS-Datei abgespeichert werden.

Wir benutzen hierfür die Funktion write_sav() und speichern das Datenframe MeinDatenframe in die Datei MeinDatenframe.sav.

# exportiere "MeinDatenframe" als SPSS-Datei
write_sav(MeinDatenframe, "MeinDatenframe.sav")

Um eine komprimierte .zsav-Datei zu erstellen, setzen wir den Parameter compress auf TRUE.

# exportiere "MeinDatenframe" als komprimierte SPSS-Datei
write_sav(MeinDatenframe, "MeinDatenframe.sav", compressp=TRUE)

16 Zusatzpakete installieren

In R stehen viele Zusatzpakete bereit, die den Funktionsumfang erweitern. Über das Comprehensive R Archive Network, kurz CRAN, können diese mit dem Befehl install.packages() installiert werden. Möchte man beispielsweise “ggplot” (siehe Kapitel 35) installieren, lautet der Befehl entsprechend (Achtung, das Paket für ggplot heisst ggplot2 !)

# ggplot installieren, ACHTUNG, das Paket heisst ggplot2 !!!
install.packages("ggplot2")

Manche Pakete benötigen weitere Zusatzpakete, um vollständig laufen zu können. Man spricht hier von “Abhängigkeiten”, da das eine Paket vom anderen abhängt. Möchte man das gewünschte Paket inklusive aller Abhängigkeiten installieren, ändert sich der Befehl in:

# Paket ggplot mit allen Abhängigkeiten installieren
install.packages("ggplot2", dependencies=TRUE)

Sollen mehrere Pakete auf einmal installiert werden, können diese mit der c()-Funktion übergeben werden.

# Pakete ggplot, wikibooks, pwr mit allen Abhängigkeiten installieren
install.packages(c("ggplot2", "wikibooks", "pwr"), dependencies=TRUE)

Um die installierten Zusatzpakete benutzen zu können, müssen sie in einer R-Sitzung zunächst geladen bzw. aktiviert werden. Dies erfolgt mit dem Befehl library().

# aktiviere das ggplot2 Paket
library(ggplot2)

Mit dem Befehl detach() können die Zusatzpakete wieder deaktiviert werden.

# de-aktiviere das ggplot2 Paket
detach("package:ggplot2")

Ein anderer Weg besteht darin, den Paketnamen mit zwei Doppelpunkten getrennt vor die Funktion zu schreiben.

# nutze Funktion "read_sav" aus dem Paket "haven"
haven::read_sav("SPSSdaten.sav")

Die beiden Doppelpunkte weisen R an, die Funktion read_sav() aus dem Paket haven zu nutzen. So kann man sich den library(haven)-Aufruf sparen. Dies bietet sich an, wenn man “nur mal kurz” eine bestimmte Funktion eines bestimmten Pakets nutzen möchte. Hat man mehrere Funktionsaufrufe, kann das Laden per library() die Schreibarbeit verkürzen.

In RStudio lassen sich Pakete auf mehrere Arten installieren. Zum einen kann man in der Menüzeile auf Tools →\rightarrow Install packages klicken. Es öffnet sich ein neues Fenster, in welches wir einfach die Paketnamen eintragen können (Abbildung 16.1).

Abb. 16.1: Zusatzpakete installieren

RStudio bietet eine weitere recht einfache und nützliche Methode zur Installation von Zusatzpaketen. Die Software durchsucht Ihre Scriptdatei nach dem Befehl library(). Ist das dort referenzierte Paket noch nicht installiert, schlägt RStudio die Installation vor (siehe gelber Balken in Abbildung Abbildung 16.2).

Abb. 16.2: Zusatzpakete per library()

Klicken Sie im gelben Balken auf Install um das Paket zu installieren. Dies ist nützlich, wenn man z.B. an einem “neuen” PC alte R-Scriptdateien öffnet, oder z.B. fremde R-Scripte verwendet. Nach dem Öffnen schlägt RStudio alle fehlenden Pakete zur Installation vor.

Natürlich können Sie auch einfach install.packages("ggplot2") (oder jeden anderen Paketnamen) in Ihre Scriptdatei schreiben und diese dann in der R-Konsole ausführen lassen.

Das Aktivieren von installierten Zusatzpaketen wird von RStudio ebenfalls ermöglicht. Klicken Sie im Ausgabefenster auf den Reiter Packages. So werden Ihnen alle installierten Zusatzpakete angezeigt (Abbildung 16.3).

Abb. 16.3: alle installierten Pakete

Wenn Sie ein Paket aktivieren möchten, klicken Sie einfach dessen Häkchen an.

Wie Abbildung Abbildung 16.4 zeigt, wurde durch den Klick automatisch der Befehl library(haven) in die R-Konsole übertragen und ausgeführt (linke Seite). Zum Deaktivieren müssen Sie einfach wieder auf das Kästchen klicken und den Haken entfernen.

Abb. 16.4: Paket aktivieren

Viele Paket enthalten Datensätze, die für Übungs- und Auswertungsbeispiele genutzt werden können.

Die enthaltenen Datensätze lassen sich mit dem Befehl data(package="PAKETNAME") auflisten. In R ist das Basispaket datasets enthalten, dessen Inhalte sich anzeigen lassen per:

# zeige die Datensätze des Pakets "datasets" an
data(package = "datasets")

Datensätze im Paket datasets Um einen Datensatz eizubinden, wird er mit der data()-Funktion aufgerufen.

# aktiviere den Datensatz "iris"
data(iris)

# zeige den Inhalt von 'iris'
head(iris)

##   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1          5.1         3.5          1.4         0.2  setosa
## 2          4.9         3.0          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.0         3.6          1.4         0.2  setosa
## 6          5.4         3.9          1.7         0.4  setosa

Eine alternative Zugriffsweise besteht darin, die Datensätze per paketname::datensatz einem Objekt zuzuweisen.

# weise den Datensatz psych::bfi dem Objekt testdf zu
testdf <- psych::bfi

# zeige erste Zeilen an
head(testdf)

##       A1 A2 A3 A4 A5 C1 C2 C3 C4 C5 E1 E2 E3 E4 E5 N1 N2 N3 N4 N5 O1 O2 O3 O4
## 61617  2  4  3  4  4  2  3  3  4  4  3  3  3  4  4  3  4  2  2  3  3  6  3  4
## 61618  2  4  5  2  5  5  4  4  3  4  1  1  6  4  3  3  3  3  5  5  4  2  4  3
## 61620  5  4  5  4  4  4  5  4  2  5  2  4  4  4  5  4  5  4  2  3  4  2  5  5
## 61621  4  4  6  5  5  4  4  3  5  5  5  3  4  4  4  2  5  2  4  1  3  3  4  3
## 61622  2  3  3  4  5  4  4  5  3  2  2  2  5  4  5  2  3  4  4  3  3  3  4  3
## 61623  6  6  5  6  5  6  6  6  1  3  2  1  6  5  6  3  5  2  2  3  4  3  5  6
##       O5 gender education age
## 61617  3      1        NA  16
## 61618  3      2        NA  18
## 61620  2      2        NA  17
## 61621  5      2        NA  17
## 61622  3      1        NA  17
## 61623  1      2         3  21

16.1 Zusatzpaket zu diesem Lehrbuch

Zu diesem Lehrbuch existiert ein eigenes Zusatzpaket, in welchem alle verwendeten Datensätze und Zusatzfunktionen enthalten sind.

Das Paket heisst jgsbook und kann über das CRAN installliert werden, siehe :

# installiere das Zusatzpaket dieses Lehrbuchs
install.packages("jgsbook")

Die enthaltenen Datensätze lassen sich mit dem Befehl data() auflisten:

# zeige die Datensätze des Pakets "jgsbook" an
data(package = "jgsbook")

Abb. 16.5: Datensätze im Paket jgsbook

…und in die Arbeitsumgebung laden:

# aktiviere jgsbook
library(jgsbook)

##
## Attache Paket: 'jgsbook'

## Die folgenden Objekte sind maskiert durch '.GlobalEnv':
##
##     mma, nw

# aktiviere den Datensatz "epa"
data(epa)

# zeige die ersten 6 Datenreihen von "epa"
head(epa)

##   age sex cms risk expert decu
## 1  50   m  40    0      0    0
## 2  75   m  40    0      0    0
## 3  86   w  13    1      1    0
## 4  77   w  24    1      1    0
## 5  66   w  40    0      0    0
## 6  51   m  37    0      0    0

Sie können auch über die Kombination paketname::datensatz auf die Datensätze zugreifen. So muss das Paket nicht erst per library() aktiviert werden.

# Datensatz "epa" aus dem Paket "jgsbook"
epa <- jgsbook::epa

# zeige die ersten 6 Datenreihen von "epa"
head(epa)

##   age sex cms risk expert decu
## 1  50   m  40    0      0    0
## 2  75   m  40    0      0    0
## 3  86   w  13    1      1    0
## 4  77   w  24    1      1    0
## 5  66   w  40    0      0    0
## 6  51   m  37    0      0    0

17 Ausgabe in Datei umleiten

Das Umleiten der R-Ausgabe in eine Textdatei ist evtl. ein etwas angestaubtes Verfahren, welches vor allem in Zeiten vor RStudio Anwendung gefunden hat. Heutzutage steht mit quarto eine komfortable Lösung zur Verfügung, hübsche R-Auswertungen z.B. in einem ansprechenden html-Dokument (sprich einer Webseite) zu präsentieren (siehe hierzu Kapitel 23).

Standardmäßig schreibt R seine Ausgaben in das Konsolfenster. Manchmal kann es aber auch vorteilhaft sein, die Ausgaben in eine Datei umzulenken. Dies kann mit der Funktion sink() erreicht werden. Der folgende Befehl leitet alle Ausgaben in die Textdatei Ausgabe.txt im Arbeitsverzeichnis um.

# leite ab jetzt alle Ausgaben in die Datei "Ausgabe.txt" um
sink("Ausgabe.txt")

Soll die Umleitung aufgehoben werden, erfolgt dies mit einem leeren Funktionsaufruf.

# beende die Umleitung
sink()

So kann man in der Scriptdatei genau festlegen, ab wann die Ausgaben in eine Datei geschrieben werden sollen, und wann nicht. Beachten Sie, dass R die Textdatei immer wieder überschreiben wird, z.B. wenn der sink() Aufruf beendet und dann erneut gestartet wird. Um die Ausgabe an eine bestehende Datei anzuhängen (ohne diese zu überschreiben) muss der Parameter append auf TRUE gesetzt werden.

# hänge ab jetzt alle Ausgaben an die Datei "Ausgabe.txt" an
sink("Ausgabe.txt", append=TRUE)

An dieser Stelle sei auf die Befehle print() und cat() hingewiesen. Wenn Sie die R-Ausgabe in eine Textdatei umleiten, möchten Sie evtl. ebenfalls Kommentare oder anderen Text in die Textdatei schreiben lassen, z.B. bevor Sie Befehle ausführen (oder nachdem).

Der Befehl print() gibt den Text dabei in der R-bekannten Weise aus.

# schreibe mit print() in die Textdatei
print("Guten Tag, beginnen wir mit der Analyse.")

In die Textdatei wird geschrieben:

[1] "Guten Tag, beginnen wir mit der Analyse."

Mit dem Befehl cat() wird tatsächlich nur der Text ausgegeben (ohne den Positionsanzeiger [1]).

# schreibe mit cat() in die Textdatei
cat("Guten Tag, beginnen wir mit der Analyse.geht es!")

In die Textdatei wird geschrieben:

Guten Tag, beginnen wir mit der Analyse.
Los geht es!

Der Parameter n gibt an, dass eine neue Zeile beginnen soll.

18 Verteilungsfunktionen

In R sind zahlreiche Verteilungsfunktionen implementiert. Wir beschränken uns in diesem Buch auf die für uns wichtigen Wahrscheinlichkeisverteilungen.

Eine vollständige Liste aller implementierten Verteilungen findet sich unter https://stat.ethz.ch/R-manual/R-devel/library/stats/html/Distributions.html

Der Funktionsumfang folgt dabei einer gewissen Logik, für jede Verteilung existiert je eine d, p, q und r-Funktion. Für die Normalverteilung (siehe Näheres in Abschnitt 18.1) sind das dnorm(), pnorm(), qnorm() und rnorm(). Für die t-Verteilung (siehe Abschnitt 18.2) heißen die Equivalente dt(), pt(), qt() und rt(), für χ2\chi^2 entsprechend dchisq(), pchisq(), qchisq() und rchisq().

d: ist jeweils die Dichtefunktion der Verteilung

p: berechnet die Werte der Verteilungsfunktion (kumulierte Dichte)

q: bestimmt die Quantile der Verteilung

r: erzeugt Zufallswerte aus der Verteilung

Dies möchten wir am Beispiel der Normalverteilung verdeutlichen.

18.1 Normalverteilung

Die Normalverteilung ist mit den oben beschriebenen Funktionen in R implementiert.

Mit der Funktion dnorm() berechnet man die Werte der Wahrscheinlichkeitsdichte. Möchte man beispielsweise die Wahrscheinlichkeitsdichte für x=2x=2 aus der Standardnormalverteilung berechnen lautet der Befehl:

# Wahrscheinlichkeitsdichte für x=2
# in der Standardnormalverteilung
dnorm(2)

## [1] 0.05399097

Standardmäßig stammen diese Werte aus der Standardnormalverteilung, sie streuen also mit einer Standardabweichung von s=1s=1 um den Mittelwert x‾=0\bar{x}=0.

Möchte man dies ändern, können der Funktion entsprechende Werte für Mittelwert und Standardabweichung übergeben werden:

# Wahrscheinlichkeitsdichte für x=4
# aus einer Verteilung mit Mittelwert 8 und Standardabweichung 2
dnorm(4, mean=8, sd=2)

## [1] 0.02699548

Die Werte der Verteilungsfunktion lassen sich mit pnorm() berechnen. Die Funktion gibt die Wahrscheinlichkeit an, dass ein Zufallsvariable einen Wert von ≤x\le x (= “höchstens” xx) annimmt.

# Wahrscheinlichkeit dass Wert kleiner-gleich 2
# in Standardnormalverteilung
pnorm(2)

## [1] 0.9772499

Für andere Normalverteilungen ändert sich der Befehl entsprechend:

# Wahrscheinlichkeit dass Wert kleiner-gleich 8
# in Normalverteilung mit xquer=12 und s=3
pnorm(8, mean=12, s=3)

## [1] 0.09121122

Quantile lassen sich mit der Funktion qnorm() berechnen. Quantile stellen Grenzen dar, die ein zufälliger Wert mit einer vorgegebenen Wahrscheinlichkeit nicht überschreiten wird. Wenn das 95%-Quantil beispielsweise den Wert 55 hat, wird eine zufällig gezogene Zahl mit 95% Sicherheit kleiner als 55 sein.

# Grenze des 95% Quantils
# in Standardnormalverteilung
qnorm(p=0.95)

## [1] 1.644854

Auch qnorm() können Werte für andere Normalverteilungen übergeben werden.

# Grenze des 95% Quantils
# in Normalverteilung mit xquer=9 und sd=5
qnorm(p=0.95, mean=9, sd=5)

## [1] 17.22427

Mit rnorm() lassen sich normalverteilte Zufallswerte erzeugen:

# erzeuge "zufällig" 10 Werte # aus der Standardnormalverteilung
rnorm(10)

##  [1] -1.400043517  0.255317055 -2.437263611 -0.005571287  0.621552721
##  [6]  1.148411606 -1.821817661 -0.247325302 -0.244199607 -0.282705449

Auch mit rnorm() können wir andere Normalverteilungen für unsere Zufallszahlen angeben:

# erzeuge "zufällig" 10 normalverteilte Werte
# mit Mittelwert 8 und Standardabweichung 2
rnorm(10, mean=8, sd=2)

##  [1]  6.892601  9.257964 12.130050  4.738021  9.024854  4.273977  6.955975
##  [8]  7.894796  9.085993  6.171850

Plot-Beispiel mit der plot()-Funktion (siehe Kapitel 34).

# Erzeuge Werte von -3 bis 6
x <- seq(-3, 6, by = 0.005)

# Plot erstellen
plot(x, dnorm(x, mean = 0, sd = 1),
     type = "l", xlim = c(-3, 6), ylim = c(0, 0.8),
     xlab = "x", ylab = "Dichtefunktion", main = "Normalverteilungen")
lines(x, dnorm(x, mean = 2, sd = 0.5), col = "blue")
lines(x, dnorm(x, mean = 2, sd = 2), col = "darkorchid")

# Annotationen hinzufügen
text(0, 0.42, "N(0;1)", col = "black")
text(3, 0.6, "N(2;0.5)", col = "blue")
text(5, 0.1, "N(2;2)", col = "darkorchid")

Beispiel mit ggplot() (siehe Kapitel 35).

# erzeuge Werte von -3 bis 6
x <- seq(-3,6, by=0.005)
# übergebe in ein data.frame
df <- data.frame(x)

# ggplot erstellen
library(ggplot2)
p <- ggplot(data=df, aes(x)) + xlim(-3,6) + ylim(0, 0.8) + xlab("x") +
      ylab("Dichtefunktion") + ggtitle("Normalverteilungen")
p +
      stat_function(fun=dnorm, args=(c(mean=0,sd=1)), colour="black")+
      annotate(geom="text", x=0, y=0.42, label="N(0;1)", color="black")+
      stat_function(fun=dnorm, args=(c(mean=2,sd=0.5)), colour="blue") +
      annotate(geom="text", x=3, y=0.6, label="N(2;0.5)", color="blue")+
      stat_function(fun=dnorm, args=(c(mean=2,sd=2)), colour="darkorchid") +
      annotate(geom="text", x=5, y=0.1, label="N(2;2)",
      color="darkorchid")

Für weitere Plot-Beispiele zur Normalverteilung siehe Abschnitt 37.2.

18.2 t-Verteilung

Die “kleine Schwester” der Standardnormalverteilung ist die Student-t-Verteilung (oder einfach kurz t-Verteilung). Für sie stehen die selben Funktionstypen zur Verfügung wie für die Normalverteilung (siehe Abschnitt 18.1). Diese heissen entsprechend dt(), pt(), qt() und rt() und funktionieren genau wie ihre Pendants der Standardnormalverteilung. Jedoch muss ihnen noch die Anzahl der Freiheitsgrade (dfdf) übergeben werden:

# Wahrscheinlichkeitsdichte für x=2
# in der t-Verteilung mit 4 Freiheitsgraden
dt(2, df=4)

## [1] 0.06629126

# Wahrscheinlichkeit dass Wert kleiner-gleich 2
# in der t-Verteilung mit 3 Freiheitsgraden
pt(2, df=3)

## [1] 0.930337

# Grenze des 95% Quantils # in der t-Verteilung mit 3 Freiheitsgraden
qt(p=0.95, df=3)

## [1] 2.353363

# erzeuge "zufällig" 10 Werte
# aus der t-Verteilung mit 13 Freiheitsgraden
rt(10, df=13)

##  [1]  0.45366019 -1.18009245  2.00550964  0.07126229  0.63480028  0.17499076
##  [7]  1.65206115 -0.22859755 -2.07243796 -0.28974980

Möchte man die t-Verteilung plotten, kann das mittels plot() (siehe Kapitel 34) beispielsweise so geschehen:

# Erzeuge x-Werte
x <- seq(-3, 3, by = 0.005)

# Plot erstellen
plot(x, dt(x, df = 1), type = "l",
     xlim = c(-3, 3), ylim = c(0, 0.4),
     xlab = "x", ylab = "Dichtefunktion",
     main = "t-Verteilungen nach Freiheitsgraden")

# Text hinzufügen
text(0, 0.25, "df=1", col = "black")

…oder mittels mit ggplot (siehe Kapitel 35) wie folgt.

# Erzeuge x-werte
df <- data.frame(x=seq(-3,3, by=0.005))

# Grundlegene Plotangaben
p <- ggplot(data=df, aes(x)) +
  # begrenze die Achsen
  xlim(-3,3) + ylim(0, 0.4) +
  # Achsen-Titel
  xlab("x") +   ylab("Dichtefunktion") +
  # Plot-Titel
  ggtitle("t-Verteilungen", subtitle = "nach Freiheitsgraden")

# t-Verteilung plotten
p + stat_function(fun=dt, args=list(df=1), col="black") +
  # Textfeld hinzufügen
  annotate(geom="text", x=0, y=0.25, label="df=1", color="black")

Für weitere Plot-Beispiele zur Normalverteilung siehe Abschnitt 37.3.

18.3 χ2\chi^2-Verteilung

Die χ2\chi^2-Verteilung ist mit den oben beschriebenen Funktionen in R implementiert. Diese heissen entsprechend dchisq(), pchisq(), qchisq() und rchisq() und funktionieren genau wie ihre Pendants der Standardnormalverteilung. Jedoch muss ihnen (wie bei der t-Verteilung) noch die Anzahl der Freiheitsgrade (dfdf) übergeben werden:

# Wahrscheinlichkeitsdichte für x=2
# in der Chi^2-Verteilung mit 4 Freiheitsgraden
dchisq(2, df=4)

## [1] 0.1839397

Möchte man die χ2\chi^2-Verteilung plotten, kann das mit der plot()-Funktion (siehe Kapitel 34) so umgesetzt werden:

# Erzeuge x-Werte
x <- seq(0, 25, by = 0.005)

# Dichte-Funktionen berechnen
df01 <- dchisq(x, df = 1)
df05 <- dchisq(x, df = 5)
df10 <- dchisq(x, df = 10)
df15 <- dchisq(x, df = 15)

# Plot erstellen
plot(x, df01, type = "l",
     xlim = c(0, 25), ylim = c(0, 0.2),
     xlab = "x", ylab = "Dichtefunktion",
     main = "Chi^2-Verteilungen nach Freiheitsgraden")

# Linien für andere Freiheitsgrade hinzufügen
lines(x, df05, col = "blue")
lines(x, df10, col = "darkgreen")
lines(x, df15, col = "darkred")

# Legende hinzufügen
legend("topright", legend = c("df=1", "df=5", "df=10", "df=15"),
       col = c("black", "blue", "darkgreen", "darkred"),
       lty = 1, cex = 0.8)

… oder mittels ggplot (siehe Kapitel 35) wie folgt.

# Erzeuge x-werte
x=seq(0,25, by=0.005)
df <- data.frame(  x,
  df01 = dchisq(x, df=1),
  df05 = dchisq(x, df=5),
  df10 = dchisq(x, df=10),
  df15 = dchisq(x, df=15)
)

# erzeuge long-table
df <- pivot_longer(df, cols=c(df01, df05, df10, df15))

p <- ggplot(data=df, aes(x, value, fill=name)) +
  xlim(0,25) + ylim(0, 0.2) +
  xlab("x") + ylab("Dichtefunktion") +
  ggtitle("Chi^2-Verteilungen", subtitle = "nach Freiheitsgraden")
p + geom_line(aes(col=name,linetype=name))+
labs(col="Freiheitsgrade",linetype="")

Für weitere Plot-Beispiele zur χ2\chi^2-Verteilung siehe Abschnitt 37.4.

18.4 Poisson-Verteilung

Die Poisson-Verteilung ist mit den oben beschriebenen Funktionen in R implementiert. Diese heissen entsprechend dpois(), ppois(), qpois() und rpois() und funktionieren genau wie ihre Pendants der Standardnormalverteilung. Jedoch muss ihnen der Wert für λ\lambda übergeben werden.

# Wahrscheinlichkeitsdichte für x=2
# in der Poisson-Verteilung mit Lambda=4
dpois(2, lambda=4)

## [1] 0.1465251

Möchte man die Poisson-Verteilung plotten, kann dies mittels plot() (siehe Kapitel 34) z.B. so erfolgen:

# Erzeuge x-Werte
x <- seq(0, 25)

# Dichtefunktionen berechnen
l1 <- dpois(x, lambda = 1)
l2 <- dpois(x, lambda = 2)
l4 <- dpois(x, lambda = 4)
l9 <- dpois(x, lambda = 9)

# Plot erstellen
plot(x, l1, type = "l",
     xlim = c(0, 17), ylim = c(0, 0.4),
     xlab = "x", ylab = "Dichtefunktion",
     main = "Poisson-Verteilungen nach Lambda")

# Linien für andere Lambdas hinzufügen
lines(x, l2, col = "blue")
lines(x, l4, col = "darkgreen")
lines(x, l9, col = "darkred")

# Legende hinzufügen
legend("topright", legend = c("lambda=1", "lambda=2", "lambda=4", "lambda=9"),
       col = c("black", "blue", "darkgreen", "darkred"),
       lty = 1, cex = 0.8)

oder mittels ggplot (siehe Kapitel 35) wie folgt.

# Erzeuge x-Werte
x=seq(0,25)
df <- data.frame(  x,
                   l1 = dpois(x, 1),
                   l2 = dpois(x, 2),
                   l4 = dpois(x, 4),
                   l9 = dpois(x, 9)
)

# erzeuge eine long-table
df <- pivot_longer(df, cols=c(l1, l2, l4, l9))

# plot vorbereiten
p <- ggplot(data=df, aes(x, value, fill=name)) +
  xlim(0,17) + ylim(0, 0.4) +
  xlab("x") + ylab("Dichtefunktion") +
  ggtitle("Poisson-Verteilungen", subtitle = "nach Lambda")
p + geom_line(aes(col=name,linetype=name))+
  labs(col="Lambda",linetype="")

Für weitere Plot-Beispiele zur Poisson-Verteilung siehe Abschnitt 37.5).

18.5 Binomial-Verteilung

Die Binomial-Verteilung ist mit den oben beschriebenen Funktionen in R implementiert. Diese heissen entsprechend dbinom(), pbinom(), qbinom() und rbinom() und funktionieren genau wie ihre Pendants der Standardnormalverteilung.

Als Parameter werden übergeben:

x = günstige Werte

n = Anzahl der Beobachtungen

prob = Wahrscheinleichkeit des Ereignisses

Die Wahrscheinlichkeit, dass ich in einem 4-tägigen Seminar mit insgesamt 10 Teilnehmern 2mal das Tagesprotokoll schreiben muss1 errechnet sich wie folgt:

# Wahrscheinlichkeit, insgesamt 2mal das Tagesprotokoll
# schreiben zu müssen,
# bei 10 Teilnehmern und 4 Seminartagen
dbinom(x=2, size=4, prob=0.1)

## [1] 0.0486

Die Wahrscheinlichkeitswerte können für jedes Ereignis “Protokoll schreiben” errechnet und geplottet werden:

# Erzeuge Ereignisraum
protokoll <- c(0:4)

# überführe in Datenframe und berechne alle
# Wahrscheinlichkeiten, das Tagesprotokoll schreiben zu müssen,
# bei 10 Teilnehmern an 4 Seminartagen
df <- data.frame(protokoll, y=dbinom(x=protokoll, size=4, prob=0.1)*100)

# plotten
ggplot(df, aes(x=protokoll, y=y)) + geom_bar(stat="identity", col="black",fill="skyblue3")+
  geom_text(aes(label = y),size = 5,vjust = -0.5)+
  xlab("Anzahl Protokolle")+
  ylab("Wahrscheinlichkeit")+ ylim(0,100)

Die Wahrscheinlichkeit, bei 10 Münzwürfen k-mal Kopf zu werfen, errechnet sich wie folgt:

# Ereignisraum erzeugen
münze <- c(1:10)

# überführe in Datenframe und berechne
# alle Wahrscheinlichkeiten, k-mal "Kopf" zu werfen
df <- data.frame(münze, y=round(dbinom(x=münze, size=10, prob=0.5)*100,1))

# plotten
ggplot(df, aes(x=münze, y=y)) +
  geom_bar(stat="identity", col="black",fill="skyblue3")+
  geom_text(aes(label = y),size = 5,vjust = -0.5)+
  xlab("Anzahl 'Kopf'")+
  ylab("Wahrscheinlichkeit")+ ylim(0,33) +
  scale_x_continuous(breaks = 0:10)

1. Kuckartz, U; Rädiker, S; Ebert, T. Schehl, J (2013): Statistik - Eine verständliche Einführung, Springer, ISBN 978-3-531-19889-7, S.125

19 Eigene Funktionen programmieren

In R lassen sich auch eigene Funktionen integrieren. Die wohl “einfachste” Methode hierbei ist, die Funktionen in der R-Sprache selber zu schreiben. Es besteht aber auch die Möglichkeit, neue Funktionen in Programmiersprachen wie z.B. C++ zu programmieren.

Zum Erstellen einer eigenen Funktion steht der Befehl function() zur Verfügung. Dieser ist wie folgt aufzurufen:

function(Parameter1, Parameter2, ...) {
        FUNKTIONSWEISE
        }

In den Klammern direkt nach function können Parameter benannt werden, welche dann beim Funktionsaufruf der Funktion zur Verfügung stehen. Innerhalb der geschweiften Klammern wird die eigentliche Funktionsweise geschrieben.

Um eine neue Funktion in R aufrufen zu können, wird die Funktionsweise einem neuen Objekt (z.b. myfunc) zugeordnet.

myfunc <- function(x,y){ # Neue Funktion mit den Parametern x und y
          z <- x+y       # Die Summe von x und y wird in z gespeichert
          return(z)      # z wird zurückgemeldet
          }

Der obigen Dummy-Funktion werden im Funktionsaufruf die Parameter x und y übergeben. Innerhalb der Funktion wird die Summe aus x und y gebildet, und per return() an die R-Konsole zurückgemeldet. Jetzt können wir diese Funktion aufrufen:

myfunc(4,6)

## [1] 10

Es besteht ferner die Möglichkeit, Standardwerte für die Parameter zu setzen, welche verwendet werden, falls der Funktionsaufruf ohne Parameter erfolgt.

myfunc <- function(x=3,y=7){ # Neue Funktion mit Standardwerten für die Parameter x und y
          z <- x+y           # Die Summe von x und y wird in z gespeichert
          return(z)          # z wird zurückgemeldet
          }

Rufen wird die Funktion ohne Parameter auf, so werden die Standardwerte genutzt.

# Funktionsaufruf OHNE Parameter
myfunc()

## [1] 10

Rufen wir die Funktion mit Parametern auf, werden diese verwendet.

myfunc(12,4)

## [1] 16

19.1 Beispiele

Alle Funktionen (und Datensätze) dieses Lehrbuches sind auch im Zusatzpaket jgsbook enthalten (siehe Abschnitt 16.1).

19.1.1 z-Transformation

Mit dieser Funktion wird die z-Transformation für gegebene Werte errechnet. Die Funktion folgt der Formel

zi=xi−μσz_{i} = \frac{x_{i}-\mu}{\sigma}

Als Standardparameter werden die Werte der Standardnormalverteilung gesetzt.

ztrans <- function(x, mu=0, sd=1){
  z = (x-mu)/sd
  return(z)
}

# x ist 120, aus einer normalverteilten Reihe mit
# Mittelwert 118 und Standardabweichung 20
ztrans(120,mu=118,sd=20)

## [1] 0.1

19.1.2 Sensitivität

Die folgende Funktion errechnet Sensitivität, Spezifität sowie positiv- und negativ-prädiktive Werte für gegebene Werte.

sens.spec <- function(rp, rn, fp, fn){
    x <- data.frame(
             sens=round(rp/(rp+fn)*100, 2),
             spec=round(rn/(rn+fp)*100, 2),
             ppw =round(rp/(rp+fp)*100, 2),
             npw =round(rn/(rn+fn)*100, 2)
             )
   return(x)
 }

Die zu übergebenden Parameter sind

rp = Anzahl richtig positive

rn = Anzahl richtig negative

fp = Anzahl falsch positive

fn = Anzahl falsch negative

sens.spec(40, 17, 85, 4)

##    sens  spec ppw   npw
## 1 90.91 16.67  32 80.95

19.1.3 Kenngrößen

Die folgende Funktion gibt die gebräuchlichsten Kenngrößen einer Wertereihe zurück:

kenngroessen <- function(werte){
  bla <- data.frame(0)
  bla$modus=paste(as.character(statip::mfv(werte)), collapse="|")
  bla$mean=mean(werte, na.rm=T)
  bla$median=median(werte, na.rm=T)
  bla$p25=quantile(werte,0.25,type=6)
  bla$p75=quantile(werte,0.75,type=6)
  bla$iqr=IQR(werte,type=6)
  bla$sd=sd(werte, na.rm=T)
  bla$var=var(werte, na.rm=T)
  bla$VK= (sd(werte, na.rm=T)/mean(werte,na.rm=T))
  return(bla[-1])
}

# erzeuge zufällige Werte
x <- ceiling(rnorm(100, 10,5))

# Kenngrößen anzeigen
kenngroessen(x)

##   modus  mean median p25 p75 iqr       sd      var       VK
## 1     9 10.84     11   9  14   5 5.304315 28.13576 0.489328

19.1.4 Häufigkeitstabellen

Die folgende Funktion gibt eine vollständige Häufigkeitstabelle mit absoluten und relativen Häufigkeiten sowie kummulierten Werten zurück.

freqTable <- function(werte){
  x <- table(werte)
  tabelle <- data.frame(x)
  tabelle$freqcum <- cumsum(x)
  tabelle$relfreq <- round(x/length(werte)*100,2)
  tabelle$relcum  <- cumsum(round(x/length(werte)*100,2))
  colnames(tabelle) <- c("Wert", "Häufig", "Hkum", "Relativ", "Rkum")
  tabelle$Wert <- as.numeric(as.vector(tabelle$Wert))
  return(tabelle)
}

x <- ceiling(rnorm(13, 10,2))
freqTable(x)

##   Wert Häufig Hkum Relativ  Rkum
## 1    7      1    1    7.69  7.69
## 2    8      2    3   15.38 23.07
## 3    9      5    8   38.46 61.53
## 4   10      2   10   15.38 76.91
## 5   11      2   12   15.38 92.29
## 6   12      1   13    7.69 99.98

19.2 Bedingungen

Innerhalb der Funktion können Variablenbedingungen mit dem if()-Befehl abgefragt werden. Der Aufruf erfolgt etwa so:

if(VARIABLENBEDINGUNG) {FUNKTIONSWEISE}

Innerhalb der Klammern des if()-Befehls werden die Variablenbedingungen gesetzt. Falls diese Bedingungen erfüllt sind, wird der Code innerhalb der geschweiften Klammern ausgeführt. Folgende Bedingungen können abgefragt werden

Zeichen | Bedingung
== | gleich
!= | ungleich
< | kleiner
<= | kleiner-gleich
> | größer
>= | größer-gleich
& | UND
| | ODER

Innerhalb von Funktionen kann man dies wie folgt anwenden

myfunc <- function(x=3,y=7){
          z <- x+y                      # Die Summe von x und y wird in z gespeichert
          if(z>20) {                    # Abfrage, ob die Summer größer als 20 ist
             z <- "wow, bist du gross"  # wenn ja, dann schreibe einen Text in das Objekt z
             }
          return(z)                     # z wird zurückgemeldet
          }

Mehrere Bedingungen können verknüpft werden, z.B. so:

myfunc <- function(x=3,y=7){
          if(x<0 & y<0) {         # Abfrage, ob x und y negativ sind
             x <- x*(-1)          # wenn ja, dann mache beide positiv
             y <- y*(-1)
             }
          z <- x+y                # Die Summe von x und y wird in z gespeichert
          if(z==0 | z>50)  {      # Abfrage, ob z gleich 0 oder größer 50 ist
            z <- "Summe ist 0 oder größer 50" # wenn ja, schreibe einen Text...
            }
          return(z)               # z wird zurückgemeldet
          }

19.2.1 Beispiel Zusatzpakete

Die folgende Funktion installiert die vorgegebenen Pakete, sofern sie noch nicht installiert sind. Dies ist hilfreich, wenn z.B. auf eine höhere R-Version geupdatet wurde, und alle Zusatzpakete neu installiert werden müssen.

install.my.packages <- function(){
  # Liste meiner favorisierten Pakete
  my_packages <- c("blogdown", "bookdown",
                 "car",
                 "foreign",
                 "gghighlight", "ggplot2",
                 "haven",
                 "likert",
                 "prettyR", "psych",
                 "reshape", "reshape2",
                 "samplingbook", "scales", "statip",
                 "tidyverse",
                 "VGAM",
                 "xtable"
)

#--------------
# Überprüfe, ob die Pakete bereits installiert sind
not_installed <- my_packages[!(my_packages %in% installed.packages()[ , "Package"])]
# installiere solche, die noch nicht installiert sind
if(length(not_installed)) install.packages(not_installed, dependencies = TRUE)
return(paste(length(not_installed), "Pakete wurden installiert (plus dependencies)."))
}

Die Funktion kann dann wie folgt aufgerufen werden:

install.my.packages()

Bei mir ist alles up-to-date, so dass kein Paket installiert werden muss.

Mit einer leichten Änderung können weitere Pakete an den Parameter p übergeben werden:

install.my.packages <- function(p=""){
  # Liste meiner favorisierten Pakete
  my_packages <- c("blogdown", "bookdown",
                 "car",
                 "foreign",
                 "gghighlight", "ggplot2",
                 "haven",
                 "likert",
                 "prettyR", "psych",
                 "reshape", "reshape2",
                 "samplingbook", "scales", "statip",
                 "tidyverse",
                 "VGAM",
                 "xtable"
)

# Falls Pakete über den Parameter "p" übergeben wurden,
# füge sie der Liste hinzu
if(p!=""){
  my_packages <- c(p, my_packages)
    }
#--------------
# Überprüfe, ob die Pakete bereits installiert sind
not_installed <- my_packages[!(my_packages %in% installed.packages()[ , "Package"])]
# installiere solche, die noch nicht installiert sind
if(length(not_installed)) install.packages(not_installed, dependencies = TRUE)
return(paste(length(not_installed), "Pakete wurden installiert (plus dependencies)."))
}

Der Funktion kann so ein Vektor weiterer Pakete übergeben werden, die zusätzlich zur vorgegebenen Liste installiert werden, falls sie noch nicht installiert sind:

install.my.packages(c("ggpubr", "qqplotr"))

[1] "2 Pakete wurden installiert (plus dependencies)."

19.3 Funktionen in Dateien speichern

Eigene Funktionen sind Objekte wie alle anderen Variablen im Workspace. Wenn sie nicht abgespeichert werden, stehen Sie u.U. nach einem Neustart (oder wenn der Workspace überschrieben oder geleert wird) nicht mehr zur Verfügung.

Es bietet sich an, die eigenen Funktionen in einer .R-Datei zu speichern. Mit dem source()-Befehl kann die Datei dann in neuen Projekten eingelesen werden, und die Funktionen stehen zur Verfügung.

source("/Pfad/zu/MeineFunktionen.R")

20 Funktionen über Datenreihen anwenden

20.0.1 apply()

Eine wichtige Funktion zur Datenmanipulation ist die Funktion apply() (wie auch ihre Tochterfunktionen lapply(), sapply() und tapply()). Mit ihr können beliebige Funktionen auf die Reihen und/oder Spalten eines Datenframes (oder einer Matrix) angewendet werden. Dadurch erlaubt sie es in vielen Fällen, Loop-Schleifen und Redundanzen zu vermeiden.

Zur Verdeutlichung sei folgendes Beispiel gegeben:

# erzeuge Testdatensatz
df <- data.frame(Name = c("Hans", "Gerda", "Kurt", "Maria"),
                 Alter = c(56, 59, 58, 58),
                 Groesse = c(178, 172, 181, 166),
                 Gewicht = c(105, 67, 95, 89))
# anzeigen
df

   Name Alter Groesse Gewicht
1  Hans    56     178     105
2 Gerda    59     172      67
3  Kurt    58     181      95
4 Maria    58     166      89

Angenommen, wir wollen die Mittelwerte der drei numerischen Variablen bestimmen, dann könnten wir so vorgehen:

mean(df$Alter)

[1] 57.75

mean(df$Groesse)

[1] 174.25

mean(df$Gewicht)

[1] 89

Die Funktion apply() erlaubt es, die Werte in nur einem Aufruf zu erzeugen, indem die Funktion mean() auf die Werte jeder Spalte angewendet wird.

apply(df[,-1], MARGIN=2, FUN=mean)

  Alter Groesse Gewicht
  57.75  174.25   89.00

Wir übergeben der Funktion zunächst das Datenframe df ohne die Spalte Name (df[,-1]). Mit dem Parameter MARGIN wird festgelegt, wie apply() auf das Datenframe schauen soll:

MARGIN=1 - Reihe für Reihe

MARGIN=2 - Spalte für Spalte

MARGIN=3 - beides zusammen

Über den Parameter FUN wird die zu verwendende Funktion (in diesem Falle mean()) ohne Klammern angegeben.

Möchten wir nun die Standardabweichung der drei Variablen berechnen, lautet der Aufruf entsprechend:

apply(df[,-1], MARGIN=2, FUN=sd)

    Alter   Groesse   Gewicht
 1.258306  6.652067 16.083117

Es ist wichtig, die nicht-numerische Variable Name vor dem apply()-Aufruf herauszufiltern. Denn wenn die zu verwendende Funktion bei nur einer Reihe fehl schlägt, liefert apply() nur eine Reihe NAs und Warnmeldungen zurück.

# aufruf mit Spalte "Name" schlägt fehl
apply(df, MARGIN=2, FUN=mean)

Warning in mean.default(newX[, i], ...): Argument ist weder numerisch noch
boolesch: gebe NA zurück

Warning in mean.default(newX[, i], ...): Argument ist weder numerisch noch
boolesch: gebe NA zurück

Warning in mean.default(newX[, i], ...): Argument ist weder numerisch noch
boolesch: gebe NA zurück

Warning in mean.default(newX[, i], ...): Argument ist weder numerisch noch
boolesch: gebe NA zurück

   Name   Alter Groesse Gewicht
     NA      NA      NA      NA

Möchten wir die Werte der Probanden beispielsweise aufaddieren, lassen wir per apply() die Funktion sum() reihenweise über die Daten laufen. Hierzu setzen wir den Parameter MARGIN=1.

# laufe pro-Reihe über die Daten
apply(df[,-1], MARGIN=1, FUN=sum)

[1] 339 298 334 313

Wir können jede Funktion per apply() auf die Variablen loslassen:

# Wurzel aus jedem Wert ziehen
apply(df[,-1], MARGIN=2, sqrt)

        Alter  Groesse   Gewicht
[1,] 7.483315 13.34166 10.246951
[2,] 7.681146 13.11488  8.185353
[3,] 7.615773 13.45362  9.746794
[4,] 7.615773 12.88410  9.433981

Da wir wissen, dass der Parameter MARGIN an zweiter Stelle kommt, müssen wir ihn dort nicht immer ausschreiben, sondern können einfach 1, 2 oder 3 schreiben.

# Häufigkeitstabelle für jede Variable
# MARGIN=2 muss nicht ausgeschrieben werden
apply(df[,-1], 2, FUN=jgsbook::freqTable)

$Alter
  Wert Haeufig Hkum Relativ Rkum
1   56       1    1      25   25
2   58       2    3      50   75
3   59       1    4      25  100

$Groesse
  Wert Haeufig Hkum Relativ Rkum
1  166       1    1      25   25
2  172       1    2      25   50
3  178       1    3      25   75
4  181       1    4      25  100

$Gewicht
  Wert Haeufig Hkum Relativ Rkum
1   67       1    1      25   25
2   89       1    2      25   50
3   95       1    3      25   75
4  105       1    4      25  100

20.0.2 Funktionsparameter spezifizieren

Parameter für die auszuführende Funktion können einfach mit einem Komma angehängt werden.

# Führen quantile(x, type=6) aus
apply(df[,-1], MARGIN=2, FUN=quantile, type=6)

     Alter Groesse Gewicht
0%   56.00  166.00    67.0
25%  56.50  167.50    72.5
50%  58.00  175.00    92.0
75%  58.75  180.25   102.5
100% 59.00  181.00   105.0

Das funktioniert auch mit mehreren Parametern.

# Führen quantile(x, type=6) aus
apply(df[,-1], MARGIN=2, FUN=quantile, type=6, probs=c(0.1, 0.6, 0.8))

    Alter Groesse Gewicht
10%    56     166      67
60%    58     178      95
80%    59     181     105

Es ist möglich, die Parameter, welche an die auszuführende Funktion übergeben werden müssen, aus dem Datenframe selbst zu ziehen. Erzeugen wir zur Verdeutlichung folgendes Datenframe:

# Testdaten
df <- data.frame(Name = c("Jerome", "Nadine", "Dominik", "Ella"),
                 Geschwindigkeit = c(120, 25, 32, 80),
                 Strecke = c(200, 34, 50, 100))
# anzeigen
df

     Name Geschwindigkeit Strecke
1  Jerome             120     200
2  Nadine              25      34
3 Dominik              32      50
4    Ella              80     100

Jetzt programmieren wir die Funktion zeit(), welche ausrechnet, wie lange die Personen für die Strecke mit der angegebenen Geschwindigkeit benötigt haben. Die Funktion nimmt die Werte velo (Geschwindigkeit) und dist (Strecke) entgegen.

# Funktion programmieren
zeit <- function(velo, dist){
  # rechne Distanz / Geschwindigkeit
  # runde auf 2 Stellen
  zeit <- round(dist/velo, 2)
  return(zeit)
}

Um die Funktion zeit() auf jede Reihe des Datenframes anzuwenden, wobei die Werte von Geschwindigkeit und Strecke jeweils als Parameter übergeben werden, müssen wir zunächst per MARGIN=1 angeben, dass apply() die Daten Reihe-für-Reihe verarbeiten soll.

Der Trick besteht darin, im Paramter FUN eine temporäre Funktion function(line) anzugeben. Diese ruft ihrerseits die Funktion zeit() (diesmal mit Klammern) auf, und übergibt die Parameter per line("SPALTENNAME").

# Übergebe die Werte an die Funktion zeit()
apply(df[,-1], MARGIN=1, function(line) zeit(line["Geschwindigkeit"],
                                             line["Strecke"]))

[1] 1.67 1.36 1.56 1.25

Wenn die Parameter dist und velo mit ihrem Namen angegeben werden, ist die Reihenfolge der Parameter egal.

# Parameter dist und velo direkt angeben
apply(df[,-1], MARGIN=1, FUN=function(line) zeit(dist=line["Strecke"],
                                                 velo=line["Geschwindigkeit"]))

[1] 1.67 1.36 1.56 1.25

Als temporäre Hilfsfunktionen können verschiedene Arten verwendet werden:

function(x) - übergibt jeden Wert einzeln und nacheinander jeweils als Parameter an die Funktion, wobei x für spezifische Parameter referenziert werden kann (siehe Abschnitt 20.0.3 für ein Beispiel).

function(line) - übergibt die Werte der gesamte Datenreihe als Parameter an die Funktion, wobei die einzelnen Variablenwerte per line["SPALTENNAME"] referenziert werden können.

In einem weiteren Beispiel wollen wir Fallzahlen für spezifische Werte von e, P und dem Konfidenzlevel berechnen (siehe Abschnitt 33.11.2). Hierzu nutzen wir die Funktion samplingbook::sample.size.prop(), wobei die Erweiterung samplingbook::sample.size.prop()$n nur die Fallzahlen zurückgibt.

# beispielhafter Funktionsaufruf
samplingbook::sample.size.prop(e=0.05, P=0.65, level=0.95, N=1500)$n

[1] 284

# Erzeuge Kombinationen von e, P und level
df <- data.frame(e = c(0.05, 0.04, 0.03),
                P = c(0.65, 0.6, 0.5),
                level = c(0.9, 0.95, 0.99))
# anzeigen
df

     e    P level
1 0.05 0.65  0.90
2 0.04 0.60  0.95
3 0.03 0.50  0.99

# Berechne Fallzahlen für jede Reihe (Kombination)
apply(df, 1, function(line) samplingbook::sample.size.prop(e = line["e"],
                                                           P = line["P"],
                                                           level = line["level"])$n
      )

[1]  247  577 1844

20.0.3 sapply()

Die Tochterfunktion sapply() kann auf Vektoren angewendet werden.

# erzeuge einen Vektor mit 3 Werten für N
N <- c(10, 30, 100)

# ziehe für jeden Wert die Wurzel
sapply(N, sqrt)

[1]  3.162278  5.477226 10.000000

Wenn die verwendete Funktion Wertereihen zurückgibt, wird das Ergebnis als Liste zurückgegeben.

# erzeuge jewils N Zufallszahlen
sapply(N, sample)

[[1]]
 [1]  8  7  1  4 10  9  2  5  3  6

[[2]]
 [1] 25  8 28  3 12 16  1 18 19 22 21 17 24 13  6 11 29 10  2 26  5 15 23  7 27
[26]  9  4 20 30 14

[[3]]
  [1]  19  60  43  58  34  57  13  36  50  85  14  47  54  72   9  49  67  97
 [19]  89  31  93  41  55  76  12  18  11  16  29  46  45  32  61   7   4  30
 [37]   8   6   5  92  70  98  33  96  74  88  20  24  59  86  42  52  62  15
 [55]  90  91  28  63  65  27  99  53   2  73 100  81  39  21  26  44  84  40
 [73]  10  64  83  22  38  69  77  78  82  23   1  48  56  68  87   3  95  94
 [91]  79  80  35  25  75  66  17  37  71  51

Da die Funktion sample() jeweils einen Datenvektor zurückgibt, liegt das Ergebnis als Liste vor.

Auch bei sapply() werden Funktionsparameter per Komma angehängt.

# erzeuge jewils N Zufallszahlen
# mit doppelten
sapply(N, sample, replace=TRUE)

[[1]]
 [1]  2  1  5  5 10  9 10  6  5  1

[[2]]
 [1] 14 16  2  4  7  5  4 16 22 19 28  2 20 22 24 16 28 19 11 22 25 24 27  8 26
[26] 10 16  8 16  4

[[3]]
  [1]  59  64  20  15  18  90 100  38  75  86  32   7  64   4  20  30  74  45
 [19]   2   5  34  87  15  84  28  19  84  97  70  81  27  40  53  32  84  14
 [37]   5  33  19  40  25  15  74  50  52  29  70   4  67  22  87  57  70  15
 [55]  70   1  59  72   3  26  43   5  14  68   2   1  45  58  12  82  56  97
 [73]  48  44  84  36  62  30  17  19  12  51  68  42  41  70  26  16  16  84
 [91]  52   2  77  87  89  74  58  21  35  71

Sollen die Werte des Ausgangsvektors als spezifischer Parameter der auszuführenden Funktion übergeben werden, muss wieder auf eine temporäre Hilfsfunktion function(x) zurückgegriffen werden. Im folgenden Aufruf sollen die Werte des Vektors N jeweils als Paramenter N an die Funktion samplingbook::sample.size.mean() übergeben werden.

# Rufe für jeden Wert x im Vektor N die Funktion auf,
# wobei Parameter N=x ist
sapply(N, FUN=function(x) samplingbook::sample.size.mean(e=0.05,
                                                         S=0.3,
                                                         level=0.90,
                                                         N=x)
       )

     [,1]   [,2]   [,3]
call list,4 list,4 list,4
n    10     23     50

Per samplingbook::sample.size.mean()$n wird nur die benötigte Fallzahl zurückgegeben. Diese Anweisung können wir auch auf sapply() übertragen:

# mit $n nur Fallzahlen
sapply(N, FUN=function(x) samplingbook::sample.size.mean(e=0.05,
                                                         S=0.3,
                                                         level=0.90,
                                                         N=x)$n
       )

[1] 10 23 50

20.0.4 lapply()

Die Funktion lapply() liefert ihr Ergebnis immer als Liste zurück, wobei für jedes x ein eigener Listeneintrag erzeugt wird.

# erzeuge einen Vektor mit 3 Werten für N
N <- c(10, 30, 100)

# ziehe für jeden Wert die Wurzel
lapply(N, sqrt)

[[1]]
[1] 3.162278

[[2]]
[1] 5.477226

[[3]]
[1] 10

Mit der Funktion unlist() kann die Liste in Vektoren überführt werden.

unlist(lapply(N, sqrt))

[1]  3.162278  5.477226 10.000000

20.0.5 tapply()

Mit der Tochterfunktion tapply() lassen sich gruppierte Auswertungen erzeugen. Die Gruppierung erfolgt anhand eines Factors im Datenframe.

# erzeuge Testdaten
df <- data.frame(Geschlecht = factor(c("m", "w", "m", "m", "w", "w", "d", "d", "d")),
                 Alter = c(45, 34, 46, 41, 31, 29, 24, 25, 26))
# anzeigen
df

  Geschlecht Alter
1          m    45
2          w    34
3          m    46
4          m    41
5          w    31
6          w    29
7          d    24
8          d    25
9          d    26

Angenommen, wir möchten nun die Mittelwerte des Alters in Abhängigkeit zum Geschlecht bestimmen, so können wir tapply() verwenden.

tapply(df$Alter, df$Geschlecht, mean)

       d        m        w
25.00000 44.00000 31.33333

Wie bei den Schwesterfunktionen lassen sich weitere Parameter per Komma übergeben.

# Berecne IQR so wie SPSS (type=6)
tapply(df$Alter, df$Geschlecht, IQR, type=6)

d m w
2 5 5

21 Markdown

Markdown ist eine “Auszeichnungssprache”, die es erlaubt, mit einer recht einfachen Syntax ansprechende Dokumente zu erstellen. Sie wurde dabei so konzipiert, dass schon der Quellcode leicht lesbar ist.

In Kombination mit RMarkdown oder quarto lassen sich Berichte, Artikel, Präsentationen und Bücher erstellen, die R-Code und -Diagramme verwenden.

Mehr Informationen erhalten Sie hier:
https://quarto.org/docs/authoring/markdown-basics.html

22 RMarkdown

Im Sommer 2022 wurde RMarkdown durch den Nachfolger quarto abgelöst, weshalb dieses Kapitel eher historische Informationen enthält. Wenn Sie RMarkdown noch nie benutzt haben, sollten Sie dieses Kapitel überspringen und direkt mit quarto einsteigen, siehe Kapitel 23.

RMarkdown ermöglicht es, R-Code, -Output und -Diagramme, sowie Formeln und externe Quellen (z.B. Bilder aus dem Internet) in ansprechenden Dokumenten (z.B. Webseite, Word, Powerpoint, PDF) zu exportieren. Es ist sehr gut dafür geeignet, die eigenen statistischen Auswertungen und Ergebnisse für Endanwender (z.b. Auftragsgeber oder Dozenten) darzustellen.

Die Funktionalität hat sich so sehr weiterentwickelt, dass mittlerweile auch Abschlussarbeiten (Bachelor, Master) komplett mit RMarkdown geschrieben werden.

Tatsächlich ist Abschnitt 33.5 zur Ordinalen Regression zunächst mit RMarkdown erstellt und veröffentlicht worden 1, das ist aber schon sehr lange her.

Wir erstellen in unserem Projekt ErsteSchritte nun das erste Markdown-Dokument. Klicken Sie hierzu wieder oben im Scriptfenster auf das grüne + Symbol und wählen Sie R Markdown... (Abbildung 22.1).

Abb. 22.1: neues RMarkdown Dokument

Falls Sie dies zum ersten Mal tun, schlägt RStudio die notwendigen Zusatzpaket zur Installation vor (Abbildung 22.2).

Abb. 22.2: Zusatzpakete für RMarkdown installieren

Die Installation dauert ein paar Minuten, und Sie können den Prozess unten im Konsolfenster verfolgen (Abbildung 22.3, siehe grüner Balken).

Abb. 22.3: Installationsprozess

Es öffnet sich ein neus Fenster (Abbildung 22.4). Wir erstellen hier ein Dokument. Sie sehen aber schon, dass RMarkdown weitere Formate (z.B. Präsentationen) ebenfalls unterstützt.

Abb. 22.4: neues HTML Dokument

Tragen Sie den Titel des Dokumentes sowie den Autoren ein. Wir belassen in diesem Beispiel die Default Output Option auf HTML. Sie können aber auch Word oder PDF wählen.

Es wird nun eine Standardvorlage des Dokumentes erzeugt (Abbildung 22.5). Bevor wir uns dem Inhalt zuwenden speichern wir das Dokument zunächst ab. Klicken Sie hierzu auf das Diskettensymbol (Mauszeiger in Abbildung 22.5) oder drücken Sie die Tastenkombination [STRG] + [S]. Geben Sie Ihrem Dokument einen Dateinamen mit der Endung .Rmd (für RMarkdown). Ich habe meine Datei MeinErstesMarkdown.Rmd genannt.

Abb. 22.5: speichern über Diskettensymbol

Die Datei ist nun ebenfalls im Dateiverzeichnis zu sehen (Abbildung 22.6) und kann von dort aus geöffnet werden.

Abb. 22.6: RMarkdown Dokument im Arbeitsverzeichnis

Über dem Scriptfenster befindet sich der Knit-Knopf (Abbildung 22.7, Mauszeigen).

Abb. 22.7: Knit-Knopf

Wenn Sie hier klicken, oder die Tastenkombination [STRG] + [SHIFT] + [K] benutzen, wird aus Ihrem RMarkdown die gewünschte Ausgabedatei (in diesem Beispiel html, also eine Webseite) erzeugt und im Arbeitsverzeichnis gespeichert.

Da die Standardvorlage alle wichtigen Dinge enthält können wir direkt auf den Knit-Knopf klicken. Das RMarkdown-Dokument wird nun “übersetzt”, und Sie können den Prozess im Konsolenfenster verfolgen (Abbildung 22.8).

Abb. 22.8: Knit-Prozess

Ist dieser Schritt erfolgreich, öffnet sich ein neues Fenster mit dem Endresultat (Abbildung 22.9.

Abb. 22.9: fertiges RMarkdown Dokument

In unserem Arbeitsverzeichnis sehen wir ebenfalls die soeben erzeugte Ausgabedatei, in Abbildung 22.10 ist das MeinErstsMarkdown.html.

Abb. 22.10: fertige HTML Datei

Beachten Sie, dass in der html-Datei bereits alle nötigen Ressourcen (z.B. alle Bilder) gespeichert sind. Möchten Sie Ihr html-Dokument mit Kolleginnen und Kollegen teilen, brauchen Sie keine weiteren Dateien kopieren oder versenden. Die html-Webseite wird auf allen Geräten “gleich” aussehen, und es werden z.B. keine Grafiken oder Bilder fehlen.

22.1 Syntax

Die Syntax von RMardown ist recht leicht, und es existiert zudem ein Cheatsheet (siehe Abschnitt 30.1) unter

https://raw.githubusercontent.com/rstudio/cheatsheets/master/rmarkdown-2.0.pdf.

Schauen wir uns unsere eben erzeugte Markdowndatei noch einmal an. Wir löschen alles, bis auf die ersten Zeilen, und beginnen ganz von vorne.

---
title: "Ein neuer Titel"
author: "Jörg große Schlarmann"
date: "27 3 2021"
output: html_document
---

Dies ist die Kopfzeile des Dokuments. Wir können hier den Titel, Autor und das Datum ändern. Unter den drei Strichen — beginnen wir unser Dokument. Schreiben Sie folgende Zeilen.

---
title: "Dies ist ein Test"
author: "Timm Thaler"
date: "27 3 2021"
output: html_document
---

# Überschrift

Willkommen zu meinem RMarkdown-Dokument

- dies ist ein Punkt
- dies ein weitere

1. erstens
2. zweitens
3. drittens

## Unter-Überschrift

Dies ist **fett**, dies *kursiv*, dies ~~durchgestrichen~~.

Drücken Sie den Knit-Knopf und erzeugen so einen Testoutput.

Das Zeichen # steht für Überschriften und deren Hierarchie. Die Anzahl der # legt die Überschriftenhierarchie fest. Aus den Spiegelstrichen ist eine Liste geworden, die Zahlen wurden in eine nummerierte Aufzählungsliste umgewandelt. Schauen Sie im RMarkdown-Cheatsheet nach, welche weiteren Zeichen für welchen Effekt stehen.

In RStudio gibt es auch einen visuellen Markdown-Modus, der so ähnlich funktioniert wie Word oder LibreOffice. So können Sie Ihren Text “wie gewohnt” gestalten. Klicken Sie hierzu auf das A-Symbol rechts oben (siehe Mauszeiger in Abbildung 22.11).

Abb. 22.11: visueller Modus über A

Der Text sieht nun “fast” schon so aus, wie auf dem Endoutput (Abbildung 22.12).

Abb. 22.12: visueller Editiermodus

Sie haben zudem oben eine Menüzeile, mit der Sie die Schriftart wechseln, externe Bilder einbinden oder Tabellen erzeugen können.

Durch Klick auf das A-Symbol wechseln Sie zwischen visueller und “klassischer” Ansicht.

Denken Sie daran, in die klassische Ansicht zurück zu wechseln, bevor Sie Markdown-Code kopieren und einfügen. Dies ist ein häufiger Anfgängerfehler, und der visuelle Editor kann mit dem Code nichts anfangen.

In Markdown können sehr leicht Tabellen erzeugt werden. Versuchen Sie diesen Code aus (vorher vom visuellen Editor zurückwechseln!), und beachten Sie, wie “gut lesbar” und “leicht zu merken” das ist.

## Tabelle

| Name   | Beruf       | Alter |
|--------|-------------|-------|
| Heinz  | Kraftfahrer | 46    |
| Lisa   | Professorin | 32    |
| Jürgen | Klemptner   | 48    |

: Meine Tabelle

Alles weitere finden Sie auf den erwähnten Cheatsheets.

Kommen wir nun zum eigentlichen Kernpunkt: der Darstellung von R-Code und -Output.

Hierfür muss im Markdowndokument der Bereich für den R-Code festgelegt werden. Dies erfolgt über die Zeichenkette ``` , die den Codebereicht einschließt.

```{r}
# hier steht der R-Code
```

Die Anführungsstriche grenzen den Codebereich ein. Dieser wird Chunk genannt. Das {r} bedeutet, dass R-Code verwendet wird.

Achtung, Anfängerfehler

Achten Sie auf die korrekten Anführungszeichen!

Achten Sie darauf, die korrekten ``` zu verwenden, und nicht etwa ''' oder ´´´.

Dies ist ein häufiger Anfängerfehler.

In RStudio können Sie einfach die Tastenkombination [STRG] + [ALT] + [i] drücken, oder im Scriptfenster oben auf das grüne c klicken (Abbildung 22.13).

Abb. 22.13: erstellt neuen Chunk

Beides fügt einen neuen Chunk (Codeblock) ein.

Schreiben Sie in Ihr Dokument:

Ich lade den pf8-Datensatz mit:
```{r}
load(url("https://www.produnis.de/R/pf8.RData"))
```

Wenn Sie den Knit-Knopf drücken, wird der load()-Befehl im Dokument angezeigt und in der Konsole ausgeführt.

Ergänzen Sie jeweils die nun folgende Zeilen, und schauen Sie sich das Ergebnis nach einem Knit an.

Ich lade den pf8-Datensatz, der Befehl wird aber im Dokument nicht angezeigt.
```{r include=FALSE}
load(url("https://www.produnis.de/R/data/pf8.RData"))
```

Durch den Parameter include=FALSE wird der Befehl zwar intern ausgeführt, jedoch nicht im Dokument angezeigt. Sollte der Befehl einen Output erzeugen, wird dieser ebenfalls nicht angezeigt.

Wenn Sie nur das Ergebnis anzeigen möchten, aber nicht die R-Befehle, setzen Sie den Parameter echo=FALSE.

Wir plotten Alter und gewicht aus dem pf8-Datensatz
```{r echo=FALSE}
plot(pf8$Alter, pf8$Gewicht)
```

In der “Standardeinstellung” werden immer Befehl und Output des Chunks im Dokument angezeigt. Sie können dieses Verhalten auch erzwingen durch den Parameter echo=TRUE

Wir plotten Alter und gewicht aus dem pf8-Datensatz
```{r echo=TRUE}
plot(pf8$Alter, pf8$Gewicht)
```

Darstellungsoptionen der Chunks

Verhalten | Code
nur Code, kein Output | results=“hide”
nur Code, nicht ausführen | eval=FALSE
kein Code, nur Output | echo=FALSE
zeige beides | echo=TRUE
zeige nichts | include=FALSE
mache auch nichts | eval=FALSE, include=FALSE

Die Chunks werden im Editorfenster farblich hervorgehoben (Abbildung 22.14).

Abb. 22.14: Chunk ausführen

Sie haben hier die Möglichkeit, den Code “ohne Knit” auszuführen und sich das Ergebnis anzeigen zu lassen. Klicken Sie dazu rechts auf das grüne Dreieck.

In diesem Beispiel wird der Plot nun direkt im Markdownfenster angezeigt (Abbildung 22.15). Dies ist wirklich hilfreich beim Erstellen des Dokuments, denn Sie müssen nicht immer erst das gesamte Dokument knitten oder überhaupt geöffnet haben, um zu sehen, was R an dieser Stelle ausgeben würde.

Abb. 22.15: Der Chunk-Output wird bereits im Editor angezeigt

Über das Zahnrad gelangen Sie zu weiteren Einstellungsmöglichkeiten. So können Sie die Chunks internet mit Namen versehen, so dass Sie später im Dokument darauf referenzieren können.

Die vielen weiteren Möglichkeiten von RMarkdown werden in diesem Buch nicht weiter behandelt, weitere Informationen erhalten Sie im RMarkdown-Cheatsheet (siehe Abschnitt 30.1) oder im frei verfügbaren Buch R Markdown: The Definitive Guide, welches Sie unter https://bookdown.org/yihui/rmarkdown/ lesen können.

Grundsätzlich sollten Sie es so halten, dass Sie in den Scriptdateien Ihre Auswertungen ausprobieren und mit den Befehlen “herumspielen”. Die Quintessenz Ihrer Analyseschritte können Sie dann in “aufgräumter” Form in einem RMarkdown festhalten und mit anderen teilen.

1. https://rpubs.com/produnis/oreg

23 quarto

quarto ermöglicht es, R-Code, -Output und -Diagramme, sowie Formeln und externe Quellen (z.B. Bilder aus dem Internet) in ansprechenden Dokumenten (z.B. Webseite, Word, Powerpoint, PDF) zu exportieren. Es ist sehr gut dafür geeignet, die eigenen statistischen Auswertungen und Ergebnisse für Endanwender (z.b. Auftragsgeber oder Dozenten) darzustellen.

quarto ist der Nachfolger von RMarkdown und wurde 2022 veröffentlicht. Basierend auf den Erkenntnissen der letzten 10 Jahre mit RMarkdown wurde es von Grund auf neu aufgebaut, um mehr Sprachen und Umgebungen zu unterstützen.

quarto unterstützt die Publikationsformate

Artikel, Berichte

Präsentationen

Webseiten, Blogs

Bücher

in den Ausgabeformaten HTML, PDF, ePub und Office.

Dieses Buch wurde jahrelang mit RMarkdown (bookdown) erzeugt und ist im September 2022 auf quarto umgezogen.

Mehr Infos erhalten Sie unter https://www.quarto.org.

23.1 Installation

In einer aktuellen Installation von RStudio ist quarto bereits enthalten.

Unter Linux muss zusätzlich das Paket quarto-cli installiert werden. Für Ubuntu steht ein .deb-Paket unter https://quarto.org/docs/get-started/ bereit, unter Archlinux lautet der Befehl:

yay -S quarto-cli-bin

23.2 Dokument erstellen

Wir erstellen in unserem Projekt ErsteSchritte nun das erste Markdown-Dokument. Klicken Sie hierzu wieder oben im Scriptfenster auf das grüne + Symbol und wählen Sie Quarto Document, siehe Abbildung 23.1…

Abb. 23.1: neues Quarto Dokument

Falls Sie dies zum ersten Mal tun, schlägt RStudio die notwendigen Zusatzpaket zur Installation vor. Die Installation dauert ein paar Minuten, und Sie können den Prozess unten im Konsolfenster verfolgen.

Es öffnet sich ein neues Fenster (Abbildung 23.2). Wir erstellen hier ein Dokument, Sie sehen aber schon, dass quarto weitere Formate (z.B. Präsentationen) ebenfalls unterstützt.

Abb. 23.2: neues HTML Dokument

Tragen Sie den Titel des Dokumentes sowie den Autoren ein. Wir belassen in diesem Beispiel die Default Output Option auf HTML.

Es wird nun eine Standardvorlage des Dokumentes erzeugt (Abbildung 23.3). Bevor wir uns dem Inhalt zuwenden speichern wir das Dokument zunächst ab. Klicken Sie hierzu auf das Diskettensymbol (Mauszeiger in Abbildung 23.3) oder drücken Sie die Tastenkombination [STRG] + [S]. Geben Sie Ihrem Dokument einen Dateinamen mit der Endung .qmd (für quarto-Markdown). Ich habe meine Datei Mein-quarto-Dokument.qmd genannt.

Abb. 23.3: speichern über Diskettensymbol

Die Datei ist nun ebenfalls im Dateiverzeichnis zu sehen (Abbildung 23.4) und kann von dort aus geöffnet werden.

Abb. 23.4: quarto-Dokument im Arbeitsverzeichnis

Über dem Scriptfenster befindet sich der Render-Knopf (Abbildung 23.5, Mauszeiger).

Abb. 23.5: Render-Knopf

Wenn Sie hier klicken, oder die Tastenkombination [STRG] + [SHIFT] + [K] benutzen, wird aus Ihrem quarto-Markdown die gewünschte Ausgabedatei (in diesem Beispiel html, also eine Webseite) erzeugt und im Arbeitsverzeichnis gespeichert.

Da die Standardvorlage alle wichtigen Dinge enthält können wir direkt auf den Render-Knopf klicken. Das quarto-Dokument wird nun “übersetzt”, und Sie können den Prozess im Konsolenfenster verfolgen (Abbildung 23.6).

Abb. 23.6: Render-Prozess

Ist dieser Schritt erfolgreich, öffnet sich ein neues Fenster mit dem Endresultat (Abbildung 23.7).

Abb. 23.7: fertiges quarto Dokument

In unserem Arbeitsverzeichnis sehen wir ebenfalls die soeben erzeugte Ausgabedatei, in Abbildung 23.8 ist das Mein-quarto-Dokument.html.

Abb. 23.8: fertige HTML-Datei

23.3 Editor-Ansicht

Das quarto-Dokument startet im “visuellen Editor”, der so ähnlich funktioniert wie Word oder LibreOffice. So können Sie Ihren Text “wie gewohnt” gestalten. Sie haben zudem oben eine Menüzeile, mit der Sie die Schriftart wechseln, externe Bilder einbinden oder Tabellen erzeugen können.

Auf der linken Seite können Sie zwischen dem visuellen Editor und der Code-Ansicht wechseln.

Source-Editor

Visueller Editor

Denken Sie daran, in die klassische Code-Ansicht zurück zu wechseln, bevor Sie Markdown-Code kopieren und einfügen.

Dies ist ein häufiger Anfgängerfehler, und der visuelle Editor kann mit dem Code nichts anfangen.

23.4 Dokumentenaufbau

Schauen wir uns unsere eben erzeugte Markdowndatei noch einmal an. Wechseln Sie hierzu in die Code-Ansicht.

---
title: "Mein quarto Bericht"
author: "Jörg große Schlarmann"
format: html
editor: visual
---

Dies ist die Kopfzeile des Dokuments, die im yaml-Format angegeben ist (so genannter yaml-Header). Wir können hier den Titel, Autor und das Datum ändern. Alle Parameter des Headers finden Sie unter https://quarto.org/docs/reference/formats/html.html

Unter den drei Strichen --- beginnen wir unser eigentliches Dokument. Schreiben Sie folgende Zeilen in Ihr Dokument:

---
title: "Mein quarto Bericht"
author: "Jörg große Schlarmann"
date: "01 09 2022"
format: html
editor: visual
---

# Überschrift

Willkommen zu meinem quarto-Markdown-Dokument

- dies ist ein Punkt

- dies ein weiterer

1. erstens

2. zweitens

3. drittens

## Unter-Überschrift

Dies ist **fett**, dies *kursiv*, dies ~~durchgestrichen~~.

Drücken Sie den Render-Knopf und erzeugen so einen Testoutput.

Abb. 23.9: fertig gerendertes Testdokument

Das Zeichen # steht für Überschriften und deren Hierarchie. Die Anzahl der # legt die Überschriftenhierarchie fest. Aus den Spiegelstrichen ist eine Liste geworden, die Zahlen wurden in eine nummerierte Aufzählungsliste umgewandelt.

23.5 Markdown-Syntax

Die Syntax von Markdown ist recht leicht, die folgenden Beispiele wurden aus dem Quarto-Guide hierhin übernommen:

23.5.1 Textformat

Markdown Syntax
Output

*italics* and **bold**

italics and bold

superscript^2^ / subscript~2~

superscript² / subscript₂

~~strikethrough~~

strikethrough

`verbatim code`

verbatim code

23.5.2 Überschriften

Markdown Syntax
Output

# Header 1

24 Header 1

## Header 2

24.1 Header 2

### Header 3

24.1.1 Header 3

#### Header 4

24.1.1.1 Header 4

##### Header 5

24.1.1.1.1 Header 5

###### Header 6

24.1.1.1.1.1 Header 6

24.1.2 Links & Bilder

Markdown Syntax
Output

<https://quarto.org>

https://quarto.org

[Quarto](https://quarto.org)

Quarto

![Caption](images/elephant.png)

Caption

[![Caption](images/elephant.png)](https://quarto.org)

[![Caption](images/elephant.png)](https://quarto.org "Ein Elefant")

[![](images/elephant.png){fig-alt="Alt text"}](https://quarto.org)

24.1.3 Listen

Markdown Syntax
Output

* unordered list
    + sub-item 1
    + sub-item 2
        - sub-sub-item 1

unordered list

sub-item 1

sub-item 2

sub-sub-item 1

*   item 2

    Continued (indent 4 spaces)

item 2

Continued (indent 4 spaces)

1. nummerierte Liste
2. item 2
    i) sub-item 1
         A.  sub-sub-item 1

nummerierte Liste

item 2

sub-item 1

sub-sub-item 1

(@)  Eine Liste deren Nummerierung

nach einer Unterbrechung

(@)  fortgeführt wird

Eine Liste deren Nummerierung

nach einer Unterbrechung

fortgeführt wird

Term
: Definition

Term

Definition

24.1.4 Tabellen

In Markdown können sehr leicht Tabellen erzeugt werden. Versuchen Sie diesen Code aus (vorher vom visuellen Editor zurückwechseln!), und beachten Sie, wie “gut lesbar” und “leicht zu merken” das ist.

24.1.4.1 Markdown Syntax

| Right | Left | Default | Center |
|------:|:-----|---------|:------:|
|   12  |  12  |    12   |    12  |
|  123  |  123 |   123   |   123  |
|    1  |    1 |     1   |     1  |

24.1.4.2 Output

Right | Left | Default | Center
12 | 12 | 12 | 12
123 | 123 | 123 | 123
1 | 1 | 1 | 1

24.1.5 Gleichungen

Mit $ können In-Text-Formeln erzeugt werden, mit $$ schließt man Matheumgebungen ein:

Markdown Syntax
Output

inline math: $E = mc^{2}$

inline math: E=mc2E=mc^{2}

display math:

$$E = mc^{2}$$

display math:
E=mc2E = mc^{2}

24.2 R-Code integrieren

Kommen wir nun zum eigentlichen Kernpunkt: der Darstellung von R-Code und -Output.

Hierfür muss im Markdowndokument der Bereich für den R-Code festgelegt werden. Dies erfolgt über die Zeichenkette ``` , die den Codebereicht einschließt.

```{r}
# hier steht der R-Code
```

Die Anführungsstriche grenzen den Codebereich ein. Dieser wird Chunk genannt. Das {r} bedeutet, dass R-Code verwendet wird.

Achten Sie auf die korrekten Anführungszeichen!

Achten Sie darauf, die korrekten ``` zu verwenden, und nicht etwa ''' oder ´´´. Dies ist ein häufiger Anfängerfehler.

In RStudio können Sie einfach die Tastenkombination [STRG] + [ALT] + [i] drücken, oder im Scriptfenster oben auf das grüne c klicken, siehe Mauszeiger in Abbildung 24.1.

Abb. 24.1: einen neuen Chunk erstellen

Beides fügt einen neuen Chunk (Codeblock) ein.

Die Chunks werden im Editorfenster farblich hervorgehoben.

Tragen Sie folgenden Code in den Chunk ein:

load(url("https://www.produnis.de/R/data/pf8.RData"))
plot(pf8$Alter, pf8$Gewicht)

Wenn Sie den Render-Knopf drücken, wird der load()-Befehl im Dokument angezeigt und in der Konsole ausgeführt. Das neu gerenderte HTML-Dokument öffnet sich automatisch.

Abb. 24.2: Dokument mit Plot

Sie haben auch die Möglichkeit, den Code auszuführen und sich das Ergebnis anzeigen zu lassen, ohne das komplette Dokument zu Rendern . Klicken Sie dazu rechts auf das grüne Dreieck (Abbildung 24.3).

Abb. 24.3: Chunk-Code ausführen

In diesem Beispiel wird der Plot nun direkt im Markdownfenster angezeigt (Abbildung 24.4). Dies ist wirklich hilfreich beim Erstellen des Dokuments, denn Sie müssen nicht immer erst das gesamte Dokument rendern oder überhaupt geöffnet haben, um zu sehen, was R an dieser Stelle ausgeben würde.

Abb. 24.4: Der Chunk-Output wird bereits im Editor angezeigt

Über das Symbol links daneben (Abbildung 24.5, siehe Mauszeiger) lassen sich “alle Chunks bis hier hin” ausführen.

Abb. 24.5: alle Chunks oberhalb ausführen

Dies ist zum Beispiel hilfreich, wenn in den vorherigen Chunks Objekte oder Variablen erzeugt wurden, auf die nun zugegriffen werden soll.

24.2.1 Chunk-Optionen

Den Chunks können Parameter übergeben, um ihr Verhalten zu steuern. Dies erfolgt über die Zeichenfolge #| direkt am Zeilenanfang.

```{r}
#| label: fig-testplot # zum verlinken der Plots
#| fig-cap: "Testplot" # Plotunterschrift
#| echo: true          # R-Befehle anzeigen
#| output: true        # R-Output anzeigen
#| warning: false      # Warnung unterdrücken
#| include: true       # Chunk anzeigen
#| eval: true          # Code ausführen
load(url("https://www.produnis.de/R/data/pf8.RData"))
plot(pf8$Alter, pf8$Gewicht)
```

## Warning in load(url("https://www.produnis.de/R/pf8.RData")): Kann URL
## 'https://www.produnis.de/R/pf8.RData' nicht öffnen: HTTP Status war '404 Not
## Found'

## Error in load(url("https://www.produnis.de/R/pf8.RData")): kann Verbindung zu 'https://www.produnis.de/R/pf8.RData' nicht öffnen

Abb. 24.6: Testplot

Der Plot ist über den Referenzierer @fig-testplot ansprechbar. Dieser wird beim rendern umgewandelt in →\rightarrow “Abbildung 24.6”.

Die Standard-Parameter lauten:

label - gibt dem Chunk einen Namen. Dies ist beim Debugging sehr hilfreich. Soll auf die Plots des Chunks referenziert werden, muss das Label zwingend mit fig- anfangen.

fig-cap - gibt den Plottitel an.

echo - gibt an, ob die Befehle des Chunks im Dokument angezeigt werden sollen (true/false).

output - gibt an, ob die R-Ausgaben im Dokument angezeigt werden sollen (true/false).

warning - gibt an, ob die Warnungen, die möglicherweise beim Ausführen des Codes ausgegeben werden, im Dokument angezeigt werden sollen (true/false).

include - gibt an, ob der Chunk (Befehle und Ausgabe) im Dokument angezeigt werden soll (true/false).

eval - gibt an, ob der Chunk ausgeführt (true) werden, oder nur der Chunkinhalt angezeigt werden soll (false).

24.2.2 Intext-Integration

Auch im Fließtext können R-Objekte referenziert werden. Die Syntax dazu lautet `r BEFEHL`.

Der Mittelwert des Alters beträgt `r mean(pf8$Alter, na.rm=TRUE)`, wobei
die Standardabweichumg mit `r sd(pf8$Alter, na.rm=TRUE)` hoch ist.

ergibt:

Der Mittelwert des Alters beträgt 37.0027397, wobei die Standardabweichumg mit 17.9997712 hoch ist.

24.3 PDF-Dokumente erzeugen

Da PDF-Dokumente von quarto intern mittels $\LaTeX$ erzeugt werden, müssen Sie auf Ihrem PC eine $\LaTeX$ Distribution installiert haben.

Falls dies nicht der Fall ist, können Sie TinyTeX mit folgendem Behfehl im RStudio-Terminal installieren:

quarto install tool tinytex

Mit dieser Methode installiert quarto alle weiteren Pakete automatisch, sofern diese benötigt werden. Sollten Sie bereits eine $\LaTeX$-Distribution installiert haben, müssen Sie das Paket fontawesome installieren, damit alles funktioniert.

24.4 quarto-Dokumentation

Die Webseite von quarto bietet umfangreiche Anleitungs- und Referenzierungsdokumente an.

Der Guide leitet Sie durch die Erstellung Ihrer Dokumente, siehe https://quarto.org/docs/guide/, und ist für Einsteiger gut geeignet.

Für HTML z.B. https://quarto.org/docs/output-formats/html-basics.html

Die References listen alle Parameter und Einstellungsmöglichkeiten der quarto-Dokumente auf, siehe https://quarto.org/docs/reference/.

Für HTML z.B. https://quarto.org/docs/reference/formats/html.html

Auf Youtube gibt es eine offizielle Quarto-Playlist

https://www.youtube.com/playlist?list=PL9HYL-VRX0oRupficE2l5DGgVIzpypTHs

In der Awesome Quarto List auf Github werden quartobezogene Anleitungen, Präsentationen, Erweiterungen und Beispiele gesammelt.

Beachten Sie auch Kapitel 40.

25 Tidyverse

Das Tidyverse ist eine Weiterentwicklung von R, die maßgeblich von Hadley Wickham vorangetrieben wurde (weshalb auch die Bezeichung Hadleyverse noch gebräuchlich ist. Wickham ist mittlerweile Chefentwickler von RStudio). Das Standardwerk zum Tiddyverse “R for Data Science” (kurz R4DS) stammt ebenfalls von Wickham (2017) und ist online frei verfügbar unter https://r4ds.had.co.nz/ (eine physische Kopie des Buches kann ebenfalls gekauft werden).

Das Tidyverse folgt streng dem Konzept Tidy Data (Wickham, 2014), welches besagt, dass ein Datensatz so aufgebaut sein muss, dass jeweils ein Fall pro Zeile abgebildet wird. Das bedeutet, dass jede Beobachtung (auch Wiederholungen) in einer eigenen Zeile steht, und die jeweiligen Variablen durch die Spalten repräsentiert werden.

Abb. 25.1: Tidy Data Konzept

Abbildung 25.1 zeigt die Sicht auf ein Datenobjekt nach dem Tidy Data Konzept. Jede Variable wird als eigene Spalte dargestellt. Jede Reihe entspricht einer Beobachtung. Somit entsprechen die einzelnen Werte in der Tabelle genau einer Variable zu einer Beobachtung.

Man spricht in diesem Zusammenhang von “long table” und “wide table”.

Die Matrix der Pflegeberufe, die wir aus Abbildung 8.1 übernommen haben, stellt dabei die wide table, die breite Tabelle dar.

                         1999   2001   2003   2005   2007   2009   2011   2013   2015
Krankenpflegeassistenz  16624  19061  19478  21537  27731  36481  46517  54371  64127
Altenpflegehilfe        55770  52710  49727  45776  48326  47903  47978  48363  49507
Kinderkrankenpflege     47779  48203  48822  48519  49080  49307  48291  48937  48913
Krankenpflege          430983 436767 444783 449355 457322 465446 468192 472580 476416
Altenpflege            109161 124879 141965 158817 178902 194195 208304 227154 246412

“Breit” bedeutet, dass die Tabelle, wenn wir ihr nun 10 weitere Jahrgänge hinzufügen würden, immer breiter und breiter werden würde. Die wide table ist als Darstellung der Daten sicherlich gut geeignet, und die Leser erhalten eine gute Übersicht. Für die Datenanalyse und -verarbeitung ist dieses Format eher ungeeignet, da mit jedem Wert in der Tabelle eigentlich zwei Variablen andressiert werden (konkrete Berufsgruppe in einem konkreten Jahr). Welche beiden Variablen das nun genau sind, lässt sich am Wert alleine nicht ablesen, und wir hätten große Probleme, wenn Zeilen und Spalten nicht benannt wären!

Als Faustregel kann man sich merken, dass eine wide table dann vorliegt, wenn auch (bedeutsame) Zeilennamen im Datensatz vergeben wurden (was bei einer matrix häufig der Fall ist).

Ein Datensatz nach dem Tidy Data-Konzept ist vom Typ long table.

Für die Matrix der Pflegeberufe sähe die long table-Version das Datensatzes so aus:

   Jahr           Berufsgruppe Anzahl
1  1999 Krankenpflegeassistenz  16624
2  2001 Krankenpflegeassistenz  19061
3  2003 Krankenpflegeassistenz  19478
4  2005 Krankenpflegeassistenz  21537
5  2007 Krankenpflegeassistenz  27731
6  2009 Krankenpflegeassistenz  36481
7  2011 Krankenpflegeassistenz  46517
8  2013 Krankenpflegeassistenz  54371
9  2015 Krankenpflegeassistenz  64127
10 1999       Altenpflegehilfe  55770
11 2001       Altenpflegehilfe  52710
12 2003       Altenpflegehilfe  49727
            ( . . . )

Jede Spalte repräsentiert eine Variable, und jede Zeile repräsentiert eine Beobachtung. Auch ohne die Spaltennamen (Zeilennamen gibt es gar keine) könnten wir erkennen, worum es in dem Datensatz geht.

Der Name long table leitet sich davon ab, dass die Tabelle, wenn wir ihr mehr Daten hinzufügen würden, immer länger und länger werden würde.

Die Tatsache, dass die Verarbeitung solcher long tables wesentlich einfacher ist, bildet den Grundgedanken des Tidyverse, und es wurden spezielle R-Erweitrungen implementiert, die speziell an Tidy Data angepasst sind. Die Erweiterungen wurden nach dieser gemeinsame Philosophie entworfen, sie verwenden die selbe “Grammatik” und greifen auf die selbe Datenstruktur (tibbles, dazu später mehr) zurück.

Abb. 25.2: Tidy Data Konzept

Abbildung 25.2 zeigt den typischen Ablauf einer Datenauswertung in 4 Schritten.

Die Daten müssen importiert und

gegebenenfalls angepasst (ins tidy data bzw. long table Format überführt werden).

Anschließend werden die Daten “statistisch ausgewertet”, d.h. wir versuchen zu verstehen, was wir in den Daten sehen und welche spezifischen Informationen extrahiert werden können. Das heisst die Daten werden transformiert (z.B. indem man Untergruppen bildet) und visualisiert (z.B. in Form von Diagrammen und Tabellen) oder wir rechnen Modelle wie Korrelationen, Regressionen und Signifikanztests.

Unsere Erkenntnisse nutzen wir dann zur Kommunikation (Präsentation) unserer Forschungsergebnisse.

Für all diese Arbeitsschritte stellt das Tidyverse passende Pakete und Funktionen in R zur Verfügung, und wir werden die Schritte nun einzeln durchlaufen.

25.1 Pakete

In R besteht das Tidyverse im Wesentlichen aus dem Zusatzpaket tidyverse, welches alle notwendingen Unterpakete als Abhängigkeit mit-installiert:

Tibble, die Datenklasse im Tidyverse, im Paket

tibble, mit den Funktionen
tibble() as_tibble()

Daten importieren:

readr, mit den Funktionen
read_csv(), read_tsv(), read_delim(), read_table()

Daten tidy machen:

tidyr, mit den Funktionen
pivot_longer(), pivot_wider(), separate(), unite(), drop_na(), replace_na(), fill(), complete(), expand()

forcats, mit den Funktionen
as_factor()

Umgang mit Datensätzen:

dplyr, mit den Funktionen
arrange(), filter(), select(), pull(), mutate(), transmute(), rename(), summarise(), bind_cols(), bind_rows()

purrr, mit den Funktionen
modify(), map(), reduce(), nest()

stringr, mit den Funktionen
str_sub(), bind_locate(), bind_extract(), bind_count(), bind_match()

Daten visualisieren:

ggplot, mit den Funktionen
ggplot(), qplot()

Dazu später mehr, denn um die Funktionen vorzustellen, müssen wir uns zunächst der Pipe zuwenden.

25.2 Pipe

Ein wichtiger Operator für die Arbeit im Tidyverse ist die Pipe aus dem magrittr-Zusatzpaket (die Pipe wird mit tidyverse aktiviert, Sie müssen magrittr nicht extra installieren). Sie erlaubt es, Datenströme weiterzuleiten. Ihre Funktionalität hat so sehr überzeugt, dass sie im Standard-R ab Version 4.1 enthalten ist, siehe Kapitel 11.

Hierfür wurde ein eigener Operator implementiert, die Zeichenkette %>% (im Standard-R lautet die Zeichenkette |>).

Sie bedeutet so viel wie “und dann”.

Zur Eklärung sei nochmals der “klassische Weg” der Arbeitsschritte in R aufgezeigt:

# wir machen etwas, und speichern es in ein Objekt
habe.getan <- mache.etwas(Datensatz)

# dann machen wir etwas weiteres,
# und speichern wieder in ein Objekt
habe.weiteres.getan <- mache.weiteres(habe.getan)

# und kommen schließlich zum Endergebnis
endergebnis <- mache.noch.letzte.Sache(habe.weiteres.getan)

Mit Einsatz der Pipe wird dieser Prozess quasi umgekehrt in

# Speichere Endergebnis
endergebnis <- Datensatz %>%
                gruppiere.nach.geschlecht %>%
                  sortiere Alter aufsteigend %>%
                    nimm die letzten 4 Werte

Die liest sich in etwa so:

“Speichere etwas ins Objekt endergebnis, und das geht so…

nimm den Datensatz und dann

gruppiere die Daten nach geschlecht und dann

sortiere nach Alter aufsteigend und dann

nimm die letzten 4 Werte. ”

Die Pipe reicht das jeweilige Ergebnis (den Datenstrom) an die nächste Code-Zeile weiter. Erst wenn die letzte Zeile durchgelaufen ist, wird das Resultat in endergebnis gespeichert.

Dies liest sich zu Beginn evtl. etwas ungewohnt, aber Sie können erkennen, dass die Befehle so wesentlich übersichtlicher und die einzelnen Manipulationsschritte nachvollziehbarer geworden sind. Auch kann man sich diese Art der “Grammatik” relativ leicht merken.

Benötigt eine Funktion die Angabe eines Datensets, so kann mit einem Punkt . auf den Pipe-Datenstrom verwiesen werden.

# Der Punkt übergibt den Pipe-Strom an die Funktion sum()
endergebnis <- Datensatz %>%
                    wähle Spalte "Alter" %>%
                        # berechne Summe
                        sum(.)

Die Pipe funktioniert bei jedem R-Objekt, egal in welchem Format (long table vs. wide table, Faktor, Vektor, Matrix, usw.) es vorliegt.

25.2.1 Unterschiede zwischen %>% und |>

Während sich |> und %>% in einfachen Fällen identisch verhalten, gibt es ein paar wichtige Unterschiede. Diese betreffen Sie aber nur, wenn Sie %>% schon lange nutzen und einige der fortgeschrittenen Funktionen verwendet haben.

Standardmäßig übergibt die Pipe das Objekt auf ihrer linken Seite an das erste Argument der Funktion auf der rechten Seite. Mit %>% können Sie die Platzierung des Datenstroms mit dem Platzhalter “.” ändern. Zum Beispiel ist x %>% f(1) äquivalent zu f(x, 1), aber x %>% f(1, .) ist äquivalent zu f(1, x). Seit R 4.2.0 ist ein ähnlicher Platzhalte über _ zur Basis-Pipe hinzugefügt worden, jedoch muss hier immer ein Argument benannt werden. Zum Beispiel ist x |> f(1, y = _) äquivalent zu f(1, y = x).

Mit %>% kann man die Klammern weglassen, wenn man eine Funktion ohne weitere Argumente aufruft; |> erfordert immer die Klammern.

Der Platzhalter |> ist absichtlich einfach gehalten und kann viele Eigenschaften des Platzhalters %>% nicht wiedergeben. Sie können ihn nicht an mehrere Argumente übergeben, und er hat kein besonderes Verhalten, wenn er innerhalb einer anderen Funktion verwendet wird. Zum Beispiel ist df %>% split(.$var) äquivalent zu split(df, df$var) und df %>% {plot(.$x, .$y)} ist äquivalent zu plot(df$x, df$y).

Mit %>% können Sie den Punkt . auf der linken Seite von Operatoren verwenden, so dass Sie eine einzelne Spalte aus einem Datenframe mit (z. B.) mtcars %>% .$cyl extrahieren können. Dies geht mit |> nicht.

Glücklicherweise gibt es keinen Grund, sich ganz auf die eine oder andere Pipe festzulegen - Sie können die Basis-Pipe für die meisten Fälle verwenden, und die Magrittr-Pipe benutzen, wenn Sie deren speziellen Eigenschaften wirklich benötigen.

25.3 tibbles

Da dieses Konzept der long table so essenziell ist, wurde im Tidyverse eine eigene Datenklasse eingeführt, die tibble heisst (das kommt daher, dass tidy data immer Tabellen sind, was häufig per tbl abgekürzt wird. Liest man im Englischn tbl laut und schnell, klingt das wie - genau - tibble).

Sie entspricht in etwa dem Datenframe, hat aber intern noch weitere wichtige Änderungen eingebaut.

Tibbles werden so wie Datenframes erzeugt, indem man gleichlange Vektoren übergibt. Der Funktionsname ist tibble().

# aktiviere Tidyverse
library(tidyverse)

# erzeuge ein tibble
tibble(a=c(1,2,3), b=c("a", "b", "c"), c=c(T, F, T))

## # A tibble: 3 × 3
##       a b     c
##   <dbl> <chr> <lgl>
## 1     1 a     TRUE
## 2     2 b     FALSE
## 3     3 c     TRUE

Die Schwesterfunktion tribble() (mit r) erlaubt diese Schreibweise

# erzeuge ein tibble mit tRibble
tribble(
  ~Text, ~Zahl, ~Logic,
  "a",     1,     T,
  "b",     2,     F,
  "c",     3,     T
  )

## # A tibble: 3 × 3
##   Text   Zahl Logic
##   <chr> <dbl> <lgl>
## 1 a         1 TRUE
## 2 b         2 FALSE
## 3 c         3 TRUE

Sind die übergebenen Reihen nicht gleich lang, werden sie als list gespeichert.

# Datenreihen NICHT gleich lang
tribble(
  ~Text, ~Zahl, ~Logic,
  "a",     1:3,     T,
  "b",     4:6,     F,
  "c",     7:9,     T
)

## # A tibble: 3 × 3
##   Text  Zahl      Logic
##   <chr> <list>    <lgl>
## 1 a     <int [3]> TRUE
## 2 b     <int [3]> FALSE
## 3 c     <int [3]> TRUE

Die meisten Funktionen des Tiddyverse funktioneren auch mit Datenframes, eben weil sie sich so ähnlich sind. Dennoch ergibt es (schon ideologisch) Sinn, die Datenframes in tibbles umzuwandeln.

Schauen wir uns hierfür die Daten der Pflegeberufe als long table an:

# Pflegeframe
head(Pflegeframe)

##   Jahr           Berufsgruppe Anzahl
## 1 1999 Krankenpflegeassistenz  16624
## 2 2001 Krankenpflegeassistenz  19061
## 3 2003 Krankenpflegeassistenz  19478
## 4 2005 Krankenpflegeassistenz  21537
## 5 2007 Krankenpflegeassistenz  27731
## 6 2009 Krankenpflegeassistenz  36481

# Übersicht von "Pflegeframe"
str(Pflegeframe)

## 'data.frame':    45 obs. of  3 variables:
##  $ Jahr        : Factor w/ 9 levels "1999","2001",..: 1 2 3 4 5 6 7 8 9 1 ...
##  $ Berufsgruppe: Factor w/ 5 levels "Krankenpflegeassistenz",..: 1 1 1 1 1 1 1 1 1 2 ...
##  $ Anzahl      : num  16624 19061 19478 21537 27731 ...

Wir erhalten eine Zusammenstellung von wichtigen Informationen. Unser Pflegetibble hat 45 Beobachtungen (Reihen) von 3 Variablen (Spalten). Die Variablen Jahr und Berufsgruppe liegen als Faktoren vor, Anzahl ist numerisch.

# Datenklasse von "Pflegeframe"
class(Pflegeframe)

## [1] "data.frame"

Jetzt wandeln wir das Pflegeframe in ein Pflegetibble um.

# erzeuge Pflegetibble aus Pflegeframe
Pflegetibble <- tibble(Pflegeframe)

# Datenklasse von "Pflegeframe"
class(Pflegetibble)

## [1] "tbl_df"     "tbl"        "data.frame"

Wie Sie sehen, ist unser Tibble immernoch ein Datenframe. Es ist aber auch ein “Tibble Datenframe” (tbl_df) und einfach ein Tibble (tbl).

Die Funktion zum Einsehen von Tibbles heisst glimpse() (englisch für Blick).

# werfe einen Blick auf "Pflegetibble"
glimpse(Pflegetibble)

## Rows: 45
## Columns: 3
## $ Jahr         <fct> 1999, 2001, 2003, 2005, 2007, 2009, 2011, 2013, 2015, 199…
## $ Berufsgruppe <fct> Krankenpflegeassistenz, Krankenpflegeassistenz, Krankenpf…
## $ Anzahl       <dbl> 16624, 19061, 19478, 21537, 27731, 36481, 46517, 54371, 6…

Die Ausgabe sieht fast genau so aus wie der str()-Aufruf. Neben den Variablennamen steht der Datentyp, wobei <fct> für Faktor, <dbl> für Dezimalzahlen und <lgl> für logical steht.

Im klassischen R hat man ein data.frame mit str() angeschaut, im Tidyverse schaut man mit glimpse() auf ein tibble.

25.4 Funktionsaufrufe

In R gibt es jede Menge Zusatzpakete, und obschon die Programmierer sich Mühe geben, einen Funktionsnamen nicht doppelt zu vergeben (vielleicht ist Ihnen schon aufgefallen, dass im Tidyverse viele Funktionen mit einem Unterstrich _ geschrieben werden, z.B. read_sav() oder as_factor()), kann dies durchaus vorkommen.

Wenn wir das Tidyverse per library() aktivieren, erhalten wir folgende Informationen:

library(tidyverse)

## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors

── Attaching packages ─────────────────── tidyverse 1.3.0 ──
✓ ggplot2 3.3.3     ✓ purrr   0.3.4
✓ tibble  3.1.0     ✓ dplyr   1.0.5
✓ tidyr   1.1.3     ✓ stringr 1.4.0
✓ readr   1.4.0     ✓ forcats 0.5.1
── Conflicts ──────────────────────────── tidyverse_conflicts() ──
x dplyr::filter() masks stats::filter()
x dplyr::lag()    masks stats::lag()

Wie Sie sehen, gibt es nun Konflikte, da die Funktionen filter() und lag() jeweils in den Paketen tidyverse und stats (R-Basispaket) existieren. Das heisst, möchte man die Funktion filter() verwenden, muss man R mitteilen, aus welchem Paket sie stammt. Dies erfolgt in R indem man den Paketnamen getrenn durch zwei Doppelpunkte vor die Funktion schreibt.

# nutze Funktion filter() aus dem Paket "dplyr"
dplyr::filter()

# nutze Funktion filter() aus dem "stats" Paket
stats::filter()

Im Tidyverse ist es daher durchaus üblich, den Paketnamen mit zwei Doppelpunkten getrennt vor den Funktionsnamen zu setzen. So ist immer klar, welche Funktion aus welchem Paket denn nun aufgerufen werden soll.

# Paketname::Funktionsname
haven::read_sav("spss.sav")

Durch die Refernzierung des Paketnamens sparen Sie sich das Einbinden des gesamten Paketes per library().

Dies funktioniert auch bei den zahlreichen Datensätzen, die R mitliefert. Möchten Sie den Datensatz flights aus dem Paket nycflights13 nutzen, können Sie (nachdem das Paket nycflights13 installiert wurde) wie folgt referenzieren:

# Installiere Paket "nycflights13"
install.packages("nycflights13")

# Paketname::Datensatz
head(nycflights13::flights)

26 Schritt 1: Daten importieren

Das Importieren von Daten unterscheidet sich nicht von den Methoden aus Kapitel 14. Tidyverse bringt mit readr() ein eigenes Paket zum Importieren mit, dies ist aber nur für Textdateien wirklich geeignet. Mit den Zusatzpaketen haven und readxl können Sie wie in Kapitel 14 beschrieben über die Funktionen read_sav(), read_xlsx() und read_csv() Ihre Daten einlesen.

In RStudio können Sie einfach im Datenfenster auf Import Dataset klicken, siehe Abschnitt Abschnitt 14.6.

26.1 gelabelte SPSS-Daten

Hier sei noch einmal erwähnt, dass in SPSS kategoriale Daten häufig mit Labels versehen werden.

# Lade Test-SPSS-Datei mit Labels
spss <- haven::read_sav(url("https://www.produnis.de/R/data/alteDaten-lang.sav"))
head(spss)

## # A tibble: 6 × 6
##   v1                 v2                 v3            v4       v5       thetruth
##   <dbl+lbl>          <dbl+lbl>          <dbl+lbl>     <dbl+lb> <dbl+lb> <dbl+lb>
## 1 4 [strongly agree] 4 [strongly agree] 4 [strongly … NA       NA       4 [stro…
## 2 4 [strongly agree] 4 [strongly agree] 4 [strongly … NA       NA       4 [stro…
## 3 4 [strongly agree] 4 [strongly agree] 4 [strongly … NA       NA       4 [stro…
## 4 4 [strongly agree] 3 [agree]          3 [agree]      4 [str…  4 [str… 4 [stro…
## 5 3 [agree]          3 [agree]          2 [disagree]   2 [dis…  4 [str… 4 [stro…
## 6 2 [disagree]       3 [agree]          3 [agree]      2 [dis…  4 [str… 4 [stro…

Mit head() sehen wir, dass die Werte der Spalten gelabelt sind (4=strongly agree, 3=agree, usw.). Die Labels stehen in eckigen Klammern neben dem eingentlichen Wert der Variable.

Mit glimpse() werden die Labels ignoriert

# glimpse() zeigt KEINE Labels
glimpse(spss)

## Rows: 45,278
## Columns: 6
## $ v1       <dbl+lbl> 4, 4, 4, 4, 3, 2, 2, 2, 4, 3, 2, 3, 2, 2, 4, 4, 3, 2, 4, …
## $ v2       <dbl+lbl> 4, 4, 4, 3, 3, 3, 1, 3, 4, 3, 2, 2, 2, 2, 4, 3, 1, 2, 4, …
## $ v3       <dbl+lbl> 4, 4, 4, 3, 2, 3, 3, 3, 4, 3, 2, 2, 2, 3, 4, 2, 3, 3, 4, …
## $ v4       <dbl+lbl> NA, NA, NA,  4,  2,  2,  2,  3, NA,  4,  2,  4,  4,  2, N…
## $ v5       <dbl+lbl> NA, NA, NA,  4,  4,  4,  1,  3, NA,  4,  4,  1,  4,  4, N…
## $ thetruth <dbl+lbl>  4,  4,  4,  4,  4,  4,  4,  3,  1,  4,  4,  4,  3,  4, N…

In R ist die Verwendung von Wertelabels eher untypisch. Es empfiehlt sich, die Ausprägungsstufen so wie sie sind als Werte einzutragen, also z.B. direkt “männlich” - “weiblich” - “divers” anstatt 0 - 1 - 2 und anschließender Labelung. So werden die Ausprägungen auch auf den Grafiken entsprechend “aussagekräftig” angezeigt.

Wir wandeln alle Variablen in Faktoren um, welche die Labels als Levelnamen nutzen (die Funktion mutate() wird später genauer erläutert).

spss %>%
  dplyr::mutate(as_factor(.))

## # A tibble: 45,278 × 6
##   v1             v2             v3             v4             v5        thetruth
##   <fct>          <fct>          <fct>          <fct>          <fct>     <fct>
## 1 strongly agree strongly agree strongly agree <NA>           <NA>      strongl…
## 2 strongly agree strongly agree strongly agree <NA>           <NA>      strongl…
## 3 strongly agree strongly agree strongly agree <NA>           <NA>      strongl…
## 4 strongly agree agree          agree          strongly agree strongly… strongl…
## 5 agree          agree          disagree       disagree       strongly… strongl…
## 6 disagree       agree          agree          disagree       strongly… strongl…
## # ℹ 45,272 more rows

Wenn die Daten umfangreich gelabelt sind, kann es hilfreich sein, sich ein “Wörterbuch” der Labels anzulegen. Hierfür nutzt man die Funktion generate_dictionary() aus dem labelled-Paket.

dict <- labelled::generate_dictionary(spss)

Das Objekt dict ist ein Datenframe, in welchem die Labels der Variablen gespeichert sind. In RStudio kann man die Labels so im Datenviewer durchsuchen.

Abb. 26.1: Labelwörterbuch

Da wir die SPSS-Datei mit den Funktionen des haven-Pakets importiert haben, liegen gelabelte Werte und Variablen als Klasse haven_labelled vor. Diese Eigenschaft machen wir uns in den nächsten Code-Schnipseln zu Nutze.

Mit diesen Befehlen werden alle Werte der im Datensatz enthaltenen Variablen, die als haven_labelled klassifiziert ist, in einen Factor umgewandelt:

spssfct <- spss %>%
  mutate_if(haven::is.labelled, haven::as_factor)
glimpse(spssfct)

## Rows: 45,278
## Columns: 6
## $ v1       <fct> strongly agree, strongly agree, strongly agree, strongly agre…
## $ v2       <fct> strongly agree, strongly agree, strongly agree, agree, agree,…
## $ v3       <fct> strongly agree, strongly agree, strongly agree, agree, disagr…
## $ v4       <fct> NA, NA, NA, strongly agree, disagree, disagree, disagree, agr…
## $ v5       <fct> NA, NA, NA, strongly agree, strongly agree, strongly agree, s…
## $ thetruth <fct> strongly agree, strongly agree, strongly agree, strongly agre…

Und folgende Befehle wandeln Variablenlabels in Variablennamen um, hierzu muss das Paket sjlabelled installiert sein:

spssn <- spssfct %>%
  sjlabelled::label_to_colnames()
glimpse(spssn)

## Rows: 45,278
## Columns: 6
## $ `Blue Question`                                      <fct> strongly agree, s…
## $ `Red Question`                                       <fct> strongly agree, s…
## $ `Green Question`                                     <fct> strongly agree, s…
## $ `Yellow Question`                                    <fct> NA, NA, NA, stron…
## $ `Pink Question`                                      <fct> NA, NA, NA, stron…
## $ `Schalke steigt nächstes Jahr in die dritte Liga ab` <fct> strongly agree, s…

27 Schritt 2: Daten tidy machen

Der zweite Schritt besteht darin, die Daten tidy zu machen, das heisst, sie ins long table Format zu überführen. Schauen wir uns hierfür die Matrix der Pflegeberufe an, die ja im wide Format vorliegt:

Pflegeberufe

##                          1999   2001   2003   2005   2007   2009   2011   2013
## Krankenpflegeassistenz  16624  19061  19478  21537  27731  36481  46517  54371
## Altenpflegehilfe        55770  52710  49727  45776  48326  47903  47978  48363
## Kinderkrankenpflege     47779  48203  48822  48519  49080  49307  48291  48937
## Krankenpflege          430983 436767 444783 449355 457322 465446 468192 472580
## Altenpflege            109161 124879 141965 158817 178902 194195 208304 227154
##                          2015
## Krankenpflegeassistenz  64127
## Altenpflegehilfe        49507
## Kinderkrankenpflege     48913
## Krankenpflege          476416
## Altenpflege            246412

Die Reihennamen repräsentieren Variablen.

Da im Tidyverse fast alles über tibbles bzw. Datenframes abläuft, muss die Matrix zunächst in ein tibble überführt werden. Hierfür verwenden wir die Funktion as_tibble()

tbl <- as_tibble(Pflegeberufe)
head(tbl)

## # A tibble: 5 × 9
##   `1999` `2001` `2003` `2005` `2007` `2009` `2011` `2013` `2015`
##    <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>
## 1  16624  19061  19478  21537  27731  36481  46517  54371  64127
## 2  55770  52710  49727  45776  48326  47903  47978  48363  49507
## 3  47779  48203  48822  48519  49080  49307  48291  48937  48913
## 4 430983 436767 444783 449355 457322 465446 468192 472580 476416
## 5 109161 124879 141965 158817 178902 194195 208304 227154 246412

Wie Sie sehen, wurden die Spalten (Jahre) als Variablen übernommen. Die Informationen aus den Zeilen (Berufsgruppe) ist verloren gegangen. Für fügen sie daher per cbind() unserem tibble hinzu.

tbl <- cbind(tbl, Berufsgruppe = rownames(Pflegeberufe))
head(tbl)

##     1999   2001   2003   2005   2007   2009   2011   2013   2015
## 1  16624  19061  19478  21537  27731  36481  46517  54371  64127
## 2  55770  52710  49727  45776  48326  47903  47978  48363  49507
## 3  47779  48203  48822  48519  49080  49307  48291  48937  48913
## 4 430983 436767 444783 449355 457322 465446 468192 472580 476416
## 5 109161 124879 141965 158817 178902 194195 208304 227154 246412
##             Berufsgruppe
## 1 Krankenpflegeassistenz
## 2       Altenpflegehilfe
## 3    Kinderkrankenpflege
## 4          Krankenpflege
## 5            Altenpflege

Zur Überführung in ein korrektes tibble vom Format long table wird die Funktion pivot_longer() aus dem Tidyverse-Paket tidyr verwendet. Die Logik der Funktion besteht darin, die Variable mit den ehemaligen Reihennamen (die per cbind() hinzugefügt wurde), bestehen zu lassen. Aus den übrigen Spalten werden neue Variablen erzeugt, deren Werte passend zu den Reihenwerten (der cbind()-Spalte) eingetragen werden. In unserem Beispiel ist die (neue) Variable Berufsgruppe in Spalte 10. Wir belassen also Spalte 10, und wenden die Funktion auf die Spalten 1-9 an.

Pflegetibble <- pivot_longer(tbl,
             # wähle die Spalten 1-9 aus
             cols=1:9,
             # speichere Spaltennamen als Werte in neuer Variable "Jahr"
             names_to="Jahr",
             # Die derzeitigen Werte werden in der neuen Variable "Anzahl" gespeichert
             values_to = "Anzahl")

head(Pflegetibble)

## # A tibble: 6 × 3
##   Berufsgruppe           Jahr  Anzahl
##   <chr>                  <chr>  <dbl>
## 1 Krankenpflegeassistenz 1999   16624
## 2 Krankenpflegeassistenz 2001   19061
## 3 Krankenpflegeassistenz 2003   19478
## 4 Krankenpflegeassistenz 2005   21537
## 5 Krankenpflegeassistenz 2007   27731
## 6 Krankenpflegeassistenz 2009   36481

Mit der Funktion pivot_wider() kann im Gegenzug eine wide table erstellt werden. Ihr wird übergeben, aus welcher Variable die neuen Spalten gebildet werden sollen, und aus welcher Variable die anzuzeigenden Werte zu nehmen sind. Für unser Pflegetibble lautet der Befehl:

pivot_wider(Pflegetibble, names_from = Jahr, values_from = Anzahl)

## # A tibble: 5 × 10
##   Berufsgruppe    `1999` `2001` `2003` `2005` `2007` `2009` `2011` `2013` `2015`
##   <chr>            <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>
## 1 Krankenpflegea…  16624  19061  19478  21537  27731  36481  46517  54371  64127
## 2 Altenpflegehil…  55770  52710  49727  45776  48326  47903  47978  48363  49507
## 3 Kinderkrankenp…  47779  48203  48822  48519  49080  49307  48291  48937  48913
## 4 Krankenpflege   430983 436767 444783 449355 457322 465446 468192 472580 476416
## 5 Altenpflege     109161 124879 141965 158817 178902 194195 208304 227154 246412

27.1 verschachtelte wide table

Schauen wir uns folgende Tabelle an:

untidy_df

##   Altersgruppe Maenner_2018 Frauen_2018 Maenner_2019 Frauen_2019 Maenner_2020
## 1         < 18        22000       20000        22000       20000        23000
## 2        18-30        36000       35000        36000       35000        38000
## 3        31-50        50000       40000        50000       40000        52000
## 4        51-60        62000       60000        62000       60000        65000
## 5         > 60        75000       72000        75000       72000        78000
##   Frauen_2020
## 1       21000
## 2       36000
## 3       47000
## 4       62000
## 5       75000

Diese Tabelle ist mehrfach verschachtelt. Zum einen haben wir “bedeutsame Reihennamen” in Form der Altersgruppe. Zum anderen sind auch in den Spaltennamen jeweils 2 Variablen “versteckt”, nämlich das Geschlecht und das Jahr.

Um diese Tabelle tidy zu machen, müssen wir in 2 Schritten vor gehen.

Mit der Funktion pivot_longer bringen wir die Daten in eine erste long table-Version. Für den Datensatz untidy_df belassen wir die Variable Altersgruppe und wenden die Funktion auf die übrigen Spalten an.

untidy_df %>%
  pivot_longer(cols = Maenner_2018:Frauen_2020,
               names_to="Geschlecht_Jahr",
               values_to = "Einkommen") %>%
  head(10)

## # A tibble: 10 × 3
##   Altersgruppe Geschlecht_Jahr Einkommen
##   <chr>        <chr>               <dbl>
## 1 < 18         Maenner_2018        22000
## 2 < 18         Frauen_2018         20000
## 3 < 18         Maenner_2019        22000
## 4 < 18         Frauen_2019         20000
## 5 < 18         Maenner_2020        23000
## 6 < 18         Frauen_2020         21000
## # ℹ 4 more rows

Wie Sie sehen, hat pivot_longer() die Altersgruppen ebenfalls richtig zugeordnet.

Im zweiten Schritt teilen wir die Variable Geschlecht_Jahr in zwei eigene Variablen Geschlecht und Jahr auf. Hierfür steht die Funktion separate() zur Verfügung.

untidy_df %>%
  pivot_longer(cols = Maenner_2018:Frauen_2020,
               names_to="Geschlecht_Jahr",
               values_to = "Einkommen") %>%
  separate(Geschlecht_Jahr,
           into = c("Geschlecht", "Jahr")) %>%
  head(10)

## # A tibble: 10 × 4
##   Altersgruppe Geschlecht Jahr  Einkommen
##   <chr>        <chr>      <chr>     <dbl>
## 1 < 18         Maenner    2018      22000
## 2 < 18         Frauen     2018      20000
## 3 < 18         Maenner    2019      22000
## 4 < 18         Frauen     2019      20000
## 5 < 18         Maenner    2020      23000
## 6 < 18         Frauen     2020      21000
## # ℹ 4 more rows

Die Daten liegen nun vollständig als tidy data vor.

27.2 Datentypen korrigieren

Gerade bei importierten Daten sollte überprüft werden, ob den Variablen der korrekte Datentyp (numerisch, faktorial, logisch) zugewiesen ist. Dies ist wichtig, wenn wir mit ihnen später die Statistikfunktionen aufrufen.

Schauen wir unser Pflegetibble an

glimpse(Pflegetibble)

## Rows: 45
## Columns: 3
## $ Berufsgruppe <chr> "Krankenpflegeassistenz", "Krankenpflegeassistenz", "Kran…
## $ Jahr         <chr> "1999", "2001", "2003", "2005", "2007", "2009", "2011", "…
## $ Anzahl       <dbl> 16624, 19061, 19478, 21537, 27731, 36481, 46517, 54371, 6…

Die Variablen Berufsgruppe und Jahr sind beide vom Typ chr, was falsch ist. Überführen wir die Berufsgruppen in einen Faktor, und die Jahre in numerische Werte. Im klassischen R geht dies so:

Pflegetibble$Berufsgruppe <- as.factor(Pflegetibble$Berufsgruppe)
Pflegetibble$Jahr <- as.numeric(Pflegetibble$Jahr)
glimpse(Pflegetibble)

## Rows: 45
## Columns: 3
## $ Berufsgruppe <fct> Krankenpflegeassistenz, Krankenpflegeassistenz, Krankenpf…
## $ Jahr         <dbl> 1999, 2001, 2003, 2005, 2007, 2009, 2011, 2013, 2015, 199…
## $ Anzahl       <dbl> 16624, 19061, 19478, 21537, 27731, 36481, 46517, 54371, 6…

Im Tidyverse kann man die Datentypen mit den Funktionen mutate() (wird später erleutert) und across() umwandeln. Letztere ist das Pendant zum klassischen apply() und erlaubt es, Funktionen auf ausgewählte Spalten (Variablen) des Datensatzes anzuwenden.

Pflegetibble <- Pflegetibble %>%
  # wende auf Spalte "Berufsgruppe" die Funktion as.factor an.
  mutate(across(Berufsgruppe, as.factor)) %>%
  # wende auf Spalte "Jar" die Funktion as.numeric an.
  mutate(across(Jahr, as.numeric))
glimpse(Pflegetibble)

## Rows: 45
## Columns: 3
## $ Berufsgruppe <fct> Krankenpflegeassistenz, Krankenpflegeassistenz, Krankenpf…
## $ Jahr         <dbl> 1999, 2001, 2003, 2005, 2007, 2009, 2011, 2013, 2015, 199…
## $ Anzahl       <dbl> 16624, 19061, 19478, 21537, 27731, 36481, 46517, 54371, 6…

Sollen der auszuführenden Funktion Parameter übergeben werden, ändert sich der Aufruf - z.B. um einen ordinalen Faktore zu erzeugen, wie folgt:

Pflegetibble %>%
  # wende auf Spalte "Berufsgruppe" die Funktion factor an.
  # Um Parameter an 'factor()' übergeben zu können,
  # muss folgende Notation erfolgen.
  # Erstelle ordinale Faktoren mit Parameter "ordered=T"
  mutate(across(Berufsgruppe, ~factor(.x, ordered=T))) %>%
  head()

## # A tibble: 6 × 3
##   Berufsgruppe            Jahr Anzahl
##   <ord>                  <dbl>  <dbl>
## 1 Krankenpflegeassistenz  1999  16624
## 2 Krankenpflegeassistenz  2001  19061
## 3 Krankenpflegeassistenz  2003  19478
## 4 Krankenpflegeassistenz  2005  21537
## 5 Krankenpflegeassistenz  2007  27731
## 6 Krankenpflegeassistenz  2009  36481

Schauen wir uns als weiteres Beispiel den gelabelten Datensazt der Nachtwachenstudie an.

# lade den Nachtwachendatensatz
load(url("https://www.produnis.de/R/data/nw.RData"))

#nutze die gelabelte Version
test <- nw_labelled

# wenn Sie das Paket jgsbook installiert haben,
# können Sie den Datensatz auch so laden:
test <- jgsbook::nw_labelled
glimpse(test)

## Rows: 276
## Columns: 49
## $ sex               <fct> männlich, weiblich, weiblich, weiblich, weiblich, we…
## $ age               <labelled> 56, 53, 55, 28, 53, 62, 55, 61, 34, 50, 54, 50,…
## $ land              <fct> Nordrhein-Westfalen, Nordrhein-Westfalen, Nordrhein-…
## $ host              <fct> "privater Träger", "Wohlfahrtsverband (z.B. AWO, Car…
## $ bed               <fct> weniger als 10, 75 - 100, 100 - 150, 100 - 150, 75 -…
## $ patient           <fct> 10 - 20, 75 - 100, 100 - 150, 100 - 150, 75 - 100, 1…
## $ liability         <labelled> 5, 30, 34, 23, 30, 41, 79, 79, 52, 32, 71, 71, …
## $ Stufe0            <labelled> 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 25, 25, 0, 0, 0, …
## $ Stufe1            <labelled> 3, 0, 10, 4, 0, 14, 15, 8, 6, 9, 15, 15, 9, 10,…
## $ Stufe2            <labelled> 9, 10, 19, 17, 11, 19, 29, 14, 32, 14, 19, 19, …
## $ Stufe3            <labelled> 12, 20, 5, 2, 19, 7, 23, 18, 14, 10, 12, 12, 14…
## $ Demenz            <labelled> 7, 20, 16, 8, 28, 7, 60, 31, 32, 22, 21, 21, 20…
## $ Bettgitter        <labelled> 2, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 11, 1…
## $ Bettgurt          <labelled> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
## $ Schlafmittel      <labelled> 7, 6, 5, 0, 5, 9, 7, 2, 15, 4, 4, 4, 4, 0, 26, …
## $ colleagueall      <labelled> 2, 3, 5, 3, 3, 3, 2, 2, 2, 3, 3, 3, 3, 2, 2, 6,…
## $ colleague         <labelled> 1, 1, 1, 0, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 2, 2,…
## $ pause             <fct> selten, immer, häufig, immer, immer, manchmal, immer…
## $ pausecolleague    <fct> nie, immer, nie, immer, immer, nie, immer, immer, ni…
## $ hintergrunddienst <fct> NA, Ja, Ja, Ja, Ja, NA, NA, Ja, Ja, Ja, Ja, Ja, Ja, …
## $ daynights         <fct> "hauptsächlich im Nachtdienst, mit vereinzelten Tagd…
## $ whynight          <fct> NA, die Arbeitszeiten sagen mir zu, NA, familiäre Gr…
## $ nights            <labelled> 6, 7, 8, 5, 7, 7, 7, 7, 5, 5, 7, 5, 9, 9, 8, 8,…
## $ timework          <fct> Vollzeit, Teilzeit, NA, Teilzeit, Teilzeit, NA, NA, …
## $ nightstarts       <labelled> "1970-01-01 20:33:00", "1970-01-01 20:30:00", "…
## $ nightstops        <labelled> "1970-01-01 07:07:00", "1970-01-01 07:00:00", "…
## $ MagNacht          <fct> eher ja, ja, ja, ja, ja, ja, ja, ja, ja, ja, ja, ja,…
## $ WiederTag         <fct> neutral, nein, nein, neutral, nein, nein, nein, nein…
## $ ueberfordert      <fct> selten, nie, nie, nie, selten, selten, nie, selten, …
## $ Zwischenfall      <fct> selten, selten, häufig, selten, häufig, selten, selt…
## $ Bew_herumirren    <fct> sehr oft, häufig, selten, häufig, häufig, selten, hä…
## $ Bew_sterben       <fct> selten, häufig, häufig, selten, häufig, selten, selt…
## $ muede             <fct> selten, nie, nie, häufig, selten, nie, nie, selten, …
## $ schwer_wach       <fct> nie, nie, nie, selten, selten, nie, nie, nie, nie, n…
## $ Demenz_aktiv      <labelled> 10, 7, 5, 5, 7, 7, 3, 5, NA, 5, 5, 5, 4, 4, 7, …
## $ Demenz_stoeren    <labelled> 8, 7, 3, 3, 7, 0, 3, 2, NA, 5, 5, 5, 2, 1, 7, 7…
## $ Demenz_sediert    <labelled> 3, 7, 1, 0, 2, 0, 0, 1, NA, 2, 2, 2, 0, 0, 5, 7…
## $ Demenz_nv         <labelled> 10, 4, 4, 0, 1, 7, 0, 0, NA, 0, 0, 0, 0, 0, 7, …
## $ Sorge_weglaufen   <fct> eher ja, ja, eher ja, nein, eher nicht, eher nicht, …
## $ Sorge_Sturz       <fct> ja, nein, ja, ja, eher ja, eher nicht, neutral, ja, …
## $ Sorge_FEM         <fct> ja, NA, NA, nein, neutral, eher ja, NA, neutral, nei…
## $ Sorge_nv          <fct> ja, neutral, eher ja, eher nicht, nein, eher nicht, …
## $ Sorge_sterben     <fct> eher ja, ja, ja, eher nicht, eher ja, ja, eher nicht…
## $ workyear          <labelled> 35, 37, 37, 10, 30, 34, 13, 28, 4, 32, 10, 32, …
## $ worknight         <labelled> 12, 11, 32, 1, 14, 29, 7, 13, 2, 5, 10, 5, 17, …
## $ meeting           <fct> selten, immer, NA, nie, immer, manchmal, selten, sel…
## $ fortbildung       <fct> Ja, Ja, Ja, Ja, Ja, Ja, Ja, Ja, Ja, Ja, Ja, Ja, Ja, …
## $ wannfort          <labelled> 2004, 2014, 2013, 2013, 2014, 2015, 2014, 2015,…
## $ edu               <fct> dreijährige Pflegeausbildung, zweijährige Pflegeausb…

Wir sehen, dass viele Variablen vom Typ labelled sind. Schauen wir uns die Variablen age und sex genauer an.

class(test$age)

## [1] "labelled" "numeric"

class(test$sex)

## [1] "labelled" "factor"

Die Variablen ist sowhohl vom Typ labelled als auch vom Typ numeric bzw. factor. Manche R-Funktionen haben mit dieser Doppelbelegung ihre Probleme, z.B. vtable::sumtable().

vtable::sumtable(test$age)

Fehler in vtable::sumtable(test$age) :
It doesn't look like you have any variables that belong in a sumtable. Check your data. Use vars to explicitly choose variables, or convert things to numeric or factor before sending to sumtable.

Wir sollten daher alle Variablentypen eindeutig setzen. Die Funktion mutate_if() wendet eine Funktion an, wenn die gesetzten Bedingungen erfüllt sind. So möchten wir im ersten Schritt nur solche Spalten, die (zusätzlich zum Typ labelled) vom Typ numeric sind, den eindeutigen Typ numeric zuweisen. Im zweiten Schritt wiederholen wir dies für Faktoren.

test %>%
  # wende nur auf labelled-numeric an
  mutate_if(is.numeric, as.numeric) %>%
  # wende nur auf labelled-factor an
  mutate_if(is.factor, as.factor) %>%
  # wende nur auf die Spalet nightstarts nightstops an
  mutate(across(nightstarts:nightstops, as.character)) %>%
  glimpse()

## Rows: 276
## Columns: 49
## $ sex               <fct> männlich, weiblich, weiblich, weiblich, weiblich, we…
## $ age               <dbl> 56, 53, 55, 28, 53, 62, 55, 61, 34, 50, 54, 50, 50, …
## $ land              <fct> Nordrhein-Westfalen, Nordrhein-Westfalen, Nordrhein-…
## $ host              <fct> "privater Träger", "Wohlfahrtsverband (z.B. AWO, Car…
## $ bed               <fct> weniger als 10, 75 - 100, 100 - 150, 100 - 150, 75 -…
## $ patient           <fct> 10 - 20, 75 - 100, 100 - 150, 100 - 150, 75 - 100, 1…
## $ liability         <dbl> 5, 30, 34, 23, 30, 41, 79, 79, 52, 32, 71, 71, 46, 5…
## $ Stufe0            <dbl> 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 25, 25, 0, 0, 0, 0, 0,…
## $ Stufe1            <dbl> 3, 0, 10, 4, 0, 14, 15, 8, 6, 9, 15, 15, 9, 10, 31, …
## $ Stufe2            <dbl> 9, 10, 19, 17, 11, 19, 29, 14, 32, 14, 19, 19, 23, 1…
## $ Stufe3            <dbl> 12, 20, 5, 2, 19, 7, 23, 18, 14, 10, 12, 12, 14, 15,…
## $ Demenz            <dbl> 7, 20, 16, 8, 28, 7, 60, 31, 32, 22, 21, 21, 20, 25,…
## $ Bettgitter        <dbl> 2, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 11, 1, 3, …
## $ Bettgurt          <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
## $ Schlafmittel      <dbl> 7, 6, 5, 0, 5, 9, 7, 2, 15, 4, 4, 4, 4, 0, 26, 1, 23…
## $ colleagueall      <dbl> 2, 3, 5, 3, 3, 3, 2, 2, 2, 3, 3, 3, 3, 2, 2, 6, 2, 1…
## $ colleague         <dbl> 1, 1, 1, 0, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 2, 2, 1, 1…
## $ pause             <fct> selten, immer, häufig, immer, immer, manchmal, immer…
## $ pausecolleague    <fct> nie, immer, nie, immer, immer, nie, immer, immer, ni…
## $ hintergrunddienst <fct> NA, Ja, Ja, Ja, Ja, NA, NA, Ja, Ja, Ja, Ja, Ja, Ja, …
## $ daynights         <fct> "hauptsächlich im Nachtdienst, mit vereinzelten Tagd…
## $ whynight          <fct> NA, die Arbeitszeiten sagen mir zu, NA, familiäre Gr…
## $ nights            <dbl> 6, 7, 8, 5, 7, 7, 7, 7, 5, 5, 7, 5, 9, 9, 8, 8, 3, 4…
## $ timework          <fct> Vollzeit, Teilzeit, NA, Teilzeit, Teilzeit, NA, NA, …
## $ nightstarts       <chr> "1970-01-01 20:33:00", "1970-01-01 20:30:00", "1970-…
## $ nightstops        <chr> "1970-01-01 07:07:00", "1970-01-01 07:00:00", "1970-…
## $ MagNacht          <fct> eher ja, ja, ja, ja, ja, ja, ja, ja, ja, ja, ja, ja,…
## $ WiederTag         <fct> neutral, nein, nein, neutral, nein, nein, nein, nein…
## $ ueberfordert      <fct> selten, nie, nie, nie, selten, selten, nie, selten, …
## $ Zwischenfall      <fct> selten, selten, häufig, selten, häufig, selten, selt…
## $ Bew_herumirren    <fct> sehr oft, häufig, selten, häufig, häufig, selten, hä…
## $ Bew_sterben       <fct> selten, häufig, häufig, selten, häufig, selten, selt…
## $ muede             <fct> selten, nie, nie, häufig, selten, nie, nie, selten, …
## $ schwer_wach       <fct> nie, nie, nie, selten, selten, nie, nie, nie, nie, n…
## $ Demenz_aktiv      <dbl> 10, 7, 5, 5, 7, 7, 3, 5, NA, 5, 5, 5, 4, 4, 7, 7, 6,…
## $ Demenz_stoeren    <dbl> 8, 7, 3, 3, 7, 0, 3, 2, NA, 5, 5, 5, 2, 1, 7, 7, 5, …
## $ Demenz_sediert    <dbl> 3, 7, 1, 0, 2, 0, 0, 1, NA, 2, 2, 2, 0, 0, 5, 7, 5, …
## $ Demenz_nv         <dbl> 10, 4, 4, 0, 1, 7, 0, 0, NA, 0, 0, 0, 0, 0, 7, 7, 6,…
## $ Sorge_weglaufen   <fct> eher ja, ja, eher ja, nein, eher nicht, eher nicht, …
## $ Sorge_Sturz       <fct> ja, nein, ja, ja, eher ja, eher nicht, neutral, ja, …
## $ Sorge_FEM         <fct> ja, NA, NA, nein, neutral, eher ja, NA, neutral, nei…
## $ Sorge_nv          <fct> ja, neutral, eher ja, eher nicht, nein, eher nicht, …
## $ Sorge_sterben     <fct> eher ja, ja, ja, eher nicht, eher ja, ja, eher nicht…
## $ workyear          <dbl> 35, 37, 37, 10, 30, 34, 13, 28, 4, 32, 10, 32, 27, 3…
## $ worknight         <dbl> 12, 11, 32, 1, 14, 29, 7, 13, 2, 5, 10, 5, 17, 32, 7…
## $ meeting           <fct> selten, immer, NA, nie, immer, manchmal, selten, sel…
## $ fortbildung       <fct> Ja, Ja, Ja, Ja, Ja, Ja, Ja, Ja, Ja, Ja, Ja, Ja, Ja, …
## $ wannfort          <dbl> 2004, 2014, 2013, 2013, 2014, 2015, 2014, 2015, 2015…
## $ edu               <fct> dreijährige Pflegeausbildung, zweijährige Pflegeausb…

27.3 fehlende Werte

Fehlende Werte (NA) können mit der Funktion drop_na() entfernt werden. Die Funktion “dropt” dabei die komplette Datenreihe, wenn ein NA in einer angegebenen Spalte enthalten ist.

# lade Beispieldatensatz
load(url("https://www.produnis.de/R/data/pf8.RData" ))

# Schaue Variable "Gewicht" an
glimpse(pf8$Gewicht)

##  num [1:731] 69 67 NA 90 68 60 80 60 NA 60 ...

Variable Gewicht enthält 731 Werte, und wir sehen, dass auch NA enthalten sind.

Jetzt “droppen” wir alle Reihen, in denen bei der Variable Gewicht ein NA steht

pf8 %>%
  drop_na(Gewicht) %>%
      glimpse(.)

## Rows: 720
## Columns: 16
## $ Standort       <fct> Münster, Münster, Münster, Münster, Münster, Münster, M…
## $ Alter          <int> 18, 67, 61, 24, 21, 59, 56, 52, 79, 22, 19, 18, 17, 21,…
## $ Geschlecht     <fct> weiblich, weiblich, männlich, männlich, weiblich, weibl…
## $ Größe          <int> 172, 165, 182, 173, 177, 168, 156, 166, 161, 206, 163, …
## $ Gewicht        <dbl> 69, 67, 90, 68, 60, 80, 60, 60, 66, 130, 52, 68, 48, 54…
## $ Bildung        <fct> Abitur, mittlere Reife, mittlere Reife, Abitur, Abitur,…
## $ Beruf          <fct> Inspektor*in, Rentner*in, Beamter*in, Student*in, Stude…
## $ Familienstand  <fct> Partnerschaft, geschieden, ledig, ledig, Partnerschaft,…
## $ Kinder         <int> 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
## $ Wohnort        <fct> ländlich, ländlich, ländlich, städtisch, städtisch, län…
## $ Rauchen        <fct> nein, nein, nein, nein, ja, nein, ja, nein, nein, nein,…
## $ SportHäufig    <dbl> NA, 2.0, 4.0, 4.0, 1.0, 2.0, 1.0, 2.0, NA, 4.0, 5.0, 2.…
## $ SportMinuten   <dbl> NA, 60, 120, 60, 60, 45, 90, 45, NA, 90, 20, 120, 120, …
## $ SportWie       <fct> Allein, Gruppe, Allein, Allein, Gruppe, Gruppe, beides,…
## $ SportWarum     <fct> Arbeitsweg, Vorbeugung, Fitness, Gewichtsreduktion, Fit…
## $ LebenZufrieden <dbl> 5, 7, 2, 9, 8, 5, 8, 8, 8, 8, 7, 8, 7, 8, 7, 7, 8, 7, 1…

Der Datensatz pf8 wird mittels Pipe weitergeleitet. In der zweiten Zeile droppen wir alle Reihen mit Gewicht NA und geben das Resultat per Pipe weiter.

Mit glimpse() sehen wir das Resultat. Datensatz pf8 hat jetzt nur noch 720 Reihen, und nicht mehr 731.

Sollen alle NAs in allen Spalten entfernt werden, lautet der Befehl

pf8 %>%
  drop_na(.) %>%
      glimpse(.)

## Rows: 398
## Columns: 16
## $ Standort       <fct> Münster, Münster, Münster, Münster, Münster, Münster, M…
## $ Alter          <int> 67, 61, 24, 21, 59, 56, 52, 22, 19, 18, 17, 21, 72, 19,…
## $ Geschlecht     <fct> weiblich, männlich, männlich, weiblich, weiblich, weibl…
## $ Größe          <int> 165, 182, 173, 177, 168, 156, 166, 206, 163, 172, 158, …
## $ Gewicht        <dbl> 67, 90, 68, 60, 80, 60, 60, 130, 52, 68, 48, 54, 86, 65…
## $ Bildung        <fct> mittlere Reife, mittlere Reife, Abitur, Abitur, mittler…
## $ Beruf          <fct> Rentner*in, Beamter*in, Student*in, Student*in, arbeitl…
## $ Familienstand  <fct> geschieden, ledig, ledig, Partnerschaft, verheiratet, g…
## $ Kinder         <int> 0, 0, 0, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
## $ Wohnort        <fct> ländlich, ländlich, städtisch, städtisch, ländlich, stä…
## $ Rauchen        <fct> nein, nein, nein, ja, nein, ja, nein, nein, nein, nein,…
## $ SportHäufig    <dbl> 2.0, 4.0, 4.0, 1.0, 2.0, 1.0, 2.0, 4.0, 5.0, 2.5, 1.0, …
## $ SportMinuten   <dbl> 60, 120, 60, 60, 45, 90, 45, 90, 20, 120, 120, 80, 160,…
## $ SportWie       <fct> Gruppe, Allein, Allein, Gruppe, Gruppe, beides, Allein,…
## $ SportWarum     <fct> Vorbeugung, Fitness, Gewichtsreduktion, Fitness, Vorbeu…
## $ LebenZufrieden <dbl> 7, 2, 9, 8, 5, 8, 8, 8, 7, 8, 7, 8, 7, 8, 10, 8, 7, 9, …

Mit dieser radikalen Methode schrumpft der Datensatz pf8 auf 398 vollständige Datenreihen zusammen.

Sie können mit den Funktionen fill() und replace_na() fehlende Werte auffüllen oder ersetzen.

Hierbei ersetzt replace_na() alle NA durch einen vorgegebenen Wert, und fill() versucht, die NA durch wahrscheinliche Werte zu ersetzen. Dies ist hilfreich, wenn die Daten z.B. nach einem Kriterium sortiert sind.

test <- tibble(Beruf = factor(c("Bäcker", NA, "Bäcker", "Ärztin", NA, "Ärztin")))

# vorher
test

## # A tibble: 6 × 1
##   Beruf
##   <fct>
## 1 Bäcker
## 2 <NA>
## 3 Bäcker
## 4 Ärztin
## 5 <NA>
## 6 Ärztin

# ersetze NA
test %>%
  tidyr::fill(Beruf)

## # A tibble: 6 × 1
##   Beruf
##   <fct>
## 1 Bäcker
## 2 Bäcker
## 3 Bäcker
## 4 Ärztin
## 5 Ärztin
## 6 Ärztin

Aufsteigende Werte können nicht erzeugt werden.

test <- tibble(Jahr = c(2014, NA, NA, NA, 2018, 2019, NA , NA , 2022))

# vorher
test

## # A tibble: 9 × 1
##    Jahr
##   <dbl>
## 1  2014
## 2    NA
## 3    NA
## 4    NA
## 5  2018
## 6  2019
## # ℹ 3 more rows

test %>%
  tidyr::fill(Jahr)

## # A tibble: 9 × 1
##    Jahr
##   <dbl>
## 1  2014
## 2  2014
## 3  2014
## 4  2014
## 5  2018
## 6  2019
## # ℹ 3 more rows

27.4 Variablen umbenennen

Mit der Funktion rename() könne Sie die Variablennamen ändern.

Pflegetibble %>%
  dplyr::rename(Neu=Berufsgruppe) %>%
  head()

## # A tibble: 6 × 3
##   Neu                     Jahr Anzahl
##   <fct>                  <dbl>  <dbl>
## 1 Krankenpflegeassistenz  1999  16624
## 2 Krankenpflegeassistenz  2001  19061
## 3 Krankenpflegeassistenz  2003  19478
## 4 Krankenpflegeassistenz  2005  21537
## 5 Krankenpflegeassistenz  2007  27731
## 6 Krankenpflegeassistenz  2009  36481

Pflegetibble %>%
  dplyr::rename(graduate=Berufsgruppe,
         year=Jahr,
         n=Anzahl) %>%
            head()

## # A tibble: 6 × 3
##   graduate                year     n
##   <fct>                  <dbl> <dbl>
## 1 Krankenpflegeassistenz  1999 16624
## 2 Krankenpflegeassistenz  2001 19061
## 3 Krankenpflegeassistenz  2003 19478
## 4 Krankenpflegeassistenz  2005 21537
## 5 Krankenpflegeassistenz  2007 27731
## 6 Krankenpflegeassistenz  2009 36481

28 Schritt 3: Umgang mit Datensätzen

Das Paket dplyr bietet zahlreiche Funktionen, die den Umgang mit Datensätzen erleichtern.

28.1 Daten filtern und sortieren

Mit der Funktion filter() kann der Datensatz nach Kriterien gefiltert werden. Beachten Sie, dass die Funktion mit stats::filter() kollidiert. Rufen Sie sie daher sicherheitshalber mit dplyr::filter() auf.

pf8 %>%
    # Nur Daten von Personen jünger 40
    dplyr::filter(Alter<40) %>%
      glimpse()

## Rows: 443
## Columns: 16
## $ Standort       <fct> Münster, Münster, Münster, Münster, Münster, Münster, M…
## $ Alter          <int> 18, 24, 21, 22, 19, 18, 17, 21, 28, 19, 21, 28, 26, 21,…
## $ Geschlecht     <fct> weiblich, männlich, weiblich, männlich, weiblich, weibl…
## $ Größe          <int> 172, 173, 177, 206, 163, 172, 158, 163, 181, 175, 186, …
## $ Gewicht        <dbl> 69, 68, 60, 130, 52, 68, 48, 54, 70, 65, 70, 85, 60, 65…
## $ Bildung        <fct> Abitur, Abitur, Abitur, Fachabitur, Abitur, mittlere Re…
## $ Beruf          <fct> Inspektor*in, Student*in, Student*in, Kaufmann/frau, St…
## $ Familienstand  <fct> Partnerschaft, ledig, Partnerschaft, ledig, ledig, ledi…
## $ Kinder         <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
## $ Wohnort        <fct> ländlich, städtisch, städtisch, ländlich, städtisch, st…
## $ Rauchen        <fct> nein, nein, ja, nein, nein, nein, nein, nein, nein, nei…
## $ SportHäufig    <dbl> NA, 4.0, 1.0, 4.0, 5.0, 2.5, 1.0, 4.0, NA, 0.5, 1.0, 5.…
## $ SportMinuten   <dbl> NA, 60, 60, 90, 20, 120, 120, 80, NA, 120, 60, 50, 30, …
## $ SportWie       <fct> Allein, Allein, Gruppe, Gruppe, Allein, Gruppe, Gruppe,…
## $ SportWarum     <fct> Arbeitsweg, Gewichtsreduktion, Fitness, Fitness, Vorbeu…
## $ LebenZufrieden <dbl> 5, 9, 8, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 9, 8, 8, 6, 7, 8…

Die Kriterien können auch kombiniert werden.

pf8 %>%
  dplyr::filter(Alter>20 , Geschlecht == "weiblich", Bildung=="Abitur") %>%
      glimpse()

## Rows: 56
## Columns: 16
## $ Standort       <fct> Münster, Münster, Münster, Bahn, Bahn, Rheine, Rheine, …
## $ Alter          <int> 21, 21, 21, 21, 21, 22, 23, 22, 30, 52, 45, 23, 49, 41,…
## $ Geschlecht     <fct> weiblich, weiblich, weiblich, weiblich, weiblich, weibl…
## $ Größe          <int> 177, 163, 175, 170, 170, 170, 168, 163, 173, 174, 168, …
## $ Gewicht        <dbl> 60, 54, 61, 58, 80, 59, 70, 63, 71, 72, 70, 56, 70, 49,…
## $ Bildung        <fct> Abitur, Abitur, Abitur, Abitur, Abitur, Abitur, Abitur,…
## $ Beruf          <fct> Student*in, Student*in, Student*in, Student*in, Student…
## $ Familienstand  <fct> Partnerschaft, Partnerschaft, Partnerschaft, NA, ledig,…
## $ Kinder         <int> 0, 0, 0, NA, 0, 0, 0, 0, 0, 1, 2, 0, 2, 0, 3, 2, 0, 1, …
## $ Wohnort        <fct> städtisch, städtisch, städtisch, ländlich, ländlich, st…
## $ Rauchen        <fct> ja, nein, nein, nein, nein, nein, nein, nein, nein, nei…
## $ SportHäufig    <dbl> 1.0, 4.0, 3.0, 5.0, 1.0, 1.0, 2.0, 4.0, 3.0, 4.0, 1.0, …
## $ SportMinuten   <dbl> 60, 80, 60, 75, 60, 90, 45, 60, 60, 20, 60, 90, 30, 120…
## $ SportWie       <fct> Gruppe, beides, Gruppe, Allein, Allein, Allein, beides,…
## $ SportWarum     <fct> Fitness, Vorbeugung, Fitness, Fitness, Fitness, Gewicht…
## $ LebenZufrieden <dbl> 8, 8, 8, 9, 8, 8, 10, 8, 7, 6, 7, 8, 7, 7, 7, 7, 8, 8, …

Beachten Sie, dass wir doppelte Gleichheitszeichen (==) verwenden müssen, da mit einem Gleichheitszeichen (=) Variablen zugewiesen werden (so wie mit <-).

Möchte man nach mehreren bestimmten Werte filtern, kann der %in% Operator verwendet werden. Hierdurch übergeben Sie einen Vektor an möglichen Ausprägungen. Angenommen, Sie möchten nur solche Fälle auswählen, in denen das Alter 20 oder 25 oder 40 Jahre beträgt, lautet der Aufruf:

# nach mehreren Werten filtern
# Suche Fälle, die 20 oder 25 oder 40 Jahre alt sind
pf8 %>%
  dplyr::filter(Alter %in% c(20, 25, 40)) %>%
  head(10)

##    Standort Alter Geschlecht Größe Gewicht     Bildung
## 1   Münster    25   männlich   182    75.0  Ausbildung
## 2      Bahn    25   männlich   180    72.0  Hochschule
## 3    Rheine    25   weiblich   168    75.0  Hochschule
## 4    Rheine    40   weiblich   163    68.0 Hauptschule
## 5    Rheine    20   weiblich   158    70.0 Hauptschule
## 6    Rheine    20   weiblich   170    85.0  Hochschule
## 7    Rheine    20   weiblich   170    65.0      Abitur
## 8    Rheine    20   weiblich   160    48.5      Abitur
## 9    Rheine    25   männlich   186    70.0  Hochschule
## 10     Bahn    40   weiblich   181    64.0  Hochschule
##                           Beruf Familienstand Kinder   Wohnort Rauchen
## 1                    Finanzwirt         ledig      0 städtisch    nein
## 2  Kaufmann/frau + Betriebswirt         ledig      0  ländlich    nein
## 3                    Student*in         ledig      0 städtisch      ja
## 4                 Angestellte*r         ledig      2 städtisch    nein
## 5                    Schüler*in         ledig      0  ländlich    nein
## 6                         Azubi         ledig      3  ländlich    nein
## 7                         Azubi         ledig      0  ländlich    nein
## 8                    Student*in         ledig      0  ländlich    nein
## 9                    Student*in         ledig      0 städtisch    nein
## 10                Angestellte*r   verheiratet      2 städtisch    nein
##    SportHäufig SportMinuten SportWie        SportWarum LebenZufrieden
## 1          2.0           90   Allein           Fitness              8
## 2          4.0           30   Allein           Fitness              9
## 3          2.0           20   Allein           Fitness              9
## 4           NA           NA     <NA>              <NA>              6
## 5          3.0           60   Allein Gewichtsreduktion              7
## 6          2.0          120   Gruppe        Vorbeugung              8
## 7          5.0          120   beides           Fitness              7
## 8          3.0           60   Allein           Fitness              7
## 9          3.0           30   Allein        Vorbeugung              9
## 10         1.5           60   Gruppe        Vorbeugung              7

Über den Operator != können Fälle ausgeschlossen werden. Wir schließen für dieses Beispiel alle Fälle aus, die verheiratet sind:

# schließe alle verheirateten Fälle aus
pf8 %>%
  dplyr::filter(Familienstand != "verheiratet") %>%
  head(10)

##    Standort Alter Geschlecht Größe Gewicht        Bildung            Beruf
## 1   Münster    18   weiblich   172      69         Abitur     Inspektor*in
## 2   Münster    67   weiblich   165      67 mittlere Reife       Rentner*in
## 3   Münster    60   weiblich   175      NA     Hochschule Ergotherapeut*in
## 4   Münster    61   männlich   182      90 mittlere Reife       Beamter*in
## 5   Münster    24   männlich   173      68         Abitur       Student*in
## 6   Münster    21   weiblich   177      60         Abitur       Student*in
## 7   Münster    56   weiblich   156      60     Ausbildung  Pflegehelfer*in
## 8   Münster    22   männlich   206     130     Fachabitur    Kaufmann/frau
## 9   Münster    19   weiblich   163      52         Abitur       Student*in
## 10  Münster    18   weiblich   172      68 mittlere Reife       Schüler*in
##    Familienstand Kinder   Wohnort Rauchen SportHäufig SportMinuten SportWie
## 1  Partnerschaft      0  ländlich    nein          NA           NA   Allein
## 2     geschieden      0  ländlich    nein         2.0           60   Gruppe
## 3  Partnerschaft      0 städtisch    nein         2.0           45   beides
## 4          ledig      0  ländlich    nein         4.0          120   Allein
## 5          ledig      0 städtisch    nein         4.0           60   Allein
## 6  Partnerschaft      0 städtisch      ja         1.0           60   Gruppe
## 7     geschieden      2 städtisch      ja         1.0           90   beides
## 8          ledig      0  ländlich    nein         4.0           90   Gruppe
## 9          ledig      0 städtisch    nein         5.0           20   Allein
## 10         ledig      0 städtisch    nein         2.5          120   Gruppe
##           SportWarum LebenZufrieden
## 1         Arbeitsweg              5
## 2         Vorbeugung              7
## 3         Vorbeugung              7
## 4            Fitness              2
## 5  Gewichtsreduktion              9
## 6            Fitness              8
## 7         Vorbeugung              8
## 8            Fitness              8
## 9         Vorbeugung              7
## 10 Gewichtsreduktion              8

Mit der Funktion arrange() können die Daten sortiert werden. Dabei wird standardmäßig aufsteigend sortiert.

# sortiere Datensatz "pf8"
pf8 %>%
  arrange(Alter, Größe, Gewicht) %>%
     head(.)

##   Standort Alter Geschlecht Größe Gewicht        Bildung      Beruf
## 1 Internet    15   weiblich   165    51.6           <NA>
## 2  Münster    17   weiblich   158    48.0 mittlere Reife Schüler*in
## 3 Internet    17   weiblich   160    75.0           <NA>
## 4 Internet    17   weiblich   163    57.0           <NA>
## 5 Internet    17   weiblich   168    59.0           <NA>
## 6 Internet    17   weiblich   175    80.0           <NA>
##   Familienstand Kinder   Wohnort Rauchen SportHäufig SportMinuten SportWie
## 1 Partnerschaft      5 städtisch    nein           6           60   Allein
## 2         ledig      0 städtisch    nein           1          120   Gruppe
## 3 Partnerschaft      0  ländlich    nein           2           60   Gruppe
## 4 Partnerschaft      0  ländlich    nein           2           70   Allein
## 5         ledig      0 städtisch    nein           3          120   Allein
## 6         ledig      0 städtisch    nein          NA           30   Allein
##   SportWarum LebenZufrieden
## 1       <NA>              7
## 2  Beruflich              7
## 3       <NA>              8
## 4       <NA>              5
## 5       <NA>              7
## 6       <NA>              8

Die Daten werden zunächst aufsteigend nach Alter sortiert. Gibt es Fälle mit dem selben Alter, wird nach dem zweiten Kriterium (in diesem Falle Größe) sortiert. Gibt es Fälle mit der selben Größe und dem selben Alter, wird in- nerhalb dieser Gruppe nach Gewicht sortiert.

Für eine absteigende Sortierung muss die Funktion desc() verwendet werden.

# sortiere Datensatz "pf8"
# diesmal absteigend
pf8 %>%
  arrange(desc(Alter), desc(Größe), desc(Gewicht)) %>%
     head(.)

##    Standort Alter Geschlecht Größe Gewicht     Bildung        Beruf
## 1   Münster    86   weiblich   162      70 Hauptschule   Rentner*in
## 2    Rheine    86   weiblich   160      60      Abitur  Postbote*in
## 3    Rheine    84   weiblich   164      70 Hauptschule Verkäufer*in
## 4    Rheine    84   weiblich   160      60 Hauptschule   Rentner*in
## 5 Ladbergen    83   männlich   182      88 Hauptschule  Rentener*in
## 6   Münster    83       <NA>   165     175        <NA>   Rentner*in
##   Familienstand Kinder   Wohnort Rauchen SportHäufig SportMinuten SportWie
## 1     verwitwet      0  ländlich    nein           2           45   Gruppe
## 2     verwitwet      1 städtisch    nein           2           45   Gruppe
## 3     verwitwet      0 städtisch    nein          NA           NA     <NA>
## 4     verwitwet      0 städtisch    nein           2           60   Allein
## 5   verheiratet      0  ländlich      ja           5           60   Gruppe
## 6         ledig      0 städtisch    nein          NA           25   Allein
##   SportWarum LebenZufrieden
## 1    Fitness              7
## 2   Freizeit              8
## 3       <NA>              5
## 4    Fitness              8
## 5 Vorbeugung              5
## 6 Vorbeugung              4

28.2 Fälle auswählen

Mit der Funktion slice() können gewünschte Datenreihen (Fälle) ausgegeben werden.

pf8 %>%
  # zeige Fälle (Reihen) 98 bis 105
  slice(98:105) %>%
    glimpse()

## Rows: 8
## Columns: 16
## $ Standort       <fct> Rheine, Rheine, Rheine, Rheine, Rheine, Rheine, Rheine,…
## $ Alter          <int> 23, 30, 26, 35, 22, 50, 35, 22
## $ Geschlecht     <fct> männlich, weiblich, männlich, männlich, männlich, männl…
## $ Größe          <int> 194, 173, 189, 184, 181, 204, 172, 175
## $ Gewicht        <dbl> 110, 71, 89, 79, 92, 102, 86, 75
## $ Bildung        <fct> Abitur, Abitur, Hauptschule, mittlere Reife, mittlere R…
## $ Beruf          <fct> Außendienst, Innendienst, Angestellte*r,  Produktionsle…
## $ Familienstand  <fct> Partnerschaft, verheiratet, Partnerschaft, verheiratet,…
## $ Kinder         <int> 0, 0, 0, 3, 0, 1, 2, 0
## $ Wohnort        <fct> ländlich, ländlich, städtisch, ländlich, ländlich, länd…
## $ Rauchen        <fct> ja, nein, ja, ja, nein, nein, ja, nein
## $ SportHäufig    <dbl> 1, 3, 1, 3, 2, 4, 1, 0
## $ SportMinuten   <dbl> 30, 60, 60, 90, 30, 30, 60, 0
## $ SportWie       <fct> Allein, Allein, Gruppe, Gruppe, Gruppe, beides, Gruppe,…
## $ SportWarum     <fct> Fitness, Fitness, Freizeit, Fitness, Fitness, Vorbeugun…
## $ LebenZufrieden <dbl> 9, 7, 6, 6, 7, 8, 7, 7

Mit slice_min() und slice_max() können die kleinsten und größten Werte eingesehen werden.

pf8 %>%
  # zeige die leichtesten Gewichte
  slice_min(Gewicht)%>%
      glimpse()

## Rows: 5
## Columns: 16
## $ Standort       <fct> Münster, Münster, Münster, Internet, Internet
## $ Alter          <int> 26, 24, 24, 24, 19
## $ Geschlecht     <fct> männlich, weiblich, weiblich, weiblich, weiblich
## $ Größe          <int> 180, 164, 160, 159, 155
## $ Gewicht        <dbl> 45, 45, 45, 45, 45
## $ Bildung        <fct> Hauptschule, Abitur, Abitur, NA, NA
## $ Beruf          <fct> Kaufmann/frau, Student*in, Student*in, ,
## $ Familienstand  <fct> ledig, Partnerschaft, Partnerschaft, Partnerschaft, led…
## $ Kinder         <int> 0, 0, 0, 0, 0
## $ Wohnort        <fct> ländlich, städtisch, städtisch, ländlich, städtisch
## $ Rauchen        <fct> ja, nein, nein, nein, nein
## $ SportHäufig    <dbl> NA, 2.0, 3.0, 4.0, 2.5
## $ SportMinuten   <dbl> NA, 60, 60, 60, 90
## $ SportWie       <fct> NA, Gruppe, Allein, Allein, Allein
## $ SportWarum     <fct> NA, Vorbeugung, Vorbeugung, NA, NA
## $ LebenZufrieden <dbl> 10, 8, 9, 10, 9

Die Tabelle wird nach Gewicht sortiert ausgegeben. Im Datensatz pf8 haben insgesamt 5 Fälle das kleinste Gewicht.

Der Parameter n gibt an, wieviele “Stufen” gezählt werden sollen, also z.B. der kleinste (n=1) oder auch der zweit-kleinste Wert (n=2) usw..

Intern zählt n allerdings mit, wieviele Fälle (Reihen) bereits ausgegeben wurde. Das ist am Anfang etwas un-intuitiv.

pf8 %>%
  # zeigen die leichtesten Gewichte
  slice_min(Gewicht, n=3)%>%
      glimpse()

## Rows: 5
## Columns: 16
## $ Standort       <fct> Münster, Münster, Münster, Internet, Internet
## $ Alter          <int> 26, 24, 24, 24, 19
## $ Geschlecht     <fct> männlich, weiblich, weiblich, weiblich, weiblich
## $ Größe          <int> 180, 164, 160, 159, 155
## $ Gewicht        <dbl> 45, 45, 45, 45, 45
## $ Bildung        <fct> Hauptschule, Abitur, Abitur, NA, NA
## $ Beruf          <fct> Kaufmann/frau, Student*in, Student*in, ,
## $ Familienstand  <fct> ledig, Partnerschaft, Partnerschaft, Partnerschaft, led…
## $ Kinder         <int> 0, 0, 0, 0, 0
## $ Wohnort        <fct> ländlich, städtisch, städtisch, ländlich, städtisch
## $ Rauchen        <fct> ja, nein, nein, nein, nein
## $ SportHäufig    <dbl> NA, 2.0, 3.0, 4.0, 2.5
## $ SportMinuten   <dbl> NA, 60, 60, 60, 90
## $ SportWie       <fct> NA, Gruppe, Allein, Allein, Allein
## $ SportWarum     <fct> NA, Vorbeugung, Vorbeugung, NA, NA
## $ LebenZufrieden <dbl> 10, 8, 9, 10, 9

Das Ergebnis ist evtl. anders, als Sie es erwarten. Es werden weiterhin 5 Fälle angezeigt, obwohl n auf 3 gesetzt wurde. Das liegt daran, dass 5 Fälle das kleinste Gewicht haben, und diese werden vollständig angezeigt. Intern ist n dabei auf 5 angewachsen, weshalb keine zweit-kleinsten Gewichte mehr ausgegeben werden.

Lassen wir uns die Älteste Probanden auswählen.

pf8 %>%
  # zeige die ältesten
  slice_max(Alter, n=2) %>%
      glimpse()

## Rows: 2
## Columns: 16
## $ Standort       <fct> Münster, Rheine
## $ Alter          <int> 86, 86
## $ Geschlecht     <fct> weiblich, weiblich
## $ Größe          <int> 162, 160
## $ Gewicht        <dbl> 70, 60
## $ Bildung        <fct> Hauptschule, Abitur
## $ Beruf          <fct> Rentner*in, Postbote*in
## $ Familienstand  <fct> verwitwet, verwitwet
## $ Kinder         <int> 0, 1
## $ Wohnort        <fct> ländlich, städtisch
## $ Rauchen        <fct> nein, nein
## $ SportHäufig    <dbl> 2, 2
## $ SportMinuten   <dbl> 45, 45
## $ SportWie       <fct> Gruppe, Gruppe
## $ SportWarum     <fct> Fitness, Freizeit
## $ LebenZufrieden <dbl> 7, 8

Die Tabelle besteht zwar aus 2 Fällen, aber beide Fälle haben das selbe Alter. Die zweit-ältesten werden nicht mit angezeigt, da n intern bereits auf 2 angewachsen ist.

Wir eröhen n auf 3:

pf8 %>%
  # zeige die ältesten
  slice_max(Alter, n=3)%>%
      glimpse()

## Rows: 4
## Columns: 16
## $ Standort       <fct> Münster, Rheine, Rheine, Rheine
## $ Alter          <int> 86, 86, 84, 84
## $ Geschlecht     <fct> weiblich, weiblich, weiblich, weiblich
## $ Größe          <int> 162, 160, 160, 164
## $ Gewicht        <dbl> 70, 60, 60, 70
## $ Bildung        <fct> Hauptschule, Abitur, Hauptschule, Hauptschule
## $ Beruf          <fct> Rentner*in, Postbote*in, Rentner*in, Verkäufer*in
## $ Familienstand  <fct> verwitwet, verwitwet, verwitwet, verwitwet
## $ Kinder         <int> 0, 1, 0, 0
## $ Wohnort        <fct> ländlich, städtisch, städtisch, städtisch
## $ Rauchen        <fct> nein, nein, nein, nein
## $ SportHäufig    <dbl> 2, 2, 2, NA
## $ SportMinuten   <dbl> 45, 45, 60, NA
## $ SportWie       <fct> Gruppe, Gruppe, Allein, NA
## $ SportWarum     <fct> Fitness, Freizeit, Fitness, NA
## $ LebenZufrieden <dbl> 7, 8, 8, 5

Jetzt haben wir insgesamt 4 Fälle. Es werden zuerst die ältesten angezeigt. Da es 2 Fälle sind, steigt n intern auf 2 an. Da wir n mit 3 aufgerufen haben, ist noch “Platz” für die zweit-ältesten. Da dies ebenfalls 2 Fälle sind, werden beide ausgegeben. Insgesamt sehen wir also 4 Fälle, obwohl wir n mit 3 aufgerufen haben.

Ähnlich wie head() und tail() zeigen slice_head() und slice_tail() die ersten bzw. letzten Fälle an.

# zeige die ersten 5 Fälle
pf8 %>% slice_head(n=5)

##   Standort Alter Geschlecht Größe Gewicht        Bildung            Beruf
## 1  Münster    18   weiblich   172      69         Abitur     Inspektor*in
## 2  Münster    67   weiblich   165      67 mittlere Reife       Rentner*in
## 3  Münster    60   weiblich   175      NA     Hochschule Ergotherapeut*in
## 4  Münster    61   männlich   182      90 mittlere Reife       Beamter*in
## 5  Münster    24   männlich   173      68         Abitur       Student*in
##   Familienstand Kinder   Wohnort Rauchen SportHäufig SportMinuten SportWie
## 1 Partnerschaft      0  ländlich    nein          NA           NA   Allein
## 2    geschieden      0  ländlich    nein           2           60   Gruppe
## 3 Partnerschaft      0 städtisch    nein           2           45   beides
## 4         ledig      0  ländlich    nein           4          120   Allein
## 5         ledig      0 städtisch    nein           4           60   Allein
##          SportWarum LebenZufrieden
## 1        Arbeitsweg              5
## 2        Vorbeugung              7
## 3        Vorbeugung              7
## 4           Fitness              2
## 5 Gewichtsreduktion              9

# zeige die letzten 2 Fälle
pf8 %>% slice_tail(n=2)

##   Standort Alter Geschlecht Größe Gewicht Bildung Beruf Familienstand Kinder
## 1 Internet    20   weiblich   168      58    <NA>               ledig      0
## 2 Internet    40   männlich   183      78    <NA>         verheiratet      2
##     Wohnort Rauchen SportHäufig SportMinuten SportWie SportWarum LebenZufrieden
## 1 städtisch    nein         1.0           60   Allein       <NA>              8
## 2 städtisch      ja         2.5          120   Gruppe       <NA>             10

Mit slice_sample() können zufällig Fälle aus dem Datensatz gezogen werden.

# ziehe zufällig 7 Fälle
pf8 %>% slice_sample(n=7)

##    Standort Alter Geschlecht Größe Gewicht        Bildung             Beruf
## 1    Rheine    74   männlich   175      84 mittlere Reife           Koch*in
## 2   Münster    74   weiblich   176      80    Hauptschule        Rentner*in
## 3 Ladbergen    24   weiblich   168      63         Abitur             Azubi
## 4    Rheine    19   männlich   186      88    Hauptschule            Pflege
## 5   Münster    63   weiblich   168      63     Hochschule         Lehrer*in
## 6  Internet    31   männlich   177      68           <NA>
## 7 Ladbergen    59   weiblich   167      63 mittlere Reife Sozialarbeiter*in
##   Familienstand Kinder   Wohnort Rauchen SportHäufig SportMinuten SportWie
## 1         ledig      0 städtisch    nein          NA           NA     <NA>
## 2   verheiratet      0  ländlich    nein           1           60   Gruppe
## 3 Partnerschaft      0 städtisch      ja           5           15   Allein
## 4 Partnerschaft      0  ländlich      ja           3           90   Gruppe
## 5     verwitwet      1 städtisch    nein           2           30   Allein
## 6 Partnerschaft      0 städtisch    nein           5           60   Allein
## 7         ledig      0 städtisch      ja           3           60   beides
##   SportWarum LebenZufrieden
## 1       <NA>              8
## 2    Fitness             NA
## 3    Fitness              6
## 4 Vorbeugung             10
## 5    Fitness              9
## 6       <NA>             10
## 7 Vorbeugung              8

Mit der Funktion group_by() können Gruppierungen vorgenommen werden.

In Kombination mit group_by() zeigen die slice()-Funktionen die jeweilige Auswahl an Fällen pro Gruppe.

pf8 %>%
  drop_na() %>%
  # Gruppiere nach Geschlecht
  group_by(Geschlecht) %>%
  #  zeige
  slice(1)

## # A tibble: 3 × 16
## # Groups:   Geschlecht [3]
##   Standort Alter Geschlecht Größe Gewicht Bildung     Beruf Familienstand Kinder
##   <fct>    <int> <fct>      <int>   <dbl> <fct>       <fct> <fct>          <int>
## 1 Münster     61 männlich     182      90 mittlere R… Beam… ledig              0
## 2 Münster     67 weiblich     165      67 mittlere R… Rent… geschieden         0
## 3 Rheine      32 divers       186      92 Hochschule  Sozi… verheiratet        0
## # ℹ 7 more variables: Wohnort <fct>, Rauchen <fct>, SportHäufig <dbl>,
## #   SportMinuten <dbl>, SportWie <fct>, SportWarum <fct>, LebenZufrieden <dbl>

pf8 %>%
  drop_na() %>%
  # Gruppiere nach Geschlecht
  group_by(Geschlecht) %>%
  # sortiere nach Alter
  arrange(desc(Alter)) %>%
  #  zeige die ersten 3 Fälle pro Gruppe
  slice(1:3)

## # A tibble: 7 × 16
## # Groups:   Geschlecht [3]
##   Standort  Alter Geschlecht Größe Gewicht Bildung    Beruf Familienstand Kinder
##   <fct>     <int> <fct>      <int>   <dbl> <fct>      <fct> <fct>          <int>
## 1 Ladbergen    83 männlich     182    88   Hauptschu… Rent… verheiratet        0
## 2 Münster      78 männlich     178    71.5 Hochschule Lehr… verheiratet        0
## 3 Bahn         76 männlich     180    90   Hochschule Arzt… verheiratet        0
## 4 Münster      86 weiblich     162    70   Hauptschu… Rent… verwitwet          0
## 5 Rheine       86 weiblich     160    60   Abitur     Post… verwitwet          1
## 6 Rheine       84 weiblich     160    60   Hauptschu… Rent… verwitwet          0
## # ℹ 1 more row
## # ℹ 7 more variables: Wohnort <fct>, Rauchen <fct>, SportHäufig <dbl>,
## #   SportMinuten <dbl>, SportWie <fct>, SportWarum <fct>, LebenZufrieden <dbl>

Innerhalb der Gruppierungen kann auch gefiltert werden.

pf8 %>%
  # gruppiere nach Standort
  group_by(Geschlecht) %>%
  # filtere "divers"
  dplyr::filter(Geschlecht != "divers") %>%
  # filtere "Internet"
  dplyr::filter(Standort != "Internet") %>%
  # sortiere nach "Gewicht"
  arrange(desc(Gewicht)) %>%
  #  zeige die ersten 2 Fälle pro Gruppe
  slice(1,2)

## # A tibble: 4 × 16
## # Groups:   Geschlecht [2]
##   Standort Alter Geschlecht Größe Gewicht Bildung     Beruf Familienstand Kinder
##   <fct>    <int> <fct>      <int>   <dbl> <fct>       <fct> <fct>          <int>
## 1 Münster     22 männlich     206     130 Fachabitur  Kauf… ledig              0
## 2 Bahn        31 männlich     184     130 Fachabitur  Scha… Partnerschaft      0
## 3 Münster     40 weiblich     171     122 Hochschule  Pres… Partnerschaft      0
## 4 Rheine      45 weiblich     168     120 mittlere R… kauf… geschieden         1
## # ℹ 7 more variables: Wohnort <fct>, Rauchen <fct>, SportHäufig <dbl>,
## #   SportMinuten <dbl>, SportWie <fct>, SportWarum <fct>, LebenZufrieden <dbl>

pf8 %>%
  group_by(Standort) %>%
  dplyr::filter(Familienstand == "ledig" & Geschlecht == "männlich") %>%
  arrange(desc(Alter)) %>%
  head()

## # A tibble: 6 × 16
## # Groups:   Standort [2]
##   Standort Alter Geschlecht Größe Gewicht Bildung     Beruf Familienstand Kinder
##   <fct>    <int> <fct>      <int>   <dbl> <fct>       <fct> <fct>          <int>
## 1 Rheine      81 männlich     179      79 Hauptschule "Han… ledig              0
## 2 Rheine      74 männlich     175      84 mittlere R… "Koc… ledig              0
## 3 Münster     72 männlich     175      86 mittlere R… "Sic… ledig              0
## 4 Münster     69 männlich     179      89 mittlere R… "Ren… ledig              0
## 5 Münster     61 männlich     182      90 mittlere R… "Bea… ledig              0
## 6 Rheine      59 männlich     175      70 Abitur      ""    ledig              1
## # ℹ 7 more variables: Wohnort <fct>, Rauchen <fct>, SportHäufig <dbl>,
## #   SportMinuten <dbl>, SportWie <fct>, SportWarum <fct>, LebenZufrieden <dbl>

28.3 Variablen hinzufügen

Variablen können mit der Funktion bind_cols() hinzugefügt werden. Dabei muss die Länge der enthaltenen Werte mit der Länge des tibble übereinstimmen, ansonsten wiederholt R die Wertereihe so lange, bis sie mit der Länge des tibble übereinstimmt.

pf8 %>%
  # Fügt eine Variable "test" hinzu, in der alle Werte TRUE sind.
  bind_cols(test = TRUE) %>%
    glimpse()

## Rows: 731
## Columns: 17
## $ Standort       <fct> Münster, Münster, Münster, Münster, Münster, Münster, M…
## $ Alter          <int> 18, 67, 60, 61, 24, 21, 59, 56, 82, 52, 79, 22, 19, 18,…
## $ Geschlecht     <fct> weiblich, weiblich, weiblich, männlich, männlich, weibl…
## $ Größe          <int> 172, 165, 175, 182, 173, 177, 168, 156, 184, 166, 161, …
## $ Gewicht        <dbl> 69, 67, NA, 90, 68, 60, 80, 60, NA, 60, 66, 130, 52, 68…
## $ Bildung        <fct> Abitur, mittlere Reife, Hochschule, mittlere Reife, Abi…
## $ Beruf          <fct> Inspektor*in, Rentner*in, Ergotherapeut*in, Beamter*in,…
## $ Familienstand  <fct> Partnerschaft, geschieden, Partnerschaft, ledig, ledig,…
## $ Kinder         <int> 0, 0, 0, 0, 0, 0, 0, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0…
## $ Wohnort        <fct> ländlich, ländlich, städtisch, ländlich, städtisch, stä…
## $ Rauchen        <fct> nein, nein, nein, nein, nein, ja, nein, ja, nein, nein,…
## $ SportHäufig    <dbl> NA, 2.0, 2.0, 4.0, 4.0, 1.0, 2.0, 1.0, 1.0, 2.0, NA, 4.…
## $ SportMinuten   <dbl> NA, 60, 45, 120, 60, 60, 45, 90, NA, 45, NA, 90, 20, 12…
## $ SportWie       <fct> Allein, Gruppe, beides, Allein, Allein, Gruppe, Gruppe,…
## $ SportWarum     <fct> Arbeitsweg, Vorbeugung, Vorbeugung, Fitness, Gewichtsre…
## $ LebenZufrieden <dbl> 5, 7, 7, 2, 9, 8, 5, 8, 10, 8, 8, 8, 7, 8, 7, 8, 7, 7, …
## $ test           <lgl> TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, T…

Unsere neue Variable test sehen wir ganz unten. Alle Werte stehen auf TRUE.

28.4 Fälle hinzufügen

Neue Fälle können mit der Funktion bind_rows() hinzugefügt werden. Dies klappt nur, wenn die neuen Fälle als tibble vorliegen, das über die selben Variablen verfügt wie der Originaldatensatz. Die Reihenfolge der Spalten ist egal, da über die Spaltennamen gematcht wird.

Wichtig ist jedoch, dass innerhalb der Variablen auch der selbe Datentyp (numerisch, faktor, logisch) vorliegt, da ansonten die Datentypen auf den kleinsten gemeinsamen Nenner (character) zurückfallen. Wir machen es hier einmal falsch:

# erzeuge neuen Fall
# ohne Angabe des Datentyps
neu <- tibble("Internet", 44, "weiblich", 166, 70, NA, "Pilot", "verheiratet", 0, "ländlich","ja")
# kopiere die Spaltennamen
colnames(neu) <- colnames(pf8)

# füge zum Datensazu hinzu
# Achtung, zerschießt die Datentypen des tibble!
pf8 %>%
   bind_rows( neu ) %>%
    tail()

##     Standort Alter Geschlecht Größe Gewicht Bildung Beruf Familienstand Kinder
## 727 Internet    19   weiblich   158      58    <NA>               ledig      0
## 728 Internet    37   weiblich   160      59    <NA>         verheiratet      1
## 729 Internet    56   männlich   185      79    <NA>       Partnerschaft      0
## 730 Internet    20   weiblich   168      58    <NA>               ledig      0
## 731 Internet    40   männlich   183      78    <NA>         verheiratet      2
## 732 Internet    44   weiblich   166      70    <NA> Pilot   verheiratet      0
##       Wohnort Rauchen SportHäufig SportMinuten SportWie SportWarum
## 727 städtisch    nein         7.0           10   Allein       <NA>
## 728  ländlich    nein         3.0           60   Allein       <NA>
## 729  ländlich    nein         2.0           90   Gruppe       <NA>
## 730 städtisch    nein         1.0           60   Allein       <NA>
## 731 städtisch      ja         2.5          120   Gruppe       <NA>
## 732  ländlich      ja          NA           NA     <NA>       <NA>
##     LebenZufrieden
## 727              7
## 728              9
## 729              9
## 730              8
## 731             10
## 732             NA

Unsere Zeile ist ganz unten zu sehen. Die fehlenden Werte wurden mit NA aufgefüllt. Alles scheint gut gelaufen zu sein. Wie ein Blick mit glimpse() jedoch zeigt, haben wir die Datentypen unseres tibbles zerschossen, da unsere hinzugefügte Zeile nicht die korrekten Datentypen beinhaltete.

pf8 %>%
   bind_rows( neu ) %>%
    glimpse()

## Rows: 732
## Columns: 16
## $ Standort       <chr> "Münster", "Münster", "Münster", "Münster", "Münster", …
## $ Alter          <dbl> 18, 67, 60, 61, 24, 21, 59, 56, 82, 52, 79, 22, 19, 18,…
## $ Geschlecht     <chr> "weiblich", "weiblich", "weiblich", "männlich", "männli…
## $ Größe          <dbl> 172, 165, 175, 182, 173, 177, 168, 156, 184, 166, 161, …
## $ Gewicht        <dbl> 69, 67, NA, 90, 68, 60, 80, 60, NA, 60, 66, 130, 52, 68…
## $ Bildung        <fct> Abitur, mittlere Reife, Hochschule, mittlere Reife, Abi…
## $ Beruf          <chr> "Inspektor*in", "Rentner*in", "Ergotherapeut*in", "Beam…
## $ Familienstand  <chr> "Partnerschaft", "geschieden", "Partnerschaft", "ledig"…
## $ Kinder         <dbl> 0, 0, 0, 0, 0, 0, 0, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0…
## $ Wohnort        <chr> "ländlich", "ländlich", "städtisch", "ländlich", "städt…
## $ Rauchen        <chr> "nein", "nein", "nein", "nein", "nein", "ja", "nein", "…
## $ SportHäufig    <dbl> NA, 2.0, 2.0, 4.0, 4.0, 1.0, 2.0, 1.0, 1.0, 2.0, NA, 4.…
## $ SportMinuten   <dbl> NA, 60, 45, 120, 60, 60, 45, 90, NA, 45, NA, 90, 20, 12…
## $ SportWie       <fct> Allein, Gruppe, beides, Allein, Allein, Gruppe, Gruppe,…
## $ SportWarum     <fct> Arbeitsweg, Vorbeugung, Vorbeugung, Fitness, Gewichtsre…
## $ LebenZufrieden <dbl> 5, 7, 7, 2, 9, 8, 5, 8, 10, 8, 8, 8, 7, 8, 7, 8, 7, 7, …

Wie Sie sehen, sind unsere ehemaligen Faktoren Standort und Geschlecht auf <chr> zurückgefallen.

Ein falscher bind_rows()-Befehl kann Ihnen also das gesamte tibble “zerschießen”!

Fügen wir nun eine korrekte neue Zeile hinzu

# erzeuge korrekte neue Zeile
neu <- tibble(factor("Internet"), 44, factor("weiblich"), 166, 70, NA, factor("Pilot"), factor("verheiratet"), 0, factor("ländlich"), factor("ja"))
# Spaltennamen übernehmen
colnames(neu) <- colnames(pf8)

pf8 %>%
   bind_rows( neu ) %>%
    glimpse()

## Rows: 732
## Columns: 16
## $ Standort       <fct> Münster, Münster, Münster, Münster, Münster, Münster, M…
## $ Alter          <dbl> 18, 67, 60, 61, 24, 21, 59, 56, 82, 52, 79, 22, 19, 18,…
## $ Geschlecht     <fct> weiblich, weiblich, weiblich, männlich, männlich, weibl…
## $ Größe          <dbl> 172, 165, 175, 182, 173, 177, 168, 156, 184, 166, 161, …
## $ Gewicht        <dbl> 69, 67, NA, 90, 68, 60, 80, 60, NA, 60, 66, 130, 52, 68…
## $ Bildung        <fct> Abitur, mittlere Reife, Hochschule, mittlere Reife, Abi…
## $ Beruf          <fct> Inspektor*in, Rentner*in, Ergotherapeut*in, Beamter*in,…
## $ Familienstand  <fct> Partnerschaft, geschieden, Partnerschaft, ledig, ledig,…
## $ Kinder         <dbl> 0, 0, 0, 0, 0, 0, 0, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0…
## $ Wohnort        <fct> ländlich, ländlich, städtisch, ländlich, städtisch, stä…
## $ Rauchen        <fct> nein, nein, nein, nein, nein, ja, nein, ja, nein, nein,…
## $ SportHäufig    <dbl> NA, 2.0, 2.0, 4.0, 4.0, 1.0, 2.0, 1.0, 1.0, 2.0, NA, 4.…
## $ SportMinuten   <dbl> NA, 60, 45, 120, 60, 60, 45, 90, NA, 45, NA, 90, 20, 12…
## $ SportWie       <fct> Allein, Gruppe, beides, Allein, Allein, Gruppe, Gruppe,…
## $ SportWarum     <fct> Arbeitsweg, Vorbeugung, Vorbeugung, Fitness, Gewichtsre…
## $ LebenZufrieden <dbl> 5, 7, 7, 2, 9, 8, 5, 8, 10, 8, 8, 8, 7, 8, 7, 8, 7, 7, …

Die Datentypen sind erhalten geblieben.

28.5 Datensätze verbinden

Um Datenframes oder Tibbles miteinander zu verbinden, stehen neben bind_rows() und bind_cols() die join-Funktionen zur Verfügung. Sie kommen dann zur Anwendung, wenn die Zusammenführung anhand von “Übereinstimmungen” erfolgen soll.

Stellen wir uns die beiden Datenframes X und Y vor.

zwei Datenframes X und Y

Lassen Sie uns nun zwei konkrete Testdatensätze für X und Y erzeugen.

Das Tibble patient soll Daten über 10 Testpatienten enthalten.

patient <- tribble(
  ~Patient, ~Alter, ~Geschlecht, ~Diagnose,
    "AB25",     51,         "w",   "00119",
    "JA26",     61,         "w",   "00030",
    "BG81",     44,         "m",   "00011",
    "ZT42",     50,         "m",   "00199",
    "AL63",     80,         "w",   "00223",
    "XV96",     88,         "w",   "00016",
    "QR49",     66,         "w",   "00027",
    "FE31",     77,         "w",   "00027",
    "PP23",     64,         "m",   "00098",
    "WU53",     86,         "w",   "00016"
  )

In nanda sollen 10 NANDA-Pflegediagnosen enthalten sein.

nanda <- tribble(
    ~Code,                                ~Diagnosetitel, ~Evidenzlevel,
  "00110", "Selbstversorgungsdefizit Toilettenbenutzung",           2.1,
  "00108",       "Selbstversorgungsdefizit Körperpflege",           2.1,
  "00027",             "Defizitäres Flüssigkeitsvolumen",           2.1,
  "00011",                                 "Obstipation",           3.1,
  "00030",               "Beeinträchtigter Gasaustausch",           3.3,
  "00223",                       "Ineffektive Beziehung",           2.1,
  "00016",            "Beeinträchtigte Harnausscheidung",           3.1,
  "00119",         "Chronisch geringes Selbstwertgefühl",           3.2,
  "00199",              "Ineffektive Aktivitätenplanung",           2.1,
  "00095",                               "Schlafstörung",           3.3
  )

Schauen wir uns die beiden Tibbles noch einmal an:

head(patient)

## # A tibble: 6 × 4
##   Patient Alter Geschlecht Diagnose
##   <chr>   <dbl> <chr>      <chr>
## 1 AB25       51 w          00119
## 2 JA26       61 w          00030
## 3 BG81       44 m          00011
## 4 ZT42       50 m          00199
## 5 AL63       80 w          00223
## 6 XV96       88 w          00016

head(nanda)

## # A tibble: 6 × 3
##   Code  Diagnosetitel                               Evidenzlevel
##   <chr> <chr>                                              <dbl>
## 1 00110 Selbstversorgungsdefizit Toilettenbenutzung          2.1
## 2 00108 Selbstversorgungsdefizit Körperpflege                2.1
## 3 00027 Defizitäres Flüssigkeitsvolumen                      2.1
## 4 00011 Obstipation                                          3.1
## 5 00030 Beeinträchtigter Gasaustausch                        3.3
## 6 00223 Ineffektive Beziehung                                2.1

Es ist erkennbar, dass patient$Diagnose und nanda$Code Daten über das selbe Item beinhalten (in vielen Projekten wird eine eindeutige ID vergeben, über die später referenziert werden kann). Diese Spalten können wir also nutzen, um beim Verschmelzen die jeweiligen Datenreihen der beiden Tibbles einander zuordnen zu können. Dies haben wir schon bei der merge()-Funktion so gemacht, siehe Abschnitt 8.4.3. Im Tidyverse muss diese Information mittels join_by( X$Spalte == Y$Spalte) an die join-Funktionen übergeben werden. In userem Beispiel würde die Übereinstimmung wie folgt festgelegt: join_by(Diagnose==Code).

Die eigentliche Zusammenführung kann dann auf verschiedene Arten erfolgen:

inner_join()

left_join()

right_join()

full_join()

28.5.1 inner_join()

Die Funktion inner_join() verschmilzt nur solche Datenreihen aus X und Y, die in den Übereinstimmungsspalten gleiche Werte haben.

inner-join() übernimmt nur übereinstimmende Zeilen

Welcher Datensatz für X oder Y steht entscheidet die Reihenfolge innerhalb des Funktionsaufrufs. Im folgenden Beispiel setzen wir patient als X und nanda als Y.

# behalte nur solche, die matchen
inner_join(patient, nanda, join_by(Diagnose == Code))

## # A tibble: 9 × 6
##   Patient Alter Geschlecht Diagnose Diagnosetitel                   Evidenzlevel
##   <chr>   <dbl> <chr>      <chr>    <chr>                                  <dbl>
## 1 AB25       51 w          00119    Chronisch geringes Selbstwertg…          3.2
## 2 JA26       61 w          00030    Beeinträchtigter Gasaustausch            3.3
## 3 BG81       44 m          00011    Obstipation                              3.1
## 4 ZT42       50 m          00199    Ineffektive Aktivitätenplanung           2.1
## 5 AL63       80 w          00223    Ineffektive Beziehung                    2.1
## 6 XV96       88 w          00016    Beeinträchtigte Harnausscheidu…          3.1
## 7 QR49       66 w          00027    Defizitäres Flüssigkeitsvolumen          2.1
## 8 FE31       77 w          00027    Defizitäres Flüssigkeitsvolumen          2.1
## 9 WU53       86 w          00016    Beeinträchtigte Harnausscheidu…          3.1

Das so erzeugte Tibble enthält nur 9 Patientendaten, da der Diagnosecode von Patient PP23 (00098) nicht in der nanda-Liste enthalten ist. Weil aber nur vollständige Matches beibehalten werden, fehlt Patient PP23 in der Ausgabe.

Das Merkmal von inner_join() ist also, dass nur vollständige Datenreihen erhalten bleiben.

28.5.2 left_join()

Die Funktion left_join() behält alle Datenreihen von X, und fügt nur solche Y hinzu, die über die Übereinstimmungsspalte matchen.

left_join() behält alle X und ergänz passende Y

Auch in diesem Beispiel setzen wir patient als X und nanda als Y.

# behalte alle patienten (x), und packe nur
# matchende y (NANDA) hinzu
left_join(patient, nanda, join_by(Diagnose == Code))

## # A tibble: 10 × 6
##    Patient Alter Geschlecht Diagnose Diagnosetitel                  Evidenzlevel
##    <chr>   <dbl> <chr>      <chr>    <chr>                                 <dbl>
##  1 AB25       51 w          00119    Chronisch geringes Selbstwert…          3.2
##  2 JA26       61 w          00030    Beeinträchtigter Gasaustausch           3.3
##  3 BG81       44 m          00011    Obstipation                             3.1
##  4 ZT42       50 m          00199    Ineffektive Aktivitätenplanung          2.1
##  5 AL63       80 w          00223    Ineffektive Beziehung                   2.1
##  6 XV96       88 w          00016    Beeinträchtigte Harnausscheid…          3.1
##  7 QR49       66 w          00027    Defizitäres Flüssigkeitsvolum…          2.1
##  8 FE31       77 w          00027    Defizitäres Flüssigkeitsvolum…          2.1
##  9 PP23       64 m          00098    <NA>                                   NA
## 10 WU53       86 w          00016    Beeinträchtigte Harnausscheid…          3.1

Alle Patienten (X) sind erhalten geblieben. Da der Diagnosecode von Patient PP23 (00098) nicht in der nanda-Liste (Y) enthalten ist, wurden NAs ergänzt.

28.5.3 right_join()

Die Funktion right_join() ist das spiegelverkehrte Pendant. Sie behält alle Datenreihen von Y, und fügt nur solche X hinzu, die über die Übereinstimmungsspalte matchen.

right_join() behält alle Y und ergänz passende X

# behalte alle NANDA (y) und packe nur
# matchende Patienten (x) hinzu
# erzeugt doppelte Y, wenn x mehrfach y enthält
right_join(patient, nanda, join_by(Diagnose == Code))

## # A tibble: 12 × 6
##    Patient Alter Geschlecht Diagnose Diagnosetitel                  Evidenzlevel
##    <chr>   <dbl> <chr>      <chr>    <chr>                                 <dbl>
##  1 AB25       51 w          00119    Chronisch geringes Selbstwert…          3.2
##  2 JA26       61 w          00030    Beeinträchtigter Gasaustausch           3.3
##  3 BG81       44 m          00011    Obstipation                             3.1
##  4 ZT42       50 m          00199    Ineffektive Aktivitätenplanung          2.1
##  5 AL63       80 w          00223    Ineffektive Beziehung                   2.1
##  6 XV96       88 w          00016    Beeinträchtigte Harnausscheid…          3.1
##  7 QR49       66 w          00027    Defizitäres Flüssigkeitsvolum…          2.1
##  8 FE31       77 w          00027    Defizitäres Flüssigkeitsvolum…          2.1
##  9 WU53       86 w          00016    Beeinträchtigte Harnausscheid…          3.1
## 10 <NA>       NA <NA>       00110    Selbstversorgungsdefizit Toil…          2.1
## 11 <NA>       NA <NA>       00108    Selbstversorgungsdefizit Körp…          2.1
## 12 <NA>       NA <NA>       00095    Schlafstörung                           3.3

Das neue Tibble ist 12 Reihen lang, da zwei Pflegediagnosen (Y) jeweils 2mal im Datensatz patient (X) vorkommen.

Das Ergebnis von right_join() lässt sich prinzipiell auch erreichen, indem innerhalb von left_join() einfach die Werte umgedreht werden. Die Reihenfolge der Spalten und Zeilen im erzeugten Tibble ist dann aber anders, obwohl insgesamt die selben Daten enthalten sind.

# geht auch mit left_join(),
# indem man einfach X und Y vertauscht
# join_by() muss ebenfalls vertauscht werden
left_join(nanda, patient, join_by(Code == Diagnose))

## # A tibble: 12 × 6
##    Code  Diagnosetitel                     Evidenzlevel Patient Alter Geschlecht
##    <chr> <chr>                                    <dbl> <chr>   <dbl> <chr>
##  1 00110 Selbstversorgungsdefizit Toilett…          2.1 <NA>       NA <NA>
##  2 00108 Selbstversorgungsdefizit Körperp…          2.1 <NA>       NA <NA>
##  3 00027 Defizitäres Flüssigkeitsvolumen            2.1 QR49       66 w
##  4 00027 Defizitäres Flüssigkeitsvolumen            2.1 FE31       77 w
##  5 00011 Obstipation                                3.1 BG81       44 m
##  6 00030 Beeinträchtigter Gasaustausch              3.3 JA26       61 w
##  7 00223 Ineffektive Beziehung                      2.1 AL63       80 w
##  8 00016 Beeinträchtigte Harnausscheidung           3.1 XV96       88 w
##  9 00016 Beeinträchtigte Harnausscheidung           3.1 WU53       86 w
## 10 00119 Chronisch geringes Selbstwertgef…          3.2 AB25       51 w
## 11 00199 Ineffektive Aktivitätenplanung             2.1 ZT42       50 m
## 12 00095 Schlafstörung                              3.3 <NA>       NA <NA>

Das neue Tibble ist 12 Reihen lang, da zwei Pflegediagnosen (X) jeweils 2mal im Datensatz patient (Y) vorkommen.

Dieses Verhalten kann über den Parameter multiple bestimmt werden. So lässt sich einstellen, ob aus Y alle (all), nur der erste (first) oder letzte (last), oder irgendein (any) passender Eintrag übernommen werden soll.

# wenn mehrfaches Vorkommen in Y, nimm den letzten
left_join(nanda, patient, join_by(Code == Diagnose),
           multiple="last")

## # A tibble: 10 × 6
##    Code  Diagnosetitel                     Evidenzlevel Patient Alter Geschlecht
##    <chr> <chr>                                    <dbl> <chr>   <dbl> <chr>
##  1 00110 Selbstversorgungsdefizit Toilett…          2.1 <NA>       NA <NA>
##  2 00108 Selbstversorgungsdefizit Körperp…          2.1 <NA>       NA <NA>
##  3 00027 Defizitäres Flüssigkeitsvolumen            2.1 FE31       77 w
##  4 00011 Obstipation                                3.1 BG81       44 m
##  5 00030 Beeinträchtigter Gasaustausch              3.3 JA26       61 w
##  6 00223 Ineffektive Beziehung                      2.1 AL63       80 w
##  7 00016 Beeinträchtigte Harnausscheidung           3.1 WU53       86 w
##  8 00119 Chronisch geringes Selbstwertgef…          3.2 AB25       51 w
##  9 00199 Ineffektive Aktivitätenplanung             2.1 ZT42       50 m
## 10 00095 Schlafstörung                              3.3 <NA>       NA <NA>

Ausgangspunkt für den Parameter multiple ist immer Datensatz X. Im letzten Beispiel wurde nanda als X angegeben. Da jeder Diagnosecode in nanda genau einmal vorkommt, ist das neue Tibble 10 Reihen lang. Sollte in Y (patient) die Diagnose mehrfach vorkommen, wird (im Beispiel oben) die letzte vorkommende Datenreihen aus Y verwendet.

Kehren wir zum ursprünglichen right_join()-Aufruf zurück und übergeben den Parameter

right_join(patient, nanda, join_by(Diagnose == Code),
           multiple="last")

## # A tibble: 12 × 6
##    Patient Alter Geschlecht Diagnose Diagnosetitel                  Evidenzlevel
##    <chr>   <dbl> <chr>      <chr>    <chr>                                 <dbl>
##  1 AB25       51 w          00119    Chronisch geringes Selbstwert…          3.2
##  2 JA26       61 w          00030    Beeinträchtigter Gasaustausch           3.3
##  3 BG81       44 m          00011    Obstipation                             3.1
##  4 ZT42       50 m          00199    Ineffektive Aktivitätenplanung          2.1
##  5 AL63       80 w          00223    Ineffektive Beziehung                   2.1
##  6 XV96       88 w          00016    Beeinträchtigte Harnausscheid…          3.1
##  7 QR49       66 w          00027    Defizitäres Flüssigkeitsvolum…          2.1
##  8 FE31       77 w          00027    Defizitäres Flüssigkeitsvolum…          2.1
##  9 WU53       86 w          00016    Beeinträchtigte Harnausscheid…          3.1
## 10 <NA>       NA <NA>       00110    Selbstversorgungsdefizit Toil…          2.1
## 11 <NA>       NA <NA>       00108    Selbstversorgungsdefizit Körp…          2.1
## 12 <NA>       NA <NA>       00095    Schlafstörung                           3.3

so ist das Tibble wieder 12 Reihen lang, denn Ausgangspunkt für multiple ist immer Datensatz X (auch bei right_join()). Im Funktionsaufruf oben steht patient als X. Daher schaut die Funktion für jede Reihe in X, ob ein oder mehrere passende Einträge in Y vorhanden sind. Da in Y (nanda) aber jede Diagnose nur einmal definiert wird, kommen keine multiplen Zeilen vor. Der Parameter multiple findet daher keine Anwendung.

Nun will right_join() aber alle Y beibehalten und mit allen passenden X (patient) verschmelzen. Da bei zwei Diagnosen in Y jeweils zwei Patienten aus X matchen, ist die Ausgabe 12 Zeilen lang.

28.5.4 full_join()

Beim full_join() werden alle Daten von X und Y beibehalten.

full_join() behält alle X und Y

# behält alle x und alle y
full_join(patient, nanda, join_by(Diagnose == Code))

## # A tibble: 13 × 6
##    Patient Alter Geschlecht Diagnose Diagnosetitel                  Evidenzlevel
##    <chr>   <dbl> <chr>      <chr>    <chr>                                 <dbl>
##  1 AB25       51 w          00119    Chronisch geringes Selbstwert…          3.2
##  2 JA26       61 w          00030    Beeinträchtigter Gasaustausch           3.3
##  3 BG81       44 m          00011    Obstipation                             3.1
##  4 ZT42       50 m          00199    Ineffektive Aktivitätenplanung          2.1
##  5 AL63       80 w          00223    Ineffektive Beziehung                   2.1
##  6 XV96       88 w          00016    Beeinträchtigte Harnausscheid…          3.1
##  7 QR49       66 w          00027    Defizitäres Flüssigkeitsvolum…          2.1
##  8 FE31       77 w          00027    Defizitäres Flüssigkeitsvolum…          2.1
##  9 PP23       64 m          00098    <NA>                                   NA
## 10 WU53       86 w          00016    Beeinträchtigte Harnausscheid…          3.1
## 11 <NA>       NA <NA>       00110    Selbstversorgungsdefizit Toil…          2.1
## 12 <NA>       NA <NA>       00108    Selbstversorgungsdefizit Körp…          2.1
## 13 <NA>       NA <NA>       00095    Schlafstörung                           3.3

28.6 Variablen auswählen

Mit der Funktion select() können Variablen (Spalten) des tibble ausgewählt werden.

# wähle Variablen "Alter", "Größe" und "Gewicht"
# aus Datensatz pf8
 pf8 %>%
   select(Alter,Größe,Gewicht) %>%
   head(.)

##    Alter Größe Gewicht
## 11    18   172      69
## 12    67   165      67
## 13    60   175      NA
## 14    61   182      90
## 15    24   173      68
## 16    21   177      60

In Kombination mit der Funktion everything() kann die Reihenfolge der Variablen im Datensatz geändert werden. Die Funktion everything() hängt alle weiteren Variablen an unsere Auswahl an.

Angenommen, wir möchten Größe und Gewicht “als erstes” sehen, und alle anderen Variablen danach, so lautet der Befehl:

# zeige erst "Größe" und "Gewicht"
# und danach alles andere.
pf8 %>%
  select(Größe, Gewicht, everything()) %>%
  as_tibble()

## # A tibble: 731 × 16
##   Größe Gewicht Standort Alter Geschlecht Bildung     Beruf Familienstand Kinder
##   <int>   <dbl> <fct>    <int> <fct>      <fct>       <fct> <fct>          <int>
## 1   172      69 Münster     18 weiblich   Abitur      Insp… Partnerschaft      0
## 2   165      67 Münster     67 weiblich   mittlere R… Rent… geschieden         0
## 3   175      NA Münster     60 weiblich   Hochschule  Ergo… Partnerschaft      0
## 4   182      90 Münster     61 männlich   mittlere R… Beam… ledig              0
## 5   173      68 Münster     24 männlich   Abitur      Stud… ledig              0
## 6   177      60 Münster     21 weiblich   Abitur      Stud… Partnerschaft      0
## # ℹ 725 more rows
## # ℹ 7 more variables: Wohnort <fct>, Rauchen <fct>, SportHäufig <dbl>,
## #   SportMinuten <dbl>, SportWie <fct>, SportWarum <fct>, LebenZufrieden <dbl>

Mit der Funktion pull() werden die Spaltenwerte als Vektor wiedergegeben

# gib Werte von "Alter" als Vektor
pf8 %>%
  pull(Alter) %>%
   head(10)

##  [1] 18 67 60 61 24 21 59 56 82 52

28.7 Variablen erzeugen

Die Funktion mutate() erlaubt es, neue Variablen (Spalten) zu erzeugen, die durch Interaktion mit den anderen Variablen enstanden sind. So können wir im Datensatz pf8 aus den Variablen Größe und Gewicht den Body-Maß-Index errechnen, und diese Werte als neue Variable (Spalte) speichern.

# erzeuge BMI
pf8 %>%
  mutate(BMI = Gewicht/Größe*100) %>%
    head(.)

##    Standort Alter Geschlecht Größe Gewicht        Bildung            Beruf
## 11  Münster    18   weiblich   172      69         Abitur     Inspektor*in
## 12  Münster    67   weiblich   165      67 mittlere Reife       Rentner*in
## 13  Münster    60   weiblich   175      NA     Hochschule Ergotherapeut*in
## 14  Münster    61   männlich   182      90 mittlere Reife       Beamter*in
## 15  Münster    24   männlich   173      68         Abitur       Student*in
## 16  Münster    21   weiblich   177      60         Abitur       Student*in
##    Familienstand Kinder   Wohnort Rauchen SportHäufig SportMinuten SportWie
## 11 Partnerschaft      0  ländlich    nein          NA           NA   Allein
## 12    geschieden      0  ländlich    nein           2           60   Gruppe
## 13 Partnerschaft      0 städtisch    nein           2           45   beides
## 14         ledig      0  ländlich    nein           4          120   Allein
## 15         ledig      0 städtisch    nein           4           60   Allein
## 16 Partnerschaft      0 städtisch      ja           1           60   Gruppe
##           SportWarum LebenZufrieden      BMI
## 11        Arbeitsweg              5 40.11628
## 12        Vorbeugung              7 40.60606
## 13        Vorbeugung              7       NA
## 14           Fitness              2 49.45055
## 15 Gewichtsreduktion              9 39.30636
## 16           Fitness              8 33.89831

Mit der Schwesterfunktion transmute() wird nur die neue Variable in einem eigenen tibble zurückgegeben

# erzeuge BMI und gib nur BMI zurück
pf8 %>%
  transmute(BMI = Gewicht/Größe*100) %>%
    head(.)

##         BMI
## 11 40.11628
## 12 40.60606
## 13       NA
## 14 49.45055
## 15 39.30636
## 16 33.89831

28.8 statistische Berechnungen

Mit der Funktion summarise() können “zusammenfassenden Statistiken” berechnet werden. Sie fungiert als eine Art “Vermittlungsfunktion”, um Statistikfunktionen (siehe Kapitel @ref(Kapitel-StatistikMitR) in den Tidyverse-Workflow zu integrieren.

Klassische zusammenfassende Kennzahlen sind Mittelwert (über Funktion mean()), Median (über Funktion median()) und Standardabweichung (über Funktion sd()). Es funktioniert aber auch mit allen anderen Statistikfunktionen. Wir beschränken uns im Weiteren auf diese drei, alle anderen werden in Kapitel @ref(Kapitel-StatistikMitR) vorgestellt.

Berechnen wir mit mean() den Mittelwert für Alter.

# Mittelwert von "Alter"
pf8 %>%
  summarise(Mittelwert = mean(Alter))

##   Mittelwert
## 1         NA

Wir erhalten ein NA zurück, weil in der Variable Alter fehlende Werte enthalten sind. Fast alle statistischen Funktionen erwarten von uns, dass NAs weggefilter wurden. Nutzen wir die Macht der Pipe und ändern den Befehl in

# Mittelwert von "Alter"
pf8 %>%
  drop_na() %>%
    summarise(Mittelwert = mean(Alter))

##   Mittelwert
## 1   38.25126

Mit der Funktion group_by() können Gruppierungen vorgenommen werden.

# Gruppiere nach Geschlecht
pf8 %>%
  dplyr::group_by(Geschlecht) %>%
    drop_na() %>%
      summarise(Mittelwert = mean(Alter))

### A tibble: 3 x 2
##  Geschlecht Mittelwert
##   <fct>           <dbl>
## 1 männlich         37.3
## 2 weiblich         39.1
## 3 divers           32

Die Liste der “zusammenfassenden Statistiken” lässt sich beliebig erweitern.

pf8 %>%
  group_by(Geschlecht) %>%
     drop_na() %>%
       summarise(Mittelwert = mean(Alter),
                 Median = median(Alter),
                 Stdabw = sd(Alter))

### A tibble: 3 x 4
##  Geschlecht Mittelwert Median Stdabw
##   <fct>           <dbl>  <dbl>  <dbl>
## 1 männlich         37.3     29   17.5
## 2 weiblich         39.1     33   18.9
## 3 divers           32       32   NA

Auch, indem Gruppierungen komplexer definiert werden.

# mit mehr Gruppierungen
pf8 %>%
  group_by(Geschlecht, Standort) %>%
     drop_na() %>%
        summarise(Mittelwert = mean(Alter),
                  Median = median(Alter),
                  Stdabw = sd(Alter))

### A tibble: 9 x 5
### Groups:   Geschlecht [3]
##  Geschlecht Standort  Mittelwert Median Stdabw
##   <fct>      <fct>          <dbl>  <dbl>  <dbl>
## 1 männlich   Rheine          34.3   26     15.4
## 2 männlich   Münster         37.9   28     18.5
## 3 männlich   Bahn            39.9   36     19.8
## 4 männlich   Ladbergen       41.4   40     17.6
## 5 weiblich   Rheine          37.6   33     17.8
## 6 weiblich   Münster         38.8   27     20.0
## 7 weiblich   Bahn            41.3   37.5   19.4
## 8 weiblich   Ladbergen       43     40.5   18.5
## 9 divers     Rheine          32     32     NA

Die Funktion count() zählt die Häufigkeit der Variablenwerte.

Dies ist nicht nur bei Faktoren hilfreich.

pf8 %>%
  dplyr::count(Geschlecht)

##   Geschlecht   n
## 1   männlich 287
## 2   weiblich 437
## 3     divers   1
## 4       <NA>   6

In Kombination mit group_by() lassen sich Ausgaben ähnlich der Kreuztabelle erstellen.

pf8 %>%
  drop_na() %>%
  group_by(Familienstand, Geschlecht) %>%
  count() %>%
  slice_head()

## # A tibble: 13 × 3
## # Groups:   Familienstand, Geschlecht [13]
##    Familienstand Geschlecht     n
##    <fct>         <fct>      <int>
##  1 ledig         männlich      74
##  2 ledig         weiblich      82
##  3 Partnerschaft männlich      32
##  4 Partnerschaft weiblich      40
##  5 verheiratet   männlich      61
##  6 verheiratet   weiblich      78
##  7 verheiratet   divers         1
##  8 geschieden    männlich       5
##  9 geschieden    weiblich       7
## 10 verwitwet     männlich       4
## 11 verwitwet     weiblich       9
## 12 getrennt      männlich       3
## 13 getrennt      weiblich       2

Mit der Funktion add_acount() werden die Häufigkeitswerte als eigene Variable im Datensatz gespeichert.

pf8 %>%
  # Speichere die Levelhäufigkeit als eigene Variable
  add_count(Geschlecht, name = "AnzahlGeschlecht") %>%
  # Zeige diese Variablen als erstes, dann den Rest
  select(Geschlecht, AnzahlGeschlecht, everything()) %>%
  # Ausgabe als tibble ist "schöner"
  as_tibble()

## # A tibble: 731 × 17
##   Geschlecht AnzahlGeschlecht Standort Alter Größe Gewicht Bildung        Beruf
##   <fct>                 <int> <fct>    <int> <int>   <dbl> <fct>          <fct>
## 1 weiblich                437 Münster     18   172      69 Abitur         Inspe…
## 2 weiblich                437 Münster     67   165      67 mittlere Reife Rentn…
## 3 weiblich                437 Münster     60   175      NA Hochschule     Ergot…
## 4 männlich                287 Münster     61   182      90 mittlere Reife Beamt…
## 5 männlich                287 Münster     24   173      68 Abitur         Stude…
## 6 weiblich                437 Münster     21   177      60 Abitur         Stude…
## # ℹ 725 more rows
## # ℹ 9 more variables: Familienstand <fct>, Kinder <int>, Wohnort <fct>,
## #   Rauchen <fct>, SportHäufig <dbl>, SportMinuten <dbl>, SportWie <fct>,
## #   SportWarum <fct>, LebenZufrieden <dbl>

Die Funktion n() fliefert die Anzahl der Fälle.

pf8 %>%
  group_by(Standort, Geschlecht) %>%
  drop_na() %>%
  summarise(xquer = mean(Alter),
            sd = sd(Alter),
            median = median(Alter),
            n = n())

# A tibble: 9 x 6
# Groups:   Standort [4]
  Standort  Geschlecht xquer    sd median     n
  <fct>     <fct>      <dbl> <dbl>  <dbl> <int>
1 Rheine    männlich    34.3  15.4   26      67
2 Rheine    weiblich    37.6  17.8   33      83
3 Rheine    divers      32    NA     32       1
4 Münster   männlich    37.9  18.5   28      62
5 Münster   weiblich    38.8  20.0   27      83
6 Bahn      männlich    39.9  19.8   36      29
7 Bahn      weiblich    41.3  19.4   37.5    38
8 Ladbergen männlich    41.4  17.6   40      21
9 Ladbergen weiblich    43    18.5   40.5    14

28.9 Umgang mit Faktoren

Mit dem Paket forcats stehen im Tidyverse viele hilfreiche Funktionen für den Umgang mit Faktoren zur Verfügung.

28.9.0.1 Levels umbenennen

Mit der Funktion fct_recode() können Faktorenlevels umbenannt werden.

pf8 %>%
  mutate(Geschlecht = fct_recode(Geschlecht,
                      m ="männlich",
                      w = "weiblich",
                      d = "divers")) %>%
  as_tibble()

## # A tibble: 731 × 16
##   Standort Alter Geschlecht Größe Gewicht Bildung     Beruf Familienstand Kinder
##   <fct>    <int> <fct>      <int>   <dbl> <fct>       <fct> <fct>          <int>
## 1 Münster     18 w            172      69 Abitur      Insp… Partnerschaft      0
## 2 Münster     67 w            165      67 mittlere R… Rent… geschieden         0
## 3 Münster     60 w            175      NA Hochschule  Ergo… Partnerschaft      0
## 4 Münster     61 m            182      90 mittlere R… Beam… ledig              0
## 5 Münster     24 m            173      68 Abitur      Stud… ledig              0
## 6 Münster     21 w            177      60 Abitur      Stud… Partnerschaft      0
## # ℹ 725 more rows
## # ℹ 7 more variables: Wohnort <fct>, Rauchen <fct>, SportHäufig <dbl>,
## #   SportMinuten <dbl>, SportWie <fct>, SportWarum <fct>, LebenZufrieden <dbl>

Enthalten die neuen Levelnamen Sonderzeichen (wie das Leerzeichen), müssen sie in Anführungsstrichen gesetzt werden.

pf8 %>%
  mutate(Geschlecht = fct_recode(Geschlecht,
                      "ein Mann" ="männlich",
                      "eine Frau" = "weiblich",
                      "ein(e) Divers" = "divers")) %>%
  as_tibble()

## # A tibble: 731 × 16
##   Standort Alter Geschlecht Größe Gewicht Bildung     Beruf Familienstand Kinder
##   <fct>    <int> <fct>      <int>   <dbl> <fct>       <fct> <fct>          <int>
## 1 Münster     18 eine Frau    172      69 Abitur      Insp… Partnerschaft      0
## 2 Münster     67 eine Frau    165      67 mittlere R… Rent… geschieden         0
## 3 Münster     60 eine Frau    175      NA Hochschule  Ergo… Partnerschaft      0
## 4 Münster     61 ein Mann     182      90 mittlere R… Beam… ledig              0
## 5 Münster     24 ein Mann     173      68 Abitur      Stud… ledig              0
## 6 Münster     21 eine Frau    177      60 Abitur      Stud… Partnerschaft      0
## # ℹ 725 more rows
## # ℹ 7 more variables: Wohnort <fct>, Rauchen <fct>, SportHäufig <dbl>,
## #   SportMinuten <dbl>, SportWie <fct>, SportWarum <fct>, LebenZufrieden <dbl>

28.9.1 Levelreihenfolge ändern

Bei kategorialen Daten existiert keine geordnete Reihe der Werte. Dennoch kann es hilfreich sein, kategoriale Levels in eine bestimmte Reihenfolge zu bringen. Über die Hausfunktion factor() können über den Parameter levels die Levelreihenfolgen von Hand geändert werden.

x <- factor(c("vielleicht", "ja", "nein"))
x

## [1] vielleicht ja         nein
## Levels: ja nein vielleicht

# ändere Levelreihenfolge
factor(x, levels=c("nein", "vielleicht", "ja"))

## [1] vielleicht ja         nein
## Levels: nein vielleicht ja

Für häufige Anwendungsfälle bietet forcats Funktionen, die uns diese Arbeit abnehmen.

Schauen wir uns im Datensatz pf8 die Anlässe für Sport an. Das Skalenniveau ist nominal. Wenn wir die Daten plotten (zu ggplot siehe Kapitel @ref(Kapitel-ggplot)), werden die Daten (die Diagrammsäulen) in der Reihenfolge der Levels angezeigt, und nicht in der Reihenfolge der Häufigkeiten.

pf8 %>%
  select(SportWarum) %>%
  drop_na() %>%
  # dies ist der Plotbefehl, den Sie jetzt noch nicht verstehen.
  # Lesen Sie das Kapitel zu "ggplot".
  ggplot(aes(x=SportWarum)) + geom_bar()

Soll die Reihenfolgde der Levels so verändert werden, dass die mit der höchsten Ausprägung zuerst angezeigt werden, kann die Funktion fct_infreq() verwendet werden.

pf8 %>%
  drop_na() %>%
  # Sortiere nach Häufigkeiten
  mutate(SportWarum = fct_infreq(SportWarum)) %>%
  # plotten
  ggplot(aes(x=SportWarum)) + geom_bar()

Mit der Funktion fct_rev() wird die Levelreihenfolge umgekehrt.

pf8 %>%
  drop_na() %>%
  # Sortiere nach Häufigkeiten
  mutate(SportWarum = fct_infreq(SportWarum)) %>%
  # kehre Levelreihenfolge um
  mutate(SportWarum = fct_rev(SportWarum)) %>%
  # plotten
  ggplot(aes(x=SportWarum)) + geom_bar()

Die Levelreihenfolge kann auch auf Grundlage einer anderen Variable erfolgen.

Erstellen wir uns hierfür ein Subset des pf8-Datensatzes, indem wir für jede Sport-Kategorie die Mittelwerte von Alter und Gewicht ermitteln.

pf8sub <- pf8 %>%
            group_by(SportWarum) %>%
            summarise(
              Alter = mean(Alter, na.rm=T),
              Gewicht = mean(Gewicht, na.rm=T),
              n = n()
            )
pf8sub

## # A tibble: 9 × 4
##   SportWarum        Alter Gewicht     n
##   <fct>             <dbl>   <dbl> <int>
## 1 0                  19      65       1
## 2 Vorbeugung         42.9    78.1   131
## 3 Gewichtsreduktion  31.9    81.7    56
## 4 Fitness            37.4    73.2   191
## 5 Therapie           52.4    80.8    14
## 6 Arbeitsweg         34.6    82.1    19
## 7 Beruflich          43.9    80.6    14
## 8 Freizeit           36.7    74.0    28
## 9 <NA>               34.1    73.9   277

Wenn wir die Daten plotten, folgen die Datenpunkte auch hier der Levelreihenfolge.

pf8sub %>%
  # plotten
  ggplot(aes(x=Alter, y=SportWarum)) + geom_point()

Soll auch hier die Reihenfolge nach Mittelwerten erfolgen, kann die Funktion fct_reorder() verwendet werden.

pf8sub %>%
  # überschreibe Variable "SportWarum"
  # ordne die Levels von "SportWarum" nach "Alter"
  mutate(SportWarum = fct_reorder(SportWarum, Alter)) %>%
  # plotten
  ggplot(aes(x=Alter, y=SportWarum)) + geom_point()

Beachten Sie, dass NA weiterhin ganz oben angezeigt werden.

Mit der Funktion fct_relevel() können einzelne Levels “nach vorne” geholt werden. In diesem Beispiel möchten wir, dass der Grund Freizeit als erstes angezeigt wird

pf8sub %>%
  # überschreibe Variable "SportWarum"
  # ordne die Levels von "SportWarum" nach "Alter"
  mutate(SportWarum = fct_reorder(SportWarum, Alter)) %>%
  # hole Level "Freizeit" nach vorne
  mutate(SportWarum = fct_relevel(SportWarum, "Freizeit")) %>%
  # plotten
  ggplot(aes(x=Alter, y=SportWarum)) + geom_point()

28.9.2 Levels zusammenfassen

Schauen wir uns die Berufe im Datenatz pf8 an.

pf8 %>%
  dplyr::select(Beruf) %>%
  dplyr::count(Beruf) %>%
  arrange(n) %>%
  as_tibble()

## # A tibble: 104 × 2
##   Beruf                    n
##   <fct>                <int>
## 1 " Produktionsleiter"     1
## 2 "Angestellte"            1
## 3 "Bäcker*in"              1
## 4 "Bauzeichner*in"         1
## 5 "Berater*in"             1
## 6 "Betriebsprüfer*in"      1
## # ℹ 98 more rows

Wie Sie sehen, haben wir sehr viele Levels, die nur einmal oder zweimal vorkommen.

Mit der Funktion fct_lump_min() können wir niedrigausgeprägte Levels zusammenfassen.

pf8 %>%
  drop_na() %>%
  # fasse alle Levels mit Ausprägung kleiner 4 zusammen
  mutate(Beruf = fct_lump_min(Beruf, 4)) %>%
  dplyr::select(Beruf) %>%
  dplyr::count(Beruf) %>%
  arrange(desc(n)) %>%
  as_tibble()

## # A tibble: 22 × 2
##   Beruf             n
##   <fct>         <int>
## 1 Other           101
## 2 Student*in       82
## 3 kaufmann/frau    29
## 4 Handwerker*in    19
## 5 Angestellte*r    18
## 6 Azubi            17
## # ℹ 16 more rows

Alle Levels mit Ausprägungen kleiner 4 wurden in der neuen Level Other zusammengefasst.

Mit der Funktion fct_lump_n() bleiben die höchsten n Levels erhalten, während der Rest unter Other zusammengefasst wird.

pf8 %>%
  drop_na() %>%
  # behalte die 3 höchstausgeprägtesten Levels
  # und fasse den Rest zusammen
  mutate(Beruf = fct_lump_n(Beruf, 3)) %>%
  dplyr::select(Beruf) %>%
  dplyr::count(Beruf) %>%
  arrange(desc(n)) %>%
  as_tibble()

## # A tibble: 4 × 2
##   Beruf             n
##   <fct>         <int>
## 1 Other           268
## 2 Student*in       82
## 3 kaufmann/frau    29
## 4 Handwerker*in    19

Mit der Funktion fct_collapse() können die Levels “von Hand” zusammengefasst werden. Schauen wir uns den Familienstand im Datensatz pf8 an.

  levels(pf8$Familienstand)

## [1] "ledig"         "Partnerschaft" "verheiratet"   "geschieden"
## [5] "verwitwet"     "getrennt"

Fassen wir nun alle “verpartnerten” und alle “Singles” zu zwei Levels zusammen.

pf8 %>%
  # fasse Singles und Partner in eigenen Levels zusammen
  mutate(Partnerschaft = fct_collapse(Familienstand,
                                      "Partnerschaft" = c("Partnerschaft", "verheiratet"),
                                      "Single" = c("ledig", "geschieden", "verwitwet", "getrennt"))) %>%
  dplyr::select(Partnerschaft, Familienstand, Alter) %>%
  as_tibble()

## # A tibble: 731 × 3
##   Partnerschaft Familienstand Alter
##   <fct>         <fct>         <int>
## 1 Partnerschaft Partnerschaft    18
## 2 Single        geschieden       67
## 3 Partnerschaft Partnerschaft    60
## 4 Single        ledig            61
## 5 Single        ledig            24
## 6 Partnerschaft Partnerschaft    21
## # ℹ 725 more rows

28.10 Daten visualisieren

Zum Visualisieren von Daten sind neben Tabellen vor allem Diagramme geeignet. Wie Sie im Tidyverse Diagramme erstellen behandeln wir im Kapitel ggplot, siehe Kapitel 35.

29 Schritt 4: Ergebisse kommunizieren

Ihre Ergebnisse können Sie Ihrem Publikum am besten mit quarto, dem Nachfolger von RMarkdown, kommunizieren. Lesen Sie hierzu Kapitel 23.

30 weitere Hilfsmittel

30.1 Cheatsheets

In RStudio ist ein Liste mit “Cheatsheets” enthalten. Ein Cheatsheet (Schummelzettel) ist eine Seite, auf der (möglichs) alle wichtigen Befehle zu einem Thema (z.B. LaTeX oder R oder ggplot) enthalten sind. Diese Seite kann z.B. ausgedruckt und neben den PC bzw. ans Pinnboard geheftet werden. So hat man immer einen guten Überblick über die wichtigsten Befehle oder Funktionen.

Klicken Sie in der Menüzeile von RStudio auf Help →\rightarrow Cheatsheets , um eine Liste der enthaltenen Cheatsheets zu erhalten. Alle Sheets sind auf dieser Webseite verfügbar: https://www.rstudio.com/resources/cheatsheets/

Folgende Cheatsheets sind besonders empfehlenswert:

Klassisches R:
http://github.com/rstudio/cheatsheets/blob/main/base-r.pdf

Datentransformation mit dplyr
https://raw.githubusercontent.com/rstudio/cheatsheets/master/data-transformation.pdf

Grafiken erstellen mit ggplot
https://raw.githubusercontent.com/rstudio/cheatsheets/master/data-visualization-2.1.pdf

RMarkdown
https://raw.githubusercontent.com/rstudio/cheatsheets/master/rmarkdown-2.0.pdf

RMarkdown references
https://www.rstudio.com/wp-content/uploads/2015/03/rmarkdown-reference.pdf

30.2 Youtube

Auf Youtube gibt es zahlreiche Anleitungsvideos zu R. In der folgenden Liste möchten wir ein paar sehenswerte Videos vorschlagen.

R und RStudio installieren
https://www.youtube.com/watch?v=X_Mxya2Fis0

https://youtu.be/X_Mxya2Fis0

Erste Schritte in RStudio
https://www.youtube.com/watch?v=tyvEHQszZJs

https://youtu.be/tyvEHQszZJs

Einfaches Rechnen und Variablen in R
https://www.youtube.com/watch?v=qOVhiOu_ARs

https://youtu.be/qOVhiOu_ARs

Zusatzpakete installieren
https://www.youtube.com/watch?v=YD8OLpvkHD8

https://youtu.be/YD8OLpvkHD8

Intro to the Tinyverse (englisch)
https://www.youtube.com/watch?v=MKwyauo8nSI

https://youtu.be/MKwyauo8nSI

Ebenso gibt es empfehlenswerte Playlists:

“Statistik in R” von Björn Walther
https://www.youtube.com/playlist?list=PLJ8d6dduOfrpqxUw6Y7lGV4SoEkI9gIAi

“Statistik mit R” von Daniela Keller
https://www.youtube.com/watch?v=dEsm3PHrAHE&list=PLTMQeIvhjir5BEI7DfSAbTmHREI8Zqpnv

“Statistische Methoden 1” von Florian Heiss
https://www.youtube.com/playlist?list=PLV0KtPjp4poWkwg73O5ykfWu2mPviYnCq

offizielle Quarto-Playlist
https://www.youtube.com/playlist?list=PL9HYL-VRX0oRupficE2l5DGgVIzpypTHs

30.3 Freie Lehrbücher

Weitere freie R-Bücher:

Einführung in R von Ellis & Mayer
https://methodenlehre.github.io/einfuehrung-in-R/

R für Psychos von Burk & Anton
https://r-intro.tadaa-data.de/book/

30.4 Internetforen

Deutsches R Forum
http://forum.r-statistik.de/

31 Statistik mit R

Ebenso wie die Statistik, ist dieses Kapitel in folgende Bereiche unterteilt:

Deskriptive Statistik (Kapitel 32)

Schließende Statistik (Kapitel 33)

Die Funktionen sind alle in klassischem R-Code dargestellt, da sie so auch in einer Tidyverse-Befehlskette stehen würden.

Zu Demonstrationszwecken werden wir in diesem Kapitel auf Beispieldatensätze epa, mma , nw und pf8 zurückgreifen, die Sie sich aus dem Internet in Ihre R-Session laden können.

# lade Datensatz "epa"
load(url("https://www.produnis.de/R/data/epa.RData"))
# lade Datensatz "mma"
load(url("https://www.produnis.de/R/data/mma.RData"))
# lade Datensatz "pf8"
load(url("https://www.produnis.de/R/data/pf8.RData"))
# lade Datensatz "nw"
load(url("https://www.produnis.de/R/data/nw.RData"))

# Wenn Sie das Paket 'jgsbook' installiert haben,
# können Sie auch wie folgt auf die Datensätze zugreifen
epa <- jgsbook::epa
mma <- jgsbook::mma
pf8 <- jgsbook::pf8
nw  <- jgsbook::nw

32 Deskriptive Statistik

Zur Beschreibung von Verteilungen eigenen sich Tabellen, Lage- und Streuungskenngrößen.

32.1 Häufigkeiten

Eine Häufigkeitstabelle wird mit der Funktion table() erstellt

# Daten erzeugen
x <- c(1, 3, 5, 7, 7, 7, 6, 34)

# Häufigkeitstabelle
table(x)

## x
##  1  3  5  6  7 34
##  1  1  1  1  3  1

Für relative Häufigkeiten teilt man einfach durch die Länge der Variable x. So ändert sich der Befehl in:

# relative Häufigkeitstabelle
table(x)/length(x)

## x
##     1     3     5     6     7    34
## 0.125 0.125 0.125 0.125 0.375 0.125

Kumulierte Häufigkeiten erhält man mit der Funktion cumsum()

# kumulierte Häufigkeitstabelle
cumsum(table(x))

##  1  3  5  6  7 34
##  1  2  3  4  7  8

Und entsprechend auch kumulierte relative Häufigkeiten

# kumulierte relative Häufigkeitstabelle
cumsum(table(x)/length(x))

##     1     3     5     6     7    34
## 0.125 0.250 0.375 0.500 0.875 1.000

Eine weitere Option bietet die Funktion freq() aus dem rettyR-Paket. Sie erstellt eine Tabelle mit absoluten und relativen Häufigkeiten sowie relativen Häufikeiten ohne fehlende Wert (NA).

# Paket "prettyR" installieren
install.packages("prettyR", dependencies=TRUE)

# Paket laden
library(prettyR)

# Häufigkeitstabelle mit absoluten und relative Werten erstellen
freq(x)

##
## Frequencies for x
##         7    1    3    5    6   34   NA
##         3    1    1    1    1    1    0
## %    37.5 12.5 12.5 12.5 12.5 12.5    0
## %!NA 37.5 12.5 12.5 12.5 12.5 12.5

Die Funktion freqTable() aus dem jgsbook-Paket erstellt für einen Vektor eine vollständige Häufigkeitstabelle mit absoluten, relativen und kumulierten Werten.

# 234 Zufallswerte zwischen 21 und 30
x <- sample(21:30, 234, replace = TRUE)
# Häufigkeitstabelle
jgsbook::freqTable(x)

##    Wert Haeufig Hkum Relativ   Rkum
## 1    21      19   19    8.12   8.12
## 2    22      18   37    7.69  15.81
## 3    23      30   67   12.82  28.63
## 4    24      23   90    9.83  38.46
## 5    25      24  114   10.26  48.72
## 6    26      36  150   15.38  64.10
## 7    27      15  165    6.41  70.51
## 8    28      24  189   10.26  80.77
## 9    29      23  212    9.83  90.60
## 10   30      22  234    9.40 100.00

32.2 Lagekenngrößen

Mit dem Befehl summary() erhält man eine erste Zusammenfassung der Werteverteilung mit den wichtigsten Lagekenngrößen.

# Daten erzeugen
x <- c(1, 3, 5, 7, 7, 7, 6, 34)

# Zusammenfassung
summary(x)

##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.
##    1.00    4.50    6.50    8.75    7.00   34.00

Die Funktion describe() aus dem psych-Zusatzpaket liefert noch detailiertere Übersichten:

psych::describe(x)

##    vars n mean    sd median trimmed  mad min max range skew kurtosis   se
## X1    1 8 8.75 10.43    6.5    8.75 1.48   1  34    33 1.69     1.35 3.69

Das Funktioniert auch bei kompletten Datensätzen.

# Zusammenfassung von pf8
psych::describe(pf8)

##                vars   n   mean    sd median trimmed   mad min max range  skew
## Standort*         1 731   2.85  1.61      2    2.81  1.48   1   5     4  0.28
## Alter             2 730  37.00 18.00     28   34.98 12.60  15  86    71  0.74
## Geschlecht*       3 725   1.61  0.49      2    1.63  0.00   1   3     2 -0.40
## Größe             4 730 174.04  9.62    173  173.84 10.38 151 206    55  0.26
## Gewicht           5 720  75.53 16.88     73   73.93 16.31  45 175   130  1.12
## Bildung*          6 506   4.88  1.77      5    4.98  2.97   1   7     6 -0.39
## Beruf*            7 731  40.42 37.04     34   38.58 48.93   1 104   103  0.26
## Familienstand*    8 729   2.11  1.12      2    1.99  1.48   1   6     5  0.83
## Kinder            9 727   0.38  0.79      0    0.18  0.00   0   5     5  2.19
## Wohnort*         10 729   1.41  0.49      1    1.39  0.00   1   2     1  0.36
## Rauchen*         11 729   1.21  0.40      1    1.13  0.00   1   2     1  1.45
## SportHäufig      12 677   2.56  1.88      2    2.41  1.48   0  21    21  1.91
## SportMinuten     13 667  62.42 40.14     60   60.47 44.48   0 400   400  1.53
## SportWie*        14 656   1.64  0.69      2    1.55  1.48   1   3     2  0.62
## SportWarum*      15 454   3.75  1.64      4    3.49  1.48   1   8     7  1.06
## LebenZufrieden   16 724   7.91  1.59      8    8.07  1.48   1  11    10 -1.10
##                kurtosis   se
## Standort*         -1.53 0.06
## Alter             -0.70 0.67
## Geschlecht*       -1.75 0.02
## Größe             -0.26 0.36
## Gewicht            2.40 0.63
## Bildung*          -1.26 0.08
## Beruf*            -1.55 1.37
## Familienstand*     0.48 0.04
## Kinder             4.45 0.03
## Wohnort*          -1.87 0.02
## Rauchen*           0.11 0.01
## SportHäufig       12.75 0.07
## SportMinuten       8.64 1.55
## SportWie*         -0.77 0.03
## SportWarum*        0.79 0.08
## LebenZufrieden     1.86 0.06

Die Lagewerte lassen sich auch einzeln bestimmen.

Mit der Funktion min() wird das Minimum der Verteilung ausgegeben.

# Minimum bestimmen
min(x)

## [1] 1

Dementsprechend liefert max() das Maximum.

# Maximum bestimmen
max(x)

## [1] 34

Die Spannweite wird mit range() abgefragt.

# Spannweite bestimmen
range(x)

## [1]  1 34

Das arithmetische Mittel wird mit der Funktion mean() bestimmt

# x-quer bestimmen
mean(x)

## [1] 8.75

Sind fehlende Werte in der Reihe enthalten, gibt R nur NA zurück.

#  mean für "Gewicht" im Datensatz "pf8"
mean(pf8$Gewicht)

## [1] NA

Beheben kann man dies, indem der Parameter na.rm auf TRUE gesetzt wird.

#  mean für "Gewicht" im Datensatz "pf8"
# entferne NAs
mean(pf8$Gewicht, na.rm=TRUE)

## [1] 75.5325

Der Median wird mit der Funktion median() ermittelt.

# Median bestimmen
median(x)

## [1] 6.5

Auch hier müssen enthaltene NAs ausgeblendet werden:

# Median bestimmen, OHNE NAs
median(pf8$Gewicht, na.rm=TRUE)

## [1] 73

In den R-Hausmitteln existiert keine eigene Funktion zur Bestimmung des Modalwertes. Wir können ihn daher “auf Umwegen” bestimmen. Wir lassen mittels table() eine Häufigkeitstabelle der Variable ausgeben. Diese sortieren wir mittels sort() absteigend. Der Modus ist dann der erste Wert der sortierten Tabelle.

# Daten als Häufigkeitstabelle
table(x)

## x
##  1  3  5  6  7 34
##  1  1  1  1  3  1

# absteigend sortieren
sort(table(x), decreasing=TRUE)

## x
##  7  1  3  5  6 34
##  3  1  1  1  1  1

# Modalwert
sort(table(x), decreasing=TRUE)[1]

## 7
## 3

Es ist der Wert 7 und er kommt 3 mal vor.

Eine weitere Möglichkeit bietet das Zusatzpaket statip. Es enthält die Funktion mfv(), mit welcher der Modalwert bestimmt werden kann.

# Daten als Häufigkeitstabelle
library(statip)

# Modalwert bestimmen
mfv(x)

## [1] 7

# bzw. auch
statip::mfv(x)

## [1] 7

Die vier Quartile werden mit der Funktion quantile() ermittelt (ja, der Befehl für Quartile heisst in R quantile()).

# Quartile bestimmen, ohne NAs
quantile(x, na.rm=T)

##   0%  25%  50%  75% 100%
##  1.0  4.5  6.5  7.0 34.0

Es lassen sich beliebige Quantile berechnen.

# Quartile bestimmen, ohne NAs
quantile(x, probs = c(.333, .666))

## 33.3% 66.6%
## 5.331 7.000

Und auch die Art der Berechnung lässt sich über den Parameter type festlegen. Beispielsweise verwendet type=6 die Methode von SPSS.

# Quartile bestimmen, ohne NAs
# so wie SPSS
quantile(x, probs = c(.333, .666), type=6)

## 33.3% 66.6%
## 4.994 7.000

32.3 Streuungskenngrößen

Die wichtigste Streuungskenngröße ist die Standardabweichung. Sie wird bestimmt mittels der Funktion sd().

# Standardabweichung bestimmen
sd(x, na.rm=T)

## [1] 10.43004

Die Varianz der Stichprobe wird mit var() berechnet.

# Varianz von x bestimmen
var(x, na.rm=T)

## [1] 108.7857

Der Interquartilsabstand (oder einfach nur Quartilsabstand) wird mit der Funktion IQR() bestimmt.

# Quartilsabstand bestimmen
IQR(x, na.rm=T)

## [1] 2.5

Ebenso wie bei quantile() lässt sich über den Parameter type die Berechnungsart ändern. Mit type=6 nutzt R die Methode wie in SPSS:

# Quartilsabstand bestimmen
# Methode wie in SPSS
IQR(x, na.rm=T, type=6)

## [1] 3.5

32.4 Kreuztabellen

Mit der Funktion table() lassen sich auch Kreuztabellen erstellen, indem einfach beide Variablen übergeben werden.

# erzeuge Daten
Punktwert <- c(2 , 12 , 7 , 15 , 10 , 4 , 13 , 9 , 16 , 19)
Geschlecht <- rep(c("maennlich" , "weiblich") ,5)

# Kreuztabelle
table(Punktwert, Geschlecht)

##          Geschlecht
## Punktwert maennlich weiblich
##        2          1        0
##        4          0        1
##        7          1        0
##        9          0        1
##        10         1        0
##        12         0        1
##        13         1        0
##        15         0        1
##        16         1        0
##        19         0        1

Ebenso steht die Funktion xtabs() zur Verfügung.

# Kreuztabelle mit "xtabs()"
xtabs(~ Punktwert + Geschlecht)

##          Geschlecht
## Punktwert maennlich weiblich
##        2          1        0
##        4          0        1
##        7          1        0
##        9          0        1
##        10         1        0
##        12         0        1
##        13         1        0
##        15         0        1
##        16         1        0
##        19         0        1

Siehe Abschnitt 36.1 für weitere Beispiele zur Kreuztabelle.

32.5 z-Transformation

Die z-Transformation erfolgt mit der Funktion scale().

# Wertereihe
Punktwerte <- c(2 , 12 , 7 , 15 , 10 , 4 , 13 , 9 , 16 , 19)

# z-Transformation
scale(Punktwerte)

##             [,1]
##  [1,] -1.6183421
##  [2,]  0.2418212
##  [3,] -0.6882604
##  [4,]  0.7998702
##  [5,] -0.1302114
##  [6,] -1.2463094
##  [7,]  0.4278376
##  [8,] -0.3162278
##  [9,]  0.9858866
## [10,]  1.5439356
## attr(,"scaled:center")
## [1] 10.7
## attr(,"scaled:scale")
## [1] 5.375872

32.6 Korrelation

Die allgemeine Funktion zur Berechnung von Korrelationen heisst cor(). Die Korrelation nach Pearson berechnet sich mit der Funktion cor() und deren Parameter method="pearson".

# erzeuge Daten
Lesetest <- c(2 , 12 , 7 , 15 , 10 , 4 , 13 , 9 , 16 , 19)
Rechtschreibung <- c(3 , 14 , 9 , 17 , 12 , 4 , 16 , 12 , 18 , 20)

# Pearson Maßkorrelationskoeffizient
cor(Lesetest, Rechtschreibung, method="pearson")

## [1] 0.9884616

Mit der Funktion cor.test() kann direkt ein Siginifkanztest mitgefahren werden

# Pearson Maßkorrelationskoeffizient
# mit Signifikanztest
cor.test(Lesetest, Rechtschreibung, method="pearson")

##
##  Pearson's product-moment correlation
##
## data:  Lesetest and Rechtschreibung
## t = 18.458, df = 8, p-value = 7.648e-08
## alternative hypothesis: true correlation is not equal to 0
## 95 percent confidence interval:
##  0.9502085 0.9973659
## sample estimates:
##       cor
## 0.9884616

Sollten fehlende Wert (NA) enthalten sein, gibt der Befehl nur ein NA zurück.

# Pearson Maßkorrelationskoeffizient
# mit Datensatz "pf8", da sind NAs enthalten!!!
cor(pf8$Alter, pf8$Größe, method="pearson")

## [1] NA

Mit dem Parameter use="complete.obs" (nutze nur vollständige Beobachtungen) können wir dies beheben.

# Pearson Maßkorrelationskoeffizient
# mit Datensatz "pf8", verwende nur "komplette" Daten (ohne NA)
cor(pf8$Alter, pf8$Größe, method="pearson", use="complete.obs")

## [1] -0.1067484

Für die Korrelationsberechnung nach Spearmen (Spearman’s ρ\rho) wird der Parameter method auf spearman gesetzt.

# Spearman's Korrelationsberechnung
cor(Lesetest, Rechtschreibung, method="spearman")

## [1] 0.9969651

Auch hier müssen NAs mit dem Parameter use="complete.obs" unterdrückt werden.

# Spearman's Rho
# mit Datensatz "pf8", verwende nur "komplette" Daten (ohne NA)
cor(pf8$Alter, pf8$Größe, method="spearman", use="complete.obs")

## [1] -0.05920716

Auch für Spearman’s Rho kann ein Signifikanztest mitgefahren werden, indem cor.test() aufgerufen wird.

# Spearman's Korrelationsberechnung
# mit Signifikanztest
cor.test(Lesetest, Rechtschreibung, method="spearman")

## Warning in cor.test.default(Lesetest, Rechtschreibung, method = "spearman"):
## Kann exakten p-Wert bei Bindungen nicht berechnen

##
##  Spearman's rank correlation rho
##
## data:  Lesetest and Rechtschreibung
## S = 0.50076, p-value = 3.698e-10
## alternative hypothesis: true rho is not equal to 0
## sample estimates:
##       rho
## 0.9969651

Die Warnmeldung kann ignoriert werden, der p-Wert wird in der Ausgabe angezeigt.

Da cor.test() ein Objekt der Klasse Liste zurückliefert, kann auf die einzelnen Komponenten des Tests zugegriffen werden.

# schreibe Testergebnis in Variable "test"
test <- cor.test(Lesetest, Rechtschreibung, method="spearman")

# zeige nur p-Wert an
test$p.value

## [1] 3.698093e-10

# zeige nur Teststatistik an
test$statistic

##         S
## 0.5007599

# zeige nur Spearmans's Rho an
test$estimate

##       rho
## 0.9969651

33 Schließende Statistik

33.1 Regressionen

Die Funktion zur Berechnung linearer Regressionen ist lm() (für “linear model”). Sie folgt der Syntax x erklärt durch y, was durch eine Tilde ~ ausgedrückt wird.

# erzeuge Daten
Lesetest <- c(2 , 12 , 7 , 15 , 10 , 4 , 13 , 9 , 16 , 19)
Rechtschreibung <- c(3 , 14 , 9 , 17 , 12 , 4 , 16 , 12 , 18 , 20)

# lineare Regression "Rechtschreibung erklärt durch Lesetest"
lm(Rechtschreibung~Lesetest)

##
## Call:
## lm(formula = Rechtschreibung ~ Lesetest)
##
## Coefficients:
## (Intercept)     Lesetest
##       1.208        1.055

Hierbei ist Intercept der Schnitt durch die Y-Achse.

Die Formel der Modellgeraden lautet also y=1,208+1,055⋅xy = 1,208 + 1,055\cdot x.

Wenn Sie über den Parameter data das Datenframe angeben, können Sie auf die Variablen referenzieren, ohne $ verwenden zu müssen.

# lade Testdaten
load(url("https://www.produnis.de/R/data/nw.RData"))

# entweder Variablen per "$" referenzieren
lm(nw$workyear ~ nw$worknight)

##
## Call:
## lm(formula = nw$workyear ~ nw$worknight)
##
## Coefficients:
##  (Intercept)  nw$worknight
##       9.9143        0.8291

# oder Datenframe mit angeben
lm(workyear ~ worknight, data=nw)

##
## Call:
## lm(formula = workyear ~ worknight, data = nw)
##
## Coefficients:
## (Intercept)    worknight
##      9.9143       0.8291

Es ist üblich, Modelle in ein Objekt zu speichern und über die summary()- Funktion aufzurufen. Die Informationen der Modellzusammenfassung sind so detailierter.

# speichere Modell in "fit"
fit <- lm(workyear~worknight, data=nw)
# schaue mit "summary()" an
summary(fit)

##
## Call:
## lm(formula = workyear ~ worknight, data = nw)
##
## Residuals:
##     Min      1Q  Median      3Q     Max
## -12.889  -5.743  -2.351   3.660  26.427
##
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)
## (Intercept)  9.91425    0.74088   13.38   <2e-16 ***
## worknight    0.82912    0.05958   13.91   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 7.463 on 274 degrees of freedom
## Multiple R-squared:  0.4141, Adjusted R-squared:  0.4119
## F-statistic: 193.6 on 1 and 274 DF,  p-value: < 2.2e-16

Außerdem lässt sich das Modell so leicht plotten.

plot(fit)

Dies erzeugt insgesamt 4 Plots (Residualplot, Q-Q, Scale-Location und Einfluss). Durch drücken der [Enter]-Taste schalten Sie zum nächsten Plot weiter.

Sie können aber auch alle 4 Plots in einem ausgeben lassen.

# plotte alle 4 auf einer Seite
par(mfrow = c(2, 2), oma = c(0, 0, 2, 0))
plot(fit)

Denken Sie aber daran, anschließend die Plotausgabe wieder zurückzusetzen:

par(mfrow = c(1, 1))

33.1.1 multiple lineare Regressionen

Multiple lineare Regressionen werden ebenfalls mit der Funktion lm() berechnet. Hierbei werden die erklärenden Variablen per + Zeichen aneinandergereiht.

# multiple lineare Regression
# "workyear" erklärt durch "worknight" UND "age"
fit <- lm(workyear ~ worknight+age, data=nw)
summary(fit)#

##
## Call:
## lm(formula = workyear ~ worknight + age, data = nw)
##
## Residuals:
##      Min       1Q   Median       3Q      Max
## -14.7077  -3.9748  -0.3066   3.5936  19.9569
##
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)
## (Intercept) -5.99856    1.72557  -3.476 0.000591 ***
## worknight    0.57707    0.05714  10.100  < 2e-16 ***
## age          0.41614    0.04195   9.921  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 6.41 on 273 degrees of freedom
## Multiple R-squared:  0.5693, Adjusted R-squared:  0.5662
## F-statistic: 180.5 on 2 and 273 DF,  p-value: < 2.2e-16

Ebenfalls können hier die 4 Plots erzeugt werden.

# plotte alle 4 auf einer Seite
par(mfrow = c(2, 2), oma = c(0, 0, 2, 0))
plot(fit)

par(mfrow = c(1, 1))

33.1.2 nicht-lineare Regression

Häufig folgen die vorhandenen Daten keinem linearen Zusammenhang.

# erzeuge dummy-Werte
x <- c(6, 9, 12, 14, 30, 35, 40, 47, 51, 55, 60)
y <- c(14, 28, 50, 70, 89, 94, 90, 75, 59, 44, 27)

# Plotte die Daten mit Regressionsgeraden
plot(x, y, col="blue")
abline(lm(y~x), col="red", lty=3)

Das Aussehen des Plots lässt schon erahnen, dass kein linearer Zusammenhang vorliegt. Wenn wir ein lineares Modell auf die Daten fitten, ist R² sehr niedrig.

# Modell fitten
fit <- lm(y~x)
# Modelldaten ausgeben
summary(fit)

##
## Call:
## lm(formula = y ~ x)
##
## Residuals:
##    Min     1Q Median     3Q    Max
## -39.34 -21.99  -2.03  23.50  35.11
##
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)
## (Intercept)  48.4531    17.3288   2.796   0.0208 *
## x             0.2981     0.4599   0.648   0.5331
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 28.72 on 9 degrees of freedom
## Multiple R-squared:  0.0446, Adjusted R-squared:  -0.06156
## F-statistic: 0.4201 on 1 and 9 DF,  p-value: 0.5331

R² ist hier 0,0446, was bedeutet, dass nur 4% der Daten durch das Modell erklärt werden können.

Das Plot legt nahe, dass ein quadratischer Zusammenhang besteht, denn die Punkte scheinen einer Parabel zu folgen. Die Formel einer quadratischen Funktion lautet vereinfacht y=x2+xy = x^2 + x. Den rechten Teil der Formel müssen wir innerhalb von lm() verwenden. Leider können wir die Formel nicht direkt in lm() schreiben, weil innerhalb von lm() nach dem ~-Zeichen keine weiteren Rechenoperationen möglich sind. Das liegt daran, dass die Prädiktoren mittels + und - übergeben werden. Unser Trick besteht nun darin, alle Komponeten der Formel als eigenständige Prädiktoren des Modells anzugeben. Dazu müssen wir zunächst ein neues Objekt erzeugen, welches die x² -Werte enthält:

# quadrierte Werte als eigenes Objekt
x2 <- x^2
# Modell erstellen mit quadratischer Formel
fit <- lm(y ~ x2 + x)
# Modellwerte ausgeben
summary(fit)

##
## Call:
## lm(formula = y ~ x2 + x)
##
## Residuals:
##     Min      1Q  Median      3Q     Max
## -6.2484 -3.7429 -0.1812  1.1464 13.6678
##
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)
## (Intercept) -18.25364    6.18507  -2.951   0.0184 *
## x2           -0.10120    0.00746 -13.565 8.38e-07 ***
## x             6.74436    0.48551  13.891 6.98e-07 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 6.218 on 8 degrees of freedom
## Multiple R-squared:  0.9602, Adjusted R-squared:  0.9502
## F-statistic: 96.49 on 2 and 8 DF,  p-value: 2.51e-06

R² liegt nun bei 0,9602, was bedeutet, dass 96% der Daten durch das Modell erklärt werden können. Die Funktionsformel lautet y=−0,10120⋅x2+6,74436⋅x−18,25364y = -0,10120\cdot x^2 + 6,74436\cdot x - 18,25364.

Mit der Funktion predict() lassen sich nun für jedes x die entsprechenden y-Werte des Modells bestimmen (vorhersagen). Der Funktion wird das gefittete Objekt übergeben, sowie eine Liste der gewünschten Prädiktorenwerte. Die so erzeugten Modellwerte können dann ins Plot gezeichnet werden.

vorhersage <- predict(fit, list(x, x2))

plot(x, y, col="blue")
lines(x, vorhersage, col='darkgreen')

Um eine schöne Parabel zu zeichnen, haben wir zu wenige x-Werte. Daher erstellen wir einen neuen Vektor, der kontinuierliche Werte für x enthält. Mit diesem können wir dann die entsprechenden (vielen) y-Werte mittels predict() bestimmen und in das Plot einzeichnen. So entsteht - bei genügend Werten - eine schöne Modellparabel.

# Erstelle kontinuierliche Werte
# für x von 0 bis 60
xref <- seq(0, 60, 0.1)
# berechne dazugehörige y-Modellwerte
yref <- predict(fit, list(x = xref,
                          x2 = xref^2))

# Plotten
plot(x, y, col="blue")
# Modell-Parabel einzeichnen
lines(xref, yref, col='darkgreen')
abline(lm(y~x), col="red", lty=3)

33.1.2.1 Funktionen höherer Ordnung

Wird ein Zusammenhang mit einer Funktion höherer Ordnung vermutet, gehen wir analog zur quadratischen Funktion vor. In diesem Beispiel nehmen wir an, der Zusammenhang ließe sich vereinfacht durch die Formel y=x3+x2+xy = x^3 + x^2 +x beschreiben. Die Daten verhalten sich wie folgt:

# erzeuge Testwerte
x <- c(0, 1, 2, 3, 4, 5, 6, 7, 8, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5)
y <- c(1, 46, 71, 82,  82,  86,  91, 106, 137,  17,  51,  68, 77,  76,  82.1,  87, 108)

# plotten
plot(x, y, col="blue")

# falsche Regressionsgerade
abline(lm(y~x), col="red", lty=3)

summary(lm(y~x))

##
## Call:
## lm(formula = y ~ x)
##
## Residuals:
##      Min       1Q   Median       3Q      Max
## -26.6471  -7.6728  -0.3309   8.2743  19.8794
##
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)
## (Intercept)   27.647      6.387   4.329 0.000596 ***
## x             11.737      1.362   8.619  3.4e-07 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 13.75 on 15 degrees of freedom
## Multiple R-squared:  0.832,  Adjusted R-squared:  0.8208
## F-statistic: 74.29 on 1 and 15 DF,  p-value: 3.398e-07

Die Regressionsgerade beschreibt die Daten nicht gut, aber das R² des linearen Modells ist mit 0,832 recht hoch. Das Modell beschreibt also 83% der vorhandenen Daten.

Versuchen wir nun, die Funktion dritten Grades ins Modell zu übernehmen. Wie bei den quadratischen Funktionen müssen wir die Werte für x³ und x² in eigene Vektoren schreiben, die wir dann als Prädiktoren innerhalb von lm() übergeben.

# erzeuge Werte für x^3 und x^2
x3 <- x*x*x
x2 <- x^2

# Modell fitten
fit <- lm(y ~ x3 + x2 +x)
summary(fit)

##
## Call:
## lm(formula = y ~ x3 + x2 + x)
##
## Residuals:
##    Min     1Q Median     3Q    Max
## -7.038 -4.905  1.872  4.254  5.005
##
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)
## (Intercept)  -2.0945     4.0612  -0.516    0.615
## x3            0.9771     0.1101   8.876 7.04e-07 ***
## x2          -12.6470     1.3414  -9.428 3.55e-07 ***
## x            55.5108     4.5344  12.242 1.64e-08 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 5.14 on 13 degrees of freedom
## Multiple R-squared:  0.9797, Adjusted R-squared:  0.975
## F-statistic: 208.8 on 3 and 13 DF,  p-value: 3.038e-11

Das R² liegt bei 0,9797, was bedeutet, dass knapp 98% der Daten durch unser Modell erklärt werden können. Dieses Modell ist also wesentlich besser als das zuvor berechnete lineare Modell. Die Modellformel lautet y=0,9772⋅x3−12,647⋅x2+55,5108⋅x−2,0945y = 0,9772\cdot x^3 - 12,647\cdot x^2 + 55,5108\cdot x - 2,0945.

Um die Funktionskurve des Modells in das Plot zu zeichnen, erstellen wir zunächst einen neuen Vektor, der kontinuierliche Werte für x enthält. Über die Funktion predict() können dann die Modellwerte für y berechnet werden.

# Erstelle kontinuierliche Werte
# für x von 0 bis 8
xref <- seq(0, 8, 0.1)
# berechne dazugehörige y-Modellwerte
yref <- predict(fit, list(x = xref,
                          x2 = xref^2,
                          x3 = xref*xref*xref))

# Plotten
plot(x, y, col="blue")
# Modell-Parabel einzeichnen
lines(xref, yref, col='darkgreen')
abline(lm(y~x), col="red", lty=3 )

33.1.2.2 Exponentialfunktion

Wird ein exponentieller Zusammenhang vermutet, gehen wir analog zur quadratischen Regression vor.

In diesem Beispiel nehmen wir an, der Zusammenhang ließe sich vereinfacht durch die Formel y=axy = a^x beschreiben. Die Daten verhalten sich wie folgt:

# Testdaten
x <- c(2, 4, 5, 6, 7, 9, 10, 11, 13, 14, 16, 17, 18, 20, 21, 22, 22, 23, 24, 24, 25)
y <- c(1, 2, 2, 1, 3, 3, 2, 3, 2, 5, 4, 7, 10, 7, 15, 12, 18, 20, 17, 19, 20)

# plotten
plot(x, y, col="blue")
# falsche Regressionsgerade
abline(lm(y~x), col="red", lty=3)

# lineares Modell
summary(lm(y~x))

##
## Call:
## lm(formula = y ~ x)
##
## Residuals:
##     Min      1Q  Median      3Q     Max
## -5.5601 -2.2566  0.3153  3.0118  4.8952
##
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)
## (Intercept) -4.40475    1.60736  -2.740    0.013 *
## x            0.84824    0.09688   8.756 4.27e-08 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 3.236 on 19 degrees of freedom
## Multiple R-squared:  0.8014, Adjusted R-squared:  0.7909
## F-statistic: 76.67 on 1 and 19 DF,  p-value: 4.275e-08

Die Regressionsgerade beschreibt die Daten nicht gut, aber das R² des linearen Modells ist mit 0,8014 recht hoch. Das Modell beschreibt also 80% der vorhandenen Daten.

Versuchen wir nun, die Exponentialfunktion ins Modell zu übernehmen. Hierfür fitten wir das Modell nicht auf y, sondern auf den natürlichen Logarithmus yon y.

# Modell fitten
fit <- lm(log(y) ~ x)
summary(fit)

##
## Call:
## lm(formula = log(y) ~ x)
##
## Residuals:
##      Min       1Q   Median       3Q      Max
## -0.73254 -0.10440  0.01856  0.24804  0.44867
##
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)
## (Intercept) -0.25510    0.16442  -1.551    0.137
## x            0.12929    0.00991  13.047 6.23e-11 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.331 on 19 degrees of freedom
## Multiple R-squared:  0.8996, Adjusted R-squared:  0.8943
## F-statistic: 170.2 on 1 and 19 DF,  p-value: 6.232e-11

Mit R² = 0,8996 beschreibt das Modell die Daten besser, als das zuvor erstellte lineare Modell. Die Funktionsformel lautet ln(y)=0,12929⋅x−0,2551ln(y) = 0,12929\cdot x -0,2551.

Um die Funktionskurve des Modells in das Plot zu zeichnen, erstellen wir zunächst einen neuen Vektor, der kontinuierliche Werte für x enthält. Über die Funktion predict() können dann die Modellwerte für y berechnet werden. Da wir das Modell auf log(y) gefittet haben, müssen die Werte von predict() mittels exp() umgewandelt werden.

# Erstelle kontinuierliche Werte
# für x von 0 bis 8
xref <- seq(0, 25, 0.1)
# berechne dazugehörige y-Modellwerte
yref <- exp(predict(fit, data.frame(x = xref)))

# Plotten
plot(x, y, col="blue")
# Modell-Parabel einzeichnen
lines(xref, yref, col='darkgreen')
abline(lm(y~x), col="red", lty=3 )

33.2 Generalisierte lineare Modelle

Mit der Funktion glm() können generalisierte lineare Modelle gebildet werden. Strenggenommen können alle Regressionsmodelle mit glm() erzeugt werden. In R wird sie vor allem verwendet für

Poisson-Regressionen

logistische Regressionen

binomiale Regressionen

Das Standardvorgehen ist in allen Fällen gleich, das gefittete Modell wird in ein Objekt geschrieben

fit <- glm(ZIEL ~ Prädiktor1 + Prädiktor2, family= gaussian)

wobei über den Parameter family die Art des Modells festgelgt wird (z.B. binomial, poisson oder gaussian).

Anschließend werden typischerweise die folgenden Befehle angewendet:

# Modellübersicht
summary(fit)

# Koeffizientenwerte
coef(fit)
#oder
fit$coefficients

# Konfidenzintervalle der Koeffizienten
confint(fit)

# Residuen / Devianz
residuals(fit, type="deviance")

# Vorhersagewerte
predict(fit, type="response")

33.2.1 lineare Regression

Für lineare Regressionen haben wir die Funktion lm() verwendet. Tatsächlich könnten wir die Modelle auch per gml() erstellen. Kommen wir zurück zu dem Beispiel des Lese- und Rechtschreibetests.

# erzeuge Daten
Lesetest <- c(2 , 12 , 7 , 15 , 10 , 4 , 13 , 9 , 16 , 19)
Rechtschreibung <- c(3 , 14 , 9 , 17 , 12 , 4 , 16 , 12 , 18 , 20)

# lineare Regression "Rechtschreibung erklärt durch Lesetest"
summary(lm(Rechtschreibung~Lesetest))

##
## Call:
## lm(formula = Rechtschreibung ~ Lesetest)
##
## Residuals:
##      Min       1Q   Median       3Q      Max
## -1.42907 -0.26211  0.04498  0.36332  1.29412
##
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)
## (Intercept)  1.20761    0.67774   1.782    0.113
## Lesetest     1.05536    0.05718  18.458 7.65e-08 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.9221 on 8 degrees of freedom
## Multiple R-squared:  0.9771, Adjusted R-squared:  0.9742
## F-statistic: 340.7 on 1 and 8 DF,  p-value: 7.648e-08

Der Aufruf in glm() erfolgt analog, wobei wir den Parameter family auf gaussian stellen müssen.

# fitte mit glm()
fit <- glm(Rechtschreibung~Lesetest, family = gaussian)
summary(fit)

##
## Call:
## glm(formula = Rechtschreibung ~ Lesetest, family = gaussian)
##
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)
## (Intercept)  1.20761    0.67774   1.782    0.113
## Lesetest     1.05536    0.05718  18.458 7.65e-08 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for gaussian family taken to be 0.850346)
##
##     Null deviance: 296.5000  on 9  degrees of freedom
## Residual deviance:   6.8028  on 8  degrees of freedom
## AIC: 30.526
##
## Number of Fisher Scoring iterations: 2

Die errechneten Parameter stimmen mit lm() überein. Es fällt auf, dass glm() keinen Wert für R² zeigt, dafür aber einen AIC-Wert liefert.

Eine alternative Modellübersicht bietet die Funktion summ() (mit 2 m) aus dem Paket jtools.

jtools::summ(fit, digits=4)

Observations | 10
Dependent variable | Rechtschreibung
Type | Linear regression

𝛘²(1) | 289.6972
Pseudo-R² (Cragg-Uhler) | 0.9790
Pseudo-R² (McFadden) | 0.6062
AIC | 30.5262
BIC | 31.4340

| Est. | S.E. | t val. | p
(Intercept) | 1.2076 | 0.6777 | 1.7818 | 0.1126
Lesetest | 1.0554 | 0.0572 | 18.4576 | 0.0000
Standard errors: MLE |  |  |  |

33.3 Poisson-Regression

Die Poisson-Regression wird beispielsweise verwendet, um die Anzahl an Ereignissen (Kunden in der Schlange, Patienten in der Notaufnahme) auf ihre Abhängikeit zu bestimmten Prädiktoren zu untersuchen.

Wir verwenden den Testdatensatz “Notaufnahme.txt”, der als Texttabelle im Internet verfügbar ist.

df <- read.table(url("https://www.produnis.de/R/data/Notaufnahme.txt"),
                 header=TRUE, sep=",")

Er besteht aus 350 Beobachtungen der Patientenanzahl pro Tag in einer Notaufnahme. Zusätzlich wurden in der Spalte Wochentag die Wochentage erhoben. In Spalte Event wurde dichtotom erfasst, ob eine “größere” Veranstaltung in der Stadt stattgefunden hat.

# anzeigen
head(df)

##   Patienten Wochentag Event
## 1         8        Mo  nein
## 2         9        Di  nein
## 3        14        Mi  nein
## 4        10        Do  nein
## 5        10        Fr  nein
## 6        15        Mo  nein

Untersuchen wir nun, inwieweit der Wochentag und das Stattfinden einer größeren Veranstaltung die Anzahl der Notfälle beeinflusst. Das Modell kann wie gewohnt mit glm() erstellt werden, wobei wir den Parameter family=poisson setzen müssen.

# Poissonregressionsmodell erzeugen
fit <- glm(Patienten ~ Wochentag + Event, data = df, family = poisson)

# Zusammenfassung des Modells
summary(fit)

##
## Call:
## glm(formula = Patienten ~ Wochentag + Event, family = poisson,
##     data = df)
##
## Coefficients:
##              Estimate Std. Error z value Pr(>|z|)
## (Intercept)  2.528011   0.050672  49.889  < 2e-16 ***
## WochentagDo  0.071575   0.057063   1.254   0.2097
## WochentagFr  0.050956   0.057349   0.889   0.3743
## WochentagMi  0.119094   0.056421   2.111   0.0348 *
## WochentagMo  0.003367   0.058026   0.058   0.9537
## WochentagSa  0.429869   0.065146   6.599 4.15e-11 ***
## WochentagSo  0.365318   0.066489   5.494 3.92e-08 ***
## Eventnein   -0.292176   0.037640  -7.762 8.33e-15 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for poisson family taken to be 1)
##
##     Null deviance: 512.02  on 349  degrees of freedom
## Residual deviance: 345.57  on 342  degrees of freedom
## AIC: 1830.7
##
## Number of Fisher Scoring iterations: 4

Das Modell zeigt an, dass die Patientenzahl signifikant an den Wochentagen “Sa” und “So” erhöht ist. Auch der Prädiktor Event ist signifikant. Der negative Prädiktorenwert bei “Eventnein” gibt an, dass sich die Patientenzahl verringert, wenn kein Event stattfindet. In der Modellzusammenfassung ist “Event=ja” der Bezugswert, so dass der Schätzwerte für “Event=nein” ausgegeben wird. Schöner anzusehen wäre es genau andersherum. Um “Event=nein” als Basiswert zu setzen, und den Schätzwert für “Event=ja” anzuzeigen, können wir die Events in einen Factor umwandeln, und dann mittels relevel() den Wert “nein” als Bezugspunkt setzen.

# Wir wollen Event="nein" als Basis
df$Event <- factor(df$Event)
df$Event <- relevel(df$Event, "nein")

# Poissonregressionsmodell neu erzeugen
fit <- glm(Patienten ~ Wochentag + Event, data = df, family = poisson)

# Zusammenfassung des Modells
summary(fit)

##
## Call:
## glm(formula = Patienten ~ Wochentag + Event, family = poisson,
##     data = df)
##
## Coefficients:
##             Estimate Std. Error z value Pr(>|z|)
## (Intercept) 2.235835   0.041828  53.453  < 2e-16 ***
## WochentagDo 0.071575   0.057063   1.254   0.2097
## WochentagFr 0.050956   0.057349   0.889   0.3743
## WochentagMi 0.119094   0.056421   2.111   0.0348 *
## WochentagMo 0.003367   0.058026   0.058   0.9537
## WochentagSa 0.429869   0.065146   6.599 4.15e-11 ***
## WochentagSo 0.365318   0.066489   5.494 3.92e-08 ***
## Eventja     0.292176   0.037640   7.762 8.33e-15 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for poisson family taken to be 1)
##
##     Null deviance: 512.02  on 349  degrees of freedom
## Residual deviance: 345.57  on 342  degrees of freedom
## AIC: 1830.7
##
## Number of Fisher Scoring iterations: 4

Eine alternative Übersicht bietet die Funktion summ() (mit 2 m) aus dem Paket jtools.

jtools::summ(fit, digits=4)

Observations | 350
Dependent variable | Patienten
Type | Generalized linear model
Family | poisson
Link | log

𝛘²(7) | 166.4510
Pseudo-R² (Cragg-Uhler) | 0.3798
Pseudo-R² (McFadden) | 0.0840
AIC | 1830.7077
BIC | 1861.5712

| Est. | S.E. | z val. | p
(Intercept) | 2.2358 | 0.0418 | 53.4531 | 0.0000
WochentagDo | 0.0716 | 0.0571 | 1.2543 | 0.2097
WochentagFr | 0.0510 | 0.0573 | 0.8885 | 0.3743
WochentagMi | 0.1191 | 0.0564 | 2.1108 | 0.0348
WochentagMo | 0.0034 | 0.0580 | 0.0580 | 0.9537
WochentagSa | 0.4299 | 0.0651 | 6.5986 | 0.0000
WochentagSo | 0.3653 | 0.0665 | 5.4944 | 0.0000
Eventja | 0.2922 | 0.0376 | 7.7624 | 0.0000
Standard errors: MLE |  |  |  |

Die Werte der Koeffizienten sind als Log-Odds dargestellt. Eine bessere Lese- und Interpretierbarkeit bietet die Darstellung als Odds-Ratio. Hierfür muss der Logarithmus mittels exp() aufgehoben werden.

# Zeige die Koeffizienten als Odds-Ratio
exp(fit$coefficients)

## (Intercept) WochentagDo WochentagFr WochentagMi WochentagMo WochentagSa
##    9.354288    1.074199    1.052277    1.126476    1.003373    1.537056
## WochentagSo     Eventja
##    1.440972    1.339339

Die Konfidenzintervalle der Prädiktorenwerte erhalten wir mit der Funktion confint().

confint(fit)

##                    2.5 %    97.5 %
## (Intercept)  2.152783594 2.3167746
## WochentagDo -0.040220978 0.1835209
## WochentagFr -0.061421948 0.1634414
## WochentagMi  0.008603452 0.2298270
## WochentagMo -0.110388958 0.1171302
## WochentagSa  0.301707989 0.5571528
## WochentagSo  0.234302393 0.4950263
## Eventja      0.217989976 0.3655505

Bzw. mit vorgeschalteter exp()-Funktion, um die Werte als Odds-Ratio auszugeben.

# Konfidenzintervalle als Odds Ratio
exp(confint(fit))

##                 2.5 %    97.5 %
## (Intercept) 8.6087884 10.142906
## WochentagDo 0.9605771  1.201440
## WochentagFr 0.9404263  1.177556
## WochentagMi 1.0086406  1.258382
## WochentagMo 0.8954858  1.124266
## WochentagSa 1.3521663  1.745695
## WochentagSo 1.2640267  1.640541
## Eventja     1.2435746  1.441307

Die Funktion tab_model() aus dem sjPlot-Paket erstellt ihrerseits eine schöne Übersichtstabelle, wobei die Werte in Odds-Ratios angegeben und die Konfidenzintervalle enthalten sind.

sjPlot::tab_model(fit)

| Patienten
Predictors | Incidence Rate Ratios | CI | p
(Intercept) | 9.35 | 8.61 – 10.14 | <0.001
Wochentag [Do] | 1.07 | 0.96 – 1.20 | 0.210
Wochentag [Fr] | 1.05 | 0.94 – 1.18 | 0.374
Wochentag [Mi] | 1.13 | 1.01 – 1.26 | 0.035
Wochentag [Mo] | 1.00 | 0.90 – 1.12 | 0.954
Wochentag [Sa] | 1.54 | 1.35 – 1.75 | <0.001
Wochentag [So] | 1.44 | 1.26 – 1.64 | <0.001
Event [ja] | 1.34 | 1.24 – 1.44 | <0.001
Observations | 350
R² Nagelkerke | 0.493

Um die Güte des Modells zu bestimmen, können nun weitere Berechnungen vorgenommen werden.

33.3.1 Dispersion

Die Equidistation (Dispersion) des Modells sollte zwischen 0,90,9 und 1,11,1 liegen. Um sie zu bestimmen teilen wir die Residualdeviance durch die Anzahl der Feiheitsgrade.

# berechne Dispersions-Ratio
fit$deviance / fit$df.residual

## [1] 1.010433

Unser Wert liegt innerhalb des Bereichs, d.h. das Modell ist nicht überdispersioniert. Mit der Funktion dispersiontest() aus dem AER-Paket lässt sich zusätzlich ein Signifikanztest auf Überdispersion fahren. Die Nullhypothese besagt, das keine Überdispersion vorliegt.

AER::dispersiontest(fit, trafo=1)

##
##  Overdispersion test
##
## data:  fit
## z = -0.64112, p-value = 0.7393
## alternative hypothesis: true alpha is greater than 0
## sample estimates:
##       alpha
## -0.04479655

Der Test ist nicht signifikant, es liegt keine Überdispersion vor.

Sollte in dem Modell Überdispersion vorliegen, erfolgt der Aufruf von gml() mit dem Parameter family=quasipoisson.

glm(Patienten ~ Wochentag + Event, data = df, family = quasipoisson)

33.3.2 Nullmodell

Mit einem Nullmodell können wir testen, ob unser gefittetes Modell die Daten besser erklären kann. Hierfür ersetzen wir alle Prädiktoren durch die Ziffer 11.

# erstelle Null-Modell
fit0 <- glm(Patienten ~ 1, data = df, family = poisson)

Vergleichen wir die AIC-Werte beider Modelle.

# Ziehe von unserem Modell-AIC den Nullmodell-AIC ab
fit$aic - fit0$aic

## [1] -152.451

Das Ergebnis ist negativ. Das beudetet, dass der AIC-Wert des Nullmodells größer ist. Somit ist unser AIC-Modellwert kleiner, und das bedeutet, dass es besser ist als das Nullmodell.

Zusätzlich können wir die beiden Modelle mittels Varianzanalyse (siehe Abschnitt 33.8) untersuchen, wobei wir als Testeinstellung den Chi²-Test wählen.

# ANOVA auf beide Modelle mittels Chi^2-Test
anova(fit0, fit, test="Chisq")

## Analysis of Deviance Table
##
## Model 1: Patienten ~ 1
## Model 2: Patienten ~ Wochentag + Event
##   Resid. Df Resid. Dev Df Deviance  Pr(>Chi)
## 1       349     512.02
## 2       342     345.57  7   166.45 < 2.2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Unser gefittetes Modell erklärt die Daten signifikant besser als das Nullmodell.

33.3.3 Vorhersagen

Mit der Funktion predict() können wir nun die wahrscheinliche Patientenzahl für Kombinationen aus Wochentag und Event vorhersagen.

# Mit wievielen Patienten ist an einem
# Dienstag mit Event in der Stadt zu rechnen?
predict(fit, data.frame(Wochentag="Di", Event="ja"), type = "response")

##        1
## 12.52856

# ohne Label "1" ausgeben
as.numeric(predict(fit, data.frame(Wochentag="Di", Event="ja"),
                   type = "response"))

## [1] 12.52856

Um gut vorbereitet zu sein, runden wir die Patientenzahl mittels ceiling() auf die nächste ganze Zahl auf.

as.numeric(ceiling(predict(fit, data.frame(Wochentag="Di", Event="ja"),
                           type = "response")))

## [1] 13

Wir können davon ausgehen, dass an einem Dienstag mit Event in der Stadt etwa 13 Patienten in die Notaufnahme kommen werden.

Die Wahrscheinlichkeiten für verschiedene Patientenanzahlen an beliebigen Wochentagen mit oder ohne Stadtevent lassen sich mit der Funktion ppois() bestimmen (siehe Abschnitt 18.4).

# Lambda-Wert für "Dienstag" und Event="ja"
Modelllambda <- predict(fit, data.frame(Wochentag="Di", Event="ja"),
                        type = "response")

## Wahrscheinlichkeit, dass an einem Dienstag mit Event...
## - bis zu 16 Patienten eingeliefert werden (nicht mehr als 16)
ppois(16, Modelllambda)

## [1] 0.8674935

## - mindestens 16 Patienten eingeliefert werden (16 oder mehr)
1 - ppois(16, Modelllambda)

## [1] 0.1325065

## - exakt 16 Patienten eingeliefert werden
ppois(16, Modelllambda) - ppois(15, Modelllambda)

## [1] 0.0637844

So können wir einen Plot erstellen, auf dem die Wahrscheinlichkeiten angegeben sind, dass an einem Dienstag mit oder ohne Event in der Stadt mindestens x Patienten aufgenommen werden.

# Lambda für Event="nein"
Modelllambda_ohne <- predict(fit, data.frame(Wochentag="Di", Event="nein"),
                             type = "response")

# Patienten N 1 bis 25
x <- 1:25

# plotten für Event="ja"
plot(x, 1-ppois(x, Modelllambda),
     type="b", col="blue",
     main = "Wahrscheinlichkeiten für Mindestpatientenanzahl",
     sub="an einem Dienstag mit und ohne Event ",
     ylab="Wahrscheinlichkeit",
     xlab="Mindestanzahl Patienten",  xaxt = "n")
# X-Achsenticks in 2er-Schritten
axis(1, at=seq(0, 25, 2))
# füge Punktlinie für Event="nein" hinzu
lines(x, 1-ppois(x, Modelllambda_ohne),
      type="b", col="red")
# füge Hilfslinie hinzu
abline(h=.5, lty=5, col="grey65")
# füge Legendenbox hinzu
legend(x="topright", inset=0.005,
       legend=c("ohne Event", "mit Event"),
       col=c("red", "blue"), lwd=6, bg="grey95")

Das geht ebenso für die Wahrscheinlichkeiten der exakten Patientenanzahl.

# Wahrscheinlichkeit für exakte Patienten-N mit Event="ja"
y1 <- ppois(x, Modelllambda) - ppois(x-1, Modelllambda)
# Wahrscheinlichkeit für exakte Patienten-N mit Event="nein"
y2 <- ppois(x, Modelllambda_ohne) - ppois(x-1, Modelllambda_ohne)

# plotte für Event="nein"
plot(x, y2, type="b",  col="red",
     main = "Wahrscheinlichkeiten für exakte Patientenanzahl",
     sub="an einem Dienstag mit und ohne Event ",
     ylab="Wahrscheinlichkeit",
     xlab="Anzahl Patienten",  xaxt = "n")
# X-Achsenticks in 2er-Schritten
axis(1, at=seq(0, 25, 2))
# Hilfslinie für Event="nein"
abline(v=9, lty=5, col="pink", lwd=2)
# Punktlinie für Event="ja" einzeichnen
lines(x, y1, type="b", col="blue")
# Hilfslinie für Event="ja"
abline(v=12, lty=5, col="paleturquoise", lwd=2)
# Legendenbox hinzufügen
legend(x="topright", inset=0.005,
       legend=c("ohne Event", "mit Event"),
       col=c("red", "blue"), lwd=6, bg="grey95")

33.4 Logistische Regression

Als Beispiel dienen Daten der Challenger-Katastrophe (am 28. Januar 1986 explodierte die Raumfähre Challenger 73 Sekunden nach dem Start).

Der Ausfall eines oder mehrerer Dichtungsringe in einer der seitlichen Feststoffrakten wurde als Grund der Katastrophe ermittelt. Probleme mit den Dichtungsringen sind auch vor- her am Boden aufgetreten. So liegen Erhebungsdaten vor, die den Defekt ei- nes Rings in Zusammenhang mit der Außentemperatur darstellen können: Je kälter es draußen ist, desto höher die Ausfallwahrscheinlichkeit eines Rings. Bedauerlicher Weise wurden diese Daten erst rückwirkend ausgewertet. Diese Daten übertragen wir wie folgt in R:

Temp <- c(66, 67, 68, 70, 72, 75, 76, 79, 53, 58, 70, 75, 67, 67, 69, 70, 73, 76, 78, 81, 57, 63, 70)
Ausfall <- factor(c(0, 0,0 ,0 ,0 ,0 ,0 ,0 ,1 ,1 ,1 ,1 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,1 ,1, 1))
shuttle <- data.frame(Temp, Ausfall)
colnames(shuttle) <- c("Temp", "Ausfall")

Die Kodierung des factor Ausfall lautet: 0=kein Ausfall | 1=Ausfall Die Einheit für Temp lautet Fahrenheit.

Der Aufruf zur logistischen Regression (Ausfall erklärt durch Temp) lautet:

fit <- glm(Ausfall~Temp,data=shuttle, family=binomial)
summary(fit)

##
## Call:
## glm(formula = Ausfall ~ Temp, family = binomial, data = shuttle)
##
## Coefficients:
##             Estimate Std. Error z value Pr(>|z|)
## (Intercept)  15.0429     7.3786   2.039   0.0415 *
## Temp         -0.2322     0.1082  -2.145   0.0320 *
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
##     Null deviance: 28.267  on 22  degrees of freedom
## Residual deviance: 20.315  on 21  degrees of freedom
## AIC: 24.315
##
## Number of Fisher Scoring iterations: 5

Die Odds Ratio (OR) für β\betaTemp= -0,2322 errechnet sich mit der exp()-Funktion:

exp(-0.2322)

## [1] 0.7927875

Dies liefert das Ergebnis OR = 0.7927875. Die Interpretation lautet: Mit jedem höhren Grad an Temparatur ändert sich die Chance auf einem Ausfall um den Faktor 0.7927. Das bedeutet, dass die Chancen eines Ausfalls niedriger werden, je höher die Außentemperatur steigt.

In der Nacht vor dem Challengerstart konnte eine Temperatur von 37° Fahrenheit gemessen werden. Auf Grundlage der logistischen Regression lässt sich nun die Ausfallwahrscheinlichkeit bei 37° Außentemperatur berechnen:

predict(fit, data.frame(Temp=37),type="response")

##         1
## 0.9984265

Das Modell sagt bei einer Außentemperatur von 37° eine Ausfallwahrscheinlichkeit von 99,84% voraus.

Jetzt kann man dies für jede mögliche Temperatur durchmachen, und schon erhält man dieses schöne Plot:

y <- predict(fit, data.frame(Temp=30:90), type="response")
plot(30:90, y, type="l", ylab="Ausfallswahrscheinlichkeit", xlab="Temperatur in F")
abline(v=seq(20,100,1), h=seq(-0.2,1.2,0.05),col="gray") #Mathegitter
abline(v=37, col="red") # 37-Grad
abline(h=0.5, col="black") # Mittellinie
points(30:90, y, type="l",lwd=2, col="blue") # Schätzwerte in "blau"

33.5 Ordinale Regression

Zur Bestimmung des Einflusses von Prädiktoren auf ein ordinales Ziel stehen verschiedene Modelle der ordinalen Regression zur Verfügung. Namentlich werden hier vor allem Proportional Odds Modelle und Continuation Ratio Modelle verwendet.

Eine verständliche Einführung in ordinale Modelle findet sich im Open Access Artikel von große Schlarmann und Galatsch (große Schlarmann & Galatsch, 2014).

Für die ordinalen Regressionen verwenden wir das “VGAM”, welches zunächst in R installiert werden muss

install.packages("VGAM", dependencies=T)

Als Beispiel dient der folgende Datensatz:

load(url("http://www.produnis.de/R/data/OrdinalSample.RData"))
mydata <- ordinalSample
head(mydata)

##    Konflikt Zufriedenh Geschlecht Stimmung
## 1       4.2       2.50          1  maessig
## 6       1.8       3.00          1 sehr gut
## 15      3.4       1.75          1  maessig
## 16      4.0       2.50          2  maessig
## 22      2.2       2.50          1      gut
## 23      3.4       2.25          1  maessig

Die Variable “Stimmung” dient hier als ordinales Ziel mit den Ausprägungen “schlecht” < “maessig” < “gut” < “sehr gut”.

Die Variable “Konflikt” dient als Prädiktor. Sie gibt an, wieviele Konflikte derzeit im Arbeitsleben vorliegen.

Die Variable “Zufriedenh” beschreibt, wie zufrieden Probanden mit ihrem Job sind.

Die Variable “Stimmung” soll nun durch “Konflikt” und “Zufriedenh” beschrieben werden.

Wir wollen wissen, wie stark Stimmung durch Konflikt und Zufriedenh beinflusst wird. Das heisst, in unserem Modell soll die Variable Stimmung durch Konflikt und Zufriedenh beschrieben werden.

33.5.1 Proportional Odds Modell

Ein Proportional Odds Modell errechnet sich leicht mit dem VGAM-Paket und der Funktion vglm():

library(VGAM)

pom <- vglm(Stimmung ~ Konflikt+Zufriedenh, data=mydata, family=cumulative(parallel=T))

# oder abgekürzt
pom <- vglm(Stimmung ~ Konflikt+Zufriedenh, data=mydata, family=propodds)
summary(pom)

##
## Call:
## vglm(formula = Stimmung ~ Konflikt + Zufriedenh, family = propodds,
##     data = mydata)
##
## Coefficients:
##               Estimate Std. Error z value Pr(>|z|)
## (Intercept):1   0.6210     0.6482   0.958   0.3381
## (Intercept):2  -1.7703     0.6540  -2.707   0.0068 **
## (Intercept):3  -4.0578     0.6759  -6.004 1.93e-09 ***
## Konflikt       -0.5762     0.0970  -5.940 2.86e-09 ***
## Zufriedenh      1.3643     0.2021   6.751 1.47e-11 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Names of linear predictors: logitlink(P[Y>=2]), logitlink(P[Y>=3]),
## logitlink(P[Y>=4])
##
## Residual deviance: 920.5493 on 1240 degrees of freedom
##
## Log-likelihood: -460.2746 on 1240 degrees of freedom
##
## Number of Fisher scoring iterations: 5
##
## No Hauck-Donner effect found in any of the estimates
##
##
## Exponentiated coefficients:
##   Konflikt Zufriedenh
##  0.5620549  3.9129275

Mit dem Modell können nun weitere Parameter errechnet werden:

# Koeffizienten
pom.coef <- (coef(summary(pom)));pom.coef

##                 Estimate Std. Error    z value     Pr(>|z|)
## (Intercept):1  0.6209962 0.64821816  0.9580049 3.380603e-01
## (Intercept):2 -1.7702692 0.65404383 -2.7066523 6.796539e-03
## (Intercept):3 -4.0577657 0.67586741 -6.0037895 1.927645e-09
## Konflikt      -0.5761558 0.09700358 -5.9395317 2.858375e-09
## Zufriedenh     1.3642858 0.20208974  6.7508909 1.469400e-11

Die Odds Ratio errechnet sich mit der exp()-Funktion:

# Odds Ratio
pom.odds <- exp(coef(pom));pom.odds

## (Intercept):1 (Intercept):2 (Intercept):3      Konflikt    Zufriedenh
##     1.8607808     0.1702871     0.0172876     0.5620549     3.9129275

# Devianz und AIC
pom.devi <- deviance(pom);pom.devi

## [1] 920.5493

pom.aic  <- AIC(pom);pom.aic

## [1] 930.5493

# logLikelihood
pom.ll <- logLik(pom);pom.ll

## [1] -460.2746

# 0-Modell (fuer pseudo R^2)
p0 <- vglm(Stimmung ~ 1, data=mydata, family=propodds)
p0.ll <- logLik(p0)
# R^2 McFadden
pom.mcfad <- as.vector(1- (pom.ll/p0.ll));pom.mcfad

## [1] 0.1240613

# R^2 Cox&Snell
N <- length(mydata[,1]) # Anzahl der Fälle
pom.cox <- as.vector(1 - exp((2/N) * (p0.ll - pom.ll)));pom.cox

## [1] 0.2696034

# R^2 Nagelkerke
pom.nagel <- as.vector((1 - exp((2/N) * (p0.ll - pom.ll))) / (1 - exp(p0.ll)^(2/N))) ;pom.nagel

## [1] 0.2928789

Das Proportional Odds Modell geht von der “equal slopes assumption” (auch: “proportional odds assumption”) aus. Diese Annahme muss geprüft werden, bevor das Modell als gültig angesehen werden kann. Dies geht mit dem VGAM-Paket recht einfach. Der Befehl vglm(Stimmung ~ Konflikt+Zufriedenh, data=mydata, family=propodds) ist eine Abkürzung für vglm(Stimmung ~ Konflikt+Zufriedenh, data=mydata, family=cumulative(parallel=T)). Der Parameter parallel=TRUE/FALSE stellt ein, ob das Modell mit equal slopes (=TRUE), oder ohne equal slopes assumption (=FALSE) erstellt werden soll. Zur Überprüfung der “Equal Slopes Assumption” erstellt man jeweils ein TRUE und ein FALSE-Modell, und führt dann einen Likelihood-Test durch. Die Nullhypothese lautet, dass equal slopes vorliegen.

# Modell OHNE equal slopes
npom <- vglm(Stimmung ~ Konflikt+Zufriedenh, data=mydata, family=cumulative(parallel=F))

# log-likelihood-Test auf Equal Slopes Assumption
lrtest(pom,npom)# Test

## Likelihood ratio test
##
## Model 1: Stimmung ~ Konflikt + Zufriedenh
## Model 2: Stimmung ~ Konflikt + Zufriedenh
##    #Df  LogLik Df  Chisq Pr(>Chisq)
## 1 1240 -460.27
## 2 1236 -456.46 -4 7.6202     0.1065

Der Test ist mit p=0,1065 nicht signifikant. Das heisst, es liegen equal slopes vor. Das Modell darf also verwendet werden.

Alternativ kann dieser Test auch auf die Devianz der Modelle bestimmt werden:

# Devianz-Test auf Equal Slopes Assumption
pom.pdevi = (1 - pchisq(deviance(pom) - deviance(npom), df=df.residual(pom)-df.residual(npom)));pom.pdevi

## [1] 0.106526

Ist der Test signifikant, liegen keine “equal slopes” vor. Hier kann ein Partial Proportional Odds Modell erstellt werden, welches die “equal slopes” nur für bestimmte Prädiktoren annimmt. Mit dem VGAM-Paket kann recht einfach bestimmt werden, wie ein solches Modell erstellt werden soll. Hierfür erweitertet man den “parallel”-Switch wie folgt:

# Equal Slopes NUR für "Konflikt"
ppom <- vglm(Stimmung ~ Konflikt+Zufriedenh, data=mydata, family=cumulative(parallel=T~Konflikt-1));ppom

##
## Call:
## vglm(formula = Stimmung ~ Konflikt + Zufriedenh, family = cumulative(parallel = T ~
##     Konflikt - 1), data = mydata)
##
##
## Coefficients:
## (Intercept):1 (Intercept):2 (Intercept):3      Konflikt  Zufriedenh:1
##     0.6614816     1.5373699     2.7337389     0.5705897    -1.9684047
##  Zufriedenh:2  Zufriedenh:3
##    -1.2805318    -0.8865225
##
## Degrees of Freedom: 1245 Total; 1238 Residual
## Residual deviance: 914.4929
## Log-likelihood: -457.2464

# Nochmals EqualSlopes NUR für "Konflikt"
ppom <- vglm(Stimmung ~ Konflikt+Zufriedenh, data=mydata, family=cumulative(parallel=F~Zufriedenh));ppom

##
## Call:
## vglm(formula = Stimmung ~ Konflikt + Zufriedenh, family = cumulative(parallel = F ~
##     Zufriedenh), data = mydata)
##
##
## Coefficients:
## (Intercept):1 (Intercept):2 (Intercept):3      Konflikt  Zufriedenh:1
##     0.6614816     1.5373699     2.7337389     0.5705897    -1.9684047
##  Zufriedenh:2  Zufriedenh:3
##    -1.2805318    -0.8865225
##
## Degrees of Freedom: 1245 Total; 1238 Residual
## Residual deviance: 914.4929
## Log-likelihood: -457.2464

parallel=T~Konflikt-1 bedeutet, dass equal slopes nur für Konflikt angenommen wird.

parallel=F~Zufriedenh bedeutet, dass equal slopes nur für Zufriedenh nicht angenommen wird.

Beide Befehle bedeuten also das selbe. Daher ist die R-Ausgabe bei beiden Befehlen gleich.

Eine Koeffizientenübersicht erhält man per

ppom.ce <- coef(summary(ppom));ppom.ce

##                 Estimate Std. Error    z value     Pr(>|z|)
## (Intercept):1  0.6614816 0.85866412  0.7703613 4.410856e-01
## (Intercept):2  1.5373699 0.71997824  2.1353005 3.273647e-02
## (Intercept):3  2.7337389 0.98084390  2.7871294 5.317723e-03
## Konflikt       0.5705897 0.09715456  5.8730094 4.279541e-09
## Zufriedenh:1  -1.9684047 0.34535219 -5.6997024 1.200167e-08
## Zufriedenh:2  -1.2805318 0.23585229 -5.4293805 5.654999e-08
## Zufriedenh:3  -0.8865225 0.32542569 -2.7241933 6.445877e-03

Mit einem kleinen Script können Konfidenzintervalle und andere Werte ausgegeben werden:

get.ci <- function(x){
  back <-cbind( x[1], # estimate
                (x[1] - (1.96 * x[2])), #lCI
                (x[1] + (1.96 * x[2])), #uCI
                x[2], x[3], # SD und z
                (2*(1 -pnorm(abs(x[3]))) ),# p-wert
                exp(x[1]))
  colnames(back) <- c("Estimate", "lCI", "uCI","SD","z","p-value", "OR")
  return(back)
}

Der Aufruf erfolgt z.B. so:

get.ci(as.numeric(ppom.ce[4,]))

##       Estimate       lCI       uCI         SD        z      p-value      OR
## [1,] 0.5705897 0.3801667 0.7610126 0.09715456 5.873009 4.279541e-09 1.76931

33.5.2 Continuation Ratio Model

Um ein Continuation Ratio Modell zu rechnen, muss der Parameter family auf cratio gesetzt werden.

# CR-Modell MIT PO-Assumption  (Vorwärts)
crm<- vglm(Stimmung ~ Konflikt+Zufriedenh, data=mydata, family=cratio(parallel=T,reverse=F))
summary(crm)

##
## Call:
## vglm(formula = Stimmung ~ Konflikt + Zufriedenh, family = cratio(parallel = T,
##     reverse = F), data = mydata)
##
## Coefficients:
##               Estimate Std. Error z value Pr(>|z|)
## (Intercept):1  0.92610    0.57405   1.613   0.1067
## (Intercept):2 -1.14132    0.57924  -1.970   0.0488 *
## (Intercept):3 -3.01660    0.60706  -4.969 6.72e-07 ***
## Konflikt      -0.53645    0.08639  -6.209 5.32e-10 ***
## Zufriedenh     1.16010    0.17944   6.465 1.01e-10 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Names of linear predictors: logitlink(P[Y>1|Y>=1]), logitlink(P[Y>2|Y>=2]),
## logitlink(P[Y>3|Y>=3])
##
## Residual deviance: 924.3824 on 1240 degrees of freedom
##
## Log-likelihood: -462.1912 on 1240 degrees of freedom
##
## Number of Fisher scoring iterations: 6
##
## No Hauck-Donner effect found in any of the estimates
##
##
## Exponentiated coefficients:
##   Konflikt Zufriedenh
##  0.5848225  3.1902597

Dies berechnet standardmäßig die Vorwärts-Methode. Möchte man die Rückwärts-Methode rechnen, setzt man den Parameter reverse auf TRUE.

# CR-Modell MIT PO-Assumption (Rückwärts)
crm<- vglm(Stimmung ~ Konflikt+Zufriedenh, data=mydata, family=cratio(parallel=T,reverse=TRUE))
summary(crm)

##
## Call:
## vglm(formula = Stimmung ~ Konflikt + Zufriedenh, family = cratio(parallel = T,
##     reverse = TRUE), data = mydata)
##
## Coefficients:
##               Estimate Std. Error z value Pr(>|z|)
## (Intercept):1  0.12822    0.57259   0.224 0.822809
## (Intercept):2  2.08751    0.58094   3.593 0.000326 ***
## (Intercept):3  4.04245    0.60636   6.667 2.62e-11 ***
## Konflikt       0.45234    0.08488   5.329 9.88e-08 ***
## Zufriedenh    -1.25631    0.18108  -6.938 3.98e-12 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Names of linear predictors: logitlink(P[Y<2|Y<=2]), logitlink(P[Y<3|Y<=3]),
## logitlink(P[Y<4|Y<=4])
##
## Residual deviance: 920.4627 on 1240 degrees of freedom
##
## Log-likelihood: -460.2314 on 1240 degrees of freedom
##
## Number of Fisher scoring iterations: 5
##
## No Hauck-Donner effect found in any of the estimates
##
##
## Exponentiated coefficients:
##   Konflikt Zufriedenh
##  1.5719790  0.2847038

Hat man die passende Methode gewählt, folgen die selben Befehle wie beim Proportional Odds Modell. Zunächst muss überprüft werden, ob “equal slopes” vorliegen:

# CR-Modell OHNE PO-Assumption (Rückwärts)
ncrm<- vglm(Stimmung ~ Konflikt+Zufriedenh, data=mydata, family=cratio(parallel=F,reverse=T));summary(ncrm)

##
## Call:
## vglm(formula = Stimmung ~ Konflikt + Zufriedenh, family = cratio(parallel = F,
##     reverse = T), data = mydata)
##
## Coefficients:
##               Estimate Std. Error z value Pr(>|z|)
## (Intercept):1   2.3649     1.1740   2.014 0.043969 *
## (Intercept):2   1.9998     0.8117   2.464 0.013753 *
## (Intercept):3   2.4670     1.1190   2.205 0.027474 *
## Konflikt:1      0.1465     0.1811   0.809 0.418633
## Konflikt:2      0.4864     0.1194   4.073 4.65e-05 ***
## Konflikt:3      0.6308     0.1689   3.736 0.000187 ***
## Zufriedenh:1   -1.8008     0.3919  -4.596 4.32e-06 ***
## Zufriedenh:2   -1.2602     0.2578  -4.889 1.01e-06 ***
## Zufriedenh:3   -0.8351     0.3382  -2.469 0.013543 *
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Names of linear predictors: logitlink(P[Y<2|Y<=2]), logitlink(P[Y<3|Y<=3]),
## logitlink(P[Y<4|Y<=4])
##
## Residual deviance: 914.7924 on 1236 degrees of freedom
##
## Log-likelihood: -457.3962 on 1236 degrees of freedom
##
## Number of Fisher scoring iterations: 6
##
## No Hauck-Donner effect found in any of the estimates
##
##
## Exponentiated coefficients:
##   Konflikt:1   Konflikt:2   Konflikt:3 Zufriedenh:1 Zufriedenh:2 Zufriedenh:3
##    1.1577337    1.6264285    1.8791766    0.1651675    0.2836103    0.4338519

# loglikelihood test auf equal slopes assumption
lrtest(ncrm,crm)# Test

## Likelihood ratio test
##
## Model 1: Stimmung ~ Konflikt + Zufriedenh
## Model 2: Stimmung ~ Konflikt + Zufriedenh
##    #Df  LogLik Df  Chisq Pr(>Chisq)
## 1 1236 -457.40
## 2 1240 -460.23  4 5.6703     0.2252

Der Test ist nicht signifikant (p=0,23). Das bedeutet, dass die Annahme der equal slopes für die hier getesteten Rückwärts-Modelle beibehalten werden kann.

Nun können weitere Modellparameter bestimmt werden:

# 0-Modell (fuer pseudo R^2)
c0 <- vglm(Stimmung ~ 1, data=mydata, family=cratio(parallel=T))
c0.ll <- logLik(c0)
crm.ll <- logLik(crm)

# R^2 Nagelkerke
crm.nagel <- as.vector((1 - exp((2/N) * (c0.ll - crm.ll))) / (1 - exp(c0.ll)^(2/N))) ;crm.nagel

## [1] 0.2930443

# Devianz
crm.devi <- deviance(crm); crm.devi

## [1] 920.4627

# AIC
crm.aic  <- AIC(crm); crm.aic

## [1] 930.4627

Mit unserer Funktion von oben können wir uns die Modellparameter der Koeffizienten ausgeben lassen.

# Konfidenzinztervalle
crm.ce <- coef(summary(crm));crm.ce

##                 Estimate Std. Error    z value     Pr(>|z|)
## (Intercept):1  0.1282227 0.57259420  0.2239329 8.228095e-01
## (Intercept):2  2.0875126 0.58094099  3.5933299 3.264788e-04
## (Intercept):3  4.0424508 0.60636000  6.6667504 2.615293e-11
## Konflikt       0.4523353 0.08488357  5.3288915 9.881398e-08
## Zufriedenh    -1.2563060 0.18108066 -6.9378255 3.981811e-12

get.ci(as.numeric(crm.ce[4,]))

##       Estimate       lCI       uCI         SD        z      p-value       OR
## [1,] 0.4523353 0.2859635 0.6187071 0.08488357 5.328892 9.881398e-08 1.571979

get.ci(as.numeric(crm.ce[5,]))

##       Estimate       lCI        uCI        SD         z      p-value        OR
## [1,] -1.256306 -1.611224 -0.9013879 0.1810807 -6.937825 3.981704e-12 0.2847038

33.6 Konfidenzintervalle

Konfidenzintervalle folgen der Logik, dass zum Punktschätzwert ein gewisser Fehlerbereich addiert und subtrahiert wird. Hierdurch wird ein Intervall mit Unter- und Obergrenze aufgezogen, das mit einer bestimmten Irrtumswahrscheinlichkeit den “wahren Wert” der Grundgesamtheit enthält.

33.6.1 Normalverteilung

Um ein Konfidenzintervall für einen Mittelwert aus einer normalverteilten Wertemenge zu berechnen wird folgende Formel angewendet:

$$\begin{equation} \label{formel:konfidenznormal} \begin{array}{l l} \mu_{u}\\\mu_{o}\\ \end{array} \Big\}= \bar{x} \pm t \cdot \frac{s}{\sqrt{n}} \end{equation}$$

x‾\bar{x} = arithmetisches Mittel

nn = Stichprobenumfang

ss = Standardabweichung

tt = Wert der tt-Verteilung, (mit Freiheitsgraden df=n−1df = n-1)

Nehmen wir an, eine Zufalls-Erhebung über die jährlichen Ausgaben für Pflegehilfsmittel ergab bei 100 Befragten (n = 100) einen Mittelwert x‾\bar{x}=400,-€ bei einer Standardabweichung von s=20,-€. Das Konfidenzintervall für α\alpha=0.05 wird nun wie folgt bestimmt.

# schreibe gegebene Werte in Variablen
n     <- 100
xquer <- 400
s     <- 20

# 95%KI-Fehler berechnen mit (alpha/2)
fehler95 <- qt(0.975, df=n-1)*s/sqrt(n)

# Fehlerwert ausgeben
fehler95

## [1] 3.968434

Beachten Sie, dass in der Funktion qt() zur Bestimmung des kritischen t-Wertes die Irrtumswahrscheinlichkeit α\alpha halbiert werden muss, da es sich um eine zweiseitige Fragestellung handelt. So muss qt() nicht mit dem Wert 0.95 (α=0.05\alpha=0.05) aufgerufen werden, sondern mit 0.975 (α2=0.025\frac{\alpha}{2}=0.025).

# untere Grenze des 95%KI
xquer - fehler95

## [1] 396.0316

Die untere Grenze des 95%-Konfidenzintervalls liegt bei 396.0316 Euro.

# obere Grenze des 95%KI
xquer + fehler95

## [1] 403.9684

Die obere Grenze des 95%-Konfidenzintervalls liegt bei 403.9684 Euro.

# Länge des 95%KI
(xquer + fehler95) - (xquer - fehler95)

## [1] 7.936868

# oder schlauer
2*fehler95

## [1] 7.936868

Das 95%-Konfidenzintervall hat eine Länge von 7.936868 Euro.

Für das 99% Konfidenzintervall ändern wir entsprechend um. Auch hier muss, da es sich um eine zweiseitige Fragestellung handelt, mit α2\frac{\alpha}{2}, also 0.005, gerechnet werden. In der Funktion qt() ändert sich also der Wert auf 0,995.

# 99%KI-Fehler berechnen mit (alpha/2)
fehler99 <- qt(0.995, df=n-1)*s/sqrt(n)

# zeige fehler99 an
fehler99

## [1] 5.252811

# untere Grenze des 99%KI
xquer - fehler99

## [1] 394.7472

Die untere Grenze des 99%-Konfidenzintervalls liegt bei 394.7472 Euro.

# obere Grenze des 99%KI
xquer + fehler99

## [1] 405.2528

Die obere Grenze des 99%-Konfidenzintervalls liegt bei 405.2528 Euro.

# Länge des 99%KI
(xquer + fehler99) - (xquer - fehler99)

## [1] 10.50562

# oder schlauer
2*fehler99

## [1] 10.50562

Das 99%-Konfidenzintervall hat eine Länge von 10.50562 Euro.

Die Funktion KInormal_a() aus dem jgsbook-Paket nimmt uns die vorgestellten Rechenschritte ab.

# Berechne das 95%-KI für
# n=100, xquer=400 und s=20
jgsbook::KInormal_a(xquer=400, n=100, s=20, alpha=.05)

## Registered S3 method overwritten by 'statip':
##   method         from
##   predict.kmeans parameters

##                       x          y
## 1 0.95 KI untere Grenze 396.031566
## 2  0.95 KI obere Grenze 403.968434
## 3        0.95 KI Laenge   7.936868

# Berechne das 99%-KI für
# n=100, xquer=400 und s=20
jgsbook::KInormal_a(xquer=400, n=100, s=20, alpha=.01)

##                       x         y
## 1 0.99 KI untere Grenze 394.74719
## 2  0.99 KI obere Grenze 405.25281
## 3        0.99 KI Laenge  10.50562

33.6.1.1 Unterschied von Mittelwerten

Das Konfidenzintervall für den Unterschied von Mittelwerten normalverteilter Daten berechnet sich mit der Formel

$$\begin{equation} \begin{array}{l l} \mu_{u}\\\mu_{o}\\ \end{array} \Big\}= \hat{d} \pm t \cdot \sqrt{\frac{s_{pool}^2}{n_x}+\frac{s_{pool}^2}{n_y}} \end{equation}$$

d̂\hat{d} = Differenz der Mittelwerte x‾\bar{x},y‾\bar{y}

nx,nyn_{x}, n_{y} = die Umfänge der beiden Stichproben

spools_{pool} = gepoolte Standardabweichungen = (nx−1)⋅sx2+(ny−1)⋅sy2nx+ny−2\frac{(n_x-1)\cdot s_x^2\ +\ (n_y-1)\cdot s_y^2}{n_x + n_y -2}

tt = Wert der tt-Verteilung (mit Freiheitsgraden df=nx+ny−1df = n_x+n_y -1)

Nehmen wir an, in einer Studie wurde die Wirkung von zwei Medikamenten auf den Magnesiumgehalt des Blutserums (in mg/dl) untersucht. 13 Personen erhielten Medikament A und 10 Personen Medikament B. Nach 6 Wochen wurden folgende Daten bestimmt:

-x‾A\bar{x}_{A} = 2,22 mg/dl

x‾B\bar{x}_{B} = 2,70 mg/dl

sAs_{A} = 0,255 mg/dl

sBs_{B} = 0,306 mg/dl

nAn_{A} = 13

nBn_{B} = 10

Das Konfidenzintervall für den Unterschied der Mittelwerte bei α=0,05\alpha=0,05 wird nun wie folgt bestimmt (Achtung, da es sich um eine zweiseitige Fragestellung handelt, müssen wir mit α2=0,025\frac{\alpha}{2} =0,025 rechnen).

# erzeuge die Daten
xa <- 2.22
xb <- 2.7
sa <- 0.255
sb <- 0.306
na <- 13
nb <- 10

# Differenz der Mittelwerte
d   <- xb - xa

# ausgeben
d

## [1] 0.48

# berechne die gepoolten Standardabweichungen
s_pool = ((na-1)*sa^2 + (nb-1)*sb^2) / (na+nb-2)

# ausgeben
s_pool

## [1] 0.07728686

#Fehlerquote berechnen
fehler95 <- qt(0.975, df=na+nb-1) * sqrt(s_pool^2/na + s_pool^2/nb)

# ausgeben
fehler95

## [1] 0.06741865

# untere Grenze des 95%KI
d - fehler95

## [1] 0.4125813

Die untere Grenze des 95%-Konfidenzintervalls liegt bei 0.4125813 mg/dl.

# obere Grenze des 95%KI
d + fehler95

## [1] 0.5474187

Die obere Grenze des 95%-Konfidenzintervalls liegt bei 0.5474187 mg/dl.

# Länge des Intervalls
2*fehler95

## [1] 0.1348373

Das 95%-Konfidenzintervall hat eine Länge von 0.1348373 mg/dl.

Für das 99%-Konfidenzintervall wird der Wert für die Funktion qt() auf 0.995 gesetzt (da es sich um eine zweiseitige Fragestellung handelt, müssen wir mit α2=0,005\frac{\alpha}{2} = 0,005 rechnen).

#Fehlerquote berechnen
fehler99 <- qt(0.995, df=na+nb-1) * sqrt(s_pool^2/na + s_pool^2/nb)

# ausgeben
fehler99

## [1] 0.09163373

# untere Grenze des 99%KI
d - fehler99

## [1] 0.3883663

Die untere Grenze des 99%-Konfidenzintervalls liegt bei 0.3883663 mg/dl.

# obere Grenze des 99%KI
d + fehler99

## [1] 0.5716337

Die obere Grenze des 99%-Konfidenzintervalls liegt bei 0.5716337 mg/dl.

# Länge des Intervalls
2*fehler99

## [1] 0.1832675

Das 99%-Konfidenzintervall hat eine Länge von 0.1832675 mg/dl.

Die Funktion KInormal_u() aus dem jgsbook-Paket nimmt uns auch hier die vorgestellten Rechenschritte ab.

# Berechne das 95%-KI
jgsbook::KInormal_u(xa, sa, na, xb, sb, nb, alpha=0.05)

##                           x         y
## 1 Differenz der Mittelwerte 0.4800000
## 2     0.95 KI untere Grenze 0.4125813
## 3      0.95 KI obere Grenze 0.5474187
## 4            0.95 KI Laenge 0.1348373

# Berechne das 99%-KI
jgsbook::KInormal_u(xa, sa, na, xb, sb, nb, alpha=0.01)

##                           x         y
## 1 Differenz der Mittelwerte 0.4800000
## 2     0.99 KI untere Grenze 0.3883663
## 3      0.99 KI obere Grenze 0.5716337
## 4            0.99 KI Laenge 0.1832675

33.6.2 Binomialverteilung

Zur Berechnung von Konfidenzintervallen für Anteilswerte wird folgende Formel verwendet.

$$\begin{equation} \label{formel:konfidenzbinomial} \begin{array}{l l} p_{o}\\p_{u}\\ \end{array} \Big\}= k \pm c \cdot \sqrt{\frac{k \cdot (1-k)}{n}} \end{equation}$$

nn = Umfang der Stichprobe

kk = geschätzter Anteil

cc = Quartilswert für Irrtumswahrscheinlichkeit α\alpha

Nehmen wir an, in einer klinischen Studie wurde an 150 Patienten die Dekubitusprävalenz ermittelt. Dabei wurde eine Prävalenz von 35% festgestellt. Die Konfidenzintervalle für den “wahren Wert” der Grundgesamtheit wird wie folgt bestimmt.

# Daten erzeugen
n <- 150
k <- 0.35

# Fehler berechnen mit (alpha/2)!
fehler95 <- qnorm(0.975)*sqrt(k*(1-k)/n)

# anzeigen
fehler95

## [1] 0.07632963

Beachten Sie, dass in der Funktion qnorm() die Irrtumswahrscheinlichkeit α\alpha halbiert werden muss, da es sich um eine zweiseitige Fragestellung handelt. So muss qnorm() nicht mit dem Wert 0.95 (α=0.05\alpha=0.05) aufgerufen werden, sondern mit 0.975 (α2=0.025\frac{\alpha}{2}=0.025).

# untere Grenze des KI
k - fehler95

## [1] 0.2736704

Die untere Grenze des 95%-Konfidenzintervalls liegt bei 0.2736704 = 27,36704%.

# obere Grenze des KI
k + fehler95

## [1] 0.4263296

Die obere Grenze des 95%-Konfidenzintervalls liegt bei 0.4263296 = 42,63296%.

# Länge des KI
2*fehler95

## [1] 0.1526593

Das 95%-Konfidenzintervall hat eine Länge von 0.1526593 = 15,26593%.

Für das 99%-Konfidenzintervall muss der Wert der Funktion qnorm() auf 0.995 (α2=0,005\frac{\alpha}{2}=0,005) angepasst werden.

# Fehler berechnen mit (alpha/2)!
fehler99 <- qnorm(0.995)*sqrt(k*(1-k)/n)

# anzeigen
fehler99

## [1] 0.1003141

# untere Grenze des KI
k - fehler99

## [1] 0.2496859

Die untere Grenze des 99%-Konfidenzintervalls liegt bei 0.2496859 = 24,96859%.

# obere Grenze des KI
k + fehler99

## [1] 0.4503141

Die obere Grenze des 99%-Konfidenzintervalls liegt bei 0.4503141 = 45,03141%.

# Länge des KI
2*fehler99

## [1] 0.2006283

Das 99%-Konfidenzintervall hat eine Länge von 0.2006283 = 20,06283%.

Die Funktion KIbinomial_a() aus dem jgsbook-Paket nimmt uns auch hier die vorgestellten Rechenschritte ab.

# Berechne das 95%-KI
jgsbook::KIbinomial_a(p=0.35, n=150, alpha=0.05)

##                       x         y
## 1 0.95 KI untere Grenze 0.2736704
## 2  0.95 KI obere Grenze 0.4263296
## 3        0.95 KI Laenge 0.1526593

# Berechne das 99%-KI
jgsbook::KIbinomial_a(p=0.35, n=150, alpha=0.01)

##                       x         y
## 1 0.99 KI untere Grenze 0.2496859
## 2  0.99 KI obere Grenze 0.4503141
## 3        0.99 KI Laenge 0.2006283

33.6.2.1 Unterschied von Anteilswerten

Zur Berechnung der Konfidenzintervalle bei Unterschieden von Anteils- werten wird die folgende Formel verwendet.

$$\begin{equation} \label{formel:konfidenzunterschiedbinomial} \begin{array}{l l} d_{o}\\d_{u}\\ \end{array} \Big\}= \hat{d} \pm c \cdot \sqrt{\frac{\hat{p}_{1} \cdot (1 - \hat{p}_{1})}{n_{1}} + \frac{\hat{p}_{2} \cdot (1 - \hat{p}_{2})}{n_{2}}} \end{equation}$$

d̂\hat{d} = die Differenz der Anteile

n1,n2n_{1}, n_{2} = die Umfänge der beiden Stichproben

p̂1\hat{p}_{1}, p̂2\hat{p}_{2} = die geschätzten Anteile in beiden Stichproben

cc = Quartilswert für Irrtumswahrscheinlichkeit α\alpha

Nehmen wir an, in Bundesland A gaben 25 von 100 Befragten an, Zuzahlungen bei Pflegehilfsmitteln leisten zu müssen. In Bundesland B wurden 150 Personen befragt, von denen 60 von Zuzahlungen betroffen waren. Bundesland A hat

dementsprechend einen Anteil von 0,25 (25%) und Bundesland B einen Anteil von 0,40 (40%) Zuzahlern. Der Punktschätzwert für die Differenz dˆ beträgt folglich 0,40-0,25 = 0,15.

Die Konfidenzintervalle für die wahre Differenz d kann nun wie folgt bestimmt werden.

# Daten erzeugen
n1 <- 100
p1 <- 0.25

n2 <- 150
p2 <- 0.4

# Differenz der Anteile
d <- p2-p1

Beachten Sie, dass in der Funktion qnorm() die Irrtumswahrscheinlichkeit α\alpha halbiert werden muss, da es sich um eine zweiseitige Fragestellung handelt. So muss qnorm() nicht mit dem Wert 0.95 (α=0.05\alpha=0.05) aufgerufen werden, sondern mit 0.975 (α2=0.025\frac{\alpha}{2}=0.025).

# 95%KI-Fehler berechnen mit (alpha/2)
fehler95 <- qnorm(0.975)*sqrt(p1*(1-p1)/n1 + p2*(1-p2)/n2)

#anzeigen
fehler95

## [1] 0.1155382

# untere Grenze des 95%KI
d - fehler95

## [1] 0.03446183

Die untere Grenze des 95%-Konfidenzintervalls liegt bei 0.03446183 = 3,446183%.

# obere Grenze des 95%KI
d + fehler95

## [1] 0.2655382

Die obere Grenze des 95%-Konfidenzintervalls liegt bei 0.2655382 = 26,55382%.

# Länge des KI
2*fehler95

## [1] 0.2310763

Die Länge des 95%-Konfidenzintervalls liegt bei 0.2310763 = 23,10763%.

Für das 99%-Konfidenzintervall muss der Wert der Funktion qnorm() auf 0.995 (α2=0,005\frac{\alpha}{2}=0,005) angepasst werden.

# 99%KI-Fehler berechnen mit (alpha/2)
fehler99 <- qnorm(0.995)*sqrt(p1*(1-p1)/n1 + p2*(1-p2)/n2)

#anzeigen
fehler99

## [1] 0.1518429

# untere Grenze des 99%KI
d - fehler99

## [1] -0.001842898

Die untere Grenze des 99%-Konfidenzintervalls liegt bei -0.0018429 = -0,18429%.

# obere Grenze des 99%KI
d + fehler99

## [1] 0.3018429

Die obere Grenze des 99%-Konfidenzintervalls liegt bei 0.3018429 = 30,18429%.

# Länge des KI
2*fehler99

## [1] 0.3036858

Die Länge des 99%-Konfidenzintervalls liegt bei 0.3036858 = 30,36858%.

Wie Sie sehen, liegt die untere Grenze des 99%-Konfidenzintervalls im negativen Bereich. Errechnet sich ein Intervall, welches die 0 einschließt, bedeutet dies, dass der wahre Unterschied auch 0 sein könnte.

Die Funktion KIbinomail_u() aus dem jgsbook-Paket nimmt uns auch hier die vorgestellten Rechenschritte ab.

# Berechne das 95%-KI
jgsbook::KIbinomial_u(p1, n1, p2, n2, alpha=0.05)

##                       x          y
## 1 Differenz der Anteile 0.15000000
## 2 0.95 KI untere Grenze 0.03446183
## 3  0.95 KI obere Grenze 0.26553817
## 4        0.95 KI Laenge 0.23107635

# Berechne das 99%-KI
jgsbook::KIbinomial_u(p1, n1, p2, n2, alpha=0.01)

##                       x            y
## 1 Differenz der Anteile  0.150000000
## 2 0.99 KI untere Grenze -0.001842898
## 3  0.99 KI obere Grenze  0.301842898
## 4        0.99 KI Laenge  0.303685796

33.7 Signifikanztests

33.7.1 χ2\chi^2-Test

Der χ2\chi^2-Test wird mit der Funktion chisq.test() durchgeführt. Hierfür müssen die Daten als Kreuztabelle vorliegen.

# lade Testdaten
load(url("https://www.produnis.de/R/data/nw.RData"))

# erstelle Kreuztabelle aus Alter und Geschlecht
xtabelle <- xtabs(~ nw$age + nw$sex)

# Führe Chiquadrat-Test durch
chisq.test(xtabelle)

## Warning in chisq.test(xtabelle): Chi-Quadrat-Approximation kann inkorrekt sein

##
##  Pearson's Chi-squared test
##
## data:  xtabelle
## X-squared = 43.886, df = 44, p-value = 0.4765

Der p-Wert ist nicht signifikant, das heisst, das kein Zusammenhang gefunden werden konnte.

Die Warnung bedeutet, dass der p-Wert aus sehr kleinen Zellenhäufigkeiten (kleiner 5) geschätzt wurde, und daher fehlerhaft sein kann (wir sollten daher einen exakten Test durchführen, zum Beispiel den Fisher-Test).

Ein gutes Anleitungsvideo zum χ2\chi^2-Test findet sich bei Youtube: https://www.youtube.com/watch?v=YJUuyaC0x48

33.7.2 Fisher’s Exakttest

Der exakte Fisher-Test wird mit der Funktion fisher.test() durchgeführt.

# Führe Fisher-Test durch
fisher.test(xtabelle)

Fisher's Exact Test for Count Data with simulated p-value (based on 2000 replicates)
data: nw$sex and nw$age
p-value = 0.3848
alternative hypothesis: two.sided

Der exakte Fisher-Test bestätigt, dass es keinen Unterschied gibt.

33.7.3 t-Test

Der t-Test ist wohl einer der bekanntesten Signifikanztests. In R wird er mit der Funktion t.test() ausgeführt.

33.7.3.1 Einstichproben t-Test

Beim Einstichproben t-Test wird der Mittelwert der Grundgesamtheit über den Parameter mu mitgegeben. Schauen wir, ob im Nachtwachendatensatz das Alter von einem gedachten Mittelwert 40 in der Grundgesamtheit abweicht.

# lade Testdaten
load(url("https://www.produnis.de/R/data/nw.RData"))

# Führe t-Test für "Alter" auf Mittelwert 40 durch
t.test(nw$age, mu=40)

##
##  One Sample t-test
##
## data:  nw$age
## t = 6.8274, df = 275, p-value = 5.524e-11
## alternative hypothesis: true mean is not equal to 40
## 95 percent confidence interval:
##  43.00908 45.44744
## sample estimates:
## mean of x
##  44.22826

Der Test ist signifikant, das bedeutet, das Alter im Datensatz nw weicht von einem Mittelwert in der Grundgesamtheit von 40 ab. Es liegt ein Unterschied vor.

Zur Beschreibung der Effektstärke des Unterschieds können wir Cohen’s d berechnen. Dies kann über das Zusatzpaket lsr und der Funktion cohensD() erfolgen.

# aktiviere Zusatzpaket "lsr"
library(lsr)

# berechne Effektsärke Cohen's D
cohensD(nw$age, mu=40)

## [1] 0.4109627

Dieser Wert kann nun nach Cohen’s Angaben (1992) interpretiert werden (0.2=kleiner Effekt, 0.5=mittel, 0.8=stark).

Unsere Effektgröße ist mit 0.4109627 eher “mittel”.

Ein gutes Anleitungsvideo zum Einstichproben t-Test findet sich bei Youtube: https://www.youtube.com/watch?v=7vcyjuSwfzI

33.7.4 unabhängiger t-Test

Für den Vergleich der Mittelwerte bei unabhängige Stichproben werden die Werte beider Gruppen als Variable übergeben. So können wir z.B. schauen, ob sich die Datensätze nw und epa hinsichtlich des Alters unterscheiden

# t-test auf Alter in Datensatz "epa" und "nw"
t.test(nw$age, epa$age)

##
##  Welch Two Sample t-test
##
## data:  nw$age and epa$age
## t = -19.316, df = 854.37, p-value < 2.2e-16
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
##  -20.65610 -16.84545
## sample estimates:
## mean of x mean of y
##  44.22826  62.97903

Das Ergebnis ist signifikant, die Gruppen unterscheiden sich also.

Standardmäßig wird ein zweiseitiger Test durchgeführt. Über den Parameter alternative kann dies geändert werden.

# t-test einseitig, kleiner
t.test(nw$age, epa$age, alternative = "less")
# t-test einseitig, größer
t.test(nw$age, epa$age, alternative = "greater")
# t-test zweiseitig
t.test(nw$age, epa$age, alternative = "two.sided")

Die Funktion nimmt auch die Syntax erklärt durch entgegen. Wir schauen, ob sich das Alter im Datensatz epa in den Geschlechtergruppen unterscheidet, also Alter erklärt durch Geschlecht.

# t-test Alter zwischen Geschlechtern im Datensatz "epa"
t.test(age ~ sex, data=epa)

##
##  Welch Two Sample t-test
##
## data:  age by sex
## t = -4.7535, df = 595.03, p-value = 2.51e-06
## alternative hypothesis: true difference in means between group m and group w is not equal to 0
## 95 percent confidence interval:
##  -9.923268 -4.120822
## sample estimates:
## mean in group m mean in group w
##        59.79646        66.81851

Das Ergebnis ist signifikant. Auch hier lässt sich wieder die Effektstärke Cohen’s D berechnen.

# Effektsärke
lsr::cohensD(age ~ sex, data=epa)

## [1] 0.3837919

Der Effekt ist mit 0.3837919 eher klein.

Ein gutes Anleitungsvideo zum unabhängigen t-Test findet sich bei Youtube: https://www.youtube.com/watch?v=3Yz-XNRasGM

33.7.4.1 abhängiger t-Test

Bei gepaarten Werten muss ein abhängiger t-Test berechnet werden. Dies erfolgt über den Parameter paired, der auf TRUE gesetzt wird. Auch hier können wir wieder die Richtung (größer, kleiner, zweiseitig) über den Parameter alternative verändern.

# erzeuge Testwerte
t0 <- rnorm(100, mean=12, sd=1)
t1 <- rnorm(100, mean=11, sd=3)

# gepaarter t-Test
t.test(t0, t1, paired=TRUE)

##
##  Paired t-test
##
## data:  t0 and t1
## t = 4.5818, df = 99, p-value = 1.345e-05
## alternative hypothesis: true mean difference is not equal to 0
## 95 percent confidence interval:
##  0.8011115 2.0249811
## sample estimates:
## mean difference
##        1.413046

# gepaarter  t-test einseitig, kleiner
t.test(t0, t1, paired=TRUE, alternative = "less")
# gepaarter t-test einseitig, größer
t.test(t0, t1, paired=TRUE, alternative = "greater")
# gepaarter t-test zweiseitig
t.test(t0, t1, paired=TRUE, alternative = "two.sided")

Die Effektstärke Cohen’s D lässt sich entsprechend berechnen, indem der Parameter method auf paired gesetzt wird:

# Effektsärke
lsr::cohensD(t0, t1, method="paired")

## [1] 0.4581845

Der Effekt ist mit 0.4581845 eher gering.

Ein gutes Anleitungsvideo zum verbundenen t-Test findet sich bei Youtube: https://www.youtube.com/watch?v=H15M_8gh1Ok

33.7.5 F-Test

Der F-Test wird mit der Funktion var.test() berechnet. So können wir prüfen, ob die Varianz des Alters im Datensatz epa in den Geschlechtergruppen unterschiedlich ist.

# lade Testdaten
load(url("https://www.produnis.de/R/data/epa.RData"))

# F-Test
var.test(age ~ sex , data=epa)

##
##  F test to compare two variances
##
## data:  age by sex
## F = 0.98352, num df = 338, denom df = 280, p-value = 0.8816
## alternative hypothesis: true ratio of variances is not equal to 1
## 95 percent confidence interval:
##  0.7848842 1.2294811
## sample estimates:
## ratio of variances
##           0.983521

Der Test ist nicht signifikant, es liegt kein Unterschied in den Varianzen vor.

33.7.6 Levene-Test

Ebenso kann mit dem Levene-Test auf Varianzunterschied geprüft werden. Hierfür rufen wir die Funktion leveneTest() aus dem Zusatzpaket car auf.

# lade Testdaten
load(url("https://www.produnis.de/R/data/epa.RData"))

# Levene-Test
car::leveneTest(epa$age, epa$sex)

## Levene's Test for Homogeneity of Variance (center = median)
##        Df F value Pr(>F)
## group   1  0.0232 0.8789
##       618

Der Test ist nicht signifikant, es liegt kein Unterschied in den Varianzen vor. Ein gutes Anleitungsvideo zum verbundenen Levene-Test findet sich bei Youtube: https://www.youtube.com/watch?v=717UWGnyQGA

33.7.7 Mann-Whitney-U-Test

Dies ist die nicht-parametrische alternative zum t-Test für unabhängige Stichproben. Er wird mit der Funktion wilcox.test() durchgeführt, wobei der Parameter paired auf FALSE gesetzt wird (ist die Standardeinstellung).

# lade Testdaten
load(url("https://www.produnis.de/R/data/epa.RData"))

# Man-Whitney-U-Test
wilcox.test(epa$age ~ epa$sex)

##
##  Wilcoxon rank sum test with continuity correction
##
## data:  epa$age by epa$sex
## W = 35862, p-value = 1.151e-07
## alternative hypothesis: true location shift is not equal to 0

Das Ergebnis ist signifikant, das Alter im Datensatz epa unterscheidet sich zwischen den Geschlechtern.

Mit dem Parameter conf.int kann das Konfidenzintervall des Schätzwertes mit ausgegeben werden.

# Man-Whitney-U-Test mit Konfidenzintervall
wilcox.test(epa$age ~ epa$sex, conf.int=TRUE)

##
##  Wilcoxon rank sum test with continuity correction
##
## data:  epa$age by epa$sex
## W = 35862, p-value = 1.151e-07
## alternative hypothesis: true location shift is not equal to 0
## 95 percent confidence interval:
##  -9.999986 -4.999952
## sample estimates:
## difference in location
##              -7.000029

Ein gutes Anleitungsvideo zum Mann-Whitney-U-Test finden Sei bei Youtube: https://www.youtube.com/watch?v=4lFyRcXoJB8

33.7.8 Wilcoxon-Test

Dies ist die nicht-parametrische alternative zum abhängigen t-Test. Er wird mit der Funktion wilcox.test() durchfegührt, wobei der Parameter paired auf TRUE gesetzt werden muss. Andernfalls rechnet R einen Man-Whitney-U-Test.

# erzeuge Testwerte
t0 <- sample(70:140, 100, replace=T)
t1 <- sample(74:170, 100, replace=T)

wilcox.test(t0, t1, paired=TRUE)

##
##  Wilcoxon signed rank test with continuity correction
##
## data:  t0 and t1
## V = 805, p-value = 5.628e-09
## alternative hypothesis: true location shift is not equal to 0

Mit dem Parameter conf.int kann das Konfidenzintervall des Schätzwertes mit ausgegeben werden.

# erzeuge Testwerte
t0 <- sample(70:140, 100, replace=T)
t1 <- sample(74:170, 100, replace=T)

wilcox.test(t0, t1, paired=TRUE, conf.int=TRUE)

##
##  Wilcoxon signed rank test with continuity correction
##
## data:  t0 and t1
## V = 1205.5, p-value = 5.748e-06
## alternative hypothesis: true location shift is not equal to 0
## 95 percent confidence interval:
##  -28.00003 -12.50000
## sample estimates:
## (pseudo)median
##      -20.49995

Ein gutes Anleitungsvideo zum Wilcoxon-Test finden Sie bei Youtube: https://www.youtube.com/watch?v=4lFyRcXoJB8

33.7.9 Shapiro-Wilk-Normalverteilungstest

Zum Test auf Normalverteilung kann der Shapiro-Wilk-Test über die Funktion shapiro.test() verwendet werden. Die Nullhypothese lautet, dass eine Normalverteilung vorliegt. Testen wir, ob im Datensatz nw das Alter (age) normalverteilt ist.

# lade Testdaten
load(url("https://www.produnis.de/R/data/nw.RData"))

# Teste "age" auf Normalverteilung
shapiro.test(nw$age)

##
##  Shapiro-Wilk normality test
##
## data:  nw$age
## W = 0.96636, p-value = 4.677e-06

Das Ergebnis ist signifikant, die Daten sind nicht normalverteilt.

Erzeugen wir normalverteilte Werte zur Gegenprobe.

# Test mit 100 zufälligen und normalverteilten Werten
shapiro.test(rnorm(100))

##
##  Shapiro-Wilk normality test
##
## data:  rnorm(100)
## W = 0.99307, p-value = 0.8921

Das Ergebnis ist (wie erwartet) nicht signifikant, das heisst, unsere Zufallszahlen sind wirklich normalverteilt.

33.8 Varianzanalysen

33.8.1 ANOVA mit Messwiederholung

Eine ANOVA mit Messwiederholung kann wie folgt in R durchgeführt werden. Wir nutzen den Testdatensatz Messwiederholung.rda.

load(url("https://www.produnis.de/R/data/Messwiederholung.rda"))

Der Datensatz besteht aus 200 Probanden, bei denen zu drei Zeitpunkten ein Wert erhoben wurde.

head(Messwiederholung)

##      Name        t1       t2       t3
## 1  Brycen 11.728110 8.190290 6.202859
## 2   Waail  9.781605 8.646142 6.311455
## 3 Abigail 11.937967 9.386623 4.577478
## 4   Holly  4.597950 8.204762 7.250849
## 5  Sheila  8.277670 8.322449 6.952457
## 6 Rebekah  7.891258 9.104856 7.191639

Zunächst müssen wir die Daten ins long table-Format überführen.

# Überführe die Daten ins "long table"-Format
df <- Messwiederholung %>%
  pivot_longer(cols=t1:t3,
               names_to = "Zeitpunkt",
               values_to = "Wert") %>%
  mutate(Zeitpunkt = factor(Zeitpunkt),
         Name = factor(Name))

# anschauen
head(df)

## # A tibble: 6 × 3
##   Name   Zeitpunkt  Wert
##   <fct>  <fct>     <dbl>
## 1 Brycen t1        11.7
## 2 Brycen t2         8.19
## 3 Brycen t3         6.20
## 4 Waail  t1         9.78
## 5 Waail  t2         8.65
## 6 Waail  t3         6.31

Zunächst verschaffen wir uns einen deskriptiven Überblick über die Daten.

# mal deskriptiv anschauen
df %>%
  group_by(Zeitpunkt) %>%
  summarise(M = mean(Wert),
            SD = sd(Wert),
            Q1 = quantile(Wert, probs = 0.25),
            Q2 = quantile(Wert, probs = 0.5),
            Q3 = quantile(Wert, probs = 0.75),
            IQR = IQR(Wert),
            N = n())

## # A tibble: 3 × 8
##   Zeitpunkt     M    SD    Q1    Q2    Q3   IQR     N
##   <fct>     <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <int>
## 1 t1         9.88 2.14   8.41  9.77 11.3   2.91   200
## 2 t2         7.99 0.935  7.24  8.09  8.60  1.36   200
## 3 t3         6.92 0.868  6.31  6.89  7.51  1.21   200

Das geht auch graphisch:

# mal graphisch anschauen
boxplot(df$Wert ~ df$Zeitpunkt)

ez::ezPlot(df, Wert, Name, within = Zeitpunkt, x = Zeitpunkt)

# mit Regressionsgerade
ez::ezPlot(df, Wert, Name, within = Zeitpunkt, x = Zeitpunkt) +
  geom_smooth(aes(x = as.numeric(Zeitpunkt)), method = 'lm', se = F)

## `geom_smooth()` using formula = 'y ~ x'

Die Plots legen nahe, dass es einen Unterschied in den Zeitpunktgruppen gibt.

Wir überprüfen mittels Shapiro-Test und QQ-Plot, ob die Daten normalverteilt sind.

## qq-Plot (Normalverteilung)
ggpubr::ggqqplot(df, x="Wert",
                 facet.by = "Zeitpunkt",
                 color = "Zeitpunkt",
                 scales = "free")

# Teste "Wert" auf Normalverteilung
shapiro.test(df$Wert[df$Zeitpunkt=="t1"])

##
##  Shapiro-Wilk normality test
##
## data:  df$Wert[df$Zeitpunkt == "t1"]
## W = 0.99364, p-value = 0.5481

shapiro.test(df$Wert[df$Zeitpunkt=="t2"])

##
##  Shapiro-Wilk normality test
##
## data:  df$Wert[df$Zeitpunkt == "t2"]
## W = 0.98766, p-value = 0.08025

shapiro.test(df$Wert[df$Zeitpunkt=="t3"])

##
##  Shapiro-Wilk normality test
##
## data:  df$Wert[df$Zeitpunkt == "t3"]
## W = 0.99116, p-value = 0.2625

Die Testergebnisse sind nicht signifikant, also liegt Normalverteilung vor.

Wir dürfen eine ANOVA mit Messwiederholung rechnen. Mit R-Hausmittel könnten wir so vorgehen:

# ANOVA mit Messwiederholung
fit <- aov(Wert ~ Zeitpunkt + Error(Name), data=df)
summary(fit)

##
## Error: Name
##            Df Sum Sq Mean Sq F value Pr(>F)
## Residuals 199  395.8   1.989
##
## Error: Within
##            Df Sum Sq Mean Sq F value Pr(>F)
## Zeitpunkt   2  898.0   449.0   212.3 <2e-16 ***
## Residuals 398  841.6     2.1
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Das Ergebnis ist signifikant. Mit der Funktion pairwise.t.test() können wir nun schauen, welche Gruppenunterschiede es konkret gibt:

# geht auch mit R-Hausmitteln so:
pairwise.t.test(df$Wert, df$Zeitpunkt,
                p.adjust="bonferroni")

##
##  Pairwise comparisons using t tests with pooled SD
##
## data:  df$Wert and df$Zeitpunkt
##
##    t1      t2
## t2 < 2e-16 -
## t3 < 2e-16 1.1e-12
##
## P value adjustment method: bonferroni

Die Unterschiede sind zwischen allen Gruppen signifikant.

Das Zusatzpaket rstatix bietet mit der Funktion anova_test() eine alternative Vorgehensweise.

# ANOVA mit Messwiederholung
fit <- rstatix::anova_test(data = df, dv = Wert, wid = Name,
                           within = Zeitpunkt, effect.size = "pes")

# auf Sphärizität prüfen
fit$`Mauchly's Test for Sphericity`

##      Effect     W        p p<.05
## 1 Zeitpunkt 0.668 4.29e-18     *

Mauchly’s Test ist signifikant, das heisst, es liegt keine Sphärizität vor. R hat in diesem Falle bereits die Freiheitsgrade automatisch korrigiert (mittels Greenhouse-Geisser-Verfahren).

Schauen wir uns die ANOVA an:

rstatix::get_anova_table(fit)

## ANOVA Table (type III tests)
##
##      Effect DFn    DFd      F        p p<.05   pes
## 1 Zeitpunkt 1.5 298.73 212.34 2.18e-48     * 0.516

Die ANOVA ist signifikant. Jetzt können wir analysieren, welche konkreten Gruppenunterschiede vorliegen.

# post-hoc Analyse: paarweise Gruppenvergleiche
df %>%
  rstatix::pairwise_t_test(Wert ~ Zeitpunkt, paired = TRUE,
                           p.adjust.method = "bonferroni") %>%
  as.data.frame()

##    .y. group1 group2  n1  n2 statistic  df        p    p.adj p.adj.signif
## 1 Wert     t1     t2 200 200  11.24384 199 5.02e-23 1.51e-22         ****
## 2 Wert     t1     t3 200 200  18.28475 199 1.79e-44 5.37e-44         ****
## 3 Wert     t2     t3 200 200  11.26870 199 4.23e-23 1.27e-22         ****

Die Unterschiede sind zwischen allen Gruppen signifikant.

Die Effektstärke kann wie folgt berechnet werden.

# Effektstärke (nur solche, die post-hoc signifikant sind, interpretieren)
df %>%
  rstatix::cohens_d(Wert ~ Zeitpunkt, paired = TRUE) %>%
  as.data.frame()

##    .y. group1 group2   effsize  n1  n2 magnitude
## 1 Wert     t1     t2 0.7950598 200 200  moderate
## 2 Wert     t1     t3 1.2929274 200 200     large
## 3 Wert     t2     t3 0.7968171 200 200  moderate

Achtung, nur solche Werte interpretieren, die oben signifkant waren. In unserem Beispiel sind alle Werte signifikant, darum dürfen wir alle Effekte interpretieren.

33.8.2 Friedman ANOVA

Sind die Werte nicht normalverteilt, kann ein Friedman-ANOVA gerechnet werden. Wir nutzen den Testdatensatz MarioANOVA.rda. Er enthält Informationen über 47 Super Mario Charaktere, deren Fehlerquote beim Spielen eines schweren Levels zu 3 Zeitpunkten erfasst wurde.

load(url("https://www.produnis.de/R/data/MarioANOVA.rda"))

# anschauen
head(MarioANOVA)

## # A tibble: 6 × 8
##   Name             Alter Kingdom          Geschlecht BadGuy    t1    t2    t3
##   <fct>            <dbl> <fct>            <fct>      <lgl>  <dbl> <dbl> <dbl>
## 1 Mario               35 Mushroom Kingdom männlich   FALSE  0.372 0.472 0.257
## 2 Luigi               35 Mushroom Kingdom männlich   FALSE  1.27  1.34  1.14
## 3 Prinzessin Peach    25 Mushroom Kingdom weiblich   FALSE  6.15  1.40  1.98
## 4 Bowser              45 Bowser's Castle  männlich   TRUE   4.85  0.648 1.27
## 5 Toad                20 Mushroom Kingdom männlich   FALSE  1.34  0.174 1.77
## 6 Toadette            19 Mushroom Kingdom weiblich   FALSE  0.727 0.413 2.09

In den Variablen t1 bis t3 liegen die Werte der 3 Messzeitpunkte. Auch hier überführen wir die Daten ins long table-Format

df <- MarioANOVA %>%
  select(Name, t1:t3) %>%
  pivot_longer(cols=t1:t3,
               names_to = "Zeitpunkt",
               values_to = "Wert")

Schauen wir uns die Daten zunächst deskriptiv an.

# mal deskriptiv anschauen
df %>%
  group_by(Zeitpunkt) %>%
  summarise(M = mean(Wert),
            SD = sd(Wert),
            Q1 = quantile(Wert, probs = 0.25),
            Q2 = quantile(Wert, probs = 0.5),
            Q3 = quantile(Wert, probs = 0.75),
            IQR = IQR(Wert),
            N = n())

## # A tibble: 3 × 8
##   Zeitpunkt     M    SD    Q1    Q2    Q3   IQR     N
##   <chr>     <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <int>
## 1 t1        2.75  4.61  0.750 1.34   2.46 1.71     47
## 2 t2        0.762 0.585 0.356 0.509  1.15 0.791    47
## 3 t3        0.901 0.701 0.266 0.650  1.34 1.07     47

Das geht auch graphisch wie oben beschrieben:

# mal graphisch anschauen
boxplot(df$Wert ~ df$Zeitpunkt)

ez::ezPlot(df, Wert, Name, within = Zeitpunkt, x = Zeitpunkt)

# mit Regressionsgerade
ez::ezPlot(df, Wert, Name, within = Zeitpunkt, x = Zeitpunkt) +
  geom_smooth(aes(x = as.numeric(Zeitpunkt)), method = 'lm', se = F)

Nun prüfen wir mittels QQ-Plot und Shapiro-Test auf Normalverteilung.

## qq-Plot (Normalverteilung)
ggpubr::ggqqplot(df, x="Wert",
                 facet.by = "Zeitpunkt",
                 color = "Zeitpunkt",
                 scales = "free")

# Teste "Wert" auf Normalverteilung
shapiro.test(df$Wert[df$Zeitpunkt=="t1"])

##
##  Shapiro-Wilk normality test
##
## data:  df$Wert[df$Zeitpunkt == "t1"]
## W = 0.52189, p-value = 3.729e-11

shapiro.test(df$Wert[df$Zeitpunkt=="t2"])

##
##  Shapiro-Wilk normality test
##
## data:  df$Wert[df$Zeitpunkt == "t2"]
## W = 0.89427, p-value = 0.0004751

shapiro.test(df$Wert[df$Zeitpunkt=="t3"])

##
##  Shapiro-Wilk normality test
##
## data:  df$Wert[df$Zeitpunkt == "t3"]
## W = 0.90101, p-value = 0.0007756

Die Werte für t1 bis t3 sind signifikant, also nicht normalverteilt.

Wir führen daher eine Friedman-ANOVA mit Messwiederholung durch.

# Friedman-Test
rstatix::friedman_test(Wert ~ Zeitpunkt | Name, data=df)

## # A tibble: 1 × 6
##   .y.       n statistic    df       p method
## * <chr> <int>     <dbl> <dbl>   <dbl> <chr>
## 1 Wert     47      12.8     2 0.00165 Friedman test

Das Ergebnis ist signifikant, es gibt also einen Unterschied.

# post-hoc-test
df %>%
  rstatix::wilcox_test(Wert ~ Zeitpunkt, paired=T,
                       p.adjust.method = "bonferroni")

## # A tibble: 3 × 9
##   .y.   group1 group2    n1    n2 statistic        p    p.adj p.adj.signif
## * <chr> <chr>  <chr>  <int> <int>     <dbl>    <dbl>    <dbl> <chr>
## 1 Wert  t1     t2        47    47       917 0.000102 0.000306 ***
## 2 Wert  t1     t3        47    47       882 0.000536 0.002    **
## 3 Wert  t2     t3        47    47       506 0.546    1        ns

Der Unterschied existiert zwischen t1 und t2 sowie zwischen t1 und t3.

Die Effktstärke wird wie folgt berechnet:

# Effektstärke
rstatix::friedman_effsize(Wert ~ Zeitpunkt | Name, data = df)

## # A tibble: 1 × 5
##   .y.       n effsize method    magnitude
## * <chr> <int>   <dbl> <chr>     <ord>
## 1 Wert     47   0.136 Kendall W small

Der Effekt des Gesamtmodells ist eher gering. Schauen wir uns die einzelnen Gruppeneffekte an.

Achtung, nur solche Werte interpretieren, die oben signifkant waren. In unserem Beispiel ist das der Unterschied von t1 zu t2 sowie von t1 zu t3..

# nur die signifikanten anschauen (t1 -> t2 und t1 -> t3)
df %>%
  rstatix::wilcox_effsize(Wert ~ Zeitpunkt, paired = TRUE)

## # A tibble: 3 × 7
##   .y.   group1 group2 effsize    n1    n2 magnitude
## * <chr> <chr>  <chr>    <dbl> <int> <int> <ord>
## 1 Wert  t1     t2      0.545     47    47 large
## 2 Wert  t1     t3      0.491     47    47 moderate
## 3 Wert  t2     t3      0.0895    47    47 small

Der Effekt von t1 nach t2 ist “groß”, der von t1 nach t3 “moderat”.

33.8.3 einfaktorielle ANOVA

Eine einfaktorielle Varianzanalyse (ANOVA) wird mit der Funktion aov() durchgeführt. Sie wird nach der Syntax x erklärt durch y aufgerufen. Wie bei anderen Modellen ist es ratsam, die Ergebnisse der Berechnung in ein Objekt zu speichern, und dieses per summary() anzuschauen.

# Lade Testdatensatz
load(url("https://www.produnis.de/R/data/pf8.RData"))

# erstelle ANOVA  "Alter" erklärt durch "Standort"
fit <- aov(Alter ~ Standort, data=pf8)

# schaue Modellzusammenfassung an
summary(fit)

##              Df Sum Sq Mean Sq F value   Pr(>F)
## Standort      4  10557  2639.3    8.48 1.09e-06 ***
## Residuals   725 225633   311.2
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 1 Beobachtung als fehlend gelöscht

Das Ergebnis ist signifikant, wir haben unterschiedliche Varianzen im Alter erklärt durch Standort.

Wir wissen aber noch nicht, innerhalb welche Standorte die Unterschiede bestehen. Dazu führen wir post hoc Tests durch, in denen alle Gruppen paarweise verglichen werden. Wegen dieses multiplen Testens muss eine Fehlerkorrektur für Alpha durchgeführt werden. Dazu steht die Funktion pairwise.t.test() zur Verfügung, die mit den selben Angaben aufgerufen wird.

pairwise.t.test(pf8$Alter, pf8$Standort)

##
##  Pairwise comparisons using t tests with pooled SD
##
## data:  pf8$Alter and pf8$Standort
##
##           Rheine Münster Bahn   Ladbergen
## Münster   0.5042 -       -      -
## Bahn      0.4354 1.0000  -      -
## Ladbergen 0.5432 1.0000  1.0000 -
## Internet  0.0090 1.8e-05 0.0002 0.0059
##
## P value adjustment method: holm

Die gewählte Fehlerkorrektur ist standardmäßig holm. Möchten Sie auf die Bonferroni-Korrekturmethode wechseln, übergeben Sie den Parameter p.adjust="bonferroni".

pairwise.t.test(pf8$Alter, pf8$Standort, p.adjust="bonferroni")

##
##  Pairwise comparisons using t tests with pooled SD
##
## data:  pf8$Alter and pf8$Standort
##
##           Rheine  Münster Bahn    Ladbergen
## Münster   1.00000 -       -       -
## Bahn      0.72572 1.00000 -       -
## Ladbergen 1.00000 1.00000 1.00000 -
## Internet  0.01292 1.8e-05 0.00022 0.00733
##
## P value adjustment method: bonferroni

Die signifikanten Unterschiede befinden sich zwischen dem Standort Internet und allen anderen Standorten.

Ein anschauliches Video zur ANOVA in R findet sich bei Youtube: https://www.youtube.com/watch?v=GctGncQrJew

33.9 Überlebenszeitanalysen

33.9.1 Kaplan-Meier-Analysen

Zur Überlebenszeitanalyse nach dem Kaplan-Meier-Verfahren steht das Zusatzpaket survival zur Verfügung. Zum Ausprobieren der Funktionen liegt dem Paket der Datensatz ovarian bei.

# Beschreibung des Datensatzes lesen
?survival::ovarian

# speichere Testdatensatz "ovarian" in Objekt "ov"
ov <- survival::ovarian
head(ov)

##   futime fustat     age resid.ds rx ecog.ps
## 1     59      1 72.3315        2  1       1
## 2    115      1 74.4932        2  1       1
## 3    156      1 66.4658        2  1       2
## 4    421      0 53.3644        2  2       1
## 5    431      1 50.3397        2  1       1
## 6    448      0 56.4301        1  1       2

Bevor wir mit der Analyse beginnen, sollten wir den Datensatz noch aufbereiten. Wir überführen alle kategorialen Variablen in einen factor.

### Datensatz aufbereiten
## alle kategorialen Variablen
# rx ist die Behandlungsgruppe
ov$rx <- factor(ov$rx,
                levels = c("1", "2"),
                labels = c("A", "B"))

# resid.ds sind Nebenerkrankungen
ov$resid.ds <- factor(ov$resid.ds,
                      levels = c("1", "2"),
                      labels = c("nein", "ja"))

# ecog.ps ist der ECOG-Status
ov$ecog.ps <- factor(ov$ecog.ps,
                     levels = c("1", "2"),
                     labels = c("gut", "schlecht"))

Als ersten Analyseschritt erzeugen wir mit der Funktion Surv() ein “Survivalobjekt”. Der Funktion muss eine Zeitvariable mit der Beobachtungszeit sowie eine dichotome Statusvariable mit dem Ereignis (0 = nicht eingetreten; 1 = eingetreten) übergeben werden. Im ovarian-Datensatz ist die Zeit in futime und der Status in fustat gespeichert. Der Funktionsaufruf lautet dementsprechend

# Survivalobjet erzeugen
survObjekt <- survival::Surv(time=ov$futime, event=ov$fustat)

Mit der Funktion survfit wird nun das Kaplan-Meier-Modell gefittet. Der Funktion muss das eben erstellte Survivalobjekt übergeben werden.

### Kaplan-Meier
#  ~1 bedeutet "Gesamtmodell", ohne Gruppierung
km_fit <- survival::survfit(survObjekt ~ 1, data=ov)

Das Modell kann als Tabelle oder Plot ausgegeben werden.

## Tabelle anschauen
#  "survival"-Spalte ist die Überlebenswahrscheinlichkeit
summary(km_fit)

## Call: survfit(formula = survObjekt ~ 1, data = ov)
##
##  time n.risk n.event survival std.err lower 95% CI upper 95% CI
##    59     26       1    0.962  0.0377        0.890        1.000
##   115     25       1    0.923  0.0523        0.826        1.000
##   156     24       1    0.885  0.0627        0.770        1.000
##   268     23       1    0.846  0.0708        0.718        0.997
##   329     22       1    0.808  0.0773        0.670        0.974
##   353     21       1    0.769  0.0826        0.623        0.949
##   365     20       1    0.731  0.0870        0.579        0.923
##   431     17       1    0.688  0.0919        0.529        0.894
##   464     15       1    0.642  0.0965        0.478        0.862
##   475     14       1    0.596  0.0999        0.429        0.828
##   563     12       1    0.546  0.1032        0.377        0.791
##   638     11       1    0.497  0.1051        0.328        0.752

Die Spalte survival gibt die entsprechenden Überlebenswahrscheinlichkeiten an.

## plot()  (sehr hässlich)
plot(km_fit)

Etwas hübschere Plots lassen sich mit der Funktion autoplot() erzeugen. Damit die Funktion die Modelldaten versteht, muss das Zusatzpaket ggfortify geladen werden.

# etwas hübscher
library(ggfortify)
autoplot(km_fit)

Darüber hinaus bietet das Paket survminer die Funktion ggsurvplot():

## survminer ggsurvplot
survminer::ggsurvplot(km_fit)

Das Modell lässt sich auch für Gruppenvergleiche erstellen. Die Variable ov$rx unterteilt den Datensatz in zwei Behandlungsgruppen. Das Überlebensmodell pro Gruppe lässt sich wie folgt erzeugen:

### Gruppierungen
# nach Behandlungsgruppe "rx" gruppieren
km_fit <- survival::survfit(survObjekt ~ rx, data=ov)

Mittels summary() werden die Überlebenstabellen pro Gruppe ausgegeben.

summary(km_fit)

## Call: survfit(formula = survObjekt ~ rx, data = ov)
##
##                 rx=A
##  time n.risk n.event survival std.err lower 95% CI upper 95% CI
##    59     13       1    0.923  0.0739        0.789        1.000
##   115     12       1    0.846  0.1001        0.671        1.000
##   156     11       1    0.769  0.1169        0.571        1.000
##   268     10       1    0.692  0.1280        0.482        0.995
##   329      9       1    0.615  0.1349        0.400        0.946
##   431      8       1    0.538  0.1383        0.326        0.891
##   638      5       1    0.431  0.1467        0.221        0.840
##
##                 rx=B
##  time n.risk n.event survival std.err lower 95% CI upper 95% CI
##   353     13       1    0.923  0.0739        0.789        1.000
##   365     12       1    0.846  0.1001        0.671        1.000
##   464      9       1    0.752  0.1256        0.542        1.000
##   475      8       1    0.658  0.1407        0.433        1.000
##   563      7       1    0.564  0.1488        0.336        0.946

Im Plot werden die Kaplan-Meier-Kurven beider Gruppen angezeigt.

# plot (hässlich)
plot(km_fit, conf.int=T) # mit Konfidenzintervallen

# plot (hübscher)
plot(km_fit, conf.int=TRUE, col=c("red", "blue"), yscale=100,
     xlab = "Überlebenszeit in Tagen", ylab = "Prozent Überleben")
legend("bottomright", title="Gruppe", c("1", "2"),
       fill=c("red", "blue"))

Die Funktion ggsurvplot() liefert auf Wunsch den p-Wert des Gruppenunterschieds mit:

## survminer ggsurvplot mit p-Wert
survminer::ggsurvplot(km_fit, pval=TRUE, conf.int = TRUE)

Der p-Wert lässt sich mit dem Lograng-Test auch selbst berechnen. Hierfür steht die Funktion survdiff() zur Verfügung.

# p-Wert selbst berechnen
## Lograng-Test
survival::survdiff(survObjekt ~rx, data=ov)

## Call:
## survival::survdiff(formula = survObjekt ~ rx, data = ov)
##
##       N Observed Expected (O-E)^2/E (O-E)^2/V
## rx=A 13        7     5.23     0.596      1.06
## rx=B 13        5     6.77     0.461      1.06
##
##  Chisq= 1.1  on 1 degrees of freedom, p= 0.3

33.9.2 Cox-Regression

Neben der Kaplan-Meier-Analyse können die Daten auch per Cox-Regression analysiert werden. Der Vorteil besteht darin, dass Prädiktoren verschiedensten Datenniveaus in die Analyse integriert werden können. Das Paket survival bietet hierzu die Funktion coxph().

## Cox-Regression
cox <- survival::coxph(survObjekt ~ rx+resid.ds+age+ecog.ps, data=ov)
# Modell ausgeben
cox

## Call:
## survival::coxph(formula = survObjekt ~ rx + resid.ds + age +
##     ecog.ps, data = ov)
##
##                     coef exp(coef) se(coef)      z       p
## rxB             -0.91450   0.40072  0.65332 -1.400 0.16158
## resid.dsja       0.82619   2.28459  0.78961  1.046 0.29541
## age              0.12481   1.13294  0.04689  2.662 0.00777
## ecog.psschlecht  0.33621   1.39964  0.64392  0.522 0.60158
##
## Likelihood ratio test=17.04  on 4 df, p=0.001896
## n= 26, number of events= 12

Die Spalte exp(coef) entspricht der Hazard-Ratio, mit welcher Richtung und Stärke des jeweiligen Einflusses interpretiert werden kann.

Das Modell kann mittels ggforest() geplottet werden:

# Forestplot der Hazard-Ratio
survminer::ggforest(cox, data=ov)

33.10 Faktorenanalyse

Die beiden am Häufigsten genutzen Verfahren sind die Hauptkomponentenanalyse (PCA) und die Hauptachsenanalyse (PAF). Sie unterscheiden sich in den Varianzanteilen, die in den Items enthalten sein können. Die PCA versucht, die gesamte Varianz auf Faktoren zurückzuführen, die PAF die gemeinsame Varianz der Items. In psychologischen Anwendungen (= am Menschen) wird daher eher die PAF verwendet.

33.10.1 explorative Faktorenanalyse

Bei der explorativen Faktorenanalyse (EFA) erfolgt die Auswertung (häufig) nicht theoriegeleitet. Ziel ist es vielmehr zu erfahren, mit wievielen Faktoren die Daten bestmöglich erklärt werden können.

Wir verwenden den Datensatz Faktorenbogen, der wie folgt geladen werden kann.

load(url("https://www.produnis.de/R/data/Faktorenbogen.rda"))

# anschauen
psych::describe(Faktorenbogen)

##         vars   n  mean    sd median trimmed   mad min max range  skew kurtosis
## age        1 150 53.11 18.82   53.0   54.16 17.05   1 101   100 -0.47     0.29
## gender*    2 150  1.65  0.55    2.0    1.64  0.00   1   3     2  0.02    -0.87
## A          3 150  4.41  2.03    5.0    4.40  2.97   0  10    10  0.05    -0.43
## B          4 150  1.92  0.84    2.0    1.84  1.48   1   5     4  0.76     0.43
## C          5 150  7.91  6.19    7.0    7.22  5.93   0  30    30  1.12     1.31
## D          6 150 12.23  5.05   12.0   11.95  4.45   3  27    24  0.51    -0.04
## E          7 150 14.77  2.39   15.0   14.69  2.97  10  21    11  0.28    -0.13
## F          8 150  5.00  1.32    5.0    5.03  1.48   2   8     6 -0.12    -0.67
## G          9 150 10.35  4.08    9.5   10.06  3.71   3  24    21  0.78     0.76
## H         10 150 15.92  2.93   16.0   15.84  2.97  10  24    14  0.21    -0.39
## I         11 150 85.02 68.19   65.0   75.93 51.15   0 356   356  1.40     2.05
## J         12 150  5.02  2.25    5.0    4.93  1.48   1  11    10  0.33    -0.12
## K         13 150  5.44  2.08    6.0    5.38  2.97   1  12    11  0.31    -0.10
## L         14 150  1.47  0.65    1.0    1.36  0.00   1   4     3  1.20     0.86
##           se
## age     1.54
## gender* 0.04
## A       0.17
## B       0.07
## C       0.51
## D       0.41
## E       0.20
## F       0.11
## G       0.33
## H       0.24
## I       5.57
## J       0.18
## K       0.17
## L       0.05

Die Items für die Faktorenanalyse befinden sich in den Variablen A bis L.

items <- Faktorenbogen %>%
  select(A:L)

Mit dem Bartlett-Test kann überprüft werden, ob die Items miteinander korrelieren (das sollten sie tun).

psych::cortest.bartlett(items)$p.value

## R was not square, finding R from data

## [1] 8.843226e-151

Das Ergebnis ist signifikant.

Mittels Kaiser-Meyer-Olkin-Verfahren (KMO) wird geprüft, wie gut die Daten für eine Faktorenanalyse geeignet sind, wobei Werte zwischen 0 (für “nicht geeignet”) und 1 (für “perfekt geeignet”) angenommen werden. Die Minimum Average Partial (MSA) ist ein Maß der internen Konsistenz, und nimmt ebenfalls Werte zwischen 0 (nicht konsistent) und 1 (perfekt konsistent) an. Beide Werte sollte größer als 0,5 sein.

# berechne KMO und MSA
psych::KMO(items)

## Kaiser-Meyer-Olkin factor adequacy
## Call: psych::KMO(r = items)
## Overall MSA =  0.69
## MSA for each item =
##    A    B    C    D    E    F    G    H    I    J    K    L
## 0.36 0.72 0.66 0.68 0.60 0.73 0.24 0.73 0.75 0.62 0.78 0.61

Die “Overall MSA” entspricht dem KMO.

Mittels Screeplot kann die Faktorenzahl graphisch entschieden werden. Dabei sollte der letzte “gültige” Faktor vor dem deutlichen Knick zu einer abflachenden Gerade genutzt werden. Darüber hinaus sollten die Eigenwerte der Faktoren größer 1 sein.

Mit R-Hausmitteln kann ein Screeplot wie folgt erzeugt werden.

plot(eigen(cor(items))$values, type="b")
abline(h=1)

Schöner geht es mit der Funktion fa.parallel().

psych::fa.parallel(items)

## Parallel analysis suggests that the number of factors =  3  and the number of components =  3

Es werden 3 Faktoren/Komponenten vorgeschlagen.

Die Funktion berechnet das Screeplot sowohl für PAC (Sternchen) als auch PAF (Dreieck). Mit dem Parameter fa = "fa" kann die PAC-Methode entfernt werden.

psych::fa.parallel(items, fa ="fa")

## Parallel analysis suggests that the number of factors =  3  and the number of components =  NA

Die Daten lassen sich am besten mit 3 Faktoren erklären.

33.10.2 konfirmatorische Faktorenanalyse

Ist die Anzahl der Faktoren bekannt (z.B. theoriegeleitet), wird die Hauptachsenanaylse wie folgt durchgeführt.

# Hauptachsen-Faktorenanalyse (PAF)
# mit 3 Faktoren, Varimax-Rotation
fit <- psych::fa(items, nfactors=3,
                 rotate = "varimax")
fit

## Factor Analysis using method =  minres
## Call: psych::fa(r = items, nfactors = 3, rotate = "varimax")
## Standardized loadings (pattern matrix) based upon correlation matrix
##     MR1   MR2   MR3     h2    u2 com
## A  0.00  0.08  0.03 0.0069 0.993 1.2
## B -0.01  0.83 -0.08 0.6997 0.300 1.0
## C -0.05  0.88 -0.01 0.7770 0.223 1.0
## D  0.96 -0.09 -0.07 0.9266 0.073 1.0
## E -0.02  0.10  0.88 0.7820 0.218 1.0
## F -0.04 -0.12  0.59 0.3655 0.635 1.1
## G  0.00  0.00  0.07 0.0048 0.995 1.0
## H  0.94 -0.02 -0.08 0.8855 0.115 1.0
## I -0.03  0.78 -0.05 0.6107 0.389 1.0
## J -0.08  0.08  0.88 0.7866 0.213 1.0
## K  0.89  0.03 -0.01 0.7896 0.210 1.0
## L -0.10  0.09  0.14 0.0393 0.961 2.6
##
##                        MR1  MR2  MR3
## SS loadings           2.61 2.13 1.94
## Proportion Var        0.22 0.18 0.16
## Cumulative Var        0.22 0.39 0.56
## Proportion Explained  0.39 0.32 0.29
## Cumulative Proportion 0.39 0.71 1.00
##
## Mean item complexity =  1.2
## Test of the hypothesis that 3 factors are sufficient.
##
## df null model =  66  with the objective function =  6.38 with Chi Square =  920.48
## df of  the model are 33  and the objective function was  0.32
##
## The root mean square of the residuals (RMSR) is  0.03
## The df corrected root mean square of the residuals is  0.05
##
## The harmonic n.obs is  150 with the empirical chi square  20.2  with prob <  0.96
## The total n.obs was  150  with Likelihood Chi Square =  45.01  with prob <  0.079
##
## Tucker Lewis Index of factoring reliability =  0.971
## RMSEA index =  0.049  and the 90 % confidence intervals are  0 0.083
## BIC =  -120.34
## Fit based upon off diagonal values = 0.99
## Measures of factor score adequacy
##                                                    MR1  MR2  MR3
## Correlation of (regression) scores with factors   0.98 0.94 0.94
## Multiple R square of scores with factors          0.96 0.88 0.88
## Minimum correlation of possible factor scores     0.92 0.76 0.77

Die Ausgabe von fit ist ziemlich lang. Wie gewohnt besteht die Möglichkeit, mittels $ auf die einzelnen Ergebnisse zuzugreifen. Eine Übersicht erhält man mittels names().

names(fit)

##  [1] "residual"      "dof"           "chi"           "nh"
##  [5] "rms"           "EPVAL"         "crms"          "EBIC"
##  [9] "ESABIC"        "fit"           "fit.off"       "sd"
## [13] "factors"       "complexity"    "n.obs"         "objective"
## [17] "criteria"      "STATISTIC"     "PVAL"          "Call"
## [21] "null.model"    "null.dof"      "null.chisq"    "TLI"
## [25] "CFI"           "RMSEA"         "BIC"           "SABIC"
## [29] "r.scores"      "R2"            "valid"         "score.cor"
## [33] "weights"       "rotation"      "hyperplane"    "communality"
## [37] "communalities" "uniquenesses"  "values"        "e.values"
## [41] "loadings"      "model"         "fm"            "rot.mat"
## [45] "Structure"     "method"        "scores"        "R2.scores"
## [49] "r"             "np.obs"        "fn"            "Vaccounted"
## [53] "ECV"

fit$communality

##           A           B           C           D           E           F
## 0.006940720 0.699704328 0.776989353 0.926609287 0.782014553 0.365479522
##           G           H           I           J           K           L
## 0.004811051 0.885466837 0.610670890 0.786557566 0.789638724 0.039273937

fit$loadings

##
## Loadings:
##   MR1    MR2    MR3
## A
## B         0.833
## C         0.880
## D  0.956
## E         0.103  0.878
## F        -0.118  0.592
## G
## H  0.938
## I         0.779
## J                0.879
## K  0.888
## L -0.104         0.141
##
##                  MR1   MR2   MR3
## SS loadings    2.605 2.129 1.940
## Proportion Var 0.217 0.177 0.162
## Cumulative Var 0.217 0.394 0.556

Mit diesen Informationen können die Items ein bisschen umsortiert…

Faktor 1: D, H, K

Faktor 2: B, C, I

Faktor 3: E, F, J

… und ein Korrelationsmatrix erstellt werden.

# ändere Reihenfolgt
items2 <- items %>%
  select(D, H, K, B, C, I, E, F, J)

# Korreltationsmatrix plotten
corrplot::corrplot(corr = cor(items2), # Korrelationsmatrix
         method = "color",   # Ausprägung farblich kodieren
         addCoef.col = "black", # schreibe Werte in schwarz
         number.cex = 0.7)   # Schriftgröße

33.10.3 Hauptkomponentenanalyse

Die Hauptkomponentenanalyse kann mit der Funktion principal() durchgeführt werden.

# Hauptkomponentenanalyse (PCA)
# mit 3 Hauptkomponenten und
# Varimax-Rotation
fit2 <- psych::principal(items, nfactors=3, rotate="varimax")

# anschauen
fit2

## Principal Components Analysis
## Call: psych::principal(r = items, nfactors = 3, rotate = "varimax")
## Standardized loadings (pattern matrix) based upon correlation matrix
##     RC1   RC2   RC3    h2    u2 com
## A  0.01  0.13  0.05 0.019 0.981 1.3
## B -0.01  0.89 -0.08 0.793 0.207 1.0
## C -0.05  0.90  0.00 0.821 0.179 1.0
## D  0.96 -0.08 -0.07 0.928 0.072 1.0
## E -0.03  0.10  0.89 0.803 0.197 1.0
## F -0.03 -0.15  0.76 0.606 0.394 1.1
## G  0.01  0.00  0.12 0.015 0.985 1.0
## H  0.95 -0.02 -0.07 0.916 0.084 1.0
## I -0.04  0.86 -0.05 0.752 0.248 1.0
## J -0.09  0.08  0.89 0.806 0.194 1.0
## K  0.94  0.04  0.00 0.884 0.116 1.0
## L -0.14  0.14  0.24 0.095 0.905 2.3
##
##                        RC1  RC2  RC3
## SS loadings           2.74 2.44 2.26
## Proportion Var        0.23 0.20 0.19
## Cumulative Var        0.23 0.43 0.62
## Proportion Explained  0.37 0.33 0.30
## Cumulative Proportion 0.37 0.70 1.00
##
## Mean item complexity =  1.2
## Test of the hypothesis that 3 components are sufficient.
##
## The root mean square of the residuals (RMSR) is  0.06
##  with the empirical chi square  61.27  with prob <  0.002
##
## Fit based upon off diagonal values = 0.96

# welche $-Werte sind verfügbar?
names(fit2)

##  [1] "values"       "rotation"     "n.obs"        "communality"  "loadings"
##  [6] "fit"          "fit.off"      "fn"           "Call"         "uniquenesses"
## [11] "complexity"   "valid"        "chi"          "EPVAL"        "R2"
## [16] "objective"    "residual"     "rms"          "factors"      "dof"
## [21] "null.dof"     "null.model"   "criteria"     "STATISTIC"    "PVAL"
## [26] "weights"      "r.scores"     "rot.mat"      "Vaccounted"   "Structure"
## [31] "scores"

# Faktorenladungen
fit2$loadings

##
## Loadings:
##   RC1    RC2    RC3
## A         0.128
## B         0.887
## C         0.905
## D  0.958
## E         0.102  0.890
## F        -0.146  0.764
## G                0.121
## H  0.954
## I         0.865
## J                0.890
## K  0.940
## L -0.135  0.143  0.238
##
##                  RC1   RC2   RC3
## SS loadings    2.744 2.435 2.260
## Proportion Var 0.229 0.203 0.188
## Cumulative Var 0.229 0.432 0.620

Ein passendes Video von Daniela Keller findet sich bei Youtube unter https://www.youtube.com/watch?v=NFPGQcq1fO8.

33.11 Fallzahlkalkulation

33.11.1 klinische Studien

Zur Fallzahlkalkulation bei klinischen Studien (z.B. RCTs) kann das Zusatzpaket pwr installiert werden. Mit der Funktion pwr.t.test() können alle Werte berechnet werden. Ihr müssen die entsprechenden Werte für das Signifikanzlevel, Power und Cohen’s d (Effektstärke) übergeben werden.

# erechne Fallzahl mit
# Cohens d = 0.1
# alpha = 0.05
# Power = 0.8
pwr::pwr.t.test(d=0.1, sig.level=0.05, power=0.8, type="two.sample", alternative="two.sided")

##
##      Two-sample t test power calculation
##
##               n = 1570.733
##               d = 0.1
##       sig.level = 0.05
##           power = 0.8
##     alternative = two.sided
##
## NOTE: n is number in *each* group

Über die Parameter type und alternative lassen sich die Testbedingungen festlegen.

# Lade Paket
library(pwr)

# unabhängige Stichprobe, zweiseitig
pwr.t.test(d=0.1, sig.level=0.05, power=0.8, type="two.sample", alternative="two.sided")

# Einstichproben-Test, kleiner
pwr.t.test(d=0.1, sig.level=0.05, power=0.8, type="one.sample", alternative="less")

# gepaarte Stichprobe, größer
pwr.t.test(d=0.1, sig.level=0.05, power=0.8, type="paired", alternative="greater")

Möchten Sie nicht immer die gesamte Testausgabe angezeigt bekommen, sondern nur den Wert für n, können Sie dies mit einem Dollarzeichen $ hinter dem Funktionsaufruf spezifizieren.

# zeige nur "n"
pwr.t.test(d=0.1, sig.level=0.05, power=0.8, type="two.sample")$n

## [1] 1570.733

# runde auf nächste ganze Zahl
ceiling(pwr.t.test(d=0.1, sig.level=0.05, power=0.8, type="two.sample")$n)

## [1] 1571

Sie benötigen 1571 Probanden pro Untersuchungsgruppe.

33.11.2 Umfragen

Soll eine Befragung (ein Survey) durchgeführt werden, kann die benötigte Fallzahl über das Zusatzpaket samplingbook errechnet werden.

Geht es um numerische Werte, wird die Funktion sample.size.mean() verwendet. Ihr übergibt man Fehlerspanne e (ein Präzisionswert, der besagt, wie breit das Konfidenzintervall - nach oben sowie nach unten - sein darf, für gewöhnlich 5% bzw. 0.05), das Konfidenzniveau, die Standardabweichung S innerhalb der Grundgesamtheit (füre gewöhnlich 50% bzw. 0.5) sowie die Populationsgröße. Standardmäßig ist die Populationsgröße auf N=Inf (für unendlich) gesetzt.

# Berechne Fallzahl für einen Survey
# Konfidenzniveau auf 95%, Fehlerbereich 5%.
# In der Grundgesamtheit hat der Wert eine
# Standardabweichung von S=0.5
samplingbook::sample.size.mean(e=0.05, S=0.5, level=0.95 )

##
## sample.size.mean object: Sample size for mean estimate
## Without finite population correction: N=Inf, precision e=0.05 and standard deviation S=0.5
##
## Sample size needed: 385

Ist die Grundgesamtheit bekannt, kann dies mit N angegeben werden.

# Die Grundgesamtheit ist 1.500 Personen groß
samplingbook::sample.size.mean(e=0.05, S=0.5, level=0.95, N=1500 )

##
## sample.size.mean object: Sample size for mean estimate
## With finite population correction: N=1500, precision e=0.05 and standard deviation S=0.5
##
## Sample size needed: 306

Sollen Prozentwerte kategorialer Variablen erfragt werden (z.B. der Anteil weiblicher Pflegepersonen), wird die Funktion sample.size.prop() verwendet. Ihr werden ebenso e, N und level (Konfidenzniveau) übergeben. Statt der Standardabweichung wird über den Parameter P die prozentuale Verteilung in der Grundgesamtheit angegeben.

# Berechne Fallzahl für einen Survey
# unsere Werte düren maximal 5% abweichen
# Konfidenzniveau auf 95%
# In der Grundgesamtheit hat der Wert eine
# Verteilung von 65%
# Die Grundgesamtheit ist 1.500 Personen groß
samplingbook::sample.size.prop(e=0.05, P=0.65, level=0.95, N=1500)

##
## sample.size.prop object: Sample size for proportion estimate
## With finite population correction: N=1500, precision e=0.05 and expected proportion P=0.65
##
## Sample size needed: 284

33.11.2.1 Tabellen

Um ein Gefühl für die benötigten Stichprobengrößen zu erhalten, können wir Tabellen erzeugen, in denen die benötigten Fallzahlen beispielsweise in Abhängigkeit zur Populationsgröße angezeigt werden. Die Tabelle soll Werte für metrische und kategoriale Items anzeigen, wobei wir jeweils das 90%-, 95% und 99%-Konfidenzintervall einsehen wollen. In diesem Beispiel gehen wir für metrische Daten von 30% Streuung aus (S=0.3) und setzen die Fehlerspanne e auf 5%. Für kategoriale Items nehmen wir eine Verteilung von 50% an (P=0.5).

# gewünschte Populationsgrößen
Population <- c(100, 200, 300, 400, 500, 600, 700, 800, 900, 1000,
                1500, 2000, 4000, 6000, 8000, 10000, Inf)

# überführe in Datenframe
df <- data.frame(Population)

Mit der Funktion sapply() können wir sample.size.mean() und sample.size.prop() auf jeden Wert des Vectors Population anwenden. Die Ergebnisse schreiben wir als neue Variable in unser Datenframes df.

## Wende sample.size.mean() auf jeden Wert in "Population" an
# 90%-Konfidenzintervall
df$ConA10 <- sapply(Population, FUN=function(x) samplingbook::sample.size.mean(e=0.05, S=0.3, level=0.90, N=x)$n)
# 95%-Konfidenzintervall
df$ConA05 <- sapply(Population, FUN=function(x) samplingbook::sample.size.mean(e=0.05, S=0.3, level=0.95, N=x)$n)
# 99%-Konfidenzintervall
df$ConA01 <- sapply(Population, FUN=function(x) samplingbook::sample.size.mean(e=0.05, S=0.3, level=0.99, N=x)$n)

## Wende sample.size.prop() auf jeden Wert in "Population" an
# 90%-Konfidenzintervall
df$CatA10 <- sapply(Population, FUN=function(x) samplingbook::sample.size.prop(e=0.05, P=0.5, level=0.9, N=x)$n)
# 95%-Konfidenzintervall
df$CatA05 <- sapply(Population, FUN=function(x) samplingbook::sample.size.prop(e=0.05, P=0.5, level=0.95, N=x)$n)
# 99%-Konfidenzintervall
df$CatA01 <- sapply(Population, FUN=function(x) samplingbook::sample.size.prop(e=0.05, P=0.5, level=0.99, N=x)$n)

Die Variablennamen ConA10 - CatA01 sind etwas kryptisch und für eine “schöne” Tabelle eher ungeeignet. Daher ändern wir die Variablennamen mittels colnames() in leserliche Spaltennamen um. Zuvor speichern wir aber unser Datenframe in ein neues Objekt df2, denn mit den veränderten Variablennamen können wir später viel schlechter weiterarbeiten.

# Mache Sicherungskopie von df
df2 <- df

# ändere die Variablennamen für eine "schöne" Tabelle
Kopfzeile <- c("Population N", "metrisch 90%", "metrisch 95%", "metrisch 99%",
               "kategor 90%", "kategor 95%", "kategor 99%")
colnames(df) <- Kopfzeile

Mit den neuen Variablennamen sieht die Tabelle nun so aus:

Population N | metrisch 90% | metrisch 95% | metrisch 99% | kategor 90% | kategor 95% | kategor 99%
100 | 50 | 59 | 71 | 74 | 80 | 87
200 | 66 | 82 | 109 | 115 | 132 | 154
300 | 74 | 95 | 133 | 143 | 169 | 207
400 | 79 | 103 | 150 | 162 | 196 | 250
500 | 82 | 109 | 162 | 176 | 218 | 286
600 | 84 | 113 | 171 | 187 | 235 | 316
700 | 86 | 116 | 179 | 196 | 249 | 341
800 | 87 | 118 | 184 | 203 | 260 | 363
900 | 88 | 120 | 189 | 209 | 270 | 382
1000 | 89 | 122 | 193 | 213 | 278 | 399
1500 | 92 | 127 | 207 | 230 | 306 | 461
2000 | 93 | 130 | 214 | 239 | 323 | 499
4000 | 96 | 134 | 226 | 254 | 351 | 570
6000 | 96 | 136 | 230 | 259 | 362 | 598
8000 | 97 | 136 | 232 | 262 | 367 | 613
10000 | 97 | 137 | 234 | 264 | 370 | 623
Inf | 98 | 139 | 239 | 271 | 385 | 664

Und um auf das nächste Kapitel vorzugreifen, können wir die Daten der Tabelle in einem einfachen Plot graphisch darstellen.

# Erzeuge den Grund-Plot mit 1 y-Variable
plot(Population, df2$ConA01, type="b", ylim=c(0,670),
        ylab="Fallzahl", col="darkgreen", pch=19, lty=2)

# Füge weitere y-Variablen mit anderer Farbe hinzu
lines(Population, df2$ConA05, col="seagreen", type="b", pch=19, lty=1)
lines(Population, df2$ConA10, col="lightgreen", type="b", pch=19, lty=3)
lines(Population, df2$CatA01, col="darkblue", type="b", pch=18, lty=2)
lines(Population, df2$CatA05, col="blue", type="b", pch=18, lty=1)
lines(Population, df2$CatA10, col="lightblue", type="b", pch=18, lty=3)

# Füge eine Legendenbox hinzu
legend(x=0, y=670,
       legend = c("metrisch 90%", "metrisch 95%", "metrisch 99%",
               "kategori 90%", "kategori 95%", "kategori 99%"),
       col = c("lightgreen", "seagreen", "darkgreen",
               "lightblue", "blue", "darkblue"),
       lty=c(3,1,2), cex=0.8)

Sieht kompliziert aus? Warten Sie es ab, es wird alles im folgenden Kapitel erklärt.

34 Diagramme plotten

In diesem Kapitel werden die Plotfunktionen des klassischen R vorgestellt. Sie erzeugen immernoch brauchbare Ergebnisse, und man kann viele Einstellungen vornehmen. Wenn Sie allerdings ein etwas komplexeres Diagramm aufbauen möchten, lohnt es sich nicht, die Spezialeinstellungen der klassischen Plotfunktionen zu durchstöbern. Schwenken Sie in diesem Falle lieber gleich zum moderneren ggplot() um, welches in Kapitel Kapitel 35 vorgestellt wird. So kommen Sie viel “einfacher” an Ihre Ziel.

Die universelle Funktion zum erstellen von Diagrammen heisst plot(). Mit ihr werden ein Großteil der Diagramme erstellt, und viele Funktionen rufen letztendlich auch nur plot() auf.

Da plot() so universal ist, nimmt sie viele Parameter entgegen. Die nachfolgende Tabelle zeigt diese Parameter in einer Übersicht. Viele der Parameter sind auch in weiteren Plotfunktionen implementiert. Es kann dennoch nie schaden, sich mit ?barplot in der R-Konsole die explizieten Parameter zu vergegenwärtigen.

Parameter der Funktion plot()

Parameter | Argumente / Beschreibung | Beispiel
type | legt den Diagrammtyp fest | type="p"
(mittlerweile veraltet, | l = Liniendiagramm |
funktioniert aber noch) | p = Punktdiagramm |
| b = (both) Kombination aus l und p |
| s = Stufendiagramm |
| h = Histogramm-artig(!) |
| n = keine Darstellung |
col | Füllfarbe | col="blue"
| (colors() zeigt alle Farben) | col="#112233" (RGB)
| gray = schwarz bis gray99 = weiß | col="gray77"
main | Überschrift | main="Mein Plot"
sub | Untertitel | main="Abbildung 12"
xlab | Beschriftung der X-Achse | xlab="Jahrgänge"
ylab | Beschriftung der Y-Achse | ylab="Häufigkeit"
xlim | Start- und Endwert der X-Achse | xlim=c(2,10)
ylim | Start- und Endwert der Y-Achse | ylim=c(0,100)
xaxt | X-Achse komplett unbeschriftet | xaxt="n"
yaxt | Y-Achse komplett unbeschriftet | yaxt="n"
pch | Darstellungssymbol | pch=4
cex | Symbolgröße | cex=2
lty | Linientyp | lty=1
lwd | Strichstärke der Linien | lwd=3
bty | Boxtyp, setzt Rahmen ums Plot | bty="o"
| n = kein Rahmen |
| o = komplett |
| C = oben, unten und links |
| L = unten und links |
| u = unten, rechts und links |
| 7 = oben und rechts |
| (die Buchstaben sehen so aus wie der Rahmen) |
font | Fontformat | font=2
| 1 = normal |
| 2 = fett |
| 3 = kursiv |
| 5 = griechische Symbole |

34.1 Punktwolke

Die Punktwolke wird mit besagter Funktion plot() erstellt. Ihr werden zwei Vektoren mit den entsprechenden x und y Werten übergeben.

# erzeuge Testwerte
x <- sort(rnorm(100, mean=12, sd=2))
y <- sort(rnorm(100, mean=140, sd=15))

# Plotten
plot(x,y)

Über die Parameter aus der Tabelle kann das Plot aufgehübscht werden.

plot(x,y, col="blue",
          ylab="Körpergröße", xlab="Alter",
          main="Ein Plot mit R base",
          sub="Dieses Plot wurde aus Zufallswerten erstellt",
          pch=13)

Mit der Funktion abline() kann dem Plot eine Gerade hinzugefügt werden, z.B. eine Regressionsgerade. Die Funktion benötigt als Angaben den Schnitt durch die Y-Achse sowie den Steigungswert. Die Funktion lm() liefert für Regressionsgeraden genau diese Werte.

# Regressionswerte von Körpergröße ~ Alter
lm(y~x)

##
## Call:
## lm(formula = y ~ x)
##
## Coefficients:
## (Intercept)            x
##      55.563        7.096

Das heisst, wir rufen die Funktion innerhalb von abline() auf:

plot(x,y, col="blue",
          ylab="Körpergröße", xlab="Alter",
          main="Ein Plot mit R base",
          sub="Dieses Plot wurde aus Zufallswerten erstellt",
          pch=13)
abline(lm(y~x))

In rot sieht sie evtl. besser aus:

plot(x,y, col="blue",
          ylab="Körpergröße", xlab="Alter",
          main="Ein Plot mit R base",
          sub="Dieses Plot wurde aus Zufallswerten erstellt",
          pch=13)
abline(lm(y~x), col="red")

Mit der Funktion lines() können Linien hinzugefügt werden. Dafür übergeben wir die entsprechenden Koordinaten des Start- und Endpunkts. So können wir z.B. den Mittelwert für Alter markieren.

# Mittelwer als x-Koordinate
mx <- mean(x)

# Berechne Schnittpunkt mit Regressionsgeraden
ymx <-  coef(lm(y~x))[1] + mx*coef(lm(y~x))[2]

plot(x,y, col="blue",
          ylab="Körpergröße", xlab="Alter",
          main="Ein Plot mit R base",
          sub="Dieses Plot wurde aus Zufallswerten erstellt",
          pch=13)
abline(lm(y~x), col="red")

# Füge Mittelwertlinie hinzu
lines(x=c(mx, mx), y=c(0, ymx), lty=3)

Liegen mehrere Variablen in einem Datenframe vor, stellt plot() nach Kombinationen getrennt dar.

x <- data.frame(Lesetest = sort(rnorm(100, mean=20, sd=6)),
                Rechentest = rnorm(100, mean=15, sd=12),
                Rechtschreibung = sort(rnorm(100, mean=21, sd=4))
)

plot(x)

Je mehr Variablen vorliegen, desto komplexer wird der Plot.

x <- data.frame(Lesetest = sort(rnorm(100, mean=20, sd=6)),
                Rechentest = rnorm(100, mean=15, sd=12),
                Rechtschreibung = sort(rnorm(100, mean=21, sd=4)),
                Grammatikfehler = sort(rnorm(100, mean=21, sd=4), decreasing=T)
)

plot(x)

Mit Farben kann man es etwas aufhübschen.

x <- data.frame(Lesetest = sort(rnorm(100, mean=20, sd=6)),
                Rechentest = rnorm(100, mean=15, sd=12),
                Rechtschreibung = sort(rnorm(100, mean=21, sd=4)),
                Grammatikfehler = sort(rnorm(100, mean=21, sd=4), decreasing=T)
)

plot(x, col=rainbow(3))

34.2 verfügbare Farben

Die Funktion colors() zeigt die verfügbaren Farben an.

# zeige verfügbare Farben an
colors()

##   [1] "white"                "aliceblue"            "antiquewhite"
##   [4] "antiquewhite1"        "antiquewhite2"        "antiquewhite3"
##   [7] "antiquewhite4"        "aquamarine"           "aquamarine1"
##  [10] "aquamarine2"          "aquamarine3"          "aquamarine4"
##  [13] "azure"                "azure1"               "azure2"
##  [16] "azure3"               "azure4"               "beige"
##  [19] "bisque"               "bisque1"              "bisque2"
##  [22] "bisque3"              "bisque4"              "black"
##  [25] "blanchedalmond"       "blue"                 "blue1"
##  [28] "blue2"                "blue3"                "blue4"
##  [31] "blueviolet"           "brown"                "brown1"
##  [34] "brown2"               "brown3"               "brown4"
##  [37] "burlywood"            "burlywood1"           "burlywood2"
##  [40] "burlywood3"           "burlywood4"           "cadetblue"
##  [43] "cadetblue1"           "cadetblue2"           "cadetblue3"
##  [46] "cadetblue4"           "chartreuse"           "chartreuse1"
##  [49] "chartreuse2"          "chartreuse3"          "chartreuse4"
##  [52] "chocolate"            "chocolate1"           "chocolate2"
##  [55] "chocolate3"           "chocolate4"           "coral"
##  [58] "coral1"               "coral2"               "coral3"
##  [61] "coral4"               "cornflowerblue"       "cornsilk"
##  [64] "cornsilk1"            "cornsilk2"            "cornsilk3"
##  [67] "cornsilk4"            "cyan"                 "cyan1"
##  [70] "cyan2"                "cyan3"                "cyan4"
##  [73] "darkblue"             "darkcyan"             "darkgoldenrod"
##  [76] "darkgoldenrod1"       "darkgoldenrod2"       "darkgoldenrod3"
##  [79] "darkgoldenrod4"       "darkgray"             "darkgreen"
##  [82] "darkgrey"             "darkkhaki"            "darkmagenta"
##  [85] "darkolivegreen"       "darkolivegreen1"      "darkolivegreen2"
##  [88] "darkolivegreen3"      "darkolivegreen4"      "darkorange"
##  [91] "darkorange1"          "darkorange2"          "darkorange3"
##  [94] "darkorange4"          "darkorchid"           "darkorchid1"
##  [97] "darkorchid2"          "darkorchid3"          "darkorchid4"
## [100] "darkred"              "darksalmon"           "darkseagreen"
## [103] "darkseagreen1"        "darkseagreen2"        "darkseagreen3"
## [106] "darkseagreen4"        "darkslateblue"        "darkslategray"
## [109] "darkslategray1"       "darkslategray2"       "darkslategray3"
## [112] "darkslategray4"       "darkslategrey"        "darkturquoise"
## [115] "darkviolet"           "deeppink"             "deeppink1"
## [118] "deeppink2"            "deeppink3"            "deeppink4"
## [121] "deepskyblue"          "deepskyblue1"         "deepskyblue2"
## [124] "deepskyblue3"         "deepskyblue4"         "dimgray"
## [127] "dimgrey"              "dodgerblue"           "dodgerblue1"
## [130] "dodgerblue2"          "dodgerblue3"          "dodgerblue4"
## [133] "firebrick"            "firebrick1"           "firebrick2"
## [136] "firebrick3"           "firebrick4"           "floralwhite"
## [139] "forestgreen"          "gainsboro"            "ghostwhite"
## [142] "gold"                 "gold1"                "gold2"
## [145] "gold3"                "gold4"                "goldenrod"
## [148] "goldenrod1"           "goldenrod2"           "goldenrod3"
## [151] "goldenrod4"           "gray"                 "gray0"
## [154] "gray1"                "gray2"                "gray3"
## [157] "gray4"                "gray5"                "gray6"
## [160] "gray7"                "gray8"                "gray9"
## [163] "gray10"               "gray11"               "gray12"
## [166] "gray13"               "gray14"               "gray15"
## [169] "gray16"               "gray17"               "gray18"
## [172] "gray19"               "gray20"               "gray21"
## [175] "gray22"               "gray23"               "gray24"
## [178] "gray25"               "gray26"               "gray27"
## [181] "gray28"               "gray29"               "gray30"
## [184] "gray31"               "gray32"               "gray33"
## [187] "gray34"               "gray35"               "gray36"
## [190] "gray37"               "gray38"               "gray39"
## [193] "gray40"               "gray41"               "gray42"
## [196] "gray43"               "gray44"               "gray45"
## [199] "gray46"               "gray47"               "gray48"
## [202] "gray49"               "gray50"               "gray51"
## [205] "gray52"               "gray53"               "gray54"
## [208] "gray55"               "gray56"               "gray57"
## [211] "gray58"               "gray59"               "gray60"
## [214] "gray61"               "gray62"               "gray63"
## [217] "gray64"               "gray65"               "gray66"
## [220] "gray67"               "gray68"               "gray69"
## [223] "gray70"               "gray71"               "gray72"
## [226] "gray73"               "gray74"               "gray75"
## [229] "gray76"               "gray77"               "gray78"
## [232] "gray79"               "gray80"               "gray81"
## [235] "gray82"               "gray83"               "gray84"
## [238] "gray85"               "gray86"               "gray87"
## [241] "gray88"               "gray89"               "gray90"
## [244] "gray91"               "gray92"               "gray93"
## [247] "gray94"               "gray95"               "gray96"
## [250] "gray97"               "gray98"               "gray99"
## [253] "gray100"              "green"                "green1"
## [256] "green2"               "green3"               "green4"
## [259] "greenyellow"          "grey"                 "grey0"
## [262] "grey1"                "grey2"                "grey3"
## [265] "grey4"                "grey5"                "grey6"
## [268] "grey7"                "grey8"                "grey9"
## [271] "grey10"               "grey11"               "grey12"
## [274] "grey13"               "grey14"               "grey15"
## [277] "grey16"               "grey17"               "grey18"
## [280] "grey19"               "grey20"               "grey21"
## [283] "grey22"               "grey23"               "grey24"
## [286] "grey25"               "grey26"               "grey27"
## [289] "grey28"               "grey29"               "grey30"
## [292] "grey31"               "grey32"               "grey33"
## [295] "grey34"               "grey35"               "grey36"
## [298] "grey37"               "grey38"               "grey39"
## [301] "grey40"               "grey41"               "grey42"
## [304] "grey43"               "grey44"               "grey45"
## [307] "grey46"               "grey47"               "grey48"
## [310] "grey49"               "grey50"               "grey51"
## [313] "grey52"               "grey53"               "grey54"
## [316] "grey55"               "grey56"               "grey57"
## [319] "grey58"               "grey59"               "grey60"
## [322] "grey61"               "grey62"               "grey63"
## [325] "grey64"               "grey65"               "grey66"
## [328] "grey67"               "grey68"               "grey69"
## [331] "grey70"               "grey71"               "grey72"
## [334] "grey73"               "grey74"               "grey75"
## [337] "grey76"               "grey77"               "grey78"
## [340] "grey79"               "grey80"               "grey81"
## [343] "grey82"               "grey83"               "grey84"
## [346] "grey85"               "grey86"               "grey87"
## [349] "grey88"               "grey89"               "grey90"
## [352] "grey91"               "grey92"               "grey93"
## [355] "grey94"               "grey95"               "grey96"
## [358] "grey97"               "grey98"               "grey99"
## [361] "grey100"              "honeydew"             "honeydew1"
## [364] "honeydew2"            "honeydew3"            "honeydew4"
## [367] "hotpink"              "hotpink1"             "hotpink2"
## [370] "hotpink3"             "hotpink4"             "indianred"
## [373] "indianred1"           "indianred2"           "indianred3"
## [376] "indianred4"           "ivory"                "ivory1"
## [379] "ivory2"               "ivory3"               "ivory4"
## [382] "khaki"                "khaki1"               "khaki2"
## [385] "khaki3"               "khaki4"               "lavender"
## [388] "lavenderblush"        "lavenderblush1"       "lavenderblush2"
## [391] "lavenderblush3"       "lavenderblush4"       "lawngreen"
## [394] "lemonchiffon"         "lemonchiffon1"        "lemonchiffon2"
## [397] "lemonchiffon3"        "lemonchiffon4"        "lightblue"
## [400] "lightblue1"           "lightblue2"           "lightblue3"
## [403] "lightblue4"           "lightcoral"           "lightcyan"
## [406] "lightcyan1"           "lightcyan2"           "lightcyan3"
## [409] "lightcyan4"           "lightgoldenrod"       "lightgoldenrod1"
## [412] "lightgoldenrod2"      "lightgoldenrod3"      "lightgoldenrod4"
## [415] "lightgoldenrodyellow" "lightgray"            "lightgreen"
## [418] "lightgrey"            "lightpink"            "lightpink1"
## [421] "lightpink2"           "lightpink3"           "lightpink4"
## [424] "lightsalmon"          "lightsalmon1"         "lightsalmon2"
## [427] "lightsalmon3"         "lightsalmon4"         "lightseagreen"
## [430] "lightskyblue"         "lightskyblue1"        "lightskyblue2"
## [433] "lightskyblue3"        "lightskyblue4"        "lightslateblue"
## [436] "lightslategray"       "lightslategrey"       "lightsteelblue"
## [439] "lightsteelblue1"      "lightsteelblue2"      "lightsteelblue3"
## [442] "lightsteelblue4"      "lightyellow"          "lightyellow1"
## [445] "lightyellow2"         "lightyellow3"         "lightyellow4"
## [448] "limegreen"            "linen"                "magenta"
## [451] "magenta1"             "magenta2"             "magenta3"
## [454] "magenta4"             "maroon"               "maroon1"
## [457] "maroon2"              "maroon3"              "maroon4"
## [460] "mediumaquamarine"     "mediumblue"           "mediumorchid"
## [463] "mediumorchid1"        "mediumorchid2"        "mediumorchid3"
## [466] "mediumorchid4"        "mediumpurple"         "mediumpurple1"
## [469] "mediumpurple2"        "mediumpurple3"        "mediumpurple4"
## [472] "mediumseagreen"       "mediumslateblue"      "mediumspringgreen"
## [475] "mediumturquoise"      "mediumvioletred"      "midnightblue"
## [478] "mintcream"            "mistyrose"            "mistyrose1"
## [481] "mistyrose2"           "mistyrose3"           "mistyrose4"
## [484] "moccasin"             "navajowhite"          "navajowhite1"
## [487] "navajowhite2"         "navajowhite3"         "navajowhite4"
## [490] "navy"                 "navyblue"             "oldlace"
## [493] "olivedrab"            "olivedrab1"           "olivedrab2"
## [496] "olivedrab3"           "olivedrab4"           "orange"
## [499] "orange1"              "orange2"              "orange3"
## [502] "orange4"              "orangered"            "orangered1"
## [505] "orangered2"           "orangered3"           "orangered4"
## [508] "orchid"               "orchid1"              "orchid2"
## [511] "orchid3"              "orchid4"              "palegoldenrod"
## [514] "palegreen"            "palegreen1"           "palegreen2"
## [517] "palegreen3"           "palegreen4"           "paleturquoise"
## [520] "paleturquoise1"       "paleturquoise2"       "paleturquoise3"
## [523] "paleturquoise4"       "palevioletred"        "palevioletred1"
## [526] "palevioletred2"       "palevioletred3"       "palevioletred4"
## [529] "papayawhip"           "peachpuff"            "peachpuff1"
## [532] "peachpuff2"           "peachpuff3"           "peachpuff4"
## [535] "peru"                 "pink"                 "pink1"
## [538] "pink2"                "pink3"                "pink4"
## [541] "plum"                 "plum1"                "plum2"
## [544] "plum3"                "plum4"                "powderblue"
## [547] "purple"               "purple1"              "purple2"
## [550] "purple3"              "purple4"              "red"
## [553] "red1"                 "red2"                 "red3"
## [556] "red4"                 "rosybrown"            "rosybrown1"
## [559] "rosybrown2"           "rosybrown3"           "rosybrown4"
## [562] "royalblue"            "royalblue1"           "royalblue2"
## [565] "royalblue3"           "royalblue4"           "saddlebrown"
## [568] "salmon"               "salmon1"              "salmon2"
## [571] "salmon3"              "salmon4"              "sandybrown"
## [574] "seagreen"             "seagreen1"            "seagreen2"
## [577] "seagreen3"            "seagreen4"            "seashell"
## [580] "seashell1"            "seashell2"            "seashell3"
## [583] "seashell4"            "sienna"               "sienna1"
## [586] "sienna2"              "sienna3"              "sienna4"
## [589] "skyblue"              "skyblue1"             "skyblue2"
## [592] "skyblue3"             "skyblue4"             "slateblue"
## [595] "slateblue1"           "slateblue2"           "slateblue3"
## [598] "slateblue4"           "slategray"            "slategray1"
## [601] "slategray2"           "slategray3"           "slategray4"
## [604] "slategrey"            "snow"                 "snow1"
## [607] "snow2"                "snow3"                "snow4"
## [610] "springgreen"          "springgreen1"         "springgreen2"
## [613] "springgreen3"         "springgreen4"         "steelblue"
## [616] "steelblue1"           "steelblue2"           "steelblue3"
## [619] "steelblue4"           "tan"                  "tan1"
## [622] "tan2"                 "tan3"                 "tan4"
## [625] "thistle"              "thistle1"             "thistle2"
## [628] "thistle3"             "thistle4"             "tomato"
## [631] "tomato1"              "tomato2"              "tomato3"
## [634] "tomato4"              "turquoise"            "turquoise1"
## [637] "turquoise2"           "turquoise3"           "turquoise4"
## [640] "violet"               "violetred"            "violetred1"
## [643] "violetred2"           "violetred3"           "violetred4"
## [646] "wheat"                "wheat1"               "wheat2"
## [649] "wheat3"               "wheat4"               "whitesmoke"
## [652] "yellow"               "yellow1"              "yellow2"
## [655] "yellow3"              "yellow4"              "yellowgreen"

Der Output ist 657 Farben lang.

# wieviele Farben gibt es?
length(colors())

## [1] 657

Abb. 34.1: Alle R Farben

Der R-Code, mit dem Abbildung 34.1 erstellt wurde, ist in Abschnitt 37.1 hinterlegt.

Eine weitere Übersicht der Farben in “echten Farbtönen” findet sich bei Björn Walther: https://www.bjoernwalther.com/farben-in-r-der-col-befehl/

Zu erwähnen ist noch die Farbe gray, die in den Abstufungen gray0 (=schwarz) bis gray99 (= weiß) knapp 99 Grauschattierungen ermöglicht. Dies ist bei schwarz-weiß Publikationen sehr hilfreich.

Soll es möglichst bunt sein, erzeugt die Funktion rainbow() die gewünschte Anzahl an Farben.

34.3 Liniendiagramm

Ein Liniendiagramm wird mit der Parametereinstellung type="l" erzeugt.

# Erzeuge ein paar Daten
x  <-1:20
y1 <- x*x+1

# Erzeuge Liniendiagramm
plot(x, y1, type="l", pch=19, col="red", xlab="x", ylab="y")

Mit type="b" (für both) wird sowohl Punkt- als auch Liniendiagramm erzeugt

plot(x, y1, type="b", pch=19, col="red", xlab="x", ylab="y")

Mit der Funktion lines() können weitere Linien hinzugefügt werden.

# erzeuge zweite Y-Reihe
y2 <- 2*y1

plot(x, y1, type="b", pch=19, col="red", xlab="x", ylab="y")
# Füge Linie hinzu
lines(x, y2, pch=18, col="blue", type="b", lty=2)

34.4 Histogram

Ein Histogram wird mit der Funktion hist() erstellt. Plotten wir ein Histogramm der Variable Alter im Beispieldatensatz pf8.

# lade Datensatz "pf8"
load(url("https://www.produnis.de/R/data/pf8.RData"))

# erstelle Histogramm von Variable "Alter" aus Datensatz "pf8"
hist(pf8$Alter)

Zum Vergleich:

# zum Vergleich plot()
plot(pf8$Alter, type="h")

Wie Sie sehen ist das plot()-Resultat nur entfernt dem Histogram ähnlich.

Die Klassengröße des Histograms kann mit dem Parameter breaks() übergeben werden. Entweder schreiben wir die Anzahl der “Balken”, dann versucht R das “möglichst gut” auszuführen, oder wir übergeben einen Vektor mit den konkreten Breakpunkten.

# Histogramm mit "möglichst" 4 Balken
hist(pf8$Alter, breaks=4)

Wie Sie sehen stellt R dennoch 5 Balken dar, weil es so “besser” aussieht als mit nur 4 Balken. Die Anzahl der Balken ist also kein konkreter Befehl, sondern eher ein Serviervorschlag.

Über die Parameter kann der Plot nun aufgehübscht werden.

# Histogramm mit "möglichst" 4 Balken
# in schön
hist(pf8$Alter, breaks=4,
     # Überschrift
     main="Mein Histogramm",
     # Unterschrift
     sub="Abbildung x.y: Ein Histogramm",
               # X-achse
               xlab="Alter in Jahren",
               # Y-Achse
               ylab="Häufigkeit",
               # blaue Balken
               col="blue")

34.5 Kreisdiagramm

Ein Kreisdiagramm wird mit der Funktion pie() erzeugt. Die Funktion benötigt einen Vektor mit den Anteilsgrößen.

# Kreisdiagramm 60 / 40
pie(c(60,40))

Soll z.B. ein Kreisdiagramm aus einem Faktor erzeugt werden, gelingt dies über die Integration der Funktion table().

# Kreisdiagramm von "Geschlecht" in "pf8"
pie(table(pf8$Geschlecht))

Mit den Parametern col, clockwise und labels können Sie weitere Anpassungen vornehmen.

pie(c(60,40), labels=c("ja", "nein"), col=c("azure", "coral"))

pie(table(pf8$Geschlecht), clockwise=TRUE, col=c("burlywood", "chartreuse","black"))

Und auch lustige Dinge kann man plotten1:

par(mar = c(0, 1, 0, 1))
pie(
  c(280, 60, 20),
  c('Sky', 'Sunny side of pyramid', 'Shady side of pyramid'),
  col = c('#0292D8', '#F7EA39', '#C4B632'),
  init.angle = -50, border = NA
)

34.6 Säulendiagramm

Säulendiagramme werden mit der Funktion barplot() erstellt. Sie benötigt einen Vektor mit den jeweiligen Ausprägungen der Säulen sowie optional die Namen der Säulen über den Parameter names.arg:

# Säulendiagramm
barplot(c(10,40,20), names.arg=c("Dritter", "Erster", "Zweiter"))

Liegen die Daten als Faktor vor, können sie mit Hilfe der Funktion table() übergeben werden.

# Säulendiagramm von "Standort" in "pf8"
barplot(table(pf8$Standort))

Liegen die Daten als wide table vor (z.B als Matrix mit bedeutsamen rownames), so wie unsere Matrix der Pflegeberufe, erstellt barplot() gruppierte Säulen. Über das Argument legend.text können die Reihennamen übergeben werden.

# Säulendiagramm der Matrix Pflegeberufe
barplot(Pflegeberufe, beside=T, col=rainbow(5), legend.text = rownames(Pflegeberufe))

Wird der Parameter beside weggelassen (entspricht FALSE), werden die Reihen in einer gemeinsamen Säule zusammengefasst.

# Säulendiagramm der Matrix Pflegeberufe
barplot(Pflegeberufe, col=rainbow(5), legend.text = rownames(Pflegeberufe))

Das entspricht vom Prinzip her der Abbildung Abbildung 8.1.

34.7 Balkendiagramm

Balkendiagramme werden genau so erstellt wie Säulendiagramme, nämlich über die Funktion barplot() mit dem Parameter horiz=TRUE.

# Balkendiagramm der Matrix Pflegeberufe
# diesmal Schwarz-weiss
barplot(Pflegeberufe,
            # schalte um auf "Balkendiagramm"
            horiz=TRUE,
            # Graustufen   führende 0 kann weggelassen werden
            col=gray(c(.1, .2, .3, .4, .5)),
            legend.text = rownames(Pflegeberufe))

# Balkendiagramm "category" aus "mma"
# diesmal Schwarz-weiss
barplot(table(mma$Kategorie),
         horiz=TRUE,
         col=rainbow(7),
         # Legende aus Faktorenlevels
         legend.text = levels(mma$Kategorie))

34.8 Boxplot

Boxplots können mit der Funktion boxplot() erstellt werden.

# lade Datensatz
load(url("https://www.produnis.de/R/data/nw.RData"))

# Boxplot des Alters in Datensatz "nw"
boxplot(nw$age)

Die Funktion erkennt auch die Syntax “erklärt durch”.

# Boxplot des Alters in Datensatz "nw" erklärt durch "sex"
boxplot(age ~ sex, data=nw)

Über die Parameter frame, border, col und notch können weitere Anpassungen erfolgen.

boxplot(age ~ sex, data=nw, frame = FALSE, border="blue", horizontal = TRUE)

boxplot(age ~ sex, data=nw, frame = FALSE, col=c("green", "red"), notch = TRUE)

Weitere innere Gruppierungen können mit einem Sternchen * übergeben werden.

boxplot(age~sex*timework, data=nw, col=c("blue", "skyblue"), notch = TRUE)

## Warning in (function (z, notch = FALSE, width = NULL, varwidth = FALSE, :
## einige notches liegen außerhalb der hinges ('box'): evtl. notch=FALSE setzen

34.9 Polygone

Mit der Funktion polygon() können Polygone hinzugefügt werden. Ein Polygon wird so gezogen, als würden wir es mit einem Stift auf Papier zeichnen, ohne den Stift dabei abzusetzen. Das heisst, wir müssen “hin und zurück” zeichnen.

# erzeuge Testwerte
x <- sort(rnorm(100, mean=12, sd=2))
y <- sort(rnorm(100, mean=140, sd=15))

# Mittelwer als x-Koordinate
mx <- mean(x)

# Berechne Schnittpunkt mit Regressionsgeraden
ymx <-  coef(lm(y~x))[1] + mx*coef(lm(y~x))[2]

plot(x,y, col="blue",
          ylab="Körpergröße", xlab="Alter",
          main="Ein Plot mit R base",
          sub="Dieses Plot wurde aus Zufallswerten erstellt",
          pch=13)
abline(lm(y~x), col="red")

# Füge Mittelwertlinie hinzu
lines(x=c(mx, mx), y=c(0, ymx), lty=3)

# füge Polygon hinzu
polygon(x=c(x, max(x), min(x)), y=c(y, 0, 0), col="snow2")

So kann ein Diagramm der Standardnormalverteilung erstellt werden, wobei beliebige Bereiche hervorgehoben werden können.

# erzeuge 10.000 Zufallswerte der Standardnormalverteilung
x <- sort(rnorm(10000))

# erzeuge die passenden Dichterwerte
y <- dnorm(x)

# beginne mit Plot
plot(x, y, ylab="Dichtefunktion", xlab="x", main="Die Standardnormalverteilung",
     sub="Bereich von 1 Standardabweichung")

# füge gefülltes Polygon hinzu
polygon(x=c(x, max(x), min(x)), y=c(y, 0,0 ), col="snow2")

# füge 1 Standardabweichung hinzu
lines(x=c(-1, -1), y=c(0, dnorm(-1)), lty=3)
lines(x=c(1, 1), y=c(0, dnorm(1)), lty=3)

# füge Polygon für 1. Standardbweichung hinzu
xsd1 <- x[x<1 & x>-1]
ysd1 <- dnorm(xsd1)
polygon(x=c(xsd1, max(xsd1), min(xsd1)), y=c(ysd1, 0, 0 ), col="green")

Der Bereich von zwei Standardabweichungen kann wie folgt hinzugefügt werden. Dies “übermalt” jedoch die grüne Fläche.

plot(x, y, ylab="Dichtefunktion", xlab="x", main="Die Standardnormalverteilung",
     sub="Bereich von 2 Standardabweichungen")

# füge 2. Standardabweichung hinzu
lines(x=c(-2, -2), y=c(0, dnorm(-2)), lty=6)
lines(x=c(2, 2), y=c(0, dnorm(2)), lty=6)

# füge Polygon für 2. Standardabweichung
# dies übermalt das 1. Polygon
xsd2 <- x[x<2 & x>-2]
ysd2 <- dnorm(xsd2)
polygon(x=c(xsd2, max(xsd2), min(xsd2)), y=c(ysd2, 0, 0 ), col="blue")

In Abschnitt 37.6 werden Polygone verwendet, um eine Übersichtsgrafik der BMI-Klassen in Abhängigkeit von Körpergröße und Körpergewicht zu erstellen.

34.10 QQ-Plots

Zur Überprüfung, ob eine Variable normalverteilt ist, werden häufig Quantil-Quantil-Diagramme (QQ-Plots) erzeugt. In R lassen sich diese mit den Funktionen qqnorm() und qqline() erstellen.

# QQ-Plot für "Alter" aus dem Datensatz "epa"
qqnorm(epa$age, col = "steelblue")
qqline(epa$age, col = "purple", lwd = 2)

Zusätzlich steht die abgewandelte Funktion qqPlot() aus dem car-Paket zur Verfügung. Dieses zeichnet standardmäßig das Konfidenzintervall mit ein, und kann neben der Normalverteilung auch anderen Verteilungsformen prüfen.

car::qqPlot(epa$age, col="darkblue",
                     col.lines = "red")

## [1] 182 371

34.11 Legendenbox

Mit der Funktion legend() kann dem Plot eine Legendenbox hinzugefügt werden. Die ersten beiden Parameter legen dabei die X- und Y-Achsenpositionen der Box fest.

# Erzeuge ein paar Daten
x  <-1:20
y1 <- x*x+1
y2 <- 2*y1

#
plot(x, y1, type="b", pch=19, col="red", xlab="x", ylab="y")
# Füge Linie hinzu
lines(x, y2, pch=18, col="blue", type="b", lty=2)
# Füge Legendenbox hinzu
legend(1, 95, legend=c("Männer", "Frauen"),
       col=c("red", "blue"), lty=1:2, cex=0.8)

Zusätzlich nimmt legend() folgende Parameter entgegen:

title - Titel der Legendenbox

text.font - ein Integer der das Aussehen festlegt:

1 = normal

2 = fett

3 = kursiv

4 = fett und kursiv

bg - die Hintergrundfarbe der Legendenbox

box.lty - die Strichart des Legenrahmens

box.lwd - die Strichstärke des Rahmens

box.col - die Farbe des Rahmens

horiz - Box horizontal ausrichten (TRUE/FALSE)

legend - Variablennamen der Objekte

lty - Strichart der Variablen

col / fill - Füll- und Strichfarben der Variablen

plot(x, y1, type="b", pch=19, col="red", xlab="x", ylab="y")
lines(x, y2, pch=18, col="blue", type="b", lty=2)
# Füge Legendenbox hinzu
legend(1, 95, legend=c("Männer", "Frauen"),
       col=c("red", "blue"), lty=1:2, cex=0.8,
       bg="lightgreen", box.lty = 4, box.lwd = 2, box.col = "purple")

Die Position der Box kann auch mit den Keywords "bottomright", "bottom", "bottomleft", "left", "topleft", "top", "topright", "right" und "center" bestimmt werden.

plot(x, y1, type="b", pch=19, col="red", xlab="x", ylab="y")
lines(x, y2, pch=18, col="blue", type="b", lty=2)
# Füge Legendenbox hinzu
legend("topleft", legend=c("Männer", "Frauen"),
       col=c("red", "blue"), lty=1:2, cex=0.8,
       bg="lightgreen", box.lty = 4, box.lwd = 2, box.col = "purple")

Mit dem Parameter horiz=TRUE werden die Variablennamen horizontal aneinandergereiht.

plot(x, y1, type="b", pch=19, col="red", xlab="x", ylab="y")
lines(x, y2, pch=18, col="blue", type="b", lty=2)
# Füge Legendenbox hinzu
legend("topleft", legend=c("Männer", "Frauen"),
       col=c("red", "blue"), lty=1:2, cex=0.8,
       bg="lightgreen", box.lty = 4, box.lwd = 2, box.col = "purple",
       horiz=TRUE)

34.12 Diagramme speichern

Zum Speichern der Diagramme bietet die Formate jpeg, png, bmp und tiff an. Und genau so heissen auch die jeweiligen Speicherfunktionen. Dieser Befehl speichert das Diagramm im Format png:

# Speichere nachfolgendes als PNG
png("Testplot.png", width = 500, height = 500, units = "px", pointsize = 12)

# erzeuge Diagramm
pie(c(60,40))

# speichern abschließen
dev.off()

Wie Sie sehen wird zunächst die Funktion png() aufgerufen. In ihr wird der Dateiname des Diagramms sowie dessen Dimensionen angegeben. Anschließend folgt der Aufruf der Plotbefehle, in diesem Falle pie(). Mit dem Befehl dev.off() (für device off ) wird das Speichern abgeschlossen. Die eigentlichen Diagrammbefehle müssen also zwischen der Speicherfunktion (png()) und dev.off() stehen.

Soll das Diagramm als jpeg gespeichert werden, lautet die Befehlskette entsprechend:

# Speichere nachfolgendes als JPEG
jpeg("Testplot2.jpg", width = 500, height = 500, units = "px", pointsize = 12)

# erzeuge Diagramm
barplot(c(10,40,20), names.arg=c("Dritter", "Erster", "Zweiter"))

# speichern abschließen
dev.off()

Genau so funktionieren auch die Funktionen bmp() und tiff(), aber diese Formate verwendet heute eigentlich niemand mehr.

1. https://www.shirin-glander.de/2017/09/moving-my-blog-to-blogdown/

35 Diagramme mit ggplot()

Das Zusatzpaket ggplot (für grammar of graphics plot) ist Hadley Wickhams (2012) R-Implementation der Grammar of Graphics von Leland Wilkinson (2005). Es ist eines der ersten Pakete des Tidyverse (welches damals noch nicht so hieß). Die Funktion zum plotten heisst ggplot(), das Installationspaket in R lautet jedoch ggplot2.

Unter https://ggplot2-book.org ist eine detaillierte Anleitung in englischer Sprache von Hadely Wickham et al. (2022) verfügbar.

# ggplot2 installieren
install.packages("ggplot2", dependencies=T)

# ggplot2 aktivieren
library(ggplot2)

Wenn Sie zuvor das Tidyverse (siehe Kapitel Kapitel 25) aktiviert haben…

library(tidyverse)

… wird das ggplot2-Paket ebenfalls aktiviert.

Da ggplot aus dem Tidyverse stammt, ist es von Vorteil, wenn die zu verabreitenden Datensätze dem Prinzip “ein Fall pro Zeile” (tidy data bzw. long table) folgen. Das bedeutet, dass jede Beobachtung (auch Wiederholungen) in einer eigenen Zeile steht, und die jeweiligen Variablen durch die Spalten repräsentiert werden.

35.1 Satzbau

Die Idee einer Grammatik für Diagramme ist, dass jedes Diagramm aus den selben Komponenten (Satzteilen) aufgebaut werden kann. Benötigt werden:

Daten (möglichst im Tidy Data Format)

Aesthetische bzw. visuelle Zuordnungen der Daten zu den Komponenten des Plots

Geometrische Objekte, die im Plot erscheinen sollen

Statistische Transformation der Daten, bevor sie geplottet werden

Coordinates (Koordinaten), um die Lage der geometrischen Objekte zu bestimmen

Scales (Skalen), um den Wertebereich der Datenzuordnung zu bestimmen

Facetten, um den Plot in Teil-Diagramme zu gruppieren

Diese Komponenten werden in ggplot() mit dem + Zeichen aneinander gereiht.

# sinnbildlich
ggplot(daten) +
   visuelle Zuordnung +
     Geometrische Objekte +
       statistische Transformationen +
         Koordinaten +
           Skalen +
             Facetten

Die enthaltenen Funktionen folgen dabei dieser Komponentenstruktur:

Geome (Flächen, Linien, Punkte, usw) beginnen mit geom_, z.B.:

gemo_area(), gemo_density(), gemo_smooth(), gemo_histogram(), gemo_point(), gemo_jitter(), gemo_line(), gemo_boxplot(), gemo_col(), gemo_text()

statistische Funktionen (Häufigkeiten, Mittelwerte, Dichte, usw.) beginnen mit stat_, z.B.:

stat_count(), stat_density(), stat_function(), stat_summary()

Koordinatensystemfunktionen beginnen mit coor_, z.B.:

coord_flip(), coord_trans()

Funktionen zum Manipulieren des grundlegenden Aussehens (Theme), beginnend mit theme_, z.B.:

theme(), theme_bw(), theme_classic()

In RStudio ist ein “Cheatsheet” (siehe Abschnitt 30.1) zu ggplot verlinkt. Hier finden Sie (fast) alle verfügbaren Komponenten zusammengefasst. Klicken Sie in der Menüzeile von RStudio auf Help →\rightarrow Cheatsheets →\rightarrow Data Visualization with ggplot2.

35.2 Erstes Plot

Mit dem Datensatz jgsbook::pf8 erzeugen wir nun unser erstes ggplot.

# Lade Datensatz
load(url("https://www.produnis.de/R/data/pf8.RData"))
# oder
pf8 <- jgsbook::pf8

# Tidyverse aktivieren!
library(tidyverse)

# unser erstes Plot
ggplot(pf8)

“Wie Sie sehen, sehen Sie nichts.”

Wir sehen deshalb nichts, weil wir noch nicht bestimmt haben, was (welche Variablen) wie (Punkte, Linien, Boxplots) wo (x-Achse, y-Achse) geplottet werden soll. Wir müssen diese “Aesthetics” erst definieren.

35.2.1 Aesthetics

Angenommen, uns interessiert das Gewicht sowie das Alter der Probanden. Mit der Funktion aes() können wir die Daten visuell zuordnen. Das Alter soll auf der x-Achse liegen, das Gewicht auf der y-Achse.

# erster Plot mit "Aesthetics"
ggplot(pf8) +
  aes(x = Alter,   # x-Achse
      y = Gewicht) # y-Achse

Ggplot hat die Achsen entsprechend beschriftet und die Wertebereiche anhand der vorhandenen Daten (Minimum, Maximum) ausgewählt.

35.2.2 Geome

Zur Darstellung von Gewicht und Alter werden geometrische Objekte (Geome) benötigt. Diese werden über die Funktionen geom_*() erzeugt, wobei das Sternchen durch die gewünschte gemoetrischen Form ersetzt werden muss.

Die 6 meist-genutzten Geome sind sicherlich:

Insgesamt sind 53 Geome verfügbar:

##  [1] "geom_abline"            "geom_area"              "geom_bar"
##  [4] "geom_bin_2d"            "geom_bin2d"             "geom_blank"
##  [7] "geom_boxplot"           "geom_col"               "geom_contour"
## [10] "geom_contour_filled"    "geom_count"             "geom_crossbar"
## [13] "geom_curve"             "geom_density"           "geom_density_2d"
## [16] "geom_density_2d_filled" "geom_density2d"         "geom_density2d_filled"
## [19] "geom_dotplot"           "geom_errorbar"          "geom_errorbarh"
## [22] "geom_freqpoly"          "geom_function"          "geom_hex"
## [25] "geom_histogram"         "geom_hline"             "geom_jitter"
## [28] "geom_label"             "geom_line"              "geom_linerange"
## [31] "geom_map"               "geom_path"              "geom_point"
## [34] "geom_pointrange"        "geom_polygon"           "geom_qq"
## [37] "geom_qq_line"           "geom_quantile"          "geom_raster"
## [40] "geom_rect"              "geom_ribbon"            "geom_rug"
## [43] "geom_segment"           "geom_sf"                "geom_sf_label"
## [46] "geom_sf_text"           "geom_smooth"            "geom_spoke"
## [49] "geom_step"              "geom_text"              "geom_tile"
## [52] "geom_violin"            "geom_vline"

In unserem Beispiel möchten wir das Gewicht sowie das Alter der Probanden als Punktwolke darstellen. Das geeignete Geom ist geom_point().

# unser erstes Plot, mit "Aesthetics" und "Geom"
ggplot(pf8) +
  aes(x = Alter,
      y = Gewicht) +
  geom_point()

35.2.3 mehr Aesthetics

Angenommen, wir möchten das Gewicht sowie das Alter der Probanden als Punktwolke darstellen, aber graphisch zwischen den Geschlechtern unterscheiden. Hierzu erweitern wir dies aes()-Funktion um den Parameter color.

# unser erstes Plot, mit “Aesthetics” und “Geom”,
# farblich nach Geschlecht
ggplot(pf8) +
  aes(x = Alter,
      y = Gewicht,
      color = Geschlecht) +
  geom_point()

Ggplot stellt die Werte in unterschiedlichen Farben pro Geschlechterkategorie dar. Ebenso wurde das Plot um eine Legendenbox erweitert, in welcher die Farbzuordnungen pro Geschlechtskategorie angezeigt ist.

35.2.4 mehr Geome

Über das + Zeichen können beliebig viele Geome dem Plot hinzugefügt werden. Dabei werden die aesthetics automatisch an die Geome weitergegeben, sie können aber auch in jedem Geom per aes() neu definiert werden.

In unserem Beispiel möchten wir nun eine Regressionsgerade mit Konfidenzintervall einzeichnen. Dies kann mit geom_smooth() umgesetzt werden. Über den Parameter method können wir angeben, dass ein lineares Modell (“lm”) eingezeichnet werden soll.

# unser erstes Plot, mit “Aesthetics” und zwei “Geomen”
# und Regressionsmodell

ggplot(pf8) +
  aes(x = Alter,
      y = Gewicht,
      color = Geschlecht) +
  geom_point()  +
  geom_smooth(method="lm")

In der Variablen Geschlecht sind NA enthalten. Diese können wir mit der Funktion drop_na() aussortieren und direkt an ggplot() weiter-pipen.

# unser erstes Plot, mit “Aesthetics” und zwei “Geomen”
# und Regressionsmodell und
# OHNE NA
pf8 %>%
  drop_na(Geschlecht) %>%
  ggplot() +
    aes(x = Alter,
        y = Gewicht,
        color = Geschlecht) +
    geom_point()  +
    geom_smooth(method="lm")

Es wird ausdrücklich empfohlen, sich über die Hilfeseiten, z.B. ?geom_smooth, mit den Funktionen vertraut zu machen. So erfährt man dort beispielsweise, dass der Konfidenzbereich per se=FALSE ausgeblendet werden kann.

# unser erstes Plot, mit “Aesthetics” und zwei “Geomen”
# und Regressionsmodell ohne Konfidenzbereich
# OHNE NA
pf8 %>%
  drop_na(Geschlecht) %>%
  ggplot() +
    aes(x = Alter,
        y = Gewicht,
        color = Geschlecht) +
    geom_point()  +
    geom_smooth(method="lm", se=FALSE)

35.2.5 Facetten

Mit den Funktionen facet_wrap() und facet_grid() können Teil-Diagramme innerhalb des Gesamtplots erzeugt werden.

So können wir beispielsweise ein Plot pro Standort erstellen.

# unser erstes Plot, mit “Aesthetics”,
# “Geom” und Facetten

pf8 %>%
  drop_na(Geschlecht) %>%
  ggplot() +
    aes(x = Alter,
        y = Gewicht,
        color = Geschlecht) +
    geom_point() +
    geom_smooth(method="lm") +
    facet_wrap(~ Standort)

Oder wir erstellen ein Plot pro Rauchen-Kategorie.

# unser erstes Plot, mit “Aesthetics”,
# “Geom” und Facetten

pf8 %>%
  drop_na(Geschlecht) %>%
  ggplot() +
    aes(x = Alter,
        y = Gewicht,
        color = Geschlecht) +
    geom_point() +
    geom_smooth(method="lm") +
    facet_wrap(~ Rauchen)

Mit der Funktion facet_grid() können Plot-Gitter erzeugt werden. So können wir beispielsweise ein Plot pro Standort und Rauchen erstellen.

# unser erstes Plot, mit “Aesthetics”,
# “Geom” und Facetten

pf8 %>%
  drop_na(Geschlecht) %>%
  ggplot() +
    aes(x = Alter,
        y = Gewicht,
        color = Geschlecht) +
    geom_point() +
    geom_smooth(method="lm") +
    facet_grid(Rauchen ~ Standort)

Mittels drop_na() können wir uns der NA-Zeile entledigen.

# unser erstes Plot, mit “Aesthetics”,
# “Geom” und Facetten

pf8 %>%
  drop_na(Geschlecht, Rauchen) %>%
  ggplot() +
    aes(x = Alter,
        y = Gewicht,
        color = Geschlecht) +
    geom_point() +
    geom_smooth(method="lm") +
    facet_grid(Rauchen ~ Standort)

35.2.6 Geom-Parameter

Geome können mit verschiedenen Parametern beeinflusst werden. Welche Parameter zur Verfügung stehen, verrät die Hilfeseite der Geome. Die meisten reagieren auf:

geom_*(data, aes, stat, position, PARAMETER)

data: Geome übernehmen das Datenobjekt aus der ggplot()-Funktion. Sie können aber auch eigene Daten verarbeiten

aes: Geome erben die Zuordnungen aus der aes()-Funktion. Sie können aber auch eigene aesthetics haben.

stat: Geome können statistische Transformationen vornehmen, z.B. stat='identity'.

position: die Positionierung der Objekte lässt sich anpassen, z.B. 'dodge', 'stack', 'jitter', 'fill'.

weitere PARAMETER sind:

color: (Rahmen)farbe

fill: Füllfarbe

size: Objektgröße

shape: Objektformen

shape nach Zahlen

Die Parameter können auch innerhalb der `aes()`-Funktion verwendet werden, so dass anhand der Daten entschieden wird, welche Größe, Farbe und Form die Geome haben.

linetype: Linientypen

35.3 Punktwolke

Punktwolken (Scatterplots) werden mit der Funktion geom_point() erzeugt. Wir plotten die Variablen Größe und Gewicht aus dem pf8-Datensatz.

ggplot(pf8) +
  aes(x=Größe, y=Gewicht) +
  geom_point()

Über die Parameter color, shape und size können wir die Punkte manipulieren.

ggplot(pf8) +
  aes(x=Größe, y=Gewicht) +
  geom_point(color="blue",
             shape=13,
             size=10)

Die Parameter können auch als aesthetics verwendet werden, so dass anhand der Daten entschieden wird, welche Größe, Farbe und Form die Punkte haben.

ggplot(pf8) +
  aes(x=Größe, y=Gewicht,
      color=Geschlecht,
      shape=Geschlecht,
      size=Alter) +
  geom_point()

35.4 Liniendiagramm

Liniendiagramme werden mit der Funktion geom_line() erzeugt.

# Erzeuge ein paar Daten
df <- data.frame(Jahr=c(2020:2023),
                 Wert=c(3.2, 11, 31.1, 78.3))

# Plotte als Liniendiagramm
ggplot(df, aes(x=Jahr, y=Wert)) +
  geom_line()

Über den Parameter linetype kann der Linientyp festgelegt werden.

ggplot(df, aes(x=Jahr, y=Wert)) +
  geom_line(linetype = "dashed")

Sollen zusätzlich die Datenpunkte eingezeichnet werden (vgl. plot(type="b")), kann einfach das geom_point() angehängt werden.

ggplot(df, aes(x=Jahr, y=Wert)) +
  geom_line(linetype = "dashed")+
  geom_point()

Farben können entweder per aes() oder direkt per col= gesetzt werden.

ggplot(df, aes(x=Jahr, y=Wert)) +
  geom_line(color="red")+
  geom_point(color="darkblue")

Liegen gruppierte Daten vor, kann die Farbe der Gruppen per aes() übergeben werden.

# erzeuge gruppierte Daten
df <- data.frame(Jahr=rep(c(2020:2023), 2),
                 Wert=c(3.2, 11, 31.1, 78.3,
                        4.1, 21, 53.5, 120),
                 Person=c(rep("Erika", 4), rep("Hans", 4)))

# Plotte mit unterschiedlichen Farben
ggplot(df, aes(x=Jahr, y=Wert, color=Person)) +
  geom_line()+
  geom_point()

Dies funktioniert nicht nur mit Farben, sondern auch mit allen anderen Parametern.

ggplot(df, aes(x=Jahr, y=Wert, color=Person,
               linetype=Person)) +
  geom_line()+
  geom_point()

Innerhalb der Geome können die Parameter überschrieben werden, z.B. die Geom-Farbe.

ggplot(df, aes(x=Jahr, y=Wert, color=Person,
               linetype=Person)) +
  geom_line()+
  geom_point(color="black")

35.5 Histogramm

Histogramme werden mit der Funktion geom_histogram() erzeugt.

ggplot(pf8) +
  aes(x=Alter) +
  geom_histogram()

Mit den Parametern color und fill können wir das Histogramm etwas aufhübschen.

ggplot(pf8) +
  aes(x=Alter) +
  geom_histogram(color="blue",   # Rahmenfarbe
                 fill="skyblue") # Füllfarbe

Über den Parameter binwidth kann die Säulenbreite bestimmt werden.

ggplot(pf8) +
  aes(x=Alter) +
  geom_histogram(color="blue",   # Rahmenfarbe
                 fill="skyblue", # Füllfarbe
                 binwidth=10)    # Klassenbreite 10

Mit dem Parameter bins kann die Anzahl der Säulen bestimmt werden.

ggplot(pf8) +
  aes(x=Alter) +
  geom_histogram(color="blue",   # Rahmenfarbe
                 fill="skyblue", # Füllfarbe
                 bins=5)         # 5 Säulen

Mit der Funktion stat_bin() kann das “Originalhistogramm” hinzugefügt werden. So ist leicht zu erkennen, wie grob die Klassen zusammengefasst wurden.

ggplot(pf8) +
  aes(x=Alter) +
  geom_histogram(color="blue",   # Rahmenfarbe
                 fill="skyblue", # Füllfarbe
                 bins=5) +       # 5 Säulen
  stat_bin(fill="darkblue")

35.5.1 Histogramm und Dichteverteilung

Über die Aesthetic ..density.. können wir auf die Skala der Dichtefunktion wechseln.

ggplot(pf8) +
  aes(x=Alter,
      y=..density..) +
  geom_histogram(color="blue",
                 fill="skyblue")

Beachten Sie die Achseneinträge der y-Achse.

Das funktioniert auch, wenn die aesthetics im geom_histogram() angegeben werden.

ggplot(pf8) +
  aes(x=Alter) +
  geom_histogram(aes(y=..density..),
                 color="blue",
                 fill="skyblue")

Die Dichtefunktion des Histogramms lässt sich nun mit der Funktion stat_density() einzeichnen.

ggplot(pf8) +
  aes(x=Alter) +
  geom_histogram(aes(y=..density..),
                 color="blue",
                 fill="skyblue") +
  stat_density(geom="line",
               color="red",
               linetype="dotted")

Wir wechseln von geom="line" auf geom="area":

ggplot(pf8) +
  aes(x=Alter) +
  geom_histogram(aes(y=..density..),
                 color="blue",
                 fill="skyblue") +
  stat_density(geom="area",
               color="red",
               fill="pink",
               linetype="dotted")

Über den Paramter alpha lässt sich die Durchsichtigkeit (so genannter Alphakanal) des Objekts eingeben.

ggplot(pf8) +
  aes(x=Alter) +
  geom_histogram(aes(y=..density..),
                 color="blue",
                 fill="skyblue") +
  stat_density(geom="area",
               color="red",
               fill="pink",
               linetype="dotted",
               alpha=0.5)

Mittels stat_function() und dnorm() lässt sich die Normalverteilung ergänzen. Hierfür bestimmen wir zunächst Mittelwert und Standardabweichung von pf8$Alter.

round(mean(pf8$Alter, na.rm=TRUE))

## [1] 37

round(sd(pf8$Alter, na.rm=TRUE))

## [1] 18

Die Werte werden nun wie folgt übertragen:

ggplot(pf8) +
  aes(x=Alter) +
  geom_histogram(aes(y=..density..),
                 color="blue",
                 fill="skyblue") +
  stat_density(geom="area",
               color="red",
               fill="pink",
               linetype="dotted",
               alpha=0.5)+
  stat_function(fun=dnorm,
                args=(c(mean=37,sd=18)),
                color = "darkgreen",
                linetype = "dashed")

Es ist deutlich erkennbar, dass keine Normalverteilung vorliegt.

35.6 Säulendiagramme

Säulendiagramme werden mit den Funktionen geom_bar() und geom_col() erzeugt.

Für geom_bar() müssen die Variablen als Factor vorliegen. Die Größe der Säulen wird automatisch mittels stat_count() berechnet.

geom_col() erstellt die Größe der Säulen anhand der tatsächlichen Werte im Datenframe (vgl. stat_identity(); absolute Häufigkeit)

Die Variable pf8$SportWarum ist ein Faktor und soll mittels geom_bar() geplottet werden.

ggplot(pf8) +
  aes(x=SportWarum ) +
  geom_bar(color="blue",
           fill="skyblue")

Mittels drop_na() kann der große NA-Balken entfernt werden.

pf8 %>%
  drop_na(SportWarum) %>%
    ggplot() +
      aes(x=SportWarum ) +
      geom_bar(color="blue",
               fill="skyblue")

Mit Hilfe von der Funktionen fct_infreq() und fct_rev() können wir die Levels auf- und absteigend sortieren. Hierzu erzeugen wir eine neue Spalte S, welche wir in der aes()-Funktion auf die x-Achse legen.

# sortiere absteigend
pf8 %>%
  drop_na(SportWarum) %>%
  mutate(S = fct_infreq(SportWarum)) %>%
  ggplot() +
    aes(x=S ) +
    geom_bar(color="blue",
             fill="skyblue")

Um aufsteigend zu sortieren erweitert sich der mutate()-Aufruf wie folgt:

# sortiere aufsteigend
pf8 %>%
  drop_na(SportWarum) %>%
  mutate(S = fct_infreq(SportWarum),
         S = fct_rev(S)) %>%
  ggplot() +
    aes(x=S) +
    geom_bar(color="blue",
             fill="skyblue")

Über die Aesthetic fill können wir gruppieren, z.B. nach Geschlecht.

pf8 %>%
  drop_na(SportWarum) %>%
  mutate(S = fct_infreq(SportWarum)) %>%
  mutate(S = fct_rev(S)) %>%
  ggplot() +
    aes(x=S,
        fill=Geschlecht) +
    geom_bar(color="blue")

Mit dem Parameter position können wir die Säulenausgabe weiter beeinflussen. Die Angabe position="fill" erzeugt Prozentanteile.

pf8 %>%
  drop_na(SportWarum) %>%
  mutate(S = fct_infreq(SportWarum)) %>%
  mutate(S = fct_rev(S)) %>%
  ggplot() +
    aes(x=S,
        fill=Geschlecht) +
    geom_bar(color="blue",
             position="fill")

Mit position="dodge" werden die Säulen der Gruppen nebeneinander gestellt.

pf8 %>%
  drop_na(SportWarum) %>%
  mutate(S = fct_infreq(SportWarum)) %>%
  mutate(S = fct_rev(S)) %>%
  ggplot() +
    aes(x=S,
        fill=Geschlecht) +
    geom_bar(color="blue",
             position="dodge")

Mit position="jitter" wird etwas Rauschen bei der Positionierung verwendet. Das sieht nicht immer schön aus.

pf8 %>%
  drop_na(SportWarum) %>%
  mutate(S = fct_infreq(SportWarum)) %>%
  mutate(S = fct_rev(S)) %>%
  ggplot() +
    aes(x=S,
        fill=Geschlecht) +
    geom_bar(color="blue",
             position="jitter")

Die Default-Einstellung ist position="stack".

pf8 %>%
  drop_na(SportWarum) %>%
  mutate(S = fct_infreq(SportWarum)) %>%
  mutate(S = fct_rev(S)) %>%
  ggplot() +
    aes(x=S,
        fill=Geschlecht) +
    geom_bar(color="blue",
             position="stack")

Mit der Funktion stat_count() können die jeweiligen Werte der Balken ausgeschrieben werden.

pf8 %>%
  drop_na(SportWarum) %>%
  mutate(S = fct_infreq(SportWarum)) %>%
  mutate(S = fct_rev(S)) %>%
  ggplot() +
    aes(x=S,
        fill=Geschlecht) +
    geom_bar(color="blue",
             position="stack")  +
    stat_count(geom = "text",
             aes(label = after_stat(count)),
             position=position_stack(),
             vjust=-0.5)

Mit dem Parameter vjust kann die Höhe der Beschriftungen verändert werden.

Beachten Sie, dass die Parameter position aus geom_bar() und stat_count() übereinstimmen müssen. Da in geom_bar(position="stack") gesetzt ist, muss innerhalb von stat_count() die Funktion position_stack() übergeben werden.

Ändern wir in geom_bar() den Parameter auf position="dodge", so muss innerhalb von stat_count() die Funktion position_dodge() übergeben werden.

pf8 %>%
  drop_na(SportWarum) %>%
  mutate(S = fct_infreq(SportWarum)) %>%
  mutate(S = fct_rev(S)) %>%
  ggplot() +
    aes(x=S,
        fill=Geschlecht) +
    geom_bar(color="blue",
             position="dodge")  +
    stat_count(geom = "text",
             aes(label = after_stat(count)),
             position=position_dodge(width=1),
             vjust=-0.5)

pf8 %>%
  drop_na(SportWarum) %>%
  mutate(S = fct_infreq(SportWarum)) %>%
  mutate(S = fct_rev(S)) %>%
  ggplot() +
    aes(x=S,
        fill=Geschlecht) +
    geom_bar(color="blue",
             position="fill")  +
    stat_count(geom = "text",
             aes(label = after_stat(count)),
             position=position_fill(vjust=0.5),
             color="white")

35.6.1 vorgegebene Werte

Mit dem Parameter stat="identity" können vorberechnete Werte (absolute Häufigkeiten) geplottet werden.

# vorgegebene Werte
df <- data.frame(Gruppe=c("Vanille","Schokolade","Erdbeer"),
                 Anteil=c(60, 25, 15))

# plotten
ggplot(df, aes(x=Gruppe, y=Anteil,
               fill=Gruppe)) +
    geom_bar(stat="identity",
             position="dodge")

Alternativ kann das Plot mit der Funktion geom_col() erzeugt werden, die von Hause aus stat_identity() nutzt.

# vorgegebene Werte
df <- data.frame(Gruppe=c("Vanille","Schokolade","Erdbeer"),
                 Anteil=c(60, 25, 15))

# plotten mit geom_col()
ggplot(df, aes(x=Gruppe, y=Anteil,
               fill=Gruppe)) +
    geom_col(position="dodge")

Mit der Funktion geom_text() können die Werte den Plot-Balken hinzugefügt werden. Über den Parameter vjust kann bestimmt werden, ob die Werte über oder in den Säulen stehen sollen.

ggplot(df, aes(x=Gruppe, y=Anteil,
               fill=Gruppe)) +
  geom_col(position="dodge")+
# Werte stehen über den Balken
  geom_text(aes(label = Anteil), vjust = -0.2)

ggplot(df, aes(x=Gruppe, y=Anteil,
               fill=Gruppe)) +
  geom_col(position="dodge")+
# Werte stehen unter den Balken
  geom_text(aes(label = Anteil), vjust = 2)

Das selbe Ergebis kann auch mit der Funktion stat_identity() erzielt werden. Achten Sie auch hier darauf, dass die Parameter position von geom_col() (bzw. geom_bar()) und von stat_identity() gleich sein müssen.

ggplot(df, aes(x=Gruppe, y=Anteil,
               fill=Gruppe)) +
  geom_col(position="dodge") +
  stat_identity(geom = "text",
            aes(label=Anteil),
            position=position_dodge(width=1),
            vjust=2, size=5, color="white")

35.7 Balkendiagramme

Balkendiagramme können auf zwei Arten erstellt werden. In der aes()-Funktion tauschen wir einfach x durch y aus:

ggplot(pf8) +
  aes(y=SportWarum ) +
  geom_bar(color="blue",
           fill="skyblue")

Alternativ kann dem Plotaufruf die Funktion coord_flip() angehängt werden.

ggplot(pf8) +
  aes(x=SportWarum ) +
  geom_bar(color="blue",
           fill="skyblue") +
  coord_flip()

35.8 Boxplots

Boxplots werden mit der Funktion geom_boxplot() erzeugt.

pf8 %>%
  ggplot() +
    aes(x = Alter) +
    geom_boxplot(fill="seagreen3")

Das Boxplot liegt auf dem Bauch, das Alter auf der x-Achse abgebildet werden soll. Um das Boxplot zu drehen, muss aes() entsprechend angepasst werden.

pf8 %>%
  ggplot() +
    aes(y = Alter) +
    geom_boxplot(fill="seagreen3")

Mit der Funktion stat_boxplot() können wir die Whiskers hinzugefügt werden.

pf8 %>%
  ggplot() +
    aes(y = Alter) +
    geom_boxplot(fill="seagreen3") +
    stat_boxplot(geom="errorbar")

Über den color-Parameter können die Farben angepasst werden.

pf8 %>%
  ggplot() +
    aes(y = Alter) +
    geom_boxplot(fill="seagreen3",
                 color="darkblue") +
    stat_boxplot(geom="errorbar",
                 color="darkgreen")

Über die Aesthetics können wir nach Geschlecht gruppieren.

pf8 %>%
  ggplot() +
    aes(y = Alter,
        x = Geschlecht) +
    geom_boxplot(fill="seagreen3",
                 color="darkblue") +
    stat_boxplot(geom="errorbar",
                 color="darkgreen")

Bzw. ohne NA:

pf8 %>%
  drop_na(Geschlecht) %>%
  ggplot() +
    aes(y = Alter,
        x = Geschlecht) +
    geom_boxplot(fill="seagreen3",
                 color="darkblue") +
    stat_boxplot(geom="errorbar",
                 color="darkgreen")

Über die Aesthetics können wir die Boxplots auch nach Geschlecht färben. Dazu muss der Parameter aus geom_boxplot() entfernt werden.

pf8 %>%
  drop_na(Geschlecht) %>%
  ggplot() +
    aes(y = Alter,
        x = Geschlecht,
        fill = Geschlecht) +
    geom_boxplot(color="darkblue") +
    stat_boxplot(geom="errorbar",
                 color="darkgreen")

In manchen Journals ist es üblich, ebenfalls das arithmetische Mittel per Raute den Boxplots hinzuzufügen. Dies kann mit der Funktion stat_summary() umgesetzt werden.

pf8 %>%
  drop_na(Geschlecht) %>%
  ggplot() +
    aes(y = Alter,
        x = Geschlecht,
        fill = Geschlecht) +
    geom_boxplot(color="darkblue") +
    stat_boxplot(geom="errorbar",
                 color="darkgreen")+
    stat_summary(fun=mean,
                 colour="darkred",
                 geom="point",
                 shape=18,
                 size=5,
                 show.legend = F)

Mit facet_wrap() können wir beispielweise nach Standort gruppieren.

pf8 %>%
  drop_na(Geschlecht) %>%
  ggplot() +
    aes(y = Alter,
        x = Geschlecht,
        fill = Geschlecht) +
    geom_boxplot(color="darkblue") +
    stat_boxplot(geom="errorbar",
                 color="darkgreen")+
    stat_summary(fun=mean,
                 colour="darkred",
                 geom="point",
                 shape=18,
                 size=5,
                 show.legend = F)+
    facet_wrap(~ Standort)

35.9 Kreisdiagramme

Kreisdiagramme werden von ggplot() nicht direkt unterstützt, können jedoch mit einem Trick erzeugt werden. Hierbei wird das Koordinatensystem eines Balkendiagramms mittels coord_polar() so verbogen, dass ein Kreis dabei herauskommt.

# pfusche Pie-Chart zurecht
pf8 %>%
  ggplot() +
    aes(x="", fill=Geschlecht) +
    geom_bar(color="white") +
    # verbiege Koordinatensystem
    coord_polar("y") +
    # entferne Achsen und Ticks
    theme_void()

Das funktioniert auch mit geom_col() und absoluten Häufigkeiten.

# vorgegebene Werte
df <- data.frame(Gruppe=c("Vanille","Schokolade","Erdbeer"),
                 Anteil=c(60, 25, 15))

# pfusche Pie-Chart zurecht
ggplot(df, aes(x="",
               y=Anteil,
               fill=Gruppe)) +
    geom_col(color="white") +
    # verbiege Koordinatensystem
    coord_polar("y", start=0) +
    # entferne Achsen und Ticks
    theme_void()

35.10 QQ-Plots

Zur Überprüfung, ob eine Variable normalverteilt ist, werden häufig Quantil-Quantil-Diagramme (QQ-Plots) erzeugt. Für ggplot() steht hierfür die Funktion stat_qq() zur Verfügung.

# erstelle QQ-Plot für Variable "age"
# gruppiert nach "sex"
ggplot(epa, aes(sample=age)) +
 stat_qq(aes(color=sex))

Mit Zusatzpaketen kann auch hier die Funktionalität erhöht werden. Beispielsweise ist im Paket ggpubr die Funktion ggqqplot() enthalten, die automatisch das Konfidenzintervall hinzufügt. Der Aufruf ist etwas anders als bei ggplot():

ggpubr::ggqqplot(epa, x="age", color="sex")

Weitere Möglichkeiten bietet das Zusatzpaket qqplotr. Es fügt weitere Geome und Funktionen für ggplot hinzu.

# aktiviere die Zusatzfunktionen
library(qqplotr)

ggplot(epa, aes(sample=age)) +
  # zeichne Punkte wie im "Original"
  stat_qq_point(aes(color=sex)) +
  # füge Linie hinzu
  stat_qq_line(aes(color=sex)) +
  # füge Konfidenzgrenzen hinzu
  stat_qq_band(aes(fill=sex))

Dieses Plot ist aber nicht ganz so “schön” wie die vorangegangenen. Es wird besser, wenn wir die Layerreihenfolge vertauschen und den Alpha-Wert der Farbe herbasetzen.

ggplot(epa, aes(sample=age)) +
  # fange mit Konfidenzgrenzen an
  # setzte Alpha-Wert für Farbe
  stat_qq_band(aes(fill=sex), alpha=2/10) +
  # füge Linie hinzu
  stat_qq_line(aes(color=sex)) +
  # zeichne Punkte wie im "Original"
  stat_qq_point(aes(color=sex))

35.11 Diagramme speichern

35.11.1 R-Objekt

ggplot()-Diagramme können in R-Objekte (zwischen)gespeichert werden.

# Plot in Obkjekt "p" speichern
p <- ggplot(pf8) +
       aes(x="", fill=Geschlecht) +
       geom_bar(color="white")
# Plot anzeigen
p

Das Objekt kann nun weitere Funktionen verarbeiten.

# Plot in Obkjekt "p" speichern
p <- ggplot(pf8) +
       aes(x="", fill=Geschlecht) +
       geom_bar(color="white")
# Plot weiter bearbeiten
p + coord_polar("y", start=0) +
    theme_void()

35.11.2 in Datei speichern

Mit der Funktion ggsave() können ggplot()-Diagramme als Datei gespeichert werden.

# Plot erzeugen
ggplot(pf8) +
    aes(x="", fill=Geschlecht) +
    geom_bar(color="white") +
    coord_polar("y", start=0) +
    theme_void()

# Plot in Datei speichern
ggsave("MeinPlot.png",
       units="mm",
       width=400,
       height=200,
       dpi=300,
       pointsize=7)

Liegt der Plot in einem R-Objekt, kann dies mit dem Parameter plot angegeben werden.

# Plot ist in Obkjekt "p" gespeichert
ggsave("MeinPlot.png",
       plot= p,
       units="mm",
       width=400,
       height=200,
       dpi=300,
       pointsize=7)

35.12 Aussehen ändern

35.12.1 Titel und Überschriften

Die Plottitel können mit der Funktion labs() verändert werden.

ggplot(pf8) +
  aes(x=Alter) +
  geom_histogram(color="blue",
                 fill="skyblue") +
  labs(title="Dies ist Überschrift",
       subtitle="Dies ist die Unter-Überschrift",
       caption="Dies ist die Bildunterschrift")

Die Schriftart kann mittels theme()-Funktion geändert werden.

ggplot(pf8) +
  aes(x=Alter) +
  geom_histogram(color="blue",
                 fill="skyblue") +
  labs(title="Dies ist Überschrift",
       subtitle="Dies ist die Unter-Überschrift in fett",
       caption="Dies ist die Bildunterschrift in kursiv") +
  theme(plot.title = element_text(size=18)) +
    theme(plot.subtitle = element_text(size=10, face="bold")) +
  theme(plot.caption = element_text(size=8, face="italic"))

35.12.2 Achsen

Die Achsenbeschriftung kann mittels xlab() und ylab() verändert werden.

ggplot(pf8) +
  aes(x=Alter) +
  geom_histogram(color="blue",
                 fill="skyblue") +
  xlab("Ich bin die x-Achse") +
  ylab("Ich bin die y-Achse")

Die Längen der Achsenausschnitte können mit xlim() und ylim() angegeben werden.

ggplot(pf8) +
  aes(x=Alter) +
  geom_histogram(color="blue",
                 fill="skyblue") +
  xlab("Ich bin die x-Achse") +
  ylab("Ich bin die y-Achse")+
  xlim(20, 50) +
  ylim(-20, 75)

Die Anzahl und Position der Achsenmarkierungen können mit scale_x_continuous() und scale_y_continuous() angepasst werden.

ggplot(pf8) +
  aes(x=Alter) +
  geom_histogram(color="blue",
                 fill="skyblue") +
  xlab("Ich bin die x-Achse") +
  ylab("Ich bin die y-Achse")+
  xlim(20, 50) +
  ylim(-20, 75) +
  scale_x_continuous(breaks = c(20, 25, 30, 35, 40)) +
  scale_y_continuous(breaks = seq(-20, 100, 10) )

Die Schriftarten werden ebenfalls mit der theme()-Funktion gesteuert.

ggplot(pf8) +
  aes(x=Alter) +
  geom_histogram(color="blue",
                 fill="skyblue") +
  xlab("Ich bin die x-Achse") +
  ylab("Ich bin die y-Achse")+
  xlim(20, 50) +
  ylim(-20, 75) +
  scale_x_continuous(breaks = c(20, 25, 30, 35, 40)) +
  scale_y_continuous(breaks = seq(-20, 100, 10) ) +
  theme(axis.title.x = element_text(size=18, face="bold")) +
  theme(axis.title.y = element_text(size=14, face="italic")) +
  theme(axis.text.x = element_text(size=8, color="red")) +
  theme(axis.text.y = element_text(size=4, color="blue")) +
  theme(axis.ticks.x = element_line(size=8, color="darkgreen")) +
  theme(axis.ticks.y = element_line(size=8, color="orange"))

So können die Achsenbeschriftungen auch über den Parameter angle gedreht werden.

ggplot(pf8) +
  aes(x=SportWarum ) +
  geom_bar(color="blue",
           fill="skyblue") +
  theme(axis.text.x = element_text(angle = 45,
                                   hjust = 1,
                                   size=10))

Sollen die Ticks und ihre Striche vollständig entfernt werden, so geht dies mit den Funktionen theme() und element_blank().

ggplot(pf8) +
  aes(x=Alter) +
  geom_histogram(color="blue",
                 fill="skyblue") +
  theme(axis.text.x=element_blank(),
        axis.ticks.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks.y=element_blank())

35.12.3 Legendenbox

Der Title der Legendbox kann prinzipiell geändert werden, indem die ursprüngliche Variable umbenannt wird. Dann ändert sich der Legendenboxtitel ebenfalls entsprechend. Sollte dies keine “gute” Lösung sein, z.B. weil mehrere Wörter als Titel verwendet werden sollen, kann der Title über die labs()-Funktion geändert werden. Hierbei besteht eine Abhängigkeit zur Gruppierung über die aes()-Funktion. Im folgenden Beispiel wird die Gruppierung über den Parameter fill=Geschlecht erzeugt. Daher muss der Legendentitel mittels labs(fill="NEU") geändert werden.

pf8 %>%
  ggplot() +
    aes(y = Alter,
        fill= Geschlecht) +
    geom_boxplot() +
    labs(fill="Neuer Titel")

Sollen die Einträge der Legenbox geändert werden, kann dies zum einen erfolgen, indem die Levels des Originalfactor entsprechend umgeschrieben werden. Dann ändern sich auch die Einträge in der Legendenbox. Sollte dies keine “gute” Lösung sein, können die Einträge von Hand über die Funktion scale_fill_discrete() überschrieben werden.

pf8 %>%
  ggplot() +
    aes(y = Alter,
        fill= Geschlecht) +
    geom_boxplot() +
    labs(fill="Neuer Titel") +
    scale_fill_discrete(labels=c('men', 'woman', 'divers', 'no answer'))

Soll die Reihenfolge der Levelboxelemte geändert werden, kann ebenfalls auf die Funktion scale_fill_discrete() zurückgegriffen werden.

pf8 %>%
  ggplot() +
    aes(y = Alter,
        fill= Geschlecht) +
    geom_boxplot() +
    labs(fill="Neuer Titel") +
    scale_fill_discrete(breaks=c(NA, "weiblich", "divers", "männlich"))

Die Schriftarten der Legendenbox können ebenfalls mit der theme()-Funktion verändert werden.

pf8 %>%
  ggplot() +
    aes(y = Alter,
        fill= Geschlecht) +
    geom_boxplot() +
    theme(legend.title = element_text(color="red", face="bold")) +
    theme(legend.text = element_text(color="blue", face="italic"))

Soll die Legendenbox vollständig entfernt werden, so geht dies über die Funktion theme(legend.position = "none")

pf8 %>%
  ggplot() +
    aes(y = Alter,
        fill= Geschlecht) +
    geom_boxplot() +
    theme(legend.position = "none")

35.12.4 Farben

Ohne weitere Angaben verwendet ggplot() seine “eingebauten” Standardfarben.

Wenn wir das Alter der Probanden nach Standort gruppieren, wählt ggplot() diese Farben aus:

pf8 %>%
  ggplot() +
    aes(y = Alter,
        color= Standort,
        fill= Standort) +
    geom_boxplot()

Die Farbintensität und -helligkeit der Standardfarben können über die Funktionen scale_color_hue() und scale_fill_hue() angepasst werden. Hierbei steht der Parameter c für die Farbintensität, und l für die Helligkeit.

pf8 %>%
  ggplot() +
    aes(y = Alter,
        color= Standort,
        fill= Standort) +
    geom_boxplot() +
    scale_color_hue(l=40, c=35) +
    scale_fill_hue(l=40, c=35)

Über die Funktionen scale_color_manual() und scale_fill_manual() können die Farben manuell ausgewählt werden (eine Liste aller Farbnamen liefert die Funktion colors()):

pf8 %>%
  ggplot() +
    aes(y = Alter,
        color= Standort,
        fill= Standort) +
    geom_boxplot() +
    scale_color_manual(values=c("darkblue", "navy", "blue",
                                "skyblue2", "skyblue3")) +
    scale_fill_manual(values=c("hotpink", "pink", "pink3",
                               "red3", "red4"))

Die Farben können über den breaks-Parameter auch direkt den Levelausprägungen zugewiesen werden.

pf8 %>%
  ggplot() +
    aes(y = Alter,
        color= Standort,
        fill= Standort) +
    geom_boxplot() +
    scale_color_manual(values=c("darkblue", "navy", "blue",
                                "skyblue2", "skyblue3"),
                       breaks=c("Internet", "Ladbergen", "Münster",
                                "Rheine", "Bahn")) +
    scale_fill_manual(values=c("hotpink", "pink", "pink3",
                               "red3", "red4"),
                      breaks=c("Internet", "Ladbergen", "Münster",
                               "Rheine", "Bahn"))

Hierdurch hat sich auch die Reihenfolge der Levelboxelemte geändert.

Soll die Reihenfolge der Levelboxelemte geändert werden, ohne dass eigene Farben definiert werden, kann auf die Funktion scale_fill_discrete() zurückgegriffen werden.

pf8 %>%
  ggplot() +
    aes(y = Alter,
        fill= Standort) +
    geom_boxplot() +
    scale_fill_discrete(breaks=c("Internet", "Ladbergen", "Bahn",
                                 "Rheine", "Münster"))

35.12.5 Linien

Mit den Funktionen geom_vline() und geom_hline() können vertikale und horizontale Linien hinzugefügt werden. Mit den Parametern xintercept und yintercept wird der Schnitt durch die jeweilige Achse festegelegt.

ggplot(pf8) +
  aes(x = Alter,
      y = Gewicht) +
  geom_point() +
  geom_hline(yintercept = 60,
             linetype = "solid",
             color = "blue",
             size = 3) +
  geom_vline(xintercept=40,
             linetype="dashed",
             color="red")

Um zwei beliebige Punkte mit einer Linie zu verbinden, muss zunächst ein Hilfsdatenframe mit den Koordinaten der Punkte erstellt werden. Dieses neue Datenframe wird dann der Funktion geom_line() übergeben.

# Hilfsdatenframe
strich <- data.frame(x = c(30,50),
                     y = c(10,90))

ggplot(pf8) +
  aes(x = Alter,
      y = Gewicht) +
  geom_point() +
  geom_line(data = strich,
            aes(x=x, y=y),
            color="darkgreen",
            size=1)

35.12.6 Text

Texte können mit der annotate()-Funktion hinzugfügt werden.

ggplot(pf8) +
  aes(x = Alter,
      y = Gewicht) +
  geom_point() +
  annotate(geom="text",
           x=30, y=120,
           label="Huhu",
           color="darkgreen",
           size=14)

35.12.7 Sonderzeichen

Sonderzeichen und Mathematiksymbole können mittels der expression()-Funktion hinzugefügt werden.

ggplot(pf8) +
  aes(x = Alter,
      y = Gewicht) +
  geom_point() +
  labs(title = expression("Dies ist ein Text mit"~a^2~+~b^2~"="~c^2),
       subtitle = expression(sigma~sqrt(alpha)~beta^3~"sind hier"~x[1])
       )

35.12.8 Themes

In ggplot() sind 8 verschiedene “Themes” implementiert, die das Aussehen der nicht-datenbezogenen Anteile des Plots festlegen. Das Standardtheme ist theme_grey(), die anderen sind:

theme_bw(): Eine Variante von theme_grey() mit weißem Hintergrund und dünnen grauen Gitterlinien.

theme_linedraw(): Ein Theme mit nur schwarzen Linien unterschiedlicher Stärke auf weißem Hintergrund, das an eine Zeichnung erinnert.

theme_light(): Ähnlich wie theme_linedraw(), aber mit hellgrauen Linien und Achsen, um die Aufmerksamkeit stärker auf die Daten zu lenken.

theme_dark(): Das dunkle Pendant von theme_light(), mit ähnlichen Linienstärken, aber dunklem Hintergrund. Nützlich, um dünne farbige Linien hervorzuheben.

theme_minimal(): Ein minimalistisches Theme ohne Hintergrundanmerkungen.

theme_classic(): Ein klassisch aussehendes Theme mit x- und y-Achsen-Linien und ohne Gitterlinien.

theme_void(): Ein vollständig leeres Theme.

# dummy Daten
df <- data.frame(x = 1:3, y = 1:3)

# Basisplot
base <- ggplot(df, aes(x, y)) +
  geom_point(color="red") +
  geom_line(color="darkgreen")

# Themepark
base +  ggtitle("theme_grey()")
base + theme_grey() + ggtitle("theme_grey()")
base + theme_bw() + ggtitle("theme_bw()")
base + theme_linedraw() + ggtitle("theme_linedraw()")
base + theme_light() + ggtitle("theme_light()")
base + theme_dark() + ggtitle("theme_dark()")
base + theme_minimal()  + ggtitle("theme_minimal()")
base + theme_classic() + ggtitle("theme_classic()")
base + theme_void() + ggtitle("theme_void()")

36 Daten manipulieren

36.1 Einer Kreuztabelle Prozentwerte hinzufügen

Ich möchte eine Kreuztabelle ausgeben, die sowohl absolute Häufigkeiten als auch Prozentwerte enthält.

Ich erstelle folgende Kreuztabelle:

A <- rep(c("A1", "A2", "A3"), 10)
B <- sample(1:5, size=30, replace=TRUE)
C <- sample(c("ja", "nein"), size=30, replace=TRUE)
df <- data.frame(rbind(table(B, A),
                 table(C, A)))
df

##      A1 A2 A3
## 1     0  4  1
## 2     1  1  4
## 3     2  1  1
## 4     2  3  0
## 5     5  1  4
## ja    5  3  6
## nein  5  7  4

Die einzelnen Werte sollen nun mit den jeweiligen Prozentwerten ergänzt werden. Dies kann mit den Funktionen transmute() und across() erfolgen.

df %>%
            # über jede Spalte
  transmute(across(everything(),
                     # x ausgeben und " (x%)" anhängen
                   ~ paste0(.x, " (", round(.x / sum(df), 2) * 100, "%)")
                  )
           )

##          A1      A2      A3
## 1    0 (0%)  4 (7%)  1 (2%)
## 2    1 (2%)  1 (2%)  4 (7%)
## 3    2 (3%)  1 (2%)  1 (2%)
## 4    2 (3%)  3 (5%)  0 (0%)
## 5    5 (8%)  1 (2%)  4 (7%)
## ja   5 (8%)  3 (5%) 6 (10%)
## nein 5 (8%) 7 (12%)  4 (7%)

36.2 Eine gemeinsame Kreuztabelle mit mehrere Variablen erstellen

Häufig erstellen wir verschiedene Kreuztabellen mit der selben “Gruppierungsvariable”, z.B. Geschlecht oder Kontroll- vs. Interventionsgruppe.

Nehmen wir als Beispiel den Datensatz pf8.

load(url("https://www.produnis.de/R/data/pf8.RData"))
# oder
pf8 <- jgsbook::pf8

Möchten wir hinsichtlich der Variable Geschlecht unterscheiden, würden wir die Kreuztabellen mit anderen Variablen einzeln per xtab() oder table() erstellen.

table(pf8$Standort, pf8$Geschlecht)

##
##             männlich weiblich divers
##   Rheine          90      112      1
##   Münster         75      103      0
##   Bahn            36       48      0
##   Ladbergen       26       17      0
##   Internet        60      157      0

table(pf8$Bildung, pf8$Geschlecht)

##
##                  männlich weiblich divers
##   keinen                2        3      0
##   Hauptschule          30       26      0
##   mittlere Reife       46       53      0
##   Ausbildung           10       19      0
##   Fachabitur           23       42      0
##   Abitur               64       76      0
##   Hochschule           50       56      1

table(pf8$Familienstand, pf8$Geschlecht)

##
##                 männlich weiblich divers
##   ledig              125      162      0
##   Partnerschaft       56       98      0
##   verheiratet         90      136      1
##   geschieden           6       23      0
##   verwitwet            5       13      0
##   getrennt             4        4      0

Eine alternative Vorgehensweise besteht darin, mit der Funktion lapply() eine list der Kreuztabellen zu erstellen.

tbl.list <- lapply(pf8[, c("Standort", "Bildung", "Familienstand")],
                   function(x) xtabs(~ x + pf8$Geschlecht))

tbl.list

## $Standort
##            pf8$Geschlecht
## x           männlich weiblich divers
##   Rheine          90      112      1
##   Münster         75      103      0
##   Bahn            36       48      0
##   Ladbergen       26       17      0
##   Internet        60      157      0
##
## $Bildung
##                 pf8$Geschlecht
## x                männlich weiblich divers
##   keinen                2        3      0
##   Hauptschule          30       26      0
##   mittlere Reife       46       53      0
##   Ausbildung           10       19      0
##   Fachabitur           23       42      0
##   Abitur               64       76      0
##   Hochschule           50       56      1
##
## $Familienstand
##                pf8$Geschlecht
## x               männlich weiblich divers
##   ledig              125      162      0
##   Partnerschaft       56       98      0
##   verheiratet         90      136      1
##   geschieden           6       23      0
##   verwitwet            5       13      0
##   getrennt             4        4      0

Wir haben Zugriff auf die einzelnen Kreuztabellen per tbl.list[[1]] oder tbl.list[[2]].

 tbl.list[[3]]

##                pf8$Geschlecht
## x               männlich weiblich divers
##   ledig              125      162      0
##   Partnerschaft       56       98      0
##   verheiratet         90      136      1
##   geschieden           6       23      0
##   verwitwet            5       13      0
##   getrennt             4        4      0

36.2.1 Zeilen- und Spaltensummen

Mit der Liste können wir nun weiterarbeiten. Zum Beispiel können wir Spalten- und Zeilensummen hinzufügen…

lapply(tbl.list, addmargins)

## $Standort
##            pf8$Geschlecht
## x           männlich weiblich divers Sum
##   Rheine          90      112      1 203
##   Münster         75      103      0 178
##   Bahn            36       48      0  84
##   Ladbergen       26       17      0  43
##   Internet        60      157      0 217
##   Sum            287      437      1 725
##
## $Bildung
##                 pf8$Geschlecht
## x                männlich weiblich divers Sum
##   keinen                2        3      0   5
##   Hauptschule          30       26      0  56
##   mittlere Reife       46       53      0  99
##   Ausbildung           10       19      0  29
##   Fachabitur           23       42      0  65
##   Abitur               64       76      0 140
##   Hochschule           50       56      1 107
##   Sum                 225      275      1 501
##
## $Familienstand
##                pf8$Geschlecht
## x               männlich weiblich divers Sum
##   ledig              125      162      0 287
##   Partnerschaft       56       98      0 154
##   verheiratet         90      136      1 227
##   geschieden           6       23      0  29
##   verwitwet            5       13      0  18
##   getrennt             4        4      0   8
##   Sum                286      436      1 723

36.2.2 Prozenttabellen

… oder Prozenttabellen erstellen. Um die Prozentwerte zeilenweise zu bilden, muss der Parameter margin=1 angegeben werden. Für spaltenweises Vorgehen gilt margin=2.

lapply(tbl.list, prop.table, margin=1)

## $Standort
##            pf8$Geschlecht
## x              männlich    weiblich      divers
##   Rheine    0.443349754 0.551724138 0.004926108
##   Münster   0.421348315 0.578651685 0.000000000
##   Bahn      0.428571429 0.571428571 0.000000000
##   Ladbergen 0.604651163 0.395348837 0.000000000
##   Internet  0.276497696 0.723502304 0.000000000
##
## $Bildung
##                 pf8$Geschlecht
## x                   männlich    weiblich      divers
##   keinen         0.400000000 0.600000000 0.000000000
##   Hauptschule    0.535714286 0.464285714 0.000000000
##   mittlere Reife 0.464646465 0.535353535 0.000000000
##   Ausbildung     0.344827586 0.655172414 0.000000000
##   Fachabitur     0.353846154 0.646153846 0.000000000
##   Abitur         0.457142857 0.542857143 0.000000000
##   Hochschule     0.467289720 0.523364486 0.009345794
##
## $Familienstand
##                pf8$Geschlecht
## x                  männlich    weiblich      divers
##   ledig         0.435540070 0.564459930 0.000000000
##   Partnerschaft 0.363636364 0.636363636 0.000000000
##   verheiratet   0.396475771 0.599118943 0.004405286
##   geschieden    0.206896552 0.793103448 0.000000000
##   verwitwet     0.277777778 0.722222222 0.000000000
##   getrennt      0.500000000 0.500000000 0.000000000

36.2.3 Signifikanztests

Zudem können wir Signifikanztests rechnen.

lapply(tbl.list, chisq.test)

## $Standort
##
##  Pearson's Chi-squared test
##
## data:  X[[i]]
## X-squared = 26.296, df = 8, p-value = 0.0009347
##
##
## $Bildung
##
##  Pearson's Chi-squared test
##
## data:  X[[i]]
## X-squared = 9.4526, df = 12, p-value = 0.6639
##
##
## $Familienstand
##
##  Pearson's Chi-squared test
##
## data:  X[[i]]
## X-squared = 10.503, df = 10, p-value = 0.3975

Wenn wir die Liste in ein Objekt speichern, können wir uns nur die p-Werte ausgeben lassen.

chi.list <- lapply(tbl.list, chisq.test)
chi.list[[1]]$p.value

## [1] 0.0009347078

36.2.4 eine große Kreuztabelle

Zu guter Letzt können wir die einzelnen Kreuztabellen zu einer “großen” Tabelle vereinen.

rbind(tbl.list[[1]],
      tbl.list[[2]],
      tbl.list[[3]])

##                männlich weiblich divers
## Rheine               90      112      1
## Münster              75      103      0
## Bahn                 36       48      0
## Ladbergen            26       17      0
## Internet             60      157      0
## keinen                2        3      0
## Hauptschule          30       26      0
## mittlere Reife       46       53      0
## Ausbildung           10       19      0
## Fachabitur           23       42      0
## Abitur               64       76      0
## Hochschule           50       56      1
## ledig               125      162      0
## Partnerschaft        56       98      0
## verheiratet          90      136      1
## geschieden            6       23      0
## verwitwet             5       13      0
## getrennt              4        4      0

dummy <- rbind(tbl.list[[1]],
      tbl.list[[2]],
      tbl.list[[3]])
# erzeuge ein Datenframe aus den Zeilennamen und den Werten
dummy <- data.frame( row.names(dummy), dummy )

# lösche die alten Rownames
rownames(dummy) <- NULL
dummy

##    row.names.dummy. männlich weiblich divers
## 1            Rheine       90      112      1
## 2           Münster       75      103      0
## 3              Bahn       36       48      0
## 4         Ladbergen       26       17      0
## 5          Internet       60      157      0
## 6            keinen        2        3      0
## 7       Hauptschule       30       26      0
## 8    mittlere Reife       46       53      0
## 9        Ausbildung       10       19      0
## 10       Fachabitur       23       42      0
## 11           Abitur       64       76      0
## 12       Hochschule       50       56      1
## 13            ledig      125      162      0
## 14    Partnerschaft       56       98      0
## 15      verheiratet       90      136      1
## 16       geschieden        6       23      0
## 17        verwitwet        5       13      0
## 18         getrennt        4        4      0

36.3 Zeilen und Spalten tauschen

Ich möchte bei meinem Datensatz Zeilen und Spalten vertauschen.

Dies kann mit der Funktion apply() gemacht werden. Angenommen das Datenframe datensatz sieht wie folgt aus …

# lade Testdatensatz datensatz
datensatz <-read.table(url("http://www.produnis.de/R/data/DieDaten.csv"), sep=";", header=TRUE)
# zeige "datensaz" an
datensatz

##     Name   Geschlecht Lieblingsfarbe Einkommen
## 1   Hans    maennlich          gruen      1233
## 2   Caro     weiblich           blau       800
## 3   Lars intersexuell           gelb      2400
## 4   Ines     weiblich        schwarz      4000
## 5 Samira     weiblich           gelb       899
## 6  Peter    maennlich          gruen      1100
## 7  Sarah     weiblich           blau      1900

…so tauschen wir Zeilen und Spalten mittels

# vertausche Spalten und Zeilen
apply(datensatz, MARGIN=1, FUN=function(x) {x})

##                [,1]        [,2]       [,3]           [,4]       [,5]
## Name           "Hans"      "Caro"     "Lars"         "Ines"     "Samira"
## Geschlecht     "maennlich" "weiblich" "intersexuell" "weiblich" "weiblich"
## Lieblingsfarbe "gruen"     "blau"     "gelb"         "schwarz"  "gelb"
## Einkommen      "1233"      " 800"     "2400"         "4000"     " 899"
##                [,6]        [,7]
## Name           "Peter"     "Sarah"
## Geschlecht     "maennlich" "weiblich"
## Lieblingsfarbe "gruen"     "blau"
## Einkommen      "1100"      "1900"

Da die Funktion apply() eine Typkonversion in die Klasse matrix vornimmt, (und somit alle Datentypen auf den kleinsten gemeinsamen Nenner character zurückfallen, siehe die Anführungszeichen im Output) muss bei Bedarf zurück in die Klasse data.frame konvertiert werden.

# vertausche Spalten und Zeilen
as.data.frame(apply(datensatz, MARGIN=1, FUN=function(x) {x}))

##                       V1       V2           V3       V4       V5        V6
## Name                Hans     Caro         Lars     Ines   Samira     Peter
## Geschlecht     maennlich weiblich intersexuell weiblich weiblich maennlich
## Lieblingsfarbe     gruen     blau         gelb  schwarz     gelb     gruen
## Einkommen           1233      800         2400     4000      899      1100
##                      V7
## Name              Sarah
## Geschlecht     weiblich
## Lieblingsfarbe     blau
## Einkommen          1900

36.4 Klassen für Variablen bilden (klassieren)

Ich möchte die Werte einer Variable in Kategorien bzw. Klassen einteilen.

Angenommen, meine Variable enthält Werte zwischen 0 und 500, und ich möchte diese Werte in die Gruppen “0-70”, “71-200”, “201-400”, “>400”, klassieren, wie gehe ich dann vor?

36.4.1 R base

Im klassischen R erstellen wir zunächst zufällige Zahlen zwischen 0 und 500.

# erzeuge 200 Zufallszahlen von 0 bis 500
dummy <- sample(0:500, 200)
# bzw. direkt als Dataframe
dummy <- data.frame(x = sample(0:500, 200))

Nun erzeugen wir die neue Variable xKAT, in welcher die Klassierung angegeben werden soll.

# wir bilden Kategorien
#      "0-70"
#      "71-200"
#      "201-400"
#      "> 400"
# und speichern das in die neue Variable xKAT
dummy$xKAT[dummy$x <71] <- "0-70"
dummy$xKAT[dummy$x <201 & dummy$x > 70] <- "71-200"
dummy$xKAT[dummy$x <401 & dummy$x > 200] <- "201-400"
dummy$xKAT[dummy$x >400] <- "größer 400"

Anschließend wandeln wir die neue Variable in einen ordinalen Faktor mit korrekter Levelreihenfolge.

dummy$xKAT <- factor(dummy$xKAT, levels=c("0-70",
                                    "71-200",
                                    "201-400",
                                    "größer 400"),
                       ordered=TRUE)
head(dummy$xKAT)

## [1] 201-400    größer 400 71-200     größer 400 71-200     0-70
## Levels: 0-70 < 71-200 < 201-400 < größer 400

36.4.2 Tidyverse

Zunächst laden wir das tidyverse-Paket und erstellen uns zufällige Zahlen zwischen 0 und 500.

# lade tidyverse
library(tidyverse)
# erzeuge 200 Zufallszahlen von 0 bis 500
dummy <- sample(0:500, 200)
# bzw. direkt als Tibble
dummy <- tibble(x = sample(0:500, 200))

Nun erzeugen wir die neue Variable xKAT, in welcher die Klassierung angegeben werden soll. Hierfür nutzen wir innerhalb von mutate() die ifelse()-Funktion. Diese folgte der Logik “WELCHE - WAS -ANSONSTEN”. In einem ersten beispielhaften Schritt wählen wir all “x < 71” aus, speichern für diese Fälle den character-Wert “0-70”, und bei allen anderen Fällen ein “NA”.

dummy %>%
              # ifelse(WELCHE, WAS, ANSONSTEN)
    mutate(xKAT = ifelse(x < 71 , "0-70", NA))

## # A tibble: 200 × 2
##       x xKAT
##   <int> <chr>
## 1   211 <NA>
## 2   241 <NA>
## 3   449 <NA>
## 4   131 <NA>
## 5   476 <NA>
## 6   459 <NA>
## # ℹ 194 more rows

Es lassen sich mehrere ifelse()-Ausdrücke kombinieren, indem diese vor den NA-Ausdruck geschrieben werden. Dabei sammeln sich die Klammer-Zu Symbole ) an, kommen Sie hier nicht durcheinander!

# Das lässt sich erweitern, indem der ", NA"-Ausdruck nach hinten wandert
dummy %>%
  mutate(xKAT = ifelse(x < 71 , "0-70",
                ifelse(x < 201 & x >70 , "71-200",
                       NA)))

## # A tibble: 200 × 2
##       x xKAT
##   <int> <chr>
## 1   211 <NA>
## 2   241 <NA>
## 3   449 <NA>
## 4   131 71-200
## 5   476 <NA>
## 6   459 <NA>
## # ℹ 194 more rows

Der vollständige Befehl zur Klassierung lautet demnach:

# wir bilden Kategorien
#      "0-70"
#      "71-200"
#      "201-400"
#      "> 400"
# und speichern das in die neue Variable xKAT
dummy %>%
  mutate(xKAT = ifelse(x < 71 , "0-70",
                ifelse(x < 201 & x >70 ,  "71-200",
                ifelse(x < 401 & x >200 , "201-400",
                ifelse(x > 400 , "> 400",
                              NA)))))

## # A tibble: 200 × 2
##       x xKAT
##   <int> <chr>
## 1   211 201-400
## 2   241 201-400
## 3   449 > 400
## 4   131 71-200
## 5   476 > 400
## 6   459 > 400
## # ℹ 194 more rows

Wenn - so wie hier - mehrere Konditionen angegeben werden, ist die Funktion case_when() etwas einfacher zu schreiben und zu lesen als die ifelse()-Staffelungen.

# nutze case_when() an Stelle von ifelse()
dummy %>%
  mutate(xKAT = case_when(x < 71 ~ "0-70",
                          x < 201 & x > 70  ~ "71-200",
                          x < 401 & x > 200 ~ "201-400",
                          x > 400 ~ "größer 400")
         )

## # A tibble: 200 × 2
##       x xKAT
##   <int> <chr>
## 1   211 201-400
## 2   241 201-400
## 3   449 größer 400
## 4   131 71-200
## 5   476 größer 400
## 6   459 größer 400
## # ℹ 194 more rows

Mit dem Parameter .default kann ein Wert festgelegt werden, der vergeben wird, wenn keine der Bedingungen zutrifft. Standardmäßig werden NAs vergeben.

# nutze case_when() an Stelle von ifelse()
dummy %>%
  mutate(xKAT = case_when(x < 71 ~ "0-70",
                          x < 201 & x > 70  ~ "71-200",
                          x < 401 & x > 200 ~ "201-400",
                          x > 400 ~ "größer 400",
                          .default="keines davon")
         )

## # A tibble: 200 × 2
##       x xKAT
##   <int> <chr>
## 1   211 201-400
## 2   241 201-400
## 3   449 größer 400
## 4   131 71-200
## 5   476 größer 400
## 6   459 größer 400
## # ℹ 194 more rows

Anschließend wandeln wir noch die neue Variable in einen ordinalen Faktor mit korrekter Levelreihenfolge.

# die neue Variable als ordinalen Factor mit korrekter
# Levelreihenfolge speichern
dummy <- dummy %>%
  mutate(xKAT = case_when(x < 71 ~ "0-70",
                          x < 201 & x > 70  ~ "71-200",
                          x < 401 & x > 200 ~ "201-400",
                          x > 400 ~ "größer 400",
                          .default="keines davon"),
         xKAT = factor(xKAT, levels=c("0-70",
                                      "71-200",
                                      "201-400",
                                      "größer 400",
                                      "keines davon"),
                       ordered=TRUE)
         )
head(dummy$xKAT)

## [1] 201-400    201-400    größer 400 71-200     größer 400 größer 400
## Levels: 0-70 < 71-200 < 201-400 < größer 400 < keines davon

37 Diagramme plotten

Ich möchte Diagramme plotten!

37.1 Alle R-Farben

Ich möchte alle Farben, die per colors() ausgegeben werden, in eine Übersichtsgrafik plotten.

# ggplot laden
library(ggplot2)

# Datenframe vorbereiten
d=data.frame(c=colors(),
             y=seq(0, length(colors())-1)%%66,
             x=seq(0, length(colors())-1)%/%66)
# Alle Farben plotten
p <- ggplot() +
  scale_x_continuous(name="", breaks=NULL, expand=c(0, 0)) +
  scale_y_continuous(name="", breaks=NULL, expand=c(0, 0)) +
  scale_fill_identity() +
  # weisse Boxen für Text
  geom_rect(data=d, mapping=aes(xmin=x,
                                xmax=x+1,
                                ymin=y,
                                ymax=y+1),
            fill="white") +
  # Farbboxen
  geom_rect(data=d, mapping=aes(xmin=x+0.05,
                                xmax=x+0.95,
                                ymin=y+0.5,
                                ymax=y+1,
                                fill=c)) +
  # Farbnamen
  geom_text(data=d, mapping=aes(x=x+0.5, y=y+0.5, label=c),
            colour="black", hjust=0.5, vjust=1, size=3)

# Plot ansehen
p

Abb. 37.1: Alle R Farben

# Plot speichern
ggsave("rbase-Farben.png",
       plot= p,
       units="px",
       width=4000,
       height=4000,
       dpi=300)

37.2 Normalverteilung

Zum Plotten von Normalverteilungen kann man mit der Funktion plot() so vorgehen:

# erstelle Werte von -4 bis 4 in 0.005er-Schritten
x <- seq(-4, 4, by=0.005)

# plotte die Standardnormalverteilung
plot(x,dnorm(x))

# etwas hübscher
plot(x,dnorm(x), col="blue", type="l", xlab="x", ylab="f(x)", main="Standardnormalverteilungen")

# mit Mittelwert 2 und sd = 0,5
plot(x,dnorm(x,mean=2,s=0.5), col="darkblue", type="l", xlab="x", ylab="f(x)", main="Normalverteilungen")

# mit Mittelwert 2 und sd = 2
plot(x,dnorm(x,mean=2,s=2), col="darkorchid", type="l", xlab="x", ylab="f(x)", main="Normalverteilungen")

# erzeuge neue Werte von -3 bis 6
x <- seq(-3,6, by=0.005)

# Alles zusammen plotten
plot(x,dnorm(x,mean=2,s=0.5), col="blue", type="l", xlab="x", ylab="f(x)",main="Normalverteilungen")
lines(x,dnorm(x,mean=0,s=1), col="black")
lines(x,dnorm(x,mean=2,s=2), col="darkorchid")
text(0,.45,"N(0;1)")
text(2.8, 0.6, "N(2;0.5)", col="blue")
text(5, 0.1, "N(2;2)", col="darkorchid")

Mit ggplot kann es so aussehen:

# aktiviere ggplot
library(ggplot2)

# erzeuge Werte von -3 bis 3
x <- seq(-3,3, by=0.005)

# übergebe in ein data.frame
df <- data.frame(x)

# ggplot erstellen
p <- ggplot(data=df, aes(x)) +
                     xlim(-4,4) + ylim(0,0.5) +
                     xlab("x") + ylab("Dichtefunktion") +
                     ggtitle("Normalverteilungen")
p + stat_function(fun=dnorm, args=(c(mean=0,sd=1)), colour="black")+
    annotate(geom="text", x=0, y=0.42, label="N(0;1)", color="black")

# erzeuge Werte von -3 bis 6
x <- seq(-3,6, by=0.005)

# übergebe in ein data.frame
df <- data.frame(x)

# ggplot erstellen
p <- ggplot(data=df, aes(x)) +
                     xlim(-3,6) + ylim(0, 0.8) +
                     xlab("x") + ylab("Dichtefunktion") +
                     ggtitle("Normalverteilungen")
p + stat_function(fun=dnorm, args=(c(mean=0,sd=1)), colour="black")+
  annotate(geom="text", x=0, y=0.42, label="N(0;1)", color="black")+
  stat_function(fun=dnorm, args=(c(mean=2,sd=0.5)), colour="blue") +
  annotate(geom="text", x=3, y=0.6, label="N(2;0.5)", color="blue")+
  stat_function(fun=dnorm, args=(c(mean=2,sd=2)), colour="darkorchid") +
  annotate(geom="text", x=5, y=0.1, label="N(2;2)", color="darkorchid")

# erzeuge X-Werte
df    <- data.frame(x=seq(-3,3, by=0.005))
# berechne Y-Werte
df$y  <- dnorm(df$x)
# Setze Cuts
df$sd <- cut(df$x, breaks = c(-Inf, 1 *(-2:2), Inf))

# plotte als area
ggplot(df, aes(x, y, fill=sd)) + geom_area()

Erzeugen wir den Bereich von 1 Standardabweichung.

df <- data.frame(x=seq(-3,3, by=0.005))
df$y  <- dnorm(df$x)
df$sd <- cut(df$x, breaks = c(-1,1))
df <- rbind(df, data.frame(x=c(-1, 1), y=c(0,0),sd=c(-1,1)))
df$sd <- forcats::fct_explicit_na(df$sd, na_level="x")

ggplot(df, aes(x, y, fill=sd)) +
  ggtitle("Standardnormalverteilung", subtitle = "Bereich von 1 Standardabweichung") +
  geom_area() + theme(legend.position = "none") +
  geom_vline(xintercept=0, linetype="dotted")+
  geom_vline(xintercept=-1, linetype="dashed", col="blue", size=1)+
  geom_vline(xintercept=1, linetype="dashed", col="blue", size=1)+
  scale_fill_manual(name=c("x","(-1,1]"), values=c("skyblue", "snow3"))

Nun erzeugen wir den Bereich von 2 Standardabweichungen.

df    <- data.frame(x=seq(-3,3, by=0.005))
df$y  <- dnorm(df$x)
df$sd <- cut(df$x, breaks = c(-2,2))
df    <- rbind(df, data.frame(x=c(-2, 2), y=c(0,0),sd=c(-2,2)))
df$sd <- forcats::fct_explicit_na(df$sd, na_level="x")

ggplot(df, aes(x, y, fill=sd)) +
  ggtitle("Standardnormalverteilung", subtitle = "Bereich von 2 Standardabweichung") +
  geom_area() + theme(legend.position = "none") +
  geom_vline(xintercept=0, linetype="dotted")+
  geom_vline(xintercept=-2, linetype="dashed", col="purple3", size=1)+
  geom_vline(xintercept=2, linetype="dashed", col="purple3", size=1)+
  scale_fill_manual(name=c("x","(-2,2]"), values=c("purple3", "snow3"))

Erzeugen wir die Fläche, in der 95% der Werte liegen, also von 1,96 Standardabweichungen.

df    <- data.frame(x=seq(-3,3, by=0.005))
df$y  <- dnorm(df$x)
df$sd <- cut(df$x, breaks = c(-1.96, 1.96))
df    <- rbind(df, data.frame(x=c(-1.96, 1.96), y=c(0,0),sd=c(-1.96,1.96)))
df$sd <- forcats::fct_explicit_na(df$sd, na_level="x")

ggplot(df, aes(x, y, fill=sd)) +
  ggtitle("Standardnormalverteilung", subtitle = "95% der Werte bei 1,96 sd") +
  geom_area() + theme(legend.position = "none") +
  geom_vline(xintercept=0, linetype="dotted")+
  geom_vline(xintercept=-2, linetype="dotted", col="purple3", size=0.5)+
  geom_vline(xintercept=2, linetype="dotted", col="purple3", size=0.5)+
  geom_vline(xintercept=-1.96, col="chartreuse4", size=0.5)+
  geom_vline(xintercept=1.96, col="chartreuse4", size=0.5)+
  scale_fill_manual(name=c("x","(-1.96,1.96]"), values=c("seagreen3", "snow3"))

Dieser Code kann für die Erzeugung der Grafiken von Tabelle Tabelle 41.1 und Tabelle 41.2 verwendet werden.

df    <- data.frame(x=seq(-3,3, by=0.005))
df$y  <- dnorm(df$x)
df$sd <- "B"
df$sd[df$x < 1.4] <- "A"

ggplot(df, aes(x, y, fill=sd)) +
  geom_area() + theme(legend.position = "none") +
  geom_vline(xintercept=0, linetype="dotted")+
  geom_vline(xintercept=1.4, col="blue", size=0.5)+
  ylab("") + scale_fill_manual(values=c("skyblue", "snow3"))

df    <- data.frame(x=seq(-3,3, by=0.005))
df$y  <- dnorm(df$x)
df$sd <- "B"
df$sd[df$x < 1.4] <- "A"

ggplot(df, aes(x, y, fill=sd)) +
  geom_area() + theme(legend.position = "none") +
  geom_vline(xintercept=0, linetype="dotted")+
  geom_vline(xintercept=1.4, col="blue", size=0.5)+
  ylab("") + scale_fill_manual(values=c("snow3", "skyblue"))

37.3 t-Verteilung

Die t-Verteilung kann mit ggplot geplottet werden.

# Erzeuge x-werte
df <- data.frame(x=seq(-3,3, by=0.005))

# Grundlegene Plotangaben
p <- ggplot(data=df, aes(x)) +
  # begrenze die Achsen
  xlim(-3,3) + ylim(0, 0.4) +
  # Achsen-Titel
  xlab("x") + ylab("Dichtefunktion") +
  # Plot-Titel
  ggtitle("t-Verteilungen", subtitle = "nach Freiheitsgraden")

  # t-Verteilung plotten
p +
  stat_function(fun=dt, args=list(df=1), col="black") +
  # Textfeld hinzufügen
  annotate(geom="text", x=0, y=0.25, label="df=1", color="black")

Dem Plott können weitere Freiheitsgrade hinzugefügt werden.

# t-Verteilungen plotten
p +
  stat_function(fun=dt, args=list(df=1), col="black") +
  annotate(geom="text", x=0, y=0.25, label="df=1", color="black")+
  stat_function(fun=dt, args=list(df=2), col="blue") +
  annotate(geom="text", x=0, y=0.37, label="df=2", color="blue")

Wenn die t-Werte bereits berechnet wurden, kann eine alternative Vorgehensweise so aussehen:

# berechne t-Werte für Freiheitsgrade 1 bis 5
x=seq(-3,3, by=0.005)
df <- data.frame(
    x,
    df1 = dt(x,df=1),
    df2 = dt(x,df=2),
    df3 = dt(x,df=3),
    df4 = dt(x,df=4),
    df5 = dt(x,df=5)
)

# wandle ins Format "long table" um
df <- pivot_longer(df, cols=c(df1, df2, df3, df4, df5))

# grundlegende Ploteinstellungen
p <- ggplot(data=df, aes(x,value)) +
  xlim(-3,3) + ylim(0, 0.4) +
  xlab("x") + ylab("Dichtefunktion") +
  ggtitle("t-Verteilungen", subtitle = "nach Freiheitsgraden")

# plotte t-Verteilungen
p + geom_line(aes(col=name))+
  labs(col="Freiheitsgrade")

37.4 χ2\chi^2-Verteilung

Die χ2\chi^2-Verteilung kann mit ggplot geplottet werden.

# Erzeuge x-werte
x=seq(0,25, by=0.005)
df <- data.frame(  x,
  df01 = dchisq(x, df=1),
  df05 = dchisq(x, df=5),
  df10 = dchisq(x, df=10),
  df15 = dchisq(x, df=15)
)

# erzeuge long-table
df <- pivot_longer(df, cols=c(df01, df05, df10, df15))

p <- ggplot(data=df, aes(x, value, fill=name)) +
  xlim(0,25) + ylim(0, 0.2) +
  xlab("x") + ylab("Dichtefunktion") +
  ggtitle("Chi^2-Verteilungen", subtitle = "nach Freiheitsgraden")
p + geom_line(aes(col=name,linetype=name))+
labs(col="Freiheitsgrade",linetype="")

Die Fläche unterhalb der Kurve kann mit geom_area() erzeugt werden. Für df=1 lautet der Aufruf:

# erzeuge Dummy-Werte
x=seq(0,25, by=0.005)
# überführe in Datenframe
df <- data.frame(  x, df01 = dchisq(x, df=1))
ggplot(data=df, aes(x, df01)) +
  xlim(0,25)  + coord_cartesian(ylim=c(0, 0.2)) +
  xlab("Chi^2-Wert") + ylab("Dichtefunktion") +
  ggtitle("Chi^2-Verteilung", subtitle = "mit 1 Freiheitsgrad") +
  geom_line(col="#F8766D") +geom_area(fill="skyblue")

## Warning: Removed 1 rows containing non-finite values (`stat_align()`).

Ähnlich wie bei der Normalverteilung können wir die Fläche für bestimmte χ2\chi^2-Werte einfärben. Bei einem Freiheitsgrad und α=0,05\alpha=0,05 ergibt sich ein kritischer χ2\chi^2-Wert von 3,84. Der Flächenanteil unterhalb dieses Wertes lässt sich wie folgt darstellen:

# Dummy-Werte erzeugen
df <- data.frame(x=seq(0.005,25, by=0.005))
# Chi^2-Werte für df=1 erstellen
df$y  <- dchisq(df$x, df=1)
# Grenze bei 3.84 einziehen
df$sd <- cut(df$x, breaks = c(0.00, 3.84))
ggplot(df, aes(x, y, fill=sd)) +
  geom_area() + theme(legend.position = "none") +
  geom_line(aes(col="#F8766D")) +
  xlim(0,25) + coord_cartesian(ylim=c(0, 0.2)) +
  xlab("Chi^2-Wert") + ylab("Dichtefunktion") +
  ggtitle("Chi^2-Verteilung", subtitle = "mit 1 Freiheitsgrad")+
  geom_vline(xintercept=3.84, col="blue", size=0.5, linetype="dashed")+
  ylab("") + scale_fill_manual(values=c("skyblue", "snow3")) +
  annotate(geom="text", x=2, y=0.028, size=8,label="95%", color="blue")

37.5 Poisson-Verteilung

Die Poisson-Verteilung kann mit ggplot geplottet werden.

# Erzeuge x-Werte
x=seq(0,25)
df <- data.frame(  x,
                   l1 = dpois(x, 1),
                   l2 = dpois(x, 2),
                   l4 = dpois(x, 4),
                   l9 = dpois(x, 9)
)

# erzeuge eine long-table
df <- pivot_longer(df, cols=c(l1, l2, l4, l9))

# plot vorbereiten
p <- ggplot(data=df, aes(x, value, fill=name)) +
  xlim(0,17) + ylim(0, 0.4) +
  xlab("x") + ylab("Dichtefunktion") +
  ggtitle("Poisson-Verteilungen", subtitle = "nach Lambda")
p + geom_line(aes(col=name,linetype=name))+
  labs(col="Lambda",linetype="")

## Warning: Removed 32 rows containing missing values (`geom_line()`).

Da wir die Plot-Grundlagen in p gespeichert haben, können wir ergänzen:

p + geom_bar(aes(col=name,linetype=name), stat="identity", position="dodge")+
   geom_line(aes(col=name,linetype=name))+
  labs(col="Lambda",linetype="")

## Warning: Removed 36 rows containing missing values (`geom_bar()`).

## Warning: Removed 32 rows containing missing values (`geom_line()`).

Möchte man einen Polygonzug, müssen als Ausgangspunkt die Koordinaten (0,0) festegelegt werden.

x=seq(0,25)#, by=0.005)
df <- data.frame(  x=c(0,x),
                   l1 = c(0, dpois(x, 1)),
                   l2 = c(0, dpois(x, 2)),
                   l4 = c(0, dpois(x, 4)),
                   l9 = c(0, dpois(x, 9))
)
df <- pivot_longer(df, cols=c(l1, l2, l4, l9))

p <- ggplot(data=df, aes(x, value, fill=name)) +
  xlim(0,17) + ylim(0, 0.4) +
  xlab("x") + ylab("Dichtefunktion") +
  ggtitle("Poisson-Verteilungen", subtitle = "nach Lambda")
p + geom_polygon(aes(col=name,linetype=name))+
  geom_line(aes(col=name),linetype="dashed")+
  labs(col="Lambda", fill="Lambda", linetype="Lambda")

## Warning: Removed 32 rows containing missing values (`geom_line()`).

37.6 BMI-Gewichtskategorien

Wir benötigen eine Übersichtsgrafik, aus der hervorgeht, in welcher BMI-Klasse sich ein Patient in Abhängigkeit von Körpergröße und Körpergewicht befindet.

Erzeugen wir zunächst unsere Daten.

# BMI-Gewichtsklassen
Gewichtsklassen <- c(0, 18.5, 25, 30, 35, 40, 100)
# Schöne Farben
Farben <- c("skyblue4", "darkgreen", "yellow", "orange", "red", "darkred", "black")
# 100 Körpergrößen von 1,4m bis 2,2m
Koerpergroesse <- seq(1.4, 2.2, length = 100)

# Funktion um das BMI-Grenzgewicht pro Klasse
# für jede Körpergröße zu bestimmen
bmi.k <- function(groesse, konstant) {
  return(groesse^2 * konstant)
}

37.6.1 R base

Wir erzeugen das Diagramm mit der plot()-Funktion, wobei wir die Klassen als Polygone einzeichnen.

Ein Polygon wird so gezogen, als würden wir es mit einem Stift auf Papier zeichnen, ohne den Stift dabei abzusetzen. Das heisst, wir müssen “hin und zurück” zeichnen. Wir beginnen unten links, zeichnen von dort nach rechts, dann nach oben, und von dort wieder zurück nach links und wieder hinunter.

Für die Körpergröße (x-Achse) bedeutet das, dass wir die Körpergrößen in umgekehrter Reihenfolge an die “Originalreihe” kleben müssen, um wieder “zurück” zum Ausgangspunkt zu kommen.

Gross <- c(Koerpergroesse, rev(Koerpergroesse))

Für die Klassengrenzen (y-Achse) benötigen wir für das “Zurückkehren” die Werte der nächsten Klasse in umgekehrter Reihenfolge.

# Berechne alle Klassengrenzen
Klasse1 <- c(bmi.k(Koerpergroesse, Gewichtsklassen[1]), rev(bmi.k(Koerpergroesse, Gewichtsklassen[2])))
Klasse2 <- c(bmi.k(Koerpergroesse, Gewichtsklassen[2]), rev(bmi.k(Koerpergroesse, Gewichtsklassen[3])))
Klasse3 <- c(bmi.k(Koerpergroesse, Gewichtsklassen[3]), rev(bmi.k(Koerpergroesse, Gewichtsklassen[4])))
Klasse4 <- c(bmi.k(Koerpergroesse, Gewichtsklassen[4]), rev(bmi.k(Koerpergroesse, Gewichtsklassen[5])))
Klasse5 <- c(bmi.k(Koerpergroesse, Gewichtsklassen[5]), rev(bmi.k(Koerpergroesse, Gewichtsklassen[6])))
Klasse6 <- c(bmi.k(Koerpergroesse, Gewichtsklassen[6]), rev(bmi.k(Koerpergroesse, Gewichtsklassen[7])))
Klasse7 <- c(bmi.k(Koerpergroesse, Gewichtsklassen[7]), rev(bmi.k(Koerpergroesse, Gewichtsklassen[8])))

Jetzt können wir das Diagramm plotten.

# Plotparameter festlegen
par(bg="whitesmoke")
plot(Koerpergroesse, bmi.k(Koerpergroesse, 18), type="n",
     xlim=c(1.60, 1.99), ylim=c(40, 125),
     xaxt="n", yaxt="n",
     cex.axis=1.4, cex.lab=1.3, cex.main=1.7,
     xlab="Größe [in m]", ylab="Gewicht [in kg]", main="Body Mass Index")

# Polygone der Gewichtsklassen einzeichnen
polygon(Gross, Klasse1, col=Farben[1])
polygon(Gross, Klasse2, col=Farben[2])
polygon(Gross, Klasse3, col=Farben[3])
polygon(Gross, Klasse4, col=Farben[4])
polygon(Gross, Klasse5, col=Farben[5])
polygon(Gross, Klasse6, col=Farben[6])
polygon(Gross, Klasse7, col=Farben[7])

# graues Gitter
box()
grid(lty="dotdash" ,col="darkgrey")
abline(v=seq(1.65, 1.95, by=0.1), h=seq(50, 110, by=20), lty="dotted", col="grey")

# Legendenbox
legend(x="bottomright", inset=0.005,
       legend=c("Untergewicht", "Normalgewicht", "Präadipositas",
                "Adipositas Grad I", "Adipositas Grad II", "Adipositas Grad III"),
       col=Farben, lwd=6, bg="skyblue")

# X-Achse in Groß
axis(1, at=format(seq(1.60, 2, by=0.1), nsmall=2),
     labels=format(seq(1.60, 2, by=0.1), nsmall=2), cex.axis=1.5)
# X-Achse Zwischenschritte in Klein und Grau
axis(1, at=seq(1.65, 2, by=0.1), cex.axis=1.2, col.axis="grey")
# Y-Achse Ticks
axis(2, at=seq(40, 120, by=10), cex.axis=1.5)

37.6.2 ggplot()

Schauen wir nun, wie wir das Diagramm mit ggplot() zeichnen würden. Dafür müssen wir die Daten zunächst als “long table” schreiben (siehe Kapitel 25). Wie Sie evtl. im obigen Beispiel bemerkt haben, kommt Klasse7 in den Plotdimensionen gar nicht vor, daher lassen wir sie direkt weg.

# bereite "long table" vor
df1 <- data.frame(Gross, Wert=Klasse1, BMI="Untergewicht")
df2 <- data.frame(Gross, Wert=Klasse2, BMI="Normalgewicht")
df3 <- data.frame(Gross, Wert=Klasse3, BMI="Präadipositas")
df4 <- data.frame(Gross, Wert=Klasse4, BMI="Adipositas I")
df5 <- data.frame(Gross, Wert=Klasse5, BMI="Adipositas II")
df6 <- data.frame(Gross, Wert=Klasse6, BMI="Adipositas III")
# schreibe alles ins Datenframe df
df <- rbind(df1, df2, df3, df4, df5, df6)
df$BMI <- factor(df$BMI, ordered = TRUE,
                 levels=c("Untergewicht", "Normalgewicht", "Präadipositas",
                          "Adipositas I", "Adipositas II", "Adipositas III"))

Das geht auch auf diese Weise:

df <- data.frame(Gross=rep(Gross, 6),
                 Wert=c(Klasse1, Klasse2, Klasse3, Klasse4, Klasse5, Klasse6),
                 BMI=factor(c(rep("Untergewicht", 200), rep("Normalgewicht", 200),
                              rep("Präadipositas", 200), rep("Adipositas I", 200),
                              rep("Adipositas II", 200), rep("Adipositas III", 200)),
                            ordered=TRUE,
                            levels=c("Untergewicht", "Normalgewicht", "Präadipositas",
                                     "Adipositas I", "Adipositas II", "Adipositas III")
                            )
                 )

Nun können wir die Daten an ggplot() übergeben.

## Plotten mittels ggplot
library(ggplot2)
ggplot(df) + aes(x=Gross, y=Wert, fill=BMI) +
  geom_polygon()+
  coord_cartesian(xlim = c(1.55, 2.05), ylim = c(35, 125), expand = FALSE)+
  scale_fill_manual(values = Farben) +
  theme(legend.position = c(0.9, 0.2))

37.6.2.1 Alternative

Eine alternative Lösung kann mit geom_contour_filled() erreicht werden. Dieses Geom kennt außer x und y noch eine zusätzliche virtuelle z-Achse. Wenn wir das Gewicht auf der y-Achse darstellen wollen, und die Körpergröße auf der x-Achse, so können wir auf der virtuellen z-Achse den jeweils dazugehörigen BMI-Wert angeben. Dem Geom geom_contour_filled() können wir dann über den breaks-Parameter die BMI-Grenzwerte mitteilen, und die jeweiligen Bereiche manuell einfärben.

Zunächst erzeugen wir mittels expand.grid() eine Tabelle mit Kombinationen von Körpergröße und Gewicht.

# erzeuge alle Kombinationen von
# Körpergröße und Gewicht
df <- expand.grid(Gewicht = seq(30, 130, 0.1),
                  Größe = seq(1.5, 2.1, 0.01))
# erste Zeilen anzeigen
head(df)

##   Gewicht Größe
## 1    30.0   1.5
## 2    30.1   1.5
## 3    30.2   1.5
## 4    30.3   1.5
## 5    30.4   1.5
## 6    30.5   1.5

Jetzt können wir ggplot() wie folgt aufrufen:

library(ggplot2)
ggplot(df) +
  aes(Größe, Gewicht) +
  # berechne für "z" den BMI (kg/m^2)
  geom_contour_filled(aes(z = Gewicht/Größe^2),
                      # schwarzer Rahmen
                      color = "black",
                      # BMI Grenzwerte
                      breaks = c(0, 18.5, 25, 30, 35, 40, 100)) +
  # manuell umfärben und umbenennen
  scale_fill_manual("BMI",
                    values = rev(c("#8b0000", "red", "#ffa500", "yellow",
                                   "#006400", "#4a708b")),
                    labels=c("Untergewicht", "Normalgewicht", "Präadipositas",
                             "Adipositas I", "Adipositas II", "Adipositas III")) +
  # Plot-Dimensionen festlegen
  coord_cartesian(xlim = c(1.55, 2.05),
                  ylim = c(35, 125),
                  expand = FALSE) +
  # X-Ticks
  scale_x_continuous(breaks = seq(1.6, 2.0, 0.05)) +
  #Y-Ticks
  scale_y_continuous(breaks = seq(40, 120, 10) )+
  # legendenbox innerhalb des Plots platzieren
  theme(legend.position = c(0.88, 0.25)) +
  # Graues Gitter per vline und hline hinzufügen
  geom_vline(xintercept = seq(1.55, 2.05, 0.01),
             linetype = "solid",
             color = "gray",
             size = 0.1,
             alpha=0.3) +
  geom_hline(yintercept=seq(36, 124, 2),
             linetype="solid",
             color="gray",
             alpha=0.3)

38 Pakete verwalten

Ich möchte Zusatzpakete verwalten.

38.1 Pakete beim Start automatisch laden

Ich möchte, dass Pakete direkt beim Start von R geladen werden.

Legen Sie hierzu in Ihrem Arbeitsverzichnis die Datei .Rprofile an. In diese Datei können Sie alle Befehle schreiben, die beim Start ausgeführt werden sollen. Das geht auch direkt in R:

file.edit(".Rprofile")

# Inhalt von .Rprofile
print("Willkommen zurück!")
print("Ich lade Paket tidyverse")
library(tidyverse)

39 Rmarkdown

Ich möchte RMarkdown individualisieren.

39.1 Ausgabeformat per .css-Datei ändern

Ich möchte die Ausgabe der html-Datei ändern.

Hierfür kann eine css-Datei angelegt werden. Im Kopf des RMarkdown-Dokumentes wird die Datei MeinStyle.css wie folgt eingebunden:

---
title: "Test"
output:
  html_document:
       css:  MeinStyle.css
---

In der css-Datei können dann wie gewohnt Style-Angaben gemacht werden, z.B. das Aussehen der Codebox. Diese sind über die Parameter class.source (R-Befehle) und class.output (R-Ausgabe) referenzierbar. In der css-Datei definieren wir unsere eigene Klasse “meine” und hinterlegen gewünschte Parameter.

 .meine {
    background-color: rgb(100, 0, 0);
}

Auf diese Klasse können wir in den Chunks verweisen mit

```{r class.output="meine"}
# hier steht der R-Code
```

```{r class.source="meine"}
# hier steht der R-Code
```

Möchte man alle Chunks automatisch auf class.output="meine" setzen, kann dies im YAML-Kopfbereich wie folgt angegeben werden:

knitr::opts_chunk$set(echo = TRUE, class.output="meine")

Es gibt auch vordefinierte Klassen für die Chunks, probieren Sie bg-primary, bg-success, bg-info, bg-warning, und bg-danger aus.

40 quarto

40.1 eigene $\LaTeX$ Vorlagen erstellen

Quarto ermöglicht es, zusätzlichen LaTeX-Code einzufügen (siehe https://quarto.org/docs/output-formats/pdf-basics.html#latex-includes) oder nur einzelne Teile des Templates auszutauschen (siehe https://quarto.org/docs/journals/templates.html#template-partials).

Es können aber auch eigene vollständige $\LaTeX$-Vorlagen erzeugt werden.

Folgende Punkte sind dabei zu beachten:

Möchten Sie alle Funktionen von quarto verfügbar und Ihre Vorlage so flexibel wie möglich machen, sollten Sie die quarto-Vorlage kopieren (siehe https://github.com/quarto-dev/quarto-cli/tree/main/src/resources/formats/pdf/pandoc) und Ihre Änderungen direkt dort einfügen.

Möchten Sie sich z.B. eine Briefvorlage erstellen, deren wesentlichen Parameter (Dokumentenklasse, Schriftgröße, Geomtery, etc.) sich eh nie ändern werden, ist es durchaus legitim, eine eigene “rudimentäre” $\LaTeX$-Datei zu erstellen.

40.1.1 .tex-Datei

Ich habe mir einen “zentralen” Ordner erstellt (z.B. Dokumente/Vorlagen/quarto), in welchem ich alle Vorlagendateien ablege. So kann ich sie später bequem in jeder .qmd-Datei einbinden.

Die $\LaTeX$-Vorlage sollte mindestens folgende Werte enthalten, damit die rudimentären quarto-Funktionen unterstützt werden.

\documentclass[a4paper, 10pt]{scrartcl} % Dokumentenklasse wählen
\usepackage[utf8]{inputenc}
\usepackage[ngerman]{babel}             % deutsche Sprache harcoded
\usepackage{lmodern}
\usepackage[numbered]{bookmark}
\usepackage{graphicx}
\usepackage{longtable}
\usepackage{booktabs}
\usepackage{calc}
\usepackage{lastpage}
\usepackage{geometry}
\usepackage{tabularx}
\usepackage{colortbl}
\usepackage{multirow}
\usepackage{float}
\usepackage{amsmath}
\usepackage{hhline}
\usepackage{blindtext}
\usepackage{xcolor}
\usepackage[normalem]{ulem}

\definecolor{linkblue}{RGB}{17, 40, 73}

\hypersetup{
colorlinks,
linkcolor=linkblue,
urlcolor=blue,
bookmarks=true,
plainpages=false,
hypertexnames=true,
pdftitle={$title$},
pdfauthor={$author$}
}

\providecommand{\tightlist}{%
  \setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}

%% Callout-Boxen
\makeatletter
\@ifpackageloaded{tcolorbox}{}{\usepackage[many]{tcolorbox}}
\@ifpackageloaded{fontawesome5}{}{\usepackage{fontawesome5}}
\definecolor{quarto-callout-color}{HTML}{909090}
\definecolor{quarto-callout-note-color}{HTML}{0758E5}
\definecolor{quarto-callout-important-color}{HTML}{CC1914}
\definecolor{quarto-callout-warning-color}{HTML}{EB9113}
\definecolor{quarto-callout-tip-color}{HTML}{00A047}
\definecolor{quarto-callout-caution-color}{HTML}{FC5300}
\definecolor{quarto-callout-color-frame}{HTML}{acacac}
\definecolor{quarto-callout-note-color-frame}{HTML}{4582ec}
\definecolor{quarto-callout-important-color-frame}{HTML}{d9534f}
\definecolor{quarto-callout-warning-color-frame}{HTML}{f0ad4e}
\definecolor{quarto-callout-tip-color-frame}{HTML}{02b875}
\definecolor{quarto-callout-caution-color-frame}{HTML}{fd7e14}
\makeatother

$if(highlighting-macros)$
$highlighting-macros$
$endif$

\begin{document}

$body$

\end{document}

Die Variable $body$ fügt den Inhalte der qmd-Datei (alles unterhalb des YAML-Headers) ein.

40.1.2 Vorlage einbinden

Die Vorlage wird wie folgt im YAML-Header eingebunden:

---
format:
  pdf:
    template: /pfad/zur/vorlage.tex
---

40.1.3 YAML-Variablen

Im YAML-Header können Variablen gesetzt werden, welche in der .tex-Datei abgerufen werden können.

---
title: Meine Vorlage
variable1: "Test"
variable2: "Noch ein Test"

format:
  pdf:
    template: /pfad/zur/vorlage.tex
---

In der .tex-Datei sind die oben definierten Variablen verfügbar per $variable1$ und $variable2$.

\begin{document}

$title$

$body$

Dies ist ein $variable1$.
Und dies ist $variable2$.

\end{document}

Eine Beispiel für die oben erwähnte Briefvorlage finden Sie unter https://www.produnis.de/blog/post/quarto-briefvorlage/.

40.2 quarto am Handy oder Tablet

Am Handy oder am Tablet sind quarto und $\LaTeX$ nicht verfügbar.

Eine Möglichkeit, dennoch .qmd-Dateien zu rendern, besteht darin, das Rendern an einen Server auszulagern. Wenn Sie zu Hause einen kleinen Linux-Heimserver betreiben, könnten Ihnen diese beiden Bots weiterhelfen. Beide setzen voraus, dass quarto auf dem Server installiert ist. Ebenfalls sollte $\LaTeX$ via quarto install tool tinytex installiert worden sein, damit eventuell benötigte Pakete automatisch nachgeladen werden.

quarto-Bot für Nextcloud

Der Bot ist ein kleines Shell-Script.

Der Bot mountet ein Nextcloud-Verzeichnis per Webdav und schaut nach, ob in dem Verzeichnis eine .qmd-Datei enthalten ist.

Sollte dies der Fall sein, führt der Bot den Befehl quarto render DATEI.qmd aus.

Da die erzeugt PDF-Datei im selben Verzeichnis liegt, ist sie über die Nextcloud-Dateiansicht verfügbar und wird ebenfalls an alle Clients synchronisiert.

https://www.produnis.de/blog/post/simpler-quarto-bot-fuer-nextcloud/

quarto-matrix-bot

Der Bot ist in python geschrieben.

Der Bot lauscht in vorher angegebenen Matrix-Räumen.

Wird eine .qmd-Datei in den Raum gesendet, führt der Bot den Befehl quarto render DATEI.qmd aus.

Wenn eine PDF-Datei erzeugt wurde, sendet der Bot diese Datei in den Matrix-Raum.

Sollte das Rendern fehlschlagen, postet der Bot die Fehlermeldungen in den Raum.

https://github.com/rgomez90/matrix-bot

41 Referenztabellen erstellen

Ich möchte Referenztabellen erstellen und benötige die Werte.

41.1 Fallzahlen nach Effektstärke und Power

Abb. 41.1: Fallzahlen nach Effektstärke und Power

Der Quellcode für die Werte in Tabelle -Abbildung 41.1 ist etwas komplizierter, da wir zunächst eine Funktion programmieren müssen, um die Tabelle ausgeben zu können.

# lade Paket zur Poweranalyse
library(pwr)
# erzeuge Hilfsfunktion, die nur die errechnete Fallzahl ausgibt
#----------------------------------
powertabelle <- function(alpha, d, power) { round(pwr::pwr.t.test(d = d, sig.level = alpha, power = power, type = "two.sample", alternative = "two.sided")$n)}
# Ende der Funktion

# ein Datenframe aus allen gewünschten Kombinationen
# von power, alpha und d
scen <- expand.grid( alpha = c(0.05, 0.01),
                    d = seq(from = 0.1, to = 0.8, by = 0.05),
                    power = c(seq(0.6, 0.9, 0.1), 0.95, 0.99)
                )

# füge Datenframe "scen" eine neue Spalte "n" hinzu
# per apply() wird auf jede Datenreihe die Funktion "powertabelle" angewendet.
# line() hilft dabei, die richtigen Werte aus dem
# Datenframe "scen" an die Funktion "powertabelle" zu übergeben
scen$n <- apply(scen, 1, function(line) powertabelle(line["alpha"], line["d"], line["power"]))

# zeige die ersten 20 Datenreihen an
head(scen, 20)

##    alpha    d power    n
## 1   0.05 0.10   0.6  981
## 2   0.01 0.10   0.6 1603
## 3   0.05 0.15   0.6  436
## 4   0.01 0.15   0.6  713
## 5   0.05 0.20   0.6  246
## 6   0.01 0.20   0.6  402
## 7   0.05 0.25   0.6  158
## 8   0.01 0.25   0.6  258
## 9   0.05 0.30   0.6  110
## 10  0.01 0.30   0.6  180
## 11  0.05 0.35   0.6   81
## 12  0.01 0.35   0.6  132
## 13  0.05 0.40   0.6   62
## 14  0.01 0.40   0.6  102
## 15  0.05 0.45   0.6   49
## 16  0.01 0.45   0.6   81
## 17  0.05 0.50   0.6   40
## 18  0.01 0.50   0.6   66
## 19  0.05 0.55   0.6   33
## 20  0.01 0.55   0.6   55

Jetzt können Teilmengen von scen erstellt werden, um die Werte entsprechend der gewünschten Tabellen (α=0.05\alpha = 0.05 und α=0.01\alpha = 0.01) zu erzeugen.

# erstelle ein subset für "alpha=0.05"
subset05 <- subset(scen, alpha == .05)

# benenne die Spalten um
# und bringe per "reshape" die Tabelle ins "wide"-Format
tabelle05 <- reshape(subset05, v.names = "n", timevar = "d",  idvar = "power", direction = "wide")

# gib tabelle aus
print(tabelle05)

##     alpha power n.0.1 n.0.15 n.0.2 n.0.25 n.0.3 n.0.35 n.0.4 n.0.45 n.0.5
## 1    0.05  0.60   981    436   246    158   110     81    62     49    40
## 31   0.05  0.70  1235    550   310    198   138    102    78     62    50
## 61   0.05  0.80  1571    699   393    252   175    129    99     78    64
## 91   0.05  0.90  2102    935   526    337   234    173   132    105    85
## 121  0.05  0.95  2600   1156   651    417   290    213   163    129   105
## 151  0.05  0.99  3675   1634   920    589   409    301   231    182   148
##     n.0.55 n.0.6 n.0.65 n.0.7 n.0.75 n.0.8
## 1       33    28     24    21     18    16
## 31      42    35     30    26     23    20
## 61      53    45     38    33     29    26
## 91      70    59     51    44     38    34
## 121     87    73     62    54     47    42
## 151    122   103     88    76     66    58

# erstelle ein subset für "alpha=0.01"
subset01 <- subset(scen, alpha == .01)

# benenne die Spalten um
# und bringe per "reshape" die Tabelle ins "wide"-Format
tabelle01 <- reshape(subset01, v.names = "n", timevar = "d",  idvar = "power", direction = "wide")

# gib tabelle aus
print(tabelle01)

##     alpha power n.0.1 n.0.15 n.0.2 n.0.25 n.0.3 n.0.35 n.0.4 n.0.45 n.0.5
## 2    0.01  0.60  1603    713   402    258   180    132   102     81    66
## 32   0.01  0.70  1924    856   482    309   215    159   122     97    79
## 62   0.01  0.80  2337   1040   586    375   261    192   148    117    95
## 92   0.01  0.90  2978   1324   746    478   332    245   188    149   121
## 122  0.01  0.95  3564   1585   892    572   398    293   224    178   144
## 152  0.01  0.99  4808   2138  1203    771   536    394   302    239   194
##     n.0.55 n.0.6 n.0.65 n.0.7 n.0.75 n.0.8
## 2       55    46     40    34     30    27
## 32      65    55     47    41     36    32
## 62      79    67     57    49     43    38
## 92     100    84     72    62     55    48
## 122    119   101     86    74     65    57
## 152    161   135    115   100     87    77

Wenn Sie den Abschnitt über Tidy Data in Kapitel Kapitel 25 gelesen haben, ist Ihnen evtl. aufgefallen, dass wir hier eine long table in eine wide table umgeformt haben. Das erwähne ich deshalb, da wir sonst immer genau das Gegenteil machen, nämlich wide table ins Format long table zu überführen.

Übrigens, die PDF-Version dieses Buch ist mit LaTeX erstellt worden, und ich hatte keine Lust, die Werte für die Tabellen von Hand in LaTeX zu übertragen.

Mit der Funktion xtable() aus dem Zusatzpaket xtable kann die Ausgabe der Tabelle in LaTeX-Quellcode erfolgen:

# lade Paket "xtable"
library(xtable)

# gib die Tabelle als LaTeX-Code aus
xtable(tabelle01)

## % latex table generated in R 4.3.2 by xtable 1.8-4 package
## % Mon Jan  1 23:11:40 2024
## \begin{table}[ht]
## \centering
## \begin{tabular}{rrrrrrrrrrrrrrrrrr}
##   \hline
##  & alpha & power & n.0.1 & n.0.15 & n.0.2 & n.0.25 & n.0.3 & n.0.35 & n.0.4 & n.0.45 & n.0.5 & n.0.55 & n.0.6 & n.0.65 & n.0.7 & n.0.75 & n.0.8 \\
##   \hline
## 2 & 0.01 & 0.60 & 1603.00 & 713.00 & 402.00 & 258.00 & 180.00 & 132.00 & 102.00 & 81.00 & 66.00 & 55.00 & 46.00 & 40.00 & 34.00 & 30.00 & 27.00 \\
##   32 & 0.01 & 0.70 & 1924.00 & 856.00 & 482.00 & 309.00 & 215.00 & 159.00 & 122.00 & 97.00 & 79.00 & 65.00 & 55.00 & 47.00 & 41.00 & 36.00 & 32.00 \\
##   62 & 0.01 & 0.80 & 2337.00 & 1040.00 & 586.00 & 375.00 & 261.00 & 192.00 & 148.00 & 117.00 & 95.00 & 79.00 & 67.00 & 57.00 & 49.00 & 43.00 & 38.00 \\
##   92 & 0.01 & 0.90 & 2978.00 & 1324.00 & 746.00 & 478.00 & 332.00 & 245.00 & 188.00 & 149.00 & 121.00 & 100.00 & 84.00 & 72.00 & 62.00 & 55.00 & 48.00 \\
##   122 & 0.01 & 0.95 & 3564.00 & 1585.00 & 892.00 & 572.00 & 398.00 & 293.00 & 224.00 & 178.00 & 144.00 & 119.00 & 101.00 & 86.00 & 74.00 & 65.00 & 57.00 \\
##   152 & 0.01 & 0.99 & 4808.00 & 2138.00 & 1203.00 & 771.00 & 536.00 & 394.00 & 302.00 & 239.00 & 194.00 & 161.00 & 135.00 & 115.00 & 100.00 & 87.00 & 77.00 \\
##    \hline
## \end{tabular}
## \end{table}

41.2 Verteilungsfunktion und Quantile der Standardnormalverteilung

Ich möchte die Werte dieser Tabelle:

Tab. 41.1: Verteilungsfunktion und Quantile der Standardnormalverteilung

ϕ\phi z | .00 | .01 | .02 | .03 | .04 | .05 | .06 | .07 | .08 | .09
0.0 | .5000 | .5040 | .5080 | .5120 | .5160 | .5199 | .5239 | .5279 | .5319 | .5359
0.1 | .5398 | .5438 | .5478 | .5517 | .5557 | .5596 | .5636 | .5675 | .5714 | .5753
0.2 | .5793 | .5832 | .5871 | .5910 | .5948 | .5987 | .6026 | .6064 | .6103 | .6141
0.3 | .6179 | .6217 | .6255 | .6293 | .6331 | .6368 | .6406 | .6443 | .6480 | .6517
0.4 | .6554 | .6591 | .6628 | .6664 | .6700 | .6736 | .6772 | .6808 | .6844 | .6879
0.5 | .6915 | .6950 | .6985 | .7019 | .7054 | .7088 | .7123 | .7157 | .7190 | .7224
0.6 | .7257 | .7291 | .7324 | .7357 | .7389 | .7422 | .7454 | .7486 | .7517 | .7549
0.7 | .7580 | .7611 | .7642 | .7673 | .7704 | .7734 | .7764 | .7794 | .7823 | .7852
0.8 | .7881 | .7910 | .7939 | .7967 | .7995 | .8023 | .8051 | .8078 | .8106 | .8133
0.9 | .8159 | .8186 | .8212 | .8238 | .8264 | .8289 | .8315 | .8340 | .8365 | .8389
1.0 | .8413 | .8438 | .8461 | .8485 | .8508 | .8531 | .8554 | .8577 | .8599 | .8621
1.1 | .8643 | .8665 | .8686 | .8708 | .8729 | .8749 | .8770 | .8790 | .8810 | .8830
1.2 | .8849 | .8869 | .8888 | .8907 | .8925 | .8944 | .8962 | .8980 | .8997 | .9015
1.3 | .9032 | .9049 | .9066 | .9082 | .9099 | .9115 | .9131 | .9147 | .9162 | .9177
1.4 | .9192 | .9207 | .9222 | .9236 | .9251 | .9265 | .9279 | .9292 | .9306 | .9319
1.5 | .9332 | .9345 | .9357 | .9370 | .9382 | .9394 | .9406 | .9418 | .9429 | .9441
1.6 | .9452 | .9463 | .9474 | .9484 | .9495 | .9505 | .9515 | .9525 | .9535 | .9545
1.7 | .9554 | .9564 | .9573 | .9582 | .9591 | .9599 | .9608 | .9616 | .9625 | .9633
1.8 | .9641 | .9649 | .9656 | .9664 | .9671 | .9678 | .9686 | .9693 | .9699 | .9706
1.9 | .9713 | .9719 | .9726 | .9732 | .9738 | .9744 | .9750 | .9756 | .9761 | .9767
2.0 | .9772 | .9778 | .9783 | .9788 | .9793 | .9798 | .9803 | .9808 | .9812 | .9817
2.1 | .9821 | .9826 | .9830 | .9834 | .9838 | .9842 | .9846 | .9850 | .9854 | .9857
2.2 | .9861 | .9864 | .9868 | .9871 | .9875 | .9878 | .9881 | .9884 | .9887 | .9890
2.3 | .9893 | .9896 | .9898 | .9901 | .9904 | .9906 | .9909 | .9911 | .9913 | .9916
2.4 | .9918 | .9920 | .9922 | .9925 | .9927 | .9929 | .9931 | .9932 | .9934 | .9936
2.5 | .9938 | .9940 | .9941 | .9943 | .9945 | .9946 | .9948 | .9949 | .9951 | .9952
2.6 | .9953 | .9955 | .9956 | .9957 | .9959 | .9960 | .9961 | .9962 | .9963 | .9964
2.7 | .9965 | .9966 | .9967 | .9968 | .9969 | .9970 | .9971 | .9972 | .9973 | .9974
2.8 | .9974 | .9975 | .9976 | .9977 | .9977 | .9978 | .9979 | .9979 | .9980 | .9981
2.9 | .9981 | .9982 | .9982 | .9983 | .9984 | .9984 | .9985 | .9985 | .9986 | .9986
3.0 | .9987 | .9987 | .9987 | .9988 | .9988 | .9989 | .9989 | .9989 | .9990 | .9990
p = | .5000 | .7500 | .8000 | .9000 | .9500 | .9750 | .9900 | .9950 | .9975 | .9990
zpz_p = | 0.0000 | 0.6745 | 0.8416 | 1.2816 | 1.6449 | 1.9600 | 2.3263 | 2.5758 | 2.8070 | 3.0902

Bei der z-Transformation benötigt man die Werte dieser Tabelle beispielsweise für Fragen wie “Wie hoch ist der prozentuale Anteil für Werte, die mindestens X sind”

Verteilungsfunktion und Quantile der Standardnormalverteilung

Die Werte werden erzeugt mit:

# erstelle Werte von 0.00 bis 3.09
z <- seq(from=0, to=3.09, by = 0.01)

# P-Werte berechnen
pnorm(z)

##   [1] 0.5000000 0.5039894 0.5079783 0.5119665 0.5159534 0.5199388 0.5239222
##   [8] 0.5279032 0.5318814 0.5358564 0.5398278 0.5437953 0.5477584 0.5517168
##  [15] 0.5556700 0.5596177 0.5635595 0.5674949 0.5714237 0.5753454 0.5792597
##  [22] 0.5831662 0.5870644 0.5909541 0.5948349 0.5987063 0.6025681 0.6064199
##  [29] 0.6102612 0.6140919 0.6179114 0.6217195 0.6255158 0.6293000 0.6330717
##  [36] 0.6368307 0.6405764 0.6443088 0.6480273 0.6517317 0.6554217 0.6590970
##  [43] 0.6627573 0.6664022 0.6700314 0.6736448 0.6772419 0.6808225 0.6843863
##  [50] 0.6879331 0.6914625 0.6949743 0.6984682 0.7019440 0.7054015 0.7088403
##  [57] 0.7122603 0.7156612 0.7190427 0.7224047 0.7257469 0.7290691 0.7323711
##  [64] 0.7356527 0.7389137 0.7421539 0.7453731 0.7485711 0.7517478 0.7549029
##  [71] 0.7580363 0.7611479 0.7642375 0.7673049 0.7703500 0.7733726 0.7763727
##  [78] 0.7793501 0.7823046 0.7852361 0.7881446 0.7910299 0.7938919 0.7967306
##  [85] 0.7995458 0.8023375 0.8051055 0.8078498 0.8105703 0.8132671 0.8159399
##  [92] 0.8185887 0.8212136 0.8238145 0.8263912 0.8289439 0.8314724 0.8339768
##  [99] 0.8364569 0.8389129 0.8413447 0.8437524 0.8461358 0.8484950 0.8508300
## [106] 0.8531409 0.8554277 0.8576903 0.8599289 0.8621434 0.8643339 0.8665005
## [113] 0.8686431 0.8707619 0.8728568 0.8749281 0.8769756 0.8789995 0.8809999
## [120] 0.8829768 0.8849303 0.8868606 0.8887676 0.8906514 0.8925123 0.8943502
## [127] 0.8961653 0.8979577 0.8997274 0.9014747 0.9031995 0.9049021 0.9065825
## [134] 0.9082409 0.9098773 0.9114920 0.9130850 0.9146565 0.9162067 0.9177356
## [141] 0.9192433 0.9207302 0.9221962 0.9236415 0.9250663 0.9264707 0.9278550
## [148] 0.9292191 0.9305634 0.9318879 0.9331928 0.9344783 0.9357445 0.9369916
## [155] 0.9382198 0.9394292 0.9406201 0.9417924 0.9429466 0.9440826 0.9452007
## [162] 0.9463011 0.9473839 0.9484493 0.9494974 0.9505285 0.9515428 0.9525403
## [169] 0.9535213 0.9544860 0.9554345 0.9563671 0.9572838 0.9581849 0.9590705
## [176] 0.9599408 0.9607961 0.9616364 0.9624620 0.9632730 0.9640697 0.9648521
## [183] 0.9656205 0.9663750 0.9671159 0.9678432 0.9685572 0.9692581 0.9699460
## [190] 0.9706210 0.9712834 0.9719334 0.9725711 0.9731966 0.9738102 0.9744119
## [197] 0.9750021 0.9755808 0.9761482 0.9767045 0.9772499 0.9777844 0.9783083
## [204] 0.9788217 0.9793248 0.9798178 0.9803007 0.9807738 0.9812372 0.9816911
## [211] 0.9821356 0.9825708 0.9829970 0.9834142 0.9838226 0.9842224 0.9846137
## [218] 0.9849966 0.9853713 0.9857379 0.9860966 0.9864474 0.9867906 0.9871263
## [225] 0.9874545 0.9877755 0.9880894 0.9883962 0.9886962 0.9889893 0.9892759
## [232] 0.9895559 0.9898296 0.9900969 0.9903581 0.9906133 0.9908625 0.9911060
## [239] 0.9913437 0.9915758 0.9918025 0.9920237 0.9922397 0.9924506 0.9926564
## [246] 0.9928572 0.9930531 0.9932443 0.9934309 0.9936128 0.9937903 0.9939634
## [253] 0.9941323 0.9942969 0.9944574 0.9946139 0.9947664 0.9949151 0.9950600
## [260] 0.9952012 0.9953388 0.9954729 0.9956035 0.9957308 0.9958547 0.9959754
## [267] 0.9960930 0.9962074 0.9963189 0.9964274 0.9965330 0.9966358 0.9967359
## [274] 0.9968333 0.9969280 0.9970202 0.9971099 0.9971972 0.9972821 0.9973646
## [281] 0.9974449 0.9975229 0.9975988 0.9976726 0.9977443 0.9978140 0.9978818
## [288] 0.9979476 0.9980116 0.9980738 0.9981342 0.9981929 0.9982498 0.9983052
## [295] 0.9983589 0.9984111 0.9984618 0.9985110 0.9985588 0.9986051 0.9986501
## [302] 0.9986938 0.9987361 0.9987772 0.9988171 0.9988558 0.9988933 0.9989297
## [309] 0.9989650 0.9989992

# In Matrix mit 10 Spalten übertragen
matrix(pnorm(z),ncol=10, byrow=TRUE)

##            [,1]      [,2]      [,3]      [,4]      [,5]      [,6]      [,7]
##  [1,] 0.5000000 0.5039894 0.5079783 0.5119665 0.5159534 0.5199388 0.5239222
##  [2,] 0.5398278 0.5437953 0.5477584 0.5517168 0.5556700 0.5596177 0.5635595
##  [3,] 0.5792597 0.5831662 0.5870644 0.5909541 0.5948349 0.5987063 0.6025681
##  [4,] 0.6179114 0.6217195 0.6255158 0.6293000 0.6330717 0.6368307 0.6405764
##  [5,] 0.6554217 0.6590970 0.6627573 0.6664022 0.6700314 0.6736448 0.6772419
##  [6,] 0.6914625 0.6949743 0.6984682 0.7019440 0.7054015 0.7088403 0.7122603
##  [7,] 0.7257469 0.7290691 0.7323711 0.7356527 0.7389137 0.7421539 0.7453731
##  [8,] 0.7580363 0.7611479 0.7642375 0.7673049 0.7703500 0.7733726 0.7763727
##  [9,] 0.7881446 0.7910299 0.7938919 0.7967306 0.7995458 0.8023375 0.8051055
## [10,] 0.8159399 0.8185887 0.8212136 0.8238145 0.8263912 0.8289439 0.8314724
## [11,] 0.8413447 0.8437524 0.8461358 0.8484950 0.8508300 0.8531409 0.8554277
## [12,] 0.8643339 0.8665005 0.8686431 0.8707619 0.8728568 0.8749281 0.8769756
## [13,] 0.8849303 0.8868606 0.8887676 0.8906514 0.8925123 0.8943502 0.8961653
## [14,] 0.9031995 0.9049021 0.9065825 0.9082409 0.9098773 0.9114920 0.9130850
## [15,] 0.9192433 0.9207302 0.9221962 0.9236415 0.9250663 0.9264707 0.9278550
## [16,] 0.9331928 0.9344783 0.9357445 0.9369916 0.9382198 0.9394292 0.9406201
## [17,] 0.9452007 0.9463011 0.9473839 0.9484493 0.9494974 0.9505285 0.9515428
## [18,] 0.9554345 0.9563671 0.9572838 0.9581849 0.9590705 0.9599408 0.9607961
## [19,] 0.9640697 0.9648521 0.9656205 0.9663750 0.9671159 0.9678432 0.9685572
## [20,] 0.9712834 0.9719334 0.9725711 0.9731966 0.9738102 0.9744119 0.9750021
## [21,] 0.9772499 0.9777844 0.9783083 0.9788217 0.9793248 0.9798178 0.9803007
## [22,] 0.9821356 0.9825708 0.9829970 0.9834142 0.9838226 0.9842224 0.9846137
## [23,] 0.9860966 0.9864474 0.9867906 0.9871263 0.9874545 0.9877755 0.9880894
## [24,] 0.9892759 0.9895559 0.9898296 0.9900969 0.9903581 0.9906133 0.9908625
## [25,] 0.9918025 0.9920237 0.9922397 0.9924506 0.9926564 0.9928572 0.9930531
## [26,] 0.9937903 0.9939634 0.9941323 0.9942969 0.9944574 0.9946139 0.9947664
## [27,] 0.9953388 0.9954729 0.9956035 0.9957308 0.9958547 0.9959754 0.9960930
## [28,] 0.9965330 0.9966358 0.9967359 0.9968333 0.9969280 0.9970202 0.9971099
## [29,] 0.9974449 0.9975229 0.9975988 0.9976726 0.9977443 0.9978140 0.9978818
## [30,] 0.9981342 0.9981929 0.9982498 0.9983052 0.9983589 0.9984111 0.9984618
## [31,] 0.9986501 0.9986938 0.9987361 0.9987772 0.9988171 0.9988558 0.9988933
##            [,8]      [,9]     [,10]
##  [1,] 0.5279032 0.5318814 0.5358564
##  [2,] 0.5674949 0.5714237 0.5753454
##  [3,] 0.6064199 0.6102612 0.6140919
##  [4,] 0.6443088 0.6480273 0.6517317
##  [5,] 0.6808225 0.6843863 0.6879331
##  [6,] 0.7156612 0.7190427 0.7224047
##  [7,] 0.7485711 0.7517478 0.7549029
##  [8,] 0.7793501 0.7823046 0.7852361
##  [9,] 0.8078498 0.8105703 0.8132671
## [10,] 0.8339768 0.8364569 0.8389129
## [11,] 0.8576903 0.8599289 0.8621434
## [12,] 0.8789995 0.8809999 0.8829768
## [13,] 0.8979577 0.8997274 0.9014747
## [14,] 0.9146565 0.9162067 0.9177356
## [15,] 0.9292191 0.9305634 0.9318879
## [16,] 0.9417924 0.9429466 0.9440826
## [17,] 0.9525403 0.9535213 0.9544860
## [18,] 0.9616364 0.9624620 0.9632730
## [19,] 0.9692581 0.9699460 0.9706210
## [20,] 0.9755808 0.9761482 0.9767045
## [21,] 0.9807738 0.9812372 0.9816911
## [22,] 0.9849966 0.9853713 0.9857379
## [23,] 0.9883962 0.9886962 0.9889893
## [24,] 0.9911060 0.9913437 0.9915758
## [25,] 0.9932443 0.9934309 0.9936128
## [26,] 0.9949151 0.9950600 0.9952012
## [27,] 0.9962074 0.9963189 0.9964274
## [28,] 0.9971972 0.9972821 0.9973646
## [29,] 0.9979476 0.9980116 0.9980738
## [30,] 0.9985110 0.9985588 0.9986051
## [31,] 0.9989297 0.9989650 0.9989992

# auf 4 Stellen runden
matrix(round(pnorm(z),4),ncol=10, byrow=TRUE)

##         [,1]   [,2]   [,3]   [,4]   [,5]   [,6]   [,7]   [,8]   [,9]  [,10]
##  [1,] 0.5000 0.5040 0.5080 0.5120 0.5160 0.5199 0.5239 0.5279 0.5319 0.5359
##  [2,] 0.5398 0.5438 0.5478 0.5517 0.5557 0.5596 0.5636 0.5675 0.5714 0.5753
##  [3,] 0.5793 0.5832 0.5871 0.5910 0.5948 0.5987 0.6026 0.6064 0.6103 0.6141
##  [4,] 0.6179 0.6217 0.6255 0.6293 0.6331 0.6368 0.6406 0.6443 0.6480 0.6517
##  [5,] 0.6554 0.6591 0.6628 0.6664 0.6700 0.6736 0.6772 0.6808 0.6844 0.6879
##  [6,] 0.6915 0.6950 0.6985 0.7019 0.7054 0.7088 0.7123 0.7157 0.7190 0.7224
##  [7,] 0.7257 0.7291 0.7324 0.7357 0.7389 0.7422 0.7454 0.7486 0.7517 0.7549
##  [8,] 0.7580 0.7611 0.7642 0.7673 0.7704 0.7734 0.7764 0.7794 0.7823 0.7852
##  [9,] 0.7881 0.7910 0.7939 0.7967 0.7995 0.8023 0.8051 0.8078 0.8106 0.8133
## [10,] 0.8159 0.8186 0.8212 0.8238 0.8264 0.8289 0.8315 0.8340 0.8365 0.8389
## [11,] 0.8413 0.8438 0.8461 0.8485 0.8508 0.8531 0.8554 0.8577 0.8599 0.8621
## [12,] 0.8643 0.8665 0.8686 0.8708 0.8729 0.8749 0.8770 0.8790 0.8810 0.8830
## [13,] 0.8849 0.8869 0.8888 0.8907 0.8925 0.8944 0.8962 0.8980 0.8997 0.9015
## [14,] 0.9032 0.9049 0.9066 0.9082 0.9099 0.9115 0.9131 0.9147 0.9162 0.9177
## [15,] 0.9192 0.9207 0.9222 0.9236 0.9251 0.9265 0.9279 0.9292 0.9306 0.9319
## [16,] 0.9332 0.9345 0.9357 0.9370 0.9382 0.9394 0.9406 0.9418 0.9429 0.9441
## [17,] 0.9452 0.9463 0.9474 0.9484 0.9495 0.9505 0.9515 0.9525 0.9535 0.9545
## [18,] 0.9554 0.9564 0.9573 0.9582 0.9591 0.9599 0.9608 0.9616 0.9625 0.9633
## [19,] 0.9641 0.9649 0.9656 0.9664 0.9671 0.9678 0.9686 0.9693 0.9699 0.9706
## [20,] 0.9713 0.9719 0.9726 0.9732 0.9738 0.9744 0.9750 0.9756 0.9761 0.9767
## [21,] 0.9772 0.9778 0.9783 0.9788 0.9793 0.9798 0.9803 0.9808 0.9812 0.9817
## [22,] 0.9821 0.9826 0.9830 0.9834 0.9838 0.9842 0.9846 0.9850 0.9854 0.9857
## [23,] 0.9861 0.9864 0.9868 0.9871 0.9875 0.9878 0.9881 0.9884 0.9887 0.9890
## [24,] 0.9893 0.9896 0.9898 0.9901 0.9904 0.9906 0.9909 0.9911 0.9913 0.9916
## [25,] 0.9918 0.9920 0.9922 0.9925 0.9927 0.9929 0.9931 0.9932 0.9934 0.9936
## [26,] 0.9938 0.9940 0.9941 0.9943 0.9945 0.9946 0.9948 0.9949 0.9951 0.9952
## [27,] 0.9953 0.9955 0.9956 0.9957 0.9959 0.9960 0.9961 0.9962 0.9963 0.9964
## [28,] 0.9965 0.9966 0.9967 0.9968 0.9969 0.9970 0.9971 0.9972 0.9973 0.9974
## [29,] 0.9974 0.9975 0.9976 0.9977 0.9977 0.9978 0.9979 0.9979 0.9980 0.9981
## [30,] 0.9981 0.9982 0.9982 0.9983 0.9984 0.9984 0.9985 0.9985 0.9986 0.9986
## [31,] 0.9987 0.9987 0.9987 0.9988 0.9988 0.9989 0.9989 0.9989 0.9990 0.9990

# In Dataframe übertragen
tabelle1 <- data.frame(matrix(round(pnorm(z),4),ncol=10, byrow=TRUE))
# benenne die Spalten
colnames(tabelle1) <- c(seq(0.00, 0.09, 0.01))
# benenne die Zeilen
row.names(tabelle1) <- c(seq(0, 3, 0.1))
# gib fertige Tabelle aus
tabelle1

##          0   0.01   0.02   0.03   0.04   0.05   0.06   0.07   0.08   0.09
## 0   0.5000 0.5040 0.5080 0.5120 0.5160 0.5199 0.5239 0.5279 0.5319 0.5359
## 0.1 0.5398 0.5438 0.5478 0.5517 0.5557 0.5596 0.5636 0.5675 0.5714 0.5753
## 0.2 0.5793 0.5832 0.5871 0.5910 0.5948 0.5987 0.6026 0.6064 0.6103 0.6141
## 0.3 0.6179 0.6217 0.6255 0.6293 0.6331 0.6368 0.6406 0.6443 0.6480 0.6517
## 0.4 0.6554 0.6591 0.6628 0.6664 0.6700 0.6736 0.6772 0.6808 0.6844 0.6879
## 0.5 0.6915 0.6950 0.6985 0.7019 0.7054 0.7088 0.7123 0.7157 0.7190 0.7224
## 0.6 0.7257 0.7291 0.7324 0.7357 0.7389 0.7422 0.7454 0.7486 0.7517 0.7549
## 0.7 0.7580 0.7611 0.7642 0.7673 0.7704 0.7734 0.7764 0.7794 0.7823 0.7852
## 0.8 0.7881 0.7910 0.7939 0.7967 0.7995 0.8023 0.8051 0.8078 0.8106 0.8133
## 0.9 0.8159 0.8186 0.8212 0.8238 0.8264 0.8289 0.8315 0.8340 0.8365 0.8389
## 1   0.8413 0.8438 0.8461 0.8485 0.8508 0.8531 0.8554 0.8577 0.8599 0.8621
## 1.1 0.8643 0.8665 0.8686 0.8708 0.8729 0.8749 0.8770 0.8790 0.8810 0.8830
## 1.2 0.8849 0.8869 0.8888 0.8907 0.8925 0.8944 0.8962 0.8980 0.8997 0.9015
## 1.3 0.9032 0.9049 0.9066 0.9082 0.9099 0.9115 0.9131 0.9147 0.9162 0.9177
## 1.4 0.9192 0.9207 0.9222 0.9236 0.9251 0.9265 0.9279 0.9292 0.9306 0.9319
## 1.5 0.9332 0.9345 0.9357 0.9370 0.9382 0.9394 0.9406 0.9418 0.9429 0.9441
## 1.6 0.9452 0.9463 0.9474 0.9484 0.9495 0.9505 0.9515 0.9525 0.9535 0.9545
## 1.7 0.9554 0.9564 0.9573 0.9582 0.9591 0.9599 0.9608 0.9616 0.9625 0.9633
## 1.8 0.9641 0.9649 0.9656 0.9664 0.9671 0.9678 0.9686 0.9693 0.9699 0.9706
## 1.9 0.9713 0.9719 0.9726 0.9732 0.9738 0.9744 0.9750 0.9756 0.9761 0.9767
## 2   0.9772 0.9778 0.9783 0.9788 0.9793 0.9798 0.9803 0.9808 0.9812 0.9817
## 2.1 0.9821 0.9826 0.9830 0.9834 0.9838 0.9842 0.9846 0.9850 0.9854 0.9857
## 2.2 0.9861 0.9864 0.9868 0.9871 0.9875 0.9878 0.9881 0.9884 0.9887 0.9890
## 2.3 0.9893 0.9896 0.9898 0.9901 0.9904 0.9906 0.9909 0.9911 0.9913 0.9916
## 2.4 0.9918 0.9920 0.9922 0.9925 0.9927 0.9929 0.9931 0.9932 0.9934 0.9936
## 2.5 0.9938 0.9940 0.9941 0.9943 0.9945 0.9946 0.9948 0.9949 0.9951 0.9952
## 2.6 0.9953 0.9955 0.9956 0.9957 0.9959 0.9960 0.9961 0.9962 0.9963 0.9964
## 2.7 0.9965 0.9966 0.9967 0.9968 0.9969 0.9970 0.9971 0.9972 0.9973 0.9974
## 2.8 0.9974 0.9975 0.9976 0.9977 0.9977 0.9978 0.9979 0.9979 0.9980 0.9981
## 2.9 0.9981 0.9982 0.9982 0.9983 0.9984 0.9984 0.9985 0.9985 0.9986 0.9986
## 3   0.9987 0.9987 0.9987 0.9988 0.9988 0.9989 0.9989 0.9989 0.9990 0.9990

Im Gegensatz zu Tabelle Tabelle 41.1 zeigt Tabelle Tabelle 41.2 die Überschreitungswahrscheinlichkeiten PP für Abszissenwerte zz der Standardnormalverteilung (μ=0\mu=0 und σ=1\sigma=1).

Die p-Werte entsprechen der Fläche unter der Standardnormalverteilung zwischen zz und +∞+\infty und damit einer einseitigen Fragestellung. Sie müssen bei zweiseitigen Fragestellungen verdoppelt werden!

Tab. 41.2: Überschreitungswahrscheinlichkeiten P für Abszissenwerte z der Standardnormalverteilung

ϕ\phi z | .00 | .01 | .02 | .03 | .04 | .05 | .06 | .07 | .08 | .09
0.0 | .5000 | .4960 | .4920 | .4880 | .4840 | .4801 | .4761 | .4721 | .4681 | .4641
0.1 | .4602 | .4562 | .4522 | .4483 | .4443 | .4404 | .4364 | .4325 | .4286 | .4247
0.2 | .4207 | .4168 | .4129 | .4090 | .4052 | .4013 | .3974 | .3936 | .3897 | .3859
0.3 | .3821 | .3783 | .3745 | .3707 | .3669 | .3632 | .3594 | .3557 | .3520 | .3483
0.4 | .3446 | .3409 | .3372 | .3336 | .3300 | .3264 | .3228 | .3192 | .3156 | .3121
0.5 | .3085 | .3050 | .3015 | .2981 | .2946 | .2912 | .2887 | .2843 | .2810 | .2776
0.6 | .2743 | .2709 | .2676 | .2643 | .2611 | .2578 | .2546 | .2514 | .2483 | .2451
0.7 | .2420 | .2389 | .2358 | .2327 | .2296 | .2266 | .2236 | .2206 | .2177 | .2148
0.8 | .2119 | .2090 | .2061 | .2033 | .2005 | .1977 | .1949 | .1922 | .1894 | .1867
0.9 | .1841 | .1814 | .1788 | .1762 | .1736 | .1711 | .1685 | .1660 | .1635 | .1611
1.0 | .1587 | .1562 | .1539 | .1515 | .1492 | .1469 | .1464 | .1423 | .1401 | .1379
1.1 | .1357 | .1335 | .1314 | .1291 | .1271 | .1251 | .1230 | .1210 | .1190 | .1170
1.2 | .1151 | .1131 | .1112 | .1093 | .1075 | .1056 | .1038 | .1020 | .1003 | .0985
1.3 | .0968 | .0951 | .0934 | .0918 | .0901 | .0885 | .0869 | .0853 | .0838 | .0823
1.4 | .0808 | .0793 | .0778 | .0764 | .0749 | .0735 | .0721 | .0708 | .0694 | .0681
1.5 | .0668 | .0655 | .0643 | .0630 | .0618 | .0606 | .0594 | .0582 | .0571 | .0559
1.6 | .0548 | .0537 | .0526 | .0516 | .0505 | .0495 | .0485 | .0475 | .0465 | .0455
1.7 | .0446 | .0436 | .0427 | .0418 | .0409 | .0401 | .0392 | .0384 | .0375 | .0367
1.8 | .0359 | .0351 | .0344 | .0336 | .0329 | .0322 | .0314 | .0307 | .0301 | .0294
1.9 | .0287 | .0281 | .0274 | .0268 | .0262 | .0256 | .0250 | .0244 | .0239 | .0233
2.0 | .0228 | .0222 | .0217 | .0212 | .0207 | .0202 | .0197 | .0192 | .0188 | .0183
2.1 | .0179 | .0174 | .0170 | .0166 | .0162 | .0158 | .0154 | .0150 | .0146 | .0143
2.2 | .0139 | .0136 | .0132 | .0129 | .0125 | .0122 | .0119 | .0116 | .0113 | .0110
2.3 | .0107 | .0104 | .0102 | .0099 | .0096 | .0094 | .0091 | .0089 | .0087 | .0084
2.4 | .0082 | .0080 | .0078 | .0075 | .0073 | .0071 | .0069 | .0068 | .0066 | .0064
2.5 | .0062 | .0060 | .0059 | .0057 | .0055 | .0054 | .0052 | .0051 | .0049 | .0048
2.6 | .0047 | .0045 | .0044 | .0043 | .0041 | .0040 | .0039 | .0038 | .0037 | .0036
2.7 | .0035 | .0034 | .0033 | .0032 | .0031 | .0030 | .0029 | .0028 | .0027 | .0026
2.8 | .0026 | .0025 | .0024 | .0023 | .0023 | .0022 | .0021 | .0021 | .0020 | .0019
2.9 | .0019 | .0018 | .0018 | .0017 | .0016 | .0016 | .0015 | .0015 | .0014 | .0014
3.0 | .0013 | .0013 | .0013 | .0012 | .0012 | .0011 | .0011 | .0011 | .0010 | .0010
p = | .5000 | .7500 | .8000 | .9000 | .9500 | .9750 | .9900 | .9950 | .9975 | .9990
zpz_p = | 0.0000 | 0.6745 | 0.8416 | 1.2816 | 1.6449 | 1.9600 | 2.3263 | 2.5758 | 2.8070 | 3.0902

Bei der z-Transformation benötigt man die Werte dieser Tabelle beispielsweise für Fragen wie “Wie hoch ist der prozentuale Anteil für Werte, die mindestens X sind”

Überschreitungswahrscheinlichkeiten P für Abszissenwerte z

Die Werte werden erzeugt mit:

# erstelle Werte von 0.00 bis 3.09
z <- seq(from=0, to=3.09, by = 0.01)

# P-Werte berechnen
1 - pnorm(z)

##   [1] 0.500000000 0.496010644 0.492021686 0.488033527 0.484046563 0.480061194
##   [7] 0.476077817 0.472096830 0.468118628 0.464143607 0.460172163 0.456204687
##  [13] 0.452241574 0.448283213 0.444329995 0.440382308 0.436440537 0.432505068
##  [19] 0.428576284 0.424654565 0.420740291 0.416833837 0.412935577 0.409045885
##  [25] 0.405165128 0.401293674 0.397431887 0.393580127 0.389738752 0.385908119
##  [31] 0.382088578 0.378280478 0.374484165 0.370699981 0.366928264 0.363169349
##  [37] 0.359423567 0.355691245 0.351972708 0.348268273 0.344578258 0.340902974
##  [43] 0.337242727 0.333597821 0.329968554 0.326355220 0.322758110 0.319177509
##  [49] 0.315613697 0.312066949 0.308537539 0.305025731 0.301531788 0.298055965
##  [55] 0.294598516 0.291159687 0.287739719 0.284338849 0.280957309 0.277595325
##  [61] 0.274253118 0.270930904 0.267628893 0.264347292 0.261086300 0.257846111
##  [67] 0.254626915 0.251428895 0.248252230 0.245097094 0.241963652 0.238852068
##  [73] 0.235762498 0.232695092 0.229649997 0.226627352 0.223627292 0.220649946
##  [79] 0.217695438 0.214763884 0.211855399 0.208970088 0.206108054 0.203269392
##  [85] 0.200454193 0.197662543 0.194894521 0.192150202 0.189429655 0.186732943
##  [91] 0.184060125 0.181411255 0.178786380 0.176185542 0.173608780 0.171056126
##  [97] 0.168527607 0.166023246 0.163543059 0.161087060 0.158655254 0.156247645
## [103] 0.153864230 0.151505003 0.149169950 0.146859056 0.144572300 0.142309654
## [109] 0.140071090 0.137856572 0.135666061 0.133499513 0.131356881 0.129238112
## [115] 0.127143151 0.125071936 0.123024403 0.121000484 0.119000107 0.117023196
## [121] 0.115069670 0.113139446 0.111232437 0.109348552 0.107487697 0.105649774
## [127] 0.103834681 0.102042315 0.100272568 0.098525329 0.096800485 0.095097918
## [133] 0.093417509 0.091759136 0.090122672 0.088507991 0.086914962 0.085343451
## [139] 0.083793322 0.082264439 0.080756659 0.079269841 0.077803841 0.076358510
## [145] 0.074933700 0.073529260 0.072145037 0.070780877 0.069436623 0.068112118
## [151] 0.066807201 0.065521712 0.064255488 0.063008364 0.061780177 0.060570758
## [157] 0.059379941 0.058207556 0.057053433 0.055917403 0.054799292 0.053698928
## [163] 0.052616138 0.051550748 0.050502583 0.049471468 0.048457226 0.047459682
## [169] 0.046478658 0.045513977 0.044565463 0.043632937 0.042716221 0.041815138
## [175] 0.040929509 0.040059157 0.039203903 0.038363570 0.037537980 0.036726956
## [181] 0.035930319 0.035147894 0.034379502 0.033624969 0.032884119 0.032156775
## [187] 0.031442763 0.030741909 0.030054039 0.029378980 0.028716560 0.028066607
## [193] 0.027428950 0.026803419 0.026189845 0.025588060 0.024997895 0.024419185
## [199] 0.023851764 0.023295468 0.022750132 0.022215594 0.021691694 0.021178270
## [205] 0.020675163 0.020182215 0.019699270 0.019226172 0.018762766 0.018308900
## [211] 0.017864421 0.017429178 0.017003023 0.016585807 0.016177383 0.015777607
## [217] 0.015386335 0.015003423 0.014628731 0.014262118 0.013903448 0.013552581
## [223] 0.013209384 0.012873721 0.012545461 0.012224473 0.011910625 0.011603792
## [229] 0.011303844 0.011010658 0.010724110 0.010444077 0.010170439 0.009903076
## [235] 0.009641870 0.009386706 0.009137468 0.008894043 0.008656319 0.008424186
## [241] 0.008197536 0.007976260 0.007760254 0.007549411 0.007343631 0.007142811
## [247] 0.006946851 0.006755653 0.006569119 0.006387155 0.006209665 0.006036558
## [253] 0.005867742 0.005703126 0.005542623 0.005386146 0.005233608 0.005084926
## [259] 0.004940016 0.004798797 0.004661188 0.004527111 0.004396488 0.004269243
## [265] 0.004145301 0.004024589 0.003907033 0.003792562 0.003681108 0.003572601
## [271] 0.003466974 0.003364160 0.003264096 0.003166716 0.003071959 0.002979763
## [277] 0.002890068 0.002802815 0.002717945 0.002635402 0.002555130 0.002477075
## [283] 0.002401182 0.002327400 0.002255677 0.002185961 0.002118205 0.002052359
## [289] 0.001988376 0.001926209 0.001865813 0.001807144 0.001750157 0.001694810
## [295] 0.001641061 0.001588870 0.001538195 0.001488999 0.001441242 0.001394887
## [301] 0.001349898 0.001306238 0.001263873 0.001222769 0.001182891 0.001144207
## [307] 0.001106685 0.001070294 0.001035003 0.001000782

# In Matrix mit 10 Spalten übertragen
matrix(1-pnorm(z),ncol=10, byrow=TRUE)

##              [,1]        [,2]        [,3]        [,4]        [,5]        [,6]
##  [1,] 0.500000000 0.496010644 0.492021686 0.488033527 0.484046563 0.480061194
##  [2,] 0.460172163 0.456204687 0.452241574 0.448283213 0.444329995 0.440382308
##  [3,] 0.420740291 0.416833837 0.412935577 0.409045885 0.405165128 0.401293674
##  [4,] 0.382088578 0.378280478 0.374484165 0.370699981 0.366928264 0.363169349
##  [5,] 0.344578258 0.340902974 0.337242727 0.333597821 0.329968554 0.326355220
##  [6,] 0.308537539 0.305025731 0.301531788 0.298055965 0.294598516 0.291159687
##  [7,] 0.274253118 0.270930904 0.267628893 0.264347292 0.261086300 0.257846111
##  [8,] 0.241963652 0.238852068 0.235762498 0.232695092 0.229649997 0.226627352
##  [9,] 0.211855399 0.208970088 0.206108054 0.203269392 0.200454193 0.197662543
## [10,] 0.184060125 0.181411255 0.178786380 0.176185542 0.173608780 0.171056126
## [11,] 0.158655254 0.156247645 0.153864230 0.151505003 0.149169950 0.146859056
## [12,] 0.135666061 0.133499513 0.131356881 0.129238112 0.127143151 0.125071936
## [13,] 0.115069670 0.113139446 0.111232437 0.109348552 0.107487697 0.105649774
## [14,] 0.096800485 0.095097918 0.093417509 0.091759136 0.090122672 0.088507991
## [15,] 0.080756659 0.079269841 0.077803841 0.076358510 0.074933700 0.073529260
## [16,] 0.066807201 0.065521712 0.064255488 0.063008364 0.061780177 0.060570758
## [17,] 0.054799292 0.053698928 0.052616138 0.051550748 0.050502583 0.049471468
## [18,] 0.044565463 0.043632937 0.042716221 0.041815138 0.040929509 0.040059157
## [19,] 0.035930319 0.035147894 0.034379502 0.033624969 0.032884119 0.032156775
## [20,] 0.028716560 0.028066607 0.027428950 0.026803419 0.026189845 0.025588060
## [21,] 0.022750132 0.022215594 0.021691694 0.021178270 0.020675163 0.020182215
## [22,] 0.017864421 0.017429178 0.017003023 0.016585807 0.016177383 0.015777607
## [23,] 0.013903448 0.013552581 0.013209384 0.012873721 0.012545461 0.012224473
## [24,] 0.010724110 0.010444077 0.010170439 0.009903076 0.009641870 0.009386706
## [25,] 0.008197536 0.007976260 0.007760254 0.007549411 0.007343631 0.007142811
## [26,] 0.006209665 0.006036558 0.005867742 0.005703126 0.005542623 0.005386146
## [27,] 0.004661188 0.004527111 0.004396488 0.004269243 0.004145301 0.004024589
## [28,] 0.003466974 0.003364160 0.003264096 0.003166716 0.003071959 0.002979763
## [29,] 0.002555130 0.002477075 0.002401182 0.002327400 0.002255677 0.002185961
## [30,] 0.001865813 0.001807144 0.001750157 0.001694810 0.001641061 0.001588870
## [31,] 0.001349898 0.001306238 0.001263873 0.001222769 0.001182891 0.001144207
##              [,7]        [,8]        [,9]       [,10]
##  [1,] 0.476077817 0.472096830 0.468118628 0.464143607
##  [2,] 0.436440537 0.432505068 0.428576284 0.424654565
##  [3,] 0.397431887 0.393580127 0.389738752 0.385908119
##  [4,] 0.359423567 0.355691245 0.351972708 0.348268273
##  [5,] 0.322758110 0.319177509 0.315613697 0.312066949
##  [6,] 0.287739719 0.284338849 0.280957309 0.277595325
##  [7,] 0.254626915 0.251428895 0.248252230 0.245097094
##  [8,] 0.223627292 0.220649946 0.217695438 0.214763884
##  [9,] 0.194894521 0.192150202 0.189429655 0.186732943
## [10,] 0.168527607 0.166023246 0.163543059 0.161087060
## [11,] 0.144572300 0.142309654 0.140071090 0.137856572
## [12,] 0.123024403 0.121000484 0.119000107 0.117023196
## [13,] 0.103834681 0.102042315 0.100272568 0.098525329
## [14,] 0.086914962 0.085343451 0.083793322 0.082264439
## [15,] 0.072145037 0.070780877 0.069436623 0.068112118
## [16,] 0.059379941 0.058207556 0.057053433 0.055917403
## [17,] 0.048457226 0.047459682 0.046478658 0.045513977
## [18,] 0.039203903 0.038363570 0.037537980 0.036726956
## [19,] 0.031442763 0.030741909 0.030054039 0.029378980
## [20,] 0.024997895 0.024419185 0.023851764 0.023295468
## [21,] 0.019699270 0.019226172 0.018762766 0.018308900
## [22,] 0.015386335 0.015003423 0.014628731 0.014262118
## [23,] 0.011910625 0.011603792 0.011303844 0.011010658
## [24,] 0.009137468 0.008894043 0.008656319 0.008424186
## [25,] 0.006946851 0.006755653 0.006569119 0.006387155
## [26,] 0.005233608 0.005084926 0.004940016 0.004798797
## [27,] 0.003907033 0.003792562 0.003681108 0.003572601
## [28,] 0.002890068 0.002802815 0.002717945 0.002635402
## [29,] 0.002118205 0.002052359 0.001988376 0.001926209
## [30,] 0.001538195 0.001488999 0.001441242 0.001394887
## [31,] 0.001106685 0.001070294 0.001035003 0.001000782

# auf 4 Stellen runden
matrix(round(1-pnorm(z),4),ncol=10, byrow=TRUE)

##         [,1]   [,2]   [,3]   [,4]   [,5]   [,6]   [,7]   [,8]   [,9]  [,10]
##  [1,] 0.5000 0.4960 0.4920 0.4880 0.4840 0.4801 0.4761 0.4721 0.4681 0.4641
##  [2,] 0.4602 0.4562 0.4522 0.4483 0.4443 0.4404 0.4364 0.4325 0.4286 0.4247
##  [3,] 0.4207 0.4168 0.4129 0.4090 0.4052 0.4013 0.3974 0.3936 0.3897 0.3859
##  [4,] 0.3821 0.3783 0.3745 0.3707 0.3669 0.3632 0.3594 0.3557 0.3520 0.3483
##  [5,] 0.3446 0.3409 0.3372 0.3336 0.3300 0.3264 0.3228 0.3192 0.3156 0.3121
##  [6,] 0.3085 0.3050 0.3015 0.2981 0.2946 0.2912 0.2877 0.2843 0.2810 0.2776
##  [7,] 0.2743 0.2709 0.2676 0.2643 0.2611 0.2578 0.2546 0.2514 0.2483 0.2451
##  [8,] 0.2420 0.2389 0.2358 0.2327 0.2296 0.2266 0.2236 0.2206 0.2177 0.2148
##  [9,] 0.2119 0.2090 0.2061 0.2033 0.2005 0.1977 0.1949 0.1922 0.1894 0.1867
## [10,] 0.1841 0.1814 0.1788 0.1762 0.1736 0.1711 0.1685 0.1660 0.1635 0.1611
## [11,] 0.1587 0.1562 0.1539 0.1515 0.1492 0.1469 0.1446 0.1423 0.1401 0.1379
## [12,] 0.1357 0.1335 0.1314 0.1292 0.1271 0.1251 0.1230 0.1210 0.1190 0.1170
## [13,] 0.1151 0.1131 0.1112 0.1093 0.1075 0.1056 0.1038 0.1020 0.1003 0.0985
## [14,] 0.0968 0.0951 0.0934 0.0918 0.0901 0.0885 0.0869 0.0853 0.0838 0.0823
## [15,] 0.0808 0.0793 0.0778 0.0764 0.0749 0.0735 0.0721 0.0708 0.0694 0.0681
## [16,] 0.0668 0.0655 0.0643 0.0630 0.0618 0.0606 0.0594 0.0582 0.0571 0.0559
## [17,] 0.0548 0.0537 0.0526 0.0516 0.0505 0.0495 0.0485 0.0475 0.0465 0.0455
## [18,] 0.0446 0.0436 0.0427 0.0418 0.0409 0.0401 0.0392 0.0384 0.0375 0.0367
## [19,] 0.0359 0.0351 0.0344 0.0336 0.0329 0.0322 0.0314 0.0307 0.0301 0.0294
## [20,] 0.0287 0.0281 0.0274 0.0268 0.0262 0.0256 0.0250 0.0244 0.0239 0.0233
## [21,] 0.0228 0.0222 0.0217 0.0212 0.0207 0.0202 0.0197 0.0192 0.0188 0.0183
## [22,] 0.0179 0.0174 0.0170 0.0166 0.0162 0.0158 0.0154 0.0150 0.0146 0.0143
## [23,] 0.0139 0.0136 0.0132 0.0129 0.0125 0.0122 0.0119 0.0116 0.0113 0.0110
## [24,] 0.0107 0.0104 0.0102 0.0099 0.0096 0.0094 0.0091 0.0089 0.0087 0.0084
## [25,] 0.0082 0.0080 0.0078 0.0075 0.0073 0.0071 0.0069 0.0068 0.0066 0.0064
## [26,] 0.0062 0.0060 0.0059 0.0057 0.0055 0.0054 0.0052 0.0051 0.0049 0.0048
## [27,] 0.0047 0.0045 0.0044 0.0043 0.0041 0.0040 0.0039 0.0038 0.0037 0.0036
## [28,] 0.0035 0.0034 0.0033 0.0032 0.0031 0.0030 0.0029 0.0028 0.0027 0.0026
## [29,] 0.0026 0.0025 0.0024 0.0023 0.0023 0.0022 0.0021 0.0021 0.0020 0.0019
## [30,] 0.0019 0.0018 0.0018 0.0017 0.0016 0.0016 0.0015 0.0015 0.0014 0.0014
## [31,] 0.0013 0.0013 0.0013 0.0012 0.0012 0.0011 0.0011 0.0011 0.0010 0.0010

# In Dataframe übertragen
tabelle2 <- data.frame(matrix(round(1-pnorm(z),4),ncol=10, byrow=TRUE))
# benenne die Spalten
colnames(tabelle2) <- c(seq(0.00, 0.09, 0.01))
# benenne die Zeilen
row.names(tabelle2) <- c(seq(0, 3, 0.1))
# gib fertige Tabelle aus
tabelle2

##          0   0.01   0.02   0.03   0.04   0.05   0.06   0.07   0.08   0.09
## 0   0.5000 0.4960 0.4920 0.4880 0.4840 0.4801 0.4761 0.4721 0.4681 0.4641
## 0.1 0.4602 0.4562 0.4522 0.4483 0.4443 0.4404 0.4364 0.4325 0.4286 0.4247
## 0.2 0.4207 0.4168 0.4129 0.4090 0.4052 0.4013 0.3974 0.3936 0.3897 0.3859
## 0.3 0.3821 0.3783 0.3745 0.3707 0.3669 0.3632 0.3594 0.3557 0.3520 0.3483
## 0.4 0.3446 0.3409 0.3372 0.3336 0.3300 0.3264 0.3228 0.3192 0.3156 0.3121
## 0.5 0.3085 0.3050 0.3015 0.2981 0.2946 0.2912 0.2877 0.2843 0.2810 0.2776
## 0.6 0.2743 0.2709 0.2676 0.2643 0.2611 0.2578 0.2546 0.2514 0.2483 0.2451
## 0.7 0.2420 0.2389 0.2358 0.2327 0.2296 0.2266 0.2236 0.2206 0.2177 0.2148
## 0.8 0.2119 0.2090 0.2061 0.2033 0.2005 0.1977 0.1949 0.1922 0.1894 0.1867
## 0.9 0.1841 0.1814 0.1788 0.1762 0.1736 0.1711 0.1685 0.1660 0.1635 0.1611
## 1   0.1587 0.1562 0.1539 0.1515 0.1492 0.1469 0.1446 0.1423 0.1401 0.1379
## 1.1 0.1357 0.1335 0.1314 0.1292 0.1271 0.1251 0.1230 0.1210 0.1190 0.1170
## 1.2 0.1151 0.1131 0.1112 0.1093 0.1075 0.1056 0.1038 0.1020 0.1003 0.0985
## 1.3 0.0968 0.0951 0.0934 0.0918 0.0901 0.0885 0.0869 0.0853 0.0838 0.0823
## 1.4 0.0808 0.0793 0.0778 0.0764 0.0749 0.0735 0.0721 0.0708 0.0694 0.0681
## 1.5 0.0668 0.0655 0.0643 0.0630 0.0618 0.0606 0.0594 0.0582 0.0571 0.0559
## 1.6 0.0548 0.0537 0.0526 0.0516 0.0505 0.0495 0.0485 0.0475 0.0465 0.0455
## 1.7 0.0446 0.0436 0.0427 0.0418 0.0409 0.0401 0.0392 0.0384 0.0375 0.0367
## 1.8 0.0359 0.0351 0.0344 0.0336 0.0329 0.0322 0.0314 0.0307 0.0301 0.0294
## 1.9 0.0287 0.0281 0.0274 0.0268 0.0262 0.0256 0.0250 0.0244 0.0239 0.0233
## 2   0.0228 0.0222 0.0217 0.0212 0.0207 0.0202 0.0197 0.0192 0.0188 0.0183
## 2.1 0.0179 0.0174 0.0170 0.0166 0.0162 0.0158 0.0154 0.0150 0.0146 0.0143
## 2.2 0.0139 0.0136 0.0132 0.0129 0.0125 0.0122 0.0119 0.0116 0.0113 0.0110
## 2.3 0.0107 0.0104 0.0102 0.0099 0.0096 0.0094 0.0091 0.0089 0.0087 0.0084
## 2.4 0.0082 0.0080 0.0078 0.0075 0.0073 0.0071 0.0069 0.0068 0.0066 0.0064
## 2.5 0.0062 0.0060 0.0059 0.0057 0.0055 0.0054 0.0052 0.0051 0.0049 0.0048
## 2.6 0.0047 0.0045 0.0044 0.0043 0.0041 0.0040 0.0039 0.0038 0.0037 0.0036
## 2.7 0.0035 0.0034 0.0033 0.0032 0.0031 0.0030 0.0029 0.0028 0.0027 0.0026
## 2.8 0.0026 0.0025 0.0024 0.0023 0.0023 0.0022 0.0021 0.0021 0.0020 0.0019
## 2.9 0.0019 0.0018 0.0018 0.0017 0.0016 0.0016 0.0015 0.0015 0.0014 0.0014
## 3   0.0013 0.0013 0.0013 0.0012 0.0012 0.0011 0.0011 0.0011 0.0010 0.0010

41.3 Kritische Werte 𝐭*\mathbf{t^*} der 𝐭\mathbf{t}-Verteilung

Kritische Werte t*t^* der tt-Verteilung

α/2\alpha/2 | 0.10 | 0.05 | 0.025 | 0.01 | 0.005 | 0.0005
α\alpha | 0.20 | 0.10 | 0.050 | 0.020 | 0.010 | 0.0010
———- | ——– | ——- | ——– | ——– | ——— | ——–
df |  |  |  |  |  |
1 | 3.078 | 6.314 | 12.706 | 31.821 | 63.657 | 318.309
2 | 1.886 | 2.920 | 4.303 | 6.965 | 9.925 | 22.327
3 | 1.638 | 2.353 | 3.182 | 4.541 | 5.841 | 10.215
4 | 1.533 | 2.132 | 2.776 | 3.747 | 4.604 | 7.173
5 | 1.476 | 2.015 | 2.571 | 3.365 | 4.032 | 5.893
6 | 1.440 | 1.943 | 2.447 | 3.143 | 3.707 | 5.208
7 | 1.415 | 1.895 | 2.365 | 2.998 | 3.499 | 4.785
8 | 1.397 | 1.860 | 2.306 | 2.896 | 3.355 | 4.501
9 | 1.383 | 1.833 | 2.262 | 2.821 | 3.250 | 4.297
10 | 1.372 | 1.812 | 2.228 | 2.764 | 3.169 | 4.144
11 | 1.363 | 1.796 | 2.201 | 2.718 | 3.106 | 4.025
12 | 1.356 | 1.782 | 2.179 | 2.681 | 3.055 | 3.930
13 | 1.350 | 1.771 | 2.160 | 2.650 | 3.012 | 3.852
14 | 1.345 | 1.761 | 2.145 | 2.624 | 2.977 | 3.787
15 | 1.341 | 1.753 | 2.131 | 2.602 | 2.947 | 3.733
16 | 1.337 | 1.746 | 2.120 | 2.583 | 2.921 | 3.686
17 | 1.333 | 1.740 | 2.110 | 2.567 | 2.898 | 3.646
18 | 1.330 | 1.734 | 2.101 | 2.552 | 2.878 | 3.611
19 | 1.328 | 1.729 | 2.093 | 2.539 | 2.861 | 3.579
20 | 1.325 | 1.725 | 2.086 | 2.528 | 2.845 | 3.552
21 | 1.323 | 1.721 | 2.080 | 2.518 | 2.831 | 3.527
22 | 1.321 | 1.717 | 2.074 | 2.508 | 2.819 | 3.505
23 | 1.319 | 1.714 | 2.069 | 2.500 | 2.807 | 3.485
24 | 1.318 | 1.711 | 2.064 | 2.492 | 2.797 | 3.467
25 | 1.316 | 1.708 | 2.060 | 2.485 | 2.787 | 3.450
26 | 1.315 | 1.706 | 2.056 | 2.479 | 2.779 | 3.435
27 | 1.314 | 1.703 | 2.052 | 2.473 | 2.771 | 3.421
28 | 1.313 | 1.701 | 2.048 | 2.467 | 2.763 | 3.408
29 | 1.311 | 1.699 | 2.045 | 2.462 | 2.756 | 3.396
30 | 1.310 | 1.697 | 2.042 | 2.457 | 2.750 | 3.385
40 | 1.303 | 1.684 | 2.021 | 2.423 | 2.704 | 3.307
50 | 1.299 | 1.676 | 2.009 | 2.403 | 2.678 | 3.261
60 | 1.296 | 1.671 | 2.000 | 2.390 | 2.660 | 3.232
70 | 1.294 | 1.667 | 1.994 | 2.381 | 2.648 | 3.211
80 | 1.292 | 1.664 | 1.990 | 2.374 | 2.639 | 3.195
90 | 1.291 | 1.662 | 1.987 | 2.368 | 2.632 | 3.183
100 | 1.290 | 1.660 | 1.984 | 2.364 | 2.626 | 3.174
200 | 1.286 | 1.653 | 1.972 | 2.345 | 2.601 | 3.131
300 | 1.284 | 1.650 | 1.968 | 2.339 | 2.592 | 3.118
400 | 1.284 | 1.649 | 1.966 | 2.336 | 2.588 | 3.111
500 | 1.283 | 1.648 | 1.965 | 2.334 | 2.586 | 3.107
∞\infty | 1.282 | 1.645 | 1.960 | 2.326 | 2.576 | 3.090

Die Werte werden erzeugt mit:

# erstelle Werte von 1 bis 100, und 200, 300, 400, 500
df <- c(seq(from=1, to=100, by = 1), 200, 300, 400, 500)

# für alpha= 0.2
qt(0.9,df=df)

##   [1] 3.077684 1.885618 1.637744 1.533206 1.475884 1.439756 1.414924 1.396815
##   [9] 1.383029 1.372184 1.363430 1.356217 1.350171 1.345030 1.340606 1.336757
##  [17] 1.333379 1.330391 1.327728 1.325341 1.323188 1.321237 1.319460 1.317836
##  [25] 1.316345 1.314972 1.313703 1.312527 1.311434 1.310415 1.309464 1.308573
##  [33] 1.307737 1.306952 1.306212 1.305514 1.304854 1.304230 1.303639 1.303077
##  [41] 1.302543 1.302035 1.301552 1.301090 1.300649 1.300228 1.299825 1.299439
##  [49] 1.299069 1.298714 1.298373 1.298045 1.297730 1.297426 1.297134 1.296853
##  [57] 1.296581 1.296319 1.296066 1.295821 1.295585 1.295356 1.295134 1.294920
##  [65] 1.294712 1.294511 1.294315 1.294126 1.293942 1.293763 1.293589 1.293421
##  [73] 1.293256 1.293097 1.292941 1.292790 1.292643 1.292500 1.292360 1.292224
##  [81] 1.292091 1.291961 1.291835 1.291711 1.291591 1.291473 1.291358 1.291246
##  [89] 1.291136 1.291029 1.290924 1.290821 1.290721 1.290623 1.290527 1.290432
##  [97] 1.290340 1.290250 1.290161 1.290075 1.285799 1.284380 1.283672 1.283247

# erstelle Datenframe
tabelle_t <- data.frame(qt(0.9,df=df), qt(0.95,df=df), qt(0.975,df=df), qt(0.99,df=df), qt(0.995,df=df), qt(0.9995,df=df))
# benenne Spalten
colnames(tabelle_t) <- c("0.01","0.05","0.025","0.01","0.005","0.0005")
# benenne Zeilen
row.names(tabelle_t) <- df
# gib Tabelle aus
tabelle_t

##         0.01     0.05     0.025      0.01     0.005     0.0005
## 1   3.077684 6.313752 12.706205 31.820516 63.656741 636.619249
## 2   1.885618 2.919986  4.302653  6.964557  9.924843  31.599055
## 3   1.637744 2.353363  3.182446  4.540703  5.840909  12.923979
## 4   1.533206 2.131847  2.776445  3.746947  4.604095   8.610302
## 5   1.475884 2.015048  2.570582  3.364930  4.032143   6.868827
## 6   1.439756 1.943180  2.446912  3.142668  3.707428   5.958816
## 7   1.414924 1.894579  2.364624  2.997952  3.499483   5.407883
## 8   1.396815 1.859548  2.306004  2.896459  3.355387   5.041305
## 9   1.383029 1.833113  2.262157  2.821438  3.249836   4.780913
## 10  1.372184 1.812461  2.228139  2.763769  3.169273   4.586894
## 11  1.363430 1.795885  2.200985  2.718079  3.105807   4.436979
## 12  1.356217 1.782288  2.178813  2.680998  3.054540   4.317791
## 13  1.350171 1.770933  2.160369  2.650309  3.012276   4.220832
## 14  1.345030 1.761310  2.144787  2.624494  2.976843   4.140454
## 15  1.340606 1.753050  2.131450  2.602480  2.946713   4.072765
## 16  1.336757 1.745884  2.119905  2.583487  2.920782   4.014996
## 17  1.333379 1.739607  2.109816  2.566934  2.898231   3.965126
## 18  1.330391 1.734064  2.100922  2.552380  2.878440   3.921646
## 19  1.327728 1.729133  2.093024  2.539483  2.860935   3.883406
## 20  1.325341 1.724718  2.085963  2.527977  2.845340   3.849516
## 21  1.323188 1.720743  2.079614  2.517648  2.831360   3.819277
## 22  1.321237 1.717144  2.073873  2.508325  2.818756   3.792131
## 23  1.319460 1.713872  2.068658  2.499867  2.807336   3.767627
## 24  1.317836 1.710882  2.063899  2.492159  2.796940   3.745399
## 25  1.316345 1.708141  2.059539  2.485107  2.787436   3.725144
## 26  1.314972 1.705618  2.055529  2.478630  2.778715   3.706612
## 27  1.313703 1.703288  2.051831  2.472660  2.770683   3.689592
## 28  1.312527 1.701131  2.048407  2.467140  2.763262   3.673906
## 29  1.311434 1.699127  2.045230  2.462021  2.756386   3.659405
## 30  1.310415 1.697261  2.042272  2.457262  2.749996   3.645959
## 31  1.309464 1.695519  2.039513  2.452824  2.744042   3.633456
## 32  1.308573 1.693889  2.036933  2.448678  2.738481   3.621802
## 33  1.307737 1.692360  2.034515  2.444794  2.733277   3.610913
## 34  1.306952 1.690924  2.032245  2.441150  2.728394   3.600716
## 35  1.306212 1.689572  2.030108  2.437723  2.723806   3.591147
## 36  1.305514 1.688298  2.028094  2.434494  2.719485   3.582150
## 37  1.304854 1.687094  2.026192  2.431447  2.715409   3.573675
## 38  1.304230 1.685954  2.024394  2.428568  2.711558   3.565678
## 39  1.303639 1.684875  2.022691  2.425841  2.707913   3.558120
## 40  1.303077 1.683851  2.021075  2.423257  2.704459   3.550966
## 41  1.302543 1.682878  2.019541  2.420803  2.701181   3.544184
## 42  1.302035 1.681952  2.018082  2.418470  2.698066   3.537745
## 43  1.301552 1.681071  2.016692  2.416250  2.695102   3.531626
## 44  1.301090 1.680230  2.015368  2.414134  2.692278   3.525801
## 45  1.300649 1.679427  2.014103  2.412116  2.689585   3.520251
## 46  1.300228 1.678660  2.012896  2.410188  2.687013   3.514957
## 47  1.299825 1.677927  2.011741  2.408345  2.684556   3.509901
## 48  1.299439 1.677224  2.010635  2.406581  2.682204   3.505068
## 49  1.299069 1.676551  2.009575  2.404892  2.679952   3.500443
## 50  1.298714 1.675905  2.008559  2.403272  2.677793   3.496013
## 51  1.298373 1.675285  2.007584  2.401718  2.675722   3.491766
## 52  1.298045 1.674689  2.006647  2.400225  2.673734   3.487691
## 53  1.297730 1.674116  2.005746  2.398790  2.671823   3.483777
## 54  1.297426 1.673565  2.004879  2.397410  2.669985   3.480016
## 55  1.297134 1.673034  2.004045  2.396081  2.668216   3.476398
## 56  1.296853 1.672522  2.003241  2.394801  2.666512   3.472916
## 57  1.296581 1.672029  2.002465  2.393568  2.664870   3.469562
## 58  1.296319 1.671553  2.001717  2.392377  2.663287   3.466329
## 59  1.296066 1.671093  2.000995  2.391229  2.661759   3.463210
## 60  1.295821 1.670649  2.000298  2.390119  2.660283   3.460200
## 61  1.295585 1.670219  1.999624  2.389047  2.658857   3.457294
## 62  1.295356 1.669804  1.998972  2.388011  2.657479   3.454485
## 63  1.295134 1.669402  1.998341  2.387008  2.656145   3.451769
## 64  1.294920 1.669013  1.997730  2.386037  2.654854   3.449142
## 65  1.294712 1.668636  1.997138  2.385097  2.653604   3.446598
## 66  1.294511 1.668271  1.996564  2.384186  2.652394   3.444135
## 67  1.294315 1.667916  1.996008  2.383302  2.651220   3.441749
## 68  1.294126 1.667572  1.995469  2.382446  2.650081   3.439435
## 69  1.293942 1.667239  1.994945  2.381615  2.648977   3.437192
## 70  1.293763 1.666914  1.994437  2.380807  2.647905   3.435015
## 71  1.293589 1.666600  1.993943  2.380024  2.646863   3.432901
## 72  1.293421 1.666294  1.993464  2.379262  2.645852   3.430848
## 73  1.293256 1.665996  1.992997  2.378522  2.644869   3.428854
## 74  1.293097 1.665707  1.992543  2.377802  2.643913   3.426916
## 75  1.292941 1.665425  1.992102  2.377102  2.642983   3.425031
## 76  1.292790 1.665151  1.991673  2.376420  2.642078   3.423197
## 77  1.292643 1.664885  1.991254  2.375757  2.641198   3.421413
## 78  1.292500 1.664625  1.990847  2.375111  2.640340   3.419676
## 79  1.292360 1.664371  1.990450  2.374482  2.639505   3.417985
## 80  1.292224 1.664125  1.990063  2.373868  2.638691   3.416337
## 81  1.292091 1.663884  1.989686  2.373270  2.637897   3.414732
## 82  1.291961 1.663649  1.989319  2.372687  2.637123   3.413167
## 83  1.291835 1.663420  1.988960  2.372119  2.636369   3.411641
## 84  1.291711 1.663197  1.988610  2.371564  2.635632   3.410152
## 85  1.291591 1.662978  1.988268  2.371022  2.634914   3.408699
## 86  1.291473 1.662765  1.987934  2.370493  2.634212   3.407282
## 87  1.291358 1.662557  1.987608  2.369977  2.633527   3.405897
## 88  1.291246 1.662354  1.987290  2.369472  2.632858   3.404546
## 89  1.291136 1.662155  1.986979  2.368979  2.632204   3.403225
## 90  1.291029 1.661961  1.986675  2.368497  2.631565   3.401935
## 91  1.290924 1.661771  1.986377  2.368026  2.630940   3.400674
## 92  1.290821 1.661585  1.986086  2.367566  2.630330   3.399442
## 93  1.290721 1.661404  1.985802  2.367115  2.629732   3.398236
## 94  1.290623 1.661226  1.985523  2.366674  2.629148   3.397057
## 95  1.290527 1.661052  1.985251  2.366243  2.628576   3.395904
## 96  1.290432 1.660881  1.984984  2.365821  2.628016   3.394775
## 97  1.290340 1.660715  1.984723  2.365407  2.627468   3.393670
## 98  1.290250 1.660551  1.984467  2.365002  2.626931   3.392588
## 99  1.290161 1.660391  1.984217  2.364606  2.626405   3.391529
## 100 1.290075 1.660234  1.983972  2.364217  2.625891   3.390491
## 200 1.285799 1.652508  1.971896  2.345137  2.600634   3.339835
## 300 1.284380 1.649949  1.967903  2.338842  2.592316   3.323252
## 400 1.283672 1.648672  1.965912  2.335706  2.588176   3.315015
## 500 1.283247 1.647907  1.964720  2.333829  2.585698   3.310091

41.4 Kritische Werte der 𝛘𝟐\mathbf{\chi^2}-Verteilung

Kritische Werte der χ2\chi^2-Verteilung

α\alpha | 0.10 | 0.05 | 0.025 | 0.01 | 0.005 | 0.0005
df |  |  |  |  |  |
1 | 2.71 | 3.84 | 5.02 | 6.63 | 7.88 | 12.12
2 | 4.61 | 5.99 | 7.38 | 9.21 | 10.60 | 15.20
3 | 6.25 | 7.81 | 9.35 | 11.34 | 12.84 | 17.73
4 | 7.78 | 9.49 | 11.14 | 13.28 | 14.86 | 20.00
5 | 9.24 | 11.07 | 12.83 | 15.09 | 16.75 | 22.11
6 | 10.64 | 12.59 | 14.45 | 16.81 | 18.55 | 24.10
7 | 12.02 | 14.07 | 16.01 | 18.48 | 20.28 | 26.02
8 | 13.36 | 15.51 | 17.53 | 20.09 | 21.95 | 27.87
9 | 14.68 | 16.92 | 19.02 | 21.67 | 23.59 | 29.67
10 | 15.99 | 18.31 | 20.48 | 23.21 | 25.19 | 31.42
11 | 17.28 | 19.68 | 21.92 | 24.72 | 26.76 | 33.14
12 | 18.55 | 21.03 | 23.34 | 26.22 | 28.30 | 34.82
13 | 19.81 | 22.36 | 24.74 | 27.69 | 29.82 | 36.48
14 | 21.06 | 23.68 | 26.12 | 29.14 | 31.32 | 38.11
15 | 22.31 | 25.00 | 27.49 | 30.58 | 32.80 | 39.72
16 | 23.54 | 26.30 | 28.85 | 32.00 | 34.27 | 41.31
17 | 24.77 | 27.59 | 30.19 | 33.41 | 35.72 | 42.88
18 | 25.99 | 28.87 | 31.53 | 34.81 | 37.16 | 44.43
19 | 27.20 | 30.14 | 32.85 | 36.19 | 38.58 | 45.97
20 | 28.41 | 31.41 | 34.17 | 37.57 | 40.00 | 47.50
21 | 29.62 | 32.67 | 35.48 | 38.93 | 41.40 | 49.01
22 | 30.81 | 33.92 | 36.78 | 40.29 | 42.80 | 50.51
23 | 32.01 | 35.17 | 38.08 | 41.64 | 44.18 | 52.00
24 | 33.20 | 36.42 | 39.36 | 42.98 | 45.56 | 53.48
25 | 34.38 | 37.65 | 40.65 | 44.31 | 46.93 | 54.95
26 | 35.56 | 38.89 | 41.92 | 45.64 | 48.29 | 56.41
27 | 36.74 | 40.11 | 43.19 | 46.96 | 49.64 | 57.86
28 | 37.92 | 41.34 | 44.46 | 48.28 | 50.99 | 59.30
29 | 39.09 | 42.56 | 45.72 | 49.59 | 52.34 | 60.73
30 | 40.26 | 43.77 | 46.98 | 50.89 | 53.67 | 62.16
31 | 41.42 | 44.99 | 48.23 | 52.19 | 55.00 | 63.58
32 | 42.58 | 46.19 | 49.48 | 53.49 | 56.33 | 65.00
33 | 43.75 | 47.40 | 50.73 | 54.78 | 57.65 | 66.40
34 | 44.90 | 48.60 | 51.97 | 56.06 | 58.96 | 67.80
35 | 46.06 | 49.80 | 53.20 | 57.34 | 60.27 | 69.20
36 | 47.21 | 51.00 | 54.44 | 58.62 | 61.58 | 70.59
37 | 48.36 | 52.19 | 55.67 | 59.89 | 62.88 | 71.97
38 | 49.51 | 53.38 | 56.90 | 61.16 | 64.18 | 73.35
39 | 50.66 | 54.57 | 58.12 | 62.43 | 65.48 | 74.73
40 | 51.81 | 55.76 | 59.34 | 63.69 | 66.77 | 76.09

Die Werte werden erzeugt mit:

# werte von 1 bis 40
df <- c(seq(from=1, to=40, by = 1))
# chi^2-Werte für unterschiedliche alpha berechnen
tabelle_chi <- data.frame(qchisq(0.9,df=df), qchisq(0.95,df=df), qchisq(0.975,df=df), qchisq(0.99,df=df), qchisq(0.995,df=df), qchisq(0.9995,df=df))
# benenne Spalten nach alpha-Werten
colnames(tabelle_chi) <- c("0.01","0.05","0.025","0.01","0.005","0.0005")
# benenne Zeilen nach df-Werten
row.names(tabelle_chi) <- df
# gib Tabelle aus
tabelle_chi

##         0.01      0.05     0.025      0.01     0.005   0.0005
## 1   2.705543  3.841459  5.023886  6.634897  7.879439 12.11567
## 2   4.605170  5.991465  7.377759  9.210340 10.596635 15.20180
## 3   6.251389  7.814728  9.348404 11.344867 12.838156 17.73000
## 4   7.779440  9.487729 11.143287 13.276704 14.860259 19.99735
## 5   9.236357 11.070498 12.832502 15.086272 16.749602 22.10533
## 6  10.644641 12.591587 14.449375 16.811894 18.547584 24.10280
## 7  12.017037 14.067140 16.012764 18.475307 20.277740 26.01777
## 8  13.361566 15.507313 17.534546 20.090235 21.954955 27.86805
## 9  14.683657 16.918978 19.022768 21.665994 23.589351 29.66581
## 10 15.987179 18.307038 20.483177 23.209251 25.188180 31.41981
## 11 17.275009 19.675138 21.920049 24.724970 26.756849 33.13662
## 12 18.549348 21.026070 23.336664 26.216967 28.299519 34.82127
## 13 19.811929 22.362032 24.735605 27.688250 29.819471 36.47779
## 14 21.064144 23.684791 26.118948 29.141238 31.319350 38.10940
## 15 22.307130 24.995790 27.488393 30.577914 32.801321 39.71876
## 16 23.541829 26.296228 28.845351 31.999927 34.267187 41.30807
## 17 24.769035 27.587112 30.191009 33.408664 35.718466 42.87921
## 18 25.989423 28.869299 31.526378 34.805306 37.156451 44.43377
## 19 27.203571 30.143527 32.852327 36.190869 38.582257 45.97312
## 20 28.411981 31.410433 34.169607 37.566235 39.996846 47.49845
## 21 29.615089 32.670573 35.478876 38.932173 41.401065 49.01081
## 22 30.813282 33.924438 36.780712 40.289360 42.795655 50.51112
## 23 32.006900 35.172462 38.075627 41.638398 44.181275 52.00019
## 24 33.196244 36.415029 39.364077 42.979820 45.558512 53.47875
## 25 34.381587 37.652484 40.646469 44.314105 46.927890 54.94746
## 26 35.563171 38.885139 41.923170 45.641683 48.289882 56.40689
## 27 36.741217 40.113272 43.194511 46.962942 49.644915 57.85759
## 28 37.915923 41.337138 44.460792 48.278236 50.993376 59.30003
## 29 39.087470 42.556968 45.722286 49.587884 52.335618 60.73465
## 30 40.256024 43.772972 46.979242 50.892181 53.671962 62.16185
## 31 41.421736 44.985343 48.231890 52.191395 55.002704 63.58201
## 32 42.584745 46.194260 49.480438 53.485772 56.328115 64.99546
## 33 43.745180 47.399884 50.725080 54.775540 57.648445 66.40251
## 34 44.903158 48.602367 51.965995 56.060909 58.963926 67.80346
## 35 46.058788 49.801850 53.203349 57.342073 60.274771 69.19856
## 36 47.212174 50.998460 54.437294 58.619215 61.581179 70.58807
## 37 48.363408 52.192320 55.667973 59.892500 62.883335 71.97222
## 38 49.512580 53.383541 56.895521 61.162087 64.181412 73.35123
## 39 50.659770 54.572228 58.120060 62.428121 65.475571 74.72529
## 40 51.805057 55.758479 59.341707 63.690740 66.765962 76.09460

42 COVID19 Fallzahlen analysieren

Ich möchte die COVID19-Fallahlen analysieren.

Hierfür gibt es eine schöne Anleitung von der Universität Toronto: https://mdl.library.utoronto.ca/technology/tutorials/covid-19-data-r

Zunächst holen wir uns die aktuellen Daten.

# aktiviere das Tidyverse
library(tidyverse)

# Importiere Johns Hopkins Github data
confirmedraw <- read.csv( "https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_confirmed_global.csv")
deathsraw <- read.csv( "https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_deaths_global.csv")
recoveredraw <- read.csv( "https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_recovered_global.csv")

Dann bringen wir sie ins richtige Format.

confirmed <- confirmedraw %>%
  gather(key="date", value="confirmed", -c(Country.Region, Province.State, Lat, Long)) %>%    group_by(Country.Region, date) %>%
  summarize(confirmed=sum(confirmed))
deaths <- deathsraw %>%
  gather(key="date", value="deaths", -c(Country.Region, Province.State, Lat, Long)) %>%
  group_by(Country.Region, date) %>%
  summarize(deaths=sum(deaths))
recovered <- recoveredraw %>%
  gather(key="date", value="recovered", -c(Country.Region, Province.State, Lat, Long)) %>%
  group_by(Country.Region, date) %>%
  summarize(recovered=sum(recovered))
summary(confirmed)

##  Country.Region         date             confirmed
##  Length:229743      Length:229743      Min.   :        0
##  Class :character   Class :character   1st Qu.:     3831
##  Mode  :character   Mode  :character   Median :    52933
##                                        Mean   :  1379412
##                                        3rd Qu.:   499592
##                                        Max.   :103802702

Jetzt kombinieren wir alles in ein Datenframe und korrigieren die Datumsangaben.

# Final data: combine all three
country <- full_join(confirmed, deaths) %>%
  full_join(recovered)

# Date variable
# repariere Datumsangaben von character nach date
country$date <- country$date %>%
  sub("X", "", .) %>%
  as.Date("%m.%d.%y")

# Neue variable: Anzahl der Tage
country <- country %>%
  group_by(Country.Region) %>%
  mutate(cumconfirmed=cumsum(confirmed), days = date - first(date) + 1)

Jetzt aggregieren wir auf Weltperspektive und Deutschland.

world <- country %>%
  group_by(date) %>%
  summarize(confirmed=sum(confirmed), cumconfirmed=sum(cumconfirmed), deaths=sum(deaths), recovered=sum(recovered)) %>%
  mutate(days = date - first(date) + 1)
# Extract specific country: Germany
germany <- country %>% dplyr::filter(Country.Region=="Germany")

So vorbereitet können wir Statistiken ausgeben …

# SUMMARY STATISTICS
summary(country)
by(country$confirmed, country$Country.Region, summary)
by(country$cumconfirmed, country$Country.Region, summary)
by(country$deaths, country$Country.Region, summary)
by(country$recovered, country$Country.Region, summary)
summary(world)
summary(germany)

… und Grafiken plotten.

# World confirmed
ggplot(world, aes(x=date, y=confirmed)) + geom_bar(stat="identity", width=0.1) +
  theme_classic() +
  labs(title = "Covid-19 Global Confirmed Cases", x= "Date", y= "Daily confirmed cases") +
  theme(plot.title = element_text(hjust = 0.5))

# Germany confirmed
ggplot(germany, aes(x=date, y=confirmed)) + geom_bar(stat="identity", width=0.1) +
  labs(title = "Covid-19 Confirmed Cases in Germany", x= "Date", y= "Daily confirmed cases") +
  theme(plot.title = element_text(hjust = 0.5))

# Line graph of cases over time
# World confirmed
ggplot(world, aes(x=days, y=confirmed)) + geom_line() +
  labs(title = "Covid-19 Global Confirmed Cases", x= "Days", y= "Daily confirmed cases") +
  theme(plot.title = element_text(hjust = 0.5))

# Ignore warning

# World confirmed with counts in log10 scale
ggplot(world, aes(x=days, y=confirmed)) + geom_line() +
  labs(title = "Covid-19 Global Confirmed Cases", x= "Days", y= "Daily confirmed cases  (log scale)") +
  theme(plot.title = element_text(hjust = 0.5)) +
  scale_y_continuous(trans="log10")

# Confirmed by country for select countries with counts in log10 scale
countryselection <- country %>% filter(Country.Region==c("US", "Italy", "China", "France", "United Kingdom", "Germany"))
ggplot(countryselection, aes(x=days, y=confirmed, colour=Country.Region)) + geom_line(size=1) +
  labs(title = "Covid-19 Confirmed Cases by Country", x= "Days", y= "Daily confirmed cases (log scale)") +
  theme(plot.title = element_text(hjust = 0.5)) +
  scale_y_continuous(trans="log10")

# Matrix of line graphs of confirmed, deaths and recovered for select countries in log10 scale
countryselection %>% gather("Type", "Cases", -c(date, days, Country.Region)) %>%
  ggplot(aes(x=days, y=Cases, colour=Country.Region)) + geom_line(size=1) +
  labs(title = "Covid-19 Cases by Country", x= "Days", y= "Daily cases (log scale)") +
  theme(plot.title = element_text(hjust = 0.5)) +
  scale_y_continuous(trans="log10") +
  facet_grid(rows=vars(Type))

Literaturverzeichnis

Cohen, J. (1992). A Power Primer. Psychological Bulletin, 112(1), 155–159.

große Schlarmann, J. (2024b). Statistik mit R und RStudio - Ein Nachschlagewerk für Gesundheitsberufe. Hochschule Niederrhein. https://www.produnis.de/R

große Schlarmann, J. (2024a). Statistik mit R und RStudio - Ein Nachschlagewerk für Gesundheitsberufe. Hochschule Niederrhein. https://www.produnis.de/R

große Schlarmann, J., & Galatsch, M. (2014). Regressionsmodelle für ordinale Zielvariablen. GMS Medizinische Informatik, Biometrie Und Epidemiologie, 10(1), 1–9. https://doi.org/10.3205/mibe000154

Isfort, M., Rottländer, R., Weidner, F., Gehlen, D., Hylla, J., & Tucman, D. (2018). Pflege-Thermometer 2018 - Eine bundesweite Befragung von Leitungskräften zur Situation der Pflege und Patientenversorgung in der stationären Langzeitpflege in Deutschland. Deutsches Institut für angewandte Pflegeforschung e.V. (DIP).

Koller, M. M. (2017). Statistik für Pflege- und andere Gesundheitsberufe (2nd ed.). Facultas.

Kuckartz, U., Rädiker, S., Ebert, T., & Schehl, J. (2013). Statistik - Eine verständliche Einführung (2nd ed.). Springer VS.

Müller, M. (2011). Statistik für die Pflege: Handbuch für Pflegeforschung und -wissenschaft (1. ed.). Hogrefe AG.

R Core Team. (2023). R: A language and environment for statistical computing. R Foundation for Statistical Computing. https://www.R-project.org/

Wickham, H. (2012). A Layered Grammar of Graphics. Journal of Computational and Graphical Statistics, 19(1), 3–28. https://doi.org/10.1198/jcgs.2009.07098

Wickham, H. (2014). Tidy Data. Journal of Statistical Software, 59(10). https://doi.org/10.18637/jss.v059.i10

Wickham, H., & Grolemund, G. (2017). R for Data Science. O’Reilly Media. https://r4ds.had.co.nz/

Wickham, H., Navarro, D., & Pedersen, T. L. (2022). ggplot2: Elegant Graphics for Data Analysis (3rd ed.). Springer. https://ggplot2-book.org/

Wilkinson, L., Wills, D., Rope, D., Norton, A., & Dubbs, R. (2005). The Grammar of Graphics (2nd Edition). Springer.

Credits

Prof. Dr. rer. medic. MScN, BScN, RN
Jörg große Schlarmann
Professor für Pflegewissenschaft
Fachbereich 10 - Gesundheitswesen
Campus Krefeld Süd
Hochschule Niederrhein

joerg.grosseschlarmann@hs-niederrhein.de

    @rbuch@mastodon.social

Changelog

2023-12-27: Ein Snapshot des Buches wurde zu ORCA-NRW hinzugefügt, siehe hier.

2023-12-01: Abschnitt 35.6 um stat_count() und stat_identity() erweitert.

2023-11-19: Erklärung zu join-Funktionen hinzugefügt (Abschnitt 28.5)

2023-11-11: Poisson-Regression hinzugefügt (Abschnitt 33.3)

2023-11-08: QQ-Plots ergänzt (Abschnitt 34.10 und Abschnitt 35.10) sowie nicht-lineare Regressionen hinzugefügt (Abschnitt 33.1.2).

2023-11-06: Kapitel 20 hinzugefügt (apply() und Tochterfunktionen); Die PDF-Version hat neue \geometry{}-Werte und kommt daher mit 20% weniger Seiten aus.

2023-11-04: Abschnitt 37.6 hinzugefügt, plot()-Kapitel um Legendenbox ergänzt, plot() und ggplot() um Liniendiagramme erweitert.

2023-11-03: Abschnitt 33.11.2.1 mit Beispieltabelle und -plot für Fallzahlschätzungen bei Umfragen (Surveys) hinzugefügt.

2023-10-14: Abschnitt 8.4.3 “Datenframes zusammenführen” (merge()) hinzugefügt und Abschnitt 35.12.3 ergänzt.

2023-09-29: Abschnitt 14.7 “Importierte Daten ins richtige Format bringen” hinzugefügt.

2023-02-13: Kapitel ggplot() grundlegend überarbeitet (Kapitel 35)

2022-12-15: ANOVA hinzugefügt (Abschnitt 33.8)

2022-12-12: Faktorenanalyse hinzugefügt (Abschnitt 33.10)

2022-12-03: sehr große bzw. sehr kleine wissenschaftliche Zahlen “runden” (Abschnitt 4.2)

2022-09-29: quarto Bots (Abschnitt 40.2)

2022-09-19: R base pipe (Kapitel 11)

2022-09-10: Format ePub hinzugefügt.

2022-09-01: Umzug des Buchs auf quarto, und Abschnitt quarto hinzugefügt (Kapitel 23)

2022-07-04: Überlebenszeitanalysen (Abschnitt 33.9)

2022-05-20: Account bei Mastodon angelegt

2022-05-13: R-Paket jgsbook zu diesem Buch ist im CRAN verfügbar.

2022-02-04: gelabelte SPSS-Daten (Abschnitt 26.1)

2021-06-21: Fallzahlkalkulation (Abschnitt 33.11)

2021-06-04: Chiquadrat-Referenzwerte (Abschnitt 41.4)

2021-04-04: Tidyverse hinzugefügt (Kapitel 25)

2021-03-21: COVID-19-Analysen (Kapitel 42)

2020-11-12: Übertragung auf RMarkdown (bookdown), somit gibt es das Buch jetzt als Webseite und PDF-Datei.

2018-08-27: Datensätze “epa”, “nw” und “mma” stehen als Download bereit.

2012-09-07: RMarkdown hinzugefügt (Kapitel 22)

2012-05-17: Ergänzung RStudio

2010-08-28: Version 1 des Nachschlagewerks steht als PDF-Datei ($\LaTeX$) zum Download auf meiner Homepage.

2006-09-22: Meine ersten Notizen verfasse ich im Wikibook “GNU_R” unter https://de.wikibooks.org/wiki/GNU_R (alle Notizen unter https://de.wikibooks.org/wiki/Spezial:Beitr%C3%A4ge/Produnis)