提问人:daj 提问时间:9/18/2012 最后编辑:Jaapdaj 更新时间:1/11/2022 访问量:25221
将多组测量列(宽格式)调整为单列(长格式)
Reshaping multiple sets of measurement columns (wide format) into single columns (long format)
问:
我有一个宽格式的数据帧,在不同的日期范围内进行重复测量。在我的示例中,有三个不同的周期,它们都有相应的值。例如,第一次测量()是在从到以下期间测量的: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
答:
你不需要任何花哨的东西;基本函数就可以了。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)
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
(根据 Josh 的建议添加了 v.names。
评论
varying=
v.names
v.names = c("DateRangeStart", "DateRangeEnd", "Value")
以下是使用 .这是它的函数的一个有趣的用例,我用它来从列名中提取组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
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"))
另外两个选项(带有一个包含多行的示例数据帧,以更好地显示代码的工作):
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)
使用回收:
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)]))
从 1.0.0 版开始,使用 tidyr 包的功能可以从具有多个值/度量列的宽格式调整为长格式。pivot_longer()
这优于之前的 tidyr 策略 than(参见 @AndrewMacDonald 的答案),因为属性不再被删除(在下面的示例中,日期仍然是日期,数字仍然是数字)。gather()
spread()
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)
创建于 2019-03-26 由 reprex 软件包 (v0.2.1)
Смотритетакже: https://tidyr.tidyverse.org/articles/pivot.html
评论
stats::reshape
stats::reshape()
names(a) <- sub("(\\d)(\\w*)", "\\2_\\1", names(a))
tidyverse
中的另一种解决方案,它利用 names_pattern
参数来 tidyr::p ivot_longer()。
names_pattern
采用与extract(
) 相同的规范,extract() 是一个包含匹配组 () 的正则表达式。()
作为不需要初步字符串操作的单个命令,这可能比 @hplieninger 在此处的答案有所改进。
溶液
library(tidyverse)
# ...
# Code to generate dataset 'ds'.
# ...
ds %>% pivot_longer(
# Target only those columns names with a numeric index; possibly suffixed by "Start"
# or "End".
cols = matches("^(.*)(\\d+)(Start|End)?$"),
# Break each name into its prefix, index, and suffix.
names_pattern = "^(.*)(\\d+)(Start|End)?$",
# Pivot by index and reassemble the other components.
names_to = c(".value", "group_id", ".value")
)
您可以进一步将代码简化为单行代码,如下所示
pivot_longer(ds, !ID, names_pattern = "^(.*)(\\d+)(Start|End)?$", names_to = c(".value", NA, ".value"))
其中,只需将每一列(但)作为分组变量;并省略组索引 (),如示例输出所示。!ID
ID
NA
group_id
结果
给定一个喜欢你的示例数据集ds
ds <- 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")
)
此解决方案应产生以下结果
# A tibble: 3 x 5
ID group_id DateRangeStart DateRangeEnd Value
<int> <chr> <date> <date> <dbl>
1 1 1 1990-01-01 1990-01-03 4.4
2 1 2 1991-05-04 1991-07-06 6.2
3 1 3 1995-05-05 1996-06-06 3.3
或者对于简化的命令:
# A tibble: 3 x 4
ID DateRangeStart DateRangeEnd Value
<int> <date> <date> <dbl>
1 1 1990-01-01 1990-01-03 4.4
2 1 1991-05-04 1991-07-06 6.2
3 1 1995-05-05 1996-06-06 3.3
评论
pivot_longer(a, cols = -ID, names_to = c('.value', '.value'), names_pattern = "(.+)\\d(.*)")
下一个:将多个值列调整为宽格式
评论
reshape2/melt/recast/tidyr