A common question about identifying player tendencies on offense is to ask “how likely is this player to receive the ball during a possession?” This methodology can be aided by the quantity touches. However, a player can touch the ball with what I like to term as an empty touch. These are touches that have no specific aim of scoring, but rather simply forcing ball movement on the court. For instance, consider Brook Lopez (formerly Brooklyn Nets) for the 2016-17 NBA season.
Lopez had 3,837 touches over the course of 75 games last season. Of these touches, Lopez continued on for 1,172 field goal attempts, 364 free throw attempts, 176 assists, and 184 turnovers. When all added up, assuming the usual 0.44 free throw factor on possession ending free throws, Lopez has 1,692 touches that are non-empty. This means that Lopez has over two-thousand empty touches. This isn’t a negative quantity, as Lopez can be completing other necessary tasks on the court; however, we see that more than half the time, Lopez is not looking to score. If we look at scoring attempts compared to assists, we do find that Lopez is thirteen times more likely to score than pass for a score. This, unfortunately, may be either a reflection of Lopez’s passing ability or his teammates’ ability to score.
In lieu of this, we are interested in understanding assists in the NBA. The most common statistic is the count of assists. This is the classic total that is used as a measuring stick to compare point guards in the league. What we could ask is then, who led the Brooklyn Nets in assists last season? After a quick look-up, you’d be quick to say Isaiah Whitehead with 192 assists. But who did Whitehead get those assists to? It’s actually irrationally even across the team: 38 to Brook Lopez, 21 to Sean Kilpatrick, 21 to Justin Hamilton, 20 to Bojan Bogdanovic, and 20 to Rondae Hollis-Jefferson. These five players accounted for 120 of Whitehead’s 192 assists.
Now let’s change this up to look at Jeremy Lin. While Lin finished third on the team with 184 assists, he managed to only participate in 36 total games. However, Lin’s assist distribution looks more like what we expect from a standard point guard: 78 assists to Brook Lopez and 28 assists to Rondae Hollis-Jefferson. That’s 106 of his 184 assists. There are no other teammates over 13 assists.
What this indicates between the two players is this: Lin tends to look for Lopez and Hollis-Jefferson while on offense. This may be a result of shared minutes played, something we can easily check using play-by-play; however, there are two players on the court too. These players reap no benefits of playing with Lin.
Similarly, we see that Whitehead is a result of extra minutes played due to injuries on the team. Despite Whitehead’s highest assist total, Whitehead required twice as many minutes as Lin to obtain nearly as many assists over the season. Whitehead’s even distribution of assists? This is more in step with a role player taking on a bigger role in the offense due to either injury or rotation.
So how can we start to break down assists for the Brooklyn Nets?
Counts: Adjacency Matrices
The first metric we can use is to build an adjacency matrix. This matrix helps us identify interactions between players. To build an adjacency matrix is simple. First, we build an empty matrix of all zeros with the number of players as the number of rows and columns. For the Brooklyn Nets, a total of 21 players obtained an assist this past season. In this case, we build a 21×21 matrix of all zeros.

Python code for generating an empty 21×21 matrix of all zeros.
Then for each game we look for situations where assists were made. When an assist occurs, we identify the row of the player who made the assist and the column of the player who made the basket. In this location, we simply add an assist.
In this situation, we are merely building a dictionary of passer, scorer, team. The team part of the dictionary is to partition players who played on multiple teams. These partitions allow us to focus on when a player played on a particular team; such as DeMarcus Cousins.
Next we wander through the assist dictionary, searching for a particular team of interest. This is what builds the adjacency matrix for our team of interest. In this case, we search the Brooklyn Nets.

Python code that walks through the assist dictionary for the entire NBA and returns the adjacency matrix for a team of interest.
Printing out the assist adjacency matrix for the Nets, we have:

Assist adjacency matrix for the Brooklyn Nets’ 2016-17 NBA season.
We see that the adjacency matrix is a little out of order. This is due to how memory is stored and retrieved in Python. Here, we show the row of the player and the column that is indexed in correct order: 0 through 21. This means the rows are jumbled. After each row, we present the sum of the row; which is the total number of assists. Finally, we output the actual row number of that player. This helps us find the column.

Brooklyn Nets 2016-17 Adjacency Matrix for assists.
Visualization: Digraphs
An adjacency matrix is useful as we can perform basic calculations such as counts and thresholds. Counts along the row identifies the number of assists a player has for the season. Counts along the column identifies the number of field goals a scorer scores off an assist.
Here, we can take the next step and visualize the distribution of assists using graphs. A graph is a picture that represents an adjacency matrix. Typically, the labels for the rows and columns are called vertices. If a nonzero value exists in a row-column pairing, we have what is called an edge. The plotting for vertices and edges is a graph.
If the adjacency matrix is always symmetric, the graph is called undirected. This means that we only care about a relationship between two players. For assists, this would mean everytime a player gets an assist, the scorer also gets an assist. That’s never true. Therefore, we have what is called a directed graph. A directed graph identifies action from one vertex to another. For instance, we see that Jeremy Lin has 11 assists to Joe Harris; while Joe Harris has 2 assists to Jeremy Lin.
Illustratively, a vertex is represented by a dot and an edge between two vertices is usually an arrow; particularly in a digraph. This of this as the assist from vertex being the tail of the arrow and the scored by vertex being the head of the arrow.
Finally, we can use weights to help illustrate the digraph. In this case, we can make the size of the vertex be in relation to the number of assists by the player of interest. The bigger the vertex, the more assists that player has. Similarly, the width of an edge arrow, the more assists from one player to another.
In Python, we leverage the networkx package. This package is a graph package that helps us visualize graphs while being able to compute simple summaries of the graph. Using the Brooklyn Nets as the example, we have the following digraph:

Brooklyn Nets’ assist digraph. Lots of vertices represent one of the largest rosters in the NBA for the 2016-17 NBA season.
Notice that Isaiah Whitehead has the largest vertex, in agreement with his 192 assists. However, other players on the team have a similar sized vertices. Compare this to the Portland Trail Blazers:

We see that Damian Lillard has a much larger vertex than his teammates showing the team disparity in assists. The blank node is due to play-by-play log missing a handful of assists.
Similarly, take a look at Al-Farouq Aminu in the Portland graph. Notice that the lines coming into his vertex are thick. The thick lines indicate the head of the arrows. This identifies incidences where Aminu scored off an assist from another teammate. The longer the thick portion of the line? That’s the more field goals made (weight) that Aminu has received from that assisting teammate.
Let’s isolate Andrew Nicholson from the Brooklyn Nets graph (lower left corner). We see that he has very few arrows coming in to his vertex. To compare the arrows coming in to Nicholson, we simply look at the column of the adjacency matrix for assists.We note that Nicholson had three field goals assisted by Whitehead and Spencer Dinwiddie, two field goals assisted by Justin Hamilton, and one field goal assisted by Quincy Acy and Trevore Booker. We see that there are six edges coming into Nicholson’s vertex. Five of these edges are the assist men mentioned above. There’s one more edge that connects Nicholson to KJ McDaniels. This is the one assist that Nicholson picked up when McDaniels scored. Notice that there is not head of the arrow pointing to Nicholson.
Comparing the remaining five arrows, we see that there are different lengths; three to be exact. The longest two are indeed coming in from Dinwiddie and Whitehead.
Here is the code in Python:
This assigns a networkx graph and adds edges accordingly.
This block of code builds the label dictionary and counts the number assists between players; allowing for weights to be used.
Finally, we draw the graph that you see above. Now let’s do something intelligent with this data.
Community Detection: Who’s Most Likely to Get the Ball
Here’s where the statistics come into play fairly heavily. One question an opposing coach may ask is “How likely will Brooke Lopez get a scoring opportunity from Jeremy Lin?” Advanced scouting is key here as live scouting and video scouting allow a coach to prep against certain offenses. However, we are also able to estimate hockey assists. Assists that are two-or-more passes from origin.
The adjacency matrix helps us with this. We see that players tend to pass to certain teammates for scores. Similarly, those certain players tend to pass to certain players of their own. For instance if Jeremy Lin passes to Brooke Lopez; Lopez may look for a corner three type shooter if the defense collapses. This helps us identify his primary responses that scouting has asked us to look at.
Let’s return to the adjacency matrix for the Brooklyn Nets. This time, we instead change to a probability of passing to another player for an assist. In this case, we have:

Assist probability from (row) passer to (column) scorer.
Now, the second-pass assists are estimated by the square of the adjacency matrix. This gives us:

Estimates second pass assists from the originator (row) to the scorer (column).
In these cases, we interpret this matrix as the following: Brook Lopez has a 22.35% chance of getting the ball back after passing out for a score. Similarly, Greivis Vasquez has effectively no chance of scoring on second pass possessions.
What we start to find are structures within the offense for the Brooklyn Nets. Effectively, this leads us to a concept of Community Detection. That is, which sets of vertices are most likely to interact on the court for baskets made off the pass. Sure, there will be assists made to any and all players. Though players tend to have their favorite targets; especially in practiced offensive sets.
One idea of a simple community detection process is through the use of Bayesian Statistics. Here, we can consider each pass for score as a Bernoulli random variable, where an edge may exist between a passer and a scorer. Let’s consider this probability to be p_in + p_out. That is, the probability of receiving an assist because a shooter is within the community plus the probability that an assist comes to a player not within the community. Think of the decomposition this way: Jeremy Lin will tend to pass to Brook Lopez because he knows Lopez can score and that’s what the offense is looking for. However, there are times where Lin hits teammates for different looks and those teammates make their shots; despite being secondary or tertiary scoring options. This means these players may not be in Lin’s community with Lopez but still may receive assists from the Brooklyn point guard. So this is how we can break down the model for Brooklyn Nets communities:
The value X_ij is the existence of a community edge between two players. the values p_in and p_out are the probabilities of being in and out of a community, respectively. The values c_1, c_2, …, c_n are the community labels for each player. Here we assume n players.
The Bayesian framework here identifies a Bayesian method where we write the distribution of an assist occurring between two players, i and j, following a Bernoulli with probability
The one-values in the probability are indicator functions. If labels c_i and c_j match, then the two players are in the same community. This means p_ij = p_in. Otherwise, they do not match, are not in the same community, and therefore p_ij = p_out.
Prior: CRP = Chinese Restaurant Process
The prior distribution is assumed to be a Chinese Restaurant Process. The name comes from a toy problem of customers entering a Chinese restaurant and having the option of joining a table already filled with patrons or forming their own new table. The assumption here is that no tables can be completely filled and new tables are always available. The distribution function is given as
Here, we have K tables with n_k patrons at table k. The value alpha determines the rate at which a new table is formed. The larger the value of alpha, the more likely a new table is to be selected by a new customer. In this case, we can have anywhere from K=1 to K=n tables.
Hierarchical Priors
Now that we can control the number of communities using alpha through the Chinese Restaurant Process and the probability of a likely assist through the building of p_out and p_in. We would like some flexibility in understanding these values. In this case, we assume hyper-priors. These are distributions to help us not fix particular values of alpha, p_in, and p_out, but rather allow us to learn these three values given data.
In this case, we place Beta distributions on p_in and p_out. We can also impose a natural requirement that p_in > p_out as a player is likely to pass to a teammate they are more comfortable with (in their community) than a teammate that is not part of the focal game plan. The best distributions impose that these probabilities are always between zero and one; which is what we expect.
Similarly, we place a Gamma prior distribution on the alpha of a CRP. This allows us to learn a mean value for alpha that identifies the best number of communities to fit to the data.
Therefore the entire model for fitting communities to the Brooklyn Nets is given by:

Posterior distribution of the community labels (c_1, …, c_n) , probabilities of being in and out of the communities (p_in and p_out), and the new community factor (alpha).
Here, we have the distribution we want given the observed X_ij’s, which are the number of assists between each teammate. By defining a optimal starting value for a_in, b_in, a_out, b_out, a_alpha, b_alpha to help filter the quantities we need, we are able to compute the needed posterior expectation to identify the communities within the Brooklyn Nets’ game play.
Posterior Computation
In the above model for community structure in scoring plays with an assist, I left off the actual distributional functions. This is merely to not completely overwhelm you with technical details. However, these functions: Binomial distribution for each player, Chinese Restaurant Process, Beta-Beta conditional (p_in > p_out), and Gamma distributions (in that order actually) form a posterior distribution where an integral can be computed to help identify the highest probability community structure. This means we have to compute an integral… which we most likely cannot compute in closed form.
Instead, we rely on methods such as posterior sampling: Metropolis-Hastings is probably my most common go-to method for sampling.
Here, we propose dummy values for pi, p_in, p_out, and alpha. Then for each value, we propose a movement: first change the set of class labels as a random draw from the Chinese Restaurant Process. Given the current states of p_in, p_out, and alpha we ask if this move makes sense; ie. is this a probable value given the current state of the sampler? If yes, we accept the move, update pi and move on to proposing a move for p_in. We continue to this process for p_in, p_out, and alpha. At the end of such a run, we start over.
After an initial burn-in period, we keep the values of the sampler. This is because the sampling process is from the posterior distribution in limit. After performing such a task, we simply look at the distribution of the output data and take the sampled expected values. In the case of the Brooklyn Nets, we obtain the following communities:
- Brook Lopez, Jeremy Lin, Rondae Hollis-Jefferson, Sean Kilpatrick, Bojan Bogdanovic
- Trevor Booker, Isaiah Whitehead, Randy Foye, Spencer Dinwiddie, Caris LeVert
- Rest are non-community members
This means we found a total of 13 communities of two 5-player rotations and 11 non-community members. We can visualize the partitioning by sorting the rows and columns of the communities and see the blocking that suggest communities.

Community Profile of Brooklyn Nets
This 2D bar code looking plot helps us identify the community structure. While this looks like one community and rest of floaters, there are really two communities in that big black blob. The grey sections in the off-diagonal capture some of this interaction. The smearing that exists indicates the real problem for the Nets last season: injuries and a large roster of players finding significant playing time.
So what do you think? There’s much more we can do for analyzing assists; however community detection is a fun way to help statistically analyze assist patterns within teams.
Pingback: Evaluating Assists with Python: Community Detection and the Brooklyn Nets — Squared Statistics: Understanding Basketball Analytics | Advance Pro Basketball
Pingback: Weekly Sports Analytics News Roundup - October 17th, 2017 - StatSheetStuffer
Pingback: Understanding the Spatial Tendencies of Assists, the K(t) Test, and the Orlando Magic | Squared Statistics: Understanding Basketball Analytics
Hi, I really liked your analysis of the Nets, especially the adjacency matrix showing assists from player to player. I was wondering how you found this data. I would like to do a similar project to this for my network analysis class but I’m having trouble finding this data. Any help would be greatly appreciated!
HM
LikeLike