A Python library for plotting hockey rinks with Matplotlib.

Overview

Hockey Rink

A Python library for plotting hockey rinks with Matplotlib.

Installation

pip install hockey_rink

Current Rinks

The following shows the custom rinks currently available for plotting.

from hockey_rink import NHLRink, IIHFRink, NWHLRink
import matplotlib.pyplot as plt

fig, axs = plt.subplots(1, 3, sharey=True, figsize=(12, 6), gridspec_kw={"width_ratios": [1, 98.4/85, 1]})
nhl_rink = NHLRink(rotation=90)
iihf_rink = IIHFRink(rotation=90)
nwhl_rink = NWHLRink(rotation=90)
axs[0] = nhl_rink.draw(ax=axs[0])
axs[1] = iihf_rink.draw(ax=axs[1])
axs[2] = nwhl_rink.draw(ax=axs[2])

The NWHL logo comes from the NWHL site.

Customization

There is also room for customization. The image at the top was created as follows:

rink = Rink(rotation=45, boards={"length": 150, "width": 150, "radius": 75})

Rinks also allow for additional features to be added. Custom features should inherit from RinkFeature and override the _get_centered_xy method. The draw method can also be overridden if the desired feature can't be drawn with a matplotlib Polygon, though _get_centered_xy should still provide the feature's boundaries. CircularImage provides an example of this by inheriting from RinkCircle.

If a custom feature is to be constrained to only display within the rink, the returned object needs to have a set_clip_path method.

Plots

There are currently wrappers available for the following Matplotlib plots:
- plot
- scatter
- arrow
- hexbin
- pcolormesh (heatmap in Hockey Rink)
- contour
- contourf

If you'd like to bypass the wrappers, you can convert coordinates to the proper scale with convert_xy:

rink = Rink()
x, y = rink.convert_xy(x, y)

When plotting to a partially drawn surface, the plot will be applied to the entire rink, not what's visible. This can be avoided by setting plot_range (or plot_xlim and plot_ylim) in the plotting functions where they're available.

It's also important to realize that the plotting functions only allow arguments to be passed without keywords for the coordinates.
ie) hexbin(x, y, values) will throw an error.

The correct call is hexbin(x, y, values=values)

Examples

Let's look at some NWHL data via the Big Data Cup.

The first game is Minnesota vs Boston, so we'll go with that and do a scatter plot of each team's shots.

from hockey_rink import NWHLRink
import pandas as pd

df = pd.read_csv("https://raw.githubusercontent.com/bigdatacup/Big-Data-Cup-2021/main/hackathon_nwhl.csv")
game_df = df.loc[(df["Home Team"] == "Minnesota Whitecaps") & (df["Away Team"] == "Boston Pride")]
shots = game_df.loc[(game_df.Event.isin(["Shot", "Goal"]))]
boston_shots = shots[shots.Team == "Boston Pride"]
minnesota_shots = shots[shots.Team == "Minnesota Whitecaps"]
rink = NWHLRink(x_shift=100, y_shift=42.5)
ax = rink.draw()
rink.scatter(boston_shots["X Coordinate"], boston_shots["Y Coordinate"])
rink.scatter(200 - minnesota_shots["X Coordinate"], 85 - minnesota_shots["Y Coordinate"])

Extending the example, let's look at all of Boston's passes.

boston_passes = game_df.loc[(game_df.Team == "Boston Pride") & (game_df.Event == "Play")]
ax.clear()
rink.draw()
arrows = rink.arrow(boston_passes["X Coordinate"], boston_passes["Y Coordinate"], 
                    boston_passes["X Coordinate 2"], boston_passes["Y Coordinate 2"], color="yellow")

For some of the other plots, let's look at some NHL shooting percentages.

To mix things up a little, binsize will take different values in each plot and the heatmap won't include shots from below the goal line. We'll also throw in a colorbar for the contour plot.

from hockey_rink import NHLRink
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

pbp = pd.read_csv("https://hockey-data.harryshomer.com/pbp/nhl_pbp20192020.csv.gz", compression="gzip")
pbp["goal"] = (pbp.Event == "GOAL").astype(int)
pbp["x"] = np.abs(pbp.xC)
pbp["y"] = pbp.yC * np.sign(pbp.xC)
shots = pbp.loc[(pbp.Ev_Zone == "Off") & ~pbp.x.isna() & ~pbp.y.isna() & (pbp.Event.isin(["GOAL", "SHOT", "MISS"]))]

fig, axs = plt.subplots(1, 3, figsize=(14, 8))
rink = NHLRink(rotation=270)
for i in range(3):
    rink.draw(ax=axs[i], display_range="ozone")
contour_img = rink.contourf(shots.x, shots.y, values=shots.goal, ax=axs[0], cmap="bwr", 
                            plot_range="ozone", binsize=10, levels=50, statistic="mean")
plt.colorbar(contour_img, ax=axs[0], orientation="horizontal")
rink.heatmap(shots.x, shots.y, values=shots.goal, ax=axs[1], cmap="magma",
             plot_xlim=(25, 89), statistic="mean", vmax=0.2, binsize=3)
rink.hexbin(shots.x, shots.y, values=shots.goal, ax=axs[2], binsize=(8, 12), plot_range="ozone", zorder=25, alpha=0.85)

Inspiration

This project was partly inspired by mplsoccer.

Hopefully, it can lower a barrier for someone looking to get involved in hockey analytics.

Contact

You can find me on twitter @the_bucketless or email me at [email protected] if you'd like to get in touch.

You might also like...
🎨 Python3 binding for `@AntV/G2Plot` Plotting Library .
🎨 Python3 binding for `@AntV/G2Plot` Plotting Library .

PyG2Plot 🎨 Python3 binding for @AntV/G2Plot which an interactive and responsive charting library. Based on the grammar of graphics, you can easily ma

Plotting library for IPython/Jupyter notebooks
Plotting library for IPython/Jupyter notebooks

bqplot 2-D plotting library for Project Jupyter Introduction bqplot is a 2-D visualization system for Jupyter, based on the constructs of the Grammar

An open-source plotting library for statistical data.
An open-source plotting library for statistical data.

Lets-Plot Lets-Plot is an open-source plotting library for statistical data. It is implemented using the Kotlin programming language. The design of Le

Plotting library for IPython/Jupyter notebooks
Plotting library for IPython/Jupyter notebooks

bqplot 2-D plotting library for Project Jupyter Introduction bqplot is a 2-D visualization system for Jupyter, based on the constructs of the Grammar

An open-source plotting library for statistical data.
An open-source plotting library for statistical data.

Lets-Plot Lets-Plot is an open-source plotting library for statistical data. It is implemented using the Kotlin programming language. The design of Le

A deceptively simple plotting library for Streamlit

🍅 Plost A deceptively simple plotting library for Streamlit. Because you've been writing plots wrong all this time. Getting started pip install plost

Simple plotting for Python. Python wrapper for D3xter - render charts in the browser with simple Python syntax.
Simple plotting for Python. Python wrapper for D3xter - render charts in the browser with simple Python syntax.

PyDexter Simple plotting for Python. Python wrapper for D3xter - render charts in the browser with simple Python syntax. Setup $ pip install PyDexter

Cartopy - a cartographic python library with matplotlib support
Cartopy - a cartographic python library with matplotlib support

Cartopy is a Python package designed to make drawing maps for data analysis and visualisation easy. Table of contents Overview Get in touch License an

Some examples with MatPlotLib library in Python

MatPlotLib Example Some examples with MatPlotLib library in Python Point: Run files only in project's directory About me Full name: Matin Ardestani Ag

Comments
  • AttributeError: 'NoneType' object has no attribute 'xmin'

    AttributeError: 'NoneType' object has no attribute 'xmin'

    I got always this error.

    fig, axs = plt.subplots(figsize=(14, 8))
    rink = NHLRink(rotation=270)
    rink.draw(ax=axs, display_range="ozone")
    rink.hexbin(data.x, data.y, ax=axs, binsize=(8, 12), plot_range="ozone", zorder=25, alpha=0.85)
    
    <matplotlib.collections.PolyCollection at 0x126a98950>
    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
    File /Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/IPython/core/formatters.py:338, in BaseFormatter.__call__(self, obj)
        336     pass
        337 else:
    --> 338     return printer(obj)
        339 # Finally look for special method names
        340 method = get_real_method(obj, self.print_method)
    
    File /Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/IPython/core/pylabtools.py:152, in print_figure(fig, fmt, bbox_inches, base64, **kwargs)
        149     from matplotlib.backend_bases import FigureCanvasBase
        150     FigureCanvasBase(fig)
    --> 152 fig.canvas.print_figure(bytes_io, **kw)
        153 data = bytes_io.getvalue()
        154 if fmt == 'svg':
    
    File /Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/matplotlib/backend_bases.py:2318, in FigureCanvasBase.print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
       2316 if bbox_inches:
       2317     if bbox_inches == "tight":
    -> 2318         bbox_inches = self.figure.get_tightbbox(
       2319             renderer, bbox_extra_artists=bbox_extra_artists)
       2320         if pad_inches is None:
       2321             pad_inches = rcParams['savefig.pad_inches']
    
    File /Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/matplotlib/figure.py:1734, in FigureBase.get_tightbbox(self, renderer, bbox_extra_artists)
       1731     artists = bbox_extra_artists
       1733 for a in artists:
    -> 1734     bbox = a.get_tightbbox(renderer)
       1735     if bbox is not None:
       1736         bb.append(bbox)
    
    File /Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/matplotlib/axes/_base.py:4451, in _AxesBase.get_tightbbox(self, renderer, call_axes_locator, bbox_extra_artists, for_layout_only)
       4448     bbox_artists = self.get_default_bbox_extra_artists()
       4450 for a in bbox_artists:
    -> 4451     bbox = a.get_tightbbox(renderer)
       4452     if (bbox is not None
       4453             and 0 < bbox.width < np.inf
       4454             and 0 < bbox.height < np.inf):
       4455         bb.append(bbox)
    
    File /Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/matplotlib/artist.py:345, in Artist.get_tightbbox(self, renderer)
        343     if clip_path is not None:
        344         clip_path = clip_path.get_fully_transformed_path()
    --> 345         bbox = Bbox.intersection(bbox, clip_path.get_extents())
        346 return bbox
    
    File /Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/matplotlib/transforms.py:666, in BboxBase.intersection(bbox1, bbox2)
        660 @staticmethod
        661 def intersection(bbox1, bbox2):
        662     """
        663     Return the intersection of *bbox1* and *bbox2* if they intersect, or
        664     None if they don't.
        665     """
    --> 666     x0 = np.maximum(bbox1.xmin, bbox2.xmin)
        667     x1 = np.minimum(bbox1.xmax, bbox2.xmax)
        668     y0 = np.maximum(bbox1.ymin, bbox2.ymin)
    
    AttributeError: 'NoneType' object has no attribute 'xmin'
    
    <Figure size 1400x800 with 1 Axes>
    
    opened by rudiruhl 5
  • Recommended Approach: Creating GIfs

    Recommended Approach: Creating GIfs

    Your library is fantastic. I have successfully built out a process that lets me run through a series of data and via .scatter I can plot the location of events. The issue is that the build process is painfully slow.

    I have attempted various solutions (cla, clf, ax.clear). While some of my attempts certainly sped up the build, I lost the plot of the rink. As you can likey tell from this question, I am not the most robust user of matplotlib.

    This is not an issue with your library of course, but I am wondering if you have any recommendations on we might be able to use the rink, perhaps plot data via .scatter, and then clear out the scatter for the next "frame" being plotted. I believe that the slowness in the build process is due to re-drawing the rink, which is why I am wondering if you have any suggestions on how to draw the rink just once in a loop.

    Cheers.

    opened by Btibert3 2
Owner
null
:small_red_triangle: Ternary plotting library for python with matplotlib

python-ternary This is a plotting library for use with matplotlib to make ternary plots plots in the two dimensional simplex projected onto a two dime

Marc 611 Dec 29, 2022
:small_red_triangle: Ternary plotting library for python with matplotlib

python-ternary This is a plotting library for use with matplotlib to make ternary plots plots in the two dimensional simplex projected onto a two dime

Marc 391 Feb 17, 2021
MPL Plotter is a Matplotlib based Python plotting library built with the goal of delivering publication-quality plots concisely.

MPL Plotter is a Matplotlib based Python plotting library built with the goal of delivering publication-quality plots concisely.

Antonio López Rivera 162 Nov 11, 2022
matplotlib: plotting with Python

Matplotlib is a comprehensive library for creating static, animated, and interactive visualizations in Python. Check out our home page for more inform

Matplotlib Developers 16.7k Jan 8, 2023
matplotlib: plotting with Python

Matplotlib is a comprehensive library for creating static, animated, and interactive visualizations in Python. Check out our home page for more inform

Matplotlib Developers 13.1k Feb 18, 2021
🎨 Python Echarts Plotting Library

pyecharts Python ❤️ ECharts = pyecharts English README ?? 简介 Apache ECharts (incubating) 是一个由百度开源的数据可视化,凭借着良好的交互性,精巧的图表设计,得到了众多开发者的认可。而 Python 是一门富有表达

pyecharts 13.1k Jan 3, 2023
🎨 Python Echarts Plotting Library

pyecharts Python ❤️ ECharts = pyecharts English README ?? 简介 Apache ECharts (incubating) 是一个由百度开源的数据可视化,凭借着良好的交互性,精巧的图表设计,得到了众多开发者的认可。而 Python 是一门富有表达

pyecharts 10.6k Feb 18, 2021
termplotlib is a Python library for all your terminal plotting needs.

termplotlib termplotlib is a Python library for all your terminal plotting needs. It aims to work like matplotlib. Line plots For line plots, termplot

Nico Schlömer 553 Dec 30, 2022
Plotting library for IPython/Jupyter notebooks

bqplot 2-D plotting library for Project Jupyter Introduction bqplot is a 2-D visualization system for Jupyter, based on the constructs of the Grammar

null 3.4k Dec 29, 2022
An intuitive library to add plotting functionality to scikit-learn objects.

Welcome to Scikit-plot Single line functions for detailed visualizations The quickest and easiest way to go from analysis... ...to this. Scikit-plot i

Reiichiro Nakano 2.3k Dec 31, 2022