sdvplot 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sdvplot/__init__.py +68 -0
- sdvplot/_cache.py +484 -0
- sdvplot/_colors.py +206 -0
- sdvplot/_contrast.py +60 -0
- sdvplot/_court.py +110 -0
- sdvplot/_deprecate.py +39 -0
- sdvplot/_dispatch.py +342 -0
- sdvplot/_errors.py +93 -0
- sdvplot/_headshots.py +131 -0
- sdvplot/_images.py +292 -0
- sdvplot/_index.py +178 -0
- sdvplot/_lazy.py +15 -0
- sdvplot/_manifest.py +73 -0
- sdvplot/_marks.py +296 -0
- sdvplot/_normalize.py +82 -0
- sdvplot/_placement.py +168 -0
- sdvplot/_resolve.py +363 -0
- sdvplot/_surface.py +220 -0
- sdvplot/_tables.py +87 -0
- sdvplot/_tiers.py +135 -0
- sdvplot/_types.py +38 -0
- sdvplot/_versions.py +30 -0
- sdvplot/_web.py +61 -0
- sdvplot/altair.py +652 -0
- sdvplot/bokeh.py +323 -0
- sdvplot/data/INDEX_VERSION +1 -0
- sdvplot/data/aliases.parquet +0 -0
- sdvplot/data/teams.parquet +0 -0
- sdvplot/folium.py +316 -0
- sdvplot/great_tables/__init__.py +267 -0
- sdvplot/great_tables/_cells.py +1814 -0
- sdvplot/great_tables/_export.py +844 -0
- sdvplot/great_tables/_layout.py +1802 -0
- sdvplot/great_tables/_marks.py +798 -0
- sdvplot/great_tables/_themes.py +2134 -0
- sdvplot/holoviews.py +303 -0
- sdvplot/matplotlib.py +934 -0
- sdvplot/plotly.py +595 -0
- sdvplot/plotnine.py +1104 -0
- sdvplot/plottable.py +141 -0
- sdvplot/py.typed +0 -0
- sdvplot/pygal.py +416 -0
- sdvplot/reactable.py +488 -0
- sdvplot/testing.py +579 -0
- sdvplot/typing.py +21 -0
- sdvplot-0.1.0.dist-info/METADATA +188 -0
- sdvplot-0.1.0.dist-info/RECORD +49 -0
- sdvplot-0.1.0.dist-info/WHEEL +4 -0
- sdvplot-0.1.0.dist-info/licenses/LICENSE +21 -0
sdvplot/plotnine.py
ADDED
|
@@ -0,0 +1,1104 @@
|
|
|
1
|
+
"""The plotnine adapter: logo, wordmark, headshot and image geoms, axis logos, team color scales and reference lines.
|
|
2
|
+
|
|
3
|
+
The geoms draw through the matplotlib adapter's drawing step, so sizing matches it: ``height``
|
|
4
|
+
is a fraction of each panel's height. ``add_logos(p, ...)`` returns a new ggplot (plotnine's ``+`` copies).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any, Literal
|
|
10
|
+
|
|
11
|
+
from sdvplot._errors import UnsupportedTargetError, requires_extra, warn
|
|
12
|
+
|
|
13
|
+
with requires_extra("plotnine"):
|
|
14
|
+
import numpy as np
|
|
15
|
+
import pandas as pd
|
|
16
|
+
from matplotlib.figure import Figure
|
|
17
|
+
from plotnine import (
|
|
18
|
+
aes,
|
|
19
|
+
element_blank,
|
|
20
|
+
element_rect,
|
|
21
|
+
element_text,
|
|
22
|
+
geom_text,
|
|
23
|
+
ggplot,
|
|
24
|
+
labs,
|
|
25
|
+
scale_color_manual,
|
|
26
|
+
scale_fill_manual,
|
|
27
|
+
scale_x_continuous,
|
|
28
|
+
scale_y_reverse,
|
|
29
|
+
theme_minimal,
|
|
30
|
+
)
|
|
31
|
+
from plotnine import theme as p9_theme # team_tiers takes a `theme` argument
|
|
32
|
+
from plotnine._utils import remove_missing
|
|
33
|
+
from plotnine.geoms import geom_hline, geom_vline
|
|
34
|
+
from plotnine.geoms.geom import geom
|
|
35
|
+
|
|
36
|
+
from sdvplot import _tiers
|
|
37
|
+
from sdvplot._colors import _column, team_colors
|
|
38
|
+
from sdvplot._marks import _check_mark_type
|
|
39
|
+
from sdvplot._placement import Placement, _warn_skipped, check_alpha, check_height, place, place_images
|
|
40
|
+
from sdvplot._resolve import _seasons, _unpack
|
|
41
|
+
from sdvplot._types import IdSystem, Which
|
|
42
|
+
from sdvplot.matplotlib import (
|
|
43
|
+
_add_title_image,
|
|
44
|
+
_align,
|
|
45
|
+
_check_title_image,
|
|
46
|
+
_draw_images,
|
|
47
|
+
_draw_placements,
|
|
48
|
+
_in_view,
|
|
49
|
+
_read_images,
|
|
50
|
+
_title_source,
|
|
51
|
+
)
|
|
52
|
+
from sdvplot.matplotlib import _axis_logos as _mpl_axis_logos
|
|
53
|
+
from sdvplot.matplotlib import _drawn_axis_marks as _mpl_drawn_axis_marks
|
|
54
|
+
from sdvplot.matplotlib import _drawn_marks as _mpl_drawn_marks
|
|
55
|
+
from sdvplot.matplotlib import _visible_axis_labels as _mpl_visible_axis_labels
|
|
56
|
+
|
|
57
|
+
_SUPPORTS_AXIS_LOGOS = True
|
|
58
|
+
_MARK_PARAMS = {
|
|
59
|
+
"stat": "identity",
|
|
60
|
+
"position": "identity",
|
|
61
|
+
"na_rm": False,
|
|
62
|
+
"league": None,
|
|
63
|
+
"season": None,
|
|
64
|
+
"height": 0.1,
|
|
65
|
+
"alpha": 1,
|
|
66
|
+
"variant": "default",
|
|
67
|
+
"id_system": "auto",
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
__all__ = [
|
|
71
|
+
"add_headshots",
|
|
72
|
+
"add_logos",
|
|
73
|
+
"add_wordmarks",
|
|
74
|
+
"axis_logos",
|
|
75
|
+
"geom_from_path",
|
|
76
|
+
"geom_mean_lines",
|
|
77
|
+
"geom_median_lines",
|
|
78
|
+
"geom_sdv_headshots",
|
|
79
|
+
"geom_sdv_logos",
|
|
80
|
+
"geom_sdv_wordmarks",
|
|
81
|
+
"scale_color_sdv",
|
|
82
|
+
"scale_fill_sdv",
|
|
83
|
+
"team_tiers",
|
|
84
|
+
"title_image",
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def __dir__() -> list[str]: # dir() and tab completion show the public API only
|
|
89
|
+
return list(__all__)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class _geom_sdv_marks(geom):
|
|
93
|
+
"""Shared drawing for the image geoms; subclasses set the kind and the id aesthetic."""
|
|
94
|
+
|
|
95
|
+
_kind = "logo"
|
|
96
|
+
_id_aes = "team"
|
|
97
|
+
_needs_league = True
|
|
98
|
+
DEFAULT_AES: dict[str, Any] = {"season": None} # optional: one season per row, which plotnine copies with the row
|
|
99
|
+
REQUIRED_AES = {"x", "y", "team"}
|
|
100
|
+
DEFAULT_PARAMS = _MARK_PARAMS
|
|
101
|
+
|
|
102
|
+
def __init__(self, mapping: Any = None, data: Any = None, **kwargs: Any) -> None:
|
|
103
|
+
if self._needs_league and kwargs.get("league") is None:
|
|
104
|
+
raise TypeError(f"{type(self).__name__}() needs league=, e.g. league='nfl'")
|
|
105
|
+
check_height(kwargs.get("height", 0.1))
|
|
106
|
+
check_alpha(kwargs.get("alpha", 1))
|
|
107
|
+
kwargs.setdefault("show_legend", False)
|
|
108
|
+
super().__init__(mapping, data, **kwargs)
|
|
109
|
+
|
|
110
|
+
def _place(self, data: pd.DataFrame, x: list[Any], y: list[Any], *, warn: bool) -> list[Placement]:
|
|
111
|
+
p = self.params
|
|
112
|
+
# the season= parameter, else the season aesthetic (a column, so each panel's copy of a row keeps its season)
|
|
113
|
+
season = p["season"] if p["season"] is not None or "season" not in data else data["season"].tolist()
|
|
114
|
+
return place(x, y, data[self._id_aes].tolist(), league=p["league"], season=season, kind=self._kind,
|
|
115
|
+
variant=p["variant"], id_system=p["id_system"], _warn=warn) # fmt: skip
|
|
116
|
+
|
|
117
|
+
def setup_data(self, data: pd.DataFrame) -> pd.DataFrame:
|
|
118
|
+
"""Once per layer and render: place every panel's rows together, so an unknown team or a missing mark warns
|
|
119
|
+
once, and a point plotnine copies into every panel counts once. At zero positions: x and y are plotnine's to
|
|
120
|
+
check, after this (``na_rm``, scale limits, its own "Removed rows" warning), so they never warn here."""
|
|
121
|
+
# ponytail: two identical points (same x, y and team) in one panel also count once
|
|
122
|
+
rows = data.drop(columns="PANEL", errors="ignore").drop_duplicates() # a row plotnine copies to every panel
|
|
123
|
+
zeros = [0.0] * len(rows)
|
|
124
|
+
self._place(rows, zeros, zeros, warn=True)
|
|
125
|
+
return data
|
|
126
|
+
|
|
127
|
+
def draw_panel(self, data: pd.DataFrame, panel_params: Any, coord: Any, ax: Any) -> None:
|
|
128
|
+
# setup_data already warned for this layer's rows in every panel, so each panel places its rows quietly
|
|
129
|
+
data = coord.transform(data, panel_params)
|
|
130
|
+
placements = self._place(data, data["x"].tolist(), data["y"].tolist(), warn=False)
|
|
131
|
+
_draw_placements(ax, placements, height=float(self.params["height"]), alpha=float(self.params["alpha"]))
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class geom_sdv_logos(_geom_sdv_marks):
|
|
135
|
+
"""Team logos at (x, y), as a plotnine layer.
|
|
136
|
+
|
|
137
|
+
For a season per row, map it instead of passing ``season=``: ``aes(..., season="season")`` (a ``season=``
|
|
138
|
+
parameter wins over the mapping).
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
mapping: ``aes(x=..., y=..., team=...)``, plus optional ``season``.
|
|
142
|
+
data: The layer's data (pandas or polars), when not the plot's.
|
|
143
|
+
**kwargs: ``league`` (required, e.g. "nfl"), ``season``, ``height`` (a fraction of the panel height, in (0, 1],
|
|
144
|
+
default 0.1), ``alpha`` (0 to 1), ``variant`` ("default", "dark" or a named variant), ``id_system`` and
|
|
145
|
+
plotnine's layer arguments (``inherit_aes``, ...).
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
geom: A plotnine layer to add with ``+``. Marks that cannot be placed (an unknown team, a missing mark) are
|
|
149
|
+
skipped with one SdvplotWarning when the plot is drawn.
|
|
150
|
+
|
|
151
|
+
Raises:
|
|
152
|
+
TypeError: If ``league`` is missing.
|
|
153
|
+
InputError: (a ValueError) If ``height`` or ``alpha`` is out of range (when the layer is built); when the plot
|
|
154
|
+
is drawn, if ``league``, ``id_system`` or ``variant`` is unknown, or a season is not a year or is outside
|
|
155
|
+
the seasons sdvplot knows for the league.
|
|
156
|
+
OfflineError: When the plot is drawn, if the logo manifest or a mark's image is neither cached nor downloadable
|
|
157
|
+
(a DownloadError, also an OSError, when the CDN answers with an error status; an IntegrityError when it
|
|
158
|
+
sends a file that does not match the manifest's sha256, or one PIL cannot decode).
|
|
159
|
+
UnsafeDownloadError: (an OSError) When the plot is drawn, if a download is refused: larger than the byte cap,
|
|
160
|
+
past the deadline, or redirected away from https.
|
|
161
|
+
UnsafeCachePathError: (a ValueError) When the plot is drawn, if the manifest's sha256 or extension for a mark
|
|
162
|
+
would put the file outside the cache directory.
|
|
163
|
+
OptionalDependencyError: When the plot is drawn, if a mark is an SVG and the ``svg`` extra is not installed.
|
|
164
|
+
|
|
165
|
+
Example:
|
|
166
|
+
::
|
|
167
|
+
|
|
168
|
+
import pandas as pd
|
|
169
|
+
from plotnine import aes, ggplot
|
|
170
|
+
from sdvplot.plotnine import geom_sdv_logos
|
|
171
|
+
|
|
172
|
+
df = pd.DataFrame({"team": ["KC", "BUF"], "epa": [0.2, 0.15], "sr": [0.48, 0.47]})
|
|
173
|
+
p = ggplot(df, aes("epa", "sr", team="team")) + geom_sdv_logos(league="nfl", height=0.12)
|
|
174
|
+
|
|
175
|
+
See Also:
|
|
176
|
+
sdvplotR geom_nfl_logos(): https://sdvplotR.sportsdataverse.org/ ;
|
|
177
|
+
sdvplot.plotnine.geom_sdv_wordmarks: the same with wordmarks
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class geom_sdv_wordmarks(_geom_sdv_marks):
|
|
182
|
+
"""Team wordmarks at (x, y), as a plotnine layer: the same aesthetics and parameters as ``geom_sdv_logos``.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
mapping: ``aes(x=..., y=..., team=...)``, plus optional ``season``.
|
|
186
|
+
data: The layer's data (pandas or polars), when not the plot's.
|
|
187
|
+
**kwargs: ``league`` (required, e.g. "nfl"), ``season``, ``height`` (a fraction of the panel height, in (0, 1],
|
|
188
|
+
default 0.1), ``alpha`` (0 to 1), ``variant`` ("default", "dark" or a named variant), ``id_system`` and
|
|
189
|
+
plotnine's layer arguments (``inherit_aes``, ...).
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
geom: A plotnine layer to add with ``+``. Marks that cannot be placed (an unknown team, a missing mark) are
|
|
193
|
+
skipped with one SdvplotWarning when the plot is drawn.
|
|
194
|
+
|
|
195
|
+
Raises:
|
|
196
|
+
TypeError: If ``league`` is missing.
|
|
197
|
+
InputError: (a ValueError) If ``height`` or ``alpha`` is out of range (when the layer is built); when the plot
|
|
198
|
+
is drawn, if ``league``, ``id_system`` or ``variant`` is unknown, or a season is not a year or is outside
|
|
199
|
+
the seasons sdvplot knows for the league.
|
|
200
|
+
OfflineError: When the plot is drawn, if the logo manifest or a mark's image is neither cached nor downloadable
|
|
201
|
+
(a DownloadError, also an OSError, when the CDN answers with an error status; an IntegrityError when it
|
|
202
|
+
sends a file that does not match the manifest's sha256, or one PIL cannot decode).
|
|
203
|
+
UnsafeDownloadError: (an OSError) When the plot is drawn, if a download is refused: larger than the byte cap,
|
|
204
|
+
past the deadline, or redirected away from https.
|
|
205
|
+
UnsafeCachePathError: (a ValueError) When the plot is drawn, if the manifest's sha256 or extension for a mark
|
|
206
|
+
would put the file outside the cache directory.
|
|
207
|
+
OptionalDependencyError: When the plot is drawn, if a mark is an SVG and the ``svg`` extra is not installed.
|
|
208
|
+
|
|
209
|
+
Example:
|
|
210
|
+
::
|
|
211
|
+
|
|
212
|
+
import pandas as pd
|
|
213
|
+
from plotnine import aes, ggplot
|
|
214
|
+
from sdvplot.plotnine import geom_sdv_wordmarks
|
|
215
|
+
|
|
216
|
+
df = pd.DataFrame({"team": ["KC", "BUF"], "epa": [0.2, 0.15], "sr": [0.48, 0.47]})
|
|
217
|
+
p = ggplot(df, aes("epa", "sr", team="team")) + geom_sdv_wordmarks(league="nfl", height=0.08)
|
|
218
|
+
|
|
219
|
+
See Also:
|
|
220
|
+
sdvplotR geom_nfl_wordmarks(): https://sdvplotR.sportsdataverse.org/ ;
|
|
221
|
+
sdvplot.plotnine.geom_sdv_logos: the same with logos
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
_kind = "wordmark"
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class geom_sdv_headshots(_geom_sdv_marks):
|
|
228
|
+
"""Player headshots at (x, y), as a plotnine layer.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
mapping: ``aes(x=..., y=..., player_id=...)``.
|
|
232
|
+
data: The layer's data (pandas or polars), when not the plot's.
|
|
233
|
+
**kwargs: ``league`` (required, e.g. "nfl"), ``height`` (a fraction of the panel height, in (0, 1],
|
|
234
|
+
default 0.1), ``alpha`` (0 to 1), ``id_system`` and
|
|
235
|
+
plotnine's layer arguments (``inherit_aes``, ...).
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
geom: A plotnine layer to add with ``+``. Marks that cannot be placed (an unknown team, a missing mark) are
|
|
239
|
+
skipped with one SdvplotWarning when the plot is drawn.
|
|
240
|
+
|
|
241
|
+
Raises:
|
|
242
|
+
TypeError: If ``league`` is missing.
|
|
243
|
+
InputError: (a ValueError) If ``height`` or ``alpha`` is out of range (when the layer is built); when the plot
|
|
244
|
+
is drawn, if ``league`` has no ESPN headshots or ``id_system`` is not valid for it.
|
|
245
|
+
OfflineError: When the plot is drawn, if a headshot, or with ``id_system="gsis"`` the nflverse player table, is
|
|
246
|
+
neither cached nor downloadable (a DownloadError, also an OSError, for an HTTP error status).
|
|
247
|
+
UnsafeDownloadError: (an OSError) When the plot is drawn, if a download is refused: larger than the byte cap,
|
|
248
|
+
past the deadline, or redirected away from https.
|
|
249
|
+
|
|
250
|
+
Example:
|
|
251
|
+
::
|
|
252
|
+
|
|
253
|
+
import pandas as pd
|
|
254
|
+
from plotnine import aes, ggplot
|
|
255
|
+
from sdvplot.plotnine import geom_sdv_headshots
|
|
256
|
+
|
|
257
|
+
df = pd.DataFrame({"x": [0.3, 0.7], "y": [0.4, 0.6], "espn_id": ["3139477", "3918298"]})
|
|
258
|
+
p = ggplot(df, aes("x", "y", player_id="espn_id")) + geom_sdv_headshots(league="nfl", height=0.15)
|
|
259
|
+
|
|
260
|
+
See Also:
|
|
261
|
+
sdvplotR geom_nfl_headshots(): https://sdvplotR.sportsdataverse.org/ ;
|
|
262
|
+
sdvplot.plotnine.geom_sdv_logos: the same with team logos
|
|
263
|
+
"""
|
|
264
|
+
|
|
265
|
+
_kind = "headshot"
|
|
266
|
+
_id_aes = "player_id"
|
|
267
|
+
REQUIRED_AES = {"x", "y", "player_id"}
|
|
268
|
+
DEFAULT_PARAMS = {**_MARK_PARAMS, "id_system": "espn"}
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
class geom_from_path(_geom_sdv_marks):
|
|
272
|
+
"""Any image, by local path or URL, at (x, y): ``aes(x=..., y=..., path=...)``, plus ``height`` (fraction of the
|
|
273
|
+
panel height, default 0.1) and ``alpha``. The port of ggpath's ``geom_from_path()``, sized like the logo geoms.
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
mapping: ``aes(x=..., y=..., path=...)``; ``path`` holds a local file path, ``file://`` URI or https URL per
|
|
277
|
+
row (http is refused).
|
|
278
|
+
data: The layer's data (pandas or polars), when not the plot's.
|
|
279
|
+
**kwargs: ``height`` in (0, 1], ``alpha`` in [0, 1], and plotnine's layer arguments (``inherit_aes``, ...).
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
geom: A plotnine layer to add with ``+``. Images that cannot be read (a missing file, a file that is not an
|
|
283
|
+
image, a failed download) are skipped with one SdvplotWarning when the plot is drawn; SVG is not read.
|
|
284
|
+
|
|
285
|
+
Raises:
|
|
286
|
+
InputError: (a ValueError) If ``height`` or ``alpha`` is out of range (when the layer is built). An image that
|
|
287
|
+
cannot be read or downloaded is skipped with a warning when the plot is drawn, not raised.
|
|
288
|
+
|
|
289
|
+
Example:
|
|
290
|
+
::
|
|
291
|
+
|
|
292
|
+
import pandas as pd
|
|
293
|
+
from plotnine import aes, ggplot
|
|
294
|
+
from sdvplot.plotnine import geom_from_path
|
|
295
|
+
|
|
296
|
+
df = pd.DataFrame({"x": [1, 2], "y": [1, 2], "img": ["a.png", "https://www.python.org/static/favicon.ico"]})
|
|
297
|
+
p = ggplot(df, aes("x", "y", path="img")) + geom_from_path(height=0.15)
|
|
298
|
+
|
|
299
|
+
See Also:
|
|
300
|
+
ggpath geom_from_path(): https://mrcaseb.github.io/ggpath/ ;
|
|
301
|
+
sdvplot.matplotlib.add_images: the matplotlib counterpart
|
|
302
|
+
"""
|
|
303
|
+
|
|
304
|
+
_id_aes = "path"
|
|
305
|
+
_needs_league = False
|
|
306
|
+
DEFAULT_AES: dict[str, Any] = {}
|
|
307
|
+
REQUIRED_AES = {"x", "y", "path"}
|
|
308
|
+
DEFAULT_PARAMS = {"stat": "identity", "position": "identity", "na_rm": False, "height": 0.1, "alpha": 1}
|
|
309
|
+
_sdv_images: dict[str, Any] # path -> image (None when unreadable), read once per render by setup_data
|
|
310
|
+
|
|
311
|
+
def _place(self, data: pd.DataFrame, x: list[Any], y: list[Any], *, warn: bool) -> list[Placement]:
|
|
312
|
+
return place_images(x, y, data["path"].tolist(), _warn=warn)
|
|
313
|
+
|
|
314
|
+
def setup_data(self, data: pd.DataFrame) -> pd.DataFrame:
|
|
315
|
+
"""Once per layer and render, as for the team geoms: read every distinct image once, so an image that cannot
|
|
316
|
+
be read (or downloaded) is tried once and warns once, whatever the facets; the panels draw from these."""
|
|
317
|
+
rows = data.drop(columns="PANEL", errors="ignore").drop_duplicates() # a row plotnine copies to every panel
|
|
318
|
+
zeros = [0.0] * len(rows)
|
|
319
|
+
self._sdv_images = {}
|
|
320
|
+
_warn_skipped("whose image could not be read", _read_images(self._place(rows, zeros, zeros, warn=True),
|
|
321
|
+
self._sdv_images)) # fmt: skip
|
|
322
|
+
return data
|
|
323
|
+
|
|
324
|
+
def draw_panel(self, data: pd.DataFrame, panel_params: Any, coord: Any, ax: Any) -> None:
|
|
325
|
+
# setup_data already read the images and warned, so each panel draws its rows quietly
|
|
326
|
+
data = coord.transform(data, panel_params)
|
|
327
|
+
placements = self._place(data, data["x"].tolist(), data["y"].tolist(), warn=False)
|
|
328
|
+
_draw_images(ax, placements, height=float(self.params["height"]), alpha=float(self.params["alpha"]),
|
|
329
|
+
cache=self._sdv_images, warn=False) # fmt: skip
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
class _geom_ref_lines(geom):
|
|
333
|
+
"""A vertical line at ``_ref(x0)`` and a horizontal line at ``_ref(y0)`` per panel, drawn by plotnine's own
|
|
334
|
+
geom_vline / geom_hline (ggpath draws through GeomVline / GeomHline the same way)."""
|
|
335
|
+
|
|
336
|
+
_ref: Any = staticmethod(np.mean)
|
|
337
|
+
# ggpath's GeomRefLines, except alpha: ggplot2's NA (the color's own alpha) is 1 in plotnine, which also keeps
|
|
338
|
+
# an 8-digit hex color's alpha
|
|
339
|
+
DEFAULT_AES = {"color": "red", "size": 0.5, "linetype": "dashed", "alpha": 1}
|
|
340
|
+
REQUIRED_AES: set[str] = set()
|
|
341
|
+
DEFAULT_PARAMS = {"stat": "identity", "position": "identity", "na_rm": False}
|
|
342
|
+
|
|
343
|
+
def __init__(self, mapping: Any = None, data: Any = None, **kwargs: Any) -> None:
|
|
344
|
+
kwargs.setdefault("show_legend", False)
|
|
345
|
+
super().__init__(mapping, data, **kwargs)
|
|
346
|
+
|
|
347
|
+
def draw_layer(self, data: pd.DataFrame, layout: Any, coord: Any) -> None:
|
|
348
|
+
# x0/y0 are position aesthetics in ggplot2: each panel's position scale transforms them (a log scale averages
|
|
349
|
+
# the logs) and censors those outside its limits to NA before the mean. plotnine does not know them, so do
|
|
350
|
+
# both here, panel by panel (free scales differ).
|
|
351
|
+
data = data.copy()
|
|
352
|
+
for ae, which in (("x0", "x"), ("y0", "y")):
|
|
353
|
+
if ae not in data:
|
|
354
|
+
continue
|
|
355
|
+
data[ae] = data[ae].astype(float)
|
|
356
|
+
for panel, rows in data.groupby("PANEL", observed=True).groups.items():
|
|
357
|
+
sc = getattr(layout.get_scales(panel), which)
|
|
358
|
+
data.loc[rows, ae] = sc.map(sc.transform(data.loc[rows, ae].to_numpy()))
|
|
359
|
+
super().draw_layer(data, layout, coord)
|
|
360
|
+
|
|
361
|
+
def draw_panel(self, data: pd.DataFrame, panel_params: Any, coord: Any, ax: Any) -> None:
|
|
362
|
+
name = type(self).__name__
|
|
363
|
+
if "x0" not in data and "y0" not in data:
|
|
364
|
+
raise ValueError(f"{name}() needs an x0 and/or a y0 aesthetic, e.g. aes(x0='epa', y0='success_rate')")
|
|
365
|
+
# ggplot2's GeomHline/GeomVline draw one segment per distinct row of these (ggpath passes them on): one line per
|
|
366
|
+
# panel, or one per group when a colour (or another line aesthetic) is mapped, all at the panel's value
|
|
367
|
+
styles = data[[c for c in ("PANEL", "group", "color", "size", "linetype", "alpha") if c in data]]
|
|
368
|
+
lines: tuple[tuple[str, Any, str], ...] = (("y0", geom_hline, "yintercept"), ("x0", geom_vline, "xintercept"))
|
|
369
|
+
for ae, line, column in lines:
|
|
370
|
+
if ae not in data:
|
|
371
|
+
continue
|
|
372
|
+
values = data[ae].to_numpy(dtype=float)
|
|
373
|
+
if self.params["na_rm"]:
|
|
374
|
+
values = values[~np.isnan(values)]
|
|
375
|
+
ref = float(self._ref(values)) if len(values) else np.nan # NaN when a value is missing, as R's mean()
|
|
376
|
+
# ggplot2 drops (and warns about) a line at NA whatever na.rm says; so does plotnine's remove_missing
|
|
377
|
+
frame = styles.assign(**{column: ref}).drop_duplicates().reset_index(drop=True)
|
|
378
|
+
frame = remove_missing(frame, False, [column], name)
|
|
379
|
+
if len(frame):
|
|
380
|
+
line.draw_panel(self, frame, panel_params, coord, ax)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
class geom_mean_lines(_geom_ref_lines):
|
|
384
|
+
"""Reference lines at the mean of ``x0`` (vertical) and/or ``y0`` (horizontal), per panel: the port of ggpath's
|
|
385
|
+
``geom_mean_lines()``.
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
mapping: ``aes(x0=..., y0=...)``, at least one of them (``x0`` alone draws only the vertical line).
|
|
389
|
+
data: The layer's data (pandas or polars), when not the plot's.
|
|
390
|
+
**kwargs: ``color`` (default "red"), ``size`` (line width, default 0.5), ``linetype`` (default "dashed"),
|
|
391
|
+
``alpha``, ``na_rm`` and plotnine's layer arguments. With ``na_rm=False`` (the default) a panel whose
|
|
392
|
+
values include a missing one draws no line on that axis, with a PlotnineWarning, as ggpath does;
|
|
393
|
+
``na_rm=True`` ignores the missing values.
|
|
394
|
+
|
|
395
|
+
Returns:
|
|
396
|
+
geom: A plotnine layer to add with ``+``; each facet panel gets its own reference value.
|
|
397
|
+
|
|
398
|
+
Raises:
|
|
399
|
+
ValueError: When the plot is drawn, if neither ``x0`` nor ``y0`` is mapped.
|
|
400
|
+
|
|
401
|
+
Example:
|
|
402
|
+
::
|
|
403
|
+
|
|
404
|
+
import pandas as pd
|
|
405
|
+
from plotnine import aes, geom_point, ggplot
|
|
406
|
+
from sdvplot.plotnine import geom_mean_lines
|
|
407
|
+
|
|
408
|
+
df = pd.DataFrame({"epa": [0.2, 0.15, 0.05], "success_rate": [0.48, 0.47, 0.44]})
|
|
409
|
+
p = (ggplot(df, aes("epa", "success_rate", x0="epa", y0="success_rate"))
|
|
410
|
+
+ geom_point() + geom_mean_lines(color="grey"))
|
|
411
|
+
|
|
412
|
+
See Also:
|
|
413
|
+
ggpath geom_mean_lines(): https://mrcaseb.github.io/ggpath/ ;
|
|
414
|
+
geom_median_lines: the same at the median
|
|
415
|
+
"""
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
class geom_median_lines(_geom_ref_lines):
|
|
419
|
+
"""Reference lines at the median of ``x0`` (vertical) and/or ``y0`` (horizontal), per panel: the port of ggpath's
|
|
420
|
+
``geom_median_lines()``.
|
|
421
|
+
|
|
422
|
+
Args:
|
|
423
|
+
mapping: ``aes(x0=..., y0=...)``, at least one of them.
|
|
424
|
+
data: The layer's data (pandas or polars), when not the plot's.
|
|
425
|
+
**kwargs: ``color`` (default "red"), ``size`` (default 0.5), ``linetype`` (default "dashed"), ``alpha``,
|
|
426
|
+
``na_rm`` (as ``geom_mean_lines``) and plotnine's layer arguments.
|
|
427
|
+
|
|
428
|
+
Returns:
|
|
429
|
+
geom: A plotnine layer to add with ``+``; each facet panel gets its own reference value.
|
|
430
|
+
|
|
431
|
+
Raises:
|
|
432
|
+
ValueError: When the plot is drawn, if neither ``x0`` nor ``y0`` is mapped.
|
|
433
|
+
|
|
434
|
+
Example:
|
|
435
|
+
::
|
|
436
|
+
|
|
437
|
+
import pandas as pd
|
|
438
|
+
from plotnine import aes, geom_point, ggplot
|
|
439
|
+
from sdvplot.plotnine import geom_median_lines
|
|
440
|
+
|
|
441
|
+
df = pd.DataFrame({"epa": [0.2, 0.15, 0.05], "success_rate": [0.48, 0.47, 0.44]})
|
|
442
|
+
p = ggplot(df, aes("epa", "success_rate", x0="epa", y0="success_rate")) + geom_point() + geom_median_lines()
|
|
443
|
+
|
|
444
|
+
See Also:
|
|
445
|
+
ggpath geom_median_lines(): https://mrcaseb.github.io/ggpath/ ;
|
|
446
|
+
geom_mean_lines: the same at the mean
|
|
447
|
+
"""
|
|
448
|
+
|
|
449
|
+
_ref = staticmethod(np.median)
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def _ggplot(target: Any) -> ggplot:
|
|
453
|
+
if not isinstance(target, ggplot):
|
|
454
|
+
raise UnsupportedTargetError(f"sdvplot.plotnine draws on a plotnine ggplot, got {type(target).__name__}")
|
|
455
|
+
return target
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def _frame(x: Any, y: Any, ids: Any, column: str, season: Any = None) -> pd.DataFrame:
|
|
459
|
+
"""The layer data: one row per point, with its season, so plotnine's per-panel copies of a row keep it."""
|
|
460
|
+
xs, ys, ts = _unpack(x)[0], _unpack(y)[0], _unpack(ids)[0]
|
|
461
|
+
if not len(xs) == len(ys) == len(ts):
|
|
462
|
+
raise ValueError(f"x, y and teams must have the same length, got {len(xs)}, {len(ys)} and {len(ts)}")
|
|
463
|
+
return pd.DataFrame({"x": xs, "y": ys, column: ts, "season": pd.Series(_seasons(season, len(ts)), dtype=object)})
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def add_logos(
|
|
467
|
+
target: ggplot,
|
|
468
|
+
x: Any,
|
|
469
|
+
y: Any,
|
|
470
|
+
teams: Any,
|
|
471
|
+
*,
|
|
472
|
+
league: str,
|
|
473
|
+
season: Any = None,
|
|
474
|
+
height: float = 0.1,
|
|
475
|
+
alpha: float = 1,
|
|
476
|
+
variant: str = "default",
|
|
477
|
+
id_system: str = "auto",
|
|
478
|
+
) -> ggplot:
|
|
479
|
+
"""A copy of the plot with each team's logo at its (x, y); the front door's plotnine adapter.
|
|
480
|
+
|
|
481
|
+
Args:
|
|
482
|
+
target: A plotnine ggplot.
|
|
483
|
+
x: The points' x positions (read by position).
|
|
484
|
+
y: The points' y positions, the same length as ``x``.
|
|
485
|
+
teams: The team for each point, in any id system ``resolve()`` understands.
|
|
486
|
+
league: The SDV league key, e.g. "nfl".
|
|
487
|
+
season: One season, or one per point, to pick each team's mark for that era.
|
|
488
|
+
height: The logo height as a fraction of the panel height, in (0, 1].
|
|
489
|
+
alpha: Opacity, 0 to 1.
|
|
490
|
+
variant: "default", "dark", or a named variant from ``marks()``.
|
|
491
|
+
id_system: The id system of ``teams``; "auto" tries each in order.
|
|
492
|
+
|
|
493
|
+
Returns:
|
|
494
|
+
ggplot: A new plot with a ``geom_sdv_logos`` layer; ``target`` is unchanged.
|
|
495
|
+
|
|
496
|
+
Raises:
|
|
497
|
+
ValueError: If the inputs differ in length.
|
|
498
|
+
UnsupportedTargetError: (a TypeError) If ``target`` is not a plotnine ggplot.
|
|
499
|
+
InputError: (a ValueError) If ``height`` or ``alpha`` is out of range, or ``season`` is not a year (or a list
|
|
500
|
+
whose length does not match the teams); when the plot is drawn, if ``league``, ``id_system`` or ``variant``
|
|
501
|
+
is unknown, or a season is not a year or is outside the seasons sdvplot knows for the league.
|
|
502
|
+
OfflineError: When the plot is drawn, if the logo manifest or a mark's image is neither cached nor downloadable
|
|
503
|
+
(a DownloadError, also an OSError, when the CDN answers with an error status; an IntegrityError when it
|
|
504
|
+
sends a file that does not match the manifest's sha256, or one PIL cannot decode).
|
|
505
|
+
UnsafeDownloadError: (an OSError) When the plot is drawn, if a download is refused: larger than the byte cap,
|
|
506
|
+
past the deadline, or redirected away from https.
|
|
507
|
+
UnsafeCachePathError: (a ValueError) When the plot is drawn, if the manifest's sha256 or extension for a mark
|
|
508
|
+
would put the file outside the cache directory.
|
|
509
|
+
OptionalDependencyError: When the plot is drawn, if a mark is an SVG and the ``svg`` extra is not installed.
|
|
510
|
+
|
|
511
|
+
Example:
|
|
512
|
+
::
|
|
513
|
+
|
|
514
|
+
import pandas as pd
|
|
515
|
+
import sdvplot
|
|
516
|
+
from plotnine import aes, geom_point, ggplot
|
|
517
|
+
|
|
518
|
+
df = pd.DataFrame({"epa": [0.2, 0.15], "sr": [0.48, 0.47]})
|
|
519
|
+
p = ggplot(df, aes("epa", "sr")) + geom_point()
|
|
520
|
+
p2 = sdvplot.add_logos(p, [0.2], [0.48], ["KC"], league="nfl")
|
|
521
|
+
|
|
522
|
+
See Also:
|
|
523
|
+
sdvplotR geom_nfl_logos(): https://sdvplotR.sportsdataverse.org/ ;
|
|
524
|
+
sdvplot.plotnine.geom_sdv_logos: the layer this adds
|
|
525
|
+
"""
|
|
526
|
+
layer = geom_sdv_logos(
|
|
527
|
+
aes("x", "y", team="team", season="season"), data=_frame(x, y, teams, "team", season), inherit_aes=False,
|
|
528
|
+
league=league, height=height, alpha=alpha, variant=variant, id_system=id_system,
|
|
529
|
+
) # fmt: skip
|
|
530
|
+
return _ggplot(target) + layer
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
def add_wordmarks(
|
|
534
|
+
target: ggplot,
|
|
535
|
+
x: Any,
|
|
536
|
+
y: Any,
|
|
537
|
+
teams: Any,
|
|
538
|
+
*,
|
|
539
|
+
league: str,
|
|
540
|
+
season: Any = None,
|
|
541
|
+
height: float = 0.1,
|
|
542
|
+
alpha: float = 1,
|
|
543
|
+
variant: str = "default",
|
|
544
|
+
id_system: str = "auto",
|
|
545
|
+
) -> ggplot:
|
|
546
|
+
"""A copy of the plot with each team's wordmark at its (x, y).
|
|
547
|
+
|
|
548
|
+
Args:
|
|
549
|
+
target: A plotnine ggplot.
|
|
550
|
+
x: The points' x positions (read by position).
|
|
551
|
+
y: The points' y positions, the same length as ``x``.
|
|
552
|
+
teams: The team for each point.
|
|
553
|
+
league: The SDV league key, e.g. "nfl".
|
|
554
|
+
season: One season, or one per point, to pick each team's wordmark for that era.
|
|
555
|
+
height: The wordmark height as a fraction of the panel height, in (0, 1].
|
|
556
|
+
alpha: Opacity, 0 to 1.
|
|
557
|
+
variant: "default", "dark", or a named variant from ``marks()``.
|
|
558
|
+
id_system: The id system of ``teams``.
|
|
559
|
+
|
|
560
|
+
Returns:
|
|
561
|
+
ggplot: A new plot with a ``geom_sdv_wordmarks`` layer.
|
|
562
|
+
|
|
563
|
+
Raises:
|
|
564
|
+
ValueError: If the inputs differ in length.
|
|
565
|
+
UnsupportedTargetError: (a TypeError) If ``target`` is not a plotnine ggplot.
|
|
566
|
+
InputError: (a ValueError) If ``height`` or ``alpha`` is out of range, or ``season`` is not a year (or a list
|
|
567
|
+
whose length does not match the teams); when the plot is drawn, if ``league``, ``id_system`` or ``variant``
|
|
568
|
+
is unknown, or a season is not a year or is outside the seasons sdvplot knows for the league.
|
|
569
|
+
OfflineError: When the plot is drawn, if the logo manifest or a mark's image is neither cached nor downloadable
|
|
570
|
+
(a DownloadError, also an OSError, when the CDN answers with an error status; an IntegrityError when it
|
|
571
|
+
sends a file that does not match the manifest's sha256, or one PIL cannot decode).
|
|
572
|
+
UnsafeDownloadError: (an OSError) When the plot is drawn, if a download is refused: larger than the byte cap,
|
|
573
|
+
past the deadline, or redirected away from https.
|
|
574
|
+
UnsafeCachePathError: (a ValueError) When the plot is drawn, if the manifest's sha256 or extension for a mark
|
|
575
|
+
would put the file outside the cache directory.
|
|
576
|
+
OptionalDependencyError: When the plot is drawn, if a mark is an SVG and the ``svg`` extra is not installed.
|
|
577
|
+
|
|
578
|
+
Example:
|
|
579
|
+
::
|
|
580
|
+
|
|
581
|
+
import pandas as pd
|
|
582
|
+
import sdvplot
|
|
583
|
+
from plotnine import aes, geom_point, ggplot
|
|
584
|
+
|
|
585
|
+
df = pd.DataFrame({"epa": [0.2, 0.15], "sr": [0.48, 0.47]})
|
|
586
|
+
p = ggplot(df, aes("epa", "sr")) + geom_point()
|
|
587
|
+
p2 = sdvplot.add_wordmarks(p, [0.2], [0.48], ["KC"], league="nfl")
|
|
588
|
+
|
|
589
|
+
See Also:
|
|
590
|
+
sdvplotR geom_nfl_wordmarks(): https://sdvplotR.sportsdataverse.org/ ;
|
|
591
|
+
sdvplot.plotnine.geom_sdv_wordmarks: the layer this adds
|
|
592
|
+
"""
|
|
593
|
+
layer = geom_sdv_wordmarks(
|
|
594
|
+
aes("x", "y", team="team", season="season"), data=_frame(x, y, teams, "team", season), inherit_aes=False,
|
|
595
|
+
league=league, height=height, alpha=alpha, variant=variant, id_system=id_system,
|
|
596
|
+
) # fmt: skip
|
|
597
|
+
return _ggplot(target) + layer
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
def add_headshots(
|
|
601
|
+
target: ggplot,
|
|
602
|
+
x: Any,
|
|
603
|
+
y: Any,
|
|
604
|
+
players: Any,
|
|
605
|
+
*,
|
|
606
|
+
league: str,
|
|
607
|
+
height: float = 0.1,
|
|
608
|
+
alpha: float = 1,
|
|
609
|
+
id_system: str = "espn",
|
|
610
|
+
) -> ggplot:
|
|
611
|
+
"""A copy of the plot with each player's headshot at its (x, y).
|
|
612
|
+
|
|
613
|
+
Args:
|
|
614
|
+
target: A plotnine ggplot.
|
|
615
|
+
x: The points' x positions (read by position).
|
|
616
|
+
y: The points' y positions, the same length as ``x``.
|
|
617
|
+
players: The player id for each point.
|
|
618
|
+
league: The SDV league key, e.g. "nfl".
|
|
619
|
+
height: The headshot height as a fraction of the panel height, in (0, 1].
|
|
620
|
+
alpha: Opacity, 0 to 1.
|
|
621
|
+
id_system: "espn" or "gsis" (NFL).
|
|
622
|
+
|
|
623
|
+
Returns:
|
|
624
|
+
ggplot: A new plot with a ``geom_sdv_headshots`` layer.
|
|
625
|
+
|
|
626
|
+
Raises:
|
|
627
|
+
ValueError: If the inputs differ in length.
|
|
628
|
+
UnsupportedTargetError: (a TypeError) If ``target`` is not a plotnine ggplot.
|
|
629
|
+
InputError: (a ValueError) If ``height`` or ``alpha`` is out of range; when the plot is drawn, if ``league`` has
|
|
630
|
+
no ESPN headshots or ``id_system`` is not valid for it.
|
|
631
|
+
OfflineError: When the plot is drawn, if a headshot, or with ``id_system="gsis"`` the nflverse player table, is
|
|
632
|
+
neither cached nor downloadable (a DownloadError, also an OSError, for an HTTP error status).
|
|
633
|
+
UnsafeDownloadError: (an OSError) When the plot is drawn, if a download is refused: larger than the byte cap,
|
|
634
|
+
past the deadline, or redirected away from https.
|
|
635
|
+
|
|
636
|
+
Example:
|
|
637
|
+
::
|
|
638
|
+
|
|
639
|
+
import pandas as pd
|
|
640
|
+
import sdvplot
|
|
641
|
+
from plotnine import aes, geom_point, ggplot
|
|
642
|
+
|
|
643
|
+
df = pd.DataFrame({"epa": [0.2, 0.15], "sr": [0.48, 0.47]})
|
|
644
|
+
p = ggplot(df, aes("epa", "sr")) + geom_point()
|
|
645
|
+
p2 = sdvplot.add_headshots(p, [0.2], [0.48], ["3139477"], league="nfl")
|
|
646
|
+
|
|
647
|
+
See Also:
|
|
648
|
+
sdvplotR geom_nfl_headshots(): https://sdvplotR.sportsdataverse.org/ ;
|
|
649
|
+
sdvplot.plotnine.geom_sdv_headshots: the layer this adds
|
|
650
|
+
"""
|
|
651
|
+
layer = geom_sdv_headshots(
|
|
652
|
+
aes("x", "y", player_id="player_id"), data=_frame(x, y, players, "player_id"), inherit_aes=False,
|
|
653
|
+
league=league, height=height, alpha=alpha, id_system=id_system,
|
|
654
|
+
) # fmt: skip
|
|
655
|
+
return _ggplot(target) + layer
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
class _AxisLogos:
|
|
659
|
+
"""Added with ``+``: pads the axis text to make room, then (as a plotnine watermark, drawn after the breaks and
|
|
660
|
+
labels) runs the matplotlib axis-logo routine on every panel."""
|
|
661
|
+
|
|
662
|
+
def __init__(self, axis: str, **kw: Any) -> None:
|
|
663
|
+
if axis not in ("x", "y"):
|
|
664
|
+
raise ValueError(f"axis must be 'x' or 'y', got {axis!r}")
|
|
665
|
+
check_height(kw["height"])
|
|
666
|
+
_check_mark_type(kw["mark_type"])
|
|
667
|
+
self.axis, self.kw = axis, kw
|
|
668
|
+
|
|
669
|
+
def __radd__(self, gg: ggplot) -> ggplot:
|
|
670
|
+
size = gg.theme.getp("figure_size") or (6.4, 4.8)
|
|
671
|
+
points = self.kw["height"] * size[1] * 72 * 0.8 # ~ the panel's share of the figure height
|
|
672
|
+
margin: Any = {"t": points, "unit": "pt"} if self.axis == "x" else {"r": points, "unit": "pt"}
|
|
673
|
+
gg += p9_theme(**{f"axis_text_{self.axis}": element_text(margin=margin)})
|
|
674
|
+
gg.watermarks.append(self)
|
|
675
|
+
return gg
|
|
676
|
+
|
|
677
|
+
def draw(self, figure: Figure) -> None:
|
|
678
|
+
# every panel's labels are placed together first, so each skip reason warns once, not once per panel
|
|
679
|
+
kw = self.kw
|
|
680
|
+
labels = list(dict.fromkeys(lab for ax in figure.axes for lab in _in_view(ax, self.axis)[1]))
|
|
681
|
+
zeros = [0.0] * len(labels)
|
|
682
|
+
place(zeros, zeros, labels, league=kw["league"], season=kw["season"], kind=kw["mark_type"],
|
|
683
|
+
variant=kw["variant"], id_system=kw["id_system"]) # fmt: skip
|
|
684
|
+
for ax in figure.axes:
|
|
685
|
+
_mpl_axis_logos(ax, self.axis, **kw, warn=False)
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
def axis_logos(
|
|
689
|
+
target: ggplot,
|
|
690
|
+
axis: str,
|
|
691
|
+
*,
|
|
692
|
+
league: str,
|
|
693
|
+
season: Any = None,
|
|
694
|
+
height: float = 0.1,
|
|
695
|
+
variant: str = "default",
|
|
696
|
+
mark_type: str = "logo",
|
|
697
|
+
id_system: str = "auto",
|
|
698
|
+
) -> ggplot:
|
|
699
|
+
"""A copy of the plot whose team axis shows logos (or wordmarks) instead of tick labels.
|
|
700
|
+
|
|
701
|
+
Args:
|
|
702
|
+
target: A plotnine ggplot whose ``axis`` is a discrete team axis.
|
|
703
|
+
axis: "x" or "y".
|
|
704
|
+
league: The SDV league key, e.g. "nfl".
|
|
705
|
+
season: One season for every label.
|
|
706
|
+
height: The image height as a fraction of the panel height, in (0, 1].
|
|
707
|
+
variant: "default", "dark", or a named variant from ``marks()``.
|
|
708
|
+
mark_type: "logo" or "wordmark".
|
|
709
|
+
id_system: The id system of the labels.
|
|
710
|
+
|
|
711
|
+
Returns:
|
|
712
|
+
ggplot: A new plot; labels that are not teams stay as text, with one SdvplotWarning when drawn.
|
|
713
|
+
|
|
714
|
+
Raises:
|
|
715
|
+
ValueError: If ``axis`` is not "x"/"y".
|
|
716
|
+
UnsupportedTargetError: (a TypeError) If ``target`` is not a plotnine ggplot.
|
|
717
|
+
InputError: (a ValueError) If ``height`` is out of range or ``mark_type`` is not "logo"/"wordmark"; when the
|
|
718
|
+
plot is drawn, if ``league``, ``id_system`` or ``variant`` is unknown, or a season is not a year or is
|
|
719
|
+
outside the seasons sdvplot knows for the league.
|
|
720
|
+
OfflineError: When the plot is drawn, if the logo manifest or a mark's image is neither cached nor downloadable
|
|
721
|
+
(a DownloadError, also an OSError, when the CDN answers with an error status; an IntegrityError when it
|
|
722
|
+
sends a file that does not match the manifest's sha256, or one PIL cannot decode).
|
|
723
|
+
UnsafeDownloadError: (an OSError) When the plot is drawn, if a download is refused: larger than the byte cap,
|
|
724
|
+
past the deadline, or redirected away from https.
|
|
725
|
+
UnsafeCachePathError: (a ValueError) When the plot is drawn, if the manifest's sha256 or extension for a mark
|
|
726
|
+
would put the file outside the cache directory.
|
|
727
|
+
OptionalDependencyError: When the plot is drawn, if a mark is an SVG and the ``svg`` extra is not installed.
|
|
728
|
+
|
|
729
|
+
Example:
|
|
730
|
+
::
|
|
731
|
+
|
|
732
|
+
import pandas as pd
|
|
733
|
+
import sdvplot
|
|
734
|
+
from plotnine import aes, geom_col, ggplot
|
|
735
|
+
|
|
736
|
+
df = pd.DataFrame({"team": ["KC", "BUF", "BAL"], "epa": [0.2, 0.15, 0.1]})
|
|
737
|
+
p = ggplot(df, aes("team", "epa")) + geom_col()
|
|
738
|
+
p2 = sdvplot.axis_logos(p, "x", league="nfl")
|
|
739
|
+
|
|
740
|
+
See Also:
|
|
741
|
+
sdvplotR element_sdv_logo(): https://sdvplotR.sportsdataverse.org/ ;
|
|
742
|
+
sdvplot.plotnine.title_image: an image beside the plot title
|
|
743
|
+
"""
|
|
744
|
+
return _ggplot(target) + _AxisLogos(
|
|
745
|
+
axis, league=league, season=season, height=height, variant=variant, mark_type=mark_type, id_system=id_system
|
|
746
|
+
)
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
class _TitleImage:
|
|
750
|
+
"""Added with ``+``: sets the title, then (as a plotnine watermark, drawn after the figure texts) puts the image
|
|
751
|
+
beside the rendered title, aligned with it as the theme's ``plot_title`` alignment says."""
|
|
752
|
+
|
|
753
|
+
def __init__(self, image: Any, title: str, league: str | None, season: Any, side: str, height: float) -> None:
|
|
754
|
+
self.height = _check_title_image(side, height)
|
|
755
|
+
self.side, self.title = side, title or " " # a blank title still gives the image a line to sit on
|
|
756
|
+
self.source = _title_source(image, league, season) # resolved (and warned about) when built, not drawn
|
|
757
|
+
|
|
758
|
+
def __radd__(self, gg: ggplot) -> ggplot:
|
|
759
|
+
gg += labs(title=self.title)
|
|
760
|
+
gg.watermarks = [w for w in gg.watermarks if not isinstance(w, _TitleImage)] + [self] # one title image
|
|
761
|
+
return gg
|
|
762
|
+
|
|
763
|
+
def draw(self, figure: Figure) -> None:
|
|
764
|
+
if self.source is None:
|
|
765
|
+
return
|
|
766
|
+
text = next((t for t in figure.texts if t.get_text() == self.title), None)
|
|
767
|
+
if text is None:
|
|
768
|
+
warn(f"title_image: the plot's title is no longer {self.title!r}; add title_image() after labs(title=...)")
|
|
769
|
+
return
|
|
770
|
+
|
|
771
|
+
def align() -> float: # plotnine left-aligns the text and places it by the theme's plot_title ha
|
|
772
|
+
theme_ = getattr(figure.get_layout_engine(), "theme", None)
|
|
773
|
+
return _align(theme_.getp(("plot_title", "ha")) if theme_ is not None else "left")
|
|
774
|
+
|
|
775
|
+
_add_title_image(figure, text, self.source, self.side, self.height, align)
|
|
776
|
+
|
|
777
|
+
|
|
778
|
+
def title_image(
|
|
779
|
+
image: Any,
|
|
780
|
+
title: str = "",
|
|
781
|
+
*,
|
|
782
|
+
league: str | None = None,
|
|
783
|
+
season: Any = None,
|
|
784
|
+
side: str = "left",
|
|
785
|
+
height: float = 15,
|
|
786
|
+
) -> Any:
|
|
787
|
+
"""A plot title with an image (a team logo, or any image) beside it, added to a ggplot with ``+``.
|
|
788
|
+
|
|
789
|
+
The pair follows the theme's ``plot_title`` alignment, like the image inside sdvplotR's title: plotnine centres a
|
|
790
|
+
lone title and left-aligns one with a subtitle.
|
|
791
|
+
|
|
792
|
+
Args:
|
|
793
|
+
image: A team, in any id system ``resolve()`` understands, when ``league`` is given; otherwise an image URL
|
|
794
|
+
(https; http is refused) or a local file path.
|
|
795
|
+
title: The title text; it replaces ``labs(title=...)``, so add ``title_image`` after any ``labs``.
|
|
796
|
+
league: The SDV league key, e.g. "nfl"; None reads ``image`` as a URL or path.
|
|
797
|
+
season: One season, to pick the team's logo for that era.
|
|
798
|
+
side: "left" or "right" of the title text.
|
|
799
|
+
height: The image height in points (1/72 inch). The title keeps its own line height, so an image much taller
|
|
800
|
+
than the text needs room: a ``plot_title`` margin in ``theme()``.
|
|
801
|
+
|
|
802
|
+
Returns:
|
|
803
|
+
object: An object to add to a ggplot; the image is loaded now, and an unknown team, or an image by URL or path
|
|
804
|
+
that cannot be read, gives one SdvplotWarning now (the title is drawn without the image). A second
|
|
805
|
+
``title_image`` added to the same plot replaces the first.
|
|
806
|
+
|
|
807
|
+
Raises:
|
|
808
|
+
TypeError: If ``league`` is given and ``image`` is not one team.
|
|
809
|
+
InputError: (a ValueError) If ``height`` is not a number of points of at least 1, or ``league`` is given and it
|
|
810
|
+
is unknown or ``season`` is not a year or is outside the seasons sdvplot knows for the league.
|
|
811
|
+
ValueError: If ``side`` is not "left"/"right".
|
|
812
|
+
OfflineError: If ``league`` is given and the team's logo (or the logo manifest) is neither cached nor
|
|
813
|
+
downloadable (a DownloadError, also an OSError, when the CDN answers with an error status; an IntegrityError
|
|
814
|
+
when it sends a file that does not match the manifest's sha256, or one PIL cannot decode). An image by URL
|
|
815
|
+
or path that cannot be read is skipped with a warning instead.
|
|
816
|
+
UnsafeDownloadError: (an OSError) If ``league`` is given and a download is refused: larger than the byte cap,
|
|
817
|
+
past the deadline, or redirected away from https.
|
|
818
|
+
UnsafeCachePathError: (a ValueError) If ``league`` is given and the manifest's sha256 or extension for the logo
|
|
819
|
+
would put the file outside the cache directory.
|
|
820
|
+
OptionalDependencyError: If ``league`` is given, the logo is an SVG and the ``svg`` extra is not installed.
|
|
821
|
+
|
|
822
|
+
Example:
|
|
823
|
+
::
|
|
824
|
+
|
|
825
|
+
import pandas as pd
|
|
826
|
+
from plotnine import aes, geom_point, ggplot
|
|
827
|
+
from sdvplot.plotnine import title_image
|
|
828
|
+
|
|
829
|
+
df = pd.DataFrame({"epa": [0.2, 0.15], "sr": [0.48, 0.47]})
|
|
830
|
+
p = ggplot(df, aes("epa", "sr")) + geom_point() + title_image("KC", "Chiefs", league="nfl", height=20)
|
|
831
|
+
|
|
832
|
+
See Also:
|
|
833
|
+
sdvplotR ggtitle_image(): https://sdvplotR.sportsdataverse.org/reference/ggtitle_image.html ;
|
|
834
|
+
sdvplot.matplotlib.title_image: the same for matplotlib.
|
|
835
|
+
"""
|
|
836
|
+
return _TitleImage(image, title, league, season, side, height)
|
|
837
|
+
|
|
838
|
+
|
|
839
|
+
def team_tiers(
|
|
840
|
+
data: Any,
|
|
841
|
+
league: str,
|
|
842
|
+
*,
|
|
843
|
+
title: str | None = None,
|
|
844
|
+
subtitle: str | None = _tiers.SUBTITLE,
|
|
845
|
+
caption: str | None = None,
|
|
846
|
+
tier_desc: dict[Any, str] | None = None,
|
|
847
|
+
presort: bool = False,
|
|
848
|
+
alpha: float = 0.8,
|
|
849
|
+
height: float | None = None,
|
|
850
|
+
no_line_below_tier: Any = None,
|
|
851
|
+
devel: bool = False,
|
|
852
|
+
theme: Literal["dark", "light"] = "dark",
|
|
853
|
+
) -> ggplot:
|
|
854
|
+
"""A tier list as a ggplot: each team's logo in its tier's row, tier 1 on top, on a dark (sdvplotR) or light theme.
|
|
855
|
+
|
|
856
|
+
Args:
|
|
857
|
+
data: A pandas or polars DataFrame with ``tier_no`` (1 is the top tier) and ``team`` (any id system
|
|
858
|
+
``resolve()`` understands), and optionally ``tier_rank``, the position within the tier; without it, teams
|
|
859
|
+
keep their order in ``data``.
|
|
860
|
+
league: The SDV league key, e.g. "nfl".
|
|
861
|
+
title: The title; None gives "{LEAGUE} Team Tiers", "" none.
|
|
862
|
+
subtitle: The subtitle; None or "" for none.
|
|
863
|
+
caption: The caption; None for none.
|
|
864
|
+
tier_desc: Each tier's label, keyed by tier number; None gives sdvplotR's (1 "Elite" ... 5 "What are they
|
|
865
|
+
doing?"). Labels wrap at 15 characters; a tier without one gets none.
|
|
866
|
+
presort: Sort teams alphabetically within each tier (ignores ``tier_rank``).
|
|
867
|
+
alpha: Logo opacity, 0 to 1.
|
|
868
|
+
height: Logo height as a fraction of the panel height; None gives 0.1, about the largest height at
|
|
869
|
+
which 32 logos in 5 tiers (7, 7, 6, 6, 6) neither overlap nor leave the panel at the default 6.4 x 4.8 in
|
|
870
|
+
figure.
|
|
871
|
+
no_line_below_tier: A tier number, or several, with no separator line below.
|
|
872
|
+
devel: Draw each team as text instead of its logo (fast, and needs no download).
|
|
873
|
+
theme: "dark" (sdvplotR's: a near-black background) or "light" (white, for dark logos such as Ohio State's,
|
|
874
|
+
Texas A&M's or Penn State's, which vanish on dark).
|
|
875
|
+
|
|
876
|
+
Returns:
|
|
877
|
+
ggplot: The plot; a team that does not resolve is skipped with one SdvplotWarning, keeping its slot.
|
|
878
|
+
|
|
879
|
+
Raises:
|
|
880
|
+
TypeError: If ``data`` is not a DataFrame, or ``tier_no``/``tier_rank`` hold non-numbers.
|
|
881
|
+
InputError: (a ValueError) If ``height``/``alpha`` is out of range, or ``league`` is unknown.
|
|
882
|
+
ValueError: If ``data`` lacks ``tier_no`` or ``team``, has no row with a tier, or ``theme`` is not "dark" or
|
|
883
|
+
"light".
|
|
884
|
+
OfflineError: Unless ``devel=True``, when the plot is drawn, if the logo manifest or a logo is neither cached
|
|
885
|
+
nor downloadable (a DownloadError, also an OSError, for an HTTP error status; an IntegrityError for a file
|
|
886
|
+
that does not match the manifest's sha256, or one PIL cannot decode).
|
|
887
|
+
UnsafeDownloadError: (an OSError) Unless ``devel=True``, when the plot is drawn, if a download is refused.
|
|
888
|
+
UnsafeCachePathError: (a ValueError) Unless ``devel=True``, when the plot is drawn, if the manifest's sha256 or
|
|
889
|
+
extension for a logo would put the file outside the cache directory.
|
|
890
|
+
OptionalDependencyError: Unless ``devel=True``, when the plot is drawn, if a logo is an SVG and the ``svg``
|
|
891
|
+
extra is not installed.
|
|
892
|
+
|
|
893
|
+
Example:
|
|
894
|
+
::
|
|
895
|
+
|
|
896
|
+
import pandas as pd
|
|
897
|
+
from sdvplot.plotnine import team_tiers
|
|
898
|
+
|
|
899
|
+
df = pd.DataFrame({"tier_no": [1, 1, 2, 3], "team": ["KC", "BUF", "BAL", "NYJ"]})
|
|
900
|
+
p = team_tiers(df, "nfl", caption="data: nflverse")
|
|
901
|
+
|
|
902
|
+
# Draft it as text first:
|
|
903
|
+
p = team_tiers(df, "nfl", devel=True)
|
|
904
|
+
|
|
905
|
+
# Dark logos on a white background:
|
|
906
|
+
p = team_tiers(df, "cfb", theme="light")
|
|
907
|
+
|
|
908
|
+
See Also:
|
|
909
|
+
sdvplotR sdv_team_tiers(): https://sdvplotR.sportsdataverse.org/reference/sdv_team_tiers.html ;
|
|
910
|
+
sdvplot.matplotlib.team_tiers: the same as a matplotlib Figure.
|
|
911
|
+
"""
|
|
912
|
+
t = _tiers.prepare(
|
|
913
|
+
data, league, title=title, subtitle=subtitle, caption=caption, tier_desc=tier_desc, presort=presort,
|
|
914
|
+
alpha=alpha, height=height, no_line_below_tier=no_line_below_tier, theme=theme,
|
|
915
|
+
) # fmt: skip
|
|
916
|
+
frame = pd.DataFrame({"x": t.x, "y": t.y, "team": t.team_ids, "label": t.labels})
|
|
917
|
+
if devel:
|
|
918
|
+
marks: Any = geom_text(aes(label="label"), color=t.text)
|
|
919
|
+
else:
|
|
920
|
+
marks = geom_sdv_logos(aes(team="team"), league=league, id_system="team_id", height=t.height, alpha=t.alpha)
|
|
921
|
+
texts = {"title": t.title, "subtitle": t.subtitle, "caption": t.caption}
|
|
922
|
+
return (
|
|
923
|
+
ggplot(frame, aes("x", "y"))
|
|
924
|
+
+ geom_hline(yintercept=t.lines, color=t.line_color)
|
|
925
|
+
+ marks
|
|
926
|
+
+ scale_x_continuous(limits=t.xlim, expand=(0, 0))
|
|
927
|
+
+ scale_y_reverse(limits=t.ylim, breaks=t.breaks, labels=t.break_labels, expand=(0, 0))
|
|
928
|
+
+ labs(**{k: v for k, v in texts.items() if v})
|
|
929
|
+
+ theme_minimal(base_size=11.5)
|
|
930
|
+
+ p9_theme(
|
|
931
|
+
plot_title=element_text(color=t.text, weight="bold"),
|
|
932
|
+
plot_subtitle=element_text(color=t.muted),
|
|
933
|
+
plot_caption=element_text(color=t.muted, ha="right"),
|
|
934
|
+
plot_title_position="plot",
|
|
935
|
+
axis_text_x=element_blank(),
|
|
936
|
+
axis_text_y=element_text(color=t.text, weight="bold", size=11.5 * 0.8 * 1.1), # sdvplotR: rel(1.1)
|
|
937
|
+
axis_title=element_blank(),
|
|
938
|
+
panel_grid=element_blank(),
|
|
939
|
+
plot_background=element_rect(fill=t.bg, color=t.bg),
|
|
940
|
+
panel_background=element_rect(fill=t.bg, color=t.bg),
|
|
941
|
+
)
|
|
942
|
+
)
|
|
943
|
+
|
|
944
|
+
|
|
945
|
+
def _scale(
|
|
946
|
+
kind: Any,
|
|
947
|
+
league: str,
|
|
948
|
+
which: Which,
|
|
949
|
+
season: Any,
|
|
950
|
+
na_value: str,
|
|
951
|
+
kwargs: dict[str, Any],
|
|
952
|
+
id_system: IdSystem = "auto",
|
|
953
|
+
strict: bool = False,
|
|
954
|
+
) -> Any:
|
|
955
|
+
_column(which) # "primary" / "secondary", else ValueError now rather than when the plot is drawn
|
|
956
|
+
|
|
957
|
+
class _TeamScale(kind):
|
|
958
|
+
def __init__(self) -> None:
|
|
959
|
+
super().__init__(values={}, na_value=na_value, **kwargs)
|
|
960
|
+
|
|
961
|
+
def map(self, x: Any, limits: Any = None) -> Any:
|
|
962
|
+
values = [v for v in (limits if limits is not None else self.final_limits) if v is not None]
|
|
963
|
+
colors = team_colors(league, values, which=which, season=season, id_system=id_system, strict=strict)
|
|
964
|
+
self._values = {v: c for v, c in zip(values, colors, strict=True) if c is not None}
|
|
965
|
+
self.palette = lambda n: [self._values.get(v, na_value) for v in values]
|
|
966
|
+
return [self._values.get(v, na_value) for v in x]
|
|
967
|
+
|
|
968
|
+
return _TeamScale()
|
|
969
|
+
|
|
970
|
+
|
|
971
|
+
def scale_color_sdv(
|
|
972
|
+
league: str,
|
|
973
|
+
*,
|
|
974
|
+
which: Which = "primary",
|
|
975
|
+
season: Any = None,
|
|
976
|
+
id_system: IdSystem = "auto",
|
|
977
|
+
strict: bool = False,
|
|
978
|
+
na_value: str = "grey",
|
|
979
|
+
**kwargs: Any,
|
|
980
|
+
) -> Any:
|
|
981
|
+
"""A discrete color scale that maps each team value (any id system) to its team color.
|
|
982
|
+
|
|
983
|
+
Args:
|
|
984
|
+
league: The SDV league key, e.g. "nfl".
|
|
985
|
+
which: "primary" or "secondary".
|
|
986
|
+
season: One season for every value.
|
|
987
|
+
id_system: The id system of the team values: ``"auto"`` (the default) tries them in order; pass
|
|
988
|
+
``"nhl_id"`` for NHL stats ids, which ``"auto"`` never tries.
|
|
989
|
+
strict: Raise ``UnresolvedTeamError`` when the plot is drawn and a value does not resolve, instead of drawing
|
|
990
|
+
it in ``na_value`` with one ``SdvplotWarning``.
|
|
991
|
+
na_value: The color of values that are not teams.
|
|
992
|
+
**kwargs: Passed to plotnine's ``scale_color_manual`` (``name``, ``breaks``, ``guide``, ...).
|
|
993
|
+
|
|
994
|
+
Returns:
|
|
995
|
+
scale: A plotnine color scale.
|
|
996
|
+
|
|
997
|
+
Raises:
|
|
998
|
+
InputError: (a ValueError) If ``which`` is not "primary" or "secondary"; when the plot is drawn, if ``league``
|
|
999
|
+
or ``id_system`` is unknown or ``season`` is not a year or is outside the seasons sdvplot knows for the
|
|
1000
|
+
league.
|
|
1001
|
+
UnresolvedTeamError: (a ValueError) With ``strict=True``, when the plot is drawn and a value does not resolve.
|
|
1002
|
+
|
|
1003
|
+
Example:
|
|
1004
|
+
::
|
|
1005
|
+
|
|
1006
|
+
import pandas as pd
|
|
1007
|
+
from plotnine import aes, geom_point, ggplot
|
|
1008
|
+
from sdvplot.plotnine import scale_color_sdv
|
|
1009
|
+
|
|
1010
|
+
df = pd.DataFrame({"team": ["KC", "BUF"], "epa": [0.2, 0.15], "sr": [0.48, 0.47]})
|
|
1011
|
+
p = ggplot(df, aes("epa", "sr", color="team")) + geom_point() + scale_color_sdv("nfl")
|
|
1012
|
+
|
|
1013
|
+
See Also:
|
|
1014
|
+
sdvplotR scale_color_sdv(): https://sdvplotR.sportsdataverse.org/ ;
|
|
1015
|
+
sdvplot.plotnine.scale_fill_sdv: the fill scale
|
|
1016
|
+
"""
|
|
1017
|
+
return _scale(scale_color_manual, league, which, season, na_value, kwargs, id_system, strict)
|
|
1018
|
+
|
|
1019
|
+
|
|
1020
|
+
def scale_fill_sdv(
|
|
1021
|
+
league: str,
|
|
1022
|
+
*,
|
|
1023
|
+
which: Which = "primary",
|
|
1024
|
+
season: Any = None,
|
|
1025
|
+
id_system: IdSystem = "auto",
|
|
1026
|
+
strict: bool = False,
|
|
1027
|
+
na_value: str = "grey",
|
|
1028
|
+
**kwargs: Any,
|
|
1029
|
+
) -> Any:
|
|
1030
|
+
"""A discrete fill scale that maps each team value (any id system) to its team color.
|
|
1031
|
+
|
|
1032
|
+
Args:
|
|
1033
|
+
league: The SDV league key, e.g. "nfl".
|
|
1034
|
+
which: "primary" or "secondary".
|
|
1035
|
+
season: One season for every value.
|
|
1036
|
+
id_system: The id system of the team values: ``"auto"`` (the default) tries them in order; pass
|
|
1037
|
+
``"nhl_id"`` for NHL stats ids, which ``"auto"`` never tries.
|
|
1038
|
+
strict: Raise ``UnresolvedTeamError`` when the plot is drawn and a value does not resolve, instead of drawing
|
|
1039
|
+
it in ``na_value`` with one ``SdvplotWarning``.
|
|
1040
|
+
na_value: The color of values that are not teams.
|
|
1041
|
+
**kwargs: Passed to plotnine's ``scale_fill_manual``.
|
|
1042
|
+
|
|
1043
|
+
Returns:
|
|
1044
|
+
scale: A plotnine fill scale.
|
|
1045
|
+
|
|
1046
|
+
Raises:
|
|
1047
|
+
InputError: (a ValueError) If ``which`` is not "primary" or "secondary"; when the plot is drawn, if ``league``
|
|
1048
|
+
or ``id_system`` is unknown or ``season`` is not a year or is outside the seasons sdvplot knows for the
|
|
1049
|
+
league.
|
|
1050
|
+
UnresolvedTeamError: (a ValueError) With ``strict=True``, when the plot is drawn and a value does not resolve.
|
|
1051
|
+
|
|
1052
|
+
Example:
|
|
1053
|
+
::
|
|
1054
|
+
|
|
1055
|
+
import pandas as pd
|
|
1056
|
+
from plotnine import aes, geom_col, ggplot
|
|
1057
|
+
from sdvplot.plotnine import scale_fill_sdv
|
|
1058
|
+
|
|
1059
|
+
df = pd.DataFrame({"team": ["KC", "BUF"], "epa": [0.2, 0.15]})
|
|
1060
|
+
p = ggplot(df, aes("team", "epa", fill="team")) + geom_col() + scale_fill_sdv("nfl")
|
|
1061
|
+
|
|
1062
|
+
See Also:
|
|
1063
|
+
sdvplotR scale_fill_sdv(): https://sdvplotR.sportsdataverse.org/ ;
|
|
1064
|
+
sdvplot.plotnine.scale_color_sdv: the color scale
|
|
1065
|
+
"""
|
|
1066
|
+
return _scale(scale_fill_manual, league, which, season, na_value, kwargs, id_system, strict)
|
|
1067
|
+
|
|
1068
|
+
|
|
1069
|
+
def _drawn(target: ggplot) -> Figure:
|
|
1070
|
+
return target.draw()
|
|
1071
|
+
|
|
1072
|
+
|
|
1073
|
+
def _drawn_marks(target: ggplot) -> list[tuple[Any, ...]]:
|
|
1074
|
+
"""Test hook: draw the plot, then (team_id, x, y, height, url) for each mark image on any panel."""
|
|
1075
|
+
import matplotlib.pyplot as plt
|
|
1076
|
+
|
|
1077
|
+
fig = _drawn(target)
|
|
1078
|
+
try:
|
|
1079
|
+
return [m for ax in fig.axes for m in _mpl_drawn_marks(ax)]
|
|
1080
|
+
finally:
|
|
1081
|
+
plt.close(fig)
|
|
1082
|
+
|
|
1083
|
+
|
|
1084
|
+
def _drawn_axis_marks(target: ggplot, axis: str) -> list[tuple[str, float, float]]:
|
|
1085
|
+
"""Test hook: draw the plot, then (team_id, tick position, measured height) for each axis image on the first
|
|
1086
|
+
panel."""
|
|
1087
|
+
import matplotlib.pyplot as plt
|
|
1088
|
+
|
|
1089
|
+
fig = _drawn(target)
|
|
1090
|
+
try:
|
|
1091
|
+
return _mpl_drawn_axis_marks(fig.axes[0], axis)
|
|
1092
|
+
finally:
|
|
1093
|
+
plt.close(fig)
|
|
1094
|
+
|
|
1095
|
+
|
|
1096
|
+
def _visible_axis_labels(target: ggplot, axis: str) -> list[str]:
|
|
1097
|
+
"""Test hook: draw the plot, then the first panel's tick labels still shown as text."""
|
|
1098
|
+
import matplotlib.pyplot as plt
|
|
1099
|
+
|
|
1100
|
+
fig = _drawn(target)
|
|
1101
|
+
try:
|
|
1102
|
+
return _mpl_visible_axis_labels(fig.axes[0], axis)
|
|
1103
|
+
finally:
|
|
1104
|
+
plt.close(fig)
|