首先,一些背景资料。 我叫Vladislav,与R的相识发生在去年8月。 由于应用性,我决定学习一种编程语言。 从小开始,我喜欢保持体育统计。 随着年龄的增长,这种爱好变成了一种以某种方式分析这些数字的愿望,并在数据分析的基础上,提出了可能的巧妙想法。 问题是,近年来,体育运动已被一波数据席卷,数十家公司相互竞争,试图计算,描述并将足球运动员,篮球运动员,棒球运动员在球场上的任何动作推向神经元。 Excel绝对不适合进行分析。 因此,我决定研究R,以便最简单的分析不会花费半天。 在学习过程中,已经增加了对编程的兴趣,但是这已经是歌词了。
我想立即注意到以后我会写的很多东西 已经在辛普森一家 在Habr上的文章“ 我们使用R创建动画直方图”中。相应地,本文是对使用 Medium中的R创建趋势动画条形图的文章的翻译。 因此,为了与上述文章有所不同,我将尝试更全面地描述我在做什么,以及那些不在原始文章中的时刻。 例如,为了填充列,我使用了NBA命令的颜色,而不是标准的ggplot2
调色板,而是在数据处理data.table
包而不是dplyr
使用了颜色。 我已经完成了所有这些工作,所以现在只需写下团队的名称和您需要计算胜利数的年份就足够了。
资料
为了制定时间表,我使用了过去15个赛季中30支NBA球队中每支球队获胜次数的数据。 它们是使用NBA Data Retriever扩展名从stats.nba.com收集的,该扩展名通过使用NBA API生成具有必要统计信息的csv文件。 这是我在Github上的项目的完整细节。
使用的图书馆
library(data.table) library(tidyverse) library(gganimate)
对于数据处理,我使用data.table
(仅因为我之前遇到过此软件包)。 我还下载了一套tidyverse
软件包,而不是单独的ggplot2
以免担心,如果在分析过程中出现某种想法,需要从该集合中另外加载一个软件包。 在这种特殊情况下, ggplot2
,不涉及其他设置包。 好吧, gganimate
使图形运动。
处理数据
首先,您需要对数据进行排序。 基本上,要构建图形,我们需要在具有原始数据的表的79列中选择2列。 您可以先选择必要的列,然后再替换一些值。 我走了第二条路。
data.table
中的表的格式为dt[i, j, by]
,其中by是对元素分组“负责”的。 我将按“团队名称”列进行分组。 而且有一个障碍。 此列显示球队名称:湖人,凯尔特人,热火等。 但是在本报告所述期间(从2004/05赛季开始),几支球队改名:新奥尔良黄蜂队变成新奥尔良鹈鹕队,夏洛特山猫队改回了历史名称夏洛特黄蜂队,西雅图超音速队变成了俄克拉荷马城雷霆队。 这会引起混乱。 以下转换有助于避免这种情况:
table1 <- table[TeamCity == "New Orleans" & TeamName == "Hornets", TeamName := "Pelicans"][ TeamCity == "New Orleans/Oklahoma City" & TeamName == "Hornets", TeamName := "Pelicans"][ TeamName == "Bobcats", TeamName := "Hornets"][ TeamName == "SuperSonics", TeamName := "Thunder"]
在此时间段内,更改很小,但是如果将其扩展,则按TeamName分组将变得非常困难,并且您将需要使用更可靠的列。 在此数据中,这是TeamID。
首先,我们摆脱“额外”信息,仅保留我们需要工作的那些列:
table1 <- table1[ , .(TeamName, WINS)]
在data.table
。()构造替换list
函数。 选择列的更“经典”选项是table1 <- table1[, c("TeamName", "WINS")]
。 之后,该表采用以下形式:
对于每个季节单独的动画,这就足够了,但是要计算选定时间段内的总胜利数,您需要计算总胜利数。
table1 <- table1[, CumWins := cumsum(WINS), by = "TeamName"]
使用cumsum
函数cumsum
我们得到所需的数字。 使用:=代替=允许您向表中添加新列;我不会用相同的CumWins列覆盖它。 by = "TeamName"
按团队名称对数据进行分组,并分别为30个团队中的每个团队by = "TeamName"
累积量。
接下来,我在每个季节开始的年份添加一列。 NBA的季节从10月到5月,所以它分为两个日历年。 在季节的指定中,即开始的年份,即 赛季:图表中的2018年实际上是2018/19赛季。
原始表具有此数据。 SeasonID列以2的形式(季节开始的年份)显示一个数字,例如22004。您可以使用stringr
程序包或基本的R函数删除前两个stringr
,但是我采用了一些不同的方式。 原来,我首先使用此列指示所需的季节,然后删除并再次创建一个包含日期的列。 额外的动作。
我这样做如下:
table1 <- table1[,year := rep(seq(2004, 2018), each = length(unique(table1$TeamName)))]
我很幸运,在选定的时间段内,NBA的球队数量没有变化,所以我只重复了2004年至2018年的数字30次。 同样,如果您不了解历史,则此方法将很不方便,因为每个赛季的球队数量会有所不同,因此最好在清除SeasonID列时使用该选项。
然后添加cumrank列。
table1 <- table1[, cumrank := frank(-CumWins, ties.method = "random"), by = "year"]
它代表每个赛季的获胜次数排名,并将用作X轴值。具有相同胜利数的球队将以什么顺序前进,因此ties.method = "random"
。好吧,所有这些ties.method = "random"
一年。
最后的表转换是添加value_rel
列。
table1 <- table1[, value_rel := CumWins/CumWins[cumrank==1], by = "year"]
该列考虑了每个团队的胜利次数与该年度最高指标的比率。 对于最佳球队,该指标为1,对于其他球队,该指标则较小,具体取决于赛季的成功程度。
在所有添加之后,该表具有以下形式:
表格中仅显示一个团队来说明累积性。 所有这些操作都完成了,就像名称更改一样,使用了一系列方括号
table1 <- table1[ ,.(TeamName, WINS)][ , CumWins := cumsum(WINS), by = "TeamName"][ ,year := rep(seq(2004, 2018), each = length(unique(table1$TeamName)))][ , cumrank := frank(-CumWins, ties.method = "random"), by = "year"][ , value_rel := CumWins/CumWins[cumrank==1], by = "year"]
将列的填充从标准更改为团队的颜色。
您可以立即进行图的构建,但是在我看来,有一个重要点:图中列的颜色。 您可以保留标准的ggplot2
调色板,但这是一个不好的选择。 首先,在我看来她很丑。 其次,这使得很难在图表上找到团队。 对于NBA球迷来说,每支球队都有特定的颜色:波士顿是绿色,芝加哥是红色,萨克拉曼多是紫色,等等。 因此,尽管有很多蓝色和红色,但在填充列中使用命令的颜色有助于更快地识别它。
为此,请创建一个带有命令名称及其主要颜色的表table_color
。 颜色取自teamcolorcodes.com 。
使用颜色表,您需要再进行一次操作。 因为 当使用绘图因子时,团队的顺序就会改变。 名单上的第一个是“数字”名称的唯一所有者费城76,然后根据字母。 因此,我们需要以相同的顺序排列颜色,然后从表中提取包含它们的向量。 我这样做如下:
table_color <- table_color[order(TeamName)] cols <- table_color[, "TEAM_color"]
绘图
我们实际上只构建了一个图表,其中包含所有450场胜利(15季* 30支球队)的胜利指标,然后使用gganimate
软件包中的函数将其“划分”为必要的变量(在我们的情况下为年份)。
gg <- ggplot(table1, aes(cumrank, group = TeamName, fill = as.factor(TeamName), color = as.factor(TeamName))) + geom_tile(aes(y = CumWins/2, height = CumWins, width = 0.7), color = NA, alpha = 0.8)
首先,我们使用ggplot
函数创建图形。 在aes
参数中,指定如何将表中的变量显示在图表上。 我们通过TeamName对它们进行分组, fill
和color
将负责列的颜色。
真实的列称它并不完全正确。 使用geom_tile
我们将图表上的数据“划分”为矩形。 这是此类图表的示例:

可以看出,图形是如何“划分”为正方形的(它们是使用coord_equal()
层从矩形获得的),每列三个。 但是由于参数width
小于1,我们的图块采用了列的形式。
geom_text(aes(y = 0, label = paste(TeamName, " ")), vjust = 0.2, hjust = 1, size = 6) + geom_text(aes(y = CumWins, label = paste0(" ",round(CumWins))), hjust = 0, size = 7) + coord_flip(clip = "off", expand = FALSE) + scale_fill_manual(values = cols) + scale_color_manual(values = cols) + scale_y_continuous(labels = scales::comma) + scale_x_reverse() + guides(color = FALSE, fill = FALSE) +
接下来,我使用geom_text
添加两个签名:团队名称和获胜次数。 coord_flip
交换轴, scale_fill_manual
和scale_color_manual
更改列的颜色, scale_x_reverse
“扩展” X轴注意,我们从先前创建的cols
矢量中获取颜色。
theme
层指定用于调整图形显示的选项。 此处指示了如何显示轴的标题和标签(无论如何,等号右侧的element_blank
都不告诉我们)。 我们沿Y轴删除图例,背景,框架和网格线,并plot.title
, plot.subtitle
, plot.caption
参数设置标题,字幕和图表签名的显示选项。 有关所有参数含义的更多详细信息,请参见gglot2。
theme(axis.line=element_blank(), axis.text.x=element_blank(), axis.text.y=element_blank(), axis.ticks=element_blank(), axis.title.x=element_blank(), axis.title.y=element_blank(), legend.position="none", panel.background=element_blank(), panel.border=element_blank(), panel.grid.major=element_blank(), panel.grid.minor=element_blank(), panel.grid.major.x = element_line( size=.1, color="grey" ), panel.grid.minor.x = element_line( size=.1, color="grey" ), plot.title=element_text(size=25, hjust=0.5, face="bold", colour="black", vjust=-1), plot.subtitle = element_text(size = 15), plot.caption =element_text(size=15, hjust=0.5, color="black"), plot.background=element_blank(), plot.margin = margin(2,2, 2, 4, "cm"))
创建动画
我不会再使用transition_states
函数,这部分与我先前在Habré上发表的文章相同。 对于labs
它将创建图表的标题,副标题和签名。 使用{closest_state}
可以让您在图表上显示每个特定的年份,这些图表我们目前正在查看。
anim <- gg + transition_states(year, transition_length = 4, state_length = 1) + view_follow(fixed_x = TRUE) + labs(title = "Cumulative Wins by teams in seasons", subtitle = "Season: {closest_state}", caption = "Telegram: @NBAatlantic, Twitter: @vshufiskiy\n Data sourse: stats.nba.com")
nba_cumulative_wins
函数用于创建图表。
如果您需要多次使用代码,编写函数可以简化并加快获得结果的过程。 通常,R中的函数具有以下形式:
_ <- function( ) { _ }
首先,您需要了解要使用该函数更改哪些参数,其参数将取决于此。 第一个参数是正在输入的数据表的名称。 这样,您可以在需要时重命名它,而无需更改函数本身中的任何内容。 我还希望图表上显示任意数量的命令:从一个(毫无意义)到30个(根本不再存在)。 我还希望能够考虑我拥有数据的那15年内的任何时间段。 所有这些都是通过以下函数形式实现的:
nba_cumulative_wins <- function(table, elements, first_season, last_season){ ... }
其中table
是具有输入数据的表的名称,
elements
应该在图表上显示的团队的名称
first_season
要在图表上显示的第一个季节
last_season
图表上要显示的最后一个季节。
如果参数经常与特定值一起使用,则可以默认设置它。 然后,如果在函数的参数中将其省略,则将替换该值。 例如,如果您注册
nba_cumulative_wins <- function(table, elements, first_season, last_season = 2018)
除非另有说明,否则时间表将一直持续到2018/19赛季。
使用参数elements
, first_season
, last_season
使用elements
参数,我们可以指定要在图表上看到的球队的名称。 当有2或3个这样的球队时,这非常方便,但是如果要显示整个联赛,我们将必须在方括号中写上elements = c()
和所有30支球队的名称。
因此,我决定将elements
参数的输入值“拆分”为几组。
nba_cumulative_wins
函数可以为单个团队,部门,会议或整个NBA构建图形。 为此,我使用了以下构造:
select_teams <- unique(table1$TeamName) select_div <- unique(table1$Division) select_conf <- unique(table1$Conference) select_nba <- "NBA" table1 <- if(elements %in% select_teams){ table1[TeamName %in% elements] } else if (elements %in% select_div){ table1[Division %in% elements] } else if(elements %in% select_conf){ table1[Conference %in% elements] } else if(elements == "NBA"){ table1 } else { NULL }
select_
符号向量包含所有30支球队,6个分区,2个会议和NBA的名称, unique
功能仅留下一个唯一名称,而不是15(按数据年限)。
然后,使用if...else
,验证输入的elements
参数属于一个类( %in%
用于确定元素是否属于向量),并相应地修改数据表。 现在,如果我想看看西南分区的比赛结果
elements = c("Mavericks", "Spurs", "Rockets", "Grillies", "Pelicans")
只需输入
elements = "Southwest"
,它更快,更方便。
由于可以选择季节,带日期的工作也会改变。 在开始时,添加以下行:
table1 <- table1[SeasonID >= as.numeric(paste(2, first_season, sep = "")) & SeasonID <= as.numeric(paste(2, last_season, sep = ""))]
因此,我只在表中保留那些属于我们选择的时间间隔的行。 创建year
列的代码也会更改。 现在看起来像这样:
table1 <- table1[ ,year := rep(seq(first_season, last_season), each = length(unique(table1$TeamName)))]
与元素分组有关,获得所需颜色的过程很复杂。 事实是在表table_color
只有命令的名称。 因此,我们需要“收缩”我们的宫缩。 为此,请再次使用if...else
构造。
elements1 <- if (elements == "NBA"){ c("Hawks", "Celtics", "Nets", "Hornets", "Bulls", "Cavaliers", "Mavericks", "Nuggets", "Pistons", "Warriors", "Rockets", "Pacers", "Clippers", "Lakers", "Grizzlies", "Heat", "Bucks", "Timberwolves", "Pelicans", "Knicks", "Thunder", "Magic", "76ers", "Suns", "Trail Blazers","Kings", "Spurs", "Raptors", "Jazz", "Wizards") } else if (elements == "West") { c("Mavericks","Nuggets", "Warriors", "Rockets", "Clippers", "Lakers", "Grizzlies","Timberwolves", "Pelicans", "Thunder", "Suns", "Trail Blazers","Kings", "Spurs", "Jazz") } else if (elements == "East") { c("Hawks", "Celtics", "Nets", "Hornets", "Bulls", "Cavaliers","Pistons", "Pacers", "Heat", "Bucks", "Knicks", "Magic", "76ers", "Raptors", "Wizards") } else if (elements == "Pacific") { c("Warriors", "Clippers", "Lakers", "Suns", "Kings") } else if (elements == "Southeast") { c("Magic", "Hornets", "Heat", "Hawks", "Wizards") } else if (elements == "Southwest") { c("Mavericks", "Grizzlies", "Pelicans", "Rockets", "Spurs") } else if (elements == "Central") { c("Bucks", "Pacers", "Pistons", "Bulls", "Cavaliers") } else if (elements == "Atlantic") { c("Knicks", "Nets", "Celtics", "Raptors", "76ers") } else if (elements == "Northwest") { c("Nuggets", "Trail Blazers", "Jazz", "Thunder", "Suns") } else { elements }
接下来,使用所需命令的名称创建一个表,使用dplyr
包中的inner_join
函数将该表连接至table_color
。 inner_join
仅包括在两个表中都匹配的情况。
table_elements1 <- data.table(TeamName = elements1) table_color <- table_color[order(TeamName)] inner_table_color <- inner_join(table_color, table_elements1) cols <- inner_table_color[, "TEAM_color"]
该功能更改标题和副标题的拼写。 他们看起来像这样:
anim <- gg + transition_states(year, transition_length = 4, state_length = 1) + view_follow(fixed_x = TRUE) + labs(title = paste("Cumulative Wins by teams in seasons", first_season, "-", last_season, sep = " "), subtitle = paste(if (elements %in% select_div ){ paste(elements, "Division", sep = " ") } else if (elements %in% select_conf ){ paste("Conference", elements, sep = " ") }, "Season: {closest_state}", sep = " "), caption = "Telegram: @NBAatlantic, Twitter: @vshufiskiy\nData sourse: stats.nba.com")
渲染图
此外,所有这些都可视化了。
animate(anim, nframes = (last_season - first_season + 1) * (length(unique(table1$TeamName)) + 20), fps = 20, width = 1200, height = 1000, renderer = gifski_renderer(paste(elements[1], "cumwins.gif", sep = "_")))
我凭经验选择了nframes
的数字,因此,根据所选命令的数量,速度会增加/减少。
图表

希望我的帖子有趣。 Github项目代码。
如果您对这些可视化的体育部分感兴趣,可以访问我在sports.ru上的博客“大西洋两岸”