Перестановка множества наборов измерительных столбцов (широкий формат) в отдельные столбцы (длинный формат)
У меня есть фрейм данных в широком формате, с повторными измерениями, выполненными в разных диапазонах дат. В моем примере есть три разных периода, все с их соответствующими значениями. Например. первое измерение (Value1
) измерялось в период от DateRange1Start
до 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
Я хочу изменить данные в длинном формате, чтобы столбцы 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
Я знаю, что должен быть способ сделать это с помощью reshape2
/melt
/recast
/tidyr
, но я не могу понять, как сопоставить несколько наборов измеряемых переменных в одиночные наборы столбцов значений таким образом.
Ответы
Ответ 1
Изменение формы с широкого на длинный формат с несколькими столбцами значения/показателя возможно с помощью функции pivot_longer()
пакета tidyr начиная с версии 1.0.0.
Это превосходит предыдущую стратегию тидира 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"))
pivot_longer()
(аналог: pivot_wider()
) работает аналогично gather()
.
Тем не менее, он предлагает дополнительные функции, такие как несколько столбцов значений.
При наличии только одного столбца значений все имена широких наборов данных будут помещаться в один длинный столбец с именем, указанным в names_to
.
Для столбцов с несколькими значениями names_to
может получить несколько новых имен.
Это проще всего, если все имена столбцов следуют определенному шаблону, например Start_1
, End_1
, Start_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
В качестве альтернативы, изменение формы может быть выполнено с использованием сводной спецификации, которая обеспечивает более точное управление (см. ссылку ниже):
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)
Created on 2019-03-26 by the reprex package (v0.2.1)
Смотрите также: https://tidyr.tidyverse.org/articles/pivot.html
Ответ 2
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
(добавлено v.names для предложения Джоша.)
Ответ 3
Функция data.table
melt
может расплавиться в несколько столбцов. Используя это, мы можем просто сделать:
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
В качестве альтернативы вы также можете ссылаться на три набора столбцов измерения по позиции столбца:
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"))
Ответ 4
Ниже приведен подход к проблеме с использованием 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
Ответ 5
Два дополнительных параметра (примерный фреймворк с более чем одной строкой, чтобы лучше показать работу кода):
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)
который дает:
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) с 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) с 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]
Если вам также нужен столбец "группа/время", вы можете адаптировать приведенные выше подходы к:
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)
который дает:
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) с 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) с 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'))
Используемые данные:
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)
Ответ 6
Использование утилизации:
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)]))
Ответ 7
Вам ничего не нужно; базовые функции R
.
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)