【问题标题】:Transforming multible columns from wide to long format.将多列从宽格式转换为长格式。
【发布时间】:2017-10-22 14:37:24
【问题描述】:

首先 - 我不认为这是重复的帖子。我发现了几篇关于将多列从宽格式转换为长格式的精彩帖子和网页,但它们都与我的数据不相似,因为它们处理的是同一事物的若干列(在我的示例中为 A1、A2 ,A3,A4),而且它们还没有包含长格式变量(在我的例子中是框架)。


这是我的问题:

我正在处理一个数据集,其中包含由两个不同的运动捕捉系统测量的许多变量。目前我的数据集是宽格式的,但我意识到 ggplot 在长格式下效果更好,因此我希望转换我的数据。

这是我的数据的一个非常简化的版本:

id <- (rep(1:3, each = 3))
frame <- (rep(1:3, 3))
A1 <- runif(9, min =1, max =100)
B1 <- runif(9, min =1, max =10)
C1 <- runif(9, min =-10, max =10)
A2 <- rnorm(9, mean = A1, sd=1)
B2 <- rnorm(9, mean = B1, sd=1)
C2 <- rnorm(9, mean = C1, sd=1)
df_wide <- as.data.frame.matrix(cbind(id, frame, A1, B1, C1, A2, B2, C2))
rm(id, frame, A1, A2, B1, B2, C1, C2)

df_wide$id <- as.factor(df_wide$id)
df_wide$frame <- as.factor(df_wide$frame)

head(df_wide)

  id frame        A1       B1        C1        A2       B2         C2
1  1     1 50.940395 4.141713 -1.294736 51.324398 4.271260  0.6174782
2  1     2 33.117691 5.044080  1.820367 32.977860 5.506677  0.8811504
3  1     3 50.000625 8.584148 -1.294245 50.603195 8.099262  0.6418580
4  2     1 61.675927 5.269216 -6.002856 61.996378 6.186417 -6.5428624
5  2     2  5.514353 6.570010  5.199728  4.798275 4.955662  5.1502535
6  2     3 51.580086 5.683788  9.831663 50.717459 5.430070 10.9601541

A1 和 A2、B1 和 B2 是系统 1 和系统 2 进行的相同类型运动(A 和 B)的度量。 如框架变量所示,每个患者都被测量了多次。

我希望我的 data.frame 看起来像这样:

  id frame system        A        B         C
1  1     1      1 
2  1     1      2 
3  1     2      1 
4  1     2      2 
5  1     3      1 
6  1     3      2 

我有两个问题让我无法解决这个问题:

1) 两个系统之间的测量不相邻放置。因此我不能使用这样的代码:

library(tidyr)
df_long <- gather(df_wide, System, A, A1:A2, factor_key=TRUE)

2) 我的数据集包含近 120 个变量,所以我想要一个不需要我为每个变量编写代码的解决方案。我正在考虑制作一个循环来解决这个问题,但是在这方面的任何帮助也将不胜感激。

【问题讨论】:

    标签: r tidyr


    【解决方案1】:

    tidyr 方法是:1) 收集度量列,2) 使用带有 regexextract 将标题分隔为 movements (alpha) + system(numeric) , 3) 将movements 传播到标题:

    library(tidyr)
    
    df_wide %>% 
        gather(keys, values, -id, -frame) %>% 
        extract(keys, c("movements", "system"), "([a-zA-Z]+)([0-9]+)") %>% 
        spread(movements, values)
    
    #   id frame system         A        B         C
    #1   1     1      1 62.175823 9.661748 -9.120404
    #2   1     1      2 62.957358 9.229938 -8.814429
    #3   1     2      1 22.463641 3.904546  4.059267
    #4   1     2      2 22.798492 3.045190  4.663611
    #5   1     3      1 13.897632 6.675986 -9.528184
    #6   1     3      2 15.036539 6.964412 -8.920507
    #7   2     1      1 38.765030 7.735174  8.373283
    #8   2     1      2 40.124285 4.947368 10.143035
    #9   2     2      1  5.924254 9.358200  9.866305
    #10  2     2      2  5.197255 9.859347 10.088928
    #11  2     3      1 29.961107 7.451472 -3.143658
    #12  2     3      2 31.322740 8.328626 -2.050261
    #13  3     1      1 71.010782 6.909414  7.128306
    #14  3     1      2 69.860047 7.675693  7.817473
    #15  3     2      1 64.985282 1.596932 -3.422237
    #16  3     2      2 64.839996 2.828168 -3.826748
    #17  3     3      1 70.631159 1.238806  5.398818
    #18  3     3      2 70.963814 1.255340  3.728302
    

    【讨论】:

    • 谢谢。这是一个非常优雅的解决方案。我有一个问题:extract(keys, c("movements", "system"), "([a-zA-Z]+)([0-9]+)") 在我的真实数据集中,变量是不称为 A1 和 A2,而是称为 A 和 cA。由于我不理解表达式的最后一部分,因此我很难将这条线转换为在我的数据集中有效的东西。你能再帮我一次吗?
    • 您能否详细说明列的外观?那么如何区分运动和系统呢?
    • 和上面的一样,只是代替了:A1、B1、C1、A2、B2、C2。我有:A、B、C、cA、cB、cC。运动是:A,B C。系统是:1如果前面运动没有字母,2如果有c。
    • 在你的问题中你说你有 120 多个变量,所以我想这仍然是你所拥有的简化版本。但是给定列 A、B、C、cA、cB、cC,您可以将 extract 行替换为 extract(keys, c("system", "movements"), "([a-z]*)([A-Z].*)") 并稍后重新编码 system 列。
    • 是的,你是对的,它仍然是一个简化版本,但命名约定是一样的。你的解决方案很有效,我现在有整洁的数据。非常感谢!
    【解决方案2】:

    运行reshape,然后对结果进行排序。

    前 4 行将参数设置为 reshape。特别是 varyinglist(A = c("A1", "A2"), B = c("B1", "B2"), C = c("C1", "C2")) 。最后一行代码对行进行排序,如果行顺序不重要,可以省略。

    这里的 A... 列与 B... 列和 C... 列具有相同的类型,但即使不是这种情况,此解决方案也将继续工作。

    没有使用任何包。

    此 SO 问题/答案类似,但有细微差别:Gather multiple date/value columns using tidyr

    idvar <- 1:2
    nms <- names(df_wide)[-idvar]   # names of non-id variables
    varying <- split(nms, sub("\\d+$", "", nms))
    v.names <- names(varying)
    r <- reshape(df_wide, dir = "long", varying = varying, v.names = v.names, idvar = idvar)
    r[order(r$id, r$frame), ]
    

    给予:

          id frame time         A        B          C
    1.1.1  1     1    1 50.940395 4.141713 -1.2947360
    1.1.2  1     1    2 51.324398 4.271260  0.6174782
    1.2.1  1     2    1 33.117691 5.044080  1.8203670
    1.2.2  1     2    2 32.977860 5.506677  0.8811504
    1.3.1  1     3    1 50.000625 8.584148 -1.2942450
    1.3.2  1     3    2 50.603195 8.099262  0.6418580
    2.1.1  2     1    1 61.675927 5.269216 -6.0028560
    2.1.2  2     1    2 61.996378 6.186417 -6.5428624
    2.2.1  2     2    1  5.514353 6.570010  5.1997280
    2.2.2  2     2    2  4.798275 4.955662  5.1502535
    2.3.1  2     3    1 51.580086 5.683788  9.8316630
    2.3.2  2     3    2 50.717459 5.430070 10.9601541
    

    注意:可重现形式的输入如下 - 生成问题中的输入的代码不可重现,因为在没有set.seed 的情况下使用了随机数。

    df_wide <- structure(list(id = c(1L, 1L, 1L, 2L, 2L, 2L), frame = c(1L, 
    2L, 3L, 1L, 2L, 3L), A1 = c(50.940395, 33.117691, 50.000625, 
    61.675927, 5.514353, 51.580086), B1 = c(4.141713, 5.04408, 8.584148, 
    5.269216, 6.57001, 5.683788), C1 = c(-1.294736, 1.820367, -1.294245, 
    -6.002856, 5.199728, 9.831663), A2 = c(51.324398, 32.97786, 50.603195, 
    61.996378, 4.798275, 50.717459), B2 = c(4.27126, 5.506677, 8.099262, 
    6.186417, 4.955662, 5.43007), C2 = c(0.6174782, 0.8811504, 0.641858, 
    -6.5428624, 5.1502535, 10.9601541)), .Names = c("id", "frame", 
    "A1", "B1", "C1", "A2", "B2", "C2"), class = "data.frame", row.names = c("1", 
    "2", "3", "4", "5", "6"))
    

    【讨论】:

    • 感谢您的详尽回复。我不知道(重新)生成实际值可能会引起人们的兴趣。我会记得在未来使用 set.seed。
    猜你喜欢
    • 1970-01-01
    • 2018-01-31
    • 1970-01-01
    • 2015-07-18
    • 1970-01-01
    • 1970-01-01
    • 2023-02-25
    相关资源
    最近更新 更多