将多组测量列(宽格式)整形为单列(长格式)

daj*_*daj 36 r reshape r-faq reshape2 tidyr

我有一个宽格式的数据帧,在不同的日期范围内重复测量.在我的例子中,有三个不同的时期,都有相应的值.例如,第一测量(Value1)是在测量期间从DateRange1StartDateRange1End:

ID DateRange1Start DateRange1End Value1 DateRange2Start DateRange2End Value2 DateRange3Start DateRange3End Value3
1 1/1/90 3/1/90 4.4 4/5/91 6/7/91 6.2 5/5/95 6/6/96 3.3 
Run Code Online (Sandbox Code Playgroud)

我希望将数据重新整形为长格式,以便将DateRangeXStart和DateRangeXEnd列分组.因此,原始表中的1行在新表中变为3行:

ID DateRangeStart DateRangeEnd Value
1 1/1/90 3/1/90 4.4
1 4/5/91 6/7/91 6.2
1 5/5/95 6/6/96 3.3
Run Code Online (Sandbox Code Playgroud)

我知道必须有一种方法可以用reshape2/ melt/ recast/ 来做到这一点tidyr,但我似乎无法弄清楚如何以这种特殊方式将多组度量变量映射到单个值列集.

42-*_*42- 32

reshape(dat, idvar="ID", direction="long", 
             varying=list(Start=c(2,5,8), End=c(3,6,9), Value=c(4,7,10)),
             v.names = c("DateRangeStart", "DateRangeEnd", "Value") )
#-------------
    ID time DateRangeStart DateRangeEnd Value
1.1  1    1          1/1/90        3/1/90    4.4
1.2  1    2          4/5/91        6/7/91    6.2
1.3  1    3          5/5/95        6/6/96    3.3
Run Code Online (Sandbox Code Playgroud)

(根据Josh的建议添加了v.names.)

  • +1用于炫耀`vary =`参数的力量.接下来,`v.names`参数也可以说明这些列名,如下所示:`v.names = c("DateRangeStart","DateRangeEnd","Value")` (10认同)

Aru*_*run 25

data.tablemelt功能可以融化成多列.使用它,我们可以简单地做:

require(data.table)
melt(setDT(dat), id=1L,
     measure=patterns("Start$", "End$", "^Value"), 
     value.name=c("DateRangeStart", "DateRangeEnd", "Value"))

#    ID variable DateRangeStart DateRangeEnd Value
# 1:  1        1         1/1/90       3/1/90   4.4
# 2:  1        2         4/5/91       6/7/91   6.2
# 3:  1        3         5/5/95       6/6/96   3.3
Run Code Online (Sandbox Code Playgroud)

或者,您也可以按列位置引用三组度量列:

melt(setDT(dat), id = 1L, 
     measure = list(c(2,5,8), c(3,6,9), c(4,7,10)), 
     value.name = c("DateRangeStart", "DateRangeEnd", "Value"))
Run Code Online (Sandbox Code Playgroud)


And*_*ald 18

以下是使用问题的方法tidyr.这是一个有趣的用例,它extract_numeric()用于从列名中提取组

library(dplyr)
library(tidyr)

a <- read.table(textConnection("
ID DateRange1Start DateRange1End Value1 DateRange2Start DateRange2End Value2 DateRange3Start DateRange3End Value3
1 1/1/90 3/1/90 4.4 4/5/91 6/7/91 6.2 5/5/95 6/6/96 3.3 
"),header=TRUE)

a %>%
  gather(variable,value,-ID) %>%
  mutate(group = extract_numeric(variable)) %>%
  mutate(variable =  gsub("\\d","",x = variable)) %>%
  spread(variable,value)

  ID group DateRangeEnd DateRangeStart Value
1  1     1       3/1/90         1/1/90   4.4
2  1     2       6/7/91         4/5/91   6.2
3  1     3       6/6/96         5/5/95   3.3
Run Code Online (Sandbox Code Playgroud)


hpl*_*ger 8

1.0.0版开始pivot_longer(),使用tidyr软件包的功能可以将具有多个value / measure列的宽格式重整为长格式。

这比以前的tidyr策略gather()要好spread()(请参阅@AndrewMacDonald的答案),因为不再删除属性(在下面的示例中,日期保留为日期,数字保留为数字)。

library("tidyr")
library("magrittr")

a <- structure(list(ID = 1L, 
                    DateRange1Start = structure(7305, class = "Date"), 
                    DateRange1End = structure(7307, class = "Date"), 
                    Value1 = 4.4, 
                    DateRange2Start = structure(7793, class = "Date"),
                    DateRange2End = structure(7856, class = "Date"), 
                    Value2 = 6.2, 
                    DateRange3Start = structure(9255, class = "Date"), 
                    DateRange3End = structure(9653, class = "Date"), 
                    Value3 = 3.3),
               row.names = c(NA, -1L), class = c("tbl_df", "tbl", "data.frame"))
Run Code Online (Sandbox Code Playgroud)

pivot_longer()(counterpart pivot_wider():)与相似gather()。但是,它提供了其他功能,例如多个值列。仅使用一个值列,宽数据集的所有同名将进入一个长列,其名称为中names_to。对于多个值列,names_to可能会收到多个新名称。

这是最简单的,如果所有的列名遵循像一个特定的模式Start_1End_1Start_2,等。因此,我改名的第一步列。

(names(a) <- sub("(\\d)(\\w*)", "\\2_\\1", names(a)))
#>  [1] "ID"               "DateRangeStart_1" "DateRangeEnd_1"  
#>  [4] "Value_1"          "DateRangeStart_2" "DateRangeEnd_2"  
#>  [7] "Value_2"          "DateRangeStart_3" "DateRangeEnd_3"  
#> [10] "Value_3"

pivot_longer(a, 
             cols = -ID, 
             names_to = c(".value", "group"),
             # names_prefix = "DateRange",
             names_sep = "_")
#> # A tibble: 3 x 5
#>      ID group DateRangeEnd DateRangeStart Value
#>   <int> <chr> <date>       <date>         <dbl>
#> 1     1 1     1990-01-03   1990-01-01       4.4
#> 2     1 2     1991-07-06   1991-05-04       6.2
#> 3     1 3     1996-06-06   1995-05-05       3.3
Run Code Online (Sandbox Code Playgroud)

或者,可以使用提供更精细控制的枢轴规格来完成整形(请参见下面的链接):

spec <- a %>%
    build_longer_spec(cols = -ID) %>%
    dplyr::transmute(.name = .name,
                     group = readr::parse_number(name),
                     .value = stringr::str_extract(name, "Start|End|Value"))

pivot_longer(a, spec = spec)
Run Code Online (Sandbox Code Playgroud)

reprex软件包(v0.2.1)创建于2019-03-26

另请参阅:https : //tidyr.tidyverse.org/articles/pivot.html

  • 这实际上是对一个稍有不同的问题的答案,即如何避免使用整齐的方法丢失属性。最初接受的答案(使用stats :: reshape`)从来没有这个问题。原始问题显然也没有日期分类变量。重塑函数保留了因子级别和日期类。 (2认同)

Jaa*_*aap 7

另外两个选项(带有多行的示例数据框可以更好地显示代码的工作情况):

1)基础R:

l <- lapply(split.default(d[-1], cumsum(grepl('Start$', names(d)[-1]))),
            setNames, c('DateRangeStart','DateRangeEnd','Value'))
data.frame(ID = d[,1], do.call(rbind, l), row.names = NULL)
Run Code Online (Sandbox Code Playgroud)

这使:

  ID DateRangeStart DateRangeEnd Value
1  1         1/1/90       3/1/90   4.4
2  2         1/2/90       3/2/90   6.1
3  1         4/5/91       6/7/91   6.2
4  2         4/6/91       6/8/91   3.2
5  1         5/5/95       6/6/96   3.3
6  2         5/5/97       6/6/98   1.3
Run Code Online (Sandbox Code Playgroud)

2)与tidyverse:

library(dplyr)
library(purrr)

split.default(d[-1], cumsum(grepl('Start$', names(d)[-1]))) %>%
  map_dfr(~set_names(., c('DateRangeStart','DateRangeEnd','Value'))) %>% 
  bind_cols(ID = rep(d$ID, nrow(.)/nrow(d)), .)
Run Code Online (Sandbox Code Playgroud)

3)使用sjmisc-package:

library(sjmisc)
to_long(d, keys = 'group',
        values = c('DateRangeStart','DateRangeEnd','Value'), 
        c('DateRange1Start','DateRange2Start','DateRange3Start'),
        c('DateRange1End','DateRange2End','DateRange3End'),
        c('Value1','Value2','Value3'))[,-2]
Run Code Online (Sandbox Code Playgroud)

如果您还需要组/时间列,则可以将上述方法调整为:

1)基础R:

l <- lapply(split.default(d[-1], cumsum(grepl('Start$', names(d)[-1]))),
            setNames, c('DateRangeStart','DateRangeEnd','Value'))
data.frame(ID = d[,1],
           group = rep(seq_along(l), each = nrow(d)),
           do.call(rbind, l), row.names = NULL)
Run Code Online (Sandbox Code Playgroud)

这使:

  ID group DateRangeStart DateRangeEnd Value
1  1     1         1/1/90       3/1/90   4.4
2  2     1         1/2/90       3/2/90   6.1
3  1     2         4/5/91       6/7/91   6.2
4  2     2         4/6/91       6/8/91   3.2
5  1     3         5/5/95       6/6/96   3.3
6  2     3         5/5/97       6/6/98   1.3
Run Code Online (Sandbox Code Playgroud)

2)与tidyverse:

split.default(d[-1], cumsum(grepl('Start$', names(d)[-1]))) %>%
  map_dfr(~set_names(., c('DateRangeStart','DateRangeEnd','Value'))) %>% 
  bind_cols(ID = rep(d$ID, nrow(.)/nrow(d)),
            group = rep(1:(nrow(.)/nrow(d)), each = nrow(d)), .)
Run Code Online (Sandbox Code Playgroud)

3)使用sjmisc-package:

library(sjmisc)
to_long(d, keys = 'group', recode.key = TRUE,
        values = c('DateRangeStart','DateRangeEnd','Value'), 
        c('DateRange1Start','DateRange2Start','DateRange3Start'),
        c('DateRange1End','DateRange2End','DateRange3End'),
        c('Value1','Value2','Value3'))
Run Code Online (Sandbox Code Playgroud)

使用数据:

d <- read.table(text = "ID DateRange1Start DateRange1End Value1 DateRange2Start DateRange2End Value2 DateRange3Start DateRange3End Value3
1 1/1/90 3/1/90 4.4 4/5/91 6/7/91 6.2 5/5/95 6/6/96 3.3
2 1/2/90 3/2/90 6.1 4/6/91 6/8/91 3.2 5/5/97 6/6/98 1.3", header = TRUE, stringsAsFactors = FALSE)
Run Code Online (Sandbox Code Playgroud)