寻找一种更快的方法来计算 R 中大型时间序列的条件反向累积和
Looking for a faster way to compute conditioned reverse cumulative sums of large time series in R
library(dplyr)
我学了几个月的R。我一直在使用纽约时报 (NY Times Covid Data on Github) 的 Covid-19 数据
作为学习统计规划的测试数据集。
从他们的 Github 存储库中,您可以获得整个美国、州或县的病例数据。无论您查看的是哪一个,每个地点和每个日期的已知病例数和死亡人数都有一个累计总数。在至少有一个病例或死亡之前,一个位置不会出现在数据集中,然后一旦出现,它就会每天永久更新。
我的目标是想出一种方法来对自昨天以来的新案例、自上周以来的新案例等进行计算,并将这些计算作为新列添加到我的数据框中。我找到了一种策略,它适用于较小的数据集,但在处理县级的全国数据时速度很慢。
我将生成一些随机数据作为示例。
set.seed(123)
data <- data.frame(rep(seq(as.Date("2020-01-01"), as.Date("2020-04-30"), 1), 3))
names(data)[1] <- "date"
data$city[1:121] <- "Boston"
data$city[122:242] <- "NYC"
data$city[243:363] <- "Chicago"
data$newcases[1:121] <- round(runif(121, 10, 15))
data$newcases[122:242] <- round(runif(121, 15, 20))
data$newcases[243:363] <- sample(data$newcases[1:242], 121, replace = TRUE)
data <- data %>% group_by(city) %>% mutate(totalcases = cumsum(newcases))
NYTIMES <- as.data.frame(data) %>% select(date, city, totalcases)
slice_sample(NYTIMES, n = 5)
date city totalcases
1 2020-01-09 NYC 152
2 2020-02-13 NYC 759
3 2020-03-10 NYC 1221
4 2020-03-16 Boston 950
5 2020-01-27 Chicago 412
所以我首先尝试对累积和进行逆向工程。我一直没能成功生产出我想要的。
NYTIMES <- NYTIMES %>% group_by(city) %>% mutate(newcases = rev(cumsum(rev(totalcases))))
slice_sample(NYTIMES, n = 5)
# A tibble: 15 x 4
# Groups: city [3]
date city totalcases newcases
<date> <chr> <dbl> <dbl>
1 2020-02-11 Boston 534 81524
2 2020-03-01 Boston 763 69365
3 2020-03-10 Boston 876 62066
4 2020-04-15 Boston 1324 22739
5 2020-04-27 Boston 1480 5996
6 2020-02-07 Chicago 570 99599
7 2020-04-21 Chicago 1690 17604
8 2020-03-04 Chicago 934 80234
9 2020-03-20 Chicago 1196 63351
10 2020-04-08 Chicago 1483 38157
11 2020-02-05 NYC 623 117708
12 2020-03-31 NYC 1588 57384
13 2020-04-29 NYC 2096 4210
14 2020-02-04 NYC 605 118313
15 2020-03-27 NYC 1523 63573
而且我需要更多的灵活性,而不仅仅是弄清楚每天的新病例。许多流行病学模型都是基于将今天的已知病例或死亡与一周前、十天前、三周前或其他情况下的已知病例或死亡进行比较。
我构建了一些工作正常的 for 循环,但代码笨拙且容易出错。因此,我想出了这个策略,将 sapply 与求和函数、括号子设置和二元关系运算符结合使用。来自 Excel 世界,这基本上是 SUMIFS 的近似值。
NYTIMES <- as.data.frame(data) %>% select(date, city, totalcases)
NYTIMES$a_day_ago <- sapply(seq_len(nrow(NYTIMES)), function(x) with(NYTIMES, sum(totalcases[date == (date[x] - 1) & city == city[x]])))
NYTIMES$new_cases <- with(NYTIMES, totalcases - a_day_ago)
n <- 12 #arbitrary number of days ago
NYTIMES$n_days_ago <- sapply(seq_len(nrow(NYTIMES)), function(x) with(NYTIMES, sum(totalcases[date == (date[x] - n) & city == city[x]])))
NYTIMES$active_cases <- with(NYTIMES, totalcases - n_days_ago)
slice_sample(NYTIMES, n = 5)
date city totalcases a_day_ago new_cases n_days_ago active_cases
1 2020-03-21 Chicago 1214 1196 18 1014 200
2 2020-02-17 Boston 603 591 12 463 140
3 2020-04-20 Boston 1390 1375 15 1233 157
4 2020-02-06 Chicago 553 535 18 380 173
5 2020-03-15 Chicago 1116 1099 17 916 200
这很好用,我可以根据这个主题进行各种计算。每天新病例、每周新病例、每月新病例。我可以通过计算今天已知案例与 n 天前已知案例之间的差异来计算已知活跃案例的代理。从中减去死亡人数,您就可以很好地代表给定地区的已知康复病例。将求和函数替换为均值,您可以进行各种滚动平均。添加一些人口数据,你就可以按人均计算。子集对于查看特定区域或大都市区或基于来自其他数据集的 demographic/mobility 变量比较不同位置非常有用。所以我对自己的进步非常满意,尤其是在国家和州一级。
但是,当您开始深入到县级时,由于数据集变得非常庞大,它变得非常慢。您知道仅德克萨斯州就有 254 个县吗?很多时候,每日更新会修改过去的数字。显然,我可以将它安排到 运行 过夜或将数据分成更小的块等。但我最感兴趣的只是了解有效处理大型时间序列数据集的机制。
所以 TLDR;有没有一种计算速度更快的方法可以在具有数百万行的数据集中进行这些计算?
作为次要问题,人们经常提到 data.table 对于非常大的数据集,这对那个包来说是一个很好的应用程序吗?语法是什么样的?我还没有想出如何在不依赖相同的 sapply 技巧的情况下做到这一点,然后它可能不会更快。或者是否有 R 的时间序列包可以很好地完成这种事情,我可以检查其代码以获取指针?
谢谢
如果重要的话,我 运行 在 Windows10 上安装 RStudio 4.0.2。我大部分时间都在与 base R 和 dplyr 争论。
我建议你看一下dplyr中的lag
/lead
函数。它们非常适合这种应用。这些函数从数据集中的上一条或下一条记录(按特定顺序)获取值。
df = data %>%
group_by(city) %>%
mutate(prev_totalcases = lag(totalcases, order_by = date),
daily_cases = totalcases - prev_totalcases)
上面的代码创建了一个新列 prev_totalcases
,它是前一天城市 totalcases
的值。然后它计算每日案例作为当前和之前总案例之间的变化。
为了处理日期,我建议您研究一下 lubridate 包。它具有多种有用的功能,例如将文本转换为日期,从日期中提取年、月和日,然后将它们重新组合成日期。
library(dplyr)
我学了几个月的R。我一直在使用纽约时报 (NY Times Covid Data on Github) 的 Covid-19 数据 作为学习统计规划的测试数据集。
从他们的 Github 存储库中,您可以获得整个美国、州或县的病例数据。无论您查看的是哪一个,每个地点和每个日期的已知病例数和死亡人数都有一个累计总数。在至少有一个病例或死亡之前,一个位置不会出现在数据集中,然后一旦出现,它就会每天永久更新。
我的目标是想出一种方法来对自昨天以来的新案例、自上周以来的新案例等进行计算,并将这些计算作为新列添加到我的数据框中。我找到了一种策略,它适用于较小的数据集,但在处理县级的全国数据时速度很慢。
我将生成一些随机数据作为示例。
set.seed(123)
data <- data.frame(rep(seq(as.Date("2020-01-01"), as.Date("2020-04-30"), 1), 3))
names(data)[1] <- "date"
data$city[1:121] <- "Boston"
data$city[122:242] <- "NYC"
data$city[243:363] <- "Chicago"
data$newcases[1:121] <- round(runif(121, 10, 15))
data$newcases[122:242] <- round(runif(121, 15, 20))
data$newcases[243:363] <- sample(data$newcases[1:242], 121, replace = TRUE)
data <- data %>% group_by(city) %>% mutate(totalcases = cumsum(newcases))
NYTIMES <- as.data.frame(data) %>% select(date, city, totalcases)
slice_sample(NYTIMES, n = 5)
date city totalcases
1 2020-01-09 NYC 152
2 2020-02-13 NYC 759
3 2020-03-10 NYC 1221
4 2020-03-16 Boston 950
5 2020-01-27 Chicago 412
所以我首先尝试对累积和进行逆向工程。我一直没能成功生产出我想要的。
NYTIMES <- NYTIMES %>% group_by(city) %>% mutate(newcases = rev(cumsum(rev(totalcases))))
slice_sample(NYTIMES, n = 5)
# A tibble: 15 x 4
# Groups: city [3]
date city totalcases newcases
<date> <chr> <dbl> <dbl>
1 2020-02-11 Boston 534 81524
2 2020-03-01 Boston 763 69365
3 2020-03-10 Boston 876 62066
4 2020-04-15 Boston 1324 22739
5 2020-04-27 Boston 1480 5996
6 2020-02-07 Chicago 570 99599
7 2020-04-21 Chicago 1690 17604
8 2020-03-04 Chicago 934 80234
9 2020-03-20 Chicago 1196 63351
10 2020-04-08 Chicago 1483 38157
11 2020-02-05 NYC 623 117708
12 2020-03-31 NYC 1588 57384
13 2020-04-29 NYC 2096 4210
14 2020-02-04 NYC 605 118313
15 2020-03-27 NYC 1523 63573
而且我需要更多的灵活性,而不仅仅是弄清楚每天的新病例。许多流行病学模型都是基于将今天的已知病例或死亡与一周前、十天前、三周前或其他情况下的已知病例或死亡进行比较。
我构建了一些工作正常的 for 循环,但代码笨拙且容易出错。因此,我想出了这个策略,将 sapply 与求和函数、括号子设置和二元关系运算符结合使用。来自 Excel 世界,这基本上是 SUMIFS 的近似值。
NYTIMES <- as.data.frame(data) %>% select(date, city, totalcases)
NYTIMES$a_day_ago <- sapply(seq_len(nrow(NYTIMES)), function(x) with(NYTIMES, sum(totalcases[date == (date[x] - 1) & city == city[x]])))
NYTIMES$new_cases <- with(NYTIMES, totalcases - a_day_ago)
n <- 12 #arbitrary number of days ago
NYTIMES$n_days_ago <- sapply(seq_len(nrow(NYTIMES)), function(x) with(NYTIMES, sum(totalcases[date == (date[x] - n) & city == city[x]])))
NYTIMES$active_cases <- with(NYTIMES, totalcases - n_days_ago)
slice_sample(NYTIMES, n = 5)
date city totalcases a_day_ago new_cases n_days_ago active_cases
1 2020-03-21 Chicago 1214 1196 18 1014 200
2 2020-02-17 Boston 603 591 12 463 140
3 2020-04-20 Boston 1390 1375 15 1233 157
4 2020-02-06 Chicago 553 535 18 380 173
5 2020-03-15 Chicago 1116 1099 17 916 200
这很好用,我可以根据这个主题进行各种计算。每天新病例、每周新病例、每月新病例。我可以通过计算今天已知案例与 n 天前已知案例之间的差异来计算已知活跃案例的代理。从中减去死亡人数,您就可以很好地代表给定地区的已知康复病例。将求和函数替换为均值,您可以进行各种滚动平均。添加一些人口数据,你就可以按人均计算。子集对于查看特定区域或大都市区或基于来自其他数据集的 demographic/mobility 变量比较不同位置非常有用。所以我对自己的进步非常满意,尤其是在国家和州一级。
但是,当您开始深入到县级时,由于数据集变得非常庞大,它变得非常慢。您知道仅德克萨斯州就有 254 个县吗?很多时候,每日更新会修改过去的数字。显然,我可以将它安排到 运行 过夜或将数据分成更小的块等。但我最感兴趣的只是了解有效处理大型时间序列数据集的机制。
所以 TLDR;有没有一种计算速度更快的方法可以在具有数百万行的数据集中进行这些计算?
作为次要问题,人们经常提到 data.table 对于非常大的数据集,这对那个包来说是一个很好的应用程序吗?语法是什么样的?我还没有想出如何在不依赖相同的 sapply 技巧的情况下做到这一点,然后它可能不会更快。或者是否有 R 的时间序列包可以很好地完成这种事情,我可以检查其代码以获取指针?
谢谢
如果重要的话,我 运行 在 Windows10 上安装 RStudio 4.0.2。我大部分时间都在与 base R 和 dplyr 争论。
我建议你看一下dplyr中的lag
/lead
函数。它们非常适合这种应用。这些函数从数据集中的上一条或下一条记录(按特定顺序)获取值。
df = data %>%
group_by(city) %>%
mutate(prev_totalcases = lag(totalcases, order_by = date),
daily_cases = totalcases - prev_totalcases)
上面的代码创建了一个新列 prev_totalcases
,它是前一天城市 totalcases
的值。然后它计算每日案例作为当前和之前总案例之间的变化。
为了处理日期,我建议您研究一下 lubridate 包。它具有多种有用的功能,例如将文本转换为日期,从日期中提取年、月和日,然后将它们重新组合成日期。