[英]rowwise operation with dplyr
我正在研究2,3万条记录的R中的大型数据框,其中包含具有开始和停止时间的位置的用户交易。 我的目标是创建一个新的数据框,其中包含每个用户/每个位置连接的时间量。 我们称这是每小时连接一次。
交易可以从8分钟到48小时不等,因此目标数据框将是大约1亿条记录,并且每个月都会增长。
下面的代码显示了最终数据框的开发方式,尽管总代码更复杂。 在英特尔(R)Xeon(R)CPU E5-2630 v3 @ 2.40GHz,16核128GB RAM上运行总代码大约需要9个小时。
library(dplyr)
numsessions<-1000000
startdate <-as.POSIXlt(runif(numsessions,1,365*60*60)*24,origin="2015-1-1")
df.Sessions<-data.frame(userID = round(runif(numsessions,1,500)),
postalcode = round(runif(numsessions,1,100)),
daynr = format(startdate,"%w"),
start =startdate ,
end= startdate + runif(1,1,60*60*10)
)
dfhourly.connected <-df.Sessions %>% rowwise %>% do(data.frame(userID=.$userID,
hourlydate=as.Date(seq(.$start,.$end,by=60*60)),
hournr=format(seq(.$start,.$end,by=60*60),"%H")
)
)
我们希望在16个核心(部分)上并行化此过程以加速该过程。 第一次尝试是使用multidplyr
包。 分区基于daynr
df.hourlyconnected<-df.Sessions %>%
partition(daynr,cluster=init_cluster(6)) %>%
rowwise %>% do(data.frame(userID=.$userID,
hourlydate=as.Date(seq(.$start,.$end,by=60*60)),
hournr=format(seq(.$start,.$end,by=60*60),"%H")
)
) %>% collect()
现在, rowwise
函数似乎需要数据帧作为输入而不是分区。
是否有解决方法来对每个核心的分区执行逐行计算?
有没有人有建议用不同的R包和方法执行这个计算?
(我认为将此作为答案发布可能会使对未来对有效编码感兴趣的读者受益。)
R是矢量化语言,因此按行操作是最昂贵的操作之一; 特别是如果您正在评估许多函数,调度方法,转换类和创建新数据集。
因此,第一步是减少“ 通过 ”操作。 通过查看您的代码,您似乎正在根据userID
, start
和end
来扩大数据集的大小 - 所有其余操作都可以在后面进行(因此可以进行矢量化)。 另外,两次按行运行seq
(它本身不是一个非常有效的函数)不会增加任何内容。 最后,在POSIXt
类上显式调用seq.POSIXt
将为您节省方法调度的开销。
我不确定如何使用dplyr
有效地执行此dplyr
,因为mutate
无法处理它,并且do
函数(IIRC)总是证明它自身是非常低效的。 因此,让我们尝试一下可以轻松处理此任务的data.table
包
library(data.table)
res <- setDT(df.Sessions)[, seq.POSIXt(start, end, by = 3600), by = .(userID, start, end)]
再次请注意,我将“ 按行 ”操作最小化为单个函数调用,同时避免了方法调度
现在我们已准备好数据集,我们不再需要任何行操作,从现在开始,所有内容都可以进行矢量化。
虽然,矢量化并不是故事的结局。 我们还需要考虑类转换,方法调度等。例如,我们可以使用不同的Date
类函数或使用format
或甚至substr
来创建hourlydate
和hournr
。 需要考虑的权衡是,例如, substr
将是最快的,但结果将是一个character
向量而不是Date
- 由您来决定您是否更喜欢速度或质量最终产品。 有时你可以赢得两者,但首先你应该检查你的选择。 让我们基于3种不同的矢量化方法来计算hournr
变量
library(microbenchmark)
set.seed(123)
N <- 1e5
test <- as.POSIXlt(runif(N, 1, 1e5), origin = "1900-01-01")
microbenchmark("format" = format(test, "%H"),
"substr" = substr(test, 12L, 13L),
"data.table::hour" = hour(test))
# Unit: microseconds
# expr min lq mean median uq max neval cld
# format 273874.784 274587.880 282486.6262 275301.78 286573.71 384505.88 100 b
# substr 486545.261 503713.314 529191.1582 514249.91 528172.32 667254.27 100 c
# data.table::hour 5.121 7.681 23.9746 27.84 33.44 55.36 100 a
data.table::hour
是速度和质量的明显赢家(结果是一个整数向量而不是一个字符),同时将你之前的解决方案的速度提高了~x12,000倍 (我甚至没有测试它对你的行实现)。
现在让我们尝试3种不同的data.table::hour
microbenchmark("as.Date" = as.Date(test),
"substr" = substr(test, 1L, 10L),
"data.table::as.IDate" = as.IDate(test))
# Unit: milliseconds
# expr min lq mean median uq max neval cld
# as.Date 19.56285 20.09563 23.77035 20.63049 21.16888 50.04565 100 a
# substr 492.61257 508.98049 525.09147 515.58955 525.20586 663.96895 100 b
# data.table::as.IDate 19.91964 20.44250 27.50989 21.34551 31.79939 145.65133 100 a
似乎第一个和第三个选项在速度方面几乎相同,而我更喜欢as.IDate
因为integer
存储模式。
既然我们知道效率和质量在哪里,我们就可以通过运行来完成任务
res[, `:=`(hourlydate = as.IDate(V1), hournr = hour(V1))]
(然后你可以使用res[, yourcolname := NULL]
的类似语法轻松删除不必要的列,我将留给你)
可能有更有效的方法来解决这个问题,但这证明了如何提高代码效率的可行方法。
作为旁注,如果您想进一步调查data.table
语法/功能,这里是一个很好的阅读
https://github.com/Rdatatable/data.table/wiki/Getting-started
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.