hockey-rink


Namehockey-rink JSON
Version 1.0.2 PyPI version JSON
download
home_pagehttps://github.com/the-bucketless/hockey_rink
SummaryA Python library for plotting hockey rinks with Matplotlib.
upload_time2023-06-03 23:35:42
maintainer
docs_urlNone
authorThe Bucketless
requires_python
licenseGNU General Public License v3 (GPLv3)
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            ![](images/hockey-rink-logo.png)

A Python library for plotting hockey rinks with Matplotlib.

## Installation
```pip install hockey-rink```

## Current Rinks

The following rinks are available for use:
- Rink
- NHLRink
- NWHLRink (from the 2021 Lake Placid games)
- IIHFRink
- OldIIHFRink

## Customization
There is also room for customization. For example, to change the dimension of a rink, update the length and/or width of the boards:

```
rink = Rink(boards={"length": 150, "width": 150, "radius": 75})
```
  
![](images/circular-rink.png)

Each rink comes with a default set of features, but additional features can 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. The RinkImage
feature provides an example of this by inheriting from RinkRectangle.

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 plotting methods:  
- plot  
- scatter  
- arrow  
- hexbin  
- pcolormesh (heatmap in hockey-rink)  
- contour  
- contourf  
- text
    
There's also a rink.plot_fn which will take as its first argument a plotting method from either Matplotlib or seaborn 
and will attempt to make the desired plot.
  
```
from hockey_rink import NHLRink, RinkImage
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns

shots = (
    pd.read_parquet("https://github.com/sportsdataverse/fastRhockey-data/blob/main/nhl/pbp/parquet/play_by_play_2023.parquet?raw=true")
    .query("event_type in ('GOAL', 'SHOT', 'MISS')")
)

team_colors = {"San Jose Sharks": (0, 0.5, 0.5), "Nashville Predators": (1, 0.7, 0.1)}

rink = NHLRink(
    sharks_logo={
        "feature_class": RinkImage,
        "image_path": "https://upload.wikimedia.org/wikipedia/en/thumb/3/37/SanJoseSharksLogo.svg/330px-SanJoseSharksLogo.svg.png",
        "x": 55, "length": 50, "width": 42,
        "zorder": 15, "alpha": 0.5,
    },
    preds_logo={
        "feature_class": RinkImage,
        "image_path": "https://upload.wikimedia.org/wikipedia/en/thumb/9/9c/Nashville_Predators_Logo_%282011%29.svg/330px-Nashville_Predators_Logo_%282011%29.svg.png",
        "x": -55, "length": 50, "width": 29,
        "zorder": 15, "alpha": 0.5,
    }
)

first_period = shots.query("game_id == 2022020001 and period == 1")

fig, axs = plt.subplots(1, 2, figsize=(18, 8))
rink.scatter("x", "y", facecolor=first_period.event_team.map(team_colors), s=100, edgecolor="white", data=first_period, ax=axs[0])
rink.plot_fn(sns.scatterplot, x="x", y="y", hue="event_team", s=100, legend=False, data=first_period, ax=axs[1], palette=team_colors);
```
![](images/scatter.png)

When using plots that require binning, it's often best to include a plot_range even when it isn't being used to 
find the bins. Here's an example using shooting percentage.

```
import numpy as np

ozone_shots = (
    shots
    .assign(
        is_goal=shots.event_type == "GOAL",
        x=np.abs(shots.x),
        y=shots.y * np.sign(shots.x),
    )
)

fig, axs = plt.subplots(1, 3, figsize=(18, 8))

rink = NHLRink(rotation=270, net={"visible": False})

rink.contourf(
    "x", "y", "is_goal", data=ozone_shots, 
    nbins=8, levels=30, plot_range="ozone", cmap="bwr",
    ax=axs[0], draw_kw={"display_range": "ozone"},
)

rink.heatmap(
    "x", "y", "is_goal", data=ozone_shots, 
    binsize=5, fill_value=0, plot_xlim=(25, 89), cmap="magma", vmax=0.25,
    ax=axs[1], draw_kw={"display_range": "ozone"},
)

rink.hexbin(
    "x", "y", "is_goal", data=ozone_shots,
    gridsize=(14, 8), plot_range="ozone", alpha=0.85, vmax=0.25,
    ax=axs[2], draw_kw={"display_range": "ozone"},
)
```
![](images/binned-plots.png)

There's also a clear method which will attempt to remove anything that isn't part of the rink unless it's passed 
to the keep variable. This can be useful for animations.
```
df = (
    pd.read_csv("https://github.com/the-bucketless/bdc/raw/main/data/2022-02-08%20Canada%20at%20USA/2022-02-08%20Canada%20at%20USA%20P1%20PP1.csv")
    .query("frame_id == 400")
    .assign(team_color=lambda df_: np.where(df_.team_name == "Canada", "lightcoral", "aqua"))
)

rink = NHLRink(x_shift=100, y_shift=42.5, rotation=270)

fig, axs = plt.subplots(1, 2, figsize=(12, 8))
for ax in axs:
    rink.draw(display_range="ozone", ax=ax)
    
    rink.scatter(
        "x_ft", "y_ft", ax=ax,
        facecolor="team_color", edgecolor="black", s=300,
        data=df,
    )

    rink.text(
        "x_ft", "y_ft", "jersey_number", ax=ax,
        ha="center", va="center", fontsize=14, 
        data=df,
    )
    
    teams_text = rink.text(
        0.5, 0.05, "Canada vs USA", ax=ax,
        use_rink_coordinates=False,
        ha="center", va="center", fontsize=20,
    )

rink.clear(ax=axs[1], keep=[teams_text])
```
![](images/clear-example.png)

## Inspiration
This project was partly inspired by [mplsoccer](https://github.com/andrewRowlinson/mplsoccer).

Hopefully, it can make things a little easier for anyone looking to get involved in hockey analytics.

## Contact
You can find me on twitter [@the_bucketless](https://twitter.com/the_bucketless) or email me at thebucketless@protonmail.com.
            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/the-bucketless/hockey_rink",
    "name": "hockey-rink",
    "maintainer": "",
    "docs_url": null,
    "requires_python": "",
    "maintainer_email": "",
    "keywords": "",
    "author": "The Bucketless",
    "author_email": "thebucketless@protonmail.com",
    "download_url": "https://files.pythonhosted.org/packages/e4/85/b6b49426ca8deca7b50c6e57b3c198cee9bb5ae615c37f0a350e6be3ae7f/hockey_rink-1.0.2.tar.gz",
    "platform": null,
    "description": "![](images/hockey-rink-logo.png)\n\nA Python library for plotting hockey rinks with Matplotlib.\n\n## Installation\n```pip install hockey-rink```\n\n## Current Rinks\n\nThe following rinks are available for use:\n- Rink\n- NHLRink\n- NWHLRink (from the 2021 Lake Placid games)\n- IIHFRink\n- OldIIHFRink\n\n## Customization\nThere is also room for customization. For example, to change the dimension of a rink, update the length and/or width of the boards:\n\n```\nrink = Rink(boards={\"length\": 150, \"width\": 150, \"radius\": 75})\n```\n  \n![](images/circular-rink.png)\n\nEach rink comes with a default set of features, but additional features can be added. Custom features should inherit \nfrom RinkFeature and override the _get_centered_xy method. The draw method can also be overridden if the desired feature can't be drawn\nwith a Matplotlib Polygon, though _get_centered_xy should still provide the feature's boundaries. The RinkImage\nfeature provides an example of this by inheriting from RinkRectangle.\n\nIf a custom feature is to be constrained to only display within the rink, the returned object needs to have a \nset_clip_path method.\n\n## Plots\nThere are currently wrappers available for the following Matplotlib plotting methods:  \n- plot  \n- scatter  \n- arrow  \n- hexbin  \n- pcolormesh (heatmap in hockey-rink)  \n- contour  \n- contourf  \n- text\n    \nThere's also a rink.plot_fn which will take as its first argument a plotting method from either Matplotlib or seaborn \nand will attempt to make the desired plot.\n  \n```\nfrom hockey_rink import NHLRink, RinkImage\nimport matplotlib.pyplot as plt\nimport pandas as pd\nimport seaborn as sns\n\nshots = (\n    pd.read_parquet(\"https://github.com/sportsdataverse/fastRhockey-data/blob/main/nhl/pbp/parquet/play_by_play_2023.parquet?raw=true\")\n    .query(\"event_type in ('GOAL', 'SHOT', 'MISS')\")\n)\n\nteam_colors = {\"San Jose Sharks\": (0, 0.5, 0.5), \"Nashville Predators\": (1, 0.7, 0.1)}\n\nrink = NHLRink(\n    sharks_logo={\n        \"feature_class\": RinkImage,\n        \"image_path\": \"https://upload.wikimedia.org/wikipedia/en/thumb/3/37/SanJoseSharksLogo.svg/330px-SanJoseSharksLogo.svg.png\",\n        \"x\": 55, \"length\": 50, \"width\": 42,\n        \"zorder\": 15, \"alpha\": 0.5,\n    },\n    preds_logo={\n        \"feature_class\": RinkImage,\n        \"image_path\": \"https://upload.wikimedia.org/wikipedia/en/thumb/9/9c/Nashville_Predators_Logo_%282011%29.svg/330px-Nashville_Predators_Logo_%282011%29.svg.png\",\n        \"x\": -55, \"length\": 50, \"width\": 29,\n        \"zorder\": 15, \"alpha\": 0.5,\n    }\n)\n\nfirst_period = shots.query(\"game_id == 2022020001 and period == 1\")\n\nfig, axs = plt.subplots(1, 2, figsize=(18, 8))\nrink.scatter(\"x\", \"y\", facecolor=first_period.event_team.map(team_colors), s=100, edgecolor=\"white\", data=first_period, ax=axs[0])\nrink.plot_fn(sns.scatterplot, x=\"x\", y=\"y\", hue=\"event_team\", s=100, legend=False, data=first_period, ax=axs[1], palette=team_colors);\n```\n![](images/scatter.png)\n\nWhen using plots that require binning, it's often best to include a plot_range even when it isn't being used to \nfind the bins. Here's an example using shooting percentage.\n\n```\nimport numpy as np\n\nozone_shots = (\n    shots\n    .assign(\n        is_goal=shots.event_type == \"GOAL\",\n        x=np.abs(shots.x),\n        y=shots.y * np.sign(shots.x),\n    )\n)\n\nfig, axs = plt.subplots(1, 3, figsize=(18, 8))\n\nrink = NHLRink(rotation=270, net={\"visible\": False})\n\nrink.contourf(\n    \"x\", \"y\", \"is_goal\", data=ozone_shots, \n    nbins=8, levels=30, plot_range=\"ozone\", cmap=\"bwr\",\n    ax=axs[0], draw_kw={\"display_range\": \"ozone\"},\n)\n\nrink.heatmap(\n    \"x\", \"y\", \"is_goal\", data=ozone_shots, \n    binsize=5, fill_value=0, plot_xlim=(25, 89), cmap=\"magma\", vmax=0.25,\n    ax=axs[1], draw_kw={\"display_range\": \"ozone\"},\n)\n\nrink.hexbin(\n    \"x\", \"y\", \"is_goal\", data=ozone_shots,\n    gridsize=(14, 8), plot_range=\"ozone\", alpha=0.85, vmax=0.25,\n    ax=axs[2], draw_kw={\"display_range\": \"ozone\"},\n)\n```\n![](images/binned-plots.png)\n\nThere's also a clear method which will attempt to remove anything that isn't part of the rink unless it's passed \nto the keep variable. This can be useful for animations.\n```\ndf = (\n    pd.read_csv(\"https://github.com/the-bucketless/bdc/raw/main/data/2022-02-08%20Canada%20at%20USA/2022-02-08%20Canada%20at%20USA%20P1%20PP1.csv\")\n    .query(\"frame_id == 400\")\n    .assign(team_color=lambda df_: np.where(df_.team_name == \"Canada\", \"lightcoral\", \"aqua\"))\n)\n\nrink = NHLRink(x_shift=100, y_shift=42.5, rotation=270)\n\nfig, axs = plt.subplots(1, 2, figsize=(12, 8))\nfor ax in axs:\n    rink.draw(display_range=\"ozone\", ax=ax)\n    \n    rink.scatter(\n        \"x_ft\", \"y_ft\", ax=ax,\n        facecolor=\"team_color\", edgecolor=\"black\", s=300,\n        data=df,\n    )\n\n    rink.text(\n        \"x_ft\", \"y_ft\", \"jersey_number\", ax=ax,\n        ha=\"center\", va=\"center\", fontsize=14, \n        data=df,\n    )\n    \n    teams_text = rink.text(\n        0.5, 0.05, \"Canada vs USA\", ax=ax,\n        use_rink_coordinates=False,\n        ha=\"center\", va=\"center\", fontsize=20,\n    )\n\nrink.clear(ax=axs[1], keep=[teams_text])\n```\n![](images/clear-example.png)\n\n## Inspiration\nThis project was partly inspired by [mplsoccer](https://github.com/andrewRowlinson/mplsoccer).\n\nHopefully, it can make things a little easier for anyone looking to get involved in hockey analytics.\n\n## Contact\nYou can find me on twitter [@the_bucketless](https://twitter.com/the_bucketless) or email me at thebucketless@protonmail.com.",
    "bugtrack_url": null,
    "license": "GNU General Public License v3 (GPLv3)",
    "summary": "A Python library for plotting hockey rinks with Matplotlib.",
    "version": "1.0.2",
    "project_urls": {
        "Homepage": "https://github.com/the-bucketless/hockey_rink"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "e485b6b49426ca8deca7b50c6e57b3c198cee9bb5ae615c37f0a350e6be3ae7f",
                "md5": "580a10602429f38544fb4cf67012771a",
                "sha256": "e2628546ded69bc810180661f1b7bacdb741adfef44de3ba1cac7fe073fd537a"
            },
            "downloads": -1,
            "filename": "hockey_rink-1.0.2.tar.gz",
            "has_sig": false,
            "md5_digest": "580a10602429f38544fb4cf67012771a",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 39868,
            "upload_time": "2023-06-03T23:35:42",
            "upload_time_iso_8601": "2023-06-03T23:35:42.883007Z",
            "url": "https://files.pythonhosted.org/packages/e4/85/b6b49426ca8deca7b50c6e57b3c198cee9bb5ae615c37f0a350e6be3ae7f/hockey_rink-1.0.2.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-06-03 23:35:42",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "the-bucketless",
    "github_project": "hockey_rink",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "requirements": [],
    "lcname": "hockey-rink"
}
        
Elapsed time: 0.07234s