<p><div><div><div><div data-show-faces="false" data-height="21" data-width="100" data-layout="button_count" data-send="true" data-href="https://www.r-bloggers.com/all-the-nba-box-scores-you-ever-wanted/"></div></div><div></div></div><div></div></div>
<div>
(This article was first published on <strong><a href="https://statisticaloddsandends.wordpress.com/2018/12/18/all-the-nba-box-scores-you-ever-wanted/"> R – Statistical Odds &amp; Ends</a></strong>, and kindly contributed to <a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers)</a> </div>
<p>In <a rel="nofollow" href="https://statisticaloddsandends.wordpress.com/2018/12/11/scraping-nba-game-data-from-basketball-reference-com/" target="_blank">this previous post</a>, I showed how one can scrape top-level NBA game data from <a rel="nofollow" href="https://www.basketball-reference.com/" target="_blank">BasketballReference.com</a>. In <a rel="nofollow" href="https://statisticaloddsandends.wordpress.com/2018/12/13/recreating-the-nba-lead-tracker-graphic/" target="_blank">the post after that</a>, I demonstrated how to scrape play-by-play data for one game. After writing those posts, I thought to myself: why not do both? And that is what I did: <strong>scrape all the box scores for the 2017-18 NBA season and save them to an RDS object.</strong></p>
<p>The code for web scraping can be found <a rel="nofollow" href="https://github.com/kjytay/misc/blob/master/blog/nba_box_score.R" target="_blank">here</a>. I don’t think it’s particularly insightful to go through the details line-by-line… What I’ll do instead is explain what is in the saved RDS object which will allow you to make use of the data, and I’ll end off with some visualizations to demonstrate what one can do with this data, specifically:</p>
<ul>
<li><strong><em>Do Kevin Durant and Steph Curry get in each other’s way?</em></strong></li>
<li><strong><em>Are the Golden State Warriors really better in the 3rd quarter?</em></strong></li>
</ul>
<p>(Code that I wrote for these questions can be found <a rel="nofollow" href="https://github.com/kjytay/misc/blob/master/blog/2018-12-18_GSW.R" target="_blank">here</a>.) Let’s load the packages and data (data available as links from <a rel="nofollow" href="https://github.com/kjytay/misc/tree/master/data" target="_blank">this page</a>):</p>
<pre>
library(tidyverse)
game_df &lt;- readRDS(&quot;NBA-2018_game_data.rds&quot;)
master_list &lt;- readRDS(&quot;NBA-2018_box_score.rds&quot;)
</pre>
<p>First, let’s take a quick look at <code>game_df</code> which contains the top-level game data:</p>
<pre>
head(game_df)
#>        game_id  date_game game_start_time visitor_team_name visitor_pts        home_team_name home_pts
#> 1 201710170CLE 2017-10-17           8:01p    Boston Celtics          99   Cleveland Cavaliers      102
#> 2 201710170GSW 2017-10-17          10:30p   Houston Rockets         122 Golden State Warriors      121
#> 3 201710180BOS 2017-10-18           7:30p   Milwaukee Bucks         108        Boston Celtics      100
#> 4 201710180DAL 2017-10-18           8:30p     Atlanta Hawks         117      Dallas Mavericks      111
#> 5 201710180DET 2017-10-18           7:00p Charlotte Hornets          90       Detroit Pistons      102
#> 6 201710180IND 2017-10-18           7:00p     Brooklyn Nets         131        Indiana Pacers      140
#>   overtimes attendance game_remarks game_type
#> 1                20562                Regular
#> 2                19596                Regular
#> 3                18624                Regular
#> 4                19709                Regular
#> 5                20491                Regular
#> 6                15008                Regular
</pre>
<p>The first column, <code>game_id</code>, is BasketBallReference.com’s way of uniquely identifying a game. For example, if we wanted to see the results of the game between the Celtics and the Cavs on 17 Oct 2017, we would look up the <code>game_id</code> (201710170CLE) and go to the URL <a rel="nofollow" href="https://www.basketball-reference.com/boxscores/201710170CLE.html" target="_blank">https://www.basketball-reference.com/boxscores/201710170CLE.html</a>. The <code>game_id</code> is how I was able to scrape the box scores for all the games.</p>
<p>Next, let’s look at the <code>master_list</code> object which contains all the box score information:</p>
<pre>
typeof(master_list)
#> [1] &quot;list&quot;
length(master_list)
#> [1] 1312
head(names(master_list))
#> [1] &quot;201710170CLE&quot; &quot;201710170GSW&quot; &quot;201710180BOS&quot; &quot;201710180DAL&quot; &quot;201710180DET&quot; &quot;201710180IND&quot;
</pre>
<p>It is a list of length 1312, which corresponds to the total number of games (Regular Season and Playoffs) in the 2017-18 season. The keys of the list are the <code>game_id</code>s, and (as we will see shortly) each element of the list is itself a list of length 5. Let’s take a closer look at the first element of the list:</p>
<pre>
str(master_list[[1]])
#> List of 5
#>  $ visitor_basic_boxscore:'data.frame':	13 obs. of  22 variables:
#>   ..$ Player: chr [1:13] &quot;Jaylen Brown&quot; &quot;Kyrie Irving&quot; &quot;Jayson Tatum&quot; &quot;Al Horford&quot; ...
#>   ..$ MP    : chr [1:13] &quot;39:36&quot; &quot;39:21&quot; &quot;36:32&quot; &quot;32:07&quot; ...
#>   ..$ FG    : num [1:13] 11 8 5 2 1 5 2 2 0 0 ...
#>   ..$ FGA   : num [1:13] 23 17 12 7 2 16 6 2 2 1 ...
#>   ..$ FG%   : num [1:13] 0.478 0.471 0.417 0.286 0.5 0.313 0.333 1 0 0 ...
#>   ..$ 3P    : num [1:13] 2 4 1 0 0 0 1 0 0 0 ...
#>   ..$ 3PA   : num [1:13] 9 9 2 2 1 4 3 0 1 1 ...
#>   ..$ 3P%   : num [1:13] 0.222 0.444 0.5 0 0 0 0.333 NA 0 0 ...
#>   ..$ FT    : num [1:13] 1 2 3 5 0 2 4 2 0 0 ...
#>   ..$ FTA   : num [1:13] 2 2 3 7 0 3 4 4 0 0 ...
#>   ..$ FT%   : num [1:13] 0.5 1 1 0.714 NA 0.667 1 0.5 NA NA ...
#>   ..$ ORB   : num [1:13] 1 2 4 0 0 0 0 2 0 0 ...
#>   ..$ DRB   : num [1:13] 5 2 6 7 1 9 3 3 0 1 ...
#>   ..$ TRB   : num [1:13] 6 4 10 7 1 9 3 5 0 1 ...
#>   ..$ AST   : num [1:13] 0 10 3 5 0 3 2 1 0 0 ...
#>   ..$ STL   : num [1:13] 2 3 0 0 0 2 4 0 0 0 ...
#>   ..$ BLK   : num [1:13] 0 0 0 1 0 2 0 1 0 0 ...
#>   ..$ TOV   : num [1:13] 3 2 1 0 0 2 0 2 0 0 ...
#>   ..$ PF    : num [1:13] 5 4 4 2 1 2 0 5 1 0 ...
#>   ..$ PTS   : num [1:13] 25 22 14 9 2 12 9 6 0 0 ...
#>   ..$ +/-   : num [1:13] -5 -1 6 8 3 -8 4 -14 -10 2 ...
#>   ..$ Role  : chr [1:13] &quot;Starter&quot; &quot;Starter&quot; &quot;Starter&quot; &quot;Starter&quot; ...
#>  $ visitor_adv_boxscore  :'data.frame':	13 obs. of  17 variables:
#>   ..$ Player: chr [1:13] &quot;Jaylen Brown&quot; &quot;Kyrie Irving&quot; &quot;Jayson Tatum&quot; &quot;Al Horford&quot; ...
#>   ..$ MP    : chr [1:13] &quot;39:36&quot; &quot;39:21&quot; &quot;36:32&quot; &quot;32:07&quot; ...
#>   ..$ TS%   : num [1:13] 0.523 0.615 0.526 0.446 0.5 0.346 0.58 0.798 0 0 ...
#>   ..$ eFG%  : num [1:13] 0.522 0.588 0.458 0.286 0.5 0.313 0.417 1 0 0 ...
#>   ..$ 3PAr  : num [1:13] 0.391 0.529 0.167 0.286 0.5 0.25 0.5 0 0.5 1 ...
#>   ..$ FTr   : num [1:13] 0.087 0.118 0.25 1 0 0.188 0.667 2 0 0 ...
#>   ..$ ORB%  : num [1:13] 2.4 4.9 10.5 0 0 0 0 10.1 0 0 ...
#>   ..$ DRB%  : num [1:13] 13.2 5.3 17.1 22.7 19.9 26.8 16 16.4 0 21.7 ...
#>   ..$ TRB%  : num [1:13] 7.6 5.1 13.7 10.9 9.5 12.8 7.7 13.1 0 10.4 ...
#>   ..$ AST%  : num [1:13] 0 46.5 13.4 22.6 0 14.1 15.8 8.1 0 0 ...
#>   ..$ STL%  : num [1:13] 2.4 3.7 0 0 0 2.8 9.9 0 0 0 ...
#>   ..$ BLK%  : num [1:13] 0 0 0 2.5 0 4.5 0 4.1 0 0 ...
#>   ..$ TOV%  : num [1:13] 11.2 10.1 7 0 0 10.4 0 34.7 0 0 ...
#>   ..$ USG%  : num [1:13] 29.9 22.2 17.3 13.8 16.8 24.3 17.5 13.3 10.2 9.1 ...
#>   ..$ ORtg  : num [1:13] 89 124 115 111 95 70 129 110 0 0 ...
#>   ..$ DRtg  : num [1:13] 104 103 108 105 107 96 87 105 112 107 ...
#>   ..$ Role  : chr [1:13] &quot;Starter&quot; &quot;Starter&quot; &quot;Starter&quot; &quot;Starter&quot; ...
#>  $ home_basic_boxscore   :'data.frame':	14 obs. of  22 variables:
#>   ..$ Player: chr [1:14] &quot;LeBron James&quot; &quot;Jae Crowder&quot; &quot;Derrick Rose&quot; &quot;Dwyane Wade&quot; ...
#>   ..$ MP    : chr [1:14] &quot;41:12&quot; &quot;34:44&quot; &quot;31:15&quot; &quot;28:30&quot; ...
#>   ..$ FG    : num [1:14] 12 3 5 3 4 4 2 3 2 0 ...
#>   ..$ FGA   : num [1:14] 19 10 14 10 9 7 3 8 3 0 ...
#>   ..$ FG%   : num [1:14] 0.632 0.3 0.357 0.3 0.444 0.571 0.667 0.375 0.667 NA ...
#>   ..$ 3P    : num [1:14] 1 1 1 0 1 1 0 0 0 0 ...
#>   ..$ 3PA   : num [1:14] 5 5 3 1 4 3 0 1 0 0 ...
#>   ..$ 3P%   : num [1:14] 0.2 0.2 0.333 0 0.25 0.333 NA 0 NA NA ...
#>   ..$ FT    : num [1:14] 4 4 3 2 6 1 1 0 0 0 ...
#>   ..$ FTA   : num [1:14] 4 4 4 2 7 1 3 0 0 0 ...
#>   ..$ FT%   : num [1:14] 1 1 0.75 1 0.857 1 0.333 NA NA NA ...
#>   ..$ ORB   : num [1:14] 1 1 1 1 3 0 1 0 1 0 ...
#>   ..$ DRB   : num [1:14] 15 4 3 1 8 4 5 0 1 0 ...
#>   ..$ TRB   : num [1:14] 16 5 4 2 11 4 6 0 2 0 ...
#>   ..$ AST   : num [1:14] 9 2 2 3 0 1 2 0 0 0 ...
#>   ..$ STL   : num [1:14] 0 2 0 0 0 0 0 0 1 0 ...
#>   ..$ BLK   : num [1:14] 2 0 0 2 0 0 0 0 0 0 ...
#>   ..$ TOV   : num [1:14] 4 1 2 4 2 0 2 1 1 0 ...
#>   ..$ PF    : num [1:14] 3 2 2 1 2 4 3 3 3 2 ...
#>   ..$ PTS   : num [1:14] 29 11 14 8 15 10 5 6 4 0 ...
#>   ..$ +/-   : num [1:14] 2 7 -7 0 1 7 2 -2 6 -1 ...
#>   ..$ Role  : chr [1:14] &quot;Starter&quot; &quot;Starter&quot; &quot;Starter&quot; &quot;Starter&quot; ...
#>  $ home_adv_boxscore     :'data.frame':	14 obs. of  17 variables:
#>   ..$ Player: chr [1:14] &quot;LeBron James&quot; &quot;Jae Crowder&quot; &quot;Derrick Rose&quot; &quot;Dwyane Wade&quot; ...
#>   ..$ MP    : chr [1:14] &quot;41:12&quot; &quot;34:44&quot; &quot;31:15&quot; &quot;28:30&quot; ...
#>   ..$ TS%   : num [1:14] 0.698 0.468 0.444 0.368 0.621 0.672 0.579 0.375 0.667 NA ...
#>   ..$ eFG%  : num [1:14] 0.658 0.35 0.393 0.3 0.5 0.643 0.667 0.375 0.667 NA ...
#>   ..$ 3PAr  : num [1:14] 0.263 0.5 0.214 0.1 0.444 0.429 0 0.125 0 NA ...
#>   ..$ FTr   : num [1:14] 0.211 0.4 0.286 0.2 0.778 0.143 1 0 0 NA ...
#>   ..$ ORB%  : num [1:14] 2.5 3 3.3 3.7 11 0 5.3 0 8.1 0 ...
#>   ..$ DRB%  : num [1:14] 35 11.1 9.2 3.4 27 17.5 24.5 0 7.5 0 ...
#>   ..$ TRB%  : num [1:14] 19.4 7.2 6.4 3.5 19.4 9.1 15.3 0 7.8 0 ...
#>   ..$ AST%  : num [1:14] 43.7 8.2 10.1 15.3 0 7.5 14.8 0 0 0 ...
#>   ..$ STL%  : num [1:14] 0 2.8 0 0 0 0 0 0 3.8 0 ...
#>   ..$ BLK%  : num [1:14] 4.2 0 0 6 0 0 0 0 0 0 ...
#>   ..$ TOV%  : num [1:14] 16.2 7.8 11.3 26.9 14.2 0 31.6 11.1 25 NA ...
#>   ..$ USG%  : num [1:14] 26 15.9 24.6 22.6 21.4 14.7 13.9 27.3 13.5 0 ...
#>   ..$ ORtg  : num [1:14] 126 100 88 67 117 138 92 67 102 0 ...
#>   ..$ DRtg  : num [1:14] 94 98 105 102 100 102 100 107 96 106 ...
#>   ..$ Role  : chr [1:14] &quot;Starter&quot; &quot;Starter&quot; &quot;Starter&quot; &quot;Starter&quot; ...
#>  $ pdp_df                :'data.frame':	114 obs. of  4 variables:
#>   ..$ time   : num [1:114] 16 72 88 99 131 151 166 191 191 238 ...
#>   ..$ visitor: num [1:114] 2 4 4 4 4 4 6 6 6 8 ...
#>   ..$ home   : num [1:114] 0 0 1 3 5 7 7 8 9 9 ...
#>   ..$ period : num [1:114] 1 1 1 1 1 1 1 1 1 1 ...
</pre>
<p>The first 4 elements of the list (<code>visitor_basic_boxscore</code>, <code>visitor_adv_boxscore</code>, <code>home_basic_boxscore</code>, <code>home_basic_boxscore</code>) are data frames for the basic and advanced box scores of both teams. They are the box scores you see on BasketBallReference.com (with some minor preprocessing):</p>
<div data-shortcode="caption" class="wp-caption"><img data-orig-file="https://statisticaloddsandends.files.wordpress.com/2018/12/boxscores.png?w=450&amp;h=705&amp;fit=584%2C705" data-orig-size="633,764" data-medium-file="https://statisticaloddsandends.files.wordpress.com/2018/12/boxscores.png?w=450&amp;h=705&amp;fit=584%2C705?w=249" data-attachment-id="742" data-image-meta="{" data-image-description="" src="https://statisticaloddsandends.files.wordpress.com/2018/12/boxscores.png?w=450&h=705&fit=584%2C705" data-permalink="https://statisticaloddsandends.wordpress.com/2018/12/18/all-the-nba-box-scores-you-ever-wanted/boxscores/" data-large-file="https://statisticaloddsandends.files.wordpress.com/2018/12/boxscores.png?w=450&amp;h=705&amp;fit=584%2C705?w=584" alt="" data-image-title="boxscores" sizes="(max-width: 584px) 100vw, 584px" data-comments-opened="1" class="wp-image-742" data-recalc-dims="1">
<p class="wp-caption-text">Box scores from BasketballReference.com.</p>
</div>
<p>The last element of the list is a data frame containing play-by-play information, much like that in <a rel="nofollow" href="https://statisticaloddsandends.wordpress.com/2018/12/13/recreating-the-nba-lead-tracker-graphic/" target="_blank">the previous post</a>.</p>
<p>Hopefully some of you will take this data and make some cool data visualizations from it! Below, I will walk through how I use this dataset for two visualizations.</p>
<p><strong>Do Kevin Durant and Steph Curry get in each other’s way?</strong></p>
<p>Kevin Durant and Steph Curry are two of the most lethal scorers in today’s game. Does both of them being on the same team hamper the other’s production? This is pretty difficult to pin down what we mean by this statement precisely; we will instead make do with a couple of suggestive plots.</p>
<p>The first plot we can make is a scatterplot of the points scored by these two players. If the relationship is positively correlated, it could mean that they make each other better.</p>
<p>Let’s pull out the <code>game_id</code>s for Golden State:</p>
<pre>
team_name &lt;- &quot;Golden State Warriors&quot;
player1 &lt;- &quot;Stephen Curry&quot;
player2 &lt;- &quot;Kevin Durant&quot;

# get game_ids
game_rows &lt;- which(game_df$visitor_team_name == team_name |
                       game_df$home_team_name == team_name)
game_ids &lt;- game_df[game_rows, &quot;game_id&quot;]
</pre>
<p>Next, we set up a data frame consisting of 4 columns: the <code>game_id</code>, <code>game_type</code> (regular or playoff), and points scored by players 1 and 2 respectively.</p>
<pre>
points_df &lt;- data.frame(matrix(NA, ncol = 4, nrow = length(game_ids)))
names(points_df) &lt;- c(&quot;game_id&quot;, &quot;game_type&quot;, player1, player2)
</pre>
<p>The loop below builds up the data frame row by row (not the most R-like way of programming but it gets the job done…):</p>
<pre>
for (i in 1:nrow(points_df)) {
    id &lt;- game_ids[i]
    points_df[i, 1] &lt;- id
    points_df[i, 2] &lt;- game_df[game_rows[i], &quot;game_type&quot;]
    
    # get the correct basic box score
    if (game_df[game_rows[i], &quot;visitor_team_name&quot;] == team_name) {
        boxscore &lt;- master_list[[id]]$visitor_basic_boxscore
    } else {
        boxscore &lt;- master_list[[id]]$home_basic_boxscore
    }
    
    # get player points
    if (player1 %in% boxscore$Player) {
        points_df[i, 3] &lt;- subset(boxscore, Player == player1)$PTS
    }
    if (player2 %in% boxscore$Player) {
        points_df[i, 4] &lt;- subset(boxscore, Player == player2)$PTS } } head(points_df) 
#>        game_id game_type Stephen Curry Kevin Durant
#> 1 201710170GSW   Regular            22           20
#> 2 201710200NOP   Regular            28           22
#> 3 201710210MEM   Regular            37           29
#> 4 201710230DAL   Regular            29           25
#> 5 201710250GSW   Regular            30           29
#> 6 201710270GSW   Regular            20           31
</pre>
<p>Next, we make the scatterplot (notice how we can use the <code>get</code> function to refer to the columns programmatically). We force the axes to start at 0 and the aspect ratio to be 1, and include the 45 degree line for reference.</p>
<pre>
ggplot(data = points_df, aes(x = get(player1), y = get(player2))) +
    geom_point(alpha = 0.5) +
    geom_smooth(se = FALSE) +
    geom_abline(slope = 1, intercept = 0, lwd = 0.5, col = &quot;red&quot;, lty = 2) +
    scale_x_continuous(limits = c(0, NA)) +
    scale_y_continuous(limits = c(0, NA)) +
    labs(x = player1, y = player2, title = &quot;Points scored&quot;) +
    coord_fixed()
</pre>
<p><img data-orig-file="https://statisticaloddsandends.files.wordpress.com/2018/12/GSW_points_scored.png?w=456" data-orig-size="397,333" data-medium-file="https://statisticaloddsandends.files.wordpress.com/2018/12/GSW_points_scored.png?w=456?w=300" data-attachment-id="746" data-image-meta="{" data-image-description="" src="https://statisticaloddsandends.files.wordpress.com/2018/12/GSW_points_scored.png?w=456" data-permalink="https://statisticaloddsandends.wordpress.com/2018/12/18/all-the-nba-box-scores-you-ever-wanted/gsw_points_scored/" data-large-file="https://statisticaloddsandends.files.wordpress.com/2018/12/GSW_points_scored.png?w=456?w=397" alt="" data-image-title="GSW_points_scored" sizes="(max-width: 397px) 100vw, 397px" data-comments-opened="1" class="wp-image-746" data-recalc-dims="1"></p>
<p>One might say, wouldn’t it be better to compare games where KD and Curry play, vs. games where only KD plays? If KD scores more if he is playing with Curry, that could suggest that playing together makes him more productive. To make these plots, we introduce two new columns which are Boolean variables representing whether a given player played in that game or not. We then make boxplots to compare the points scored depending on whether the other player played.</p>
<pre>
# ignore games where both didn't play
points_df2 &lt;- points_df[!(is.na(points_df[[3]]) &amp; is.na(points_df[[4]])), ]
points_df2[[paste(player1, &quot;played&quot;)]] &lt;- !is.na(points_df2[[3]])
points_df2[[paste(player2, &quot;played&quot;)]] &lt;- !is.na(points_df2[[4]])

# boxplots
ggplot(data = points_df2, aes(x = get(paste(player1, &quot;played&quot;)), y = get(player2))) +
    geom_boxplot() +
    geom_jitter(height = 0, alpha = 0.5, col = &quot;blue&quot;) +
    labs(x = paste(player1, &quot;played?&quot;), y = &quot;Points scored&quot;, 
         title = paste(&quot;Points&quot;, player2, &quot;scored&quot;))
ggplot(data = points_df2, aes(x = get(paste(player2, &quot;played&quot;)), y = get(player1))) +
    geom_boxplot() +
    geom_jitter(height = 0, alpha = 0.5, col = &quot;blue&quot;) +
    labs(x = paste(player2, &quot;played?&quot;), y = &quot;Points scored&quot;, 
         title = paste(&quot;Points&quot;, player1, &quot;scored&quot;))
</pre>
<p><img data-orig-file="https://statisticaloddsandends.files.wordpress.com/2018/12/KD_score.png" data-orig-size="397,333" data-medium-file="https://statisticaloddsandends.files.wordpress.com/2018/12/KD_score.png?w=300&amp;h=252&amp;fit=300%2C252&amp;resize=300%2C252" data-attachment-id="747" data-image-meta="{" data-image-description="" src="https://statisticaloddsandends.files.wordpress.com/2018/12/KD_score.png?w=300&h=252&fit=300%2C252&resize=300%2C252" data-permalink="https://statisticaloddsandends.wordpress.com/2018/12/18/all-the-nba-box-scores-you-ever-wanted/kd_score/" data-large-file="https://statisticaloddsandends.files.wordpress.com/2018/12/KD_score.png?w=397" alt="" data-image-title="KD_score" sizes="(max-width: 300px) 100vw, 300px" width="300" data-comments-opened="1" class="wp-image-747" data-recalc-dims="1" height="252"><img data-orig-file="https://statisticaloddsandends.files.wordpress.com/2018/12/Curry_score.png" data-orig-size="397,333" data-medium-file="https://statisticaloddsandends.files.wordpress.com/2018/12/Curry_score.png?w=300&amp;h=252&amp;fit=300%2C252&amp;resize=300%2C252" data-attachment-id="748" data-image-meta="{" data-image-description="" src="https://statisticaloddsandends.files.wordpress.com/2018/12/Curry_score.png?w=300&h=252&fit=300%2C252&resize=300%2C252" data-permalink="https://statisticaloddsandends.wordpress.com/2018/12/18/all-the-nba-box-scores-you-ever-wanted/curry_score/" data-large-file="https://statisticaloddsandends.files.wordpress.com/2018/12/Curry_score.png?w=397" alt="" data-image-title="Curry_score" sizes="(max-width: 300px) 100vw, 300px" width="300" data-comments-opened="1" class="wp-image-748" data-recalc-dims="1" height="252"></p>
<p>From the plots, it looks like each player scores a bit more when the other person is not playing, although there is quite a bit of variance.</p>
<p>Instead of looking at points scored, we could look at more sophisticated measures of production. For example, the NBA computes individual offensive and defensive ratings for each game based on a complicated formula (see <a rel="nofollow" href="https://www.basketball-reference.com/about/ratings.html" target="_blank">here</a> for details). These measures are provided as <code>ORtg</code> and <code>DRtg</code> in the advanced box score.</p>
<p>The code below pulls out the ORtg and DRtg values for KD and Curry:</p>
<pre>
points_df &lt;- data.frame(matrix(NA, ncol = 6, nrow = length(game_ids)))
names(points_df) &lt;- c(&quot;game_id&quot;, &quot;game_type&quot;, 
                      sapply(c(player1, player2), function(x) paste(x, &quot;ORtg&quot;)),
                      sapply(c(player1, player2), function(x) paste(x, &quot;DRtg&quot;)))

for (i in 1:nrow(points_df)) {
    id &lt;- game_ids[i]
    points_df[i, 1] &lt;- id
    points_df[i, 2] &lt;- game_df[game_rows[i], &quot;game_type&quot;]
    
    # get the correct advanced box score
    if (game_df[game_rows[i], &quot;visitor_team_name&quot;] == team_name) {
        boxscore &lt;- master_list[[id]]$visitor_adv_boxscore
    } else {
        boxscore &lt;- master_list[[id]]$home_adv_boxscore
    }
    
    # get player ORtg &amp; DRtg
    if (player1 %in% boxscore$Player) {
        points_df[i, 3] &lt;- subset(boxscore, Player == player1)$ORtg
        points_df[i, 5] &lt;- subset(boxscore, Player == player1)$DRtg
    }
    if (player2 %in% boxscore$Player) {
        points_df[i, 4] &lt;- subset(boxscore, Player == player2)$ORtg
        points_df[i, 6] &lt;- subset(boxscore, Player == player2)$DRtg
    }
}
</pre>
<p>And here are the plots (along with a linear fit):</p>
<p><img data-orig-file="https://statisticaloddsandends.files.wordpress.com/2018/12/GSW_ORtg.png?w=456" data-orig-size="397,333" data-medium-file="https://statisticaloddsandends.files.wordpress.com/2018/12/GSW_ORtg.png?w=456?w=300" data-attachment-id="750" data-image-meta="{" data-image-description="" src="https://statisticaloddsandends.files.wordpress.com/2018/12/GSW_ORtg.png?w=456" data-permalink="https://statisticaloddsandends.wordpress.com/2018/12/18/all-the-nba-box-scores-you-ever-wanted/gsw_ortg/" data-large-file="https://statisticaloddsandends.files.wordpress.com/2018/12/GSW_ORtg.png?w=456?w=397" alt="" data-image-title="GSW_ORtg" sizes="(max-width: 397px) 100vw, 397px" data-comments-opened="1" class="wp-image-750" data-recalc-dims="1"><br>
<img data-orig-file="https://statisticaloddsandends.files.wordpress.com/2018/12/POR_DRtg.png?w=456" data-orig-size="397,333" data-medium-file="https://statisticaloddsandends.files.wordpress.com/2018/12/POR_DRtg.png?w=456?w=300" data-attachment-id="751" data-image-meta="{" data-image-description="" src="https://statisticaloddsandends.files.wordpress.com/2018/12/POR_DRtg.png?w=456" data-permalink="https://statisticaloddsandends.wordpress.com/2018/12/18/all-the-nba-box-scores-you-ever-wanted/por_drtg/" data-large-file="https://statisticaloddsandends.files.wordpress.com/2018/12/POR_DRtg.png?w=456?w=397" alt="" data-image-title="POR_DRtg" sizes="(max-width: 397px) 100vw, 397px" data-comments-opened="1" class="wp-image-751" data-recalc-dims="1"></p>
<p>This was pretty interesting to me: it looks like the ORtgs are independent of each other while the DRtgs are highly correlated with each other. Might this suggest that defense is more of a team activity, while offense can be driven by individuals?</p>
<p>There are of course many other visualizations we could do to explore this question. We might also want to see how the KD-Curry pairing compares with other pairings. Because of the way the code is written, it’s really easy to generate these figures for other pairings: we just need to update the <code>team_name</code>, <code>player1</code> and <code>player2</code> variables and rerun the code. As an example, here are the ORtg and DRtg scatterplots for Damian Lillard and C.J. McCollum of the Portland Trail Blazers:</p>
<p><img data-orig-file="https://statisticaloddsandends.files.wordpress.com/2018/12/POR_ORtg.png?w=456" data-orig-size="397,333" data-medium-file="https://statisticaloddsandends.files.wordpress.com/2018/12/POR_ORtg.png?w=456?w=300" data-attachment-id="753" data-image-meta="{" data-image-description="" src="https://statisticaloddsandends.files.wordpress.com/2018/12/POR_ORtg.png?w=456" data-permalink="https://statisticaloddsandends.wordpress.com/2018/12/18/all-the-nba-box-scores-you-ever-wanted/por_ortg/" data-large-file="https://statisticaloddsandends.files.wordpress.com/2018/12/POR_ORtg.png?w=456?w=397" alt="" data-image-title="POR_ORtg" sizes="(max-width: 397px) 100vw, 397px" data-comments-opened="1" class="wp-image-753" data-recalc-dims="1"><br>
<img data-orig-file="https://statisticaloddsandends.files.wordpress.com/2018/12/POR_DRtg-1.png?w=456" data-orig-size="397,333" data-medium-file="https://statisticaloddsandends.files.wordpress.com/2018/12/POR_DRtg-1.png?w=456?w=300" data-attachment-id="756" data-image-meta="{" data-image-description="" src="https://statisticaloddsandends.files.wordpress.com/2018/12/POR_DRtg-1.png?w=456" data-permalink="https://statisticaloddsandends.wordpress.com/2018/12/18/all-the-nba-box-scores-you-ever-wanted/por_drtg-2/" data-large-file="https://statisticaloddsandends.files.wordpress.com/2018/12/POR_DRtg-1.png?w=456?w=397" alt="" data-image-title="POR_DRtg" sizes="(max-width: 397px) 100vw, 397px" data-comments-opened="1" class="wp-image-756" data-recalc-dims="1"></p>
<p>We see similar trends here.</p>
<p><strong>Are the Golden State Warriors really better in the 3rd quarter?</strong></p>
<p>Last season, it seemed like the Warriors were often down at the half, only to come roaring back in the third quarter. Is this really their best quarter? (<a rel="nofollow" href="https://www.nytimes.com/2018/05/31/sports/warriors-third-quarter.html" target="_blank">This NYT article</a> says yes and suggests why that might be the case.) To answer this question, we’ll make a lead tracker graphic (much like the <a rel="nofollow" href="https://statisticaloddsandends.wordpress.com/2018/12/13/recreating-the-nba-lead-tracker-graphic/" target="_blank">previous post</a>) but (i) we will do it for all games GSW played in, and (ii) we reset the score differential to 0 at the beginning of each period.</p>
<p>We first set up a data frame that records the <code>game_id</code>, whether the team was at home, and whether they won (we might want to use latter two variables in our plotting later):</p>
<pre>
team_name &lt;- &quot;Golden State Warriors&quot;

game_rows &lt;- which(game_df$visitor_team_name == team_name |
                       game_df$home_team_name == team_name)
game_ids &lt;- game_df[game_rows, &quot;game_id&quot;]

# set up game data frame and play-by-play list
df &lt;- data.frame(matrix(NA, ncol = 3, nrow = length(game_ids)))
names(df) &lt;- c(&quot;game_id&quot;, &quot;home&quot;, &quot;win&quot;)
df$game_id &lt;- game_ids
df$home &lt;- game_df[game_rows, &quot;home_team_name&quot;] == team_name
df$win &lt;- (df$home &amp; game_df[game_rows, &quot;home_pts&quot;] > game_df[game_rows, &quot;visitor_pts&quot;]) |
    ((!df$home) &amp; game_df[game_rows, &quot;home_pts&quot;] &lt; game_df[game_rows, &quot;visitor_pts&quot;])
</pre>
<p>Next, we create a list with keys being the <code>game_id</code>s and the value being the play-by-play data frame from the master list we scraped earlier. We standardize the column names so that GSW’s score is labeled <code>team</code> and the opponent’s score is labeled <code>opp</code>. (Note: At the time of writing, BasketBallReference.com had incorrect data for the game <code>201710200NOP</code>, so I have removed that game from our data.)</p>
<pre>
pbp_list &lt;- list()
for (i in 1:nrow(df)) {
    id &lt;- game_ids[i]
    pdp &lt;- master_list[[id]]$pdp_df
    if (df[i, &quot;home&quot;]) {
        names(pdp) &lt;- c(&quot;time&quot;, &quot;opp&quot;, &quot;team&quot;, &quot;period&quot;)
    } else {
        names(pdp) &lt;- c(&quot;time&quot;, &quot;team&quot;, &quot;opp&quot;, &quot;period&quot;)
    }
    pbp_list[[id]] &lt;- pdp
}

# NOTE: mistake in 201710200NOP. original data on website is wrong
# https://www.basketball-reference.com/boxscores/pbp/201710200NOP.html
# so we remove it
df &lt;- df[-which(df$game_id == &quot;201710200NOP&quot;), ]
pbp_list[[&quot;201710200NOP&quot;]] &lt;- NULL
</pre>
<p>We create a function <code>parse_pbp</code> which helps us reset the score at the beginning of every quarter. To make the lines in our eventual visualization smoother, <code>parse_pbp</code> also adds additional rows for the beginning and end of every quarter.</p>
<pre>
parse_pbp &lt;- function(pbp) {
    pbp &lt;- rbind(0, pbp)
    new_pbp &lt;- pbp
    
    # get points scored in the period
    last_opp &lt;- 0; last_team &lt;- 0; last_period &lt;- 0 for (i in 2:nrow(pbp)) { if (pbp[i, &quot;period&quot;] > last_period + 1) {
            last_period &lt;- last_period + 1
            last_opp &lt;- pbp[i-1, &quot;opp&quot;]
            last_team &lt;- pbp[i-1, &quot;team&quot;]
        }
        new_pbp$opp[i] &lt;- pbp$opp[i] - last_opp
        new_pbp$team[i] &lt;- pbp$team[i] - last_team
    }
    
    # add extra rows to denote beginning and end of periods
    num_period &lt;- max(new_pbp$period)
    for (i in 1:num_period) {
        end_row &lt;- new_pbp[max(which(new_pbp$period == i)), ]
        end_row[1] &lt;- 12 * 60 * min(i, 4) + 5 * 60 * max(i-4, 0) 
        beg_row &lt;- c(0, 0, 0, i)
        beg_row[1] &lt;- 12 * 60 * min(i-1, 4) + 5 * 60 * max(i-1-4, 0) 
        new_pbp &lt;- rbind(new_pbp, beg_row)
        new_pbp &lt;- rbind(new_pbp, end_row)
    }
    new_pbp &lt;- new_pbp[order(new_pbp$time), ]
    new_pbp$adv &lt;- with(new_pbp, team - opp)
    new_pbp[-1, ]
}

pbp_list &lt;- lapply(pbp_list, parse_pbp)
</pre>
<p>Finally, we make the list into a dataframe:</p>
<pre>
pbp_df &lt;- data.frame(matrix(ncol = 6, nrow = 0))
names(pbp_df) &lt;- c(&quot;game_id&quot;, &quot;home&quot;, &quot;win&quot;, &quot;time&quot;, &quot;period&quot;, &quot;adv&quot;)
for (i in seq_along(pbp_list)) {
    xx &lt;- pbp_list[[i]]
    xx$game_id &lt;- df$game_id[i]
    xx$home &lt;- df$home[i]
    xx$win &lt;- df$win[i]
    pbp_df &lt;- rbind(pbp_df, xx) } head(pbp_df) #>     time opp team period adv      game_id home   win
#> 124    0   0    0      1   0 201710170GSW TRUE FALSE
#> 2     13   2    0      1  -2 201710170GSW TRUE FALSE
#> 3     55   2    1      1  -1 201710170GSW TRUE FALSE
#> 4     55   2    2      1   0 201710170GSW TRUE FALSE
#> 5     90   2    5      1   3 201710170GSW TRUE FALSE
#> 6    104   5    5      1   0 201710170GSW TRUE FALSE
</pre>
<p>We can now make the line plot easily. We use the <code>interaction</code> function to group the line plots by both <code>game_id</code> and <code>quarter</code>, color the lines by whether GSW won or not, and manually define the breaks to be at the end of the periods:</p>
<pre>
periods &lt;- unique(pbp_df$period)
x_value &lt;- ifelse(periods &lt;= 4, 12 * 60 * periods, 
                  12 * 60 * 4 + 5 * 60 * (periods - 4))
x_label &lt;- ifelse(periods &lt;= 4, paste0(&quot;Q&quot;, periods), 
                  paste0(&quot;OT&quot;, periods - 4))

ggplot(pbp_df, aes(x = time, y = adv)) +
    geom_line(aes(col = win, group = interaction(game_id, period)), 
              lwd = 0.1) +
    geom_smooth(aes(group = period), col = &quot;black&quot;, se = FALSE) +
    scale_x_continuous(breaks = x_value, labels = x_label) +
    scale_color_manual(values = c(&quot;#ff6600&quot;, &quot;#3366ff&quot;)) +
    labs(title = paste(&quot;Point Advantage by Quarter,&quot;, team_name)) +
    theme_minimal() +
    theme(plot.title = element_text(face = &quot;bold&quot;, hjust = 0.5),
          axis.title.x = element_blank(), axis.title.y = element_blank(),
          panel.grid.minor.x = element_blank(), 
          panel.grid.minor.y = element_blank(),
          legend.position = &quot;bottom&quot;)
</pre>
<p><img data-orig-file="https://statisticaloddsandends.files.wordpress.com/2018/12/diff_by_quarter.png?w=456" data-orig-size="500,333" data-medium-file="https://statisticaloddsandends.files.wordpress.com/2018/12/diff_by_quarter.png?w=456?w=300" data-attachment-id="762" data-image-meta="{" data-image-description="" src="https://statisticaloddsandends.files.wordpress.com/2018/12/diff_by_quarter.png?w=456" data-permalink="https://statisticaloddsandends.wordpress.com/2018/12/18/all-the-nba-box-scores-you-ever-wanted/diff_by_quarter/" data-large-file="https://statisticaloddsandends.files.wordpress.com/2018/12/diff_by_quarter.png?w=456?w=500" alt="" data-image-title="diff_by_quarter" sizes="(max-width: 500px) 100vw, 500px" data-comments-opened="1" class="wp-image-762" data-recalc-dims="1"></p>
<p>It may not be so obvious in this plot, but there certainly seems to be a bit of a difference in the third quarter. On average, the Warriors are even or up 1-2 points, but in the third quarter GSW gains almost 5 points. The difference is more obvious in the plot below, where we zoom in on the y-axis. (Note that we have to use <code>coord_cartesian</code> instead of <code>scale_y_continuous</code> to zoom in: <code>scale_y_continuous</code> will remove the points outside the range before plotting, which results in the incorrect result for the <code>geom_smooth</code> layer.</p>
<pre>
ggplot(pbp_df, aes(x = time, y = adv)) +
    geom_line(aes(col = win, group = interaction(game_id, period)), 
              lwd = 0.1) +
    geom_smooth(aes(group = period), col = &quot;black&quot;, se = FALSE) +
    scale_x_continuous(breaks = x_value, labels = x_label) +
    scale_color_manual(values = c(&quot;#ff6600&quot;, &quot;#3366ff&quot;)) +
    coord_cartesian(ylim = c(-10, 10)) +
    labs(title = paste(&quot;Point Advantage by Quarter,&quot;, team_name)) +
    theme_minimal() +
    theme(plot.title = element_text(face = &quot;bold&quot;, hjust = 0.5),
          axis.title.x = element_blank(), axis.title.y = element_blank(),
          panel.grid.minor.x = element_blank(), 
          panel.grid.minor.y = element_blank(),
          legend.position = &quot;bottom&quot;)
</pre>
<p><img data-orig-file="https://statisticaloddsandends.files.wordpress.com/2018/12/diff_by_quarter2.png?w=456" data-orig-size="500,333" data-medium-file="https://statisticaloddsandends.files.wordpress.com/2018/12/diff_by_quarter2.png?w=456?w=300" data-attachment-id="763" data-image-meta="{" data-image-description="" src="https://statisticaloddsandends.files.wordpress.com/2018/12/diff_by_quarter2.png?w=456" data-permalink="https://statisticaloddsandends.wordpress.com/2018/12/18/all-the-nba-box-scores-you-ever-wanted/diff_by_quarter2/" data-large-file="https://statisticaloddsandends.files.wordpress.com/2018/12/diff_by_quarter2.png?w=456?w=500" alt="" data-image-title="diff_by_quarter2" sizes="(max-width: 500px) 100vw, 500px" data-comments-opened="1" class="wp-image-763" data-recalc-dims="1"></p>
<p>The advantage becomes even more obvious when we facet by whether GSW won or not: in wins, they gain almost 10 points in the 3rd quarter, compared to something like 1-3 points in each of the other quarters.</p>
<p><img data-orig-file="https://statisticaloddsandends.files.wordpress.com/2018/12/diff_by_quarter3.png?w=456" data-orig-size="500,333" data-medium-file="https://statisticaloddsandends.files.wordpress.com/2018/12/diff_by_quarter3.png?w=456?w=300" data-attachment-id="764" data-image-meta="{" data-image-description="" src="https://statisticaloddsandends.files.wordpress.com/2018/12/diff_by_quarter3.png?w=456" data-permalink="https://statisticaloddsandends.wordpress.com/2018/12/18/all-the-nba-box-scores-you-ever-wanted/diff_by_quarter3/" data-large-file="https://statisticaloddsandends.files.wordpress.com/2018/12/diff_by_quarter3.png?w=456?w=500" alt="" data-image-title="diff_by_quarter3" sizes="(max-width: 500px) 100vw, 500px" data-comments-opened="1" class="wp-image-764" data-recalc-dims="1"></p>
	<p>
<div>
<div>To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://statisticaloddsandends.wordpress.com/2018/12/18/all-the-nba-box-scores-you-ever-wanted/"> R – Statistical Odds &amp; Ends</a></strong>.</div>
<hr>
<a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers.com</a> offers <strong><a rel="nofollow" href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers">daily e-mail updates</a></strong> about <a rel="nofollow" href="https://www.r-project.org/" title="The R Project for Statistical Computing">R</a> news and <a rel="nofollow" href="https://www.r-bloggers.com/search/tutorial" title="R tutorials">tutorials</a> on topics such as: <a rel="nofollow" href="https://www.r-bloggers.com/search/data%20science" title="Data science">Data science</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/Big%20Data" title="Big Data">Big Data, <a rel="nofollow" href="https://www.r-users.com/" title="R jobs">R jobs</a>, visualization (<a rel="nofollow" href="https://www.r-bloggers.com/search/ggplot2" title="ggplot and ggplot2 tutorials">ggplot2</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/boxplot" title="Boxplots using lattice and ggplot2 tutorials">Boxplots</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/map" title="Maps and gis">maps</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/animation" title="Animation in R">animation</a>), programming (<a rel="nofollow" href="https://www.r-bloggers.com/search/RStudio" title="RStudio IDE for R">RStudio</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/sweave" title="Sweave and literate programming">Sweave</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/LaTeX" title="LaTeX in R">LaTeX</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/SQL" title="SQL and databases">SQL</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/eclipse" title="Eclipse IDE for R">Eclipse</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/git" title="git and github, Version Control System">git</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/hadoop" title="Large data in R using Hadoop">hadoop</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/Web+Scraping" title="Web Scraping of google, facebook, yahoo, twitter and more using R">Web Scraping</a>) statistics (<a rel="nofollow" href="https://www.r-bloggers.com/search/regression" title="Regressions and ANOVA analysis tutorials">regression</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/PCA" title="principal component analysis tutorial">PCA</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/time+series" title="Time series">time series</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/trading" title="finance trading">trading</a>) and more...
<img alt="" width="1" src="http://feeds.feedburner.com/~r/RBloggers/~4/5qlOAsFI_xA" height="1"></a></div>