我想基于给定的时间间隔将观察从单行每个id扩展到多行每个id:
> dput(df)
structure(list(id = c(123, 456, 789), gender = c(0, 1, 1), yr.start = c(2005,
2010, 2000), yr.last = c(2007, 2012, 2000)), .Names = c("id",
"gender", "yr.start", "yr.last"), class = c("tbl_df", "tbl",
"data.frame"), row.names = c(NA, -3L))
> df
# A tibble: 3 x 4
id gender yr.start yr.last
<dbl> <dbl> <dbl> <dbl>
1 123 0 2005 2007
2 456 1 2010 2012
3 789 1 2000 2000
我希望每年将ID扩展为一行:
> dput(df_out)
structure(list(id = c(123, 123, 123, 456, 456, 456, 789), gender = c(0,
0, 0, 1, 1, 1, 1), yr = c(2005, 2006, 2007, 2010, 2011, 2012,
2000)), .Names = c("id", "gender", "yr"), class = c("tbl_df",
"tbl", "data.frame"), row.names = c(NA, -7L))
> df_out
# A tibble: 7 x 3
id gender yr
<dbl> <dbl> <dbl>
1 123 0 2005
2 123 0 2006
3 123 0 2007
4 456 1 2010
5 456 1 2011
6 456 1 2012
7 789 1 2000
我知道如何融化/重塑,但我不确定如何扩展这些年。 感谢。
答案 0 :(得分:4)
这是一个基础R方法。
# expand years to a list
yearList <- mapply(":", df$yr.start, df$yr.last)
现在,使用此列表计算要为每个ID重复的行数(rep
的第二个参数),然后将其作为向量附加(使用unlist
从列表转换)使用{ {1}}。
cbind
答案 1 :(得分:4)
您可以gather
使用长格式,然后使用 tidyr 通过complete
填写缺失的行。
library(dplyr)
library(tidyr)
df %>%
gather(group, yr, starts_with("yr") ) %>%
group_by(id, gender) %>%
complete(yr = full_seq(yr, period = 1) )
您可以使用select
删除额外的列。
df %>%
gather(group, yr, starts_with("yr") ) %>%
select(-group) %>%
group_by(id, gender) %>%
complete(yr = full_seq(yr, period = 1) )
# A tibble: 8 x 3
# Groups: id, gender [3]
id gender yr
<dbl> <dbl> <dbl>
1 123 0 2005
2 123 0 2006
3 123 0 2007
4 456 1 2010
5 456 1 2011
6 456 1 2012
7 789 1 2000
8 789 1 2000
答案 2 :(得分:3)
这是一个整齐的解决方案
library(tidyverse)
df %>%
group_by(id, gender) %>%
nest() %>%
mutate(data = map(data, ~ seq(.x$yr.start, .x$yr.last))) %>%
unnest() %>%
rename(year = data)
# A tibble: 7 x 3
id gender year
<dbl> <dbl> <int>
1 123 0 2005
2 123 0 2006
3 123 0 2007
4 456 1 2010
5 456 1 2011
6 456 1 2012
7 789 1 2000
答案 3 :(得分:3)
由于OP提到他的生产数据集超过1M行并且他正在对不同的解决方案进行基准测试,因此尝试data.table
版本可能是值得的:
library(data.table) # CRAN version 1.10.4 used
data.table(DF)[, .(yr = yr.start:yr.last), by = .(id, gender)]
返回
id gender yr 1: 123 0 2005 2: 123 0 2006 3: 123 0 2007 4: 456 1 2010 5: 456 1 2011 6: 456 1 2012 7: 789 1 2000
如果有不同的列而不仅仅是gender
,那么进行连接可能会更有效,而不是在分组参数by =
中包含所有这些列:
data.table(DF)[DF[, .(yr = yr.start:yr.last), by = id], on = "id"]
id gender yr.start yr.last yr 1: 123 0 2005 2007 2005 2: 123 0 2005 2007 2006 3: 123 0 2005 2007 2007 4: 456 1 2010 2012 2010 5: 456 1 2010 2012 2011 6: 456 1 2010 2012 2012 7: 789 1 2000 2000 2000
请注意,这两种方法都假设id
在输入数据中是唯一的。
OP有noted他感到惊讶的是,data.table
以上的解决方案比lmo's base R solution慢五倍,显然OP的生产数据集超过1M行。
此外,这个问题吸引了5个不同的答案以及其他建议。因此,在处理速度方面比较解决方案是值得的。
由于生产数据集不可用,并且问题大小等数据的结构对于基准测试非常重要,因此会创建样本数据集。
# parameters
n_rows <- 1E2
yr_range <- 10L
start_yr <- seq(2000L, length.out = 10L, by = 1L)
# create sample data set
set.seed(123L)
library(data.table)
DT <- data.table(id = seq_len(n_rows),
gender = sample(0:1, n_rows, replace = TRUE),
yr.start = sample(start_yr, n_rows, replace = TRUE))
DT[, yr.last := yr.start + sample(0:yr_range, n_rows, replace = TRUE)]
DF <- as.data.frame(DT)
str(DT)
Classes ‘data.table’ and 'data.frame': 100 obs. of 4 variables: $ id : int 1 2 3 4 5 6 7 8 9 10 ... $ gender : int 0 1 0 1 1 0 1 1 1 0 ... $ yr.start: int 2005 2003 2004 2009 2004 2008 2009 2006 2004 2001 ... $ yr.last : int 2007 2013 2010 2014 2008 2017 2013 2009 2005 2002 ... - attr(*, ".internal.selfref")=<externalptr>
对于第一次运行,创建了100行,开始年份可能在2000年到2009年之间变化,而单个id
可以涵盖的年数跨度为0到10年。因此,结果集应该预计有大约100 *(10 + 1)/ 2行。
此外,只包含一个额外的列gender
,尽管the OP has told生产数据可能包含2到10个不变的列。
library(magrittr)
bm <- microbenchmark::microbenchmark(
lmo = {
yearList <- mapply(":", DF$yr.start, DF$yr.last)
res_lmo <- cbind(DF[rep(seq_along(DF$id), lengths(yearList)), c("id", "gender")],
yr=unlist(yearList))
},
hao = {
res_hao <- DF %>%
dplyr::group_by(id, gender) %>%
tidyr::nest() %>%
dplyr::mutate(data = purrr::map(data, ~ seq(.x$yr.start, .x$yr.last))) %>%
tidyr::unnest() %>%
dplyr::rename(yr = data)
},
aosmith = {
res_aosmith <- DF %>%
tidyr::gather(group, yr, dplyr::starts_with("yr") ) %>%
dplyr::select(-group) %>%
dplyr::group_by(id, gender) %>%
tidyr::complete(yr = tidyr::full_seq(yr, period = 1) )
},
jason = {
res_jason <- DF %>%
dplyr::group_by(id, gender) %>%
dplyr::do(data.frame(yr=.$yr.start:.$yr.last))
},
uwe1 = {
res_uwe1 <- DT[, .(yr = yr.start:yr.last), by = .(id, gender)]
},
uwe2 = {
res_uwe2 <- DT[DT[, .(yr = yr.start:yr.last), by = id], on = "id"
][, c("yr.start", "yr.last") := NULL]
},
frank1 = {
res_frank1 <- DT[rep(1:.N, yr.last - yr.start + 1L),
.(id, gender, yr = DT[, unlist(mapply(":", yr.start, yr.last))])]
},
frank2 = {
res_frank2 <- DT[, {
m = mapply(":", yr.start, yr.last); c(.SD[rep(.I, lengths(m))], .(yr = unlist(m)))},
.SDcols=id:gender]
},
times = 3L
)
请注意,对 tidyverse 函数的引用是显式的,以避免由于名称空间混乱而导致名称冲突。
Unit: microseconds expr min lq mean median uq max neval lmo 655.860 692.6740 968.749 729.488 1125.193 1520.899 3 hao 40610.776 41484.1220 41950.184 42357.468 42619.887 42882.307 3 aosmith 319715.984 336006.9255 371176.437 352297.867 396906.664 441515.461 3 jason 77525.784 78197.8795 78697.798 78869.975 79283.804 79697.634 3 uwe1 834.079 870.1375 894.869 906.196 925.264 944.332 3 uwe2 1796.910 1810.8810 1880.482 1824.852 1922.268 2019.684 3 frank1 981.712 1057.4170 1086.680 1133.122 1139.164 1145.205 3 frank2 994.172 1003.6115 1081.016 1013.051 1124.438 1235.825 3
对于给定的100行问题大小,时间清楚地表明dplyr
/ tidyr
解决方案的幅度比基本R或data.table
解决方案慢。
结果基本一致:
all.equal(as.data.table(res_lmo), res_uwe1)
all.equal(res_hao, res_uwe1)
all.equal(res_jason, res_uwe1)
all.equal(res_uwe2, res_uwe1)
all.equal(res_frank1, res_uwe1)
all.equal(res_frank2, res_uwe1)
返回TRUE
,但返回
all.equal(res_aosmith, res_uwe1)
除外
[1] "Incompatible type for column yr: x numeric, y integer"
由于执行时间较长,在对较大的问题规模进行基准测试时会跳过tidyverse
个解决方案。
使用修改后的参数
n_rows <- 1E4
yr_range <- 100L
结果集预计包含大约500'000行。
Unit: milliseconds
expr min lq mean median uq max neval
lmo 425.026101 447.716671 455.85324 470.40724 471.26681 472.12637 3
uwe1 9.555455 9.796163 10.05562 10.03687 10.30571 10.57455 3
uwe2 18.711805 18.992726 19.40454 19.27365 19.75091 20.22817 3
frank1 22.639031 23.129131 23.58424 23.61923 24.05685 24.49447 3
frank2 13.989016 14.124945 14.47987 14.26088 14.72530 15.18973 3
对于给定的问题大小和结构,data.table
解决方案是最快的,而基本R方法的速度要慢一些。在这里,最简洁的解决方案uwe1
也是最快的。
请注意,结果取决于数据的结构,特别是参数n_rows
和yr_range
以及非变化列的数量。如果这些列中的列数多于gender
,那么时序可能会有所不同。
基准测试结果与OP对执行速度的观察结果相矛盾,需要进一步研究。
答案 4 :(得分:1)
在do
中使用dplyr
的另一种方法,但它比基本R方法慢。
df %>%
group_by(id, gender) %>%
do(data.frame(yr=.$yr.start:.$yr.last))
# # A tibble: 7 x 3
# # Groups: id, gender [3]
# id gender yr
# <dbl> <dbl> <int>
# 1 123 0 2005
# 2 123 0 2006
# 3 123 0 2007
# 4 456 1 2010
# 5 456 1 2011
# 6 456 1 2012
# 7 789 1 2000