Visualisierung der Anzahl der Siege für NBA-Teams mithilfe animierter Balkendiagramme in R.

Zunächst einige Hintergrundinformationen. Mein Name ist Vladislav und meine Bekanntschaft mit R fand im August letzten Jahres statt. Ich habe mich aufgrund der angewandten Natur entschieden, eine Programmiersprache zu lernen. Seit meiner Kindheit habe ich gerne Sportstatistiken geführt. Mit zunehmendem Alter verwandelte sich dieses Hobby in den Wunsch, diese Zahlen irgendwie zu analysieren und auf der Grundlage der Datenanalyse, wenn möglich, kluge Gedanken auszusprechen. Das Problem ist, dass in den letzten Jahren der Sport von einer Datenwelle erfasst wurde, Dutzende von Unternehmen miteinander konkurrieren und versuchen, jede Aktion eines Fußballspielers, Basketballspielers oder Baseballspielers auf dem Platz zu berechnen, zu beschreiben und in das Neuron zu schieben. Und Excel ist nicht kategorisch für die Analyse geeignet. Also habe ich beschlossen, R zu studieren, damit die einfachste Analyse nicht einen halben Tag dauert. Bereits im Verlauf der Studie wurde ein Interesse an der Programmierung als solcher hinzugefügt, aber dies ist bereits der Text.


Ich möchte sofort bemerken, wie viel ich in Zukunft schreiben werde war schon in den simpsons war auf Habr in dem Artikel Wir erstellen animierte Histogramme mit R. Dieser Artikel ist wiederum eine Übersetzung des Artikels Erstellen von trendigen animierten Balkendiagrammen mit R aus Medium. Um mich irgendwie von den obigen Artikeln zu unterscheiden, werde ich versuchen, genauer zu beschreiben, was ich tue, sowie jene Momente, die nicht im Originalartikel enthalten sind. Zum Füllen der Spalten habe ich beispielsweise die Farben der NBA-Befehle verwendet, nicht die Standardpalette ggplot2 , sondern in der Datenverarbeitung das Paket data.table , nicht dplyr . Ich habe all diese Arbeit als Funktion erledigt. Jetzt reicht es aus, einfach den Namen des Teams und die Jahre zu schreiben, für die Sie die Anzahl der Siege zählen müssen.


Daten


Um den Zeitplan zu erstellen, habe ich Daten zur Anzahl der Siege für jedes der 30 NBA-Teams in den letzten 15 Spielzeiten verwendet. Sie wurden von stats.nba.com mit der NBA Data Retriever- Erweiterung gesammelt, die mithilfe der NBA-API CSV-Dateien mit den erforderlichen Statistiken erstellt. Hier sind die vollständigen Details aus meinem Projekt auf Github .


Verwendete Bibliotheken


 library(data.table) library(tidyverse) library(gganimate) 

Für die Datenverarbeitung verwende ich data.table (einfach weil ich dieses Paket zuvor getroffen habe). Ich tidyverse auch eine Reihe von tidyverse Paketen herunter und kein separates ggplot2 um ggplot2 keine Sorgen zu machen, wenn während der Analyse eine Idee auftaucht, die das zusätzliche Laden eines Pakets aus dieser Menge erfordert. In diesem speziellen Fall kann auf ggplot2 verzichtet ggplot2 , andere Set-Pakete sind nicht beteiligt. Nun, gganimate setzt die Grafiken in Bewegung.


Mit Daten arbeiten


Zuerst müssen Sie die Daten in Ordnung bringen. Grundsätzlich benötigen wir zum Erstellen von Diagrammen 2 von 79 Spalten einer Tabelle mit Rohdaten. Sie können zuerst die erforderlichen Spalten auswählen und zuerst einige Werte ersetzen. Ich ging den zweiten Weg.


Die Tabelle in data.table hat die Form dt[i, j, by] , wobei by für die Gruppierung von Elementen "verantwortlich" ist. Ich werde nach der TeamName-Spalte gruppieren. Und da ist ein Haken. In dieser Spalte werden die Namen der Teams angezeigt: Lakers, Celtics, Heat usw. Im Berichtszeitraum (ab der Saison 2004/05) änderten mehrere Teams ihren Namen: New Orleans Hornets wurden zu New Orleans Pelicans, Charlotte Bobcats gab den historischen Namen Charlotte Hornets zurück und Seattle Supersonics wurde Oklahoma City Thunder. Dies kann zu Verwirrung führen. Die folgenden Konvertierungen helfen, dies zu vermeiden:


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

In diesem Zeitraum sind die Änderungen minimal. Wenn Sie sie jedoch erweitern, wird es sehr schwierig, sie nach TeamName zu gruppieren, und Sie müssen eine zuverlässigere Spalte verwenden. In diesen Daten ist dies TeamID.


Zunächst werden die "zusätzlichen" Informationen entfernt, und es bleiben nur die Spalten übrig, die wir für die Arbeit benötigen:


 table1 <- table1[ , .(TeamName, WINS)] 

In data.table ersetzt das Konstrukt. () Die data.table . Eine "klassischere" Option zum Auswählen von Spalten ist table1 <- table1[, c("TeamName", "WINS")] . Danach hat die Tabelle die folgende Form:


TeamnameGEWINNT
Sonnen62
Hitze59
Sporen59
Kolben54

Für die Animation für jede Saison ist dies ausreichend. Um jedoch die Gesamtzahl der Siege für einen ausgewählten Zeitraum zu berechnen, müssen Sie die kumulierte Gesamtzahl der Siege berechnen.


table1 <- table1[, CumWins := cumsum(WINS), by = "TeamName"]


Mit der cumsum Funktion erhalten wir die Zahlen, die wir brauchen. Wenn Sie: = anstelle von = verwenden, können Sie der Tabelle eine neue Spalte hinzufügen. Ich überschreibe sie nicht mit derselben CumWins-Spalte. by = "TeamName" gruppiert die Daten nach dem Namen des Teams und der kumulierte Betrag wird für jedes der 30 Teams separat by = "TeamName" .


Als nächstes füge ich eine Spalte mit dem Jahr hinzu, in dem jede Saison begann. Die NBA-Saison dauert von Oktober bis Mai und fällt in zwei Kalenderjahre. In der Bezeichnung der Jahreszeit ist das Jahr ihres Beginns, d.h. Saison: 2018 in der Tabelle ist die Saison 2018/19 in der Realität.


Die Originaltabelle enthält diese Daten. Die SeasonID-Spalte zeigt eine Zahl in Form von 2 (dem Jahr, in dem die Saison begann), z. B. 22004. Sie können die ersten beiden mit dem stringr Paket oder den grundlegenden R-Funktionen entfernen, aber ich bin einen etwas anderen Weg gegangen. Es stellte sich heraus, dass ich diese Spalte zuerst verwende, um die erforderlichen Jahreszeiten anzugeben, dann eine Spalte mit Datumsangaben lösche und erneut erstelle. Zusätzliche Aktionen.


Ich habe es wie folgt gemacht:


table1 <- table1[,year := rep(seq(2004, 2018), each = length(unique(table1$TeamName)))]


Ich hatte „Glück“, dass sich die Anzahl der Teams in der NBA für den ausgewählten Zeitraum nicht geändert hat. Deshalb habe ich die Zahlen von 2004 bis 2018 nur 30 Mal wiederholt. Wenn Sie in die Geschichte eingehen, ist diese Methode aufgrund der Tatsache, dass die Anzahl der Teams in jeder Saison unterschiedlich ist, unpraktisch. Daher ist es vorzuziehen, die Option zum Löschen der Spalte SeasonID zu verwenden.


Fügen Sie dann die Cumrank-Spalte hinzu.


table1 <- table1[, cumrank := frank(-CumWins, ties.method = "random"), by = "year"]


Es stellt die Rangfolge der Teams in jeder Saison nach der Anzahl der Siege dar und wird als Wert für die X-Achse verwendet. frank ein schnelleres data.table Analogon zum data.table , minus bedeutet Rangfolge in absteigender Reihenfolge (dies kann auch mit dem Argument decreasing = TRUE . Welche Reihenfolge die Teams mit der gleichen Anzahl von Siegen gehen werden, daher ties.method = "random" Nun, all dies ist in einem Jahr zusammengefasst.


Bei der letzten Tabellenkonvertierung wird die Spalte value_rel .


table1 <- table1[, value_rel := CumWins/CumWins[cumrank==1], by = "year"]


In dieser Spalte wird das Verhältnis der Anzahl der Siege jedes Teams zum höchsten Indikator für das Jahr berücksichtigt. Für die beste Mannschaft ist dieser Indikator 1, für den Rest ist er weniger, abhängig vom Erfolg der Saison.


Nach allen Ergänzungen hat die Tabelle die folgende Form:


TeamnameGEWINNTCumwinsJahrcumrankvalue_rel
Sporen5959200430,9516129
Sporen63122200511.0000000
Sporen58180200620,9729730
Sporen56236200711.0000000

In der Tabelle ist nur ein Team dargestellt, um die Kumulativität zu veranschaulichen. Alle diese Aktionen werden wie bei der Namensänderung mit einer Kaskade eckiger Klammern ausgeführt


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

Ändern Sie die Füllung der Spalten vom Standard in die Farben der Teams.


Sie können sofort mit der Erstellung von Diagrammen fortfahren, aber es gibt, wie mir scheint, einen wichtigen Punkt: die Farbe der Spalten im Diagramm. Sie können die Standard-Palette ggplot2 verlassen, dies ist jedoch eine schlechte Option. Erstens scheint es mir, dass sie hässlich ist. Und zweitens ist es schwierig, ein Team in der Tabelle zu finden. Für NBA-Fans ist jedes Team einer bestimmten Farbe zugeordnet: Boston ist grün, Chicago ist rot, Sacramento ist lila usw. Daher hilft die Verwendung der Farbe des Befehls in den Füllspalten, ihn trotz der Fülle an Blau und Rot schneller zu identifizieren.


Erstellen Sie dazu eine Tabelle table_color mit dem Namen des Befehls und seiner Hauptfarbe. Farben von teamcolorcodes.com .


TeamnameTEAM_color
Hawks# E03A3E
Celtics# 007A33
Netze# 000000

Mit der Farbtabelle müssen Sie noch eine Manipulation durchführen. Weil Wenn Plotfaktoren verwendet werden, ändert sich die Reihenfolge der Teams. Der erste auf der Liste ist Philadelphia 76 als alleiniger Eigentümer des "digitalen" Namens und dann nach dem Alphabet. Wir müssen also die Farben in derselben Reihenfolge anordnen und dann den Vektor, der sie enthält, aus der Tabelle extrahieren. Ich habe es wie folgt gemacht:


  table_color <- table_color[order(TeamName)] cols <- table_color[, "TEAM_color"] 

Plotten


Wir erstellen wirklich nur ein Diagramm, das alle 450 (15 Spielzeiten * 30 Teams) Indikatoren für Siege enthält, und "dividieren" es dann durch die erforderliche Variable (in unserem Fall durch Jahre) unter Verwendung der Funktionen aus dem gganimate Paket.


 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) 

Zuerst erstellen wir eine Grafik mit der Funktion ggplot . aes Argument aes , wie die Variablen aus der Tabelle im Diagramm angezeigt werden sollen. Wir gruppieren sie nach Teamname, fill und color sind für die Farbe der Spalten verantwortlich.


Wahre Spalten nennen es nicht ganz wahr. Mit geom_tile "teilen" wir die Daten im Diagramm in Rechtecke. Hier ist ein Beispiel für ein Diagramm dieses Typs:

Es ist zu sehen, wie der Graph in Quadrate "unterteilt" wird (sie werden aus Rechtecken unter Verwendung der coord_equal() ), drei in jeder Spalte. Dank der Argumentbreite von weniger als eins hat unsere Kachel die Form von Spalten.


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

Als nächstes füge ich mit geom_text zwei Signaturen geom_text : den Namen des Teams und die Anzahl der Siege. coord_flip die Achsen, scale_fill_manual und scale_color_manual ändern die Farbe der Spalten, scale_x_reverse "erweitert" die X-Achse. Beachten Sie, dass wir die Farben aus dem zuvor erstellten cols .


Die Themenebene gibt Optionen zum Anpassen der Anzeige des Diagramms an. Hier wird angegeben, wie die Überschriften und Beschriftungen der Achsen angezeigt werden sollen (in keiner Weise, was element_blank auf der rechten Seite der Gleichheit sagt). Wir entfernen die Legenden-, Hintergrund-, Rahmen- und Gitterlinien entlang der Y-Achse. plot.title Argumenten plot.title , plot.subtitle , plot.caption wir die Anzeigeoptionen für Titel, Untertitel und Diagrammsignatur fest. Weitere Einzelheiten zur Bedeutung aller Parameter finden Sie unter 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")) 

Animation erstellen


Ich werde nicht auf die Verwendung der Funktion transition_states eingehen, dieser Teil ist identisch mit meiner früheren Veröffentlichung über Habré. In labs Titel, Untertitel und Signatur des Diagramms erstellt. Mit {closest_state} können Sie jedes bestimmte Jahr in der Tabelle anzeigen, deren Spalten derzeit angezeigt werden.


  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 Funktion zum Erstellen von Diagrammen.


Das Schreiben von Funktionen vereinfacht und beschleunigt das Abrufen des Ergebnisses, wenn Sie den Code mehrmals verwenden müssen. Normalerweise hat eine Funktion in R die folgende Form:


 _ <- function( ) { _ } 

Zuerst müssen Sie verstehen, welche Parameter Sie mit der Funktion ändern möchten. Die Argumente hängen davon ab. Das erste Argument ist der Name der Datentabelle, die eingegeben wird. Auf diese Weise können Sie es umbenennen, wenn ein solcher Wunsch auftritt, ohne die Funktion selbst zu ändern. Ich möchte auch, dass eine beliebige Anzahl von Befehlen in der Tabelle angezeigt wird: von einem (was sinnlos ist) bis 30 (es gibt einfach keine mehr). Ich möchte auch in der Lage sein, alle Zeiträume innerhalb der 15 Jahre zu berücksichtigen, für die ich Daten habe. All dies wird in dieser Funktionsform implementiert:


 nba_cumulative_wins <- function(table, elements, first_season, last_season){ ... } 

Dabei ist table der Name der Tabelle mit den Eingabedaten.
elements - die Namen der Teams, die in der Tabelle angezeigt werden sollen
first_season - Die erste Saison, die in der Tabelle angezeigt wird
last_season - Die letzte Saison, die in der Tabelle angezeigt wird.


Wenn das Argument sehr häufig mit einem bestimmten Wert verwendet wird, können Sie es standardmäßig festlegen. Wenn es dann unter den Argumenten der Funktion weggelassen wird, wird dieser Wert ersetzt. Zum Beispiel, wenn Sie sich registrieren


nba_cumulative_wins <- function(table, elements, first_season, last_season = 2018)


dann werden die Zeitpläne bis zur Saison 2018/19 erstellt, sofern nicht anders angegeben.


Arbeiten mit Argumentelementen, first_season , last_season


Mit dem Argument elements können wir den Namen der Teams angeben, die im Diagramm angezeigt werden sollen. Dies ist sehr praktisch, wenn es 2 oder 3 solcher Teams gibt. Wenn wir jedoch die gesamte Liga anzeigen möchten, müssen wir elements = c() und den Namen aller 30 Teams in Klammern schreiben.


Deshalb habe ich beschlossen, die Eingabewerte für das Elementargument in mehrere Gruppen aufzuteilen.
Mit der Funktion nba_cumulative_wins können Diagramme für einzelne Teams, Abteilungen, Konferenzen oder die NBA als Ganzes erstellt werden. Dafür habe ich folgende Konstruktion verwendet:


  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_ Symbolvektoren enthalten die Namen aller 30 Teams, 6 Abteilungen, 2 Konferenzen und der NBA, und die unique Funktion hinterlässt nur einen eindeutigen Namen anstelle von 15 (nach der Anzahl der Jahre in den Daten).


Anschließend wird mithilfe des if...else überprüft, ob das eingegebene Elementargument zu einer der Klassen gehört ( %in% verwendet, um zu bestimmen, ob das Element zum Vektor gehört), und die Datentabelle wird entsprechend geändert. Nun, wenn ich stattdessen die Ergebnisse von Teams sehen möchte, die in der Southwest Division spielen


elements = c("Mavericks", "Spurs", "Rockets", "Grillies", "Pelicans")


einfach eintreten


elements = "Southwest" , was viel schneller und bequemer ist.


Aufgrund der Möglichkeit, Jahreszeiten zu wählen, ändert sich auch die Arbeit mit Daten. Ganz am Anfang wird die Zeile hinzugefügt:


 table1 <- table1[SeasonID >= as.numeric(paste(2, first_season, sep = "")) & SeasonID <= as.numeric(paste(2, last_season, sep = ""))] 

Daher lasse ich in der Tabelle nur die Zeilen, die in das von uns gewählte Zeitintervall fallen. Der Code zum Erstellen der Jahresspalte ändert sich ebenfalls. Jetzt sieht es so aus:


 table1 <- table1[ ,year := rep(seq(first_season, last_season), each = length(unique(table1$TeamName)))] 

Im Zusammenhang mit der Gruppierung von Elementen ist das Verfahren zum Erhalten der gewünschten Farben kompliziert. Tatsache ist, dass in der Tabelle table_color nur die Namen der Befehle angegeben sind. Daher müssen wir unsere Kontraktionen wieder „einsetzen“. Verwenden Sie dazu das Konstrukt if...else erneut.


  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 } 

Erstellen Sie als Nächstes eine Tabelle mit den Namen der benötigten Befehle und verbinden Sie diese Tabelle mit table_color mithilfe der Funktion inner_join aus dem Paket dplyr . inner_join enthält nur Fälle, die in beiden Tabellen übereinstimmen.


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

Die Funktion ändert die Schreibweise von Titel und Untertitel. Sie nehmen diesen Blick an:


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

Rendern


Weiter wird dies alles visualisiert.


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

Ich habe die Anzahl in nframes empirisch ausgewählt, sodass die Geschwindigkeit abhängig von der Anzahl der ausgewählten Befehle zunimmt / abnimmt.


Grafik



Ich hoffe mein Beitrag ist interessant. Github- Projektcode.


Wenn Sie an der Sportkomponente dieser Visualisierungen interessiert sind, können Sie meinen Blog auf sports.ru "Auf beiden Seiten des Atlantiks" besuchen.

Source: https://habr.com/ru/post/de458904/


All Articles