使用R中的动画条形图可视化NBA球队的获胜次数

首先,一些背景资料。 我叫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")] 。 之后,该表采用以下形式:


队名
太阳队62
热度59
马刺队59
活塞杆54

对于每个季节单独的动画,这就足够了,但是要计算选定时间段内的总胜利数,您需要计算总胜利数。


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,对于其他球队,该指标则较小,具体取决于赛季的成功程度。


在所有添加之后,该表具有以下形式:


队名康文斯年份cumrankvalue_rel
马刺队59592004年30.9516129
马刺队631222005年1个1.0000000
马刺队581802006年20.9729730
马刺队562362007年1个1.0000000

表格中仅显示一个团队来说明累积性。 所有这些操作都完成了,就像名称更改一样,使用了一系列方括号


 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


队名TEAM_color
鹰队#E03A3E
凯尔特人队#007A33
篮网#000000

使用颜色表,您需要再进行一次操作。 因为 当使用绘图因子时,团队的顺序就会改变。 名单上的第一个是“数字”名称的唯一所有者费城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对它们进行分组, fillcolor将负责列的颜色。


真实的列称它并不完全正确。 使用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_manualscale_color_manual更改列的颜色, scale_x_reverse “扩展” X轴注意,我们从先前创建的cols矢量中获取颜色。


theme层指定用于调整图形显示的选项。 此处指示了如何显示轴的标题和标签(无论如何,等号右侧的element_blank都不告诉我们)。 我们沿Y轴删除图例,背景,框架和网格线,并plot.titleplot.subtitleplot.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赛季。


使用参数elementsfirst_seasonlast_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_colorinner_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上的博客“大西洋两岸”

Source: https://habr.com/ru/post/zh-CN458904/


All Articles