简体   繁体   English

提取和减去不同数据帧中的行R的更有效方法

[英]More efficient way to extract and subtract rows R in different dataframes

I am working with this basketball game data with a dataframe games with around 50,000 rows. 我正在将这个篮球比赛数据与大约50,000行的数据框游戏一起使用。 I am trying to compare the stats of each team ( A and B) in each game. 我正在尝试比较每场比赛中每支球队(A和B)的统计数据。

I have another dataframe called teamStats that has around 3000 rows each with teams from every season. 我还有另一个称为teamStats的数据框,该数据框每个季节都有大约3000行,每个团队都有。

So far, I have assembled a code as the following: 到目前为止,我已经汇编了以下代码:

    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
}

Basically, this code searches for the correct teamID for each team A and B, after subsetting the correct season. 基本上,此代码在设置正确的赛季之后,为每个A和B团队搜索正确的teamID。 Since every team for every season is not present in the teamstats, I have filled the missing rows with NA's for now. 由于每个赛季的每支球队都没有出现在teamstats中,因此我现在用NA填补了缺失的行。 The "differences" dataframe is an empty dataframe that will be filled my the differences of stats of team A and B from the for loop. “差异”数据框是一个空的数据框,该填充框将填充我和A队在for循环中的状态差异。

To give you an idea of the data: 让您对数据有所了解:

Games - first 6 rows 游戏-前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 - first 6 rows and only first 6 columns for space - lot of columns with different stats in full dataframe. teamStats-前6行,仅前6列用于空间-整个数据帧中的许多列具有不同的统计信息。 The code finds the correct row for the teamID and then substracts the stat columns such as GWL etc 代码为teamID找到正确的行,然后减去stat列,例如GWL等

              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

And to close this very long post, my question. 而关闭这个很长的帖子,我的问题。 My for loop code works and fills the differences dataframe. 我的for循环代码可以正常工作并填充差异数据帧。 The issue is it takes 20-30 minutes to run this code. 问题是运行此代码需要20到30分钟。 I am not very experienced working with this much data. 我对处理这么多数据不是很有经验。 Is there a technique I don't know? 有我不知道的技术吗? How can I rewrite this code in a more efficient manner? 如何以更有效的方式重写此代码?

One approach is to merge games and teamStats , as an alternative to iterating across rows. 一种方法是合并gamesteamStats ,以替代teamStats迭代。

Some code to replicate your set-up, to create a minimal working example: 一些代码可以复制您的设置,以创建一个最小的工作示例:

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)

We have games with 50k rows and teamStats with 3k rows. 我们的games有5万行,teamStats有3k行。 Now, we collapse teamStats into a tibble by Year and teamID : 现在,我们崩溃teamStats到由tibble YearteamID

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>

Make a small convenience function to calculate differences: 做一个小的便利函数来计算差异:

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]
  }
}

Now, we (1) join (or merge) games with teamStats , (2) calculate differences using the joined dataset, and (3) unnest (or un-collapse) the dataframe. 现在,我们(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

with the result 结果

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

Here's an approach using tidyverse packages that I expect should be much faster than the loop solution in the OP. 这是一种使用tidyverse软件包的方法,我希望它比OP中的循环解决方案要快得多。 The speed (I expect) comes from relying more on database join operations (base merge or dplyr's left_join , for example) to connect the two tables. 速度(我期望)来自于更多地依赖数据库联接操作(例如,基本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)

To test this, I altered the given sample data so that the two tables would relate to each other: 为了测试这一点,我更改了给定的样本数据,以使两个表相互关联:

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"
)

Then I get the following output, which seems to make sense. 然后,我得到以下输出,这似乎很有意义。 (I just realized that the gather/mutate/spread I applied changed the order of the columns; if I have time I might try to use a mutate_if to preserve the order.) (我刚刚意识到,我应用的聚集/变异/扩展改变了列的顺序;如果有时间,我可能会尝试使用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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM