24  Wide- & Long-Format

Einleitung

Wenn man wissenschaftliche Fragestellungen untersuchen möchte, benötigt man ein geeignetes Format, in dem die Daten vorliegen. Manchmal stehen die Daten, die man auswerten möchte, aber nicht im “richtigen” Format zur Verfügung.

Im Wesentlichen gibt es zwei verschiedene Darstellungsformen von Tabellendaten (Datensätzen): das Wide- und das Long-Format.

Im Wide-Format entspricht jede Zeile einer Untersuchungseinheit. So liegen beispielsweise messwiederholte Variablen in unterschiedlichen Spalten vor.

Beispiel 1 Wide-Format: Messzeitpunkte

Beispiel 2 Wide-Format: Rater

Untersuchungseinheit self friend parent
1 2 1 3
2 3 4 2

Im Long-Format hingegen liegen Messung von einer Untersuchungseinheit in mehreren Zeilen vor. Dabei sind die Werte einer Untersuchungseinheit, die in unterschiedlichen Modi (z.B. Messzeitpunkte, Rater) erhoben wurden, untereinander gelistet. So werden beispielsweise messwiederholte Variablen in einer Spalte dargestellt.

Beispiel 1 Long-Format: Messzeitpunkte

Untersuchungseinheit Zeitpunkt Messung
1 1 4
1 2 3
1 3 1
2 1 5
2 2 2
2 3 3

Beispiel 2 Long-Format: Rater

Untersuchungseinheit Rater Messung
1 self 2
1 friend 1
1 parent 3
2 self 3
2 friend 4
2 parent 2

Wir schauen uns zwei Möglichkeiten an, mit denen man Datensätze umformatieren kann:

Wir werden beide Pakete nutzen, um unsere Daten vom Long- ins Wide-Format und vom Wide- ins Long-Format zu überführen. Wir stellen beide Möglichkeiten zum Umformatieren vor, damit jeder ausprobieren kann, mit welcher Funktion sie bzw. er besser arbeiten kann. Während gather() und spread() manchen Nutzern intuitiver und einfacher erscheinen, werden die Daten mit reshape() etwas übersichtlicher dargestellt (z.B. durch Zeilennummerierungen).

Falls ihr das Paket tidyr noch nicht installiert habt, könnt ihr das folgendermaßen tun: install.packages("tidyr", dependencies = TRUE)

Beispieldatensatz für dieses Kapitel

Im Zuge dieses Kapitels werden wir mit dem Datensatz ChickWeight arbeiten, der standardmäßig in R enthalten ist. Wir laden ihn folgendermaßen herunter:

data(ChickWeight)

Die Daten stammen aus einem Längsschnitt-Experiment, in dem der Einfluss von verschiedenem Futter auf das Wachstum von Küken untersucht wurde.

Der Datensatz liegt im Long-Format vor und enthält vier Variablen. Mehr Informationen zu den Variablen finden wir hier.

  • weight: Körpergewicht eines Kükens in Gramm
  • Time: Tage seit der Geburt des Kükens
  • Chick: Identifikationsnummer des Kükens
  • Diet: Nummer der Futtergruppe (1, 2, 3, 4)
Von besonderem Interesse für uns ist die Zeitvariable Time. Diese werden wir im Folgenden nutzen, um den Datensatz vom Long- ins Wide-Format, und dann wieder zurück ins Long-Format, zu bringen.

Achtung: Der Einfachheit halber wird im Folgenden der Begriff messwiederholte Variable verwendet, wenn von einer Variablen die Rede ist, die mehrfach erhoben wurde (d.h. im Wide-Format in mehreren Spalten und im Long-Format in einer Spalte vorliegt). Im Beispiel zum Wide-Format oben sind demnach die Variablen t1, t2, t_3 sowie self, friend und parent messwiederholt. In unserem Beispiel-Datensatz (der im Long-Format vorliegt) ist das die Variable weight. Gleiches gilt für den Begriff Zeitvariable, die die verschiedenen Modi der messwiederholten Variablen kodiert. Im Beispiel zum Long-Format oben entspricht diese den Variablen Zeitpunkt und Rater. In unserem Beispiel-Datensatz (der im Long-Format vorliegt) ist das die Variable Time.

24.1 Vom Long- ins Wide-Format

Hierbei wird eine messwiederholte Variable (weight) in Abhängigkeit der Ausprägungen der Zeitvariable (Time) in mehrere Spalten aufgeteilt.

Im Wide-Format gibt es mehr Spalten und weniger Zeilen (als im Long-Format). Die Anzahl der Zeilen entspricht der Anzahl der Untersuchungseinheiten.

Sowohl reshape() als auch spread() benötigen zur Formatierung von Long nach Wide eine ID-Variable im Datensatz (auch wenn man diese bei spread() nicht an ein Argument übergeben muss). Eine ID-Variable bezeichnet die Untersuchungsobjekte. Im Eingangsbeispiel wäre dies die Variable Untersuchungseinheit.

24.1.1 stats: reshape()

Mit dem reshape()-Befehl kann man Daten sowohl vom Long- ins Wide-Format als auch vom Wide- ins Long-Format bringen.

Bei der Formatierung von Long in Wide sind folgende 5 Argumente wichtig:

  • data: Name des Datensatzes
  • v.names: messwiederholte Variable, die im Long-Format in einer Spalte vorliegt und im Wide-Format auf mehrere Spalten ausgedehnt werden soll
  • timevar: Zeitvariable, die im Long-Format die Modi kodiert
  • idvar: ID-Variable, die unterschiedliche Untersuchungseinheiten kodiert
  • direction: vom Long- ins Wide-Format

Zusätzlich kann man mit drop="Variable" eine bzw. mit drop=c("Variable_1", "Variable_2", ..., "Variable_x") mehrere Variablen aus dem umformatierten Datensatz entfernen.

reshape_wide <- reshape(data=ChickWeight, # Name des Datensatzes
                    v.names="weight", 
                      # messwiederholte Variable (in einer Spalte)
                    timevar="Time", 
                      # (bestehende) Zeitvariable
                    idvar="Chick", 
                      # (bestehende) ID-Variable
                    direction="wide") # vom Long- ins Wide-Format

Achtung: Ganz links sehen wir die “alte” Zeilennummerierung von ChickWeight. Die Zeilennummerierung ändert sich in reshape_wide, weil alle Beobachtungen einer Untersuchungseinheit zu unterschiedlichen Messzeitpunkten in einer Zeile vorliegen. Das erkennt man auch daran, dass die Zeilennummerierung von ChickWeight (meistens) in 12-er Schritten vorliegt und es genau 12 Messzeitpunkte gibt. Da bei einigen Küken weniger als 12 Messzeitpunkte vorhanden sind, gilt das aber nicht für den ganzen Datensatz reshape_wide.

24.1.2 tidyr: spread()

Hier sind nur drei Argumente wichtig:

  • data: Name des Datensatzes
  • key: Zeitvariable, die im Long-Format unterschiedliche Modi kodiert
  • value: messwiederholte Variable, die im Long-Format in einer Spalte vorliegt und im Wide-Format auf mehrere Spalten ausgedehnt werden soll
library(tidyr) # Laden des Pakets

spread_wide <-  spread(data=ChickWeight, # Name des Datensatzes
                  key="Time", #  Zeitvariable
                  value="weight") # messwiederholte Variable

24.1.3 Unterschied zwischen reshape() und spread()

Bei reshape() werden Zeilennummerierungen auf Basis des übergebenen Datensatzes erzeugt (siehe Hinweis im reshape()-Abschnitt).

Wide-Format: Argumente von reshape() und spread() gegenübergestellt

reshape spread
data data
v.names value
timevar key
idvar *kein explizites Argument, aber benötigt*

Die Benennung der Spalten der messwiederholten Variable unterscheidet sich in den beiden Ansätzen. Während bei reshape() eine Kombination aus dem Namen der messwiederholten Variablen und der Ausprägung der Zeitvariablen genutzt wird (z.B. weight.0), wird bei spread() nur die Ausprägung der Zeitvariablen genutzt (z.B. 0).

24.2 Vom Wide- ins Long-Format

Hierbei wird eine messwiederholte Variable (weight), die in mehreren Spalten vorliegt, zu einer Spalte zusammengefasst und es wird eine dazugehörige Zeitvariable (Time) erstellt.

Im Long-Format gibt es weniger Spalten und mehr Zeilen (als im Wide-Format). Die Anzahl der Zeilen entspricht nicht der Anzahl der Untersuchungseinheiten, sondern der Anzahl der Untersuchungseinheiten x Anzahl der (jeweiligen) Messwiederholungen. Deswegen ist es im Long-Format besonders wichtig, eine ID-Variable zu haben, um einzelne Untersuchungseinheiten differenzieren zu können.

Achtung: Manchmal werden nicht alle Untersuchungseinheiten zu jedem Zeitpunkt untersucht. In unserem Datensatz ChickWeight ist das der Fall.

Wir können uns folgendermaßen anschauen, wie häufig jede Untersuchungseinheit beobachtet wurde:

table(ChickWeight$Chick)

18 16 15 13  9 20 10  8 17 19  4  6 11  3  1 12  2  5 14  7 24 30 22 23 27 28 
 2  7  8 12 12 12 12 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 
26 25 29 21 33 37 36 31 39 38 32 40 34 35 44 45 43 41 47 49 46 50 42 48 
12 12 12 12 12 12 12 12 12 12 12 12 12 12 10 12 12 12 12 12 12 12 12 12 

Deswegen gibt es auch einen Unterschied zwischen der Anzahl der Zeilen im ursprünglichen Long-Format von ChickWeight und den nachfolgend erstellten Long-Formaten des Datensatzes. Im ursprünglichen Long-Format ChickWeight werden die Zeilen der Küken, die zu bestimmten Messzeitpunkten nicht beobachtet wurden, weggelassen. Mit reshape() und gather() werden diese Zeilen erstellt und die Beobachtung der messwiederholten Variable mit NA (= fehlender Wert) versehen.

24.2.1 stats: reshape()

Bei der Formatierung von Wide in Long sind folgende 6 Argumente wichtig:

  • data: Name des Datensatzes
  • varying: messwiederholte Variable, die im Wide-Format auf mehrere Spalten ausgedehnt ist und im Long-Format in einer Spalte vorliegen soll
  • timevar: Zeitvariable, die unterschiedliche Modi kodiert, und die neu im Long-Format erstellt werden soll
  • times: Ausprägungen der Zeitvariable, d.h. die Kodierung der Modi
  • idvar: ID-Variable, die unterschiedliche Untersuchungseinheiten kodiert
  • direction: vom Wide- ins Long-Format

Mit v.names kann man die messwiederholte Variable, welche im Wide-Format in mehreren Spalten vorliegt und nun im Long-Format zu einer Spalte zusammengefasst wird, umbenennen. Wenn alle Spaltennamen den gleichen Stamm haben (bei uns weight.0, weight.2, …), dann wird dieser als Name der zusammengefassten Variable genutzt. Wenn die Spalten unterschiedliche Namen haben, sollte man hier einen gemeinsamen Namen angeben.

Auch hier kann man einzelne Variablen mit drop="Variable" bzw. mehrere Variablen mit drop=c("Variable_1", "Variable_2", ..., "Variable_x") aus dem umformatierten Datensatz entfernen.

reshape_long <- reshape(data=reshape_wide, # Name des Datensatzes
                    varying=3:14, 
                      # messwiederholte Variable (mehrere Spalten) 
                    timevar="Time", 
                      # (neu erstellte) Zeitvariable 
                    times=c(seq(0, 20, 2), 21), 
                      # Ausprägungen von timevar (Messzeitpunkte)
                    idvar="Chick", 
                      # (bestehende) ID-Variable
                    direction="long") # vom Wide- ins Long-Format

Achtung: Ganz links sehen wir die neue Zeilennummerierung von reshape_long. Die Zeilennummerierung hat hier Nachkommastellen (z.B. 1.0), weil hier ID-Variable und Zeitvariable kombiniert wurden. Die Zahl vor dem Punkt steht für die Untersuchungseinheit (Chick); die Zahl nach dem Komma für den Messzeitpunkt (Time).

Was macht varying=3:14?

Man kann bestehende Variablen auch mit ihren Indizes (d.h. hier: ihren Spaltennamen) ansprechen. Das ist häufig weniger schreibintensiv, als alle Spaltennamen in einem Vektor zu speichern z.B. varying=c("weight.0", "weight.2", ..., "weight.21"). So könnte man z.B. auch idvar=1 anstatt idvar="Chick" schreiben.

Mit den Variablen, die man im Rahmen der Umformatierung erst neu erstellt (z.B. timevar), geht das aber nicht, da diese noch gar nicht existieren und von daher auch keine Indizes haben.
Wie können wir verschiedene messwiederholte Variablen jeweils zusammenfassen? bzw. Wie nutzen wir varying=list() und v.names?

Wenn man mehrere Variablen zu den verschiedenen Messzeitpunkten erhoben hat, kann man diese mit varying=list() vom Wide- ins Long-Format bringen. Dabei muss man darauf achten, dass alle messwiederholten Variablen die gleiche Anzahl an Spalten besitzen (d.h. alle zu jedem Messzeitpunkt erhoben wurden).

Für unseren Datensatz generieren wir ein fiktives Beispiel, in dem wir die 12 messwiederholten weight-Spalten duplizieren und dann jeweils zu einer Variablen zusammenfügen. Deswegen schreiben wir varying=list(3:14, 15:26) in reshape().

Zusätzlich kann man die messwiederholten Variablen mit v.names=c("...", "...") umbenennen. Das funktioniert auch für einzelne messwiederholte Variablen: Dann benötigt man keinen Namensvektor c("...", "...") mehr, sondern nur v.names="...".

Wir nennen die zwei fiktiven Variablen in unserem Beispiel AV_1 und AV_2.

# messwiderholte Variablen duplizieren:
reshape_dup <- cbind(reshape_wide, reshape_wide[3:14])

reshape_dup_long <- reshape(data=reshape_dup,
                    varying=list(3:14, 15:26), 
                      # zwei messwiederholte Variable ...
                      # ... mit jeweils mehreren Spalten
                    v.names=c("AV_1", "AV_2"),
                      # Benennung der zwei messwiederholten Variablen
                      # optional
                    timevar="Time", 
                    times=c(seq(0, 20, 2), 21),
                      # Ausprägungen von timevar (Messzeitpunkte)
                    idvar="Chick", 
                    direction="long")

24.2.2 tidyr: gather()

Hierfür benötigt man 5 Argumente:

  • data: Name des Datensatzes
  • key: Zeitvariable, die unterschiedliche Modi kodiert und die neu im Long-Format erstellt werden soll
  • value: Benennung der neu zu erstellenden messwiederholten Variable
  • ...: messwiederholte Variable, die im Wide-Format auf mehrere Spalten ausgedehnt ist und im Long-Format in einer Spalte vorliegen soll
  • factor_key: ob die neu erstellte Zeitvariable als Faktor (TRUE) oder als Character (FALSE; Default) gehandhabt werden soll
library(tidyr) # Laden des Pakets

gather_long <- gather(data=spread_wide, # Name des Datensatzes
                  key="Time", # (neu erstellte) Zeitvariable 
                  value="weight", # (neu erstellte) messwiederholte Variable 
                  3:14, # messwiederholte Variable (mehrere Spalten) 
                  factor_key=TRUE) # ob die Zeitvariable ein Faktor sein soll

24.2.3 Unterschied zwischen reshape() und gather()

Bei reshape() werden Zeilennummerierungen auf Basis des übergebenen Datensatzes erzeugt (siehe Hinweis im reshape()-Abschnitt).

Long-Format: Argumente von reshape() und gather() gegenübergestellt

reshape gather
data data
varying ...
timevar key
times
idvar
v.names value
drop
factor_key

Bei gather() werden die Ausprägungen der Zeitvariable (Modi) so benannt, wie vorher die einzelnen Spalten der messwiederholten Variablen hießen. Wenn wir uns das anschauen wollen, könnten wir einfach reshape_wide (anstatt spread_wide) mit gather() bearbeiten. Im Gegensatz dazu kann man bei reshape() mit times eine eigene Benennung der Ausprägungen festlegen.

Bei reshape() wird mit idvar eine neue ID-Variable erstellt. Dazu muss ich dem Argument nur einen Namen geben. Wenn es bereits eine ID-Variable gibt, sollte man den Namen dieser angeben, weil ansonsten noch eine neue ID-Variable erstellt wird.

Mit drop kann man zusätzlich bestimmte Variablen aus dem neu formatierten Datensatz entfernen.

Bei gather() kann man mit mit factor_key entscheiden, ob die neu erstellte Zeitvariable als Faktor oder als Character behandelt werden sollen. Mit reshape() ist der Datentyp immer numerisch.

24.3 Andere Funktionen zum Umformatieren

Es gibt noch andere Funktionen, mit denen man Datensätze vom Long- ins Wide-Format oder umgekehrt umformatieren kann.

In gewisser Hinsicht sind pivot_longer() und pivot_wider() aus dem Paket tidyr die Nachfolger von gather() und spread(). Neben der intuitiveren Benennung enthalten sie einige Neuerungen wie z.B. die Möglichkeit, mit mehreren messwiederholten Variablen mit verschiedenen Datentypen zu arbeiten (dieses Feature wurde von melt() und dcast() übernommen). Außerdem wurde der Support für gather() und spread() eingestellt, d.h. bestehende Probleme mit diesen beiden Funktionen werden nicht mehr behoben werden. Hier finden wir eine Vignette, in der die Handhabung von pivot_longer() und pivot_wider() erklärt wird.

Wenn wir wissen wollen, wie man melt() (Wide zu Long) und dcast() (Long zu Wide) aus dem Paket reshape2 nutzt, können wir dafür hier nachschauen.


Um eine möglichst exakte Replikation der Funktionen zu gewährleisten gibt es im folgenden relevante Angaben zum System (R-Version, Betriebssystem, geladene Pakete mit Angaben zur Version), mit welchem diese Seite erstellt wurde.

sessionInfo()
R version 4.4.2 (2024-10-31)
Platform: x86_64-pc-linux-gnu
Running under: Ubuntu 24.04.1 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.26.so;  LAPACK version 3.12.0

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
 [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
 [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

time zone: Etc/UTC
tzcode source: system (glibc)

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] kableExtra_1.4.0 dplyr_1.1.4      tidyr_1.3.1      rmarkdown_2.29  

loaded via a namespace (and not attached):
 [1] vctrs_0.6.5       svglite_2.1.3     cli_3.6.3         knitr_1.49       
 [5] rlang_1.1.4       xfun_0.49         stringi_1.8.4     purrr_1.0.2      
 [9] generics_0.1.3    jsonlite_1.8.9    glue_1.8.0        colorspace_2.1-1 
[13] htmltools_0.5.8.1 scales_1.3.0      fansi_1.0.6       munsell_0.5.1    
[17] evaluate_1.0.1    tibble_3.2.1      fastmap_1.2.0     yaml_2.3.10      
[21] lifecycle_1.0.4   stringr_1.5.1     compiler_4.4.2    htmlwidgets_1.6.4
[25] pkgconfig_2.0.3   rstudioapi_0.17.1 systemfonts_1.1.0 digest_0.6.37    
[29] viridisLite_0.4.2 R6_2.5.1          tidyselect_1.2.1  utf8_1.2.4       
[33] pillar_1.9.0      magrittr_2.0.3    withr_3.0.2       tools_4.4.2      
[37] xml2_1.3.6       

Für Informationen zur Interpretation dieses Outputs schaut auch den Abschnitt Replizierbarkeit von Analysen des Kapitels zu Paketen an.