Sammeln Sie mehrere Sätze von Spalten

107

Ich habe Daten aus einer Online-Umfrage, bei der die Befragten 1-3 Mal eine Reihe von Fragen durchlaufen. Die Umfrage - Software (Qualtrics) diese Daten in mehreren Spalten-das notiert , ist, F3.2 in der Umfrage werden Spalten haben Q3.2.1., Q3.2.2.und Q3.2.3.:

df <- data.frame(
  id = 1:10,
  time = as.Date('2009-01-01') + 0:9,
  Q3.2.1. = rnorm(10, 0, 1),
  Q3.2.2. = rnorm(10, 0, 1),
  Q3.2.3. = rnorm(10, 0, 1),
  Q3.3.1. = rnorm(10, 0, 1),
  Q3.3.2. = rnorm(10, 0, 1),
  Q3.3.3. = rnorm(10, 0, 1)
)

# Sample data

   id       time    Q3.2.1.     Q3.2.2.    Q3.2.3.     Q3.3.1.    Q3.3.2.     Q3.3.3.
1   1 2009-01-01 -0.2059165 -0.29177677 -0.7107192  1.52718069 -0.4484351 -1.21550600
2   2 2009-01-02 -0.1981136 -1.19813815  1.1750200 -0.40380049 -1.8376094  1.03588482
3   3 2009-01-03  0.3514795 -0.27425539  1.1171712 -1.02641801 -2.0646661 -0.35353058
...

Ich möchte alle QN.N * -Spalten zu ordentlichen einzelnen QN.N-Spalten kombinieren und am Ende so etwas erhalten:

   id       time loop_number        Q3.2        Q3.3
1   1 2009-01-01           1 -0.20591649  1.52718069
2   2 2009-01-02           1 -0.19811357 -0.40380049
3   3 2009-01-03           1  0.35147949 -1.02641801
...
11  1 2009-01-01           2 -0.29177677  -0.4484351
12  2 2009-01-02           2 -1.19813815  -1.8376094
13  3 2009-01-03           2 -0.27425539  -2.0646661
...
21  1 2009-01-01           3 -0.71071921 -1.21550600
22  2 2009-01-02           3  1.17501999  1.03588482
23  3 2009-01-03           3  1.11717121 -0.35353058
...

Die tidyrBibliothek verfügt über die gather()Funktion, die sich hervorragend zum Kombinieren eines Satzes von Spalten eignet:

library(dplyr)
library(tidyr)
library(stringr)

df %>% gather(loop_number, Q3.2, starts_with("Q3.2")) %>% 
  mutate(loop_number = str_sub(loop_number,-2,-2)) %>%
  select(id, time, loop_number, Q3.2)


   id       time loop_number        Q3.2
1   1 2009-01-01           1 -0.20591649
2   2 2009-01-02           1 -0.19811357
3   3 2009-01-03           1  0.35147949
...
29  9 2009-01-09           3 -0.58581232
30 10 2009-01-10           3 -2.33393981

Der resultierende Datenrahmen hat erwartungsgemäß 30 Zeilen (10 Personen, jeweils 3 Schleifen). Das Sammeln eines zweiten Satzes von Spalten funktioniert jedoch nicht richtig - es werden die beiden kombinierten Spalten erfolgreich erstellt Q3.2und Q3.3es werden 90 statt 30 Zeilen angezeigt (alle Kombinationen von 10 Personen, 3 Schleifen von Q3.2 und 3 Schleifen von Q3 .3; die Kombinationen werden für jede Gruppe von Spalten in den tatsächlichen Daten erheblich zunehmen):

df %>% gather(loop_number, Q3.2, starts_with("Q3.2")) %>% 
  gather(loop_number, Q3.3, starts_with("Q3.3")) %>%
  mutate(loop_number = str_sub(loop_number,-2,-2))


   id       time loop_number        Q3.2        Q3.3
1   1 2009-01-01           1 -0.20591649  1.52718069
2   2 2009-01-02           1 -0.19811357 -0.40380049
3   3 2009-01-03           1  0.35147949 -1.02641801
...
89  9 2009-01-09           3 -0.58581232 -0.13187024
90 10 2009-01-10           3 -2.33393981 -0.48502131

Gibt es eine Möglichkeit, mehrere Aufrufe zu verwenden, um gather()dies zu mögen, indem kleine Teilmengen von Spalten wie diese kombiniert werden, während die richtige Anzahl von Zeilen beibehalten wird?

Andrew
quelle
Was ist los mitdf %>% gather(loop_number, Q3.2, starts_with("Q3."))
Alex
Das bringt mir eine konsolidierte Spalte mit 60 Zeilen. Ich denke, das könnte funktionieren, wenn ich dann eine Art Aufruf seperate()einfügen würde, um die Q3.3-Werte (und darüber hinaus) in ihre eigenen Spalten aufzuteilen. Aber das scheint immer noch eine wirklich umständliche Hacky-Lösung zu sein ...
Andrew
verwende spreadich an einer Lösung arbeite jetzt: p
Alex
Versuche dies! df %>% gather(question_number, Q3.2, starts_with("Q3.")) %>% mutate(loop_number = str_sub(question_number,-2,-2), question_number = str_sub(question_number,1,4)) %>% select(id, time, loop_number, question_number, Q3.2) %>% spread(key = question_number, value = Q3.2)
Alex
Oh, das funktioniert wirklich gut für die beiden Variablen. Ich bin allerdings neugierig, ob es skalierbar ist - in meinen realen Daten habe ich Q3.2-Q3.30, daher wären einige Einzelanrufe erforderlich spread(). Obwohl mehrere Anrufe ohnehin unvermeidlich erscheinen, egal ob es sich um ein paar generate()funktionierende oder verschachtelte spread()s handelt ...
Andrew

Antworten:

145

Dieser Ansatz erscheint mir ziemlich natürlich:

df %>%
  gather(key, value, -id, -time) %>%
  extract(key, c("question", "loop_number"), "(Q.\\..)\\.(.)") %>%
  spread(question, value)

Sammeln Sie zuerst alle Fragenspalten, extract()trennen Sie sie in questionund loop_numberdann spread()wieder in die Spalten.

#>    id       time loop_number         Q3.2        Q3.3
#> 1   1 2009-01-01           1  0.142259203 -0.35842736
#> 2   1 2009-01-01           2  0.061034802  0.79354061
#> 3   1 2009-01-01           3 -0.525686204 -0.67456611
#> 4   2 2009-01-02           1 -1.044461185 -1.19662936
#> 5   2 2009-01-02           2  0.393808163  0.42384717
Hadley
quelle
5
Hallo. Ich habe viele Spalten mit Namen, die auf 1 und 2 enden, wie Alter1, Alter2, Gewicht1, Gewicht2, Blut1, Blut2 .... Wie würde ich Ihre Methode hier anwenden?
Skan
4
Was bedeutet dieser Teil: "(Q. \\ ..) \\. (.)" Wonach würde ich suchen, um zu entschlüsseln, was dort passiert?
Mob
3
@mob Reguläre Ausdrücke
Hadley
1
@mob "(Q. \\ ..) \\. (.)" ist ein regulärer Ausdruck mit Klammern, die die Gruppen des regulären Ausdrucks definieren, die in "question" und "loop_number" extrahiert werden sollen. In diesem Beispiel werden die Schlüsselelemente mit dem Ausdruck "Q. \\ .." in die Spalte "Frage" (dh "Q3.2" und "Q3.3") und anschließend in den nachfolgenden Teil verschoben Punkt, ausgedrückt als ".", geht in die Spalte "loop_number".
LC-Datenwissenschaftler
31

Dies könnte mit erfolgen reshape. Es ist aber möglich mit dplyr.

  colnames(df) <- gsub("\\.(.{2})$", "_\\1", colnames(df))
  colnames(df)[2] <- "Date"
  res <- reshape(df, idvar=c("id", "Date"), varying=3:8, direction="long", sep="_")
  row.names(res) <- 1:nrow(res)

   head(res)
  #  id       Date time       Q3.2       Q3.3
  #1  1 2009-01-01    1  1.3709584  0.4554501
  #2  2 2009-01-02    1 -0.5646982  0.7048373
  #3  3 2009-01-03    1  0.3631284  1.0351035
  #4  4 2009-01-04    1  0.6328626 -0.6089264
  #5  5 2009-01-05    1  0.4042683  0.5049551
  #6  6 2009-01-06    1 -0.1061245 -1.7170087

Oder mit dplyr

  library(tidyr)
  library(dplyr)
  colnames(df) <- gsub("\\.(.{2})$", "_\\1", colnames(df))

  df %>%
     gather(loop_number, "Q3", starts_with("Q3")) %>% 
     separate(loop_number,c("L1", "L2"), sep="_") %>% 
     spread(L1, Q3) %>%
     select(-L2) %>%
     head()
  #  id       time       Q3.2       Q3.3
  #1  1 2009-01-01  1.3709584  0.4554501
  #2  1 2009-01-01  1.3048697  0.2059986
  #3  1 2009-01-01 -0.3066386  0.3219253
  #4  2 2009-01-02 -0.5646982  0.7048373
  #5  2 2009-01-02  2.2866454 -0.3610573
  #6  2 2009-01-02 -1.7813084 -0.7838389

Aktualisieren

Mit tidyr_0.8.3.9000können wir pivot_longermehrere Spalten umformen. (Verwenden der geänderten Spaltennamen von gsuboben)

library(dplyr)
library(tidyr)
df %>% 
    pivot_longer(cols = starts_with("Q3"), 
          names_to = c(".value", "Q3"), names_sep = "_") %>% 
    select(-Q3)
# A tibble: 30 x 4
#      id time         Q3.2    Q3.3
#   <int> <date>      <dbl>   <dbl>
# 1     1 2009-01-01  0.974  1.47  
# 2     1 2009-01-01 -0.849 -0.513 
# 3     1 2009-01-01  0.894  0.0442
# 4     2 2009-01-02  2.04  -0.553 
# 5     2 2009-01-02  0.694  0.0972
# 6     2 2009-01-02 -1.11   1.85  
# 7     3 2009-01-03  0.413  0.733 
# 8     3 2009-01-03 -0.896 -0.271 
#9     3 2009-01-03  0.509 -0.0512
#10     4 2009-01-04  1.81   0.668 
# … with 20 more rows

HINWEIS: Die Werte sind unterschiedlich, da beim Erstellen des Eingabedatensatzes kein festgelegter Startwert vorhanden war

akrun
quelle
Whoa, das funktioniert perfekt. tidyr ist angeblich ein Ersatz / Upgrade für die Umformung - ich frage mich, ob @hadley einen Weg kennt, dasselbe mit dplyr oder tidyr zu tun ...
Andrew
Das ist pure Magie. Das einzige, was ich hinzugefügt habe, war mutate(loop_number = as.numeric(L2))vor dem Ablegen L2, und es ist perfekt.
Andrew
1
@ Andrew Ich persönlich bevorzuge die reshapeMethode wegen ihres kompakten Codes, obwohl dplyrsie für große Datenmengen möglicherweise schneller ist.
Akrun
1
Ich war nie in der Lage, die reshape()Funktion zu verstehen , meine Lösung für eine meiner Meinung nach ziemlich saubere Tidyr-Implementierung zu sehen.
Hadley
22

Mit dem letzten Update auf melt.data.tablekönnen wir jetzt mehrere Spalten schmelzen. Damit können wir tun:

require(data.table) ## 1.9.5
melt(setDT(df), id=1:2, measure=patterns("^Q3.2", "^Q3.3"), 
     value.name=c("Q3.2", "Q3.3"), variable.name="loop_number")
 #    id       time loop_number         Q3.2        Q3.3
 # 1:  1 2009-01-01           1 -0.433978480  0.41227209
 # 2:  2 2009-01-02           1 -0.567995351  0.30701144
 # 3:  3 2009-01-03           1 -0.092041353 -0.96024077
 # 4:  4 2009-01-04           1  1.137433487  0.60603396
 # 5:  5 2009-01-05           1 -1.071498263 -0.01655584
 # 6:  6 2009-01-06           1 -0.048376809  0.55889996
 # 7:  7 2009-01-07           1 -0.007312176  0.69872938

Die Entwicklungsversion erhalten Sie hier .

Arun
quelle
Hallo. Ich habe viele Spalten mit Namen, die auf 1 und 2 enden, wie Alter1, Alter2, Gewicht1, Gewicht2, Blut1, Blut2 .... Wie würde ich Ihre Methode hier anwenden?
Skan
skan, überprüfen Sie die Umformung Vignette . Viel Glück!
Arun
Ich habe es getan, aber ich weiß nicht, wie ich reguläre Ausdrücke richtig einbetten kann, um Spaltennamen zu teilen und sie zum Schmelzen zu übergeben. Es gibt nur ein Beispiel mit Mustern, und es ist zu einfach. In meinem Fall müsste ich viele viele Spaltennamen in pattern ()
einfügen
Stellen Sie sich vor, Sie haben diese Spalten: paste0 (rep (LETTERS, each = 3), 1: 3) und Sie möchten die lange Tabelle durch einen Buchstaben und eine Zahl definieren
skan
Dies ist zweifellos die prägnanteste und am einfachsten zu interpretierende.
Michael Bellhouse
10

Es hat überhaupt nichts mit "tidyr" und "dplyr" zu tun, aber hier ist eine weitere Option, die Sie berücksichtigen sollten: merged.stackaus meinem "splitstackshape" -Paket , V1.4.0 und höher.

library(splitstackshape)
merged.stack(df, id.vars = c("id", "time"), 
             var.stubs = c("Q3.2.", "Q3.3."),
             sep = "var.stubs")
#     id       time .time_1       Q3.2.       Q3.3.
#  1:  1 2009-01-01      1. -0.62645381  1.35867955
#  2:  1 2009-01-01      2.  1.51178117 -0.16452360
#  3:  1 2009-01-01      3.  0.91897737  0.39810588
#  4:  2 2009-01-02      1.  0.18364332 -0.10278773
#  5:  2 2009-01-02      2.  0.38984324 -0.25336168
#  6:  2 2009-01-02      3.  0.78213630 -0.61202639
#  7:  3 2009-01-03      1. -0.83562861  0.38767161
# <<:::SNIP:::>>
# 24:  8 2009-01-08      3. -1.47075238 -1.04413463
# 25:  9 2009-01-09      1.  0.57578135  1.10002537
# 26:  9 2009-01-09      2.  0.82122120 -0.11234621
# 27:  9 2009-01-09      3. -0.47815006  0.56971963
# 28: 10 2009-01-10      1. -0.30538839  0.76317575
# 29: 10 2009-01-10      2.  0.59390132  0.88110773
# 30: 10 2009-01-10      3.  0.41794156 -0.13505460
#     id       time .time_1       Q3.2.       Q3.3.
A5C1D2H2I1M1N2O1R2T1
quelle
1
Hallo. Ich habe viele Spalten mit Namen, die auf 1 und 2 enden, wie Alter1, Alter2, Gewicht1, Gewicht2, Blut1, Blut2 .... Wie würde ich Ihre Methode hier anwenden?
Skan
6

Wenn Sie wie ich sind und nicht herausfinden können, wie "regulärer Ausdruck mit Erfassungsgruppen" verwendet werden soll extract, repliziert der folgende Code die extract(...)Zeile in Hadleys Antwort:

df %>% 
    gather(question_number, value, starts_with("Q3.")) %>%
    mutate(loop_number = str_sub(question_number,-2,-2), question_number = str_sub(question_number,1,4)) %>%
    select(id, time, loop_number, question_number, value) %>% 
    spread(key = question_number, value = value)

Das Problem hierbei ist, dass die anfängliche Erfassung eine Schlüsselspalte bildet, die tatsächlich eine Kombination aus zwei Schlüsseln ist. Ich habe mich für die Verwendung mutatein meiner ursprünglichen Lösung in den Kommentaren entschieden, um diese Spalte in zwei Spalten mit entsprechenden Informationen, eine loop_numberSpalte und eine question_numberSpalte , aufzuteilen . spreadkann dann verwendet werden, um die langen Formulardaten, die Schlüsselwertpaare sind, (question_number, value)in breite Formulardaten umzuwandeln .

Alex
quelle