Thu thập nhiều tập hợp cột


108

Tôi có dữ liệu từ một cuộc khảo sát trực tuyến trong đó người trả lời đi qua một vòng câu hỏi 1-3 lần. Các phần mềm khảo sát (Qualtrics) ghi lại dữ liệu này trong nhiều cột-có nghĩa là, Q3.2 trong cuộc khảo sát sẽ có cột Q3.2.1., Q3.2.2.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
...

Tôi muốn kết hợp tất cả các cột QN.N * thành các cột QN.N riêng lẻ gọn gàng, cuối cùng kết thúc bằng một cái gì đó như sau:

   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
...

Các tidyrthư viện có gather()chức năng, trong đó hoạt động tuyệt vời để kết hợp một loạt các cột:

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

Khung dữ liệu kết quả có 30 hàng, như mong đợi (10 cá thể, mỗi vòng 3 vòng). Tuy nhiên, việc thu thập tập hợp cột thứ hai không hoạt động chính xác — nó làm cho hai cột được kết hợp thành côngQ3.2Q3.3nhưng kết thúc với 90 hàng thay vì 30 (tất cả các kết hợp của 10 cá nhân, 3 vòng của Q3.2 và 3 vòng của Q3 .3; các kết hợp sẽ tăng đáng kể cho mỗi nhóm cột trong dữ liệu thực tế):

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

Có cách nào để sử dụng nhiều lệnh gọi gather()như thế này, kết hợp các tập hợp con nhỏ của các cột như thế này trong khi vẫn duy trì số hàng chính xác không?


sai với những gìdf %>% gather(loop_number, Q3.2, starts_with("Q3."))
Alex

Điều đó giúp tôi có một cột tổng hợp với 60 hàng. Tôi đoán điều đó có thể hoạt động nếu sau đó tôi bao gồm một số loại lệnh gọi seperate()để chia các giá trị Q3.3 (và hơn thế nữa) thành các cột của riêng chúng. Nhưng đó vẫn có vẻ như là một giải pháp hacky thực sự vòng vo…
Andrew

sử dụng spreadtôi đang làm việc trên một giải pháp bây giờ: p
Alex

thử cái này! 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

Ồ, điều đó thực sự hiệu quả đối với hai biến. Tôi tò mò liệu nó có thể mở rộng hay không - trong dữ liệu thực của tôi, tôi có Q3.2-Q3.30, vì vậy nó sẽ cần một loạt các cuộc gọi riêng lẻ spread(). Mặc dù nhiều cuộc gọi xuất hiện nào không thể tránh khỏi, cho dù đó là một loạt các generate()s rằng công việc hoặc lồng nhau spread()s ...
Andrew

Câu trả lời:


146

Cách tiếp cận này có vẻ khá tự nhiên đối với tôi:

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

Đầu tiên tập hợp tất cả các cột câu hỏi, sử dụng extract()để tách thành questionloop_number, sau đó spread()đặt câu hỏi lại vào các cột.

#>    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

5
Xin chào. Tôi có nhiều cột có tên kết thúc bằng 1 và 2, như age1, age2, weight1, weight2, blood1, blood2 .... Làm cách nào để áp dụng phương pháp của bạn ở đây?
skan

4
Phần này có nghĩa là gì: "(Q. \\ ..) \\. (.)" Tôi sẽ tìm kiếm gì để giải mã những gì đang xảy ra ở đó?
mob

3
@mob Regular expressions
hadley

1
@mob "(Q. \\ ..) \\. (.)" là một biểu thức chính quy có dấu ngoặc đơn xác định các nhóm của biểu thức chính quy cần trích xuất thành "câu hỏi" và "loop_number". Cụ thể hơn, trong ví dụ này, các mục trong khóa có biểu thức "Q. \\ .." đi vào cột "câu hỏi" (tức là "Q3.2" và "Q3.3"), rồi đến phần tiếp theo dấu chấm, được biểu thị bằng ".", đi vào cột "loop_number".
LC-datascientist

31

Điều này có thể được thực hiện bằng cách sử dụng reshape. Nó có thể với dplyrmặc dù.

  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

Hoặc sử dụng 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

Cập nhật

Với tidyr_0.8.3.9000, chúng ta có thể sử dụng pivot_longerđể định hình lại nhiều cột. (Sử dụng các tên cột đã thay đổi gsubở trên)

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

LƯU Ý: Các giá trị khác nhau vì không có hạt giống nào được thiết lập trong việc tạo tập dữ liệu đầu vào


Whoa, điều này hoạt động hoàn hảo. tidyr là bề ngoài là một sự thay thế / nâng cấp cho Reshape - Tôi tự hỏi nếu @hadley biết một cách để làm điều này cùng với dplyr hoặc tidyr ...
Andrew

Đó là phép thuật thuần túy. Điều duy nhất tôi đã thêm là mutate(loop_number = as.numeric(L2))trước khi bỏ L2, và nó hoàn hảo.
Andrew

1
@Andrew Cá nhân tôi thích reshapephương pháp này hơn cho mã nhỏ gọn của nó, mặc dù dplyrcó thể nhanh hơn đối với các bộ dữ liệu lớn.
akrun

1
Tôi chưa bao giờ có thể hiểu được reshape()hàm, hãy xem giải pháp của tôi cho những gì tôi có vẻ là một triển khai gọn gàng hơn khá rõ ràng.
hadley

22

Với bản cập nhật gần đây melt.data.table, chúng tôi hiện có thể làm tan nhiều cột. Với điều đó, chúng tôi có thể làm:

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

Bạn có thể tải phiên bản phát triển từ đây .


Xin chào. Tôi có nhiều cột có tên kết thúc bằng 1 và 2, như age1, age2, weight1, weight2, blood1, blood2 .... Làm cách nào để áp dụng phương pháp của bạn ở đây?
skan

skan, kiểm tra họa tiết định hình lại . Chúc may mắn!
Arun

Tôi đã làm nhưng tôi không biết cách nhúng biểu thức chính quy đúng cách để tách tên cột và chuyển nó vào để tan chảy. Chỉ có một ví dụ với các mẫu và nó quá đơn giản. Trong trường hợp của tôi, tôi sẽ cần bao gồm nhiều tên cột bên trong mẫu ()
skan

Hãy tưởng tượng bạn có các cột này: paste0 (rep (LETTERS, each = 3), 1: 3) và bạn muốn nhận được bảng dài được xác định bởi một chữ cái và một số
skan

Đây là cách ngắn gọn và dễ giải thích nhất.
Michael Bellhouse

10

Nó hoàn toàn không liên quan đến "slimr" và "dplyr", nhưng đây là một tùy chọn khác cần xem xét: merged.stacktừ gói "splitstackshape" của tôi , V1.4.0 trở lên.

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.

1
Xin chào. Tôi có nhiều cột có tên kết thúc bằng 1 và 2, như age1, age2, weight1, weight2, blood1, blood2 .... Làm cách nào để áp dụng phương pháp của bạn ở đây?
skan

6

Trong trường hợp bạn giống tôi và không thể tìm ra cách sử dụng "biểu thức chính quy với các nhóm nắm bắt" extract, đoạn mã sau sẽ sao chép extract(...)dòng trong câu trả lời của Hadleys:

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)

Vấn đề ở đây là tập hợp ban đầu tạo thành một cột khóa thực sự là sự kết hợp của hai khóa. Tôi đã chọn sử dụng mutatetrong giải pháp ban đầu của mình trong các nhận xét để chia cột này thành hai cột với thông tin tương đương, một loop_numbercột và một question_numbercột. spreadsau đó có thể được sử dụng để chuyển đổi dữ liệu dạng dài, là các cặp giá trị quan trọng (question_number, value)thành dữ liệu dạng rộng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.