概述:以下是我的篮球统计网站的R代码的重要部分。在较高的层次上,R代码将阵容统计信息转换为开/关统计数据,其中每一行代表一个唯一的阵容(一个阵容是5个玩家一起玩的组合),其中每一行代表团队的整体统计信息,其中包含特定的(a)场上或(b)场外球员。
我觉得一小段数据对这个可重现的示例不起作用,因此我将数据上传到了Google表格中,并公开了该表格。可复制的代码捕获了CSV数据,但是您可以通过访问url来轻松下载文件。
话虽如此,这是我正在使用的三重嵌套for循环,我已尽力清楚地发表了看法:
# Raw Data Is Lineup Data - Each Row contains stats for a single lineup (combination of 5 basketball players)
sheets_url <- 'https://docs.google.com/spreadsheets/d/1GjDbWfZglwdwMwhNemWpX6uWjhmYfpQe-WNcCNE8EK4/export?format=csv&id=1GjDbWfZglwdwMwhNemWpX6uWjhmYfpQe-WNcCNE8EK4&gid=218640693'
raw.lineup.stats <- httr::content(httr::GET(url = sheets_url))
# Will contain the final output
on.off.stats <- c()
all_seasons <- c('1718', '1819')
# Loop each season
for(i in 1:length(all_seasons)) {
# Filter Lineup Data to include only lineups / stats from this season
this_season <- all_seasons[i]
season.lineup.stats <- raw.lineup.stats %>% dplyr::filter(season == this_season)
all_teams <- unique(season.lineup.stats$teamId)
# Loop each team that appeared in data for this season
for(j in 1:length(all_teams)) {
# Filter Lineup Data again to include only lineups / stats for this team
print(paste0(j, ': ', all_teams[j]))
this_team <- all_teams[j]
team.season.lineup.stats <- season.lineup.stats %>% dplyr::filter(teamId == this_team)
players_on_team <- unique(c(team.season.lineup.stats$onCtId1, team.season.lineup.stats$onCtId2, team.season.lineup.stats$onCtId3, team.season.lineup.stats$onCtId4, team.season.lineup.stats$onCtId5))
# Loop each player on team j
for(k in 1:length(players_on_team)) {
# Identify if player is on-court or off-court - is his ID one of the 5
this_player <- players_on_team[k]
this.players.teams.lineup.stats <- team.season.lineup.stats %>%
dplyr::mutate(isOnOrOff = ifelse(onCtId1 == this_player | onCtId2 == this_player | onCtId3 == this_player
| onCtId4 == this_player | onCtId5 == this_player, 'On Ct', 'Off Ct')) %>%
dplyr::mutate(playerId = this_player) %>%
dplyr::select(playerId, isOnOrOff, everything())
# Convert this team' lineup data into 2 Rows: 1 for team's stats w/ player on-court, and 1 for team's stats w/ player off-court
this.players.onoff.stats <- this.players.teams.lineup.stats %>%
dplyr::group_by(playerId, isOnOrOff) %>%
dplyr::mutate_at(vars(possessions:minutes), .funs = sum) %>%
dplyr::mutate_at(vars(fieldGoalsMade:oppDefensiveReboundPct), .funs = sum) %>%
dplyr::filter(!duplicated(isOnOrOff))
# If player played every minute for his team, nrow(this.players.onoff.stats) == 1. If so, create needed blank off-row
if(nrow(this.players.onoff.stats) == 1) {
off.row <- this.players.onoff.stats %>%
dplyr::ungroup() %>% dplyr::mutate(isOnOrOff = 'Off Ct') %>%
dplyr::mutate_at(vars(possessions:oppPersonalFoulsPer40), .funs = function(x) return(0)) %>%
dplyr::group_by(playerId, isOnOrOff)
this.players.onoff.stats <- this.players.onoff.stats %>% rbind(off.row)
}
# And Rbind to the main container
on.off.stats <- on.off.stats %>% base::rbind(this.players.onoff.stats)
}
}
}
请让我知道该示例是否有任何可复制的内容。数据获取和for循环都对我有效。 代码流(在代码注释中全部列出)是一个较高的级别:
isOnOrOff
,该列指定指定的玩家是否是每个阵容/行中5个玩家之一。在检查代码时遵循注释将希望清楚代码将如何将数据从阵容统计转换为开/关统计。
当前速度/将来的数据:就当前速度而言,上一次运行此循环耗时1.6分钟。使用所有统计信息(我在示例数据中删除了约300列),循环需要3.5分钟。这是大学篮球数据,目前我在建立网站时只使用了约40个团队。很快将更改为约350个团队,并且随着这一更改,每个团队将增加约50%的阵容。总体而言,数据大小将增加约15倍。
鉴于我使用的是for循环,我希望整个数据集至少可以减慢15倍(如果不是更多的话)(15循环,但每个循环在使用较大的整体数据集时可能会变慢)。我还需要每次运行代码两次而不是一次调用此循环。总的来说,我估计未来的运行时间为3.5 *更多的团队15倍* 2次代码运行==〜105分钟。这太长了。我的代码必须每天运行,而这个三重for循环只是更大脚本中的一小部分。
关闭:非常感谢您提供的任何帮助。我知道这不是最简单的for循环矢量化处理,因此我计划奖励这篇文章以及任何需要的超级有用答案。
编辑:关于我的方法的快速分享想法。我觉得我必须使用这种嵌套的for-loop方法,因为非常重要的group_by必须仅在团队的阵容统计上完成。我不在乎球员是否在场外,如果阵容是完全不同的球队/这个球员甚至没有参加过大学篮球的赛季。
编辑2:如果我可以简单地在j
季节和i
团队中同时在j
for循环内运行代码( i
的每个j
团队,确定该团队中的球员,循环该团队中的球员,计算每个球员的开/关统计数据,完成),这很可能会完成工作,对吧?>
答案 0 :(得分:1)
通过利用gather
和group_by
的数据透视/聚合操作,可以大大提高速度。
从raw.lineup.stats
开始,这是一个通行证,至少在粗略的行程中,它可以带给您大部分的帮助。请参阅下面的注释。
library(tidyverse)
all_seasons <- c('1718', '1819')
# make a list of unique players per team, per season
players <- raw.lineup.stats %>%
filter(season %in% all_seasons) %>%
gather(position, player, starts_with("onCtId")) %>%
select(season, teamId, player) %>%
group_by(season, teamId) %>%
distinct(player, .keep_all = TRUE) %>%
ungroup()
# cartesian join with the full df
# use lineupId to determine on/off court (on_ct)
# group_by and aggregate, then use distinct to drop duplicate rows
on_off <- inner_join(
players, raw.lineup.stats,
by = c("season" = "season", "teamId" = "teamId")
) %>%
mutate(on_ct = stringr::str_detect(lineupId, player)) %>%
group_by(season, teamId, player, on_ct) %>%
mutate_at(vars(possessions:minutes, fieldGoalsMade:oppDefensiveReboundPct),
list(~sum)) %>%
ungroup() %>%
distinct(player, on_ct, .keep_all = TRUE)
下面是运行代码与更新代码的一些测试比较:
# new code
> on_off[on_off$teamId == "WVU" & on_off$season == "1819",
+ c("player", "on_ct", "possessions", "minutes")] %>%
arrange(player)
player on_ct possessions minutes
1 AndrewGordon4009 TRUE 86.5 46.133333
2 AndrewGordon4009 FALSE 689.0 374.650000
3 BrandonKnappercbd1 TRUE 225.5 123.233333
4 BrandonKnappercbd1 FALSE 550.0 297.550000
5 ChaseHarler8a7e TRUE 369.5 201.900000
6 ChaseHarler8a7e FALSE 406.0 218.883333
...
# old code
> on.off.stats[on.off.stats$teamId == "WVU" & on.off.stats$season == "1819",
c("playerId", "isOnOrOff", "possessions", "minutes")] %>%
arrange(playerId)
playerId isOnOrOff possessions minutes
1 AndrewGordon4009 On Ct 86.5 46.133333
2 AndrewGordon4009 Off Ct 689.0 374.650000
3 BrandonKnappercbd1 On Ct 225.5 123.233333
4 BrandonKnappercbd1 Off Ct 550.0 297.550000
5 ChaseHarler8a7e On Ct 369.5 201.900000
6 ChaseHarler8a7e Off Ct 406.0 218.883333
...
注意:
magrittr
管道,因为我认为它有助于解决问题(并且因为我认为很多tidyverse函数确实很方便),但是如果您愿意,可以加快速度转换为基数R。JamesBolden043b
,WVU
仅在1718
赛季为球队raw.lineup.stats
效力,但是您的on.off.stats
最终输出是他在{{1 }}。我也很确定您的1819
和summarise
命令没有完全给您您想要的东西。 mutate
。 (当我查看数据时,这对我来说更有意义,但当然是您的电话。)我认为剩下的就是语法调整和错误查找。此代码更新背后的主要直觉应该可以助您一臂之力。另一项调整:在球员有100%的情况下在场上的情况下,您需要添加缺少的行-但您也不需要for循环。