【问题标题】:More efficient way to extract and subtract rows R in different dataframes在不同数据帧中提取和减去行 R 的更有效方法
【发布时间】:2019-08-08 02:20:18
【问题描述】:

我正在处理这个篮球比赛数据,其中包含大约 50,000 行的数据框游戏。我正在尝试比较每支球队(A 和 B)在每场比赛中的统计数据。

我有另一个名为 teamStats 的数据框,其中有大约 3000 行,每行包含每个赛季的球队。

到目前为止,我已经组装了如下代码:

    for (i in 1:nrow(games)) {
  if (length(which(((teamStats$Year == games$Season[i])==1) & (teamStats$teamID == games$teamA[i]))) == 1) {
    selectTeamA <- teamStats[which(((teamStats$Year == games$Season[i])==1) & (teamStats$teamID == games$teamA[i])),4:45]
  } else {
    selectTeamA <- as.numeric(rep(NA, ncol(differences)))
  }
  if (length(which(((teamStats$Year == games$Season[i])==1) & (teamStats$teamID == games$teamB[i]))) == 1) {
    selectTeamB <- teamStats[which(((teamStats$Year == games$Season[i])==1) & (teamStats$teamID == games$teamB[i])),4:45]
  } else {
    selectTeamB <- as.numeric(rep(NA, ncol(differences)))
  }

  differences[i,] <- selectTeamA - selectTeamB
}

基本上,此代码在对正确的赛季进行子集化后,为每个团队 A 和 B 搜索正确的 teamID。由于每个赛季的每支球队都没有出现在球队统计中,所以我现在用 NA 填补了缺失的行。 “差异”数据框是一个空数据框,它将填充我在 for 循环中的团队 A 和 B 的统计数据差异。

为了让您了解数据:

游戏 - 前 6 行

           Season teamA teamB winner scoreA scoreB
108123   2010  1143  1293      A     75     70
108124   2010  1198  1314      B     72     88
108125   2010  1108  1326      B     60    100
108126   2010  1107  1393      B     43     75
108127   2010  1143  1178      A     95     61

teamStats - 前 6 行和仅前 6 列用于空间 - 完整数据框中具有不同统计信息的大量列。代码为teamID找到正确的行,然后减去G W L等stat列

              School Year teamID  G  W  L
1  abilene christian 2018   1101 32 16 16
2          air force 2018   1102 31 12 19
3              akron 2018   1103 32 14 18
4        alabama a&m 2018   1105 31  3 28
5 alabama-birmingham 2018   1412 33 20 13

结束这篇很长的帖子,我的问题。我的 for 循环代码有效并填充了差异数据框。问题是运行此代码需要 20-30 分钟。我对处理这么多数据不是很有经验。有什么我不知道的技术吗?如何以更有效的方式重写此代码?

【问题讨论】:

    标签: r performance dataframe for-loop bigdata


    【解决方案1】:

    一种方法是合并gamesteamStats,作为跨行迭代的替代方法。

    复制您的设置的一些代码,以创建一个最小的工作示例:

    library(dplyr)
    library(purrr)
    
    set.seed(123)
    n_games <- 50000
    n_teams <- 400
    n_years <- 10
    
    games <- data.frame(Season = rep(2005:(2005 + n_years - 1),
                                     each = n_games / n_years)) %>%
      mutate(teamA = sample(1000:(1000 + n_teams - 1), n_games, r = TRUE),
             teamB = map_int(teamA, ~sample(setdiff(1000:(1000 + n_teams - 1), .), 1)),
             scoreA = as.integer(rnorm(n_games, 80, 20)),
             scoreB = as.integer(rnorm(n_games, 80, 20)),
             scoreB = ifelse(scoreA == scoreB, scoreA + sample(c(-1, 1), n_games, r = TRUE), scoreB),
             winner = ifelse(scoreA > scoreB, "A", "B"))
    
    gen_random_string <- function(...) {
      paste(sample(c(letters, " "), rpois(1, 10), r = TRUE), collapse = "")
    }
    
    schools_ids <- data.frame(teamID = 1000:(1000 + n_teams - 1)) %>%
      mutate(School = map_chr(teamID, gen_random_string))
    
    teamStats <- data.frame(Year = rep(2005:(2005 + n_years - 1), each = 300)) %>%
      mutate(teamID = as.vector(replicate(n_years, sample(schools_ids$teamID, 300))),
             G = 32, W = rpois(length(teamID), 16), L = G - W) %>%
      left_join(schools_ids)
    

    我们有 50k 行的 games 和 3k 行的 teamStats。现在,我们通过YearteamIDteamStats 折叠成一个小标题:

    teamStats <- teamStats %>%
      group_by(Year, teamID) %>%
      nest()
    
    # # A tibble: 3,000 x 3
    #     Year teamID data            
    #    <int>  <int> <list>          
    #  1  2005   1321 <tibble [1 x 4]>
    #  2  2005   1192 <tibble [1 x 4]>
    #  3  2005   1074 <tibble [1 x 4]>
    # <snip>
    

    制作一个小的方便函数来计算差异:

    calculate_diff <- function(x, y) {
      if (is.null(x) | is.null(y)) {
        data.frame(G = NA, W = NA, L = NA)
      } else {
        x[, 1:3] - y[, 1:3]
      }
    }
    

    现在,我们 (1) 将 gamesteamStats 连接(或合并),(2) 使用连接的数据集计算差异,以及 (3) unnest(或取消折叠)数据框。

    start <- Sys.time()
    differences <- games %>%
      left_join(teamStats, c("Season" = "Year", "teamA" = "teamID")) %>%
      rename(teamA_stats = data) %>%
      left_join(teamStats, c("Season" = "Year", "teamB" = "teamID")) %>%
      rename(teamB_stats = data) %>%
      mutate(diff = map2(teamA_stats, teamB_stats, calculate_diff)) %>%
      select(Season, teamA, teamB, diff) %>%
      unnest()
    difftime(Sys.time(), start)
    # Time difference of 11.27832 secs
    

    结果

    head(differences)
    #   Season teamA teamB  G  W  L
    # 1   2005  1115  1085 NA NA NA
    # 2   2005  1315  1177 NA NA NA
    # 3   2005  1163  1051  0 -9  9
    # 4   2005  1353  1190  0 -4  4
    # 5   2005  1376  1286 NA NA NA
    # 6   2005  1018  1362  0 -1  1
    

    【讨论】:

      【解决方案2】:

      这是一种使用 tidyverse 包的方法,我希望它应该比 OP 中的循环解决方案快得多。速度(我期望)来自于更多地依赖数据库连接操作(例如 base merge 或 dplyr 的 left_join)来连接两个表。

      library(tidyverse)
      
      # First, use the first few columns from the `games` table, and convert to long format with
      #   a row for each team, and a label column `team_cat` telling us if it's a teamA or teamB.
      stat_differences <- games %>%
        select(row, Season, teamA, teamB)  %>% 
        gather(team_cat, teamID, teamA:teamB) %>%  
      
      # Join to the teamStats table to bring in the team's total stats for that year
        left_join(teamStats %>% select(-row),    # We don't care about this "row"
                  by = c("teamID", "Season" = "Year")) %>%
      
      # Now I want to reverse the stats' sign if it's a teamB. To make this simpler, I gather
      #   all the stats into long format so that we can do the reversal on all of them, and 
      #   then spread back out.
        gather(stat, value, G:L) %>%
        mutate(value = if_else(team_cat == "teamB", value * -1, value * 1)) %>%
        spread(stat, value) %>%
      
      # Get the difference in stats for each row in the original games table.
        group_by(row) %>%
        summarise_at(vars(G:W), sum)
      
      # Finally, add the output to the original table
      output <- games %>% 
        left_join(stat_differences)
      

      为了测试这一点,我更改了给定的示例数据,以便两个表相互关联:

      games <- read.table(header = T, stringsAsFactors = F,
        text = "row           Season teamA teamB winner scoreA scoreB
      108123   2010  1143  1293      A     75     70
      108124   2010  1198  1314      B     72     88
      108125   2010  1108  1326      B     60    100")
      
      teamStats <- read.table(header = T, stringsAsFactors = F,
        text = "row   School Year teamID  G  W  L
      1  abilene_christian 2010   1143 32 16 16
      2          air_force 2010   1293 31 12 19
      3              akron 2010   1314 32 14 18
      4        alabama_a&m 2010   1198 31  3 28
      5 alabama-birmingham 2010   1108 33 20 13
      6       made_up_team 2018   1326 160 150 10    # To confirm getting right season
      7       made_up_team 2010   1326 60 50 10"
      )
      

      然后我得到以下输出,这似乎是有道理的。 (我刚刚意识到我应用的收集/变异/传播改变了列的顺序;如果我有时间,我可能会尝试使用 mutate_if 来保持顺序。)

      > output
           row Season teamA teamB winner scoreA scoreB   G  L   W
      1 108123   2010  1143  1293      A     75     70   1 -3   4
      2 108124   2010  1198  1314      B     72     88  -1 10 -11
      3 108125   2010  1108  1326      B     60    100 -27  3 -30
      

      【讨论】:

      • 请原谅我,但我正在尝试此代码,但我不断收到错误...Error: row must resolve to integer column positions, not a function Error in tbl_vars(y) : object 'stat_differences' not found . 我可能不完全理解您在做什么,但您有什么帮助吗?
      • 在我的示例代码中,我将示例中最左边的未命名列导入为名为“行”的列。我认为错误是指出在您的数据中未找到具有该名称的列。做一个会很有用。您可以在stat_differences &lt;- games %&gt;%select(row, Season, teamA, teamB) %&gt;% 行之间添加mutate(row = row_number()) %&gt;%
      猜你喜欢
      • 1970-01-01
      • 2015-11-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-27
      • 1970-01-01
      相关资源
      最近更新 更多