Correcting Player Positions

Published

September 30, 2022

NHL clubs will commonly ice a roster consisting of 18 skaters; 4 lines from 12 forwards F and 3 pairs of defenders D. A Center C, Left Wing LW, and Right Wing RW form a line. Left Defence LD and Right Defence RD form a pair. During the even strength portions of a game a team will have 5 skaters on the ice. Predominantly one from each position. While some teams opt to tinker with this set up, we expect the total number of C, LW and RW games played to be balanced. For the 2021/2022 regular season we see this is not the case.

Position Counts %
D 15903 0.336871
C 15794 0.334562
LW 9096 0.192679
RW 6415 0.135888

Positions are hardly ever updated once a player joins the league. This leads to some untrustworthy situations. Consider the game between the Ducks and Hawks on March 23rd 2022 (gameId: 2021021018). By the submitted roster sheet, Anaheim played with 1 left wing, 1 right wing and a cast of 10 centers. On top of that, the league doesn’t distinguish between left and right defenders. It is commonly brought into question how well a defender fairs playing on their off hand, or how shallow the league’s RD depth is. Corrected position labels can settle these questions and other roster considerations. Our goal is to assigned one of the five positions to each skater observed in each game for the 2021/2022 regular season.

Data

The National Hockey League recorded the following information for every game in the 2021/2022 season:

  • Players: Who played, for what team and at what position.
  • Events: A list of events that occurred. Who was involved, when it happened and the integer coordinates where it took place. Note the following agent descriptors derived from the event type in bracket:
    • Faceoff (Faceoff)
    • Shooter (Missed Shot, Blocked Shot, Shot, Goal)
    • Hitter (Hit)
    • Hittee (Hit)
    • Giveaway (Giveaway)
    • Takeaway (Takeaway)
    • Blocker (Blocked Shot)
  • Shifts: Who was on the ice for each second of the game.

x, y axes for event coordinates. From a players point of view the opposition’s net is always placed on the positive side of the x-axis.

Rates per sixty minutes for each position in the 2021/2022 season. Contour plots inspired by at IneffectiveMath on twitter

Tampa Bay’s shift data for the opening game. (gameId: 2021020001)

Methodology

Instead of clustering all positions at once, we take a top-down hierarchical approach. First skaters are partitioned into forwards and defenders. Then defenders into LD and RD. Then forwards into centers and wingers, and finally wingers into LW and RW.

Forwards vs. Defenders

Define the n by n matrix \(\Delta\) whose \((i, j)\)-th entry corresponds to the number of seconds teammates \(i\) and \(j\) shared on the ice at even strength in a game. The diagonal entries refer to each player’s total ice time. \(\Delta\) can be constructed using the shift data. Let \(B\) be the binary matrix obtained from the shift chart (refer to above image), then \(\Delta=BB^T\). From \(\Delta\), we compute \(\delta\) by dividing each row by its diagonal entry, so each entry refers to the proportion of a player’s time spent with another. For convenience we set the diagonal elements of \(\delta\) to zero. Each player is always accompanied by 4 teammates so every row will sum to 4. Following this thought, consider just the columns corresponding to defenders, \(\delta^D\). The sum of a forward’s row will be very close to 2; and very close to 1 for a defender. Then the binary vector \(x\) which minimizes ${i=1}^{n} | 2-x_i-^D{i}x | $ exhibits our F/D labels (\(x_i = 1\) indicating the skater is a defender). I’ve opted to solve this heuristically, via a steepest ascent local search. Neighbourhoods are defined by at most 2 label mutations from the current candidate solution. While the search is robust to choice of starting location, the provided D and F labels are quite reliable.

Tampa Bay’s shift data for the opening game of the 2021/2022 season presented in matrix form

72 instances required swapping defenders to forwards, none in the other direction. One was the result of Robert Burtuzzo moving from D to F after the first period - caught with a row sum near 1.6. Which prompts the question, how will we handle mid game role changes? For simplicity, and issues detailed ahead, we restrict the problem to one label per player in each game.

Left vs. Right Defenders

We proceed in a similar fashion to classical expectation-maximization algorithms for mixture models. EM is an iterative algorithm that is useful for handling missing data. While defender labels are truly missing, the forwards are so messed up it’s in our best interest to treat them so.

Defenders are assigned an initial label using a biased coin, 51% in favour of whichever side of the \(y = 0\) line they appeared more often on in the game. This gentle nudge is only necessary to avoid any manual aliasing afterwards. The algorithm is described below in detail. In the next section we will adjust it to cluster the forwards.

Step 1. Naive Bayes Classifier

Let \(x_{ij}\) be a vector encoding the counts of each action \(a\) at each coordinate \((x,y)\) conducted by player \(i\) in game \(j\). For example, in the \(j\)-th game of the season, player \(i\) recorded one shot at center ice, thus \(x_{ijk}=1\) where \(k=(\text{Shooter},(0,0))\) otherwise 0. We can think of each \(x_{ijk}\) as generated independently from a poisson distribution with mean \(n_{ij}\theta_{pk}\) where \(p\) identifies the player’s position. Parameter \(\theta_{pk}\) defines the rate at which \(k\) occurs per 60 minutes. \(n_{ij}\) is the constant exposure or player \(i\)’s ice time in game \(j\) divided by 60 minutes. Then we can model the entire population of defenders as a multivariate poisson mixture with two components for LD and RD.

The conditional likelihood of \(x_{ij}\) given it has label LD is:

\[f(x_{ij} | p=LD) = \prod_{k=1}^K \frac{(n_{ij}\theta_{LD,k})^{x_{ijk}}e^{-n_{ij}\theta_{LD,k}}}{x_{ijk}!}\]

Following bayes rule the posterior likelihood is:

\[f(LD | x_{i,j}) \propto f(LD) f(x_{ij} | LD)\]

where \(f(LD)\) is the prior probability. Prior to any available information, both positions are considered to be equally likely, so \(f(RD) = f(LD) = .5\).

Finally, the log posterior odds of the two positions is:

\[ \begin{aligned} \alpha_{i,j} &= \log \frac{f(LD | x_{i,j})}{f(RD | x_{i,j})} \\ &= \sum x_{ijk} \log \frac{\theta_{LD,k}}{\theta_{RD,k}} + n_{i,j} \sum(\theta_{RD,k} - \theta_{LD,k})\\ \end{aligned} \]

which determines membership for each sub-population. A positive \(\alpha_{i,j}\) means the player is more likely to be LD.

The log odds require estimating the rate parameters, which in itself calls for existing labels. A typical solution using EM tumbles between the two calculations, with each update hopefully bringing us closer to convergence. At this point we’ve only utilized event data. A satisfying solution will pair both event and shift sources, and we address that in step 2.

Estimating the rate parameters

It can be helpful to visualize the \(\theta_{p,k}\)’s on a set of grids for each action, seen previously with the contour plots.

  1. For each position and action store the total counts in matrix \(M_p\), whose rows and columns coincide with the (x,y) coordinates.

Faceoffs are omitted for defenders. None were recorded, nor do we except them to hold any information for the position.

  1. Add the matrix corresponding to its mirrored position flipped along the \(y=0\) entries to \(M_p\). Add corresponding total exposure times \(N_p\) as well. \[M_{LD}'[x,y]=M_{LD}[x,y] + M_{LD}[x,-y]\]

    \[N_{LD}'=N_{LD} + N_{RD}\]

This step enforces symmetry between the two positions and sets any action on the \(y=0\) line to have zero sway. Now if a player takes two shots at \((x,-10)\) and \((x,10)\) they will cancel each other out. It has the added benefit of equating the total rates for each action, setting the terms \(\sum(\theta_{RD,k} - \theta_{LD,k}) = 0\). If we were given an event at an undisclosed location it provides no evidence. This is a wanted consequence, as it prevents clustering based on archetypes. For example “stay at home” defenders tend to have high hit and block rates but low shooting rates. The opposite is true for “offensive” defenders.

  1. Apply a Kernel Smoothing Method to each matrix.

To avoid zero frequency problems we add a pseudo count of \(\frac{N_p}{N_{\text{base class}}}\) to each count (The fraction preserves a one to one ratio after calculating rates). Afterwards a kernel smoothing method is applied to \(M_p'\) to incorporate spatial dependency amongst \(\theta\)’s. The main idea here is to apply regularization techniques, commonly found in generalized additive models, while retaining the benefits of our naive bayes classifier, predominantly speed.

At the moment I’ve using a gaussian kernel with \(\sigma = 5\) based on visual inspection. A more principled approach, such as selection through cross validation, is left to future work. However, current results have been impartial to alternative choices. I’ve included a viz for \(\sigma = 20\) at the bottom of the article. It will be worth looking into adaptive kernels, whose bandwidth fluctuates to accommodate sparse regions of the rink. Recall our log odds formula; our aim isn’t an accurate estimate of each position’s rate, but of the ratio between classes. It is the ratio which dictates the separation of classes. When count data is sparse for either position the estimated ratio can be highly variable and our pseudo count - which in some way influences the results like a prior - may have a stronger than intended effect.

  1. Obtain the rates per 60 minutes by dividing each count by exposure \(N_{p}'\)

Step 2. Pairwise Comparisons

Due to the nature of the problem, whenever two teammates are on the ice we wish to compare them to determine who gets what label. The Bradley-Terry model is a standard approach to model pairwise comparisons. It’s commonly used in sports to model team strength from W-L records. The Davidson model is an extension to incorporate ties. Here we’ll treat winning as being assigned LD while sharing the ice with a RD. A tie refers to both players sharing the same label. The probabilities for each outcome is given as:

  • \[ p(i=LD, j=RD) = \frac{e^{\alpha_i}}{e^{\alpha_i}+e^{\alpha_j}+e^{z + .5(\alpha_i+\alpha_j)}}\]

  • \[ p(i=j) = \frac{e^{z + .5(\alpha_i+\alpha_j)}}{e^{\alpha_i}+e^{\alpha_j}+e^{z + .5(\alpha_i+\alpha_j)}}\]

  • \[ p(i=RD, j=LD) = \frac{e^{\alpha_j}}{e^{\alpha_i}+e^{\alpha_j}+e^{z + .5(\alpha_i+\alpha_j)}}\]

Where \(z \in (-\infty,\infty)\) determines the probability of a tie. We set \(z=0\) which seems reasonable enough for our purposes. \(\alpha_i\) represents player \(i\)’s “strength” or evidence for playing on the left side. The \(\alpha\)’s are usually fit to the response data, but for this clustering problem we take them to be the log odds computed previously. I think I’m getting away with this since both the Davidson strength terms and log odds are relative values.

Since pairwise comparisons only occur between teammates and only across one game, we can compartmentalize the labelling process accordinglyy. We find labels \(l\) such that log likelihood of our pairwise comparison model weighted by the time each pair spent on the ice is maximized:

\[ \sum_{i<j}\Delta[i,j]*log(p(i=l_i, j=l_j))\]

Furthermore, we impose a constraint on \(l\) such that the size of the majority can only surpass the minority class by 1. This enforces our expectations of roster construction. The most defenders a team has iced this season is 7; which has 70 possible configurations of LD & RD. This makes brute force is a viable option.

Step 1 and 2 are repeated until the labels converge or capped after a certain number of iterations.

The histogram for strength terms between left and right defenders.

Incorporating Correlation

Event data is like splicing a video stream into snapshots of key action moments. Key moments - which might be less discriminative than the times a player is simply floating around - are rare enough a single game’s worth doesn’t ensure success. The most extreme example being Buffalo’s entire D core registering a single even strength event in (gameId: 2021020566); Boston’s Curtis Lazar was generous enough to hit Casey Fitzgerald. Conversely, events can be misleading. It’s not uncommon for players to have “off” games while their team does not provide enough to mitigate the faulty evidence.

The good news is that we don’t have to restrict ourselves to one game worth of information. It is reasonable to expect that players prefer the same roles throughout the season. Say a pair play 20 games in one orientation, how much evidence do you need to be convinced they swapped roles for the next game? To stay within our framework, I’ve decided to apply a weighted aggregate to feature vectors. Each \(x_{i,j} = \sum_{i',j'}w_{i',j'}x_{i,j}\) where \(w\) should resemble our intuition of which responses are correlated. This leads to a bevy of options. For example, a weighting can be proportional to the ice time given to the player in each game. This results in players having a constant \(\alpha\) throughout the season. It would be equivalent to a set ranking for role priority. Another option is to only weigh in games where the player spends the majority of the time with common linemates. Lines can be determined by a graph constructed from \(\delta\). Let an edge \({i,j}\) exist if \(\delta[i,j]\) and \(\delta[j,i]\) are greater than some threshold. Whittle down the threshold until you like what you see. Then all disconnected components which are triangles form F lines, all arcs form D pairs. For extra measure, restrict labels to avoid conflicts within each component.

Histogram of log odds for 4 prominent members of Toronto’s Defence. Notice Morgan Rielly, who I contest played every game as LD strictly due to seniority, has some games suggesting otherwise. His partner for most of the year, TJ Brodie is flexible but indulges in the left side whenever the pair is split.

Let’s compare the results Toronto’s defence with and without aggregating. I prefer the latter, which exhibits a cleaner separation at the cost of paving over any edge cases.

Single Game Strength Season Strength Aggregate Linemate Strength Aggregate
Name LD RD α LD RD LD RD
Jake Muzzin 47 0 3.57 47 0 47 0
Kristians Rubins 3 0 3.3 3 0 3 0
Mark Giordano 19 1 2.97 20 0 19 1
Morgan Rielly 76 6 2.32 82 0 82 0
Carl Dahlstrom 3 0 2.07 3 0 3 0
Rasmus Sandin 43 8 1.25 49 2 49 2
Travis Dermott 23 20 0.32 19 24 20 23
TJ Brodie 28 54 0.31 23 59 23 59
Ilya Lyubushkin 2 29 -1.47 0 31 1 30
Timothy Liljegren 3 58 -2.08 0 61 0 61
Justin Holl 0 69 -2.85 0 69 0 69
Alex Biega 0 2 -3.84 0 2 0 2

Centers vs. Wingers

Some slight adjustments are made to accommodate forwards. At each pass of the algorithm forwards are clustered into center and winger groups, then wingers into left and right. LW and RW labels are assigned similarly to defenders. Separating out centers is a bit different and we detail the changes below.

  • Faceoffs are included, but since they only occur in 9 locations we do not apply any KDE, though there is merit to jacking up the pseudo count to balance its effect against the other smoothed over actions.

  • When reflecting the counts, mirror center counts onto themselves.

  • Normalize the count rates for each action except Faceoffs. Essentially this creates a debt which centers can easily work out of by taking faceoffs. Thus if no actions are taken, the player is more likely to be a winger.

  • Calculate the log odds between center and the two winger classes as:

\[\begin{aligned} \beta_{i,j} &= log \frac{f(C | x_{i,j})}{f(LW | x_{i,j})+f(RW | x_{i,j})} \\ &= log\frac{\prod e^{-n_{i,j}\theta_{C,k}}(n_{i,j}\theta_{C,k})^{x_ijk}}{\prod e^{-n_{i,j}\theta_{LW,k}}(n_{i,j}\theta_{LW,k})^{x_ijk} + \prod e^{-n_{i,j}\theta_{RW,k}}(n_{i,j}\theta_{RW,k})^{x_ijk}}\\ &= \sum x_{ijk}log \theta_{C,k} - n_{i,j} \sum\theta_{C,k} - log (\prod e^{-n_{i,j}\theta_{LW,k}}\theta_{LW,k}^{x_ijk} + \prod e^{-n_{i,j}\theta_{RW,k}}\theta_{RW,k}^{x_ijk})\\ &= \sum x_{ijk}log \theta_{C,k} - n_{i,j} \sum\theta_{C,k} - log (\prod e^{-n_{i,j}\theta_{LW,k}}(\prod\theta_{LW,k}^{x_ijk} + \prod\theta_{RW,k}^{x_ijk}))\\ &= \sum x_{ijk}log \theta_{C,k} - n_{i,j} \sum(\theta_{C,\text{Faceoffs}} - \theta_{LW,\text{Faceoffs}}) - log (e^{\sum x_{ijk}log \theta_{LW,k}} + e^{\sum x_{ijk}log \theta_{RW,k}})\\ \end{aligned} \]

  • Calculate the log likelihood using the Davidson-Luce Model for groupwise comparisons [1]. Compare every combination of three forwards. Once again weigh by shared ice time. Limit the amount of C labels assigned in one game between \(\lfloor \frac{\text{\# of forwards}}{3}\rfloor\) and \(\lceil \frac{\text{\# of forwards}}{3}\rceil\).

    • \[ p(l_i=C, l_j=W, l_k=W) \propto e^{\beta_i}\]
    • \[ p(l_i=W, l_j=C, l_k=W) \propto e^{\beta_j}\]
    • \[ p(l_i=W, l_j=W, l_k=C) \propto e^{\beta_k}\]
    • \[ p(l_i=C, l_j=C, l_k=W) \propto e^{z_2 + \frac{1}{2}(\beta_i + \beta_j)}\]
    • \[ p(l_i=C, l_j=W, l_k=C) \propto e^{z_2 + \frac{1}{2}(\beta_i + \beta_k)}\]
    • \[ p(l_i=W, l_j=C, l_k=C) \propto e^{z_2 + \frac{1}{2}(\beta_j + \beta_k)}\]
    • \[ p(l_i=W, l_j=W, l_k=W) \propto e^{z_3 + \frac{1}{3}(\beta_i + \beta_j + \beta_k)}\]
    • \[ p(l_i=C, l_j=C, l_k=C) \propto e^{z_3 + \frac{1}{3}(\beta_i + \beta_j + \beta_k)}\]

The histogram for log odds between center and winger. The mixture clearly exhibits two components, it’s easy to spot the pretenders immediately. The final distribution for centers is wide due to the weight of faceoffs. Modelling faceoff rates as a poisson distribution may not be the best choice, since coaches try to allocate them based on preference.

The histogram for log odds between left and right wingers, a sad site.

Once again, let’s look at the results for Toronto. The winger labels are suspect until we aggregate games. Season and linemate weightings tend to agree. When they do not, manual inspection typically favours the latter when the line has played plenty of games together.

Single Game Strength Season Strength Aggregrate Linemate Strength Aggregrate
Name C LW RW Β α C LW RW C LW RW
Auston Matthews 73 0 0 27.57 2.47 73 0 0 73 0 0
John Tavares 79 0 0 22.8 1.01 79 0 0 79 0 0
David Kampf 82 0 0 19.31 0.95 82 0 0 82 0 0
Jason Spezza 61 1 9 9.54 -2.61 70 0 1 62 3 6
Brett Seney 2 0 0 0.55 1.72 1 1 0 2 0 0
Colin Blackwell 11 0 8 -3.1 -1.58 5 0 14 9 0 10
Nicholas Abruzzese 1 5 3 -4.95 0.03 1 7 1 1 6 2
Kirill Semyonov 1 1 1 -5.77 0.28 0 3 0 1 1 1
Alexander Kerfoot 12 53 17 -6.24 0.7 10 72 0 12 54 16
Alex Steeves 0 0 3 -6.31 -0.27 2 0 1 0 0 3
Michael Amadio 0 1 2 -6.35 -0.99 0 0 3 0 3 0
Kyle Clifford 1 15 7 -6.73 0.22 3 15 5 1 19 3
Wayne Simmonds 0 31 41 -7.51 -0.23 1 11 60 0 9 63
Pierre Engvall 4 43 31 -7.94 0.17 0 59 19 4 44 30
Nicholas Robertson 0 7 3 -7.99 -0.04 0 8 2 0 9 1
Joey Anderson 0 1 4 -8.13 -0.19 0 0 5 0 0 5
Nick Ritchie 0 25 8 -8.94 0.48 0 33 0 0 33 0
Ondrej Kase 0 11 39 -9.21 -0.41 0 3 47 0 2 48
William Nylander 1 26 54 -9.34 -0.54 0 0 81 1 16 64
Ilya Mikheyev 0 35 18 -9.82 0.25 0 38 15 0 52 1
Michael Bunting 0 51 28 -10.72 0.21 0 77 2 0 76 3
Mitchell Marner 0 21 51 -11.3 -0.18 0 1 71 0 0 72

As for the entire league, 36229 out of 47208 come to a concensus for all three options. The remaining conflicts are resolved in the following manner. First by majority vote, then by sorting out the remaining labels for each game and team with the single game strength terms.

Concluding Remarks

There remains a lot to be tinkered with. Adding penalties, separating wrap-arounds or other secondary types from shots, partitioning the rink’s grid by zone before smoothing, possibly some more denoising… I suspect most to be fruitless. The main sticking point is distilling winger labels. Diminishing the additive smoothing or pushing the KDE to produce more discriminative ratios leads to similar yet murky results. Forwards tend to cross over the \(y = 0\) line enough to require more spatial sampling for consistency. The only way I’ve found to overcome this is by feeding season data into the single game strength terms, in perhaps the most unprincipled manner (please feel free to lecture me on my bad behaviour). I subsist this provides close to ideal results without manual inspection. It seems adequate if your goal is to get positional eligibility status for fantasy hockey. Keep scrolling down for results.

You can reach me on twitter at yimmymcbill if you have suggestions, there is certainly room for improvement!

I’ve provided a summary for each team in the links below.

Anaheim Ducks Arizona Coyotes Boston Bruins Buffalo Sabres Calgary Flames Carolina Hurricanes Chicago Blackhawks Colorado Avalanche Columbus Blue Jackets Dallas Stars Detroit Red Wings Edmonton Oilers Florida Panthers Los Angeles Kings Minnesota Wild Montréal Canadiens Nashville Predators New Jersey Devils New York Islanders New York Rangers Ottawa Senators Philadelphia Flyers Pittsburgh Penguins San Jose Sharks Seattle Kraken St. Louis Blues Tampa Bay Lightning Toronto Maple Leafs Vancouver Canucks Vegas Golden Knights Washington Capitals Winnipeg Jets

References

[1] Firth, D., Kosmidis, I., & Turner, H. (2019). Davidson-Luce model for multi-item choice with ties. arXiv preprint arXiv:1909.07123.

Defender Results

The final estimates for the coefficient-like terms for the event counts in the posterior odds ratio formula. The log of the LD over RD rate parameters.

The final Rates per sixty minutes for left and right defenders

Forward Results

The final estimates for the coefficient-like terms for the event counts in the posterior odds ratio formula between two of the three forward positions.

An example using Gaussian KDE with \(\sigma\) cranking up to 20.

The final Rates per sixty minutes for all forward positions

Forwards Labelled as Defenders

playerId fullName gameId
8474145 Robert Bortuzzo 2021020311
8474722 Luke Witkowski 2021020939
8475625 Matt Irwin 2021020469
8476372 Nick Seeler 2021020739
8476470 Nathan Beaulieu 2021020314
8476779 Brad Hunt 2021021152
8477073 Kurtis MacDermid 2021020299, 2021020321, 2021020399, 2021020415, 2021020433, 2021020490, 2021020580, 2021020591, 2021020606, 2021020622, 2021020634, 2021020641, 2021020663, 2021020805, 2021020826, 2021020836, 2021020857, 2021020871, 2021020886, 2021020962, 2021020982, 2021021119
8477335 Kyle Burroughs 2021021175, 2021021192
8477419 Mason Geertsen 2021020043, 2021020056, 2021020090, 2021020226, 2021020327, 2021020360, 2021020422, 2021020529, 2021020586, 2021020631, 2021020666, 2021020817, 2021020875, 2021020904, 2021020921, 2021020935, 2021020966, 2021020989, 2021021005, 2021021074, 2021021274
8477851 Jordan Oesterle 2021020472, 2021021101, 2021021222, 2021021235
8477938 Haydn Fleury 2021020967
8478013 Jake Walman 2021020394, 2021020547
8478017 Mark Friedman 2021021029
8479372 Josh Mahura 2021020907
8479376 Victor Mete 2021021199
8479439 Jacob MacDonald 2021020105, 2021020122, 2021020145
8479639 Dylan Coghlan 2021021073, 2021021126
8480160 Radim Simek 2021021171
8480884 Calen Addison 2021020344, 2021020599
8481003 Hunter Drew 2021021285, 2021021307
8482624 Daniil Miromanov 2021020100