【问题标题】:Need help optimizing cumsum like code - sqldf, data.table, non-equi joins需要帮助优化 cumsum 之类的代码 - sqldf、data.table、非 equi 连接
【发布时间】:2018-05-03 21:14:43
【问题描述】:

寻求帮助以优化我的 sqldf 代码,该代码基于非 equi 连接生成汇总的历史统计信息,即数据只能汇总到当前数据行。

重要的是,任何解决方案都能够适用于许多不同的组,例如在 sqldf 示例中按 tourney_name 过滤聚合等。

获取数据:

library(dplyr); library(sqldf); data_list <- list()

for(i in 2000:2018){
    data_list[[i]] <- 
        readr::read_csv(paste0('https://raw.githubusercontent.com/JeffSackmann/tennis_atp/master/atp_matches_',i,'.csv')) %>% 
        as.data.frame}

data <- data.table::rbindlist(data_list)
data <- select(data, tourney_name, tourney_date, match_num, winner_id, winner_name, loser_id, loser_name)

system.time(
    data2 <- sqldf("select a.*, 
                   count(b.winner_id) as winner_overall_wins
                   from data a 
                   left join data b 
                   on (a.winner_id = b.winner_id and a.tourney_date > b.tourney_date)
                   group by a.tourney_name, a.tourney_date, a.match_num, a.winner_id
                   order by tourney_date desc, tourney_name, match_num desc",
                   stringsAsFactors = FALSE)
) # takes 16 sec, would like to look for a vectorized solution

head(data2)

我尝试加快代码速度的方法:

  1. For 循环 - 太慢了

  2. Dplyr 完全连接/过滤器 - 内存超过 60gb。

  3. Data.table/cumsum - 无法让代码正常工作。更喜欢非 data.table 方法,但愿意学习通用解决方案

谢谢!

【问题讨论】:

  • 这个问题太长了(对我来说)。一般来说,如果您的问题比一页长,那么您的要求就太多了。将您的问题简化为核心本质。
  • 只是想展示我所有的尝试......我会尽量简化。让我知道现在是否更好。
  • 这是一个开始。如果您有一个小的(!)玩具示例,而不是用最少的列和行来说明您正在尝试做什么,那就更好了。我的猜测是您想要类似于a[b, on = .(p1_id, tourney_date &gt; tourney_date), ...] 的东西,但我不想实际运行您的所有代码来检查,抱歉。并在asking great questions 上查看本指南。
  • 感谢您的帮助...我一口气尝试了加入和聚合,但没有成功。您是否建议先进行联接,然后再进行聚合? data3a[data3b, list(p1_overall_wins2 = sum(p1_won), p1_overall_losses2 = sum(p1_won == 0), p1_overall_ace2 = mean(p1_ace, na.rm = TRUE)), by = list(tourney_name, tourney_date, match_num, p1_id), on = .(p1_id = p1_id2, tourney_date &gt; tourney_date2), allow.cartesian = TRUE]
  • 还非常注重使我的示例可重现,并希望检查大型数据集,因此示例很长。我会试着把它归结为更基本的概念,看看我能不能弄清楚,然后问一个更简单的问题。谢谢!

标签: r dplyr data.table cumsum


【解决方案1】:

OP 已要求优化许多级联 sqldf 语句(在 OP 编辑​​之前)。不幸的是,OP 没有口头解释他实施了哪些聚合。因此,需要进行大量的逆向工程。

无论如何,这是我会做的,使用data.table 来实现相同的结果。执行时间从 OP 的 sqldf 代码的 16 秒下降到 data.table 版本的不到 0.2 秒。

data.table 已编辑示例的版本

OP 已编辑问题以减少 sqldf 语句的数量。现在,只计算一个聚合。

data2 中的新列 winner_overall_wins 是获胜者在实际比赛开始之前赢得的所有比赛的计数。该号码附在获胜者赢得的实际比赛的所有比赛中。 (请注意,这与实际比赛之前赢得的比赛计数不同)。

自 1.9.8 版(2016 年 11 月 25 日在 CRAN 上)以来,data.table 能够进行非等值连接。此外,可以建议fread() 只读取选定的列,从而进一步加快 I/O。

library(data.table)  # v1.11.2

urls <- sprintf(
  "https://raw.githubusercontent.com/JeffSackmann/tennis_atp/master/atp_matches_%i.csv", 
  2000:2018)
selected_cols <- c("tourney_name", "tourney_date", "match_num", 
                   "winner_id", "winner_name", 
                   "loser_id", "loser_name") 

# read only selected columns from files & combine into one data object
matches <- rbindlist(lapply(urls, fread, select = selected_cols))

# non-equi join to compute aggregate, second join to append, order result 
system.time({
  result_nej <- matches[
    unique(matches[matches, on = .(winner_id, tourney_date < tourney_date), 
                   .(winner_overall_wins = .N), by = .EACHI]),
    on = .(winner_id, tourney_date)][
      order(-tourney_date, tourney_name, -match_num)]
})

两个data.table 连接和随后的排序在我的系统上花费了大约 0.15 秒,而 OP 的sqldf 代码的各种运行需要 16 到 19 秒。

可以通过以下方式检索特定玩家的历史记录

p_name <- "Federer"; result_nej[winner_name %like% p_name | loser_id %like% p_name]
                     tourney_name tourney_date match_num winner_id   winner_name loser_id         loser_name winner_overall_wins
   1:             Australian Open     20180115       701    103819 Roger Federer   105227        Marin Cilic                1128
   2:             Australian Open     20180115       602    103819 Roger Federer   111202        Hyeon Chung                1128
   3:             Australian Open     20180115       504    103819 Roger Federer   104607      Tomas Berdych                1128
   4:             Australian Open     20180115       408    103819 Roger Federer   105916   Marton Fucsovics                1128
   5:             Australian Open     20180115       316    103819 Roger Federer   104755    Richard Gasquet                1128
  ---                                                                                                                           
1131:                   Marseille     20000207         3    103819 Roger Federer   102179      Antony Dupuis                   4
1132: Davis Cup WG R1: SUI vs AUS     20000204         2    103819 Roger Federer   102882 Mark Philippoussis                   3
1133:             Australian Open     20000117        90    103819 Roger Federer   102466        Jan Kroslak                   1
1134:             Australian Open     20000117        52    103819 Roger Federer   102021      Michael Chang                   1
1135:                    Adelaide     20000103         2    103819 Roger Federer   102533   Jens Knippschild                   0

使用cumsum()shift() 有另一种更快的解决方案:

system.time({
  # cumumlative operations require ordered data
  setorder(matches, tourney_date, tourney_name, match_num)
  # add tourney id for convenience and conciseness
  matches[, t_id := rleid(tourney_date, tourney_name)]
  # aggregate by player and tourney
  p_t_hist <- matches[, .(winner_won = .N), by = .(winner_id, t_id)]
  # compute cumulative sum for each player and 
  # lag to show only matches of previous tourneys
  tmp <- p_t_hist[order(t_id), 
                  .(t_id, winner_overall_wins = shift(cumsum(winner_won))), 
                  by = winner_id]
  # append new column & order result
  result_css <- matches[tmp, on = .(t_id, winner_id)][order(-t_id)]
})
p_name <- "Federer"; result_css[winner_name %like% p_name | loser_id %like% p_name]

在我的系统上,经过的时间约为 0.05 秒,这比非 equi 连接变体快 3 倍,并且比 OP 的方法快。

【讨论】:

  • 感谢您的工作!我根据其他人的反馈进行了编辑以简化我的问题。我能够将您的 data.table 解决方案用于我的目的,并且效果很好。
  • 对于您的 data.table 解决方案,data.table 是否允许加入 on = .(winner_id, tourney_date &lt; tourney_date, tourney_date &gt; tourney_date - 365) 之类的参数 [例如,为了获得去年的总结胜利]?收到tourney_date + 30 未找到的错误
  • 请尝试在第二个 data.table 中添加一个计算列,例如 tourney_date_m365 := tourney_date - 365 并在连接中使用新列,例如on = .(winner_id, tourney_date &lt; tourney_date, tourney_date &gt; tourney_date_m365
  • 也是从那条路开始的...太好了,感谢您的帮助!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-03
  • 2012-04-09
  • 2014-12-23
  • 2017-04-12
  • 2011-08-13
  • 2015-11-20
相关资源
最近更新 更多