寻找一种更快的方法来计算 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 包。它具有多种有用的功能,例如将文本转换为日期,从日期中提取年、月和日,然后将它们重新组合成日期。