Birden çok sütun kümesi toplayın


108

Yanıtlayanların 1-3 kez soru döngüsünden geçtiği çevrimiçi bir anketten verilerim var. Anket yazılımı (Qualtrics) birden çok sütun-olduğu bu verileri kaydeder, ankete Q3.2 sütunlar bulunur Q3.2.1., Q3.2.2.ve 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üm QN.N * sütunlarını düzenli tek tek QN.N sütunlarında birleştirmek ve sonuçta şuna benzer bir sonuç elde etmek istiyorum:

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

tidyrKütüphane vardır gather()birleştirmek için inşaat büyük işlevi, bir sütun kümesi:

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

Ortaya çıkan veri çerçevesi beklendiği gibi 30 satıra sahiptir (10 kişi, her biri 3 döngü). Bununla birlikte, çalışmaz sütun ikinci bir dizi toplama doğru-başarılı bir araya getirilen iki sütun yapar Q3.2ve Q3.3fakat 90 satır yerine 30 ile uçları (10 birey, Q3.2 3 döngüler ve Q3 3 döngüyle tüm kombinasyonlarını .3; kombinasyonlar, gerçek verilerdeki her sütun grubu için önemli ölçüde artacaktır):

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

gather()Doğru satır sayısını korurken bunun gibi küçük sütun alt kümelerini birleştirerek birden çok çağrı kullanmanın bir yolu var mı ?


sorun nedf %>% gather(loop_number, Q3.2, starts_with("Q3."))
Alex

Bu bana 60 satırlık bir konsolide sütun getiriyor. seperate()Q3.3 (ve ötesi) değerlerini kendi sütunlarına bölmek için bir tür çağrı eklersem bu işe yarayabilir. Ancak bu yine de gerçekten dolambaçlı bir hacky çözüm gibi görünüyor…
Andrew

kullan spreadşimdi bir çözüm üzerinde çalışıyorum: p
Alex

bunu dene! 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

Ooh, bu iki değişken için gerçekten işe yarıyor. Yine de ölçeklenebilir olup olmadığını merak ediyorum - gerçek verilerimde Q3.2-Q3.30 var, bu yüzden bir sürü bireysel çağrıya ihtiyaç duyacak spread(). Birden fazla arama kaçınılmaz gibi görünse de, işe yarayan generate()veya iç içe geçmiş bir grup arama olsun spread()
Andrew

Yanıtlar:


146

Bu yaklaşım bana oldukça doğal görünüyor:

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

Önce tüm soru sütunlarını toplayın, extract()ayırmak için kullanın questionve loop_numberardından spread()sütunlara tekrar soru sorun.

#>    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
Merhaba. Yaş1, yaş2, ağırlık1, ağırlık2, kan1, kan2 gibi isimleri 1 ve 2 ile biten birçok sütunum var ... Burada yönteminizi nasıl uygularım?
skan

4
Bu bölüm ne anlama geliyor: "(S. \\ ..) \\. (.)" Orada olanların kodunu çözmek için ne arayacağım?
mob

3
@mob Normal ifadeler
hadley

1
@mob "(Q. \\ ..) \\. (.)", "soru" ve "döngü_numarası" olarak ayıklanacak normal ifade gruplarını tanımlayan parantezli normal bir ifadedir. Daha spesifik olarak, bu örnekte, "Q. \\ .." ifadesine sahip anahtardaki öğeler "soru" sütununa (yani, "S3.2" ve "S3.3"), ardından sonraki kısma gider "." olarak ifade edilen dönem "döngü_sayı" sütununa gider.
LC-datascientist

31

Bu kullanılarak yapılabilir reshape. Yine de mümkündür 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

Veya kullanarak 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

Güncelleme

İle birden çok sütunu yeniden şekillendirmek tidyr_0.8.3.9000için kullanabiliriz pivot_longer. ( gsubYukarıdan değiştirilen sütun adlarını kullanarak )

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

NOT: Değerler farklıdır çünkü giriş veri kümesini oluştururken ayarlanmış bir başlangıç ​​noktası yoktur.


Whoa, bu mükemmel çalışıyor. tidyr görünüşte yeniden şekillendirme için bir yedek / yükseltmedir - @ hadley aynı şeyi dplyr veya tidyr ile yapmanın bir yolunu biliyor mu merak ediyorum…
Andrew

Bu saf sihirdir. Eklediğim tek şey mutate(loop_number = as.numeric(L2))düşmeden önceydi L2ve mükemmel.
Andrew

1
@Andrew Ben kişisel olarak reshapekompakt kodu için yöntemi tercih ediyorum , ancak dplyrbüyük veri kümeleri için daha hızlı olabilir.
akrun

1
reshape()İşlevi asla anlayamadım, çözümümün bana oldukça temiz bir tidyr uygulaması gibi göründüğünü görmedim.
hadley

22

Son güncellemeyle birlikte melt.data.table, artık birden fazla sütunu eritebiliriz. Bununla şunları yapabiliriz:

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

Geliştirme sürümünü buradan edinebilirsiniz .


Merhaba. Yaş1, yaş2, ağırlık1, ağırlık2, kan1, kan2 gibi isimleri 1 ve 2 ile biten birçok sütunum var ... Burada yönteminizi nasıl uygularım?
skan

skan, yeniden şekillendirme vinyetini kontrol et . İyi şanslar!
Arun

Yaptım ama düzenli ifadeleri sütun adlarını bölmek ve erimeye geçirmek için nasıl düzgün şekilde gömeceğimi bilmiyorum. Desenli tek bir örnek var ve bu çok basit. Benim durumumda pattern () içine birçok sütun adı
eklemem gerekir

Şu sütunlara sahip olduğunuzu hayal edin: paste0 (rep (LETTERS, her biri = 3), 1: 3) ve bir harf ve bir sayı ile tanımlanan uzun tabloyu almak istiyorsunuz
skan

Bu, en özlü ve yorumlaması kolay olanıdır.
Michael Bellhouse

10

Bu "tidyr" ve "dplyr" ile ilgili hiç değil, ama burada size başka bir seçenek var: merged.stackdan benim "splitstackshape" paket , v1.4.0 ve üzeri.

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
Merhaba. Yaş1, yaş2, ağırlık1, ağırlık2, kan1, kan2 gibi isimleri 1 ve 2 ile biten birçok sütunum var ... Burada yönteminizi nasıl uygularım?
skan

6

Benim gibiyseniz ve "yakalama grupları ile normal ifadenin" nasıl kullanılacağını çözemiyorsanız extract, aşağıdaki kod extract(...)Hadleys'in cevabındaki satırı çoğaltır :

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)

Buradaki sorun, ilk toplama işleminin aslında iki anahtarın birleşimi olan bir anahtar sütun oluşturmasıdır. mutateBu sütunu eşdeğer bilgi, bir loop_numbersütun ve bir question_numbersütun içeren iki sütuna bölmek için yorumlarda orijinal çözümümde kullanmayı seçtim . spreaddaha sonra anahtar değer çiftleri olan uzun biçimli verileri (question_number, value)geniş biçimli verilere dönüştürmek için kullanılabilir .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.