From 84cc0c2871f7a5b7d94c900819c4434c6df26620 Mon Sep 17 00:00:00 2001 From: "David H. Hagan" Date: Wed, 24 Apr 2024 22:23:14 -0400 Subject: [PATCH] Add examples and docs --- .github/workflows/deploy-docs.yml | 39 +++ README.md | 5 +- atmospy/__init__.py | 1 - atmospy/calendar.py | 299 -------------------- atmospy/rcmod.py | 39 ++- atmospy/relational.py | 63 +++-- atmospy/rose.py | 66 +++-- atmospy/trends.py | 359 ++++++++++++++++++++++-- atmospy/utils.py | 29 +- examples/anscombes_quartet.py | 18 ++ examples/dielplot.py | 30 ++ examples/pollution_rose.py | 18 ++ examples/regression.py | 19 ++ poetry.lock | 451 +++++++++++++++++++++++++----- pyproject.toml | 7 + 15 files changed, 978 insertions(+), 465 deletions(-) create mode 100644 .github/workflows/deploy-docs.yml delete mode 100644 atmospy/calendar.py create mode 100644 examples/anscombes_quartet.py create mode 100644 examples/dielplot.py create mode 100644 examples/pollution_rose.py create mode 100644 examples/regression.py diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 0000000..8e33fc2 --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,39 @@ +name: docs/gh-pages +on: [workflow_dispatch] + +jobs: + build-docs: + name: Build the docs and push to github pages + runs-on: ubuntu-latest + steps: + - name: Checkout branch + uses: actions/checkout@v2 + + - name: Setup Python + uses: actions/setup-python@master + with: + python-version: 3.8 + + - name: Install poetry + uses: snok/install-poetry@v1 + with: + virtualenvs-create: true + + - name: Install dependencies + run: poetry install --no-interaction + + - name: build docs + run: | + source $(poetry env info --path)/bin/activate + cd docs + make clean + make notebooks html + cd .. + + - name: deploy to gh-pages + uses: Cecilapp/GitHub-Pages-deploy@v3 + env: + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + with: + email: david.hagan@quant-aq.com + build_dir: docs/_build/html \ No newline at end of file diff --git a/README.md b/README.md index 39785e5..c809e33 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ # atmospy: air quality data visualization - - +[![PyPI version](https://badge.fury.io/py/atmospy.svg)](https://badge.fury.io/py/atmospy) +![PyPI - Python Version](https://img.shields.io/pypi/pyversions/atmospy) [![Tests](https://github.com/dhhagan/atmospy/actions/workflows/tests.yml/badge.svg)](https://github.com/dhhagan/atmospy/actions/workflows/tests.yml) [![codecov](https://codecov.io/gh/dhhagan/atmospy/branch/main/graph/badge.svg)](https://codecov.io/gh/dhhagan/atmospy) ![Apache 2.0 licensed](https://img.shields.io/github/license/dhhagan/atmospy) diff --git a/atmospy/__init__.py b/atmospy/__init__.py index c8b28ac..238ce2e 100644 --- a/atmospy/__init__.py +++ b/atmospy/__init__.py @@ -8,7 +8,6 @@ from .utils import * from .rcmod import * -from .calendar import * from .relational import * from .trends import * from .rose import * diff --git a/atmospy/calendar.py b/atmospy/calendar.py deleted file mode 100644 index 7bd368a..0000000 --- a/atmospy/calendar.py +++ /dev/null @@ -1,299 +0,0 @@ -"""This file will contain figures for: - - 1. Calendar - 2. straight line calendar thing like Sentry? -""" -from .utils import ( - remove_na, -) -from seaborn import ( - FacetGrid, -) -from atmospy.utils import ( - check_for_numeric_cols, - check_for_timestamp_col -) -import numpy as np -import math -import matplotlib as mpl -import matplotlib.pyplot as plt - -__all__ = ["calendarplot", ] - -@mpl.ticker.FuncFormatter -def custom_month_formatter(x, pos): - return str(math.ceil(x)) - - -def _yearplot(data, x, y, ax=None, agg="mean", cmap="crest", - height=2, aspect=5, vmin=None, vmax=None, - linecolor="white", linewidths=0, cbar=True, cbar_kws=None, units=""): - """Plot a full year of time series data on a heatmap by month. - """ - if ax is None: - ax = plt.gca() - ax.figure.set_size_inches(height*aspect, height) - - # if more than 1Y of data was provided, limit to 1Y - years = np.unique(data.index.year) - if years.size > 1: - # warn - data = data[data.index.year == years[0]] - - data["Day of Week"] = data.index.weekday - data["Week of Year"] = data.index.isocalendar().week - - # compute pivoted data - pivot = data.pivot_table( - index="Day of Week", - columns="Week of Year", - values=y, - aggfunc=agg - ) - - # adjust the index to ensure we have a properly-sized array - pivot = pivot.reindex( - index=range(0, 7), - columns=range(1, 53) - ) - - # reverse the array along the yaxis so that Monday ends up at the top of the fig - pivot = pivot[::-1] - - # set the min and max of the colorbar - if vmin is None: - vmin = np.nanmin(pivot.values) - - if vmax is None: - vmax = np.nanmax(pivot.values) - - # plot the heatmap - im = ax.pcolormesh( - pivot, - vmin=vmin, vmax=vmax, cmap=cmap, - linewidth=linewidths, edgecolors=linecolor - ) - - # modify the axes ticks - ax.xaxis.set_major_locator(mpl.ticker.LinearLocator(14)) - ax.xaxis.set_ticklabels([ - "", - "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" - "" - ], rotation="horizontal", va="center") - - ax.yaxis.tick_right() - ax.yaxis.set_ticks([0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5]) - ax.yaxis.set_ticklabels([ - "Sun", "Sat", "Fri", "Thu", "Wed", "Tue", "Mon" - ], rotation="horizontal", va="center", ha="left") - ax.yaxis.set_tick_params(right=False) - - # add a big ol' year on the left-hand side - ax.set_ylabel( - f"{years[0]}", - fontsize=28, color="gray", ha="center" - ) - - # add a colorbar if set - if cbar: - cbar_kws["pad"] = cbar_kws.get("pad", 0.05) - - cb = ax.figure.colorbar(im, ax=ax, **cbar_kws) - cb.outline.set_visible(False) - - # adjust the colorbar ticklabels - cb.ax.yaxis.set_major_locator(mpl.ticker.MaxNLocator(4)) - - # modify the tick labels - # TODO: this is currently not working - ticklabels = [x.get_text() for x in cb.ax.get_yticklabels()] - ticklabels[-1] = f"{ticklabels[-1]} {units}" - cb.set_ticks(cb.get_ticks()) - cb.set_ticklabels(ticklabels) - - return ax - - -def _monthplot(data, x, y, ax=None, agg="mean", height=3, aspect=1, - vmin=None, vmax=None, cmap="crest", linewidths=0.1, - linecolor="white", cbar=True, cbar_kws=None, units=None): - """Plot a full month of time series data on a heatmap by hour. - """ - if ax is None: - ax = plt.gca() - ax.figure.set_size_inches(height*aspect, height) - - # if more than 1mo of data was provided, limit to 1mo - months = np.unique(data.index.month) - if months.size > 1: - # TODO: log warning - data = data[data.index.month == months[0]] - - # add pivot columns - data["Day of Month"] = data.index.day - data["Hour of Day"] = data.index.hour - - # compute the pivot data - pivot = data.pivot_table( - index="Hour of Day", - columns="Day of Month", - values=y, - aggfunc=agg - ) - - # get the total number of available days in the month - days_in_month = data.index.days_in_month[0] - - # adjust the index to ensure we have a properly-sized array - pivot = pivot.reindex( - index=range(0, 24), - columns=range(1, days_in_month + 1) - ) - - # reverse the order of the matrix along the y-axis so that Monday is at the top - pivot = pivot[::-1] - - # set the min and max values for the colorbar - if vmin is None: - vmin = np.nanmin(pivot.values) - - if vmax is None: - vmax = np.nanmax(pivot.values) - - # plot the heatmap - im = ax.pcolormesh( - pivot, - cmap=cmap, vmin=vmin, vmax=vmax, - linewidth=linewidths, edgecolors=linecolor - ) - - # add a colorbar if set - if cbar: - cb = ax.figure.colorbar(im, ax=ax, **cbar_kws) - cb.outline.set_visible(False) - - # adjust the tick labels - ticklabels = [x.get_text() for x in cb.ax.get_yticklabels()] - ticklabels[-1] = f"{ticklabels[-1]} {units}" - cb.set_ticks(cb.get_ticks()) - cb.set_ticklabels(ticklabels) - - # adjust the axes labels - ax.xaxis.set_major_locator(mpl.ticker.FixedLocator([x - 0.5 for x in list(range(1, days_in_month, 4))])) - ax.xaxis.set_major_formatter(custom_month_formatter) - ax.set_yticks([0, 6, 12, 18, 24]) - ax.set_yticklabels([ - "12 AM", "6 PM", "12 PM", "6 AM", "12 AM" - ]) - - return ax - - -def calendarplot(data, x, y, freq="day", agg="mean", vmin=None, vmax=None, cmap="crest", - ax=None, linecolor="white", linewidths=0, cbar=True, cbar_kws=None, - xlabel=None, ylabel=None, title=None, units="", height=2, aspect=5.0): - """Create a heatmap from time series data. - - Parameters - ---------- - data : pd.DataFrame - Dataframe containing the data of interest. - x : str - The name of the datetime column. - y : str - The name of the data column. - freq : str, optional - The frequency by which to average (one of [`hour`, `day`]), by default "day" - agg : str, optional - The function to aggregate by, by default "mean" - vmin : float, optional - The minimum value to color by, by default None - vmax : float, optional - The maximum value to color by, by default None - cmap : str, optional - The name of the colormap, by default "crest" - ax : axes, optional - A matplotlib axes object, by default None - linecolor : str, optional - The color of the inner lines, by default "white" - linewidths : int, optional - The width of the inner lines, by default 0 - cbar : bool, optional - If true, add a colorbar, by default True - cbar_kws : dict, optional - A dictionary of kwargs to send along to the colorbar, by default None - xlabel : str, optional - The x-axis label, by default None - ylabel : str, optional - The y-axis label, by default None - title : str, optional - The figure title, by default None - units : str, optional - The units of the plotted item for labeling purposes only, by default "" - height : int, optional - The figure height in inches, by default 2 - aspect : float, optional - The aspect ratio of the figure, by default 5.0 - - Examples - -------- - - Returns - ------- - ax : axes object - TODO: Fill this out. - - """ - check_for_timestamp_col(data, x) - check_for_numeric_cols(data, [y]) - - if freq not in ("hour", "day"): - raise ValueError("Invalid argument for `freq`") - - cbar_kws_default = { - "shrink": 0.67, - "drawedges": False - } - - if cbar_kws is None: - cbar_kws = {} - - cbar_kws = dict(cbar_kws_default, **cbar_kws) - - # select only the data that is needed - df = data[[x, y]].copy(deep=True) - df = df.set_index(x) - - if freq == "day": - ax = _yearplot( - df, x, y, - agg=agg, height=height, aspect=aspect, - vmin=vmin, vmax=vmax, linewidths=linewidths, linecolor=linecolor, - cbar=cbar, cbar_kws=cbar_kws, units=units, cmap=cmap - ) - elif freq == "hour": - ax = _monthplot( - df, x, y, - agg=agg, height=height, aspect=aspect, - vmin=vmin, vmax=vmax, linewidths=linewidths, linecolor=linecolor, - cbar=cbar, cbar_kws=cbar_kws, units=units, cmap=cmap - ) - - ax.set_aspect("equal") - - # remove the spines - for spine in ("top", "bottom", "right", "left"): - ax.spines[spine].set_visible(False) - - if title: - ax.set_title(title) - - if xlabel: - ax.set_xlabel(xlabel) - - if ylabel: - ax.set_ylabel(ylabel) - - return ax \ No newline at end of file diff --git a/atmospy/rcmod.py b/atmospy/rcmod.py index aaab282..048603b 100644 --- a/atmospy/rcmod.py +++ b/atmospy/rcmod.py @@ -3,28 +3,37 @@ __all__ = ["set_theme"] def set_theme(context="notebook", style='ticks', palette='colorblind', - font='sans-serif', font_scale=1, color_codes=True, rc=None): - """_summary_ + font='sans-serif', font_scale=1., color_codes=True, rc=None): + """Change the look and feel of your plots with one simple function. + + This is a simple pass-through function to the Seaborn function of the + same name, but with different default parameters. For complete information + and a better description that I can provide, please see the Seaborn docs + `here `_. This mostly passes down to the seaborn function of the same name, but with a few opinions mixed in. Parameters ---------- - context : str, optional - _description_, by default "notebook" - style : str, optional - _description_, by default 'white' - palette : str, optional - _description_, by default 'colorblind' - font : str, optional - _description_, by default 'sans-serif' - font_scale : int, optional - _description_, by default 1 + context : string or dict, optional + Set the scaling parameter for different environments, by default "notebook" + style : string or dict, optional + Set the axes style parameters, by default 'white' + palette : string or sequence, optional + Set the color palette, by default 'colorblind' + font : string, optional + Set the font family, by default 'sans-serif'. See the + matplotlib font manager for more information. + font_scale : float, optional + Independently scale the font size, by default 1 color_codes : bool, optional - _description_, by default True - rc : _type_, optional - _description_, by default None + If `True`, remap the shorthand color codes assuming you are + using a seaborn palette, by default True + rc : dict or None, optional + Pass through a dictionary of rc parameter mappings to override + the defaults, by default None + """ default_rcparams = { "mathtext.default": "regular" diff --git a/atmospy/relational.py b/atmospy/relational.py index 747c441..4849545 100644 --- a/atmospy/relational.py +++ b/atmospy/relational.py @@ -13,27 +13,58 @@ __all__ = ["regplot", ] def regplot(data, x, y, fit_reg=True, color=None, marker="o", ylim=None, **kwargs): - """Plot data and a linear regression best fit line between the two variables. + """Plot data and a best-fit line (OLS) between two variables. - This function builds on Seaborn's `jointplot` and most kwargs can be passed - straight through to the `jointplot` for customization. + This figure is intended to convey the relationship between two variables. Often, + this may be an air sensor and a reference sensor. It can also be two different + variables where you are trying to understand the relationship. This function + is a straight pass-through to Seaborn's `jointplot` with a few additions such + as a unity line and explicitly listing the fit parameters of a linear model + (Ordinary Least Squares). + + Since it is directly passed through to Seaborn's `jointplot`, it is incredibly + customizable and powerful. Please see the Seaborn docs for more details. Parameters ---------- - data : pd.DataFrame - Input data structure. - x : str - Variable that corresponds to the x data in `data`. - y : str - _description_ + data : :class:`pandas.DataFrame` + Tabular data as a pandas DataFrame. This should be a wide-form dataset + where the x and y keys are columns in the DataFrame. + x : key in `data` + Variable that corresponds to the data plotted on the x axis. + y : key in `data`. + Variable that corresponds to the data plotted on the y axis. fit_reg : bool, optional - _description_, by default True - color : _type_, optional - _description_, by default None + If `True`, a linear regression model will be fit to the data + and fit parameters listed in the legend, by default True + color : str, optional + A single color to map to the data; if None, the next + color in the color cycle will be used, by default None marker : str, optional - _description_, by default "o" - ylim : _type_, optional - _description_, by default None + A single marker style to use to plot the data, by default "o" + ylim : tuple of floats, optional + Set the limits of the figure on both axes using the + ylim (the plot is forced to be squared); if left as None, + defaults will be determined from the underlying data, by default None + kwargs : dict or None, optional + Additional keyword arguments are passed directly to the underlying + :class:`seaborn.jointplot` call. + + Returns + ------- + :class:`seaborn.JointGrid` + An object with multiple subplots including the + joint (primary) and marginal (top and right) axes. + + + Examples + -------- + Using defaults, plot the relationship between a reference particle monitor + and an air sensor: + + >>> df = atmospy.load_dataset("air-sensors-pm") + >>> atmospy.regplot(df, x="Reference", y="Sensor A") + """ check_for_numeric_cols(data, [x, y]) @@ -93,7 +124,7 @@ def regplot(data, x, y, fit_reg=True, color=None, marker="o", ylim=None, **kwarg res = linregress(xdata, ydata) # build the label - label = f"y = {res.slope:.2f}" + label = f"y = {res.slope:.2f}x" if res.intercept < 0: label += f" - {res.intercept:.2f}" else: diff --git a/atmospy/rose.py b/atmospy/rose.py index 9f6c155..cb739a7 100644 --- a/atmospy/rose.py +++ b/atmospy/rose.py @@ -14,39 +14,61 @@ def pollutionroseplot(data=None, *, ws=None, wd=None, pollutant=None, faceted=False, segments=12, bins=[0, 10, 100, 1000], suffix="a.u.", calm=0., lw=1, legend=True, palette="flare", title=None, **kwargs): - """_summary_ + """Plot the intensity and directionality of a variable on a traditional wind-rose plot. + This function is a modified version of `Phil Hobson's work `_. - The basis of this function can orginally found here: https://gist.github.com/phobson/41b41bdd157a2bcf6e14 + Traditionally, a wind rose plots wind speed and direction so that you can see from what + direction is the wind coming from and at what velocity. For air quality purposes, we + often wonder whether or not there is directionality to the intensity of a certain + air pollutant. Well, look no further. This plot allows you to easily visualize the + directionality of a pollutant. Parameters ---------- - data : _type_, optional - _description_, by default None - ws : _type_, optional - _description_, by default None - wd : _type_, optional - _description_, by default None - pollutant : _type_, optional - _description_, by default None + data : :class:`pandas.DataFrame` + Tabular data as a pandas DataFrame. + ws : key in `data` + Variable that corresponds to the wind speed data in `data`. + wd : key in `data` + Variable that corresponds to the wind direction data in `data`. + pollutant : key in `data` + Variable that corresponds to the pollutant of interest in `data`. faceted : bool, optional - _description_, by default False + Set to `True` if plotting on a FacetGrid, by default False segments : int, optional - _description_, by default 12 - bins : list, optional - _description_, by default [0, 10, 100, 1000] + The number of bins along the theta axis to group by wind direction + , by default 12 + bins : list or array of floats, optional + An array of floats corresponding to the bin boundaries + for `pollutant`; if the last entry is not inf, it will be + automatically added, by default [0, 10, 100, 1000] suffix : str, optional - _description_, by default "a.u." - calm : _type_, optional - _description_, by default 0. + The suffix (or units) to use on the labels for `pollutant`, by default "a.u." + calm : float, optional + Set the definition of calm conditions; data under calm winds + will not be used to compute the statistics and + will be shown on the plot as blank in the center, by default 0. lw : int, optional - _description_, by default 1 + Set the line width, by default 1 legend : bool, optional - _description_, by default True + If `True` a legend will be added to the figure, by default True palette : str, optional - _description_, by default "flare" - title : _type_, optional - _description_, by default None + Select the color palette to use, by default "flare" + title : str, optional + Set the figure title, by default None + + Returns + ------- + :class:`matplotlib.axes._axes.Axes` + + Examples + -------- + Using defaults, plot the pollution rose for PM2.5: + + >>> df = atmospy.load_dataset("air-sensors-met") + >>> atmospy.pollutionroseplot(data=df, ws="ws", wd="wd", pollutant="pm25") + """ check_for_numeric_cols(data, [ws, wd, pollutant]) diff --git a/atmospy/trends.py b/atmospy/trends.py index db70559..587d149 100644 --- a/atmospy/trends.py +++ b/atmospy/trends.py @@ -3,43 +3,348 @@ import seaborn as sns import pandas as pd import numpy as np -from .utils import ( - remove_na, -) +import math from .utils import ( check_for_numeric_cols, check_for_timestamp_col ) -__all__ = ["dielplot", ] +__all__ = ["dielplot", "calendarplot"] +@mpl.ticker.FuncFormatter +def custom_month_formatter(x, pos): + return str(math.ceil(x)) -def dielplot(data, x, y, - ax=None, ylim=None, xlabel=None, ylabel=None, title=None, - plot_kws=None, **kwargs - ): - """_summary_ +def _yearplot(data, x, y, ax=None, agg="mean", cmap="crest", + height=2, aspect=5, vmin=None, vmax=None, + linecolor="white", linewidths=0, cbar=True, cbar_kws=None, units=""): + """Plot a full year of time series data on a heatmap by month. + """ + if ax is None: + ax = plt.gca() + ax.figure.set_size_inches(height*aspect, height) + + # if more than 1Y of data was provided, limit to 1Y + years = np.unique(data.index.year) + if years.size > 1: + # warn + data = data[data.index.year == years[0]] + + data["Day of Week"] = data.index.weekday + data["Week of Year"] = data.index.isocalendar().week + + # compute pivoted data + pivot = data.pivot_table( + index="Day of Week", + columns="Week of Year", + values=y, + aggfunc=agg + ) + + # adjust the index to ensure we have a properly-sized array + pivot = pivot.reindex( + index=range(0, 7), + columns=range(1, 53) + ) + + # reverse the array along the yaxis so that Monday ends up at the top of the fig + pivot = pivot[::-1] + + # set the min and max of the colorbar + if vmin is None: + vmin = np.nanmin(pivot.values) + + if vmax is None: + vmax = np.nanmax(pivot.values) + + # plot the heatmap + im = ax.pcolormesh( + pivot, + vmin=vmin, vmax=vmax, cmap=cmap, + linewidth=linewidths, edgecolors=linecolor + ) + + # modify the axes ticks + ax.xaxis.set_major_locator(mpl.ticker.LinearLocator(14)) + ax.xaxis.set_ticklabels([ + "", + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + "" + ], rotation="horizontal", va="center") + + ax.yaxis.tick_right() + ax.yaxis.set_ticks([0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5]) + ax.yaxis.set_ticklabels([ + "Sun", "Sat", "Fri", "Thu", "Wed", "Tue", "Mon" + ], rotation="horizontal", va="center", ha="left") + ax.yaxis.set_tick_params(right=False) + + # add a big ol' year on the left-hand side + ax.set_ylabel( + f"{years[0]}", + fontsize=28, color="gray", ha="center" + ) + + # add a colorbar if set + if cbar: + cbar_kws["pad"] = cbar_kws.get("pad", 0.05) + + cb = ax.figure.colorbar(im, ax=ax, **cbar_kws) + cb.outline.set_visible(False) + + # adjust the colorbar ticklabels + cb.ax.yaxis.set_major_locator(mpl.ticker.MaxNLocator(4)) + + # modify the tick labels + # TODO: this is currently not working + ticklabels = [x.get_text() for x in cb.ax.get_yticklabels()] + ticklabels[-1] = f"{ticklabels[-1]} {units}" + cb.set_ticks(cb.get_ticks()) + cb.set_ticklabels(ticklabels) + + return ax + + +def _monthplot(data, x, y, ax=None, agg="mean", height=3, aspect=1, + vmin=None, vmax=None, cmap="crest", linewidths=0.1, + linecolor="white", cbar=True, cbar_kws=None, units=None): + """Plot a full month of time series data on a heatmap by hour. + """ + if ax is None: + ax = plt.gca() + ax.figure.set_size_inches(height*aspect, height) + + # if more than 1mo of data was provided, limit to 1mo + months = np.unique(data.index.month) + if months.size > 1: + # TODO: log warning + data = data[data.index.month == months[0]] + + # add pivot columns + data["Day of Month"] = data.index.day + data["Hour of Day"] = data.index.hour + + # compute the pivot data + pivot = data.pivot_table( + index="Hour of Day", + columns="Day of Month", + values=y, + aggfunc=agg + ) + + # get the total number of available days in the month + days_in_month = data.index.days_in_month[0] + + # adjust the index to ensure we have a properly-sized array + pivot = pivot.reindex( + index=range(0, 24), + columns=range(1, days_in_month + 1) + ) + + # reverse the order of the matrix along the y-axis so that Monday is at the top + pivot = pivot[::-1] + + # set the min and max values for the colorbar + if vmin is None: + vmin = np.nanmin(pivot.values) + + if vmax is None: + vmax = np.nanmax(pivot.values) + + # plot the heatmap + im = ax.pcolormesh( + pivot, + cmap=cmap, vmin=vmin, vmax=vmax, + linewidth=linewidths, edgecolors=linecolor + ) + + # add a colorbar if set + if cbar: + cb = ax.figure.colorbar(im, ax=ax, **cbar_kws) + cb.outline.set_visible(False) + + # adjust the tick labels + ticklabels = [x.get_text() for x in cb.ax.get_yticklabels()] + ticklabels[-1] = f"{ticklabels[-1]} {units}" + cb.set_ticks(cb.get_ticks()) + cb.set_ticklabels(ticklabels) + + # adjust the axes labels + ax.xaxis.set_major_locator(mpl.ticker.FixedLocator([x - 0.5 for x in list(range(1, days_in_month, 4))])) + ax.xaxis.set_major_formatter(custom_month_formatter) + ax.set_yticks([0, 6, 12, 18, 24]) + ax.set_yticklabels([ + "12 AM", "6 PM", "12 PM", "6 AM", "12 AM" + ]) + + return ax + + +def calendarplot(data, x, y, freq="day", agg="mean", vmin=None, vmax=None, cmap="crest", + ax=None, linecolor="white", linewidths=0, cbar=True, cbar_kws=None, + xlabel=None, ylabel=None, title=None, units="", height=2, aspect=5.0): + """Visualize data as a heatmap on a monthly or annual basis. + + Calendar plots can be a useful way to visualize trends in data over longer periods + of time. This function is quite generic and allows you to visualize data either by + month (where the x-axis is day of month and y-axis is hour of day) or year (where + x-axis is the week of the year and y-axis is the day of the week). Configure the plot + to aggregrate the data any way you choose (e.g., sum, mean, max). + + Currently, you can only plot a single month or single year at a time depending on + configuration. To facet these, please set up a Seaborn FacetGrid and call the + calendarplot separately. + + Parameters + ---------- + data : :class:`pandas.DataFrame` + Tabular data as a pandas DataFrame. + x : key in `data` + Variable that corresponds to the timestamp column in `data`. + y : key in `data` + Variable that corresponds to the variable you would like to group and plot. + freq : str, optional + The frequency by which to average (one of [`hour`, `day`]), by default "day" + agg : str, optional + The function to aggregate by, by default "mean" + vmin : float, optional + The minimum value to color by, by default None + vmax : float, optional + The maximum value to color by, by default None + cmap : str, optional + The name of the colormap, by default "crest" + ax : axes, optional + A matplotlib axes object, by default None + linecolor : str, optional + The color of the inner lines, by default "white" + linewidths : int, optional + The width of the inner lines, by default 0 + cbar : bool, optional + If true, add a colorbar, by default True + cbar_kws : dict, optional + A dictionary of kwargs to send along to the colorbar, by default None + xlabel : str, optional + The x-axis label, by default None + ylabel : str, optional + The y-axis label, by default None + title : str, optional + The figure title, by default None + units : str, optional + The units of the plotted item for labeling purposes only, by default "" + height : int, optional + The figure height in inches, by default 2 + aspect : float, optional + The aspect ratio of the figure, by default 5.0 + + Returns + ------- + :class:`matplotlib.axes._axes.Axes` + + Examples + -------- + + Plot a simple heatmap for the entire year. + + >>> df = atmospy.load_dataset("us-bc") + >>> atmospy.calendarplot(df, x="Timestamp GMT", y="Sample Measurement") + + """ + check_for_timestamp_col(data, x) + check_for_numeric_cols(data, [y]) + + if freq not in ("hour", "day"): + raise ValueError("Invalid argument for `freq`") + + cbar_kws_default = { + "shrink": 0.67, + "drawedges": False + } + + if cbar_kws is None: + cbar_kws = {} + + cbar_kws = dict(cbar_kws_default, **cbar_kws) + + # select only the data that is needed + df = data[[x, y]].copy(deep=True) + df = df.set_index(x) + + if freq == "day": + ax = _yearplot( + df, x, y, + agg=agg, height=height, aspect=aspect, + vmin=vmin, vmax=vmax, linewidths=linewidths, linecolor=linecolor, + cbar=cbar, cbar_kws=cbar_kws, units=units, cmap=cmap + ) + elif freq == "hour": + ax = _monthplot( + df, x, y, + agg=agg, height=height, aspect=aspect, + vmin=vmin, vmax=vmax, linewidths=linewidths, linecolor=linecolor, + cbar=cbar, cbar_kws=cbar_kws, units=units, cmap=cmap + ) + + ax.set_aspect("equal") + + # remove the spines + for spine in ("top", "bottom", "right", "left"): + ax.spines[spine].set_visible(False) + + if title: + ax.set_title(title) + + if xlabel: + ax.set_xlabel(xlabel) + + if ylabel: + ax.set_ylabel(ylabel) + + return ax + + +def dielplot(data, x, y, ax=None, ylim=None, xlabel=None, + ylabel=None, title=None, plot_kws=None, **kwargs): + """Plot the diel (e.g., diurnal) trend for a pollutant. + + Diel plots can be incredibly useful for understanding daily + patterns of air pollutants. Parameters ---------- - data : _type_ - _description_ - x : _type_ - _description_ - y : _type_ - _description_ - ax : _type_, optional - _description_, by default None - ylim : _type_, optional - _description_, by default None - xlabel : _type_, optional - _description_, by default None - ylabel : _type_, optional - _description_, by default None - title : _type_, optional - _description_, by default None - plot_kws : _type_, optional - _description_, by default None + data : :class:`pandas.DataFrame` + Tabular data as a pandas DataFrame. + x : key in `data` + Variable that corresponds to the timestamp in `data`. + y : key in `data` + Variable that corresponds to the pollutant of interest. + ax : :class:`matplotlib.axes._axes.Axes`, optional + An axis to plot on; if not defined, one will be created, by default None + ylim : tuple of floats, optional + A tuple describing (ymin, ymax), by default None + xlabel : str, optional + The label for the x-axis, by default None + ylabel : str, optional + The label for the y-axis, by default None + title : str, optional + The title for the plot, by default None + plot_kws : dict or None, optional + Additional keyword arguments are passed directly to the underlying plot call + , by default None + + Returns + ------- + :class:`matplotlib.axes._axes.Axes` + + + Examples + -------- + + Plot a simple heatmap for the entire year. + + >>> df = atmospy.load_dataset("us-bc") + >>> atmospy.dielplot(df, x="Timestamp GMT", y="Sample Measurement") + """ default_plot_kws = { "lw": 3, diff --git a/atmospy/utils.py b/atmospy/utils.py index 6efb7e6..c7b2a68 100644 --- a/atmospy/utils.py +++ b/atmospy/utils.py @@ -14,7 +14,7 @@ DATASET_NAMES_URL = f"{DATASET_SOURCE}/dataset_names.txt" def get_dataset_names(): - """List the avaiable sample datasets. + """List the available example datasets. Requires an internet connection. """ @@ -47,22 +47,33 @@ def get_data_home(data_home=None): return data_home def load_dataset(name, cache=True, data_home=None, **kwargs): - """Load an example dataset from the online repository. + """Load an example dataset from the online repository (requires internet). - TODO: Add proper docs. + This function provides quick access to a number of example datasets that + can be used to either explore the plotting functionality of atmospy or + to report issues without needing to upload your own data. + + This function also handles some basic data pre-processing to ensure they + are ready-to-go. Parameters ---------- - name : _type_ - _description_ + name : str + The name of the dataset. Dataset names can be found on + https://github.com/dhhagan/atmospy-data or by running + the `get_dataset_names` function. cache : bool, optional - _description_, by default True - data_home : _type_, optional - _description_, by default None + If `True`, the dataset will be loaded from local cache if + available and it will save to local cache if it needs to + be downloaded, by default True + data_home : str, optional + The directory to store the cached data; if not set, + it will be determined for your operating system using the + `get_data_home` function, by default None Returns ------- - df : :classL`pandas.DataFrame` + df : :class:`pandas.DataFrame` Tabular data. """ diff --git a/examples/anscombes_quartet.py b/examples/anscombes_quartet.py new file mode 100644 index 0000000..afbd181 --- /dev/null +++ b/examples/anscombes_quartet.py @@ -0,0 +1,18 @@ +""" +Anscombe's quartet +================== + +_thumb: .4, .4 +""" +import seaborn as sns +sns.set_theme(style="ticks") + +# Load the example dataset for Anscombe's quartet +df = sns.load_dataset("anscombe") + +# Show the results of a linear regression within each dataset +sns.lmplot( + data=df, x="x", y="y", col="dataset", hue="dataset", + col_wrap=2, palette="muted", ci=None, + height=4, scatter_kws={"s": 50, "alpha": 1} +) \ No newline at end of file diff --git a/examples/dielplot.py b/examples/dielplot.py new file mode 100644 index 0000000..da3f5e3 --- /dev/null +++ b/examples/dielplot.py @@ -0,0 +1,30 @@ +""" +Diurnal Ozone +============= + +_thumb: .8, .8 +""" +import atmospy +import pandas as pd +atmospy.set_theme() + +# Load the example dataset +df = atmospy.load_dataset("us-ozone") + +# Select a single location +single_site_ozone = df[ + df["Local Site Name"] == df["Local Site Name"].unique()[0] +] + +# Adjust the timestamp so that it is in local time +single_site_ozone["Timestamp Local"] = single_site_ozone["Timestamp GMT"].apply( + lambda ts: ts + pd.Timedelta(hours=-7) +) + +# Plot the diel trend +atmospy.dielplot( + single_site_ozone, + y="Sample Measurement", x="Timestamp Local", + ylabel="$O_3 \; [ppm]$", + plot_kws={"c": "g"} +) \ No newline at end of file diff --git a/examples/pollution_rose.py b/examples/pollution_rose.py new file mode 100644 index 0000000..7cc6cc5 --- /dev/null +++ b/examples/pollution_rose.py @@ -0,0 +1,18 @@ +""" +Pollution Rose +============== + +_thumb: .8, .8 +""" +import atmospy +atmospy.set_theme() + +# Load the example dataset +df = atmospy.load_dataset("air-sensors-met") + +# Plot a pollution rose example for PM2.5 +atmospy.pollutionroseplot( + data=df, wd="wd", ws="ws", pollutant="pm25", + suffix="$µgm^{-3}$", segments=30, calm=0.1, + bins=[0, 8, 15, 25, 35, 100] +) \ No newline at end of file diff --git a/examples/regression.py b/examples/regression.py new file mode 100644 index 0000000..008c10d --- /dev/null +++ b/examples/regression.py @@ -0,0 +1,19 @@ +""" +Regression Plot +=============== + +_thumb: .4, .4 +""" +import atmospy +atmospy.set_theme() + +# Load the example dataset +df = atmospy.load_dataset("air-sensors-pm") + +# Plot a pollution rose example for PM2.5 +atmospy.regplot( + df, x="Reference", y="Sensor A", + ylim=(0, 60), + color="g", + # title="Performance of Sensor A vs US EPA FEM Reference" +) \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 90e93a8..3ad915a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,17 @@ # This file is automatically @generated by Poetry and should not be changed by hand. +[[package]] +name = "alabaster" +version = "0.7.13" +description = "A configurable sidebar-enabled Sphinx theme" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, + {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, +] + [[package]] name = "anyio" version = "4.3.0" @@ -579,64 +591,64 @@ test-no-images = ["pytest", "pytest-cov", "wurlitzer"] [[package]] name = "coverage" -version = "7.4.4" +version = "7.5.0" description = "Code coverage measurement for Python" category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0be5efd5127542ef31f165de269f77560d6cdef525fffa446de6f7e9186cfb2"}, - {file = "coverage-7.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ccd341521be3d1b3daeb41960ae94a5e87abe2f46f17224ba5d6f2b8398016cf"}, - {file = "coverage-7.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fa497a8ab37784fbb20ab699c246053ac294d13fc7eb40ec007a5043ec91f8"}, - {file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b1a93009cb80730c9bca5d6d4665494b725b6e8e157c1cb7f2db5b4b122ea562"}, - {file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:690db6517f09336559dc0b5f55342df62370a48f5469fabf502db2c6d1cffcd2"}, - {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:09c3255458533cb76ef55da8cc49ffab9e33f083739c8bd4f58e79fecfe288f7"}, - {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ce1415194b4a6bd0cdcc3a1dfbf58b63f910dcb7330fe15bdff542c56949f87"}, - {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b91cbc4b195444e7e258ba27ac33769c41b94967919f10037e6355e998af255c"}, - {file = "coverage-7.4.4-cp310-cp310-win32.whl", hash = "sha256:598825b51b81c808cb6f078dcb972f96af96b078faa47af7dfcdf282835baa8d"}, - {file = "coverage-7.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:09ef9199ed6653989ebbcaacc9b62b514bb63ea2f90256e71fea3ed74bd8ff6f"}, - {file = "coverage-7.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0f9f50e7ef2a71e2fae92774c99170eb8304e3fdf9c8c3c7ae9bab3e7229c5cf"}, - {file = "coverage-7.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:623512f8ba53c422fcfb2ce68362c97945095b864cda94a92edbaf5994201083"}, - {file = "coverage-7.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0513b9508b93da4e1716744ef6ebc507aff016ba115ffe8ecff744d1322a7b63"}, - {file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40209e141059b9370a2657c9b15607815359ab3ef9918f0196b6fccce8d3230f"}, - {file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a2b2b78c78293782fd3767d53e6474582f62443d0504b1554370bde86cc8227"}, - {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:73bfb9c09951125d06ee473bed216e2c3742f530fc5acc1383883125de76d9cd"}, - {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f384c3cc76aeedce208643697fb3e8437604b512255de6d18dae3f27655a384"}, - {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:54eb8d1bf7cacfbf2a3186019bcf01d11c666bd495ed18717162f7eb1e9dd00b"}, - {file = "coverage-7.4.4-cp311-cp311-win32.whl", hash = "sha256:cac99918c7bba15302a2d81f0312c08054a3359eaa1929c7e4b26ebe41e9b286"}, - {file = "coverage-7.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:b14706df8b2de49869ae03a5ccbc211f4041750cd4a66f698df89d44f4bd30ec"}, - {file = "coverage-7.4.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:201bef2eea65e0e9c56343115ba3814e896afe6d36ffd37bab783261db430f76"}, - {file = "coverage-7.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41c9c5f3de16b903b610d09650e5e27adbfa7f500302718c9ffd1c12cf9d6818"}, - {file = "coverage-7.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d898fe162d26929b5960e4e138651f7427048e72c853607f2b200909794ed978"}, - {file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ea79bb50e805cd6ac058dfa3b5c8f6c040cb87fe83de10845857f5535d1db70"}, - {file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce4b94265ca988c3f8e479e741693d143026632672e3ff924f25fab50518dd51"}, - {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:00838a35b882694afda09f85e469c96367daa3f3f2b097d846a7216993d37f4c"}, - {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fdfafb32984684eb03c2d83e1e51f64f0906b11e64482df3c5db936ce3839d48"}, - {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:69eb372f7e2ece89f14751fbcbe470295d73ed41ecd37ca36ed2eb47512a6ab9"}, - {file = "coverage-7.4.4-cp312-cp312-win32.whl", hash = "sha256:137eb07173141545e07403cca94ab625cc1cc6bc4c1e97b6e3846270e7e1fea0"}, - {file = "coverage-7.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:d71eec7d83298f1af3326ce0ff1d0ea83c7cb98f72b577097f9083b20bdaf05e"}, - {file = "coverage-7.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ae728ff3b5401cc320d792866987e7e7e880e6ebd24433b70a33b643bb0384"}, - {file = "coverage-7.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc4f1358cb0c78edef3ed237ef2c86056206bb8d9140e73b6b89fbcfcbdd40e1"}, - {file = "coverage-7.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8130a2aa2acb8788e0b56938786c33c7c98562697bf9f4c7d6e8e5e3a0501e4a"}, - {file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf271892d13e43bc2b51e6908ec9a6a5094a4df1d8af0bfc360088ee6c684409"}, - {file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4cdc86d54b5da0df6d3d3a2f0b710949286094c3a6700c21e9015932b81447e"}, - {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ae71e7ddb7a413dd60052e90528f2f65270aad4b509563af6d03d53e979feafd"}, - {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:38dd60d7bf242c4ed5b38e094baf6401faa114fc09e9e6632374388a404f98e7"}, - {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa5b1c1bfc28384f1f53b69a023d789f72b2e0ab1b3787aae16992a7ca21056c"}, - {file = "coverage-7.4.4-cp38-cp38-win32.whl", hash = "sha256:dfa8fe35a0bb90382837b238fff375de15f0dcdb9ae68ff85f7a63649c98527e"}, - {file = "coverage-7.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:b2991665420a803495e0b90a79233c1433d6ed77ef282e8e152a324bbbc5e0c8"}, - {file = "coverage-7.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b799445b9f7ee8bf299cfaed6f5b226c0037b74886a4e11515e569b36fe310d"}, - {file = "coverage-7.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b4d33f418f46362995f1e9d4f3a35a1b6322cb959c31d88ae56b0298e1c22357"}, - {file = "coverage-7.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aadacf9a2f407a4688d700e4ebab33a7e2e408f2ca04dbf4aef17585389eff3e"}, - {file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c95949560050d04d46b919301826525597f07b33beba6187d04fa64d47ac82e"}, - {file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff7687ca3d7028d8a5f0ebae95a6e4827c5616b31a4ee1192bdfde697db110d4"}, - {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5fc1de20b2d4a061b3df27ab9b7c7111e9a710f10dc2b84d33a4ab25065994ec"}, - {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c74880fc64d4958159fbd537a091d2a585448a8f8508bf248d72112723974cbd"}, - {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:742a76a12aa45b44d236815d282b03cfb1de3b4323f3e4ec933acfae08e54ade"}, - {file = "coverage-7.4.4-cp39-cp39-win32.whl", hash = "sha256:d89d7b2974cae412400e88f35d86af72208e1ede1a541954af5d944a8ba46c57"}, - {file = "coverage-7.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:9ca28a302acb19b6af89e90f33ee3e1906961f94b54ea37de6737b7ca9d8827c"}, - {file = "coverage-7.4.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:b2c5edc4ac10a7ef6605a966c58929ec6c1bd0917fb8c15cb3363f65aa40e677"}, - {file = "coverage-7.4.4.tar.gz", hash = "sha256:c901df83d097649e257e803be22592aedfd5182f07b3cc87d640bbb9afd50f49"}, + {file = "coverage-7.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:432949a32c3e3f820af808db1833d6d1631664d53dd3ce487aa25d574e18ad1c"}, + {file = "coverage-7.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2bd7065249703cbeb6d4ce679c734bef0ee69baa7bff9724361ada04a15b7e3b"}, + {file = "coverage-7.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbfe6389c5522b99768a93d89aca52ef92310a96b99782973b9d11e80511f932"}, + {file = "coverage-7.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39793731182c4be939b4be0cdecde074b833f6171313cf53481f869937129ed3"}, + {file = "coverage-7.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85a5dbe1ba1bf38d6c63b6d2c42132d45cbee6d9f0c51b52c59aa4afba057517"}, + {file = "coverage-7.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:357754dcdfd811462a725e7501a9b4556388e8ecf66e79df6f4b988fa3d0b39a"}, + {file = "coverage-7.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a81eb64feded34f40c8986869a2f764f0fe2db58c0530d3a4afbcde50f314880"}, + {file = "coverage-7.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:51431d0abbed3a868e967f8257c5faf283d41ec882f58413cf295a389bb22e58"}, + {file = "coverage-7.5.0-cp310-cp310-win32.whl", hash = "sha256:f609ebcb0242d84b7adeee2b06c11a2ddaec5464d21888b2c8255f5fd6a98ae4"}, + {file = "coverage-7.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:6782cd6216fab5a83216cc39f13ebe30adfac2fa72688c5a4d8d180cd52e8f6a"}, + {file = "coverage-7.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e768d870801f68c74c2b669fc909839660180c366501d4cc4b87efd6b0eee375"}, + {file = "coverage-7.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:84921b10aeb2dd453247fd10de22907984eaf80901b578a5cf0bb1e279a587cb"}, + {file = "coverage-7.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:710c62b6e35a9a766b99b15cdc56d5aeda0914edae8bb467e9c355f75d14ee95"}, + {file = "coverage-7.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c379cdd3efc0658e652a14112d51a7668f6bfca7445c5a10dee7eabecabba19d"}, + {file = "coverage-7.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fea9d3ca80bcf17edb2c08a4704259dadac196fe5e9274067e7a20511fad1743"}, + {file = "coverage-7.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:41327143c5b1d715f5f98a397608f90ab9ebba606ae4e6f3389c2145410c52b1"}, + {file = "coverage-7.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:565b2e82d0968c977e0b0f7cbf25fd06d78d4856289abc79694c8edcce6eb2de"}, + {file = "coverage-7.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cf3539007202ebfe03923128fedfdd245db5860a36810136ad95a564a2fdffff"}, + {file = "coverage-7.5.0-cp311-cp311-win32.whl", hash = "sha256:bf0b4b8d9caa8d64df838e0f8dcf68fb570c5733b726d1494b87f3da85db3a2d"}, + {file = "coverage-7.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c6384cc90e37cfb60435bbbe0488444e54b98700f727f16f64d8bfda0b84656"}, + {file = "coverage-7.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fed7a72d54bd52f4aeb6c6e951f363903bd7d70bc1cad64dd1f087980d309ab9"}, + {file = "coverage-7.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cbe6581fcff7c8e262eb574244f81f5faaea539e712a058e6707a9d272fe5b64"}, + {file = "coverage-7.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad97ec0da94b378e593ef532b980c15e377df9b9608c7c6da3506953182398af"}, + {file = "coverage-7.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd4bacd62aa2f1a1627352fe68885d6ee694bdaebb16038b6e680f2924a9b2cc"}, + {file = "coverage-7.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:adf032b6c105881f9d77fa17d9eebe0ad1f9bfb2ad25777811f97c5362aa07f2"}, + {file = "coverage-7.5.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ba01d9ba112b55bfa4b24808ec431197bb34f09f66f7cb4fd0258ff9d3711b1"}, + {file = "coverage-7.5.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f0bfe42523893c188e9616d853c47685e1c575fe25f737adf473d0405dcfa7eb"}, + {file = "coverage-7.5.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a9a7ef30a1b02547c1b23fa9a5564f03c9982fc71eb2ecb7f98c96d7a0db5cf2"}, + {file = "coverage-7.5.0-cp312-cp312-win32.whl", hash = "sha256:3c2b77f295edb9fcdb6a250f83e6481c679335ca7e6e4a955e4290350f2d22a4"}, + {file = "coverage-7.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:427e1e627b0963ac02d7c8730ca6d935df10280d230508c0ba059505e9233475"}, + {file = "coverage-7.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9dd88fce54abbdbf4c42fb1fea0e498973d07816f24c0e27a1ecaf91883ce69e"}, + {file = "coverage-7.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a898c11dca8f8c97b467138004a30133974aacd572818c383596f8d5b2eb04a9"}, + {file = "coverage-7.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07dfdd492d645eea1bd70fb1d6febdcf47db178b0d99161d8e4eed18e7f62fe7"}, + {file = "coverage-7.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3d117890b6eee85887b1eed41eefe2e598ad6e40523d9f94c4c4b213258e4a4"}, + {file = "coverage-7.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6afd2e84e7da40fe23ca588379f815fb6dbbb1b757c883935ed11647205111cb"}, + {file = "coverage-7.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a9960dd1891b2ddf13a7fe45339cd59ecee3abb6b8326d8b932d0c5da208104f"}, + {file = "coverage-7.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ced268e82af993d7801a9db2dbc1d2322e786c5dc76295d8e89473d46c6b84d4"}, + {file = "coverage-7.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e7c211f25777746d468d76f11719e64acb40eed410d81c26cefac641975beb88"}, + {file = "coverage-7.5.0-cp38-cp38-win32.whl", hash = "sha256:262fffc1f6c1a26125d5d573e1ec379285a3723363f3bd9c83923c9593a2ac25"}, + {file = "coverage-7.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:eed462b4541c540d63ab57b3fc69e7d8c84d5957668854ee4e408b50e92ce26a"}, + {file = "coverage-7.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d0194d654e360b3e6cc9b774e83235bae6b9b2cac3be09040880bb0e8a88f4a1"}, + {file = "coverage-7.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:33c020d3322662e74bc507fb11488773a96894aa82a622c35a5a28673c0c26f5"}, + {file = "coverage-7.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbdf2cae14a06827bec50bd58e49249452d211d9caddd8bd80e35b53cb04631"}, + {file = "coverage-7.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3235d7c781232e525b0761730e052388a01548bd7f67d0067a253887c6e8df46"}, + {file = "coverage-7.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2de4e546f0ec4b2787d625e0b16b78e99c3e21bc1722b4977c0dddf11ca84e"}, + {file = "coverage-7.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4d0e206259b73af35c4ec1319fd04003776e11e859936658cb6ceffdeba0f5be"}, + {file = "coverage-7.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2055c4fb9a6ff624253d432aa471a37202cd8f458c033d6d989be4499aed037b"}, + {file = "coverage-7.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:075299460948cd12722a970c7eae43d25d37989da682997687b34ae6b87c0ef0"}, + {file = "coverage-7.5.0-cp39-cp39-win32.whl", hash = "sha256:280132aada3bc2f0fac939a5771db4fbb84f245cb35b94fae4994d4c1f80dae7"}, + {file = "coverage-7.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:c58536f6892559e030e6924896a44098bc1290663ea12532c78cef71d0df8493"}, + {file = "coverage-7.5.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:2b57780b51084d5223eee7b59f0d4911c31c16ee5aa12737c7a02455829ff067"}, + {file = "coverage-7.5.0.tar.gz", hash = "sha256:cf62d17310f34084c59c01e027259076479128d11e4661bb6c9acb38c5e19bb8"}, ] [package.dependencies] @@ -717,6 +729,18 @@ files = [ {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, ] +[[package]] +name = "docutils" +version = "0.19" +description = "Docutils -- Python Documentation Utilities" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc"}, + {file = "docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6"}, +] + [[package]] name = "exceptiongroup" version = "1.2.1" @@ -911,6 +935,18 @@ files = [ {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] +[[package]] +name = "imagesize" +version = "1.4.1" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] + [[package]] name = "importlib-metadata" version = "7.1.0" @@ -1423,14 +1459,14 @@ files = [ [[package]] name = "jupyterlab-server" -version = "2.26.0" +version = "2.27.1" description = "A set of server components for JupyterLab and JupyterLab like applications." category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "jupyterlab_server-2.26.0-py3-none-any.whl", hash = "sha256:54622cbd330526a385ee0c1fdccdff3a1e7219bf3e864a335284a1270a1973df"}, - {file = "jupyterlab_server-2.26.0.tar.gz", hash = "sha256:9b3ba91cf2837f7f124fca36d63f3ca80ace2bed4898a63dd47e6598c1ab006f"}, + {file = "jupyterlab_server-2.27.1-py3-none-any.whl", hash = "sha256:f5e26156e5258b24d532c84e7c74cc212e203bff93eb856f81c24c16daeecc75"}, + {file = "jupyterlab_server-2.27.1.tar.gz", hash = "sha256:097b5ac709b676c7284ac9c5e373f11930a561f52cd5a86e4fc7e5a9c8a8631d"}, ] [package.dependencies] @@ -1916,6 +1952,29 @@ files = [ {file = "numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463"}, ] +[[package]] +name = "numpydoc" +version = "1.6.0" +description = "Sphinx extension to support docstrings in Numpy format" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "numpydoc-1.6.0-py3-none-any.whl", hash = "sha256:b6ddaa654a52bdf967763c1e773be41f1c3ae3da39ee0de973f2680048acafaa"}, + {file = "numpydoc-1.6.0.tar.gz", hash = "sha256:ae7a5380f0a06373c3afe16ccd15bd79bc6b07f2704cbc6f1e7ecc94b4f5fc0d"}, +] + +[package.dependencies] +Jinja2 = ">=2.10" +sphinx = ">=5" +tabulate = ">=0.8.10" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} + +[package.extras] +developer = ["pre-commit (>=3.3)", "tomli"] +doc = ["matplotlib (>=3.5)", "numpy (>=1.22)", "pydata-sphinx-theme (>=0.13.3)", "sphinx (>=7)"] +test = ["matplotlib", "pytest", "pytest-cov"] + [[package]] name = "overrides" version = "7.7.0" @@ -2164,19 +2223,20 @@ files = [ [[package]] name = "platformdirs" -version = "4.2.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +version = "4.2.1" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, - {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, + {file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"}, + {file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"}, ] [package.extras] docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] [[package]] name = "pluggy" @@ -2292,6 +2352,31 @@ files = [ {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] +[[package]] +name = "pydata-sphinx-theme" +version = "0.10.0rc2" +description = "Bootstrap-based Sphinx theme from the PyData community" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydata_sphinx_theme-0.10.0rc2-py3-none-any.whl", hash = "sha256:60e823b8ef3573e86d80d30dbe5508bc54087bafefcbdb10eddcee75fb927d01"}, + {file = "pydata_sphinx_theme-0.10.0rc2.tar.gz", hash = "sha256:dfee7026a94199ce54ee7f131968885aeebea2722f280bcd04881644712c2832"}, +] + +[package.dependencies] +beautifulsoup4 = "*" +docutils = "!=0.17.0" +packaging = "*" +pygments = ">=2.7" +sphinx = ">=4.0.2" + +[package.extras] +coverage = ["codecov", "pydata-sphinx-theme[test]", "pytest-cov"] +dev = ["nox", "pre-commit", "pydata-sphinx-theme[coverage]", "pyyaml"] +doc = ["ablog", "jupyter_sphinx", "matplotlib", "myst-nb", "nbsphinx", "numpy", "numpydoc", "pandas", "plotly", "pytest", "pytest-regressions", "rich", "sphinx (>=4,<5)", "sphinx-copybutton", "sphinx-design", "sphinx-sitemap", "sphinx-togglebutton", "sphinxext-rediraffe", "xarray"] +test = ["pydata-sphinx-theme[doc]", "pytest"] + [[package]] name = "pygments" version = "2.17.2" @@ -2641,14 +2726,14 @@ test = ["pytest (>=6,!=7.0.0,!=7.0.1)", "pytest-cov (>=3.0.0)", "pytest-qt"] [[package]] name = "referencing" -version = "0.34.0" +version = "0.35.0" description = "JSON Referencing + Python" category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "referencing-0.34.0-py3-none-any.whl", hash = "sha256:d53ae300ceddd3169f1ffa9caf2cb7b769e92657e4fafb23d34b93679116dfd4"}, - {file = "referencing-0.34.0.tar.gz", hash = "sha256:5773bd84ef41799a5a8ca72dc34590c041eb01bf9aa02632b4a973fb0181a844"}, + {file = "referencing-0.35.0-py3-none-any.whl", hash = "sha256:8080727b30e364e5783152903672df9b6b091c926a146a759080b62ca3126cd6"}, + {file = "referencing-0.35.0.tar.gz", hash = "sha256:191e936b0c696d0af17ad7430a3dc68e88bc11be6514f4757dc890f04ab05889"}, ] [package.dependencies] @@ -2915,6 +3000,18 @@ files = [ {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] +[[package]] +name = "snowballstemmer" +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] + [[package]] name = "soupsieve" version = "2.5" @@ -2927,6 +3024,199 @@ files = [ {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, ] +[[package]] +name = "sphinx" +version = "5.3.0" +description = "Python documentation generator" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "Sphinx-5.3.0.tar.gz", hash = "sha256:51026de0a9ff9fc13c05d74913ad66047e104f56a129ff73e174eb5c3ee794b5"}, + {file = "sphinx-5.3.0-py3-none-any.whl", hash = "sha256:060ca5c9f7ba57a08a1219e547b269fadf125ae25b06b9fa7f66768efb652d6d"}, +] + +[package.dependencies] +alabaster = ">=0.7,<0.8" +babel = ">=2.9" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +docutils = ">=0.14,<0.20" +imagesize = ">=1.3" +importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} +Jinja2 = ">=3.0" +packaging = ">=21.0" +Pygments = ">=2.12" +requests = ">=2.5.0" +snowballstemmer = ">=2.0" +sphinxcontrib-applehelp = "*" +sphinxcontrib-devhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" +sphinxcontrib-jsmath = "*" +sphinxcontrib-qthelp = "*" +sphinxcontrib-serializinghtml = ">=1.1.5" + +[package.extras] +docs = ["sphinxcontrib-websupport"] +lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-bugbear", "flake8-comprehensions", "flake8-simplify", "isort", "mypy (>=0.981)", "sphinx-lint", "types-requests", "types-typed-ast"] +test = ["cython", "html5lib", "pytest (>=4.6)", "typed_ast"] + +[[package]] +name = "sphinx-copybutton" +version = "0.5.2" +description = "Add a copy button to each of your code cells." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd"}, + {file = "sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e"}, +] + +[package.dependencies] +sphinx = ">=1.8" + +[package.extras] +code-style = ["pre-commit (==2.12.1)"] +rtd = ["ipython", "myst-nb", "sphinx", "sphinx-book-theme", "sphinx-examples"] + +[[package]] +name = "sphinx-design" +version = "0.5.0" +description = "A sphinx extension for designing beautiful, view size responsive web components." +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "sphinx_design-0.5.0-py3-none-any.whl", hash = "sha256:1af1267b4cea2eedd6724614f19dcc88fe2e15aff65d06b2f6252cee9c4f4c1e"}, + {file = "sphinx_design-0.5.0.tar.gz", hash = "sha256:e8e513acea6f92d15c6de3b34e954458f245b8e761b45b63950f65373352ab00"}, +] + +[package.dependencies] +sphinx = ">=5,<8" + +[package.extras] +code-style = ["pre-commit (>=3,<4)"] +rtd = ["myst-parser (>=1,<3)"] +testing = ["myst-parser (>=1,<3)", "pytest (>=7.1,<8.0)", "pytest-cov", "pytest-regressions"] +theme-furo = ["furo (>=2023.7.0,<2023.8.0)"] +theme-pydata = ["pydata-sphinx-theme (>=0.13.0,<0.14.0)"] +theme-rtd = ["sphinx-rtd-theme (>=1.0,<2.0)"] +theme-sbt = ["sphinx-book-theme (>=1.0,<2.0)"] + +[[package]] +name = "sphinx-issues" +version = "4.1.0" +description = "A Sphinx extension for linking to your project's issue tracker" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "sphinx_issues-4.1.0-py3-none-any.whl", hash = "sha256:d779dddff441175c9fddb7a4018eca38f9f6cdb1e0a2fe31d93b4f89587c7ba1"}, + {file = "sphinx_issues-4.1.0.tar.gz", hash = "sha256:a67f7ef31d164b420b2f21b6c9b020baeb4a014afd4045f5be314c984e8ee520"}, +] + +[package.dependencies] +sphinx = "*" + +[package.extras] +dev = ["pre-commit (>=3.6,<4.0)", "sphinx-issues[tests]", "tox"] +tests = ["pytest"] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "1.0.4" +description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e"}, + {file = "sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "1.0.2" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, + {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.0.1" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff"}, + {file = "sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["html5lib", "pytest"] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +description = "A sphinx extension which renders display math in HTML via JavaScript" +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] + +[package.extras] +test = ["flake8", "mypy", "pytest"] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "1.0.3" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, + {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "1.1.5" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, + {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + [[package]] name = "stack-data" version = "0.6.3" @@ -2947,6 +3237,21 @@ pure-eval = "*" [package.extras] tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] +[[package]] +name = "tabulate" +version = "0.9.0" +description = "Pretty-print tabular data" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, + {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, +] + +[package.extras] +widechars = ["wcwidth"] + [[package]] name = "terminado" version = "0.18.1" @@ -2971,14 +3276,14 @@ typing = ["mypy (>=1.6,<2.0)", "traitlets (>=5.11.1)"] [[package]] name = "tinycss2" -version = "1.2.1" +version = "1.3.0" description = "A tiny CSS parser" category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "tinycss2-1.2.1-py3-none-any.whl", hash = "sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847"}, - {file = "tinycss2-1.2.1.tar.gz", hash = "sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627"}, + {file = "tinycss2-1.3.0-py3-none-any.whl", hash = "sha256:54a8dbdffb334d536851be0226030e9505965bb2f30f21a4a82c55fb2a80fae7"}, + {file = "tinycss2-1.3.0.tar.gz", hash = "sha256:152f9acabd296a8375fbca5b84c961ff95971fcfc32e79550c8df8e29118c54d"}, ] [package.dependencies] @@ -2986,7 +3291,7 @@ webencodings = ">=0.4" [package.extras] doc = ["sphinx", "sphinx_rtd_theme"] -test = ["flake8", "isort", "pytest"] +test = ["pytest", "ruff"] [[package]] name = "tomli" @@ -3148,18 +3453,18 @@ files = [ [[package]] name = "websocket-client" -version = "1.7.0" +version = "1.8.0" description = "WebSocket client for Python with low level API options" category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "websocket-client-1.7.0.tar.gz", hash = "sha256:10e511ea3a8c744631d3bd77e61eb17ed09304c413ad42cf6ddfa4c7787e8fe6"}, - {file = "websocket_client-1.7.0-py3-none-any.whl", hash = "sha256:f4c3d22fec12a2461427a29957ff07d35098ee2d976d3ba244e688b8b4057588"}, + {file = "websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526"}, + {file = "websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da"}, ] [package.extras] -docs = ["Sphinx (>=6.0)", "sphinx-rtd-theme (>=1.1.0)"] +docs = ["Sphinx (>=6.0)", "myst-parser (>=2.0.0)", "sphinx-rtd-theme (>=1.1.0)"] optional = ["python-socks", "wsaccel"] test = ["websockets"] @@ -3194,4 +3499,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.8" -content-hash = "da15da61808f17c875a5174626955c40275ae16e78dcde66d4e95202a06ff715" +content-hash = "79728af31551668bf01209449bdfb89a551bd04c4b0628b137da600d84ae6266" diff --git a/pyproject.toml b/pyproject.toml index 677db3b..312f07b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,13 @@ pytest = "^8.1.1" pytest-cov = "^5.0.0" ipykernel = "^6.29.4" jupyter = "^1.0.0" +sphinx = "<6.0.0" +sphinx-copybutton = "^0.5.2" +pydata-sphinx-theme = "0.10.0rc2" +nbconvert = "^7.16.3" +numpydoc = "^1.5" +sphinx-issues = "^4.1.0" +sphinx-design = "^0.5.0" [build-system] requires = ["poetry-core>=1.0.0"]