Remodelación de múltiples conjuntos de columnas de medición (formato ancho) en columnas individuales (formato largo)

Tengo un dataframe en un formato amplio, con mediciones repetidas tomadas dentro de diferentes intervalos de fechas. En mi ejemplo, hay tres períodos diferentes, todos con sus valores correspondientes. Por ejemplo, la primera medición ( Value1 ) se midió en el período de DateRange1Start a 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 

Estoy buscando reformar los datos a un formato largo para que las columnas DateRangeXStart y DateRangeXEnd estén agrupadas. Por lo tanto, lo que era 1 fila en la tabla original se convierte en 3 filas en la nueva tabla:

 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 

Sé que debe haber una manera de hacer esto con reshape2 / melt / tidyr / tidyr , pero parece que no puedo resolver cómo mapear los múltiples conjuntos de variables de medida en conjuntos individuales de columnas de valores de esta manera particular.

 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 

(Agregó los v.names según la sugerencia de Josh).

La función de melt data.table puede fundirse en múltiples columnas. Usando eso, podemos simplemente hacer:

 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 

Alternativamente, también puede hacer referencia a los tres conjuntos de columnas de medidas por la posición de la columna:

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

Aquí hay un acercamiento al problema usando tidyr . Este es un caso de uso interesante para su función extract_numeric() , que utilicé para sacar el grupo de los nombres de las columnas

 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 

Dos opciones adicionales (con un dataframe de ejemplo con más de una fila para mostrar mejor el funcionamiento del código):

1) con 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) 

lo que da:

  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) con el 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) con el 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] 

Si también desea una columna de grupo / tiempo, puede adaptar los enfoques anteriores a:

1) con 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) 

lo que da:

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

Datos 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 el reciclaje:

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

No necesitas nada lujoso; las funciones R base funcionarán.

 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)