Remodelando vários conjuntos de colunas de medição (formato amplo) em colunas únicas (formato longo)

Eu tenho um dataframe em um formato amplo, com medições repetidas em diferentes períodos. No meu exemplo, existem três períodos diferentes, todos com seus valores correspondentes. Por exemplo, a primeira medição ( Value1 ) foi medida no período entre DateRange1Start e DateRange1End :

 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 

Eu estou olhando para remodelar os dados para um formato longo de tal forma que as colunas DateRangeXStart e DateRangeXEnd são agrupadas. Assim, o que foi 1 linha na tabela original torna-se 3 linhas na nova tabela:

 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 

Eu sei que deve haver uma maneira de fazer isso com reshape2 / melt / recast / tidyr , mas não consigo descobrir como mapear os vários conjuntos de variables ​​de medida em conjuntos únicos de colunas de valores dessa maneira específica.

     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 

    (Adicionado o v.names por sugestão de Josh.)

    A function de melt data.table pode derreter em múltiplas colunas. Usando isso, podemos simplesmente fazer:

     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 

    Como alternativa, você também pode referenciar os três conjuntos de colunas de medida pela posição da coluna:

     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")) 

    Aqui está uma abordagem para o problema usando o tidyr . Este é um caso de uso interessante para sua function extract_numeric() , que eu usei para retirar o grupo dos nomes das colunas

     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 

    Duas opções adicionais (com um exemplo de dataframe com mais de uma linha para melhor mostrar o funcionamento do código):

    1) com base 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) 

    que dá:

      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 

    2) com o 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)), .) 

    3) com o sjmisc :

     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] 

    Se você também quiser uma coluna de grupo / hora, poderá adaptar as abordagens acima para:

    1) com base 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) 

    que dá:

      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 

    2) com o 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)), .) 

    3) com o sjmisc :

     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')) 

    Dados usados:

     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) 

    Usando recyclerview:

     data.frame(ID = d[, 1], DateRangeStart = unlist(d[, -1][, c(TRUE, FALSE, FALSE)]), DateRangeEnd = unlist(d[, -1][, c(FALSE, TRUE, FALSE)]), Value = unlist(d[, -1][, c(FALSE, FALSE, TRUE)])) 

    Você não precisa de nada extravagante; funções base R irá fazer.

     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) b1 <- a[,c(1:4)]; b2 <- a[,c(1,5:7)]; b3 <- a[,c(1,8:10)] colnames(b1) <- colnames(b2) <- colnames(b3) <- c("ID","DateRangeStart","DateRangeEnd","Value") b <- rbind(b1,b2,b3)