r - 有没有更快的方法通过比较 R 中第 i 行和第 i-1 行的 4 个其他向量来创建新向量?
问题描述
想象一下,您有一个客户数据集,其中包含他们的购买历史。
数据按客户和他们的活动(即购买)日期订购
目标是计算他们的购买频率,但速度很快
Data <- tibble(Customer = c("Person A", "Person A", "Person A", "Person A", "Person A", "Person A","Person B", "Person C","Person C"),
First_Activity_Date = c(1,1,1,1,1,1,1,1,1), # imagine these numbers as dates
Activity_Date = c(1,2,3,4,5,6,1,1,2),
Last_Activity_Date =c(6,6,6,6,6,6,1,2,2)
)
View(Data)
tic()
h <- vector( "integer", length = 9)
f <- function(x, y, z, q){
for( i in 1:length(x)){
if ( identical(z[i],y[i])) { h[i] <- 1 }
else if ( identical(x[i],x[i-1]) && (z[i]<=q[i])) { h[i] <- (h[i-1]+1) }
}
return(h)
}
Data <- mutate(Data, Frequency = f(Customer, First_Activity_Date,
Activity_Date, Last_Activity_Date) )
View(Data)
toc()
#Data <- select( Data, Customer, First_Activity_Date, Activity_Date, Last_Activity_Date)
#remove(h)
#remove(f)
它适用于填充数字的小型数据集,但如果行号超过 50K 并填充日期,则需要大约 2 分钟。
有没有办法向量化这个函数/计算?
解决方案
让我们建立一个替代解决方案
f1 <- function(x, y, z, q) {
使用传递给函数的参数在函数内部分配结果向量
h <- integer(length(x)) # allocate the result inside the function
您的循环由可以“矢量化”的部分组成(一个函数调用,而不是循环的每次迭代的函数调用)。编写矢量化版本
tst_1 <- z == y # 'hoist' outside loop as vectorized comparison
h[tst_1] <- 1L # update h; '1L': integer, not '1': numeric
条件的else
部分有一个错误 when i == 1
,因为人们试图与x[1]
不存在的进行比较x[0]
。假设我们从不输入条件 for i == 1
,所以向量化版本是
tst_2 <- !tst_1 & c(FALSE, tail(x, -1) == head(x, -1)) & z <= q
实现更新的最直接的方法h
是一个简单的循环,如
for (i in which(tst_2))
h[i] <- h[i - 1] + 1L
最后返回结果
h
}
稍作调整的完整功能是
f1 <- function(x, y, z, q) {
h <- integer(length(x)) # allocate the result inside the function
## if (...)
h[z == y] <- 1L
## else if (...)
tst <- !h & c(FALSE, x[-1] == x[-length(x)]) & z <= q
for (i in which(tst))
h[i] <- h[i - 1] + 1L
h
}
通过关注剩余的循环可以进一步提高性能for()
,但也许这已经让您获得所需的性能,而不会太神秘?
还可以更清晰地分离选择相关事件的“过滤”操作
keep <- (y >= z) & (z <= q)
x0 <- x[keep]
从对每个组的操作过程中。在这里,您正在创建一个从 1 到组成员数的分组序列。几种方法是
h0 <- ave(seq_along(x0), x0, FUN=seq_along)
或者
grp_size = rle(x0)$lengths
offset = rep(cumsum(c(0L, grp_size[-length(grp_size)])), grp_size)
h0 <- seq_len(sum(grp_size)) - offset
或者
grp_size = tabulate(match(x0, unique(x0)))
offset = rep(cumsum(c(0L, grp_size[-length(grp_size)])), grp_size)
h0 <- seq_len(sum(grp_size)) - offset
这个问题的其他解决方案可以在 StackOverflow 的其他地方找到。最后一步是创建返回值
h <- integer(length(x))
h[keep] <- h0
h
Data
是一个小标题,所以也许你对 dplyr 很熟悉。以可理解但不一定有效的方式实现结果的一种方法是
d0 <- Data %>%
filter(
Activity_Date >= First_Activity_Date,
Activity_Date <= Last_Activity_Date
) %>%
group_by(Customer) %>%
mutate(Frequency = seq_along(Customer))
left_join(Data, d0)
推荐阅读
- javascript - 我为机器人发出了一个命令,以显示他正在“服务”的所有成员、文本频道和服务器,但它返回的值为 0
- java - 使用 Gson 根据嵌套 JSON 对象的父值填充空值
- css - CSS Hover 兄弟姐妹,但不是一个
- python - 使用子进程避免 GIL
- java - Kafka Listener - 在 Spring Boot 中配置拦截器?
- c - 比较用户在 C 中给出的三个数字
- assembly - 访问 MASM 中的位
- python - 尝试按月对数据框进行排序时重新索引导致 NaN 值
- typescript - 我使用的是哪个版本的打字稿,全局的还是本地的?
- css - css div到完整的视口高度并且没有滚动条