45

当我想要携带多个度量变量时,我无法找出将数据从长格式转换为宽格式的最优雅和灵活的方法。

例如,这是一个长格式的简单数据框。 ID是主题,TIME是时间变量,并且X是在 处Y进行的测量:IDTIME

> my.df <- data.frame(ID=rep(c("A","B","C"), 5), TIME=rep(1:5, each=3), X=1:15, Y=16:30)
> my.df

   ID TIME  X  Y
1   A    1  1 16
2   B    1  2 17
3   C    1  3 18
4   A    2  4 19
5   B    2  5 20
6   C    2  6 21
7   A    3  7 22
8   B    3  8 23
9   C    3  9 24
10  A    4 10 25
11  B    4 11 26
12  C    4 12 27
13  A    5 13 28
14  B    5 14 29
15  C    5 15 30

如果我只是想将 的值TIME转换为包含 include 的列标题X,我知道我可以cast()reshape包(或dcast()from reshape2)中使用:

> cast(my.df, ID ~ TIME, value="X")
  ID 1 2 3  4  5
1  A 1 4 7 10 13
2  B 2 5 8 11 14
3  C 3 6 9 12 15

但我真正想做的也是带来Y另一个度量变量,并让列名反映度量变量名称和时间值:

  ID X_1 X_2 X_3  X_4 X_5 Y_1 Y_2 Y_3 Y_4 Y_5
1  A   1   4   7   10  13  16  19  22  25  28
2  B   2   5   8   11  14  17  20  23  26  29
3  C   3   6   9   12  15  18  21  24  27  30

(FWIW,我真的不在乎是否所有的X's 都先跟在Y's 之后,或者它们是否交错为X_1, Y_1, X_2,Y_2等)

我可以通过对长数据进行两次转换并合并结果来接近这一点,尽管列名需要一些工作,如果我需要在Xand之外添加第三个或第四个变量,我需要对其进行调整Y

merge(
cast(my.df, ID ~ TIME, value="X"),
cast(my.df, ID ~ TIME, value="Y"),
by="ID", suffixes=c("_X","_Y")
)

似乎reshape2和/或plyr应该能够比我的尝试更优雅地执行此操作,以及更干净地处理多个度量变量。类似的东西cast(my.df, ID ~ TIME, value=c("X","Y")),这是无效的。但我一直无法弄清楚。

4

6 回答 6

23

为了像你想要的那样处理多个变量,你需要melt在转换之前拥有你拥有的数据。

library("reshape2")

dcast(melt(my.df, id.vars=c("ID", "TIME")), ID~variable+TIME)

这使

  ID X_1 X_2 X_3 X_4 X_5 Y_1 Y_2 Y_3 Y_4 Y_5
1  A   1   4   7  10  13  16  19  22  25  28
2  B   2   5   8  11  14  17  20  23  26  29
3  C   3   6   9  12  15  18  21  24  27  30

根据评论编辑:

数据框

num.id = 10 
num.time=10 
my.df <- data.frame(ID=rep(LETTERS[1:num.id], num.time), 
                    TIME=rep(1:num.time, each=num.id), 
                    X=1:(num.id*num.time), 
                    Y=(num.id*num.time)+1:(2*length(1:(num.id*num.time))))

给出不同的结果(所有条目都是 2),因为ID/TIME组合不表示唯一的行。实际上,每个ID/TIME组合都有两行。reshape2假设每个可能的变量组合都有一个值,如果有多个条目,将应用汇总函数来创建单个变量。这就是为什么有警告

Aggregation function missing: defaulting to length

如果您添加另一个打破冗余的变量,您可以获得一些有用的东西。

my.df$cycle <- rep(1:2, each=num.id*num.time)
dcast(melt(my.df, id.vars=c("cycle", "ID", "TIME")), cycle+ID~variable+TIME)

这是因为cycle//现在唯一地定义了ID.timemy.df

于 2012-05-14T19:17:43.563 回答
23
   reshape(my.df,
           idvar = "ID",
           timevar = "TIME",
           direction = "wide")

  ID X.1 Y.1 X.2 Y.2 X.3 Y.3 X.4 Y.4 X.5 Y.5
1  A   1  16   4  19   7  22  10  25  13  28
2  B   2  17   5  20   8  23  11  26  14  29
3  C   3  18   6  21   9  24  12  27  15  30
于 2012-05-14T19:18:55.373 回答
15

使用data.table_1.9.5,这可以在没有 的情况下完成,melt因为它可以处理多个value.var列。您可以从安装它here

 library(data.table)
 dcast(setDT(my.df), ID~TIME, value.var=c('X', 'Y'))
 #   ID 1_X 2_X 3_X 4_X 5_X 1_Y 2_Y 3_Y 4_Y 5_Y
 #1:  A   1   4   7  10  13  16  19  22  25  28
 #2:  B   2   5   8  11  14  17  20  23  26  29
 #3:  C   3   6   9  12  15  18  21  24  27  30
于 2015-03-19T15:37:32.253 回答
7

注意 - 2019 年 9 月:在tidyr中,gather()+spread()方法(在此答案中描述)或多或少被pivot_wider()方法(在 ` this newer tidyr answer中描述)取代。有关过渡的当前信息,请参阅旋转小插图


这是tidyr包的解决方案,它基本上取代了reshapereshape2。与这两个包一样,它的策略是首先使数据集更长,然后更宽。

library(magrittr); requireNamespace("tidyr"); requireNamespace("dplyr")
my.df %>%
  tidyr::gather(key=variable, value=value, c(X, Y)) %>%   # Make it even longer.
  dplyr::mutate(                                          # Create the spread key.
    time_by_variable   = paste0(variable, "_", TIME)
  ) %>%
  dplyr::select(ID, time_by_variable, value) %>%          # Retain these three.
  tidyr::spread(key=time_by_variable, value=value)        # Spread/widen.

调用后tidyr::gather(),中间数据集为:

ID TIME variable value
1   A    1        X     1
2   B    1        X     2
3   C    1        X     3
...
28  A    5        Y    28
29  B    5        Y    29
30  C    5        Y    30

最终结果是:

  ID X_1 X_2 X_3 X_4 X_5 Y_1 Y_2 Y_3 Y_4 Y_5
1  A   1   4   7  10  13  16  19  22  25  28
2  B   2   5   8  11  14  17  20  23  26  29
3  C   3   6   9  12  15  18  21  24  27  30

tidyr::unite()是@JWilliman 建议的替代方案。当参数为 true(这是默认值)时,这在功能上等同于上面的dplyr::mutate()anddplyr::select()组合。remove

如果您不习惯这种类型的操作,这tidyr::unite()可能是一个小障碍,因为它是您必须学习和记住的另一个功能。但是,它的好处包括 (a) 更简洁的代码(,四行替换为一)和 (b) 重复变量名的位置更少(,您不必在dplyr::select()子句中重复/修改变量)。

my.df %>%
  tidyr::gather(key=variable, value=value, c(X, Y)) %>%           # Make it even longer.
  tidyr::unite("time_by_variable", variable, TIME, remove=T) %>%  # Create the spread key `time_by_variable` while simultaneously dropping `variable` and `TIME`.
  tidyr::spread(key=time_by_variable, value=value)                # Spread/widen.
于 2016-12-13T16:42:05.367 回答
5

pivot_wider()功能是tidyr的第二代方法(在 tidyr 1.0.0 中发布)。

library(magrittr); requireNamespace("tidyr");

my.df %>%
  tidyr::pivot_wider(
    names_from  = c(TIME), # Can accommodate more variables, if needed.
    values_from = c(X, Y)
  )

结果

# A tibble: 3 x 11
  ID      X_1   X_2   X_3   X_4   X_5   Y_1   Y_2   Y_3   Y_4   Y_5
  <fct> <int> <int> <int> <int> <int> <int> <int> <int> <int> <int>
1 A         1     4     7    10    13    16    19    22    25    28
2 B         2     5     8    11    14    17    20    23    26    29
3 C         3     6     9    12    15    18    21    24    27    30

这可能比以前的 tidyr 方法 (使用gather()和的组合spread())更可取。

旋转小插图中描述了更多功能。此示例特别简洁,因为您所需的规范与 和 的默认值相id_cols匹配names_sep

于 2019-09-16T22:44:31.467 回答
0

有点晚了,但以下解决方案也可能有效。

# Input
my.df <- data.frame(ID=rep(c("A","B","C"), 5), TIME=rep(1:5, each=3), X=1:15, Y=16:30)

# Conversion into wide format
my.df %>%
  pivot_wider(ID, c(TIME), 
              values_from = c(X, Y),
              names_glue = '{.value}_{TIME}')

names_glue1-使用此解决方案,您可以通过在选项中将它们设置为来调整列名

names_glue = '{.value}_{TIME}' or
names_glue = '{TIME}_{.value}'

2-同样,使用此解决方案,您可以加入两个或多个字段,例如,c(TIME, DAY, MONTH)然后相应地更新names_glue选项:

names_glue = '{TIME}_{DAY}_{MONTH}_{.value}'
于 2022-01-05T00:38:03.870 回答