diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8b33947de1..58c0157a6f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -12,6 +12,7 @@ on: env: NB_KERNEL: python MPLBACKEND: Agg + SEABORN_DATA: ${{ github.workspace }}/seaborn-data jobs: build-docs: @@ -34,9 +35,13 @@ jobs: sudo apt-get install pandoc - name: Cache datasets - run: python ci/cache_datasets.py + run: | + git clone https://github.com/mwaskom/seaborn-data.git + ls $SEABORN_DATA - name: Build docs + env: + SPHINXOPTS: -j `nproc` run: | cd doc make -j `nproc` notebooks @@ -49,17 +54,14 @@ jobs: strategy: matrix: python: ["3.7", "3.8", "3.9", "3.10"] - target: [test] install: [full] deps: [latest] include: - python: "3.7" - target: unittests install: full deps: pinned - python: "3.10" - target: unittests install: light deps: latest @@ -78,11 +80,8 @@ jobs: if [[ ${{matrix.deps }} == 'pinned' ]]; then DEPS='-r ci/deps_pinned.txt'; fi pip install .[dev$EXTRAS] $DEPS - - name: Cache datasets - run: python ci/cache_datasets.py - - name: Run tests - run: make ${{ matrix.target }} + run: make test - name: Upload coverage uses: codecov/codecov-action@v2 diff --git a/Makefile b/Makefile index 073785f22b..0d2b7247a1 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,6 @@ export SHELL := /bin/bash test: - pytest -n auto --doctest-modules --cov=seaborn --cov=tests --cov-config=.coveragerc seaborn tests - -unittests: pytest -n auto --cov=seaborn --cov=tests --cov-config=.coveragerc tests lint: diff --git a/README.md b/README.md index fc2d963023..8b40b5f669 100644 --- a/README.md +++ b/README.md @@ -60,11 +60,9 @@ Testing Testing seaborn requires installing additional dependencies; they can be installed with the `dev` extra (e.g., `pip install .[dev]`). -To test the code, run `make test` in the source directory. This will exercise both the unit tests and docstring examples (using [pytest](https://docs.pytest.org/)) and generate a coverage report. +To test the code, run `make test` in the source directory. This will exercise the unit tests (using [pytest](https://docs.pytest.org/)) and generate a coverage report. -The doctests require a network connection (unless all example datasets are cached), but the unit tests can be run offline with `make unittests`. - -Code style is enforced with `flake8` using the settings in the [`setup.cfg`](./setup.cfg) file. Run `make lint` to check. Alternately, you can use `pre-commit` to automatically run lint checks on any files you are committing – just run `pre-commit install` to set it up, and then commit as usual going forward. +Code style is enforced with `flake8` using the settings in the [`setup.cfg`](./setup.cfg) file. Run `make lint` to check. Alternately, you can use `pre-commit` to automatically run lint checks on any files you are committing: just run `pre-commit install` to set it up, and then commit as usual going forward. Development ----------- diff --git a/doc/README.md b/doc/README.md index 05c99c5670..78cfc1ef64 100644 --- a/doc/README.md +++ b/doc/README.md @@ -1,7 +1,7 @@ Building the seaborn docs ========================= -Building the docs requires additional dependencies; they can be installed with `pip install seaborn[docs]`. +Building the docs requires additional dependencies; they can be installed with `pip install seaborn[stats,docs]`. The build process involves conversion of Jupyter notebooks to `rst` files. To facilitate this, you may need to set `NB_KERNEL` environment variable to the name of a kernel on your machine (e.g. `export NB_KERNEL="python3"`). To get a list of available Python kernels, run `jupyter kernelspec list`. diff --git a/doc/_docstrings/FacetGrid.ipynb b/doc/_docstrings/FacetGrid.ipynb index 7fc9b8146d..eeb329f2ce 100644 --- a/doc/_docstrings/FacetGrid.ipynb +++ b/doc/_docstrings/FacetGrid.ipynb @@ -280,9 +280,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -294,7 +294,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/JointGrid.ipynb b/doc/_docstrings/JointGrid.ipynb index 608529cfdf..ef8014aa0a 100644 --- a/doc/_docstrings/JointGrid.ipynb +++ b/doc/_docstrings/JointGrid.ipynb @@ -222,9 +222,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -236,7 +236,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/PairGrid.ipynb b/doc/_docstrings/PairGrid.ipynb index a5c54c8eab..c39af330e9 100644 --- a/doc/_docstrings/PairGrid.ipynb +++ b/doc/_docstrings/PairGrid.ipynb @@ -249,9 +249,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -263,7 +263,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/axes_style.ipynb b/doc/_docstrings/axes_style.ipynb index aedc546911..2dca0e87cf 100644 --- a/doc/_docstrings/axes_style.ipynb +++ b/doc/_docstrings/axes_style.ipynb @@ -80,9 +80,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -94,7 +94,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/barplot.ipynb b/doc/_docstrings/barplot.ipynb index 3a128a39c6..7b1264448d 100644 --- a/doc/_docstrings/barplot.ipynb +++ b/doc/_docstrings/barplot.ipynb @@ -103,9 +103,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -117,7 +117,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/blend_palette.ipynb b/doc/_docstrings/blend_palette.ipynb new file mode 100644 index 0000000000..85f8755a1c --- /dev/null +++ b/doc/_docstrings/blend_palette.ipynb @@ -0,0 +1,103 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "8f97280e-cec8-42b2-a968-4fd4364594f8", + "metadata": { + "tags": [ + "hide" + ] + }, + "outputs": [], + "source": [ + "import seaborn as sns\n", + "sns.set_theme()\n", + "sns.palettes._patch_colormap_display()" + ] + }, + { + "cell_type": "raw", + "id": "972edede-df1a-4010-9674-00b864d020e2", + "metadata": {}, + "source": [ + "Pass a list of two colors to interpolate between them:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e6ae2547-1042-4ac0-84ea-6f37a0229871", + "metadata": {}, + "outputs": [], + "source": [ + "sns.blend_palette([\"b\", \"r\"])" + ] + }, + { + "cell_type": "raw", + "id": "1d983eac-2dd5-4746-b27f-4dfa19b5e091", + "metadata": {}, + "source": [ + "The color list can be arbitrarily long, and any color format can be used:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "846b78fd-30ce-4507-93f4-4274122c1987", + "metadata": {}, + "outputs": [], + "source": [ + "sns.blend_palette([\"#45a872\", \".8\", \"xkcd:golden\"])" + ] + }, + { + "cell_type": "raw", + "id": "318fef32-1f83-44d9-9ff9-21fa0231b7c6", + "metadata": {}, + "source": [ + "Return a continuous colormap instead of a discrete palette:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f0a05bc3-c60b-47a1-b276-d2e28a4a8226", + "metadata": {}, + "outputs": [], + "source": [ + "sns.blend_palette([\"#bdc\", \"#7b9\", \"#47a\"], as_cmap=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0473a402-0ec2-4877-81d2-ed6c57aefc77", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "py310", + "language": "python", + "name": "py310" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/_docstrings/boxenplot.ipynb b/doc/_docstrings/boxenplot.ipynb index 00176eb646..1b2f863b63 100644 --- a/doc/_docstrings/boxenplot.ipynb +++ b/doc/_docstrings/boxenplot.ipynb @@ -108,9 +108,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -122,7 +122,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/boxplot.ipynb b/doc/_docstrings/boxplot.ipynb index 1c6f5daf91..098cda1ac4 100644 --- a/doc/_docstrings/boxplot.ipynb +++ b/doc/_docstrings/boxplot.ipynb @@ -151,9 +151,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -165,7 +165,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/catplot.ipynb b/doc/_docstrings/catplot.ipynb index d4f01b6a67..ba956e6ab5 100644 --- a/doc/_docstrings/catplot.ipynb +++ b/doc/_docstrings/catplot.ipynb @@ -168,9 +168,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -182,7 +182,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/clustermap.ipynb b/doc/_docstrings/clustermap.ipynb new file mode 100644 index 0000000000..6937145881 --- /dev/null +++ b/doc/_docstrings/clustermap.ipynb @@ -0,0 +1,184 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "ffc1e1d9-fa74-4121-aa87-e1a8665e4c2b", + "metadata": { + "tags": [ + "hide" + ] + }, + "outputs": [], + "source": [ + "import seaborn as sns\n", + "sns.set_theme()" + ] + }, + { + "cell_type": "raw", + "id": "41b4f602-32af-44f8-bf1a-0f1695c9abbb", + "metadata": {}, + "source": [ + "Plot a heatmap with row and column clustering:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c715bd8f-cf5d-4caa-9244-336b3d0248a8", + "metadata": {}, + "outputs": [], + "source": [ + "iris = sns.load_dataset(\"iris\")\n", + "species = iris.pop(\"species\")\n", + "sns.clustermap(iris)" + ] + }, + { + "cell_type": "raw", + "id": "1cc3134c-579a-442a-97d8-a878651ce90a", + "metadata": {}, + "source": [ + "Change the size and layout of the figure:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fd33cf4b-9589-4b9a-a246-0b95bad28c51", + "metadata": {}, + "outputs": [], + "source": [ + "sns.clustermap(\n", + " iris,\n", + " figsize=(7, 5),\n", + " row_cluster=False,\n", + " dendrogram_ratio=(.1, .2),\n", + " cbar_pos=(0, .2, .03, .4)\n", + ")" + ] + }, + { + "cell_type": "raw", + "id": "c5d3408d-f5d6-4045-9d61-15573a981587", + "metadata": {}, + "source": [ + "Add colored labels to identify observations:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "79d3fe52-6146-4f33-a39a-1d4a47243ea5", + "metadata": {}, + "outputs": [], + "source": [ + "lut = dict(zip(species.unique(), \"rbg\"))\n", + "row_colors = species.map(lut)\n", + "sns.clustermap(iris, row_colors=row_colors)" + ] + }, + { + "cell_type": "raw", + "id": "f2f944e2-36cd-4653-86b4-6d2affec13d6", + "metadata": {}, + "source": [ + "Use a different colormap and adjust the limits of the color range:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6137c7ad-db92-47b8-9d00-3228c4e1f7df", + "metadata": {}, + "outputs": [], + "source": [ + "sns.clustermap(iris, cmap=\"mako\", vmin=0, vmax=10)" + ] + }, + { + "cell_type": "raw", + "id": "93f96d1c-9d04-464f-93c9-4319caa8504a", + "metadata": {}, + "source": [ + "Use differente clustering parameters:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f9e76bde-a222-4eca-971f-54f56ad53281", + "metadata": {}, + "outputs": [], + "source": [ + "sns.clustermap(iris, metric=\"correlation\", method=\"single\")" + ] + }, + { + "cell_type": "raw", + "id": "ea6ed3fd-188d-4244-adac-ec0169c02205", + "metadata": {}, + "source": [ + "Standardize the data within the columns:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e5f744c4-b959-4ed1-b2cf-6046c9214568", + "metadata": {}, + "outputs": [], + "source": [ + "sns.clustermap(iris, standard_scale=1)" + ] + }, + { + "cell_type": "raw", + "id": "7ca72242-4eb0-4f8e-b0c0-d1ef7166b738", + "metadata": {}, + "source": [ + "Normalize the data within rows:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "33815c4c-9bae-4226-bd11-3dfdb7ecab2b", + "metadata": {}, + "outputs": [], + "source": [ + "sns.clustermap(iris, z_score=0, cmap=\"vlag\", center=0)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0f37d57a-b049-4665-9c24-4d5fbbca00ba", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "py310", + "language": "python", + "name": "py310" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/_docstrings/color_palette.ipynb b/doc/_docstrings/color_palette.ipynb index 2dcfc8f93f..a0408b429a 100644 --- a/doc/_docstrings/color_palette.ipynb +++ b/doc/_docstrings/color_palette.ipynb @@ -10,47 +10,9 @@ }, "outputs": [], "source": [ - "import seaborn as sns; sns.set_theme()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "hide" - ] - }, - "outputs": [], - "source": [ - "# Add colormap display methods to matplotlib colormaps.\n", - "# These are forthcoming in matplotlib 3.4, but, the matplotlib display\n", - "# method includes the colormap name, which is redundant.\n", - "def _repr_png_(self):\n", - " \"\"\"Generate a PNG representation of the Colormap.\"\"\"\n", - " import io\n", - " from PIL import Image\n", - " import numpy as np\n", - " IMAGE_SIZE = (400, 50)\n", - " X = np.tile(np.linspace(0, 1, IMAGE_SIZE[0]), (IMAGE_SIZE[1], 1))\n", - " pixels = self(X, bytes=True)\n", - " png_bytes = io.BytesIO()\n", - " Image.fromarray(pixels).save(png_bytes, format='png')\n", - " return png_bytes.getvalue()\n", - " \n", - "def _repr_html_(self):\n", - " \"\"\"Generate an HTML representation of the Colormap.\"\"\"\n", - " import base64\n", - " png_bytes = self._repr_png_()\n", - " png_base64 = base64.b64encode(png_bytes).decode('ascii')\n", - " return ('')\n", - " \n", - "import matplotlib as mpl\n", - "mpl.colors.Colormap._repr_png_ = _repr_png_\n", - "mpl.colors.Colormap._repr_html_ = _repr_html_" + "import seaborn as sns\n", + "sns.set_theme()\n", + "sns.palettes._patch_colormap_display()" ] }, { @@ -122,7 +84,39 @@ "cell_type": "raw", "metadata": {}, "source": [ - "Return one of the perceptually-uniform colormaps included in seaborn:" + "Return a diverging Color Brewer palette as a continuous colormap:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sns.color_palette(\"Spectral\", as_cmap=True)" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "Return one of the perceptually-uniform palettes included in seaborn as a discrete palette:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sns.color_palette(\"flare\")" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "Return one of the perceptually-uniform palettes included in seaborn as a continuous colormap:" ] }, { @@ -154,7 +148,7 @@ "cell_type": "raw", "metadata": {}, "source": [ - "Return a light-themed sequential colormap to a seed color:" + "Return a light sequential gradient:" ] }, { @@ -166,6 +160,91 @@ "sns.color_palette(\"light:#5A9\", as_cmap=True)" ] }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "Return a reversed dark sequential gradient:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sns.color_palette(\"dark:#5A9_r\", as_cmap=True)" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "Return a blend gradient between two endpoints:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sns.color_palette(\"blend:#7AB,#EDA\", as_cmap=True)" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "Use as a context manager to change the default qualitative color palette:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "hide" + ] + }, + "outputs": [], + "source": [ + "x, y = list(range(10)), [0] * 10\n", + "hue = list(map(str, x))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "with sns.color_palette(\"Set3\"):\n", + " sns.relplot(x=x, y=y, hue=hue, s=500, legend=False, height=1.3, aspect=4)\n", + "\n", + "sns.relplot(x=x, y=y, hue=hue, s=500, legend=False, height=1.3, aspect=4)" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "See the underlying color values as hex codes:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "show-output" + ] + }, + "outputs": [], + "source": [ + "print(sns.color_palette(\"pastel6\").as_hex())" + ] + }, { "cell_type": "code", "execution_count": null, @@ -176,9 +255,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -190,7 +269,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/countplot.ipynb b/doc/_docstrings/countplot.ipynb index c0cdf8abdb..6205ac15c1 100644 --- a/doc/_docstrings/countplot.ipynb +++ b/doc/_docstrings/countplot.ipynb @@ -77,9 +77,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -91,7 +91,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/cubehelix_palette.ipynb b/doc/_docstrings/cubehelix_palette.ipynb new file mode 100644 index 0000000000..a48aab5aed --- /dev/null +++ b/doc/_docstrings/cubehelix_palette.ipynb @@ -0,0 +1,229 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "60aebc68-2c7c-4af5-a159-8421e1f94ba6", + "metadata": { + "tags": [ + "hide" + ] + }, + "outputs": [], + "source": [ + "import seaborn as sns\n", + "sns.set_theme()\n", + "sns.palettes._patch_colormap_display()" + ] + }, + { + "cell_type": "raw", + "id": "242b3d42-1f10-4da2-9ef9-af06f7fbd724", + "metadata": {}, + "source": [ + "Return a discrete palette with default parameters:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6526accb-9930-4e39-9f58-1ca2941c1c9d", + "metadata": {}, + "outputs": [], + "source": [ + "sns.cubehelix_palette()" + ] + }, + { + "cell_type": "raw", + "id": "887a40f0-d949-41fa-9a43-0ee246c9a077", + "metadata": {}, + "source": [ + "Increase the number of colors:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "02833290-b1ee-46df-a2a0-8268fba94628", + "metadata": {}, + "outputs": [], + "source": [ + "sns.cubehelix_palette(8)" + ] + }, + { + "cell_type": "raw", + "id": "a9eb86c7-f92e-4422-ae62-a2ef136e7e35", + "metadata": {}, + "source": [ + "Return a continuous colormap rather than a discrete palette:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a460efc2-cf0a-46bf-a12f-12870afce8a5", + "metadata": {}, + "outputs": [], + "source": [ + "sns.cubehelix_palette(as_cmap=True)" + ] + }, + { + "cell_type": "raw", + "id": "5b84aa6c-ad79-45b1-a7d2-44b7ecba5f7d", + "metadata": {}, + "source": [ + "Change the starting point of the helix:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "70ee079a-e760-4d43-8447-648fd236ab15", + "metadata": {}, + "outputs": [], + "source": [ + "sns.cubehelix_palette(start=2)" + ] + }, + { + "cell_type": "raw", + "id": "5e21fa22-9ac3-4354-8694-967f2447b286", + "metadata": {}, + "source": [ + "Change the amount of rotation in the helix:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ddb1b8c7-8933-4317-827f-4f10d2b4cecc", + "metadata": {}, + "outputs": [], + "source": [ + "sns.cubehelix_palette(rot=.2)" + ] + }, + { + "cell_type": "raw", + "id": "fa91aff7-54e7-4754-a13c-b629dfc33e8f", + "metadata": {}, + "source": [ + "Rotate in the reverse direction:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "548a3942-48ae-40d2-abb7-acc2ffd71601", + "metadata": {}, + "outputs": [], + "source": [ + "sns.cubehelix_palette(rot=-.2)" + ] + }, + { + "cell_type": "raw", + "id": "e7188a1b-183f-4b04-93a0-975c27fe408e", + "metadata": {}, + "source": [ + "Apply a nonlinearity to the luminance ramp:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9ced54ff-a396-451e-b17f-2366b56f920b", + "metadata": {}, + "outputs": [], + "source": [ + "sns.cubehelix_palette(gamma=.5)" + ] + }, + { + "cell_type": "raw", + "id": "bc82ce48-2df3-464e-b70e-a1d73d0432c6", + "metadata": {}, + "source": [ + "Increase the saturation of the colors:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a38b91a8-3fdc-4293-a3ea-71b4006cd2a1", + "metadata": {}, + "outputs": [], + "source": [ + "sns.cubehelix_palette(hue=1)" + ] + }, + { + "cell_type": "raw", + "id": "f8d23ba1-013a-489f-94c4-f2080bfdae87", + "metadata": {}, + "source": [ + "Change the luminance at the start and end points:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a4f05a16-18f0-4c14-99a4-57a0734aad02", + "metadata": {}, + "outputs": [], + "source": [ + "sns.cubehelix_palette(dark=.25, light=.75)" + ] + }, + { + "cell_type": "raw", + "id": "0bfcc5d9-05ba-4715-94ac-8d430d9416c2", + "metadata": {}, + "source": [ + "Reverse the direction of the luminance ramp:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "74563491-5448-42c3-86c5-f5d55ce6924c", + "metadata": {}, + "outputs": [], + "source": [ + "sns.cubehelix_palette(reverse=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "94a83211-8b8e-4e60-8365-9600e71ddc5d", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "py310", + "language": "python", + "name": "py310" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/_docstrings/dark_palette.ipynb b/doc/_docstrings/dark_palette.ipynb new file mode 100644 index 0000000000..a4ed7adf43 --- /dev/null +++ b/doc/_docstrings/dark_palette.ipynb @@ -0,0 +1,139 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "5cd1cbb8-ba1a-460b-8e3a-bc285867f1d1", + "metadata": { + "tags": [ + "hide" + ] + }, + "outputs": [], + "source": [ + "import seaborn as sns\n", + "sns.set_theme()\n", + "sns.palettes._patch_colormap_display()" + ] + }, + { + "cell_type": "raw", + "id": "b157eb25-015f-4dd6-9785-83ba19cf4f94", + "metadata": {}, + "source": [ + "Define a sequential ramp from a dark gray to a specified color:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5b655d28-9855-4528-8b8e-a6c50288fd1b", + "metadata": {}, + "outputs": [], + "source": [ + "sns.dark_palette(\"seagreen\")" + ] + }, + { + "cell_type": "raw", + "id": "50053b26-112a-4378-8ef0-9be0fb565ec7", + "metadata": {}, + "source": [ + "Specify the color with a hex code:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "74ae0d17-f65b-4bcf-ae66-d97d46964d5c", + "metadata": {}, + "outputs": [], + "source": [ + "sns.dark_palette(\"#79C\")" + ] + }, + { + "cell_type": "raw", + "id": "eea376a2-fdf5-40e4-a187-3a28af529072", + "metadata": {}, + "source": [ + "Specify the color from the husl system:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "66e451ee-869a-41ea-8dc5-4240b11e7be5", + "metadata": {}, + "outputs": [], + "source": [ + "sns.dark_palette((20, 60, 50), input=\"husl\")" + ] + }, + { + "cell_type": "raw", + "id": "e4f44dcd-cf49-4920-ac05-b4db67870363", + "metadata": {}, + "source": [ + "Increase the number of colors:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "75985f07-de92-4d8b-89d5-caf445b9375e", + "metadata": {}, + "outputs": [], + "source": [ + "sns.dark_palette(\"xkcd:golden\", 8)" + ] + }, + { + "cell_type": "raw", + "id": "34687ae8-fd6d-427a-a639-208f19e61122", + "metadata": {}, + "source": [ + "Return a continuous colormap rather than a discrete palette:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2c342db4-7f97-40f5-934e-9a82201890d1", + "metadata": {}, + "outputs": [], + "source": [ + "sns.dark_palette(\"#b285bc\", as_cmap=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e7ebe64b-25fa-4c52-9ebe-fdcbba0ee51e", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "py310", + "language": "python", + "name": "py310" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/_docstrings/displot.ipynb b/doc/_docstrings/displot.ipynb index 543aa7fb8c..1b2011fec0 100644 --- a/doc/_docstrings/displot.ipynb +++ b/doc/_docstrings/displot.ipynb @@ -217,9 +217,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -231,7 +231,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/diverging_palette.ipynb b/doc/_docstrings/diverging_palette.ipynb new file mode 100644 index 0000000000..c38196be98 --- /dev/null +++ b/doc/_docstrings/diverging_palette.ipynb @@ -0,0 +1,183 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "01295cb6-cc7a-4c6d-94cf-9b0e6cde9fa7", + "metadata": { + "tags": [ + "hide" + ] + }, + "outputs": [], + "source": [ + "import seaborn as sns\n", + "sns.set_theme()\n", + "sns.palettes._patch_colormap_display()" + ] + }, + { + "cell_type": "raw", + "id": "84880848-0805-4c41-999a-50808b397275", + "metadata": {}, + "source": [ + "Generate diverging ramps from blue to red through white:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "643b3e07-8365-46e3-b033-af7a2fdcd158", + "metadata": {}, + "outputs": [], + "source": [ + "sns.diverging_palette(240, 20)" + ] + }, + { + "cell_type": "raw", + "id": "5ae53941-d9d9-4b5a-8abc-173911ebee74", + "metadata": {}, + "source": [ + "Change the center color to be dark:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "41f03771-8fb2-46f6-93c5-5a0e28be625c", + "metadata": {}, + "outputs": [], + "source": [ + "sns.diverging_palette(240, 20, center=\"dark\")" + ] + }, + { + "cell_type": "raw", + "id": "0aeb2402-2cbe-4546-a354-f1f501f762ae", + "metadata": {}, + "source": [ + "Return a continuous colormap rather than a discrete palette:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "64d335a5-f8b2-433f-a83f-5aeff7db583a", + "metadata": {}, + "outputs": [], + "source": [ + "sns.diverging_palette(240, 20, as_cmap=True)" + ] + }, + { + "cell_type": "raw", + "id": "77223a07-8492-4056-a0f7-14e133e3ce2c", + "metadata": {}, + "source": [ + "Increase the amount of separation around the center value:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "82472c1e-4b16-40eb-be1d-480bbd2aa702", + "metadata": {}, + "outputs": [], + "source": [ + "sns.diverging_palette(240, 20, sep=30, as_cmap=True)" + ] + }, + { + "cell_type": "raw", + "id": "966e8594-b458-414c-a7b0-3e804ce407bf", + "metadata": {}, + "source": [ + "Use a magenta-to-green palette instead:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a03f8ede-b424-4e06-beb6-cf63c94bcd9e", + "metadata": {}, + "outputs": [], + "source": [ + "sns.diverging_palette(280, 150)" + ] + }, + { + "cell_type": "raw", + "id": "b3b17689-58e2-4065-9d52-1cf5ebcd4e89", + "metadata": {}, + "source": [ + "Decrease the saturation of the endpoints:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "02aaa009-f257-4fc7-a2de-40fbb1464490", + "metadata": {}, + "outputs": [], + "source": [ + "sns.diverging_palette(280, 150, s=50)" + ] + }, + { + "cell_type": "raw", + "id": "db75ca48-ba72-4ca2-8480-bc72c20a70cc", + "metadata": {}, + "source": [ + "Decrease the lightness of the endpoints:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "89e3bcb1-a17c-4465-830f-46043cb6c322", + "metadata": {}, + "outputs": [], + "source": [ + "sns.diverging_palette(280, 150, l=35)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4e42452a-a485-43e7-bbc3-338db58e4637", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e19f523f-c2f7-489a-ba00-326810e31a67", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "py310", + "language": "python", + "name": "py310" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/_docstrings/ecdfplot.ipynb b/doc/_docstrings/ecdfplot.ipynb index 9eff8bd735..d95e517a1d 100644 --- a/doc/_docstrings/ecdfplot.ipynb +++ b/doc/_docstrings/ecdfplot.ipynb @@ -120,9 +120,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -134,7 +134,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/heatmap.ipynb b/doc/_docstrings/heatmap.ipynb new file mode 100644 index 0000000000..b4563a880e --- /dev/null +++ b/doc/_docstrings/heatmap.ipynb @@ -0,0 +1,213 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "987b9549-532e-4091-a6cf-007d1b23e825", + "metadata": { + "tags": [ + "hide" + ] + }, + "outputs": [], + "source": [ + "import seaborn as sns\n", + "sns.set_theme()" + ] + }, + { + "cell_type": "raw", + "id": "2c78ca60-e232-44f6-956b-b86b472b1c28", + "metadata": {}, + "source": [ + "Pass a :class:`DataFrame` to plot with indices as row/column labels:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fad17798-c2e3-4334-abf0-0d46153971fa", + "metadata": {}, + "outputs": [], + "source": [ + "glue = sns.load_dataset(\"glue\").pivot(\"Model\", \"Task\", \"Score\")\n", + "sns.heatmap(glue)" + ] + }, + { + "cell_type": "raw", + "id": "f3255c5f-2477-4d13-b4c2-7e56380e9cc2", + "metadata": {}, + "source": [ + "Use `annot` to represent the cell values with text:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3c9f3c73-c8bc-426e-bc67-dec8f807082e", + "metadata": {}, + "outputs": [], + "source": [ + "sns.heatmap(glue, annot=True)" + ] + }, + { + "cell_type": "raw", + "id": "bc412da8-866a-49b7-8496-01fbf06dd908", + "metadata": {}, + "source": [ + "Control the annotations with a formatting string:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ac952d0d-9187-4dff-a560-88430076851a", + "metadata": {}, + "outputs": [], + "source": [ + "sns.heatmap(glue, annot=True, fmt=\".1f\")" + ] + }, + { + "cell_type": "raw", + "id": "5eb12725-e9ee-4df0-9708-243d7e0a77b5", + "metadata": {}, + "source": [ + "Use a separate dataframe for the annotations:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1189a37f-9f74-455a-a09a-c22e056d8ba7", + "metadata": {}, + "outputs": [], + "source": [ + "sns.heatmap(glue, annot=glue.rank(axis=\"columns\"))" + ] + }, + { + "cell_type": "raw", + "id": "253dfb7f-aa12-4716-adc2-3a38b003b2c3", + "metadata": {}, + "source": [ + "Add lines between cells:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5cac673e-9b86-490b-9e67-ec0cf865bede", + "metadata": {}, + "outputs": [], + "source": [ + "sns.heatmap(glue, annot=True, linewidth=.5)" + ] + }, + { + "cell_type": "raw", + "id": "b7d3659c-f996-4af3-a612-430d97799c72", + "metadata": {}, + "source": [ + "Select a different colormap by name:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "86806d72-e784-430e-8320-48f2c91115bb", + "metadata": {}, + "outputs": [], + "source": [ + "sns.heatmap(glue, cmap=\"crest\")" + ] + }, + { + "cell_type": "raw", + "id": "8336fd53-3841-458f-b26c-411efff54d45", + "metadata": {}, + "source": [ + "Or pass a colormap object:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9944ff33-991f-4138-a951-e3015c0326f1", + "metadata": {}, + "outputs": [], + "source": [ + "sns.heatmap(glue, cmap=sns.cubehelix_palette(as_cmap=True))" + ] + }, + { + "cell_type": "raw", + "id": "52cc4dba-b86a-4da8-9cbd-3f8aa06b43b4", + "metadata": {}, + "source": [ + "Set the colormap norm (data values corresponding to minimum and maximum points):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b4ddb41e-c075-41a5-8afe-422ad6d105bf", + "metadata": {}, + "outputs": [], + "source": [ + "sns.heatmap(glue, vmin=50, vmax=100)" + ] + }, + { + "cell_type": "raw", + "id": "6e828517-a532-49b1-be11-eda47c50cc37", + "metadata": {}, + "source": [ + "Use methods on the :class:`matplotlib.axes.Axes` object to tweak the plot:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1aab26fc-2de4-4d4f-ad08-487809573deb", + "metadata": {}, + "outputs": [], + "source": [ + "ax = sns.heatmap(glue, annot=True)\n", + "ax.set(xlabel=\"\", ylabel=\"\")\n", + "ax.xaxis.tick_top()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1d8e738c-388a-453a-b9c7-4c71a674b69c", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "py310", + "language": "python", + "name": "py310" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/_docstrings/histplot.ipynb b/doc/_docstrings/histplot.ipynb index f3ed72e328..308d0db15e 100644 --- a/doc/_docstrings/histplot.ipynb +++ b/doc/_docstrings/histplot.ipynb @@ -461,9 +461,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -475,7 +475,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/hls_palette.ipynb b/doc/_docstrings/hls_palette.ipynb new file mode 100644 index 0000000000..03c95a248d --- /dev/null +++ b/doc/_docstrings/hls_palette.ipynb @@ -0,0 +1,157 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "158cd1cf-6b30-4054-b32f-a166fcb883be", + "metadata": { + "tags": [ + "hide" + ] + }, + "outputs": [], + "source": [ + "import seaborn as sns\n", + "sns.set_theme()\n", + "sns.palettes._patch_colormap_display()" + ] + }, + { + "cell_type": "raw", + "id": "c81b86cb-fb4e-418b-8d2f-6cd10601ac5a", + "metadata": {}, + "source": [ + "By default, return 6 colors with identical lightness and saturation and evenly-sampled hues:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6c3eaeaf-88eb-4012-96ea-41b328fa98b9", + "metadata": {}, + "outputs": [], + "source": [ + "sns.hls_palette()" + ] + }, + { + "cell_type": "raw", + "id": "f7624b0b-2311-45de-b6a5-fc07132ce455", + "metadata": {}, + "source": [ + "Increase the number of colors:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "555c29d1-6972-4a19-ad32-957fb7545634", + "metadata": {}, + "outputs": [], + "source": [ + "sns.hls_palette(8)" + ] + }, + { + "cell_type": "raw", + "id": "24713fa6-e485-4358-9ffc-d40bd9543caa", + "metadata": {}, + "source": [ + "Decrease the lightness:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b6f80b4c-f7b4-4deb-a119-cdf6cfe1f7b5", + "metadata": {}, + "outputs": [], + "source": [ + "sns.hls_palette(l=.3)" + ] + }, + { + "cell_type": "raw", + "id": "e521b514-5572-43e8-95ae-a20cc30169b8", + "metadata": {}, + "source": [ + "Decrease the saturation:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f88bd038-0c9c-48b1-92b0-d272a9c199f4", + "metadata": {}, + "outputs": [], + "source": [ + "sns.hls_palette(s=.3)" + ] + }, + { + "cell_type": "raw", + "id": "92a2212c-2177-4c82-8a5e-9dd788e9f87c", + "metadata": {}, + "source": [ + "Change the start-point for hue sampling:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f8da8fbc-551c-4896-b1b8-04203e740d78", + "metadata": {}, + "outputs": [], + "source": [ + "sns.hls_palette(h=.5)" + ] + }, + { + "cell_type": "raw", + "id": "87780608-1f5a-409f-b31f-6a31a599f122", + "metadata": {}, + "source": [ + "Return a continuous colormap. Notice the perceptual discontinuities, especially around yellow, cyan, and magenta: " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4c622b3b-70d7-4139-8389-f3d0d4addd66", + "metadata": {}, + "outputs": [], + "source": [ + "sns.hls_palette(as_cmap=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3a83c1de-88c5-4327-abd2-19e8f3642052", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "py310", + "language": "python", + "name": "py310" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/_docstrings/husl_palette.ipynb b/doc/_docstrings/husl_palette.ipynb new file mode 100644 index 0000000000..a933bb0496 --- /dev/null +++ b/doc/_docstrings/husl_palette.ipynb @@ -0,0 +1,157 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "a6794650-f28f-40eb-95a7-3f0e5c4b332d", + "metadata": { + "tags": [ + "hide" + ] + }, + "outputs": [], + "source": [ + "import seaborn as sns\n", + "sns.set_theme()\n", + "sns.palettes._patch_colormap_display()" + ] + }, + { + "cell_type": "raw", + "id": "fab2f86e-45d4-4982-ade7-0a5ea6d762d1", + "metadata": {}, + "source": [ + "By default, return 6 colors with identical lightness and saturation and evenly-sampled hues:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b220950e-0ca2-4101-b56a-14eebe8ee8d0", + "metadata": {}, + "outputs": [], + "source": [ + "sns.husl_palette()" + ] + }, + { + "cell_type": "raw", + "id": "c5e4a2e3-e6b8-42bf-be19-348ff7ae2798", + "metadata": {}, + "source": [ + "Increase the number of colors:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7d0af740-cfca-49fb-a472-1daa4ccb3f3a", + "metadata": {}, + "outputs": [], + "source": [ + "sns.husl_palette(8)" + ] + }, + { + "cell_type": "raw", + "id": "1a7189f2-2a26-446a-90e7-cf41dcac4f25", + "metadata": {}, + "source": [ + "Decrease the lightness:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "43af79c7-f497-41e5-874a-83eed99500f3", + "metadata": {}, + "outputs": [], + "source": [ + "sns.husl_palette(l=.4)" + ] + }, + { + "cell_type": "raw", + "id": "6d4099b7-5115-4365-b120-33a345581f5d", + "metadata": {}, + "source": [ + "Decrease the saturation:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "52c1afc7-d982-4199-b218-222aa94563c5", + "metadata": {}, + "outputs": [], + "source": [ + "sns.husl_palette(s=.4)" + ] + }, + { + "cell_type": "raw", + "id": "d26131ac-0d11-48c5-88b1-4e5cf9383000", + "metadata": {}, + "source": [ + "Change the start-point for hue sampling:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d72f06a0-13e0-47f7-bc70-4c5935eaa130", + "metadata": {}, + "outputs": [], + "source": [ + "sns.husl_palette(h=.5)" + ] + }, + { + "cell_type": "raw", + "id": "7e6c3c19-41d3-4315-b03e-909d201d0e76", + "metadata": {}, + "source": [ + "Return a continuous colormap:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "49c18838-0589-496f-9a61-635195c07f61", + "metadata": {}, + "outputs": [], + "source": [ + "sns.husl_palette(as_cmap=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c710a557-8e84-44cb-ab4c-baabcc4fd328", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "py310", + "language": "python", + "name": "py310" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/_docstrings/jointplot.ipynb b/doc/_docstrings/jointplot.ipynb index 32801c3a3b..379b5307c8 100644 --- a/doc/_docstrings/jointplot.ipynb +++ b/doc/_docstrings/jointplot.ipynb @@ -172,9 +172,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -186,7 +186,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/kdeplot.ipynb b/doc/_docstrings/kdeplot.ipynb index 81d375a7b3..40693d05b4 100644 --- a/doc/_docstrings/kdeplot.ipynb +++ b/doc/_docstrings/kdeplot.ipynb @@ -327,9 +327,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -341,7 +341,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/light_palette.ipynb b/doc/_docstrings/light_palette.ipynb new file mode 100644 index 0000000000..a1a830a3d9 --- /dev/null +++ b/doc/_docstrings/light_palette.ipynb @@ -0,0 +1,139 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "5cd1cbb8-ba1a-460b-8e3a-bc285867f1d1", + "metadata": { + "tags": [ + "hide" + ] + }, + "outputs": [], + "source": [ + "import seaborn as sns\n", + "sns.set_theme()\n", + "sns.palettes._patch_colormap_display()" + ] + }, + { + "cell_type": "raw", + "id": "b157eb25-015f-4dd6-9785-83ba19cf4f94", + "metadata": {}, + "source": [ + "Define a sequential ramp from a light gray to a specified color:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "851a4742-6276-4383-b17e-480beb896877", + "metadata": {}, + "outputs": [], + "source": [ + "sns.light_palette(\"seagreen\")" + ] + }, + { + "cell_type": "raw", + "id": "50053b26-112a-4378-8ef0-9be0fb565ec7", + "metadata": {}, + "source": [ + "Specify the color with a hex code:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "74ae0d17-f65b-4bcf-ae66-d97d46964d5c", + "metadata": {}, + "outputs": [], + "source": [ + "sns.light_palette(\"#79C\")" + ] + }, + { + "cell_type": "raw", + "id": "eea376a2-fdf5-40e4-a187-3a28af529072", + "metadata": {}, + "source": [ + "Specify the color from the husl system:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "66e451ee-869a-41ea-8dc5-4240b11e7be5", + "metadata": {}, + "outputs": [], + "source": [ + "sns.light_palette((20, 60, 50), input=\"husl\")" + ] + }, + { + "cell_type": "raw", + "id": "e4f44dcd-cf49-4920-ac05-b4db67870363", + "metadata": {}, + "source": [ + "Increase the number of colors:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "75985f07-de92-4d8b-89d5-caf445b9375e", + "metadata": {}, + "outputs": [], + "source": [ + "sns.light_palette(\"xkcd:copper\", 8)" + ] + }, + { + "cell_type": "raw", + "id": "34687ae8-fd6d-427a-a639-208f19e61122", + "metadata": {}, + "source": [ + "Return a continuous colormap rather than a discrete palette:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2c342db4-7f97-40f5-934e-9a82201890d1", + "metadata": {}, + "outputs": [], + "source": [ + "sns.light_palette(\"#a275ac\", as_cmap=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e7ebe64b-25fa-4c52-9ebe-fdcbba0ee51e", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "py310", + "language": "python", + "name": "py310" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/_docstrings/lineplot.ipynb b/doc/_docstrings/lineplot.ipynb index 02b20c73d2..2d73607415 100644 --- a/doc/_docstrings/lineplot.ipynb +++ b/doc/_docstrings/lineplot.ipynb @@ -431,9 +431,9 @@ "hash": "8bdfc9d9da1e36addfcfc8a3409187c45d33387af0f87d0d91e99e8d6403f1c3" }, "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -445,7 +445,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/lmplot.ipynb b/doc/_docstrings/lmplot.ipynb new file mode 100644 index 0000000000..c080373d40 --- /dev/null +++ b/doc/_docstrings/lmplot.ipynb @@ -0,0 +1,157 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "034a9a5b-91ff-4ccc-932d-0f314e2cd6d2", + "metadata": {}, + "source": [ + "See the :func:`regplot` docs for demonstrations of various options for specifying the regression model, which are also accepted here." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "76c91243-3bd8-49a1-b8c8-b7272f09a3f1", + "metadata": { + "tags": [ + "hide" + ] + }, + "outputs": [], + "source": [ + "import seaborn as sns\n", + "sns.set_theme(style=\"ticks\")\n", + "penguins = sns.load_dataset(\"penguins\")" + ] + }, + { + "cell_type": "raw", + "id": "0ba9f55d-17ea-4084-a74f-852d51771380", + "metadata": {}, + "source": [ + "Plot a regression fit over a scatter plot:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2f789265-93c0-4867-b666-798713e4e7e5", + "metadata": {}, + "outputs": [], + "source": [ + "sns.lmplot(data=penguins, x=\"bill_length_mm\", y=\"bill_depth_mm\")" + ] + }, + { + "cell_type": "raw", + "id": "7e4b0ad4-446c-4109-9393-961f76132e34", + "metadata": {}, + "source": [ + "Condition the regression fit on another variable and represent it using color:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "61347189-34e5-42ea-b77b-4acdef843326", + "metadata": {}, + "outputs": [], + "source": [ + "sns.lmplot(data=penguins, x=\"bill_length_mm\", y=\"bill_depth_mm\", hue=\"species\")" + ] + }, + { + "cell_type": "raw", + "id": "c9b6d059-49dc-46a7-869b-86baa3a7ed65", + "metadata": {}, + "source": [ + "Condition the regression fit on another variable and split across subplots:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d8ec2955-ccc9-493c-b9ec-c78648ce9f53", + "metadata": {}, + "outputs": [], + "source": [ + "sns.lmplot(\n", + " data=penguins, x=\"bill_length_mm\", y=\"bill_depth_mm\",\n", + " hue=\"species\", col=\"sex\", height=4,\n", + ")" + ] + }, + { + "cell_type": "raw", + "id": "de01dee1-b2ce-445c-8d0d-d054ca0dfedb", + "metadata": {}, + "source": [ + "Condition across two variables using both columns and rows:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6f1264aa-829c-416a-805a-b989e5f11a17", + "metadata": {}, + "outputs": [], + "source": [ + "sns.lmplot(\n", + " data=penguins, x=\"bill_length_mm\", y=\"bill_depth_mm\",\n", + " col=\"species\", row=\"sex\", height=3,\n", + ")" + ] + }, + { + "cell_type": "raw", + "id": "b3888f04-b22f-4205-8acc-24ce5b59568e", + "metadata": {}, + "source": [ + "Allow axis limits to vary across subplots:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "67ed5af1-d228-4b81-b4f8-21937c513a10", + "metadata": {}, + "outputs": [], + "source": [ + "sns.lmplot(\n", + " data=penguins, x=\"bill_length_mm\", y=\"bill_depth_mm\",\n", + " col=\"species\", row=\"sex\", height=3,\n", + " facet_kws=dict(sharex=False, sharey=False),\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "46e9cf18-c847-4c40-8e38-6c20cdde2be5", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "py310", + "language": "python", + "name": "py310" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/_docstrings/move_legend.ipynb b/doc/_docstrings/move_legend.ipynb index 604eda0b3a..f36848cf54 100644 --- a/doc/_docstrings/move_legend.ipynb +++ b/doc/_docstrings/move_legend.ipynb @@ -134,9 +134,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -148,7 +148,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/mpl_palette.ipynb b/doc/_docstrings/mpl_palette.ipynb new file mode 100644 index 0000000000..d878fcc9ec --- /dev/null +++ b/doc/_docstrings/mpl_palette.ipynb @@ -0,0 +1,139 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "1d0d41d3-463c-4c6f-aa65-38131bdf3ddb", + "metadata": { + "tags": [ + "hide" + ] + }, + "outputs": [], + "source": [ + "import seaborn as sns\n", + "sns.set_theme()\n", + "sns.palettes._patch_colormap_display()" + ] + }, + { + "cell_type": "markdown", + "id": "d2a0ae1e-a01e-49b3-a677-2b05a195990a", + "metadata": {}, + "source": [ + "Return discrete samples from a continuous matplotlib colormap:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2b6a4ce9-6e4e-4b59-ada8-14ef8aef21d7", + "metadata": {}, + "outputs": [], + "source": [ + "sns.mpl_palette(\"viridis\")" + ] + }, + { + "cell_type": "raw", + "id": "0ccc47b1-c969-46e2-93bb-b9eb5a2e2141", + "metadata": {}, + "source": [ + "Return the continuous colormap instead; note how the extreme values are more intense:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a8a1bc5d-1d62-45c6-a53b-9fadb58f11c0", + "metadata": {}, + "outputs": [], + "source": [ + "sns.mpl_palette(\"viridis\", as_cmap=True)" + ] + }, + { + "cell_type": "raw", + "id": "ff0d1a3b-8641-40c0-bb4b-c22b83ec9432", + "metadata": {}, + "source": [ + "Return more colors:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8faef1d8-a1eb-4060-be10-377342c9bd1d", + "metadata": {}, + "outputs": [], + "source": [ + "sns.mpl_palette(\"viridis\", 8)" + ] + }, + { + "cell_type": "raw", + "id": "612bf052-e888-411d-a2ea-6a742a78bc63", + "metadata": {}, + "source": [ + "Return values from a qualitative colormap:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "74db95a8-4898-4f6c-a57d-c751af1dc7bf", + "metadata": {}, + "outputs": [], + "source": [ + "sns.mpl_palette(\"Set2\")" + ] + }, + { + "cell_type": "raw", + "id": "918494bf-1b8e-4b00-8950-1bd73032dee1", + "metadata": {}, + "source": [ + "Notice how the palette will only contain distinct colors and can be shorter than requested:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d97efa25-9050-4e28-b758-da6f43c9f963", + "metadata": {}, + "outputs": [], + "source": [ + "sns.mpl_palette(\"Set2\", 10)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f64ad118-e213-43cc-a714-98ed13cc3824", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "py310", + "language": "python", + "name": "py310" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/_docstrings/objects.Area.ipynb b/doc/_docstrings/objects.Area.ipynb index f3ddc6aefd..256b46a93f 100644 --- a/doc/_docstrings/objects.Area.ipynb +++ b/doc/_docstrings/objects.Area.ipynb @@ -139,9 +139,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -153,7 +153,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/objects.Band.ipynb b/doc/_docstrings/objects.Band.ipynb index 902e33a3aa..bcd1975847 100644 --- a/doc/_docstrings/objects.Band.ipynb +++ b/doc/_docstrings/objects.Band.ipynb @@ -13,7 +13,7 @@ "source": [ "import seaborn.objects as so\n", "from seaborn import load_dataset\n", - "fmri = load_dataset(\"fmri\")\n", + "fmri = load_dataset(\"fmri\").query(\"region == 'parietal'\")\n", "seaice = (\n", " load_dataset(\"seaice\")\n", " .assign(\n", @@ -22,7 +22,7 @@ " )\n", " .query(\"Year >= 1980\")\n", " .astype({\"Year\": str})\n", - " .pivot(\"Day\", \"Year\", \"Extent\")\n", + " .pivot(index=\"Day\", columns=\"Year\", values=\"Extent\")\n", " .filter([\"1980\", \"2019\"])\n", " .dropna()\n", " .reset_index()\n", @@ -90,16 +90,40 @@ }, { "cell_type": "raw", - "id": "4e817cdd-09a3-4cf6-8602-e9665607bfe1", + "id": "9f0c82bf-3457-4ac5-ba48-8930bac03d75", "metadata": {}, + "source": [ + "When min/max values are not explicitly assigned or added in a transform, the band will cover the full extent of the data:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "309f578e-da3d-4dc5-b6ac-a354321334c8", + "metadata": {}, + "outputs": [], + "source": [ + "(\n", + " so.Plot(fmri, x=\"timepoint\", y=\"signal\", color=\"event\")\n", + " .add(so.Line(linewidth=.5), group=\"subject\")\n", + " .add(so.Band())\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4330a3cd-63fe-470a-8e83-09e9606643b5", + "metadata": {}, + "outputs": [], "source": [] } ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -111,7 +135,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/objects.Bar.ipynb b/doc/_docstrings/objects.Bar.ipynb index 30ac8ece4b..1ca0851975 100644 --- a/doc/_docstrings/objects.Bar.ipynb +++ b/doc/_docstrings/objects.Bar.ipynb @@ -164,9 +164,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -178,7 +178,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/objects.Bars.ipynb b/doc/_docstrings/objects.Bars.ipynb index eb47550ab6..71969df4b3 100644 --- a/doc/_docstrings/objects.Bars.ipynb +++ b/doc/_docstrings/objects.Bars.ipynb @@ -143,9 +143,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -157,7 +157,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/objects.Dot.ipynb b/doc/_docstrings/objects.Dot.ipynb index 34a262bcef..d133c04127 100644 --- a/doc/_docstrings/objects.Dot.ipynb +++ b/doc/_docstrings/objects.Dot.ipynb @@ -168,9 +168,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -182,7 +182,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/objects.Dots.ipynb b/doc/_docstrings/objects.Dots.ipynb index a7f920fa43..f1b3a53d2c 100644 --- a/doc/_docstrings/objects.Dots.ipynb +++ b/doc/_docstrings/objects.Dots.ipynb @@ -124,9 +124,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -138,7 +138,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/objects.Hist.ipynb b/doc/_docstrings/objects.Hist.ipynb new file mode 100644 index 0000000000..778e0def95 --- /dev/null +++ b/doc/_docstrings/objects.Hist.ipynb @@ -0,0 +1,231 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "59690096-a0ad-4ff3-b82c-0258d724035a", + "metadata": { + "tags": [ + "hide" + ] + }, + "outputs": [], + "source": [ + "import seaborn.objects as so\n", + "from seaborn import load_dataset\n", + "penguins = load_dataset(\"penguins\")" + ] + }, + { + "cell_type": "raw", + "id": "c345a35c-bac8-4163-ba40-e7c208df1033", + "metadata": {}, + "source": [ + "For discrete or categorical variables, this stat is commonly combined with a :class:`Bar` mark:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6a96ac9b-1240-496d-9385-840205945208", + "metadata": {}, + "outputs": [], + "source": [ + "so.Plot(penguins, \"island\").add(so.Bar(), so.Hist())" + ] + }, + { + "cell_type": "raw", + "id": "1e5ff9d5-c6a9-4adc-a9be-0f155b1575be", + "metadata": {}, + "source": [ + "When used to estimate a univariate distribution, it is better to use the :class:`Bars` mark:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7f3e3144-752a-4d71-9528-85eb1ed0a9a4", + "metadata": {}, + "outputs": [], + "source": [ + "p = so.Plot(penguins, \"flipper_length_mm\")\n", + "p.add(so.Bars(), so.Hist())" + ] + }, + { + "cell_type": "raw", + "id": "008b9ffe-da74-4406-9756-4f70e333f33b", + "metadata": {}, + "source": [ + "The granularity of the bins will influence whether the underlying distribution is accurately represented. Adjust it by setting the total number:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "27d221d5-add5-40a8-85d2-05102384dad1", + "metadata": {}, + "outputs": [], + "source": [ + "p.add(so.Bars(), so.Hist(bins=20))" + ] + }, + { + "cell_type": "raw", + "id": "fffebb54-0299-45c5-b7fb-6fcad6427239", + "metadata": {}, + "source": [ + "Alternatively, specify the *width* of the bins:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d036ca65-7dcf-45ac-a2d1-caafb9f922a7", + "metadata": {}, + "outputs": [], + "source": [ + "p.add(so.Bars(), so.Hist(binwidth=5))" + ] + }, + { + "cell_type": "raw", + "id": "bc1e4bd3-2a16-42bd-9c13-a660dd381f66", + "metadata": {}, + "source": [ + "By default, the transform returns the count of observations in each bin. The counts can be normalized, e.g. to show a proportion:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dbf23712-2231-4226-8265-0e2a5299c4bb", + "metadata": {}, + "outputs": [], + "source": [ + "p.add(so.Bars(), so.Hist(stat=\"proportion\"))" + ] + }, + { + "cell_type": "raw", + "id": "6c6fb23e-78c5-4630-a958-62cb4dee4ec8", + "metadata": {}, + "source": [ + "When additional variables define groups, the default behavior is to normalize across all groups:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ac3fe4ef-56e3-4ec7-b580-596d2a3d924b", + "metadata": {}, + "outputs": [], + "source": [ + "p = p.facet(\"island\")\n", + "p.add(so.Bars(), so.Hist(stat=\"proportion\"))" + ] + }, + { + "cell_type": "raw", + "id": "f7afc403-26cc-4325-a28a-913c2291aa35", + "metadata": {}, + "source": [ + "Pass `common_norm=False` to normalize each distribution independently:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b2029324-069f-4261-a178-1efad2fd0e88", + "metadata": {}, + "outputs": [], + "source": [ + "p.add(so.Bars(), so.Hist(stat=\"proportion\", common_norm=False))" + ] + }, + { + "cell_type": "raw", + "id": "0f83401a-e456-4a14-af69-f1483c6c03c4", + "metadata": {}, + "source": [ + "Or, with more than one grouping varible, specify a subset to normalize within:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5c092262-8a8f-4a3e-8cae-9e0f23dd94ba", + "metadata": {}, + "outputs": [], + "source": [ + "p.add(so.Bars(), so.Hist(stat=\"proportion\", common_norm=[\"col\"]), color=\"sex\")" + ] + }, + { + "cell_type": "raw", + "id": "86532133-bf33-4674-9614-86ae3408aa51", + "metadata": {}, + "source": [ + "When distributions overlap it may be easier to discern their shapes with an :class:`Area` mark:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "00b18ad8-52d4-460a-a012-d87c66b3e71e", + "metadata": {}, + "outputs": [], + "source": [ + "p.add(so.Area(), so.Hist(), color=\"sex\")" + ] + }, + { + "cell_type": "raw", + "id": "2b34d435-abbf-41aa-b219-91883d7d29f3", + "metadata": {}, + "source": [ + "Or add :class:`Stack` move to represent a part-whole relationship:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3a7a0c05-d774-4f99-950f-5dc9865027c4", + "metadata": {}, + "outputs": [], + "source": [ + "p.add(so.Bars(), so.Hist(), so.Stack(), color=\"sex\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e247e74b-2c09-40f0-8f45-9fa5f8264d78", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "py310", + "language": "python", + "name": "py310" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/_docstrings/objects.Jitter.ipynb b/doc/_docstrings/objects.Jitter.ipynb new file mode 100644 index 0000000000..6aa600cddd --- /dev/null +++ b/doc/_docstrings/objects.Jitter.ipynb @@ -0,0 +1,178 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "f2e5a85d-c710-492b-a4fc-09b45ae26471", + "metadata": { + "tags": [ + "hide" + ] + }, + "outputs": [], + "source": [ + "import seaborn.objects as so\n", + "from seaborn import load_dataset\n", + "penguins = load_dataset(\"penguins\")" + ] + }, + { + "cell_type": "raw", + "id": "14b5927c-42f1-4934-adee-3d380b8b3228", + "metadata": {}, + "source": [ + "When used without any arguments, a small amount of jitter will be applied along the orientation axis:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bc1b4941-bbe6-4afc-b51a-0ac67cbe417d", + "metadata": {}, + "outputs": [], + "source": [ + "(\n", + " so.Plot(penguins, \"species\", \"body_mass_g\")\n", + " .add(so.Dots(), so.Jitter())\n", + ")" + ] + }, + { + "cell_type": "raw", + "id": "1101690e-6c19-4219-aa4e-180798454df1", + "metadata": {}, + "source": [ + "The `width` parameter controls the amount of jitter relative to the spacing between the marks:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c4251b9d-8b11-4c2c-905c-2f3b523dee70", + "metadata": {}, + "outputs": [], + "source": [ + "(\n", + " so.Plot(penguins, \"species\", \"body_mass_g\")\n", + " .add(so.Dots(), so.Jitter(.5))\n", + ")" + ] + }, + { + "cell_type": "raw", + "id": "38aa639a-356e-4674-970b-53d55379b2b7", + "metadata": {}, + "source": [ + "The `width` parameter always applies to the orientation axis, so the direction of jitter will adapt along with the orientation:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1cfe1c07-7e81-45a0-a989-240503046133", + "metadata": {}, + "outputs": [], + "source": [ + "(\n", + " so.Plot(penguins, \"body_mass_g\", \"species\")\n", + " .add(so.Dots(), so.Jitter(.5))\n", + ")" + ] + }, + { + "cell_type": "raw", + "id": "0f5de4cc-3383-4503-8b59-9c48230a12a5", + "metadata": {}, + "source": [ + "Because the `width` jitter is relative, it can be used when the orientation axis is numeric without further tweaking:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c94c41e8-29c4-4439-a5d1-0b8ffb244890", + "metadata": {}, + "outputs": [], + "source": [ + "(\n", + " so.Plot(penguins[\"body_mass_g\"].round(-3), penguins[\"flipper_length_mm\"])\n", + " .add(so.Dots(), so.Jitter())\n", + ")" + ] + }, + { + "cell_type": "raw", + "id": "dd982dfa-fd9f-4edc-8190-18f0e101ae1a", + "metadata": {}, + "source": [ + "In contrast to `width`, the `x` and `y` parameters always refer to specific axes and control the jitter in data units:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b0f2e5ca-68ad-4439-a4ee-f32f65682e95", + "metadata": {}, + "outputs": [], + "source": [ + "(\n", + " so.Plot(penguins[\"body_mass_g\"].round(-3), penguins[\"flipper_length_mm\"])\n", + " .add(so.Dots(), so.Jitter(x=100))\n", + ")" + ] + }, + { + "cell_type": "raw", + "id": "a90ba526-8043-42ed-8f57-36445c163c0d", + "metadata": {}, + "source": [ + "Both `x` and `y` can be used in a single transform:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6c07ed1d-ac77-4b30-90a8-e1b8760f9fad", + "metadata": {}, + "outputs": [], + "source": [ + "(\n", + " so.Plot(\n", + " penguins[\"body_mass_g\"].round(-3),\n", + " penguins[\"flipper_length_mm\"].round(-1),\n", + " )\n", + " .add(so.Dots(), so.Jitter(x=200, y=5))\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bb04c7a2-93f0-44cf-aacf-0eb436d0f14b", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "py310", + "language": "python", + "name": "py310" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/_docstrings/objects.Line.ipynb b/doc/_docstrings/objects.Line.ipynb index 27c140913a..bc8b8b5ec3 100644 --- a/doc/_docstrings/objects.Line.ipynb +++ b/doc/_docstrings/objects.Line.ipynb @@ -146,9 +146,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -160,7 +160,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/objects.Lines.ipynb b/doc/_docstrings/objects.Lines.ipynb index ccce6c28ae..375715636d 100644 --- a/doc/_docstrings/objects.Lines.ipynb +++ b/doc/_docstrings/objects.Lines.ipynb @@ -75,9 +75,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -89,7 +89,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/objects.Path.ipynb b/doc/_docstrings/objects.Path.ipynb index 1b9a440013..39b4a2b78a 100644 --- a/doc/_docstrings/objects.Path.ipynb +++ b/doc/_docstrings/objects.Path.ipynb @@ -64,9 +64,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -78,7 +78,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/objects.Paths.ipynb b/doc/_docstrings/objects.Paths.ipynb index 0f67ab75ee..5d9d33990e 100644 --- a/doc/_docstrings/objects.Paths.ipynb +++ b/doc/_docstrings/objects.Paths.ipynb @@ -81,9 +81,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -95,7 +95,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/objects.Perc.ipynb b/doc/_docstrings/objects.Perc.ipynb new file mode 100644 index 0000000000..b97c87cc1f --- /dev/null +++ b/doc/_docstrings/objects.Perc.ipynb @@ -0,0 +1,130 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "2d44a326-029b-47ff-b560-5f4b6a4bb73f", + "metadata": { + "tags": [ + "hide" + ] + }, + "outputs": [], + "source": [ + "import seaborn.objects as so\n", + "from seaborn import load_dataset\n", + "diamonds = load_dataset(\"diamonds\")" + ] + }, + { + "cell_type": "raw", + "id": "65e975a2-2559-4bf1-8851-8bbbf52bf22d", + "metadata": {}, + "source": [ + "The default behavior computes the quartiles and min/max of the input data:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "36f927f5-3b64-4871-a355-adadc4da769b", + "metadata": {}, + "outputs": [], + "source": [ + "p = (\n", + " so.Plot(diamonds, \"cut\", \"price\")\n", + " .scale(y=\"log\")\n", + ")\n", + "p.add(so.Dot(), so.Perc())" + ] + }, + { + "cell_type": "raw", + "id": "feba1b99-0f71-4b18-8e7e-bd5470cc2d0c", + "metadata": {}, + "source": [ + "Passing an integer will compute that many evenly-spaced percentiles:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f030dd39-1223-475a-93e1-1759a8971a6c", + "metadata": {}, + "outputs": [], + "source": [ + "p.add(so.Dot(), so.Perc(20))" + ] + }, + { + "cell_type": "raw", + "id": "85bd754b-122e-4475-8727-2d584a90a38e", + "metadata": {}, + "source": [ + "Passing a list will compute exactly those percentiles:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2fde7549-45b5-411a-afba-eb0da754d9e9", + "metadata": {}, + "outputs": [], + "source": [ + "p.add(so.Dot(), so.Perc([10, 25, 50, 75, 90]))" + ] + }, + { + "cell_type": "raw", + "id": "7be16a13-dfc8-4595-a904-42f9be10f4f6", + "metadata": {}, + "source": [ + "Combine with a range mark to show a percentile interval:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "05c561c6-0449-4a61-96d1-390611a1b694", + "metadata": {}, + "outputs": [], + "source": [ + "(\n", + " so.Plot(diamonds, \"price\", \"cut\")\n", + " .add(so.Dots(pointsize=1, alpha=.2), so.Jitter(.3))\n", + " .add(so.Range(color=\"k\"), so.Perc([25, 75]), so.Shift(y=.2))\n", + " .scale(x=\"log\")\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d464157c-3187-49c1-9cd8-71f284ce4c50", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "py310", + "language": "python", + "name": "py310" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/_docstrings/objects.Plot.add.ipynb b/doc/_docstrings/objects.Plot.add.ipynb index 3f9089982e..e997aca980 100644 --- a/doc/_docstrings/objects.Plot.add.ipynb +++ b/doc/_docstrings/objects.Plot.add.ipynb @@ -196,9 +196,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -210,7 +210,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/objects.Plot.facet.ipynb b/doc/_docstrings/objects.Plot.facet.ipynb index dbded39a7c..2155dfb5ec 100644 --- a/doc/_docstrings/objects.Plot.facet.ipynb +++ b/doc/_docstrings/objects.Plot.facet.ipynb @@ -200,9 +200,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -214,7 +214,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/objects.Plot.label.ipynb b/doc/_docstrings/objects.Plot.label.ipynb index 5d9d0b6be8..3a4300b3e3 100644 --- a/doc/_docstrings/objects.Plot.label.ipynb +++ b/doc/_docstrings/objects.Plot.label.ipynb @@ -139,9 +139,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -153,7 +153,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/objects.Plot.layout.ipynb b/doc/_docstrings/objects.Plot.layout.ipynb index 53e60c0e58..1198766365 100644 --- a/doc/_docstrings/objects.Plot.layout.ipynb +++ b/doc/_docstrings/objects.Plot.layout.ipynb @@ -80,9 +80,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -94,7 +94,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/objects.Plot.limit.ipynb b/doc/_docstrings/objects.Plot.limit.ipynb index cf7ec4a1b0..9d8f33cfa7 100644 --- a/doc/_docstrings/objects.Plot.limit.ipynb +++ b/doc/_docstrings/objects.Plot.limit.ipynb @@ -98,9 +98,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -112,7 +112,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/objects.Plot.on.ipynb b/doc/_docstrings/objects.Plot.on.ipynb index 5596c7b107..7e14557bc0 100644 --- a/doc/_docstrings/objects.Plot.on.ipynb +++ b/doc/_docstrings/objects.Plot.on.ipynb @@ -160,9 +160,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -174,7 +174,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/objects.Plot.pair.ipynb b/doc/_docstrings/objects.Plot.pair.ipynb index b917a90fcd..fc78cbf175 100644 --- a/doc/_docstrings/objects.Plot.pair.ipynb +++ b/doc/_docstrings/objects.Plot.pair.ipynb @@ -195,9 +195,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -209,7 +209,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/objects.Plot.scale.ipynb b/doc/_docstrings/objects.Plot.scale.ipynb index 9bf784263e..d2d679f429 100644 --- a/doc/_docstrings/objects.Plot.scale.ipynb +++ b/doc/_docstrings/objects.Plot.scale.ipynb @@ -294,9 +294,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -308,7 +308,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/objects.Plot.share.ipynb b/doc/_docstrings/objects.Plot.share.ipynb index d26ecd0862..d0b1ef5cb1 100644 --- a/doc/_docstrings/objects.Plot.share.ipynb +++ b/doc/_docstrings/objects.Plot.share.ipynb @@ -109,9 +109,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -123,7 +123,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/objects.Plot.theme.ipynb b/doc/_docstrings/objects.Plot.theme.ipynb index 01eae3eca7..bb459a5620 100644 --- a/doc/_docstrings/objects.Plot.theme.ipynb +++ b/doc/_docstrings/objects.Plot.theme.ipynb @@ -125,9 +125,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -139,7 +139,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/objects.Range.ipynb b/doc/_docstrings/objects.Range.ipynb index 40725f3825..cccb2296ed 100644 --- a/doc/_docstrings/objects.Range.ipynb +++ b/doc/_docstrings/objects.Range.ipynb @@ -57,7 +57,7 @@ " so.Plot(penguins, x=\"sex\", y=\"body_mass_g\", linestyle=\"species\")\n", " .facet(\"species\")\n", " .add(so.Line(marker=\"o\"), so.Agg())\n", - " .add(so.Range(), so.Est(errorbar=\"pi\"))\n", + " .add(so.Range(), so.Est(errorbar=\"sd\"))\n", ")" ] }, @@ -78,21 +78,39 @@ "source": [ "(\n", " penguins\n", - " .rename_axis(\"penguin\")\n", - " .pipe(so.Plot, ymin=\"bill_depth_mm\", ymax=\"bill_length_mm\", x=\"penguin\")\n", - " .add(so.Range(), color=\"island\", linewidth=\"body_mass_g\")\n", - " .scale(x=so.Continuous().tick(count=0), linewidth=(.5, 1.5))\n", - " .facet(row=\"species\", col=\"sex\")\n", - " .layout(size=(8, 4))\n", - " .share(x=False)\n", - " .label(x=\"\", y=\"Size (mm)\")\n", + " .rename_axis(index=\"penguin\")\n", + " .pipe(so.Plot, x=\"penguin\", ymin=\"bill_depth_mm\", ymax=\"bill_length_mm\")\n", + " .add(so.Range(), color=\"island\")\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "2191bec6-a02e-48e0-b92c-69c38826049d", + "metadata": {}, + "source": [ + "When `min`/`max` variables are neither computed as part of a transform or explicitly assigned, the range will cover the full extent of the data at each unique observation on the orient axis:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "63c6352e-4ef5-4cff-940e-35fa5804b2c7", + "metadata": {}, + "outputs": [], + "source": [ + "(\n", + " so.Plot(penguins, x=\"sex\", y=\"body_mass_g\")\n", + " .facet(\"species\")\n", + " .add(so.Dots(pointsize=6))\n", + " .add(so.Range(linewidth=2))\n", ")" ] }, { "cell_type": "code", "execution_count": null, - "id": "08751ee7-d0a0-4e70-92b4-c1b38ea28890", + "id": "c215deb1-e510-4631-b999-737f5f41cae2", "metadata": {}, "outputs": [], "source": [] @@ -100,9 +118,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -114,7 +132,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/objects.Text.ipynb b/doc/_docstrings/objects.Text.ipynb new file mode 100644 index 0000000000..ce0455fc6d --- /dev/null +++ b/doc/_docstrings/objects.Text.ipynb @@ -0,0 +1,188 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "cd1cdefe-b8c1-40b9-be31-006d52ec9f18", + "metadata": { + "tags": [ + "hide" + ] + }, + "outputs": [], + "source": [ + "import seaborn.objects as so\n", + "from seaborn import load_dataset\n", + "glue = (\n", + " load_dataset(\"glue\")\n", + " .pivot(index=[\"Model\", \"Encoder\"], columns=\"Task\", values=\"Score\")\n", + " .assign(Average=lambda x: x.mean(axis=1).round(1))\n", + " .sort_values(\"Average\")\n", + ")" + ] + }, + { + "cell_type": "raw", + "id": "3e49ffb1-8778-4cd5-80d6-9d7e1438bc9c", + "metadata": {}, + "source": [ + "Add text at x/y locations on the plot:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3bf21068-d39e-436c-8deb-aa1b15aeb2b3", + "metadata": {}, + "outputs": [], + "source": [ + "(\n", + " so.Plot(glue, x=\"SST-2\", y=\"MRPC\", text=\"Model\")\n", + " .add(so.Text())\n", + ")" + ] + }, + { + "cell_type": "raw", + "id": "a4b9a8b2-6603-46db-9ede-3b3fb45e0e64", + "metadata": {}, + "source": [ + "Add bar annotations, horizontally-aligned with `halign`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f68501f0-c868-439e-9485-d71cca86ea47", + "metadata": {}, + "outputs": [], + "source": [ + "(\n", + " so.Plot(glue, x=\"Average\", y=\"Model\", text=\"Average\")\n", + " .add(so.Bar())\n", + " .add(so.Text(color=\"w\", halign=\"right\"))\n", + ")" + ] + }, + { + "cell_type": "raw", + "id": "a9d39479-0afa-477b-8403-fe92a54643c9", + "metadata": {}, + "source": [ + "Fine-tune the alignment using `offset`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b5da4a9d-79f3-4c11-bab3-f89da8512ce4", + "metadata": {}, + "outputs": [], + "source": [ + "(\n", + " so.Plot(glue, x=\"Average\", y=\"Model\", text=\"Average\")\n", + " .add(so.Bar())\n", + " .add(so.Text(color=\"w\", halign=\"right\", offset=6))\n", + ")" + ] + }, + { + "cell_type": "raw", + "id": "e9c43798-70d5-42b5-bd91-b85684d1b671", + "metadata": {}, + "source": [ + "Add text above dots, mapping the text color with a third variable:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b2d26ebc-24ac-4531-9ba2-fa03720c58bc", + "metadata": {}, + "outputs": [], + "source": [ + "(\n", + " so.Plot(glue, x=\"SST-2\", y=\"MRPC\", color=\"Encoder\", text=\"Model\")\n", + " .add(so.Dot())\n", + " .add(so.Text(valign=\"bottom\"))\n", + "\n", + ")" + ] + }, + { + "cell_type": "raw", + "id": "f31aaa38-6728-4299-8422-8762c52c9857", + "metadata": {}, + "source": [ + "Map the text alignment for better use of space:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cf4bbf0c-0c5f-4c31-b971-720ea8910918", + "metadata": {}, + "outputs": [], + "source": [ + "(\n", + " so.Plot(glue, x=\"RTE\", y=\"MRPC\", color=\"Encoder\", text=\"Model\")\n", + " .add(so.Dot())\n", + " .add(so.Text(), halign=\"Encoder\")\n", + " .scale(halign={\"LSTM\": \"left\", \"Transformer\": \"right\"})\n", + ")" + ] + }, + { + "cell_type": "raw", + "id": "a5de35a6-1ccf-4958-8013-edd9ed1cd4b0", + "metadata": {}, + "source": [ + "Use additional matplotlib parameters to control the appearance of the text:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9c4be188-1614-4c19-9bd7-b07e986f6a23", + "metadata": {}, + "outputs": [], + "source": [ + "(\n", + " so.Plot(glue, x=\"RTE\", y=\"MRPC\", color=\"Encoder\", text=\"Model\")\n", + " .add(so.Dot())\n", + " .add(so.Text({\"fontweight\": \"bold\"}), halign=\"Encoder\")\n", + " .scale(halign={\"LSTM\": \"left\", \"Transformer\": \"right\"})\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "95fb7aee-090a-4415-917c-b5258d2b298b", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "py310", + "language": "python", + "name": "py310" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/_docstrings/pairplot.ipynb b/doc/_docstrings/pairplot.ipynb index dd91f3638b..67948e4f9b 100644 --- a/doc/_docstrings/pairplot.ipynb +++ b/doc/_docstrings/pairplot.ipynb @@ -203,9 +203,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -217,7 +217,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/plotting_context.ipynb b/doc/_docstrings/plotting_context.ipynb index 48f65bc23a..4f757331a8 100644 --- a/doc/_docstrings/plotting_context.ipynb +++ b/doc/_docstrings/plotting_context.ipynb @@ -88,9 +88,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -102,7 +102,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/pointplot.ipynb b/doc/_docstrings/pointplot.ipynb index a4541bbd32..e58aeec19a 100644 --- a/doc/_docstrings/pointplot.ipynb +++ b/doc/_docstrings/pointplot.ipynb @@ -120,9 +120,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -134,7 +134,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/regplot.ipynb b/doc/_docstrings/regplot.ipynb new file mode 100644 index 0000000000..2a8f102091 --- /dev/null +++ b/doc/_docstrings/regplot.ipynb @@ -0,0 +1,251 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "611aed40-d120-4fbf-b1e6-9712ed8167fc", + "metadata": { + "tags": [ + "hide" + ] + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "import seaborn as sns\n", + "sns.set_theme()\n", + "mpg = sns.load_dataset(\"mpg\")" + ] + }, + { + "cell_type": "raw", + "id": "61bebade-0c45-4e99-9567-dfe0bc2dc6e1", + "metadata": {}, + "source": [ + "Plot the relationship between two variables in a DataFrame:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2f4107db-d89b-46ad-a4c6-9ba1181b2122", + "metadata": {}, + "outputs": [], + "source": [ + "sns.regplot(data=mpg, x=\"weight\", y=\"acceleration\")" + ] + }, + { + "cell_type": "raw", + "id": "146225d0-2e38-4b92-8e64-6d7f78311f40", + "metadata": {}, + "source": [ + "Fit a higher-order polynomial regression to capture nonlinear trends:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ba29488c-8a45-4387-bfb1-71a584fa1b3d", + "metadata": {}, + "outputs": [], + "source": [ + "sns.regplot(data=mpg, x=\"weight\", y=\"mpg\", order=2)" + ] + }, + { + "cell_type": "raw", + "id": "0ad71f54-b362-465e-8780-1d8b99ff2d51", + "metadata": {}, + "source": [ + "Alternatively, fit a log-linear regression:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "aae2acaa-ed07-4568-97d2-8665603eb7eb", + "metadata": {}, + "outputs": [], + "source": [ + "sns.regplot(data=mpg, x=\"displacement\", y=\"mpg\", logx=True)" + ] + }, + { + "cell_type": "raw", + "id": "eef37c8a-7190-465c-b963-076ec17e1b3a", + "metadata": {}, + "source": [ + "Or use a locally-weighted (LOWESS) smoother:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9276c469-72ea-4c36-9b7c-19ecba564376", + "metadata": {}, + "outputs": [], + "source": [ + "sns.regplot(data=mpg, x=\"horsepower\", y=\"mpg\", lowess=True)" + ] + }, + { + "cell_type": "raw", + "id": "d18f1534-598e-4f08-91dd-0c4020f30b00", + "metadata": {}, + "source": [ + "Fit a logistic regression when the response variable is binary:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "79ec9180-10c9-4910-9713-dcd1fdd266be", + "metadata": {}, + "outputs": [], + "source": [ + "sns.regplot(x=mpg[\"weight\"], y=mpg[\"origin\"].eq(\"usa\").rename(\"from_usa\"), logistic=True)" + ] + }, + { + "cell_type": "raw", + "id": "2e165783-d505-4acb-a20a-d22a49965c2b", + "metadata": {}, + "source": [ + "Fit a robust regression to downweight the influence of outliers:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fd5cf940-de8f-4230-8b04-5c650418f3c4", + "metadata": {}, + "outputs": [], + "source": [ + "sns.regplot(data=mpg, x=\"horsepower\", y=\"weight\", robust=True)" + ] + }, + { + "cell_type": "raw", + "id": "e7d43c4e-e819-4634-8269-cbf5de4a2f24", + "metadata": {}, + "source": [ + "Disable the confidence interval for faster plotting:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b21384ff-6395-4fa9-b7da-63e8a951d8a5", + "metadata": {}, + "outputs": [], + "source": [ + "sns.regplot(data=mpg, x=\"weight\", y=\"horsepower\", ci=None)" + ] + }, + { + "cell_type": "raw", + "id": "06e979ac-f418-4ead-bde1-ec684d0545ff", + "metadata": {}, + "source": [ + "Jitter the scatterplot when the `x` variable is discrete:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "543a8ace-a89e-4af9-bf6d-a8722ebdfac5", + "metadata": {}, + "outputs": [], + "source": [ + "sns.regplot(data=mpg, x=\"cylinders\", y=\"weight\", x_jitter=.15)" + ] + }, + { + "cell_type": "raw", + "id": "c3042eb2-0933-4886-9bff-88c276371516", + "metadata": {}, + "source": [ + "Or aggregate over the distinct `x` values:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "158c6e36-8858-415b-b78c-7d8d79879ee5", + "metadata": {}, + "outputs": [], + "source": [ + "sns.regplot(data=mpg, x=\"cylinders\", y=\"acceleration\", x_estimator=np.mean, order=2)" + ] + }, + { + "cell_type": "raw", + "id": "d9cefe7a-7f86-4353-95da-d7e72e65d4fc", + "metadata": {}, + "source": [ + "With a continuous `x` variable, bin and then aggregate:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1c48829b-2e3b-4e6b-9b1d-5ba69f713617", + "metadata": {}, + "outputs": [], + "source": [ + "sns.regplot(data=mpg, x=\"weight\", y=\"mpg\", x_bins=np.arange(2000, 5500, 250), order=2)" + ] + }, + { + "cell_type": "raw", + "id": "dfe5a36a-20b0-4e69-b986-fede8e1506cc", + "metadata": {}, + "source": [ + "Customize the appearance of various elements:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "df689a39-c5e1-4f7b-a8f9-8ffb09b95238", + "metadata": {}, + "outputs": [], + "source": [ + "sns.regplot(\n", + " data=mpg, x=\"weight\", y=\"horsepower\",\n", + " ci=99, marker=\"x\", color=\".3\", line_kws=dict(color=\"r\"),\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d625745b-3706-447b-9224-88e6cb1eb7f9", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "py310", + "language": "python", + "name": "py310" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/_docstrings/relplot.ipynb b/doc/_docstrings/relplot.ipynb index 9e782b756c..a7b36f1a44 100644 --- a/doc/_docstrings/relplot.ipynb +++ b/doc/_docstrings/relplot.ipynb @@ -240,9 +240,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -254,7 +254,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/residplot.ipynb b/doc/_docstrings/residplot.ipynb new file mode 100644 index 0000000000..3dd26d0168 --- /dev/null +++ b/doc/_docstrings/residplot.ipynb @@ -0,0 +1,113 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "776f8271-21ed-4707-a1ad-09d8c63ae95a", + "metadata": { + "tags": [ + "hide" + ] + }, + "outputs": [], + "source": [ + "import seaborn as sns\n", + "sns.set_theme()\n", + "mpg = sns.load_dataset(\"mpg\")" + ] + }, + { + "cell_type": "raw", + "id": "85717971-adc9-45b0-9c4b-3f022d96179c", + "metadata": {}, + "source": [ + "Pass `x` and `y` to see a scatter plot of the residuals after fitting a simple regression model:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5aea4655-fb51-4b51-b41d-4769de50e956", + "metadata": {}, + "outputs": [], + "source": [ + "sns.residplot(data=mpg, x=\"weight\", y=\"displacement\")" + ] + }, + { + "cell_type": "raw", + "id": "175b6287-9240-493f-94bc-9d18258e952b", + "metadata": {}, + "source": [ + "Structure in the residual plot can reveal a violation of linear regression assumptions:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "39aa84c2-d623-44be-9b0b-746f52b55fd4", + "metadata": {}, + "outputs": [], + "source": [ + "sns.residplot(data=mpg, x=\"horsepower\", y=\"mpg\")" + ] + }, + { + "cell_type": "raw", + "id": "bd9641e4-8df5-4751-b261-6443888fbbfe", + "metadata": {}, + "source": [ + "Remove higher-order trends to test whether that stabilizes the residuals:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "03a68199-1272-464b-8b85-7a309c22a4a6", + "metadata": {}, + "outputs": [], + "source": [ + "sns.residplot(data=mpg, x=\"horsepower\", y=\"mpg\", order=2)" + ] + }, + { + "cell_type": "raw", + "id": "b17750af-0393-4c53-8057-bf95d0de821a", + "metadata": {}, + "source": [ + "Adding a LOWESS curve can help reveal or emphasize structure:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "494359bd-47b2-426e-9c35-14b5351eec93", + "metadata": {}, + "outputs": [], + "source": [ + "sns.residplot(data=mpg, x=\"horsepower\", y=\"mpg\", lowess=True, line_kws=dict(color=\"r\"))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "py310", + "language": "python", + "name": "py310" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/_docstrings/rugplot.ipynb b/doc/_docstrings/rugplot.ipynb index 0819e2061c..4092dab06b 100644 --- a/doc/_docstrings/rugplot.ipynb +++ b/doc/_docstrings/rugplot.ipynb @@ -115,9 +115,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -129,7 +129,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/scatterplot.ipynb b/doc/_docstrings/scatterplot.ipynb index 4b51829f44..315a54845b 100644 --- a/doc/_docstrings/scatterplot.ipynb +++ b/doc/_docstrings/scatterplot.ipynb @@ -285,9 +285,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -299,7 +299,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/set_context.ipynb b/doc/_docstrings/set_context.ipynb index 882d577bab..07a5c091d4 100644 --- a/doc/_docstrings/set_context.ipynb +++ b/doc/_docstrings/set_context.ipynb @@ -82,9 +82,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -96,7 +96,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/set_style.ipynb b/doc/_docstrings/set_style.ipynb index 8f7f7b545d..25cdde23d6 100644 --- a/doc/_docstrings/set_style.ipynb +++ b/doc/_docstrings/set_style.ipynb @@ -63,9 +63,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -77,7 +77,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/set_theme.ipynb b/doc/_docstrings/set_theme.ipynb index 8b09f43936..add6eb2886 100644 --- a/doc/_docstrings/set_theme.ipynb +++ b/doc/_docstrings/set_theme.ipynb @@ -139,9 +139,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -153,7 +153,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/stripplot.ipynb b/doc/_docstrings/stripplot.ipynb index 34264355a7..d33034b5ba 100644 --- a/doc/_docstrings/stripplot.ipynb +++ b/doc/_docstrings/stripplot.ipynb @@ -291,9 +291,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -305,7 +305,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/swarmplot.ipynb b/doc/_docstrings/swarmplot.ipynb index 87b1eb653a..e90ee52115 100644 --- a/doc/_docstrings/swarmplot.ipynb +++ b/doc/_docstrings/swarmplot.ipynb @@ -263,9 +263,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -277,7 +277,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_docstrings/violinplot.ipynb b/doc/_docstrings/violinplot.ipynb index cbb4a8b10f..ebf5c4d963 100644 --- a/doc/_docstrings/violinplot.ipynb +++ b/doc/_docstrings/violinplot.ipynb @@ -171,9 +171,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -185,7 +185,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_tutorial/aesthetics.ipynb b/doc/_tutorial/aesthetics.ipynb index ecd78c0763..46f55d83ac 100644 --- a/doc/_tutorial/aesthetics.ipynb +++ b/doc/_tutorial/aesthetics.ipynb @@ -404,9 +404,9 @@ "metadata": { "celltoolbar": "Tags", "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -418,7 +418,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_tutorial/axis_grids.ipynb b/doc/_tutorial/axis_grids.ipynb index 051e4efcb6..2c3aacdc24 100644 --- a/doc/_tutorial/axis_grids.ipynb +++ b/doc/_tutorial/axis_grids.ipynb @@ -531,9 +531,9 @@ "metadata": { "celltoolbar": "Tags", "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -545,7 +545,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_tutorial/categorical.ipynb b/doc/_tutorial/categorical.ipynb index 7af75e398c..a1faa86a4f 100644 --- a/doc/_tutorial/categorical.ipynb +++ b/doc/_tutorial/categorical.ipynb @@ -520,9 +520,9 @@ "metadata": { "celltoolbar": "Tags", "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -534,7 +534,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_tutorial/color_palettes.ipynb b/doc/_tutorial/color_palettes.ipynb index 70afb7bf18..48cb84640f 100644 --- a/doc/_tutorial/color_palettes.ipynb +++ b/doc/_tutorial/color_palettes.ipynb @@ -982,9 +982,9 @@ "metadata": { "celltoolbar": "Tags", "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -996,7 +996,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_tutorial/data_structure.ipynb b/doc/_tutorial/data_structure.ipynb index 754460aebd..be7d55a026 100644 --- a/doc/_tutorial/data_structure.ipynb +++ b/doc/_tutorial/data_structure.ipynb @@ -475,9 +475,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -489,7 +489,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_tutorial/distributions.ipynb b/doc/_tutorial/distributions.ipynb index 5c1dca9ed8..7e47442b49 100644 --- a/doc/_tutorial/distributions.ipynb +++ b/doc/_tutorial/distributions.ipynb @@ -836,9 +836,9 @@ "metadata": { "celltoolbar": "Tags", "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -850,7 +850,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_tutorial/error_bars.ipynb b/doc/_tutorial/error_bars.ipynb index d8525ea842..1d34b35c75 100644 --- a/doc/_tutorial/error_bars.ipynb +++ b/doc/_tutorial/error_bars.ipynb @@ -347,9 +347,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -361,7 +361,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_tutorial/function_overview.ipynb b/doc/_tutorial/function_overview.ipynb index 08d69cea24..5096ca02e4 100644 --- a/doc/_tutorial/function_overview.ipynb +++ b/doc/_tutorial/function_overview.ipynb @@ -474,9 +474,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -488,7 +488,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_tutorial/introduction.ipynb b/doc/_tutorial/introduction.ipynb index 0955c375d5..c3f74f0fcf 100644 --- a/doc/_tutorial/introduction.ipynb +++ b/doc/_tutorial/introduction.ipynb @@ -447,9 +447,9 @@ "metadata": { "celltoolbar": "Tags", "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -461,7 +461,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_tutorial/objects_interface.ipynb b/doc/_tutorial/objects_interface.ipynb index 14bcd9c8f4..d5a0700ef1 100644 --- a/doc/_tutorial/objects_interface.ipynb +++ b/doc/_tutorial/objects_interface.ipynb @@ -1057,9 +1057,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -1071,7 +1071,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_tutorial/properties.ipynb b/doc/_tutorial/properties.ipynb index 36501bf819..acfd7fad61 100644 --- a/doc/_tutorial/properties.ipynb +++ b/doc/_tutorial/properties.ipynb @@ -916,6 +916,157 @@ ")" ] }, + { + "cell_type": "raw", + "id": "c2ca33db-df52-4958-889a-320b4833a0d7", + "metadata": {}, + "source": [ + "Text properties\n", + "---------------" + ] + }, + { + "cell_type": "raw", + "id": "b75af2fe-4d81-407c-9858-23362710f25f", + "metadata": {}, + "source": [ + ".. _horizontalalignment_property:\n", + "\n", + ".. _verticalalignment_property:\n", + "\n", + "halign, valign\n", + "~~~~~~~~~~~~~~\n", + "\n", + "The `halign` and `valign` properties control the *horizontal* and *vertical* alignment of text marks. The options for horizontal alignment are `'left'`, `'right'`, and `'center'`, while the options for vertical alignment are `'top'`, `'bottom'`, `'center'`, `'baseline'`, and `'center_baseline'`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e9588309-bee4-4b97-b428-eb91ea582105", + "metadata": { + "tags": [ + "hide-input" + ] + }, + "outputs": [], + "source": [ + "x = [\"left\", \"right\", \"top\", \"bottom\", \"baseline\", \"center\"]\n", + "ha = x[:2] + [\"center\"] * 4\n", + "va = [\"center_baseline\"] * 2 + x[2:]\n", + "y = np.zeros(len(x))\n", + "(\n", + " so.Plot(x=[f\"'{_x_}'\" for _x_ in x], y=y, halign=ha, valign=va)\n", + " .add(so.Dot(marker=\"+\", color=\"r\", alpha=.5, stroke=1, pointsize=24))\n", + " .add(so.Text(text=\"XyZ\", fontsize=14, offset=0))\n", + " .scale(y=so.Continuous().tick(at=[]), halign=None, valign=None)\n", + " .limit(x=(-.25, len(x) - .75))\n", + " .layout(size=(9, .6), engine=None)\n", + " .theme({\n", + " **axes_style(\"ticks\"),\n", + " **{f\"axes.spines.{side}\": False for side in [\"left\", \"right\", \"top\"]},\n", + " \"xtick.labelsize\": 12,\n", + " \"axes.xmargin\": .015,\n", + " \"ytick.labelsize\": 12,\n", + " })\n", + " .plot()\n", + ")" + ] + }, + { + "cell_type": "raw", + "id": "ea74c7e5-798b-47bc-bc18-9086902fb5c6", + "metadata": {}, + "source": [ + ".. _fontsize_property:\n", + "\n", + "fontsize\n", + "~~~~~~~~\n", + "\n", + "The `fontsize` property controls the size of textual marks. The value has point units:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c515b790-385d-4521-b14a-0769c1902928", + "metadata": { + "tags": [ + "hide-input" + ] + }, + "outputs": [], + "source": [ + "from string import ascii_uppercase\n", + "n = 26\n", + "s = np.arange(n) + 1\n", + "y = np.zeros(n)\n", + "t = list(ascii_uppercase[:n])\n", + "(\n", + " so.Plot(x=s, y=y, text=t, fontsize=s)\n", + " .add(so.Text())\n", + " .scale(x=so.Nominal(), y=so.Continuous().tick(at=[]))\n", + " .layout(size=(9, .5), engine=None)\n", + " .theme({\n", + " **axes_style(\"ticks\"),\n", + " **{f\"axes.spines.{side}\": False for side in [\"left\", \"right\", \"top\"]},\n", + " \"xtick.labelsize\": 12,\n", + " \"axes.xmargin\": .015,\n", + " \"ytick.labelsize\": 12,\n", + " })\n", + " .plot()\n", + ")" + ] + }, + { + "cell_type": "raw", + "id": "4b367f36-fb96-44fa-83a3-1cc66c7a3279", + "metadata": {}, + "source": [ + ".. _offset_property:\n", + "\n", + "offset\n", + "~~~~~~\n", + "\n", + "The `offset` property controls the spacing between a text mark and its anchor position. It applies when *not* using `center` alignment (i.e., when using left/right or top/bottom). The value has point units. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "25a49331-9580-4578-8bdb-d0d1829dde71", + "metadata": { + "tags": [ + "hide-input" + ] + }, + "outputs": [], + "source": [ + "n = 17\n", + "x = np.linspace(0, 8, n)\n", + "y = np.full(n, .5)\n", + "(\n", + " so.Plot(x=x, y=y, offset=x)\n", + " .add(so.Bar(color=\".6\", edgecolor=\"k\"))\n", + " .add(so.Text(text=\"abc\", valign=\"bottom\"))\n", + " .scale(\n", + " x=so.Continuous().tick(every=1, minor=1),\n", + " y=so.Continuous().tick(at=[]),\n", + " offset=None,\n", + " )\n", + " .limit(y=(0, 1.5))\n", + " .layout(size=(9, .5), engine=None)\n", + " .theme({\n", + " **axes_style(\"ticks\"),\n", + " **{f\"axes.spines.{side}\": False for side in [\"left\", \"right\", \"top\"]},\n", + " \"axes.xmargin\": .015,\n", + " \"xtick.labelsize\": 12,\n", + " \"ytick.labelsize\": 12,\n", + " })\n", + " .plot()\n", + ")" + ] + }, { "cell_type": "raw", "id": "77723ffd-2da3-4ece-a97a-3c00e864c743", @@ -932,6 +1083,11 @@ "source": [ ".. _property_property:\n", "\n", + "text\n", + "~~~~\n", + "\n", + "The `text` property is used to set the content of a textual mark. It is always used literally (not mapped), and cast to string when necessary.\n", + "\n", "group\n", "~~~~~\n", "\n", @@ -949,9 +1105,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -963,7 +1119,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_tutorial/regression.ipynb b/doc/_tutorial/regression.ipynb index d08fdbec76..72e7c68667 100644 --- a/doc/_tutorial/regression.ipynb +++ b/doc/_tutorial/regression.ipynb @@ -432,9 +432,9 @@ "metadata": { "celltoolbar": "Tags", "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -446,7 +446,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/_tutorial/relational.ipynb b/doc/_tutorial/relational.ipynb index face989a7f..4d8fb5f6b1 100644 --- a/doc/_tutorial/relational.ipynb +++ b/doc/_tutorial/relational.ipynb @@ -663,9 +663,9 @@ "metadata": { "celltoolbar": "Tags", "kernelspec": { - "display_name": "seaborn-py39-latest", + "display_name": "py310", "language": "python", - "name": "seaborn-py39-latest" + "name": "py310" }, "language_info": { "codemirror_mode": { @@ -677,7 +677,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/doc/api.rst b/doc/api.rst index 5ca95bf8c6..357d1e7f14 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -66,6 +66,15 @@ Mark objects Area Band +.. rubric:: Text marks + +.. autosummary:: + :toctree: generated/ + :template: object + :nosignatures: + + Text + Stat objects ~~~~~~~~~~~~ @@ -77,6 +86,7 @@ Stat objects Agg Est Hist + Perc PolyFit Move objects diff --git a/doc/conf.py b/doc/conf.py index f3a36eb936..81d2c1b9ff 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -139,6 +139,12 @@ "icon": "fab fa-github", "type": "fontawesome", }, + { + "name": "StackOverflow", + "url": "https://stackoverflow.com/tags/seaborn", + "icon": "fab fa-stack-overflow", + "type": "fontawesome", + }, { "name": "Twitter", "url": "https://twitter.com/michaelwaskom", diff --git a/doc/whatsnew/v0.12.1.rst b/doc/whatsnew/v0.12.1.rst index 283cf05d32..f1ed1bb85a 100644 --- a/doc/whatsnew/v0.12.1.rst +++ b/doc/whatsnew/v0.12.1.rst @@ -2,6 +2,22 @@ v0.12.1 (Unreleased) -------------------- +- |Feature| Added the :class:`objects.Text` mark (:pr:`3051`). + +- |Feature| Added the :class:`objects.Perc` stat (:pr:`3063`). + +- |Feature| The :class:`Band` and :class:`Range` marks will now cover the full extent of the data if `min` / `max` variables are not explicitly assigned or added in a transform (:pr:`3056`). + +- |Enhancement| The :class:`Jitter` move now applies a small amount of jitter by default (:pr:`3066`). + +- |Enhancement| Marks that sort along the orient axis (e.g. :class:`Line`) now use a stable algorithm (:pr:`3064`). + - |Enhancement| |Fix| Add a `label` parameter to :func:`pointplot`, which addresses a regression in 0.12.0 when :func:`pointplot` is passed to :class:`FacetGrid` (:pr:`3016`). -- |Fix| Make :class:`objects.PolyFit` robust to missing data (:pr:`3010`). \ No newline at end of file +- |Fix| Fixed a bug that caused an exception when more than two layers with the same mappings were added (:pr:`3055`). + +- |Fix| Make :class:`objects.PolyFit` robust to missing data (:pr:`3010`). + +- |Fix| Fixed a regression in :func:`kdeplot` where passing `cmap` for an unfilled bivariate plot would raise an exception (:pr:`3065`). + +- |Build| Seaborn no longer contains doctest-style examples, simplifying the testing infrastructure (:pr:`3034`). diff --git a/pyproject.toml b/pyproject.toml index f53063cb40..3a1a207406 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ requires-python = ">=3.7" dependencies = [ "numpy>=1.17", "pandas>=0.25", - "matplotlib>=3.1", + "matplotlib>=3.1,!=3.6.1", "typing_extensions; python_version < '3.8'", ] @@ -40,6 +40,7 @@ dev = [ "pytest-xdist", "flake8", "mypy", + "pandas-stubs", "pre-commit", ] docs = [ diff --git a/seaborn/_core/data.py b/seaborn/_core/data.py index 9de8be5fb8..535fafe83f 100644 --- a/seaborn/_core/data.py +++ b/seaborn/_core/data.py @@ -3,16 +3,14 @@ """ from __future__ import annotations -from collections import abc -import pandas as pd +from collections.abc import Mapping, Sized +from typing import cast -from typing import TYPE_CHECKING -if TYPE_CHECKING: - from pandas import DataFrame - from seaborn._core.typing import DataSource, VariableSpec +import pandas as pd +from pandas import DataFrame +from seaborn._core.typing import DataSource, VariableSpec, ColumnName -# TODO Repetition in the docstrings should be reduced with interpolation tools class PlotData: """ @@ -154,7 +152,7 @@ def _assign_variables( non-indexed vector datatypes that have a different length from `data`. """ - source_data: dict | DataFrame + source_data: Mapping | DataFrame frame: DataFrame names: dict[str, str | None] ids: dict[str, str | int] @@ -164,7 +162,7 @@ def _assign_variables( ids = {} given_data = data is not None - if given_data: + if data is not None: source_data = data else: # Data is optional; all variables can be defined as vectors @@ -208,7 +206,7 @@ def _assign_variables( ) if val_as_data_key: - + val = cast(ColumnName, val) if val in source_data: plot_data[key] = source_data[val] elif val in index: @@ -231,12 +229,12 @@ def _assign_variables( # Otherwise, assume the value somehow represents data # Ignore empty data structures - if isinstance(val, abc.Sized) and len(val) == 0: + if isinstance(val, Sized) and len(val) == 0: continue # If vector has no index, it must match length of data table if isinstance(data, pd.DataFrame) and not isinstance(val, pd.Series): - if isinstance(val, abc.Sized) and len(data) != len(val): + if isinstance(val, Sized) and len(data) != len(val): val_cls = val.__class__.__name__ err = ( f"Length of {val_cls} vectors must match length of `data`" diff --git a/seaborn/_core/groupby.py b/seaborn/_core/groupby.py index 3809a530f5..cc41566cde 100644 --- a/seaborn/_core/groupby.py +++ b/seaborn/_core/groupby.py @@ -1,6 +1,8 @@ """Simplified split-apply-combine paradigm on dataframes for internal use.""" from __future__ import annotations +from typing import cast, Iterable + import pandas as pd from seaborn._core.rules import categorical_order @@ -44,7 +46,9 @@ def __init__(self, order: list[str] | dict[str, list | None]): order = {k: None for k in order} self.order = order - def _get_groups(self, data: DataFrame) -> MultiIndex: + def _get_groups( + self, data: DataFrame + ) -> tuple[str | list[str], Index | MultiIndex]: """Return index with Cartesian product of ordered grouping variable levels.""" levels = {} for var, order in self.order.items(): @@ -54,10 +58,10 @@ def _get_groups(self, data: DataFrame) -> MultiIndex: levels[var] = order grouper: str | list[str] - groups: Index | MultiIndex | None + groups: Index | MultiIndex if not levels: grouper = [] - groups = None + groups = pd.Index([]) elif len(levels) > 1: grouper = list(levels) groups = pd.MultiIndex.from_product(levels.values(), names=grouper) @@ -115,7 +119,8 @@ def apply( for key in groups: if key in parts: if isinstance(grouper, list): - group_ids = dict(zip(grouper, key)) + # Implies that we had a MultiIndex so key is iterable + group_ids = dict(zip(grouper, cast(Iterable, key))) else: group_ids = {grouper: key} stack.append(parts[key].assign(**group_ids)) diff --git a/seaborn/_core/moves.py b/seaborn/_core/moves.py index 2c814be87d..1efba41e63 100644 --- a/seaborn/_core/moves.py +++ b/seaborn/_core/moves.py @@ -1,12 +1,15 @@ from __future__ import annotations from dataclasses import dataclass -from typing import ClassVar, Callable, Optional, Union +from typing import ClassVar, Callable, Optional, Union, cast import numpy as np from pandas import DataFrame from seaborn._core.groupby import GroupBy from seaborn._core.scales import Scale +from seaborn._core.typing import Default + +default = Default() @dataclass @@ -24,26 +27,34 @@ def __call__( @dataclass class Jitter(Move): """ - Random displacement of marks along either or both axes to reduce overplotting. + Random displacement along one or both axes to reduce overplotting. + + Parameters + ---------- + width : float + Magnitude of jitter, relative to mark width, along the orientation axis. + If not provided, the default value will be 0 when `x` or `y` are set, otherwise + there will be a small amount of jitter applied by default. + x : float + Magnitude of jitter, in data units, along the x axis. + y : float + Magnitude of jitter, in data units, along the y axis. + + Examples + -------- + .. include:: ../docstrings/objects.Jitter.rst + """ - width: float = 0 + width: float | Default = default x: float = 0 y: float = 0 - - seed: Optional[int] = None - - # TODO what is the best way to have a reasonable default? - # The problem is that "reasonable" seems dependent on the mark + seed: int | None = None def __call__( self, data: DataFrame, groupby: GroupBy, orient: str, scales: dict[str, Scale], ) -> DataFrame: - # TODO is it a problem that GroupBy is not used for anything here? - # Should we type it as optional? - data = data.copy() - rng = np.random.default_rng(self.seed) def jitter(data, col, scale): @@ -51,8 +62,13 @@ def jitter(data, col, scale): offsets = noise * scale return data[col] + offsets + if self.width is default: + width = 0.0 if self.x or self.y else 0.2 + else: + width = cast(float, self.width) + if self.width: - data[orient] = jitter(data, orient, self.width * data["width"]) + data[orient] = jitter(data, orient, width * data["width"]) if self.x: data["x"] = jitter(data, "x", self.x) if self.y: diff --git a/seaborn/_core/plot.py b/seaborn/_core/plot.py index 2ac220bc7b..4f0290a494 100644 --- a/seaborn/_core/plot.py +++ b/seaborn/_core/plot.py @@ -15,7 +15,7 @@ from cycler import cycler import pandas as pd -from pandas import DataFrame, Series +from pandas import DataFrame, Series, Index import matplotlib as mpl from matplotlib.axes import Axes from matplotlib.artist import Artist @@ -29,7 +29,13 @@ from seaborn._core.subplots import Subplots from seaborn._core.groupby import GroupBy from seaborn._core.properties import PROPERTIES, Property -from seaborn._core.typing import DataSource, VariableSpec, VariableSpecList, OrderSpec +from seaborn._core.typing import ( + DataSource, + VariableSpec, + VariableSpecList, + OrderSpec, + Default, +) from seaborn._core.rules import categorical_order from seaborn._compat import set_scale_obj, set_layout_engine from seaborn.rcmod import axes_style, plotting_context @@ -47,6 +53,9 @@ from typing_extensions import TypedDict +default = Default() + + # ---- Definitions for internal specs --------------------------------- # @@ -79,13 +88,6 @@ class PairSpec(TypedDict, total=False): # --- Local helpers ---------------------------------------------------------------- -class Default: - def __repr__(self): - return "" - - -default = Default() - @contextmanager def theme_context(params: dict[str, Any]) -> Generator: @@ -258,7 +260,8 @@ def _resolve_positionals( if name in variables: raise TypeError(f"`{name}` given by both name and position.") # Keep coordinates at the front of the variables dict - variables = {name: var, **variables} + # Cast type because we know this isn't a DataSource at this point + variables = {name: cast(VariableSpec, var), **variables} return data, variables @@ -331,8 +334,11 @@ def _variables(self) -> list[str]: + list(self._facet_spec.get("variables", [])) ) for layer in self._layers: - variables.extend(c for c in layer["vars"] if c not in variables) - return variables + variables.extend(v for v in layer["vars"] if v not in variables) + + # Coerce to str in return to appease mypy; we know these will only + # ever be strings but I don't think we can type a DataFrame that way yet + return [str(v) for v in variables] def on(self, target: Axes | SubFigure | Figure) -> Plot: """ @@ -558,7 +564,7 @@ def facet( .. include:: ../docstrings/objects.Plot.facet.rst """ - variables = {} + variables: dict[str, VariableSpec] = {} if col is not None: variables["col"] = col if row is not None: @@ -1106,7 +1112,7 @@ def _compute_stats(self, spec: Plot, layers: list[Layer]) -> None: for axis, var in zip(*pairings): if axis != var: df = df.rename(columns={var: axis}) - drop_cols = [x for x in df if re.match(rf"{axis}\d+", x)] + drop_cols = [x for x in df if re.match(rf"{axis}\d+", str(x))] df = df.drop(drop_cols, axis=1) scales[axis] = scales[var] @@ -1176,7 +1182,7 @@ def _setup_scales( for layer in layers: variables.extend(layer["data"].frame.columns) for df in layer["data"].frames.values(): - variables.extend(v for v in df if v not in variables) + variables.extend(str(v) for v in df if v not in variables) variables = [v for v in variables if v not in self._scales] for var in variables: @@ -1307,7 +1313,7 @@ def get_order(var): if "width" in mark._mappable_props: width = mark._resolve(df, "width", None) else: - width = df.get("width", 0.8) # TODO what default + width = 0.8 if "width" not in df else df["width"] # TODO what default? if orient in df: df["width"] = width * scales[orient]._spacing(df[orient]) @@ -1321,7 +1327,7 @@ def get_order(var): # TODO unlike width, we might not want to add baseline to data # if the mark doesn't use it. Practically, there is a concern about # Mark abstraction like Area / Ribbon - baseline = df.get("baseline", 0) + baseline = 0 if "baseline" not in df else df["baseline"] df["baseline"] = baseline if move is not None: @@ -1351,33 +1357,11 @@ def get_order(var): if layer["legend"]: self._update_legend_contents(p, mark, data, scales) - def _scale_coords(self, subplots: list[dict], df: DataFrame) -> DataFrame: - # TODO stricter type on subplots - - coord_cols = [c for c in df if re.match(r"^[xy]\D*$", c)] - out_df = ( - df - .copy(deep=False) - .drop(coord_cols, axis=1) - .reindex(df.columns, axis=1) # So unscaled columns retain their place - ) - - for view in subplots: - view_df = self._filter_subplot_data(df, view) - axes_df = view_df[coord_cols] - with pd.option_context("mode.use_inf_as_null", True): - axes_df = axes_df.dropna() - for var, values in axes_df.items(): - scale = view[f"{var[0]}scale"] - out_df.loc[values.index, var] = scale(values) - - return out_df - def _unscale_coords( self, subplots: list[dict], df: DataFrame, orient: str, ) -> DataFrame: # TODO do we still have numbers in the variable name at this point? - coord_cols = [c for c in df if re.match(r"^[xy]\D*$", c)] + coord_cols = [c for c in df if re.match(r"^[xy]\D*$", str(c))] drop_cols = [*coord_cols, "width"] if "width" in df else coord_cols out_df = ( df @@ -1391,11 +1375,11 @@ def _unscale_coords( axes_df = view_df[coord_cols] for var, values in axes_df.items(): - axis = getattr(view["ax"], f"{var[0]}axis") + axis = getattr(view["ax"], f"{str(var)[0]}axis") # TODO see https://github.com/matplotlib/matplotlib/issues/22713 transform = axis.get_transform().inverted().transform inverted = transform(values) - out_df.loc[values.index, var] = inverted + out_df.loc[values.index, str(var)] = inverted if var == orient and "width" in view_df: width = view_df["width"] @@ -1442,12 +1426,12 @@ def _generate_pairings( for axis, var in zip("xy", (x, y)): if axis != var: out_df = out_df.rename(columns={var: axis}) - cols = [col for col in out_df if re.match(rf"{axis}\d+", col)] + cols = [col for col in out_df if re.match(rf"{axis}\d+", str(col))] out_df = out_df.drop(cols, axis=1) yield subplots, out_df, scales - def _get_subplot_index(self, df: DataFrame, subplot: dict) -> DataFrame: + def _get_subplot_index(self, df: DataFrame, subplot: dict) -> Index: dims = df.columns.intersection(["col", "row"]) if dims.empty: @@ -1553,11 +1537,12 @@ def _update_legend_contents( ) -> None: """Add legend artists / labels for one layer in the plot.""" if data.frame.empty and data.frames: - legend_vars = set() + legend_vars: list[str] = [] for frame in data.frames.values(): - legend_vars.update(frame.columns.intersection(scales)) + frame_vars = frame.columns.intersection(list(scales)) + legend_vars.extend(v for v in frame_vars if v not in legend_vars) else: - legend_vars = data.frame.columns.intersection(scales) + legend_vars = list(data.frame.columns.intersection(list(scales))) # First pass: Identify the values that will be shown for each variable schema: list[tuple[ @@ -1579,12 +1564,15 @@ def _update_legend_contents( schema.append(entry) # Second pass, generate an artist corresponding to each value - contents = [] + contents: list[tuple[tuple[str, str | int], Any, list[str]]] = [] for key, variables, (values, labels) in schema: artists = [] for val in values: - artists.append(mark._legend_artist(variables, val, scales)) - contents.append((key, artists, labels)) + artist = mark._legend_artist(variables, val, scales) + if artist is not None: + artists.append(artist) + if artists: + contents.append((key, artists, labels)) self._legend_contents.extend(contents) @@ -1596,7 +1584,7 @@ def _make_legend(self, p: Plot) -> None: merged_contents: dict[ tuple[str, str | int], tuple[list[Artist], list[str]], ] = {} - for key, artists, labels in self._legend_contents: + for key, new_artists, labels in self._legend_contents: # Key is (name, id); we need the id to resolve variable uniqueness, # but will need the name in the next step to title the legend if key in merged_contents: @@ -1605,11 +1593,11 @@ def _make_legend(self, p: Plot) -> None: for i, artist in enumerate(existing_artists): # Matplotlib accepts a tuple of artists and will overlay them if isinstance(artist, tuple): - artist += artist[i], + artist += new_artists[i], else: - existing_artists[i] = artist, artists[i] + existing_artists[i] = artist, new_artists[i] else: - merged_contents[key] = artists.copy(), labels + merged_contents[key] = new_artists.copy(), labels # TODO explain loc = "center right" if self._pyplot else "center left" diff --git a/seaborn/_core/properties.py b/seaborn/_core/properties.py index 173fd45efb..cd10e260ef 100644 --- a/seaborn/_core/properties.py +++ b/seaborn/_core/properties.py @@ -298,6 +298,23 @@ class Alpha(IntervalProperty): # TODO validate / enforce that output is in [0, 1] +class Offset(IntervalProperty): + """Offset for edge-aligned text, in point units.""" + _default_range = 0, 5 + _legend = False + + +class FontSize(IntervalProperty): + """Font size for textual marks, in points.""" + _legend = False + + @property + def default_range(self) -> tuple[float, float]: + """Min and max values used by default for semantic mapping.""" + base = mpl.rcParams["font.size"] + return base * .5, base * 2 + + # =================================================================================== # # Properties defined by arbitrary objects with inherently nominal scaling # =================================================================================== # @@ -496,6 +513,24 @@ def _get_dash_pattern(style: str | DashPattern) -> DashPatternWithOffset: return offset, dashes +class TextAlignment(ObjectProperty): + legend = False + + +class HorizontalAlignment(TextAlignment): + + def _default_values(self, n: int) -> list: + vals = itertools.cycle(["left", "right"]) + return [next(vals) for _ in range(n)] + + +class VerticalAlignment(TextAlignment): + + def _default_values(self, n: int) -> list: + vals = itertools.cycle(["top", "bottom"]) + return [next(vals) for _ in range(n)] + + # =================================================================================== # # Properties with RGB(A) color values # =================================================================================== # @@ -751,6 +786,11 @@ def mapping(x): "edgestyle": LineStyle, "edgecolor": Color, "edgealpha": Alpha, + "text": Property, + "halign": HorizontalAlignment, + "valign": VerticalAlignment, + "offset": Offset, + "fontsize": FontSize, "xmin": Coordinate, "xmax": Coordinate, "ymin": Coordinate, diff --git a/seaborn/_core/rules.py b/seaborn/_core/rules.py index d378fb2dc2..fea910342b 100644 --- a/seaborn/_core/rules.py +++ b/seaborn/_core/rules.py @@ -147,7 +147,7 @@ def categorical_order(vector: Series, order: list | None = None) -> list: order = list(vector.cat.categories) else: order = list(filter(pd.notnull, vector.unique())) - if variable_type(order) == "numeric": + if variable_type(pd.Series(order)) == "numeric": order.sort() return order diff --git a/seaborn/_core/scales.py b/seaborn/_core/scales.py index 368d17a143..bbd71ec1b5 100644 --- a/seaborn/_core/scales.py +++ b/seaborn/_core/scales.py @@ -4,7 +4,7 @@ from collections.abc import Sequence from dataclasses import dataclass from functools import partial -from typing import Any, Callable, Tuple, Optional, Union, ClassVar +from typing import Any, Callable, Tuple, Optional, ClassVar import numpy as np import matplotlib as mpl @@ -39,13 +39,15 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: from seaborn._core.properties import Property - from numpy.typing import ArrayLike + from numpy.typing import ArrayLike, NDArray - Transforms = Tuple[ + TransFuncs = Tuple[ Callable[[ArrayLike], ArrayLike], Callable[[ArrayLike], ArrayLike] ] - Pipeline = Sequence[Optional[Callable[[Union[Series, ArrayLike]], ArrayLike]]] + # TODO Reverting typing to Any as it was proving too complicated to + # work out the right way to communicate the types to mypy. Revisit! + Pipeline = Sequence[Optional[Callable[[Any], Any]]] class Scale: @@ -101,20 +103,24 @@ def _setup( def __call__(self, data: Series) -> ArrayLike: + trans_data: Series | NDArray | list + # TODO sometimes we need to handle scalars (e.g. for Line) # but what is the best way to do that? scalar_data = np.isscalar(data) if scalar_data: - data = np.array([data]) + trans_data = np.array([data]) + else: + trans_data = data for func in self._pipeline: if func is not None: - data = func(data) + trans_data = func(trans_data) if scalar_data: - data = data[0] - - return data + return trans_data[0] + else: + return trans_data @staticmethod def _identity(): @@ -319,7 +325,7 @@ def _setup( forward, inverse = new._get_transform() - mpl_scale = new._get_scale(data.name, forward, inverse) + mpl_scale = new._get_scale(str(data.name), forward, inverse) if axis is None: axis = PseudoAxis(mpl_scale) @@ -411,7 +417,7 @@ class Continuous(ContinuousBase): A numeric scale supporting norms and functional transforms. """ values: tuple | str | None = None - trans: str | Transforms | None = None + trans: str | TransFuncs | None = None # TODO Add this to deal with outliers? # outside: Literal["keep", "drop", "clip"] = "keep" @@ -530,7 +536,7 @@ def label( return new def _parse_for_log_params( - self, trans: str | Transforms | None + self, trans: str | TransFuncs | None ) -> tuple[float | None, float | None]: log_base = symlog_thresh = None @@ -877,7 +883,7 @@ def get_majorticklocs(self): # Transform function creation -def _make_identity_transforms() -> Transforms: +def _make_identity_transforms() -> TransFuncs: def identity(x): return x @@ -885,7 +891,7 @@ def identity(x): return identity, identity -def _make_logit_transforms(base: float = None) -> Transforms: +def _make_logit_transforms(base: float = None) -> TransFuncs: log, exp = _make_log_transforms(base) @@ -900,8 +906,9 @@ def expit(x): return logit, expit -def _make_log_transforms(base: float | None = None) -> Transforms: +def _make_log_transforms(base: float | None = None) -> TransFuncs: + fs: TransFuncs if base is None: fs = np.log, np.exp elif base == 2: @@ -913,18 +920,18 @@ def forward(x): return np.log(x) / np.log(base) fs = forward, partial(np.power, base) - def log(x): + def log(x: ArrayLike) -> ArrayLike: with np.errstate(invalid="ignore", divide="ignore"): return fs[0](x) - def exp(x): + def exp(x: ArrayLike) -> ArrayLike: with np.errstate(invalid="ignore", divide="ignore"): return fs[1](x) return log, exp -def _make_symlog_transforms(c: float = 1, base: float = 10) -> Transforms: +def _make_symlog_transforms(c: float = 1, base: float = 10) -> TransFuncs: # From https://iopscience.iop.org/article/10.1088/0957-0233/24/2/027001 @@ -944,7 +951,7 @@ def symexp(x): return symlog, symexp -def _make_sqrt_transforms() -> Transforms: +def _make_sqrt_transforms() -> TransFuncs: def sqrt(x): return np.sign(x) * np.sqrt(np.abs(x)) @@ -955,7 +962,7 @@ def square(x): return sqrt, square -def _make_power_transforms(exp: float) -> Transforms: +def _make_power_transforms(exp: float) -> TransFuncs: def forward(x): return np.sign(x) * np.power(np.abs(x), exp) diff --git a/seaborn/_core/typing.py b/seaborn/_core/typing.py index 15ac4ec07d..5295b995e4 100644 --- a/seaborn/_core/typing.py +++ b/seaborn/_core/typing.py @@ -1,16 +1,22 @@ from __future__ import annotations +from datetime import date, datetime, timedelta from typing import Any, Optional, Union, Mapping, Tuple, List, Dict from collections.abc import Hashable, Iterable + from numpy import ndarray # TODO use ArrayLike? -from pandas import DataFrame, Series, Index +from pandas import DataFrame, Series, Index, Timestamp, Timedelta from matplotlib.colors import Colormap, Normalize + +ColumnName = Union[ + str, bytes, date, datetime, timedelta, bool, complex, Timestamp, Timedelta +] Vector = Union[Series, Index, ndarray] -PaletteSpec = Union[str, list, dict, Colormap, None] -VariableSpec = Union[Hashable, Vector, None] + +VariableSpec = Union[ColumnName, Vector, None] VariableSpecList = Union[List[VariableSpec], Index, None] -# TODO can we better unify the VarType object and the VariableType alias? + DataSource = Union[DataFrame, Mapping[Hashable, Vector], None] OrderSpec = Union[Iterable, None] # TODO technically str is iterable @@ -18,7 +24,16 @@ # TODO for discrete mappings, it would be ideal to use a parameterized type # as the dict values / list entries should be of specific type(s) for each method +PaletteSpec = Union[str, list, dict, Colormap, None] DiscreteValueSpec = Union[dict, list, None] ContinuousValueSpec = Union[ Tuple[float, float], List[float], Dict[Any, float], None, ] + + +class Default: + def __repr__(self): + return "" + + +default = Default() diff --git a/seaborn/_marks/area.py b/seaborn/_marks/area.py index 243a1572f5..7514a6d13b 100644 --- a/seaborn/_marks/area.py +++ b/seaborn/_marks/area.py @@ -59,7 +59,7 @@ def _postprocess_artist(self, artist, ax, orient): def _get_verts(self, data, orient): dv = {"x": "y", "y": "x"}[orient] - data = data.sort_values(orient) + data = data.sort_values(orient, kind="mergesort") verts = np.concatenate([ data[[orient, f"{dv}min"]].to_numpy(), data[[orient, f"{dv}max"]].to_numpy()[::-1], @@ -162,4 +162,9 @@ class Band(AreaBase, Mark): def _standardize_coordinate_parameters(self, data, orient): # dv = {"x": "y", "y": "x"}[orient] # TODO assert that all(ymax >= ymin)? + # TODO what if only one exist? + other = {"x": "y", "y": "x"}[orient] + if not set(data.columns) & {f"{other}min", f"{other}max"}: + agg = {f"{other}min": (other, "min"), f"{other}max": (other, "max")} + data = data.groupby(orient).agg(**agg).reset_index() return data diff --git a/seaborn/_marks/base.py b/seaborn/_marks/base.py index 1fc2b9f2ef..87e0216d9d 100644 --- a/seaborn/_marks/base.py +++ b/seaborn/_marks/base.py @@ -217,8 +217,8 @@ def _plot( def _legend_artist( self, variables: list[str], value: Any, scales: dict[str, Scale], ) -> Artist: - # TODO return some sensible default? - raise NotImplementedError + + return None def resolve_properties( diff --git a/seaborn/_marks/line.py b/seaborn/_marks/line.py index ba69acfe99..d1ae76e816 100644 --- a/seaborn/_marks/line.py +++ b/seaborn/_marks/line.py @@ -60,7 +60,7 @@ def _plot(self, split_gen, scales, orient): vals["marker"] = vals["marker"]._marker if self._sort: - data = data.sort_values(orient) + data = data.sort_values(orient, kind="mergesort") artist_kws = self.artist_kws.copy() self._handle_capstyle(artist_kws, vals) @@ -184,7 +184,7 @@ def _setup_lines(self, split_gen, scales, orient): vals["color"] = resolve_color(self, keys, scales=scales) if self._sort: - data = data.sort_values(orient) + data = data.sort_values(orient, kind="mergesort") # Column stack to avoid block consolidation xy = np.column_stack([data["x"], data["y"]]) @@ -204,8 +204,9 @@ def _plot(self, split_gen, scales, orient): # Handle datalim update manually # https://github.com/matplotlib/matplotlib/issues/23129 ax.add_collection(lines, autolim=False) - xy = np.concatenate(ax_data["segments"]) - ax.update_datalim(xy) + if ax_data["segments"]: + xy = np.concatenate(ax_data["segments"]) + ax.update_datalim(xy) def _legend_artist(self, variables, value, scales): @@ -270,9 +271,16 @@ def _setup_lines(self, split_gen, scales, orient): "linestyles": [], } + # TODO better checks on what variables we have + vals = resolve_properties(self, keys, scales) vals["color"] = resolve_color(self, keys, scales=scales) + # TODO what if only one exist? + if not set(data.columns) & {f"{other}min", f"{other}max"}: + agg = {f"{other}min": (other, "min"), f"{other}max": (other, "max")} + data = data.groupby(orient).agg(**agg).reset_index() + cols = [orient, f"{other}min", f"{other}max"] data = data[cols].melt(orient, value_name=other)[["x", "y"]] segments = [d.to_numpy() for _, d in data.groupby(orient)] diff --git a/seaborn/_marks/text.py b/seaborn/_marks/text.py new file mode 100644 index 0000000000..58d757c1ac --- /dev/null +++ b/seaborn/_marks/text.py @@ -0,0 +1,76 @@ +from __future__ import annotations +from collections import defaultdict +from dataclasses import dataclass + +import numpy as np +import matplotlib as mpl +from matplotlib.transforms import ScaledTranslation + +from seaborn._marks.base import ( + Mark, + Mappable, + MappableFloat, + MappableString, + MappableColor, + resolve_properties, + resolve_color, + document_properties, +) + + +@document_properties +@dataclass +class Text(Mark): + """ + A textual mark to annotate or represent data values. + + Examples + -------- + .. include:: ../docstrings/objects.Text.rst + + """ + text: MappableString = Mappable("") + color: MappableColor = Mappable("k") + alpha: MappableFloat = Mappable(1) + fontsize: MappableFloat = Mappable(rc="font.size") + halign: MappableString = Mappable("center") + valign: MappableString = Mappable("center_baseline") + offset: MappableFloat = Mappable(4) + + def _plot(self, split_gen, scales, orient): + + ax_data = defaultdict(list) + + for keys, data, ax in split_gen(): + + vals = resolve_properties(self, keys, scales) + color = resolve_color(self, keys, "", scales) + + halign = vals["halign"] + valign = vals["valign"] + fontsize = vals["fontsize"] + offset = vals["offset"] / 72 + + offset_trans = ScaledTranslation( + {"right": -offset, "left": +offset}.get(halign, 0), + {"top": -offset, "bottom": +offset, "baseline": +offset}.get(valign, 0), + ax.figure.dpi_scale_trans, + ) + + for row in data.to_dict("records"): + artist = mpl.text.Text( + x=row["x"], + y=row["y"], + text=str(row.get("text", vals["text"])), + color=color, + fontsize=fontsize, + horizontalalignment=halign, + verticalalignment=valign, + transform=ax.transData + offset_trans, + **self.artist_kws, + ) + ax.add_artist(artist) + ax_data[ax].append([row["x"], row["y"]]) + + for ax, ax_vals in ax_data.items(): + ax.update_datalim(np.array(ax_vals)) diff --git a/seaborn/_statistics.py b/seaborn/_statistics.py index a19fdea7f1..7fe4fbe7b2 100644 --- a/seaborn/_statistics.py +++ b/seaborn/_statistics.py @@ -194,6 +194,8 @@ def __call__(self, x1, x2=None, weights=None): return self._eval_bivariate(x1, x2, weights) +# Note: we no longer use this for univariate histograms in histplot, +# preferring _stats.Hist. We'll deprecate this once we have a bivariate Stat class. class Histogram: """Univariate and bivariate histogram estimator.""" def __init__( diff --git a/seaborn/_stats/aggregation.py b/seaborn/_stats/aggregation.py index 73da54bdad..0dffba6455 100644 --- a/seaborn/_stats/aggregation.py +++ b/seaborn/_stats/aggregation.py @@ -9,7 +9,6 @@ from seaborn._core.groupby import GroupBy from seaborn._stats.base import Stat from seaborn._statistics import EstimateAggregator - from seaborn._core.typing import Vector @@ -83,7 +82,7 @@ def __call__( boot_kws = {"n_boot": self.n_boot, "seed": self.seed} engine = EstimateAggregator(self.func, self.errorbar, **boot_kws) - var = {"x": "y", "y": "x"}.get(orient) + var = {"x": "y", "y": "x"}[orient] res = ( groupby .apply(data, self._process, var, engine) diff --git a/seaborn/_stats/base.py b/seaborn/_stats/base.py index c538812493..6584a8e61a 100644 --- a/seaborn/_stats/base.py +++ b/seaborn/_stats/base.py @@ -1,7 +1,8 @@ """Base module for statistical transformations.""" from __future__ import annotations +from collections.abc import Iterable from dataclasses import dataclass -from typing import ClassVar +from typing import ClassVar, Any from typing import TYPE_CHECKING if TYPE_CHECKING: @@ -28,6 +29,18 @@ class Stat: # value on the orient axis, but we would not in the latter case. group_by_orient: ClassVar[bool] = False + def _check_param_one_of(self, param: Any, options: Iterable[Any]) -> None: + """Raise when parameter value is not one of a specified set.""" + value = getattr(self, param) + if value not in options: + *most, last = options + option_str = ", ".join(f"{x!r}" for x in most[:-1]) + f" or {last!r}" + err = " ".join([ + f"The `{param}` parameter for `{self.__class__.__name__}` must be", + f"one of {option_str}; not {value!r}.", + ]) + raise ValueError(err) + def __call__( self, data: DataFrame, diff --git a/seaborn/_stats/histogram.py b/seaborn/_stats/histogram.py index 85abed1036..59b7b12f2e 100644 --- a/seaborn/_stats/histogram.py +++ b/seaborn/_stats/histogram.py @@ -1,6 +1,6 @@ from __future__ import annotations from dataclasses import dataclass -from functools import partial +from warnings import warn import numpy as np import pandas as pd @@ -17,22 +17,76 @@ class Hist(Stat): """ Bin observations, count them, and optionally normalize or cumulate. - """ - stat: str = "count" # TODO how to do validation on this arg? + Parameters + ---------- + stat : str + Aggregate statistic to compute in each bin: + + - `count`: the number of observations + - `density`: normalize so that the total area of the histogram equals 1 + - `percent`: normalize so that bar heights sum to 100 + - `probability` or `proportion`: normalize so that bar heights sum to 1 + - `frequency`: divide the number of observations by the bin width + + bins : str, int, or ArrayLike + Generic parameter that can be the name of a reference rule, the number + of bins, or the bin breaks. Passed to :func:`numpy.histogram_bin_edges`. + binwidth : float + Width of each bin; overrides `bins` but can be used with `binrange`. + binrange : (min, max) + Lowest and highest value for bin edges; can be used with either + `bins` (when a number) or `binwidth`. Defaults to data extremes. + common_norm : bool or list of variables + When not `False`, the normalization is applied across groups. Use + `True` to normalize across all groups, or pass variable name(s) that + define normalization groups. + common_bins : bool or list of variables + When not `False`, the same bins are used for all groups. Use `True` to + share bins across all groups, or pass variable name(s) to share within. + cumulative : bool + If True, cumulate the bin values. + discrete : bool + If True, set `binwidth` and `binrange` so that bins have unit width and + are centered on integer values + + Notes + ----- + + The choice of bins for computing and plotting a histogram can exert + substantial influence on the insights that one is able to draw from the + visualization. If the bins are too large, they may erase important features. + On the other hand, bins that are too small may be dominated by random + variability, obscuring the shape of the true underlying distribution. The + default bin size is determined using a reference rule that depends on the + sample size and variance. This works well in many cases, (i.e., with + "well-behaved" data) but it fails in others. It is always a good to try + different bin sizes to be sure that you are not missing something important. + This function allows you to specify bins in several different ways, such as + by setting the total number of bins to use, the width of each bin, or the + specific locations where the bins should break. + + + Examples + -------- + .. include:: ../docstrings/objects.Hist.rst + + """ + stat: str = "count" bins: str | int | ArrayLike = "auto" binwidth: float | None = None binrange: tuple[float, float] | None = None common_norm: bool | list[str] = True common_bins: bool | list[str] = True cumulative: bool = False - - # TODO Require this to be set here or have interface with scale? - # Q: would Discrete() scale imply binwidth=1 or bins centered on integers? discrete: bool = False - # TODO Note that these methods are mostly copied from _statistics.Histogram, - # but it only computes univariate histograms. We should reconcile the code. + def __post_init__(self): + + stat_options = [ + "count", "density", "percent", "probability", "proportion", "frequency" + ] + self._check_param_one_of("stat", stat_options) def _define_bin_edges(self, vals, weight, bins, binwidth, binrange, discrete): """Inner function that takes bin parameters as arguments.""" @@ -58,14 +112,14 @@ def _define_bin_edges(self, vals, weight, bins, binwidth, binrange, discrete): def _define_bin_params(self, data, orient, scale_type): """Given data, return numpy.histogram parameters to define bins.""" vals = data[orient] - weight = data.get("weight", None) + weights = data.get("weight", None) # TODO We'll want this for ordinal / discrete scales too # (Do we need discrete as a parameter or just infer from scale?) discrete = self.discrete or scale_type == "nominal" bin_edges = self._define_bin_edges( - vals, weight, self.bins, self.binwidth, self.binrange, discrete, + vals, weights, self.bins, self.binwidth, self.binrange, discrete, ) if isinstance(self.bins, (str, int)): @@ -85,24 +139,19 @@ def _get_bins_and_eval(self, data, orient, groupby, scale_type): def _eval(self, data, orient, bin_kws): vals = data[orient] - weight = data.get("weight", None) + weights = data.get("weight", None) density = self.stat == "density" - hist, bin_edges = np.histogram( - vals, **bin_kws, weights=weight, density=density, - ) - - width = np.diff(bin_edges) - pos = bin_edges[:-1] + width / 2 - other = {"x": "y", "y": "x"}[orient] + hist, edges = np.histogram(vals, **bin_kws, weights=weights, density=density) - return pd.DataFrame({orient: pos, other: hist, "space": width}) + width = np.diff(edges) + center = edges[:-1] + width / 2 - def _normalize(self, data, orient): + return pd.DataFrame({orient: center, "count": hist, "space": width}) - other = "y" if orient == "x" else "x" - hist = data[other] + def _normalize(self, data): + hist = data["count"] if self.stat == "probability" or self.stat == "proportion": hist = hist.astype(float) / hist.sum() elif self.stat == "percent": @@ -116,13 +165,10 @@ def _normalize(self, data, orient): else: hist = hist.cumsum() - return data.assign(**{other: hist}) + return data.assign(**{self.stat: hist}) def __call__(self, data, groupby, orient, scales): - # TODO better to do this as an isinstance check? - # We are only asking about Nominal scales now, - # but presumably would apply to Ordinal too? scale_type = scales[orient].__class__.__name__.lower() grouping_vars = [v for v in data if v in groupby.order] if not grouping_vars or self.common_bins is True: @@ -133,23 +179,30 @@ def __call__(self, data, groupby, orient, scales): bin_groupby = GroupBy(grouping_vars) else: bin_groupby = GroupBy(self.common_bins) + undefined = set(self.common_bins) - set(grouping_vars) + if undefined: + param = f"{self.__class__.__name__}.common_bins" + names = ", ".join(f"{x!r}" for x in undefined) + msg = f"Undefined variables(s) passed to `{param}`: {names}." + warn(msg) data = bin_groupby.apply( data, self._get_bins_and_eval, orient, groupby, scale_type, ) - # TODO Make this an option? - # (This needs to be tested if enabled, and maybe should be in _eval) - # other = {"x": "y", "y": "x"}[orient] - # data = data[data[other] > 0] - if not grouping_vars or self.common_norm is True: - data = self._normalize(data, orient) + data = self._normalize(data) else: if self.common_norm is False: norm_grouper = grouping_vars else: norm_grouper = self.common_norm - normalize = partial(self._normalize, orient=orient) - data = GroupBy(norm_grouper).apply(data, normalize) + undefined = set(self.common_norm) - set(grouping_vars) + if undefined: + param = f"{self.__class__.__name__}.common_norm" + names = ", ".join(f"{x!r}" for x in undefined) + msg = f"Undefined variables(s) passed to `{param}`: {names}." + warn(msg) + data = GroupBy(norm_grouper).apply(data, self._normalize) - return data + other = {"x": "y", "y": "x"}[orient] + return data.assign(**{other: data[self.stat]}) diff --git a/seaborn/_stats/order.py b/seaborn/_stats/order.py new file mode 100644 index 0000000000..a0780c1976 --- /dev/null +++ b/seaborn/_stats/order.py @@ -0,0 +1,78 @@ + +from __future__ import annotations +from dataclasses import dataclass +from typing import ClassVar, cast +try: + from typing import Literal +except ImportError: + from typing_extensions import Literal # type: ignore + +import numpy as np +from pandas import DataFrame + +from seaborn._core.scales import Scale +from seaborn._core.groupby import GroupBy +from seaborn._stats.base import Stat +from seaborn.external.version import Version + + +# From https://github.com/numpy/numpy/blob/main/numpy/lib/function_base.pyi +_MethodKind = Literal[ + "inverted_cdf", + "averaged_inverted_cdf", + "closest_observation", + "interpolated_inverted_cdf", + "hazen", + "weibull", + "linear", + "median_unbiased", + "normal_unbiased", + "lower", + "higher", + "midpoint", + "nearest", +] + + +@dataclass +class Perc(Stat): + """ + Replace observations with percentile values. + + Parameters + ---------- + k : list of numbers or int + If a list of numbers, this gives the percentiles (in [0, 100]) to compute. + If an integer, compute `k` evenly-spaced percentiles between 0 and 100. + For example, `k=5` computes the 0, 25, 50, 75, and 100th percentiles. + method : str + Method for interpolating percentiles between observed datapoints. + See :func:`numpy.percentile` for valid options and more information. + + Examples + -------- + .. include:: ../docstrings/objects.Perc.rst + + """ + k: int | list[float] = 5 + method: str = "linear" + + group_by_orient: ClassVar[bool] = True + + def _percentile(self, data: DataFrame, var: str) -> DataFrame: + + k = list(np.linspace(0, 100, self.k)) if isinstance(self.k, int) else self.k + method = cast(_MethodKind, self.method) + values = data[var].dropna() + if Version(np.__version__) < Version("1.22.0"): + res = np.percentile(values, k, interpolation=method) # type: ignore + else: + res = np.percentile(data[var].dropna(), k, method=method) + return DataFrame({var: res, "percentile": k}) + + def __call__( + self, data: DataFrame, groupby: GroupBy, orient: str, scales: dict[str, Scale], + ) -> DataFrame: + + var = {"x": "y", "y": "x"}[orient] + return groupby.apply(data, self._percentile, var) diff --git a/seaborn/axisgrid.py b/seaborn/axisgrid.py index 5a80694225..7a56310bde 100644 --- a/seaborn/axisgrid.py +++ b/seaborn/axisgrid.py @@ -742,7 +742,7 @@ def map(self, func, *args, **kwargs): plot_data = data_ijk[list(args)] if self._dropna: plot_data = plot_data.dropna() - plot_args = [v for k, v in plot_data.iteritems()] + plot_args = [v for k, v in plot_data.items()] # Some matplotlib functions don't handle pandas objects correctly if func_module.startswith("matplotlib"): diff --git a/seaborn/categorical.py b/seaborn/categorical.py index 87d5346b4c..09b1bd59b7 100644 --- a/seaborn/categorical.py +++ b/seaborn/categorical.py @@ -289,7 +289,7 @@ def plot_strips( jitter_move = jitterer(size=len(sub_data)) if len(sub_data) > 1 else 0 adjusted_data = sub_data[self.cat_axis] + dodge_move + jitter_move - sub_data.loc[:, self.cat_axis] = adjusted_data + sub_data[self.cat_axis] = adjusted_data for var in "xy": if self._log_scaled(var): @@ -346,7 +346,7 @@ def plot_swarms( dodge_move = offsets[sub_data["hue"].map(self._hue_map.levels.index)] if not sub_data.empty: - sub_data.loc[:, self.cat_axis] = sub_data[self.cat_axis] + dodge_move + sub_data[self.cat_axis] = sub_data[self.cat_axis] + dodge_move for var in "xy": if self._log_scaled(var): @@ -466,7 +466,7 @@ def establish_variables(self, x=None, y=None, hue=None, data=None, group_label = data.columns.name # Convert to a list of arrays, the common representation - iter_data = plot_data.iteritems() + iter_data = plot_data.items() plot_data = [np.asarray(s, float) for k, s in iter_data] # Option 1b: @@ -2096,10 +2096,10 @@ def plot(self, ax, box_kws, flier_kws, line_kws): stat_api_params=dedent("""\ estimator : string or callable that maps vector -> scalar, optional Statistical function to estimate within each categorical bin. - errorbar : string, (string, number) tuple, or callable + errorbar : string, (string, number) tuple, callable or None Name of errorbar method (either "ci", "pi", "se", or "sd"), or a tuple with a method name and a level parameter, or a function that maps from a - vector to a (min, max) interval. + vector to a (min, max) interval, or None to hide errorbar. n_boot : int, optional Number of bootstrap samples used to compute confidence intervals. units : name of variable in ``data`` or vector data, optional diff --git a/seaborn/distributions.py b/seaborn/distributions.py index a6f6ab5cc3..a4526e0edf 100644 --- a/seaborn/distributions.py +++ b/seaborn/distributions.py @@ -16,11 +16,12 @@ from ._oldcore import ( VectorPlotter, ) -from ._statistics import ( - KDE, - Histogram, - ECDF, -) + +# We have moved univariate histogram computation over to the new Hist class, +# but still use the older Histogram for bivariate computation. +from ._statistics import ECDF, Histogram, KDE +from ._stats.histogram import Hist + from .axisgrid import ( FacetGrid, _facet_docs, @@ -246,30 +247,26 @@ def _resolve_multiple(self, curves, multiple): # Find column groups that are nested within col/row variables column_groups = {} - for i, keyd in enumerate(map(dict, curves.columns.tolist())): + for i, keyd in enumerate(map(dict, curves.columns)): facet_key = keyd.get("col", None), keyd.get("row", None) column_groups.setdefault(facet_key, []) column_groups[facet_key].append(i) baselines = curves.copy() - for cols in column_groups.values(): + for col_idxs in column_groups.values(): + cols = curves.columns[col_idxs] - norm_constant = curves.iloc[:, cols].sum(axis="columns") + norm_constant = curves[cols].sum(axis="columns") # Take the cumulative sum to stack - curves.iloc[:, cols] = curves.iloc[:, cols].cumsum(axis="columns") + curves[cols] = curves[cols].cumsum(axis="columns") # Normalize by row sum to fill if multiple == "fill": - curves.iloc[:, cols] = (curves - .iloc[:, cols] - .div(norm_constant, axis="index")) + curves[cols] = curves[cols].div(norm_constant, axis="index") # Define where each segment starts - baselines.iloc[:, cols] = (curves - .iloc[:, cols] - .shift(1, axis=1) - .fillna(0)) + baselines[cols] = curves[cols].shift(1, axis=1).fillna(0) if multiple == "dodge": @@ -423,19 +420,20 @@ def plot_univariate_histogram( if estimate_kws["stat"] == "count": common_norm = False + orient = self.data_variable + # Now initialize the Histogram estimator - estimator = Histogram(**estimate_kws) + estimator = Hist(**estimate_kws) histograms = {} # Do pre-compute housekeeping related to multiple groups all_data = self.comp_data.dropna() all_weights = all_data.get("weights", None) - if set(self.variables) - {"x", "y"}: # Check if we'll have multiple histograms + multiple_histograms = set(self.variables) - {"x", "y"} + if multiple_histograms: if common_bins: - estimator.define_bin_params( - all_data[self.data_variable], weights=all_weights - ) + bin_kws = estimator._define_bin_params(all_data, orient, None) else: common_norm = False @@ -464,17 +462,26 @@ def plot_univariate_histogram( # Prepare the relevant data key = tuple(sub_vars.items()) - observations = sub_data[self.data_variable] + orient = self.data_variable if "weights" in self.variables: - weights = sub_data["weights"] - part_weight = weights.sum() + sub_data["weight"] = sub_data.pop("weights") + part_weight = sub_data["weight"].sum() else: - weights = None part_weight = len(sub_data) # Do the histogram computation - heights, edges = estimator(observations, weights=weights) + if not (multiple_histograms and common_bins): + bin_kws = estimator._define_bin_params(sub_data, orient, None) + res = estimator._normalize(estimator._eval(sub_data, orient, bin_kws)) + heights = res[estimator.stat].to_numpy() + widths = res["space"].to_numpy() + edges = res[orient].to_numpy() - widths / 2 + + # Convert edges back to original units for plotting + if self._log_scaled(self.data_variable): + widths = np.power(10, edges + widths) - np.power(10, edges) + edges = np.power(10, edges) # Rescale the smoothed curve to match the histogram if kde and key in densities: @@ -482,17 +489,12 @@ def plot_univariate_histogram( if estimator.cumulative: hist_norm = heights.max() else: - hist_norm = (heights * np.diff(edges)).sum() + hist_norm = (heights * widths).sum() densities[key] *= hist_norm - # Convert edges back to original units for plotting - if self._log_scaled(self.data_variable): - edges = np.power(10, edges) - # Pack the histogram data and metadata together - orig_widths = np.diff(edges) - widths = shrink * orig_widths - edges = edges[:-1] + (1 - shrink) / 2 * orig_widths + edges = edges + (1 - shrink) / 2 * widths + widths *= shrink index = pd.MultiIndex.from_arrays([ pd.Index(edges, name="edges"), pd.Index(widths, name="widths"), @@ -1128,14 +1130,6 @@ def plot_bivariate_density( for k, d in densities.items() } - # Get a default single color from the attribute cycle - if self.ax is None: - default_color = "C0" if color is None else color - else: - scout, = self.ax.plot([], color=color) - default_color = scout.get_color() - scout.remove() - # Define the coloring of the contours if "hue" in self.variables: for param in ["cmap", "colors"]: @@ -1148,10 +1142,10 @@ def plot_bivariate_density( # Work out a default coloring of the contours coloring_given = set(contour_kws) & {"cmap", "colors"} if fill and not coloring_given: - cmap = self._cmap_from_color(default_color) + cmap = self._cmap_from_color(color) contour_kws["cmap"] = cmap if not fill and not coloring_given: - contour_kws["colors"] = [default_color] + contour_kws["colors"] = [color] # Use our internal colormap lookup cmap = contour_kws.pop("cmap", None) diff --git a/seaborn/matrix.py b/seaborn/matrix.py index 2e60d5839f..76f22b89af 100644 --- a/seaborn/matrix.py +++ b/seaborn/matrix.py @@ -439,105 +439,8 @@ def heatmap( Examples -------- - Plot a heatmap for a numpy array: + .. include:: ../docstrings/heatmap.rst - .. plot:: - :context: close-figs - - >>> import numpy as np; np.random.seed(0) - >>> import seaborn as sns; sns.set_theme() - >>> uniform_data = np.random.rand(10, 12) - >>> ax = sns.heatmap(uniform_data) - - Change the limits of the colormap: - - .. plot:: - :context: close-figs - - >>> ax = sns.heatmap(uniform_data, vmin=0, vmax=1) - - Plot a heatmap for data centered on 0 with a diverging colormap: - - .. plot:: - :context: close-figs - - >>> normal_data = np.random.randn(10, 12) - >>> ax = sns.heatmap(normal_data, center=0) - - Plot a dataframe with meaningful row and column labels: - - .. plot:: - :context: close-figs - - >>> flights = sns.load_dataset("flights") - >>> flights = flights.pivot("month", "year", "passengers") - >>> ax = sns.heatmap(flights) - - Annotate each cell with the numeric value using integer formatting: - - .. plot:: - :context: close-figs - - >>> ax = sns.heatmap(flights, annot=True, fmt="d") - - Add lines between each cell: - - .. plot:: - :context: close-figs - - >>> ax = sns.heatmap(flights, linewidths=.5) - - Use a different colormap: - - .. plot:: - :context: close-figs - - >>> ax = sns.heatmap(flights, cmap="YlGnBu") - - Center the colormap at a specific value: - - .. plot:: - :context: close-figs - - >>> ax = sns.heatmap(flights, center=flights.loc["Jan", 1955]) - - Plot every other column label and don't plot row labels: - - .. plot:: - :context: close-figs - - >>> data = np.random.randn(50, 20) - >>> ax = sns.heatmap(data, xticklabels=2, yticklabels=False) - - Don't draw a colorbar: - - .. plot:: - :context: close-figs - - >>> ax = sns.heatmap(flights, cbar=False) - - Use different axes for the colorbar: - - .. plot:: - :context: close-figs - - >>> grid_kws = {"height_ratios": (.9, .05), "hspace": .3} - >>> f, (ax, cbar_ax) = plt.subplots(2, gridspec_kw=grid_kws) - >>> ax = sns.heatmap(flights, ax=ax, - ... cbar_ax=cbar_ax, - ... cbar_kws={"orientation": "horizontal"}) - - Use a mask to plot only part of a matrix - - .. plot:: - :context: close-figs - - >>> corr = np.corrcoef(np.random.randn(10, 200)) - >>> mask = np.zeros_like(corr) - >>> mask[np.triu_indices_from(mask)] = True - >>> with sns.axes_style("white"): - ... f, ax = plt.subplots(figsize=(7, 5)) - ... ax = sns.heatmap(corr, mask=mask, vmax=.3, square=True) """ # Initialize the plotter object plotter = _HeatMapper(data, vmin, vmax, cmap, center, robust, annot, fmt, @@ -1340,70 +1243,8 @@ def clustermap( Examples -------- - Plot a clustered heatmap: - - .. plot:: - :context: close-figs - - >>> import seaborn as sns; sns.set_theme(color_codes=True) - >>> iris = sns.load_dataset("iris") - >>> species = iris.pop("species") - >>> g = sns.clustermap(iris) - - Change the size and layout of the figure: - - .. plot:: - :context: close-figs - - >>> g = sns.clustermap(iris, - ... figsize=(7, 5), - ... row_cluster=False, - ... dendrogram_ratio=(.1, .2), - ... cbar_pos=(0, .2, .03, .4)) - - Add colored labels to identify observations: - - .. plot:: - :context: close-figs - - >>> lut = dict(zip(species.unique(), "rbg")) - >>> row_colors = species.map(lut) - >>> g = sns.clustermap(iris, row_colors=row_colors) - - Use a different colormap and adjust the limits of the color range: - - .. plot:: - :context: close-figs - - >>> g = sns.clustermap(iris, cmap="mako", vmin=0, vmax=10) - - Use a different similarity metric: - - .. plot:: - :context: close-figs - - >>> g = sns.clustermap(iris, metric="correlation") - - Use a different clustering method: - - .. plot:: - :context: close-figs - - >>> g = sns.clustermap(iris, method="single") - - Standardize the data within the columns: - - .. plot:: - :context: close-figs - - >>> g = sns.clustermap(iris, standard_scale=1) - - Normalize the data within the rows: - - .. plot:: - :context: close-figs + .. include:: ../docstrings/clustermap.rst - >>> g = sns.clustermap(iris, z_score=0, cmap="vlag") """ if _no_scipy: raise RuntimeError("clustermap requires scipy to be available") diff --git a/seaborn/objects.py b/seaborn/objects.py index f2d6520199..90fc6530a5 100644 --- a/seaborn/objects.py +++ b/seaborn/objects.py @@ -31,13 +31,15 @@ from seaborn._marks.base import Mark # noqa: F401 from seaborn._marks.area import Area, Band # noqa: F401 from seaborn._marks.bar import Bar, Bars # noqa: F401 -from seaborn._marks.line import Line, Lines, Path, Paths, Range # noqa: F401 from seaborn._marks.dot import Dot, Dots # noqa: F401 +from seaborn._marks.line import Line, Lines, Path, Paths, Range # noqa: F401 +from seaborn._marks.text import Text # noqa: F401 from seaborn._stats.base import Stat # noqa: F401 from seaborn._stats.aggregation import Agg, Est # noqa: F401 -from seaborn._stats.regression import PolyFit # noqa: F401 from seaborn._stats.histogram import Hist # noqa: F401 +from seaborn._stats.order import Perc # noqa: F401 +from seaborn._stats.regression import PolyFit # noqa: F401 from seaborn._core.moves import Dodge, Jitter, Norm, Shift, Stack, Move # noqa: F401 diff --git a/seaborn/palettes.py b/seaborn/palettes.py index 9b0fd58ce1..3306b0f2e9 100644 --- a/seaborn/palettes.py +++ b/seaborn/palettes.py @@ -91,6 +91,34 @@ def _repr_html_(self): return html +def _patch_colormap_display(): + """Simplify the rich display of matplotlib color maps in a notebook.""" + def _repr_png_(self): + """Generate a PNG representation of the Colormap.""" + import io + from PIL import Image + import numpy as np + IMAGE_SIZE = (400, 50) + X = np.tile(np.linspace(0, 1, IMAGE_SIZE[0]), (IMAGE_SIZE[1], 1)) + pixels = self(X, bytes=True) + png_bytes = io.BytesIO() + Image.fromarray(pixels).save(png_bytes, format='png') + return png_bytes.getvalue() + + def _repr_html_(self): + """Generate an HTML representation of the Colormap.""" + import base64 + png_bytes = self._repr_png_() + png_base64 = base64.b64encode(png_bytes).decode('ascii') + return ('') + + mpl.colors.Colormap._repr_png_ = _repr_png_ + mpl.colors.Colormap._repr_html_ = _repr_html_ + + def color_palette(palette=None, n_colors=None, desat=None, as_cmap=False): """Return a list of colors or continuous colormap defining a palette. @@ -125,11 +153,11 @@ def color_palette(palette=None, n_colors=None, desat=None, as_cmap=False): desat : float, optional Proportion to desaturate each color by. as_cmap : bool - If True, return a :class:`matplotlib.colors.Colormap`. + If True, return a :class:`matplotlib.colors.ListedColormap`. Returns ------- - list of RGB tuples or :class:`matplotlib.colors.Colormap` + list of RGB tuples or :class:`matplotlib.colors.ListedColormap` See Also -------- @@ -228,25 +256,36 @@ def color_palette(palette=None, n_colors=None, desat=None, as_cmap=False): def hls_palette(n_colors=6, h=.01, l=.6, s=.65, as_cmap=False): # noqa - """Get a set of evenly spaced colors in HLS hue space. + """ + Return hues with constant lightness and saturation in the HLS system. + + The hues are evenly sampled along a circular path. The resulting palette will be + appropriate for categorical or cyclical data. - h, l, and s should be between 0 and 1 + The `h`, `l`, and `s` values should be between 0 and 1. + + .. note:: + While the separation of the resulting colors will be mathematically + constant, the HLS system does not construct a perceptually-uniform space, + so their apparent intensity will vary. Parameters ---------- - n_colors : int - number of colors in the palette + Number of colors in the palette. h : float - first hue + The value of the first hue. l : float - lightness + The lightness value. s : float - saturation + The saturation intensity. + as_cmap : bool + If True, return a matplotlib colormap object. Returns ------- - list of RGB tuples or :class:`matplotlib.colors.Colormap` + palette + list of RGB tuples or :class:`matplotlib.colors.ListedColormap` See Also -------- @@ -254,35 +293,7 @@ def hls_palette(n_colors=6, h=.01, l=.6, s=.65, as_cmap=False): # noqa Examples -------- - - Create a palette of 10 colors with the default parameters: - - .. plot:: - :context: close-figs - - >>> import seaborn as sns; sns.set_theme() - >>> sns.palplot(sns.hls_palette(10)) - - Create a palette of 10 colors that begins at a different hue value: - - .. plot:: - :context: close-figs - - >>> sns.palplot(sns.hls_palette(10, h=.5)) - - Create a palette of 10 colors that are darker than the default: - - .. plot:: - :context: close-figs - - >>> sns.palplot(sns.hls_palette(10, l=.4)) - - Create a palette of 10 colors that are less saturated than the default: - - .. plot:: - :context: close-figs - - >>> sns.palplot(sns.hls_palette(10, s=.4)) + .. include:: ../docstrings/hls_palette.rst """ if as_cmap: @@ -299,62 +310,42 @@ def hls_palette(n_colors=6, h=.01, l=.6, s=.65, as_cmap=False): # noqa def husl_palette(n_colors=6, h=.01, s=.9, l=.65, as_cmap=False): # noqa - """Get a set of evenly spaced colors in HUSL hue space. + """ + Return hues with constant lightness and saturation in the HUSL system. - h, s, and l should be between 0 and 1 + The hues are evenly sampled along a circular path. The resulting palette will be + appropriate for categorical or cyclical data. + + The `h`, `l`, and `s` values should be between 0 and 1. + + This function is similar to :func:`hls_palette`, but it uses a nonlinear color + space that is more perceptually uniform. Parameters ---------- - n_colors : int - number of colors in the palette + Number of colors in the palette. h : float - first hue - s : float - saturation + The value of the first hue. l : float - lightness + The lightness value. + s : float + The saturation intensity. + as_cmap : bool + If True, return a matplotlib colormap object. Returns ------- - list of RGB tuples or :class:`matplotlib.colors.Colormap` + palette + list of RGB tuples or :class:`matplotlib.colors.ListedColormap` See Also -------- - hls_palette : Make a palette using evently spaced circular hues in the - HSL system. + hls_palette : Make a palette using evenly spaced hues in the HSL system. Examples -------- - - Create a palette of 10 colors with the default parameters: - - .. plot:: - :context: close-figs - - >>> import seaborn as sns; sns.set_theme() - >>> sns.palplot(sns.husl_palette(10)) - - Create a palette of 10 colors that begins at a different hue value: - - .. plot:: - :context: close-figs - - >>> sns.palplot(sns.husl_palette(10, h=.5)) - - Create a palette of 10 colors that are darker than the default: - - .. plot:: - :context: close-figs - - >>> sns.palplot(sns.husl_palette(10, l=.4)) - - Create a palette of 10 colors that are less saturated than the default: - - .. plot:: - :context: close-figs - - >>> sns.palplot(sns.husl_palette(10, s=.4)) + .. include:: ../docstrings/husl_palette.rst """ if as_cmap: @@ -373,17 +364,16 @@ def husl_palette(n_colors=6, h=.01, s=.9, l=.65, as_cmap=False): # noqa def mpl_palette(name, n_colors=6, as_cmap=False): - """Return discrete colors from a matplotlib palette. + """ + Return a palette or colormap from the matplotlib registry. - Note that this handles the qualitative colorbrewer palettes - properly, although if you ask for more colors than a particular - qualitative palette can provide you will get fewer than you are - expecting. In contrast, asking for qualitative color brewer palettes - using :func:`color_palette` will return the expected number of colors, - but they will cycle. + For continuous palettes, evenly-spaced discrete samples are chosen while + excluding the minimum and maximum value in the colormap to provide better + contrast at the extremes. - If you are using the IPython notebook, you can also use the function - :func:`choose_colorbrewer_palette` to interactively select palettes. + For qualitative palettes (e.g. those from colorbrewer), exact values are + indexed (rather than interpolated), but fewer than `n_colors` can be returned + if the palette does not define that many. Parameters ---------- @@ -394,39 +384,11 @@ def mpl_palette(name, n_colors=6, as_cmap=False): Returns ------- - list of RGB tuples or :class:`matplotlib.colors.Colormap` + list of RGB tuples or :class:`matplotlib.colors.ListedColormap` Examples -------- - - Create a qualitative colorbrewer palette with 8 colors: - - .. plot:: - :context: close-figs - - >>> import seaborn as sns; sns.set_theme() - >>> sns.palplot(sns.mpl_palette("Set2", 8)) - - Create a sequential colorbrewer palette: - - .. plot:: - :context: close-figs - - >>> sns.palplot(sns.mpl_palette("Blues")) - - Create a diverging palette: - - .. plot:: - :context: close-figs - - >>> sns.palplot(sns.mpl_palette("seismic", 8)) - - Create a "dark" sequential palette: - - .. plot:: - :context: close-figs - - >>> sns.palplot(sns.mpl_palette("GnBu_d")) + .. include: ../docstrings/mpl_palette.rst """ if name.endswith("_d"): @@ -491,14 +453,15 @@ def dark_palette(color, n_colors=6, reverse=False, as_cmap=False, input="rgb"): reverse : bool, optional if True, reverse the direction of the blend as_cmap : bool, optional - If True, return a :class:`matplotlib.colors.Colormap`. + If True, return a :class:`matplotlib.colors.ListedColormap`. input : {'rgb', 'hls', 'husl', xkcd'} Color space to interpret the input color. The first three options apply to tuple inputs and the latter applies to string inputs. Returns ------- - list of RGB tuples or :class:`matplotlib.colors.Colormap` + palette + list of RGB tuples or :class:`matplotlib.colors.ListedColormap` See Also -------- @@ -507,38 +470,7 @@ def dark_palette(color, n_colors=6, reverse=False, as_cmap=False, input="rgb"): Examples -------- - - Generate a palette from an HTML color: - - .. plot:: - :context: close-figs - - >>> import seaborn as sns; sns.set_theme() - >>> sns.palplot(sns.dark_palette("purple")) - - Generate a palette that decreases in lightness: - - .. plot:: - :context: close-figs - - >>> sns.palplot(sns.dark_palette("seagreen", reverse=True)) - - Generate a palette from an HUSL-space seed: - - .. plot:: - :context: close-figs - - >>> sns.palplot(sns.dark_palette((260, 75, 60), input="husl")) - - Generate a colormap object: - - .. plot:: - :context: close-figs - - >>> from numpy import arange - >>> x = arange(25).reshape(5, 5) - >>> cmap = sns.dark_palette("#2ecc71", as_cmap=True) - >>> ax = sns.heatmap(x, cmap=cmap) + .. include:: ../docstrings/dark_palette.rst """ rgb = _color_to_rgb(color, input) @@ -552,34 +484,32 @@ def dark_palette(color, n_colors=6, reverse=False, as_cmap=False, input="rgb"): def light_palette(color, n_colors=6, reverse=False, as_cmap=False, input="rgb"): """Make a sequential palette that blends from light to ``color``. - This kind of palette is good for data that range between relatively - uninteresting low values and interesting high values. - The ``color`` parameter can be specified in a number of ways, including all options for defining a color in matplotlib and several additional color spaces that are handled by seaborn. You can also use the database of named colors from the XKCD color survey. - If you are using the IPython notebook, you can also choose this palette + If you are using a Jupyter notebook, you can also choose this palette interactively with the :func:`choose_light_palette` function. Parameters ---------- color : base color for high values - hex code, html color name, or tuple in ``input`` space. + hex code, html color name, or tuple in `input` space. n_colors : int, optional number of colors in the palette reverse : bool, optional if True, reverse the direction of the blend as_cmap : bool, optional - If True, return a :class:`matplotlib.colors.Colormap`. + If True, return a :class:`matplotlib.colors.ListedColormap`. input : {'rgb', 'hls', 'husl', xkcd'} Color space to interpret the input color. The first three options apply to tuple inputs and the latter applies to string inputs. Returns ------- - list of RGB tuples or :class:`matplotlib.colors.Colormap` + palette + list of RGB tuples or :class:`matplotlib.colors.ListedColormap` See Also -------- @@ -588,38 +518,7 @@ def light_palette(color, n_colors=6, reverse=False, as_cmap=False, input="rgb"): Examples -------- - - Generate a palette from an HTML color: - - .. plot:: - :context: close-figs - - >>> import seaborn as sns; sns.set_theme() - >>> sns.palplot(sns.light_palette("purple")) - - Generate a palette that increases in lightness: - - .. plot:: - :context: close-figs - - >>> sns.palplot(sns.light_palette("seagreen", reverse=True)) - - Generate a palette from an HUSL-space seed: - - .. plot:: - :context: close-figs - - >>> sns.palplot(sns.light_palette((260, 75, 60), input="husl")) - - Generate a colormap object: - - .. plot:: - :context: close-figs - - >>> from numpy import arange - >>> x = arange(25).reshape(5, 5) - >>> cmap = sns.light_palette("#2ecc71", as_cmap=True) - >>> ax = sns.heatmap(x, cmap=cmap) + .. include:: ../docstrings/light_palette.rst """ rgb = _color_to_rgb(color, input) @@ -652,11 +551,12 @@ def diverging_palette(h_neg, h_pos, s=75, l=50, sep=1, n=6, # noqa center : {"light", "dark"}, optional Whether the center of the palette is light or dark as_cmap : bool, optional - If True, return a :class:`matplotlib.colors.Colormap`. + If True, return a :class:`matplotlib.colors.ListedColormap`. Returns ------- - list of RGB tuples or :class:`matplotlib.colors.Colormap` + palette + list of RGB tuples or :class:`matplotlib.colors.ListedColormap` See Also -------- @@ -665,39 +565,7 @@ def diverging_palette(h_neg, h_pos, s=75, l=50, sep=1, n=6, # noqa Examples -------- - - Generate a blue-white-red palette: - - .. plot:: - :context: close-figs - - >>> import seaborn as sns; sns.set_theme() - >>> sns.palplot(sns.diverging_palette(240, 10, n=9)) - - Generate a brighter green-white-purple palette: - - .. plot:: - :context: close-figs - - >>> sns.palplot(sns.diverging_palette(150, 275, s=80, l=55, n=9)) - - Generate a blue-black-red palette: - - .. plot:: - :context: close-figs - - >>> sns.palplot(sns.diverging_palette(250, 15, s=75, l=40, - ... n=9, center="dark")) - - Generate a colormap object: - - .. plot:: - :context: close-figs - - >>> from numpy import arange - >>> x = arange(25).reshape(5, 5) - >>> cmap = sns.diverging_palette(220, 20, as_cmap=True) - >>> ax = sns.heatmap(x, cmap=cmap) + .. include: ../docstrings/diverging_palette.rst """ palfunc = dict(dark=dark_palette, light=light_palette)[center] @@ -715,16 +583,21 @@ def blend_palette(colors, n_colors=6, as_cmap=False, input="rgb"): Parameters ---------- - colors : sequence of colors in various formats interpreted by ``input`` - hex code, html color name, or tuple in ``input`` space. + colors : sequence of colors in various formats interpreted by `input` + hex code, html color name, or tuple in `input` space. n_colors : int, optional Number of colors in the palette. as_cmap : bool, optional - If True, return a :class:`matplotlib.colors.Colormap`. + If True, return a :class:`matplotlib.colors.ListedColormap`. Returns ------- - list of RGB tuples or :class:`matplotlib.colors.Colormap` + palette + list of RGB tuples or :class:`matplotlib.colors.ListedColormap` + + Examples + -------- + .. include: ../docstrings/blend_palette.rst """ colors = [_color_to_rgb(color, input) for color in colors] @@ -741,18 +614,17 @@ def xkcd_palette(colors): See xkcd for the full list of colors: https://xkcd.com/color/rgb/ - This is just a simple wrapper around the ``seaborn.xkcd_rgb`` dictionary. + This is just a simple wrapper around the `seaborn.xkcd_rgb` dictionary. Parameters ---------- colors : list of strings - List of keys in the ``seaborn.xkcd_rgb`` dictionary. + List of keys in the `seaborn.xkcd_rgb` dictionary. Returns ------- - palette : seaborn color palette - Returns the list of colors as RGB tuples in an object that behaves like - other seaborn color palettes. + palette + A list of colors as RGB tuples. See Also -------- @@ -769,18 +641,17 @@ def crayon_palette(colors): Colors are taken from here: https://en.wikipedia.org/wiki/List_of_Crayola_crayon_colors - This is just a simple wrapper around the ``seaborn.crayons`` dictionary. + This is just a simple wrapper around the `seaborn.crayons` dictionary. Parameters ---------- colors : list of strings - List of keys in the ``seaborn.crayons`` dictionary. + List of keys in the `seaborn.crayons` dictionary. Returns ------- - palette : seaborn color palette - Returns the list of colors as rgb tuples in an object that behaves like - other seaborn color palettes. + palette + A list of colors as RGB tuples. See Also -------- @@ -803,20 +674,19 @@ def cubehelix_palette(n_colors=6, start=0, rot=.4, gamma=1.0, hue=0.8, defaults. In addition to using this function, it is also possible to generate a - cubehelix palette generally in seaborn using a string-shorthand; see the - example below. + cubehelix palette generally in seaborn using a string starting with + `ch:` and containing other parameters (e.g. `"ch:s=.25,r=-.5"`). Parameters ---------- n_colors : int Number of colors in the palette. start : float, 0 <= start <= 3 - The hue at the start of the helix. + The hue value at the start of the helix. rot : float Rotations around the hue wheel over the range of the palette. gamma : float 0 <= gamma - Gamma factor to emphasize darker (gamma < 1) or lighter (gamma > 1) - colors. + Nonlinearity to emphasize dark (gamma < 1) or light (gamma > 1) colors. hue : float, 0 <= hue <= 1 Saturation of the colors. dark : float 0 <= dark <= 1 @@ -826,11 +696,12 @@ def cubehelix_palette(n_colors=6, start=0, rot=.4, gamma=1.0, hue=0.8, reverse : bool If True, the palette will go from dark to light. as_cmap : bool - If True, return a :class:`matplotlib.colors.Colormap`. + If True, return a :class:`matplotlib.colors.ListedColormap`. Returns ------- - list of RGB tuples or :class:`matplotlib.colors.Colormap` + palette + list of RGB tuples or :class:`matplotlib.colors.ListedColormap` See Also -------- @@ -847,60 +718,7 @@ def cubehelix_palette(n_colors=6, start=0, rot=.4, gamma=1.0, hue=0.8, Examples -------- - - Generate the default palette: - - .. plot:: - :context: close-figs - - >>> import seaborn as sns; sns.set_theme() - >>> sns.palplot(sns.cubehelix_palette()) - - Rotate backwards from the same starting location: - - .. plot:: - :context: close-figs - - >>> sns.palplot(sns.cubehelix_palette(rot=-.4)) - - Use a different starting point and shorter rotation: - - .. plot:: - :context: close-figs - - >>> sns.palplot(sns.cubehelix_palette(start=2.8, rot=.1)) - - Reverse the direction of the lightness ramp: - - .. plot:: - :context: close-figs - - >>> sns.palplot(sns.cubehelix_palette(reverse=True)) - - Generate a colormap object: - - .. plot:: - :context: close-figs - - >>> from numpy import arange - >>> x = arange(25).reshape(5, 5) - >>> cmap = sns.cubehelix_palette(as_cmap=True) - >>> ax = sns.heatmap(x, cmap=cmap) - - Use the full lightness range: - - .. plot:: - :context: close-figs - - >>> cmap = sns.cubehelix_palette(dark=0, light=1, as_cmap=True) - >>> ax = sns.heatmap(x, cmap=cmap) - - Use through the :func:`color_palette` interface: - - .. plot:: - :context: close-figs - - >>> sns.palplot(sns.color_palette("ch:2,r=.2,l=.6")) + .. include:: ../docstrings/cubehelix_palette.rst """ def get_color_function(p0, p1): @@ -996,32 +814,17 @@ def set_color_codes(palette="deep"): set_palette : Color codes can also be set through the function that sets the matplotlib color cycle. - Examples - -------- - - Map matplotlib color codes to the default seaborn palette. - - .. plot:: - :context: close-figs - - >>> import matplotlib.pyplot as plt - >>> import seaborn as sns; sns.set_theme() - >>> sns.set_color_codes() - >>> _ = plt.plot([0, 1], color="r") - - Use a different seaborn palette. - - .. plot:: - :context: close-figs - - >>> sns.set_color_codes("dark") - >>> _ = plt.plot([0, 1], color="g") - >>> _ = plt.plot([0, 2], color="m") - """ if palette == "reset": - colors = [(0., 0., 1.), (0., .5, 0.), (1., 0., 0.), (.75, 0., .75), - (.75, .75, 0.), (0., .75, .75), (0., 0., 0.)] + colors = [ + (0., 0., 1.), + (0., .5, 0.), + (1., 0., 0.), + (.75, 0., .75), + (.75, .75, 0.), + (0., .75, .75), + (0., 0., 0.) + ] elif not isinstance(palette, str): err = "set_color_codes requires a named seaborn palette" raise TypeError(err) diff --git a/seaborn/rcmod.py b/seaborn/rcmod.py index 145698ca69..ca70a44695 100644 --- a/seaborn/rcmod.py +++ b/seaborn/rcmod.py @@ -516,12 +516,6 @@ def set_palette(palette, n_colors=None, desat=None, color_codes=False): If ``True`` and ``palette`` is a seaborn palette, remap the shorthand color codes (e.g. "b", "g", "r", etc.) to the colors from this palette. - Examples - -------- - >>> set_palette("Reds") - - >>> set_palette("Set1", 8, .75) - See Also -------- color_palette : build a color palette or set the color cycle temporarily diff --git a/seaborn/regression.py b/seaborn/regression.py index a6b1087338..1c7d804e26 100644 --- a/seaborn/regression.py +++ b/seaborn/regression.py @@ -728,98 +728,7 @@ def update_datalim(data, x, y, ax, **kws): Examples -------- - These examples focus on basic regression model plots to exhibit the - various faceting options; see the :func:`regplot` docs for demonstrations - of the other options for plotting the data and models. There are also - other examples for how to manipulate plot using the returned object on - the :class:`FacetGrid` docs. - - Plot a simple linear relationship between two variables: - - .. plot:: - :context: close-figs - - >>> import seaborn as sns; sns.set_theme(color_codes=True) - >>> tips = sns.load_dataset("tips") - >>> g = sns.lmplot(x="total_bill", y="tip", data=tips) - - Condition on a third variable and plot the levels in different colors: - - .. plot:: - :context: close-figs - - >>> g = sns.lmplot(x="total_bill", y="tip", hue="smoker", data=tips) - - Use different markers as well as colors so the plot will reproduce to - black-and-white more easily: - - .. plot:: - :context: close-figs - - >>> g = sns.lmplot(x="total_bill", y="tip", hue="smoker", data=tips, - ... markers=["o", "x"]) - - Use a different color palette: - - .. plot:: - :context: close-figs - - >>> g = sns.lmplot(x="total_bill", y="tip", hue="smoker", data=tips, - ... palette="Set1") - - Map ``hue`` levels to colors with a dictionary: - - .. plot:: - :context: close-figs - - >>> g = sns.lmplot(x="total_bill", y="tip", hue="smoker", data=tips, - ... palette=dict(Yes="g", No="m")) - - Plot the levels of the third variable across different columns: - - .. plot:: - :context: close-figs - - >>> g = sns.lmplot(x="total_bill", y="tip", col="smoker", data=tips) - - Change the height and aspect ratio of the facets: - - .. plot:: - :context: close-figs - - >>> g = sns.lmplot(x="size", y="total_bill", hue="day", col="day", - ... data=tips, height=6, aspect=.4, x_jitter=.1) - - Wrap the levels of the column variable into multiple rows: - - .. plot:: - :context: close-figs - - >>> g = sns.lmplot(x="total_bill", y="tip", col="day", hue="day", - ... data=tips, col_wrap=2, height=3) - - Condition on two variables to make a full grid: - - .. plot:: - :context: close-figs - - >>> g = sns.lmplot(x="total_bill", y="tip", row="sex", col="time", - ... data=tips, height=3) - - Use methods on the returned :class:`FacetGrid` instance to further tweak - the plot: - - .. plot:: - :context: close-figs - - >>> g = sns.lmplot(x="total_bill", y="tip", row="sex", col="time", - ... data=tips, height=3) - >>> g = (g.set_axis_labels("Total bill (US Dollars)", "Tip") - ... .set(xlim=(0, 60), ylim=(0, 12), - ... xticks=[10, 30, 50], yticks=[2, 6, 10]) - ... .fig.subplots_adjust(wspace=.02)) - - + .. include:: ../docstrings/lmplot.rst """).format(**_regression_docs) @@ -921,101 +830,7 @@ def regplot( Examples -------- - Plot the relationship between two variables in a DataFrame: - - .. plot:: - :context: close-figs - - >>> import seaborn as sns; sns.set_theme(color_codes=True) - >>> tips = sns.load_dataset("tips") - >>> ax = sns.regplot(x="total_bill", y="tip", data=tips) - - Plot with two variables defined as numpy arrays; use a different color: - - .. plot:: - :context: close-figs - - >>> import numpy as np; np.random.seed(8) - >>> mean, cov = [4, 6], [(1.5, .7), (.7, 1)] - >>> x, y = np.random.multivariate_normal(mean, cov, 80).T - >>> ax = sns.regplot(x=x, y=y, color="g") - - Plot with two variables defined as pandas Series; use a different marker: - - .. plot:: - :context: close-figs - - >>> import pandas as pd - >>> x, y = pd.Series(x, name="x_var"), pd.Series(y, name="y_var") - >>> ax = sns.regplot(x=x, y=y, marker="+") - - Use a 68% confidence interval, which corresponds with the standard error - of the estimate, and extend the regression line to the axis limits: - - .. plot:: - :context: close-figs - - >>> ax = sns.regplot(x=x, y=y, ci=68, truncate=False) - - Plot with a discrete ``x`` variable and add some jitter: - - .. plot:: - :context: close-figs - - >>> ax = sns.regplot(x="size", y="total_bill", data=tips, x_jitter=.1) - - Plot with a discrete ``x`` variable showing means and confidence intervals - for unique values: - - .. plot:: - :context: close-figs - - >>> ax = sns.regplot(x="size", y="total_bill", data=tips, - ... x_estimator=np.mean) - - Plot with a continuous variable divided into discrete bins: - - .. plot:: - :context: close-figs - - >>> ax = sns.regplot(x=x, y=y, x_bins=4) - - Fit a higher-order polynomial regression: - - .. plot:: - :context: close-figs - - >>> ans = sns.load_dataset("anscombe") - >>> ax = sns.regplot(x="x", y="y", data=ans.loc[ans.dataset == "II"], - ... scatter_kws={{"s": 80}}, - ... order=2, ci=None) - - Fit a robust regression and don't plot a confidence interval: - - .. plot:: - :context: close-figs - - >>> ax = sns.regplot(x="x", y="y", data=ans.loc[ans.dataset == "III"], - ... scatter_kws={{"s": 80}}, - ... robust=True, ci=None) - - Fit a logistic regression; jitter the y variable and use fewer bootstrap - iterations: - - .. plot:: - :context: close-figs - - >>> tips["big_tip"] = (tips.tip / tips.total_bill) > .175 - >>> ax = sns.regplot(x="total_bill", y="big_tip", data=tips, - ... logistic=True, n_boot=500, y_jitter=.03) - - Fit the regression model using log(x): - - .. plot:: - :context: close-figs - - >>> ax = sns.regplot(x="size", y="total_bill", data=tips, - ... x_estimator=np.mean, logx=True) + .. include: ../docstrings/regplot.rst """).format(**_regression_docs) @@ -1075,6 +890,11 @@ def residplot( jointplot : Draw a :func:`residplot` with univariate marginal distributions (when used with ``kind="resid"``). + Examples + -------- + + .. include:: ../docstrings/residplot.rst + """ plotter = _RegressionPlotter(x, y, data, ci=None, order=order, robust=robust, diff --git a/seaborn/utils.py b/seaborn/utils.py index 3cc529a64b..a4887b2c42 100644 --- a/seaborn/utils.py +++ b/seaborn/utils.py @@ -100,7 +100,8 @@ def _default_color(method, hue, color, kws): elif method.__name__ == "plot": - scout, = method([], [], scalex=False, scaley=False, **kws) + color = _normalize_kwargs(kws, mpl.lines.Line2D).get("color") + scout, = method([], [], scalex=False, scaley=False, color=color) color = scout.get_color() scout.remove() diff --git a/seaborn/widgets.py b/seaborn/widgets.py index c75cc66c48..502812af57 100644 --- a/seaborn/widgets.py +++ b/seaborn/widgets.py @@ -2,26 +2,12 @@ import matplotlib.pyplot as plt from matplotlib.colors import LinearSegmentedColormap -# Lots of different places that widgets could come from... try: from ipywidgets import interact, FloatSlider, IntSlider except ImportError: - import warnings - # ignore ShimWarning raised by IPython, see GH #892 - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - try: - from IPython.html.widgets import interact, FloatSlider, IntSlider - except ImportError: - try: - from IPython.html.widgets import (interact, - FloatSliderWidget, - IntSliderWidget) - FloatSlider = FloatSliderWidget - IntSlider = IntSliderWidget - except ImportError: - pass - + def interact(f): + msg = "Interactive palettes require `ipywidgets`, which is not installed." + raise ImportError(msg) from .miscplot import palplot from .palettes import (color_palette, dark_palette, light_palette, diff --git a/tests/_core/test_moves.py b/tests/_core/test_moves.py index bac86d12e2..6fd88bb5fc 100644 --- a/tests/_core/test_moves.py +++ b/tests/_core/test_moves.py @@ -78,6 +78,15 @@ def check_pos(self, res, df, var, limit): assert (res[var] < df[var] + limit / 2).all() assert (res[var] > df[var] - limit / 2).all() + def test_default(self, df): + + orient = "x" + groupby = self.get_groupby(df, orient) + res = Jitter()(df, groupby, orient, {}) + self.check_same(res, df, "y", "grp2", "width") + self.check_pos(res, df, "x", 0.2 * df["width"]) + assert (res["x"] - df["x"]).abs().min() > 0 + def test_width(self, df): width = .4 diff --git a/tests/_core/test_plot.py b/tests/_core/test_plot.py index b612eeec22..03a64917f3 100644 --- a/tests/_core/test_plot.py +++ b/tests/_core/test_plot.py @@ -880,7 +880,7 @@ def test_theme_default(self): def test_theme_params(self): - color = "r" + color = ".888" p = Plot().theme({"axes.facecolor": color}).plot() assert mpl.colors.same_color(p._figure.axes[0].get_facecolor(), color) @@ -1963,6 +1963,20 @@ def _legend_artist(self, variables, value, scales): assert len(contents.findobj(mpl.lines.Line2D)) == len(names) assert len(contents.findobj(mpl.patches.Patch)) == len(names) + def test_three_layers(self, xy): + + class MockMarkLine(MockMark): + def _legend_artist(self, variables, value, scales): + return mpl.lines.Line2D([], []) + + s = pd.Series(["a", "b", "a", "c"], name="s") + p = Plot(**xy, color=s) + for _ in range(3): + p = p.add(MockMarkLine()) + p = p.plot() + texts = p._figure.legends[0].get_texts() + assert len(texts) == len(s.unique()) + def test_identity_scale_ignored(self, xy): s = pd.Series(["r", "g", "b", "g"]) @@ -1981,8 +1995,17 @@ def test_anonymous_title(self, xy): legend, = p._figure.legends assert legend.get_title().get_text() == "" + def test_legendless_mark(self, xy): + + class NoLegendMark(MockMark): + def _legend_artist(self, variables, value, scales): + return None + + p = Plot(**xy, color=["a", "b", "c", "d"]).add(NoLegendMark()).plot() + assert not p._figure.legends + -class TestHelpers: +class TestDefaultObject: def test_default_repr(self): diff --git a/tests/_marks/test_area.py b/tests/_marks/test_area.py index 1e44314082..d725e154ce 100644 --- a/tests/_marks/test_area.py +++ b/tests/_marks/test_area.py @@ -8,7 +8,7 @@ from seaborn._marks.area import Area, Band -class TestAreaMarks: +class TestArea: def test_single_defaults(self): @@ -17,6 +17,7 @@ def test_single_defaults(self): ax = p._figure.axes[0] poly = ax.patches[0] verts = poly.get_path().vertices.T + colors = p._theme["axes.prop_cycle"].by_key()["color"] expected_x = [1, 2, 3, 3, 2, 1, 1] assert_array_equal(verts[0], expected_x) @@ -25,21 +26,21 @@ def test_single_defaults(self): assert_array_equal(verts[1], expected_y) fc = poly.get_facecolor() - assert_array_equal(fc, to_rgba("C0", .2)) + assert_array_equal(fc, to_rgba(colors[0], .2)) ec = poly.get_edgecolor() - assert_array_equal(ec, to_rgba("C0", 1)) + assert_array_equal(ec, to_rgba(colors[0], 1)) lw = poly.get_linewidth() assert_array_equal(lw, mpl.rcParams["patch.linewidth"] * 2) - def test_set_parameters(self): + def test_set_properties(self): x, y = [1, 2, 3], [1, 2, 1] mark = Area( - color="C2", + color=".33", alpha=.3, - edgecolor=".3", + edgecolor=".88", edgealpha=.8, edgewidth=2, edgestyle=(0, (2, 1)), @@ -62,11 +63,12 @@ def test_set_parameters(self): expected = (0, (mark.edgewidth * dash_on / 4, mark.edgewidth * dash_off / 4)) assert ls == expected - def test_mapped(self): + def test_mapped_properties(self): x, y = [1, 2, 3, 2, 3, 4], [1, 2, 1, 1, 3, 2] g = ["a", "a", "a", "b", "b", "b"] - p = Plot(x=x, y=y, color=g, edgewidth=g).add(Area()).plot() + cs = [".2", ".8"] + p = Plot(x=x, y=y, color=g, edgewidth=g).scale(color=cs).add(Area()).plot() ax = p._figure.axes[0] expected_x = [1, 2, 3, 3, 2, 1, 1], [2, 3, 4, 4, 3, 2, 2] @@ -78,10 +80,10 @@ def test_mapped(self): assert_array_equal(verts[1], expected_y[i]) fcs = [p.get_facecolor() for p in ax.patches] - assert_array_equal(fcs, to_rgba_array(["C0", "C1"], .2)) + assert_array_equal(fcs, to_rgba_array(cs, .2)) ecs = [p.get_edgecolor() for p in ax.patches] - assert_array_equal(ecs, to_rgba_array(["C0", "C1"], 1)) + assert_array_equal(ecs, to_rgba_array(cs, 1)) lws = [p.get_linewidth() for p in ax.patches] assert lws[0] > lws[1] @@ -89,12 +91,16 @@ def test_mapped(self): def test_unfilled(self): x, y = [1, 2, 3], [1, 2, 1] - p = Plot(x=x, y=y).add(Area(fill=False)).plot() + c = ".5" + p = Plot(x=x, y=y).add(Area(fill=False, color=c)).plot() ax = p._figure.axes[0] poly = ax.patches[0] - assert poly.get_facecolor() == to_rgba("C0", 0) + assert poly.get_facecolor() == to_rgba(c, 0) - def test_band(self): + +class TestBand: + + def test_range(self): x, ymin, ymax = [1, 2, 4], [2, 1, 4], [3, 3, 5] p = Plot(x=x, ymin=ymin, ymax=ymax).add(Band()).plot() @@ -106,3 +112,17 @@ def test_band(self): expected_y = [2, 1, 4, 5, 3, 3, 2] assert_array_equal(verts[1], expected_y) + + def test_auto_range(self): + + x = [1, 1, 2, 2, 2] + y = [1, 2, 3, 4, 5] + p = Plot(x=x, y=y).add(Band()).plot() + ax = p._figure.axes[0] + verts = ax.patches[0].get_path().vertices.T + + expected_x = [1, 2, 2, 1, 1] + assert_array_equal(verts[0], expected_x) + + expected_y = [1, 3, 5, 2, 1] + assert_array_equal(verts[1], expected_y) diff --git a/tests/_marks/test_bar.py b/tests/_marks/test_bar.py index e68f66ae38..373882e12c 100644 --- a/tests/_marks/test_bar.py +++ b/tests/_marks/test_bar.py @@ -67,7 +67,7 @@ def test_set_properties(self): y = [1, 3, 2] mark = Bar( - color="C2", + color=".8", alpha=.5, edgecolor=".3", edgealpha=.9, @@ -92,9 +92,10 @@ def test_mapped_properties(self): mark = Bar(alpha=.2) p = Plot(x, y, color=x, edgewidth=y).add(mark).plot() ax = p._figure.axes[0] + colors = p._theme["axes.prop_cycle"].by_key()["color"] for i, bar in enumerate(ax.patches): - assert bar.get_facecolor() == to_rgba(f"C{i}", mark.alpha) - assert bar.get_edgecolor() == to_rgba(f"C{i}", 1) + assert bar.get_facecolor() == to_rgba(colors[i], mark.alpha) + assert bar.get_edgecolor() == to_rgba(colors[i], 1) assert ax.patches[0].get_linewidth() < ax.patches[1].get_linewidth() def test_zero_height_skipped(self): @@ -166,7 +167,8 @@ def test_mapped_color_direct_alpha(self, x, y, color): p = Plot(x, y, color=color).add(Bars(alpha=alpha)).plot() ax = p._figure.axes[0] fcs = ax.collections[0].get_facecolors() - expected = to_rgba_array(["C0", "C1", "C2", "C0", "C2"], alpha) + C0, C1, C2, *_ = p._theme["axes.prop_cycle"].by_key()["color"] + expected = to_rgba_array([C0, C1, C2, C0, C2], alpha) assert_array_equal(fcs, expected) def test_mapped_edgewidth(self, x, y): @@ -195,5 +197,6 @@ def test_unfilled(self, x, y): ax = p._figure.axes[0] fcs = ax.collections[0].get_facecolors() ecs = ax.collections[0].get_edgecolors() - assert_array_equal(fcs, to_rgba_array(["C0"] * len(x), 0)) - assert_array_equal(ecs, to_rgba_array(["C4"] * len(x), 1)) + colors = p._theme["axes.prop_cycle"].by_key()["color"] + assert_array_equal(fcs, to_rgba_array([colors[0]] * len(x), 0)) + assert_array_equal(ecs, to_rgba_array([colors[4]] * len(x), 1)) diff --git a/tests/_marks/test_dot.py b/tests/_marks/test_dot.py index 7e5a6f56de..49b5e8f129 100644 --- a/tests/_marks/test_dot.py +++ b/tests/_marks/test_dot.py @@ -39,9 +39,10 @@ def test_simple(self): p = Plot(x=x, y=y).add(Dot()).plot() ax = p._figure.axes[0] points, = ax.collections + C0, *_ = p._theme["axes.prop_cycle"].by_key()["color"] self.check_offsets(points, x, y) - self.check_colors("face", points, ["C0"] * 3, 1) - self.check_colors("edge", points, ["C0"] * 3, 1) + self.check_colors("face", points, [C0] * 3, 1) + self.check_colors("edge", points, [C0] * 3, 1) def test_filled_unfilled_mix(self): @@ -54,9 +55,10 @@ def test_filled_unfilled_mix(self): p = Plot(x=x, y=y).add(mark, marker=marker).scale(marker=shapes).plot() ax = p._figure.axes[0] points, = ax.collections + C0, *_ = p._theme["axes.prop_cycle"].by_key()["color"] self.check_offsets(points, x, y) - self.check_colors("face", points, ["C0", to_rgba("C0", 0)], None) - self.check_colors("edge", points, ["w", "C0"], 1) + self.check_colors("face", points, [C0, to_rgba(C0, 0)], None) + self.check_colors("edge", points, ["w", C0], 1) expected = [mark.edgewidth, mark.stroke] assert_array_equal(points.get_linewidths(), expected) @@ -93,22 +95,24 @@ def test_simple(self): p = Plot(x=x, y=y).add(Dots()).plot() ax = p._figure.axes[0] points, = ax.collections + C0, *_ = p._theme["axes.prop_cycle"].by_key()["color"] self.check_offsets(points, x, y) - self.check_colors("face", points, ["C0"] * 3, .2) - self.check_colors("edge", points, ["C0"] * 3, 1) + self.check_colors("face", points, [C0] * 3, .2) + self.check_colors("edge", points, [C0] * 3, 1) - def test_color_direct(self): + def test_set_color(self): x = [1, 2, 3] y = [4, 5, 2] - p = Plot(x=x, y=y).add(Dots(color="g")).plot() + m = Dots(color=".25") + p = Plot(x=x, y=y).add(m).plot() ax = p._figure.axes[0] points, = ax.collections self.check_offsets(points, x, y) - self.check_colors("face", points, ["g"] * 3, .2) - self.check_colors("edge", points, ["g"] * 3, 1) + self.check_colors("face", points, [m.color] * 3, .2) + self.check_colors("edge", points, [m.color] * 3, 1) - def test_color_mapped(self): + def test_map_color(self): x = [1, 2, 3] y = [4, 5, 2] @@ -116,9 +120,10 @@ def test_color_mapped(self): p = Plot(x=x, y=y, color=c).add(Dots()).plot() ax = p._figure.axes[0] points, = ax.collections + C0, C1, *_ = p._theme["axes.prop_cycle"].by_key()["color"] self.check_offsets(points, x, y) - self.check_colors("face", points, ["C0", "C1", "C0"], .2) - self.check_colors("edge", points, ["C0", "C1", "C0"], 1) + self.check_colors("face", points, [C0, C1, C0], .2) + self.check_colors("edge", points, [C0, C1, C0], 1) def test_fill(self): @@ -128,9 +133,10 @@ def test_fill(self): p = Plot(x=x, y=y, color=c).add(Dots(fill=False)).plot() ax = p._figure.axes[0] points, = ax.collections + C0, C1, *_ = p._theme["axes.prop_cycle"].by_key()["color"] self.check_offsets(points, x, y) - self.check_colors("face", points, ["C0", "C1", "C0"], 0) - self.check_colors("edge", points, ["C0", "C1", "C0"], 1) + self.check_colors("face", points, [C0, C1, C0], 0) + self.check_colors("edge", points, [C0, C1, C0], 1) def test_pointsize(self): @@ -165,7 +171,8 @@ def test_filled_unfilled_mix(self): p = Plot(x=x, y=y).add(mark, marker=marker).scale(marker=shapes).plot() ax = p._figure.axes[0] points, = ax.collections + C0, C1, *_ = p._theme["axes.prop_cycle"].by_key()["color"] self.check_offsets(points, x, y) - self.check_colors("face", points, [to_rgba("C0", .2), to_rgba("C0", 0)], None) - self.check_colors("edge", points, ["C0", "C0"], 1) + self.check_colors("face", points, [to_rgba(C0, .2), to_rgba(C0, 0)], None) + self.check_colors("edge", points, [C0, C0], 1) assert_array_equal(points.get_linewidths(), [mark.stroke] * 2) diff --git a/tests/_marks/test_line.py b/tests/_marks/test_line.py index 4157f82079..726daea55a 100644 --- a/tests/_marks/test_line.py +++ b/tests/_marks/test_line.py @@ -27,18 +27,19 @@ def test_xy_data(self): def test_shared_colors_direct(self): x = y = [1, 2, 3] - m = Path(color="r") + color = ".44" + m = Path(color=color) p = Plot(x=x, y=y).add(m).plot() line, = p._figure.axes[0].get_lines() - assert same_color(line.get_color(), "r") - assert same_color(line.get_markeredgecolor(), "r") - assert same_color(line.get_markerfacecolor(), "r") + assert same_color(line.get_color(), color) + assert same_color(line.get_markeredgecolor(), color) + assert same_color(line.get_markerfacecolor(), color) def test_separate_colors_direct(self): x = y = [1, 2, 3] y = [1, 2, 3] - m = Path(color="r", edgecolor="g", fillcolor="b") + m = Path(color=".22", edgecolor=".55", fillcolor=".77") p = Plot(x=x, y=y).add(m).plot() line, = p._figure.axes[0].get_lines() assert same_color(line.get_color(), m.color) @@ -52,10 +53,11 @@ def test_shared_colors_mapped(self): m = Path() p = Plot(x=x, y=y, color=c).add(m).plot() ax = p._figure.axes[0] + colors = p._theme["axes.prop_cycle"].by_key()["color"] for i, line in enumerate(ax.get_lines()): - assert same_color(line.get_color(), f"C{i}") - assert same_color(line.get_markeredgecolor(), f"C{i}") - assert same_color(line.get_markerfacecolor(), f"C{i}") + assert same_color(line.get_color(), colors[i]) + assert same_color(line.get_markeredgecolor(), colors[i]) + assert same_color(line.get_markerfacecolor(), colors[i]) def test_separate_colors_mapped(self): @@ -65,10 +67,11 @@ def test_separate_colors_mapped(self): m = Path() p = Plot(x=x, y=y, color=c, fillcolor=d).add(m).plot() ax = p._figure.axes[0] + colors = p._theme["axes.prop_cycle"].by_key()["color"] for i, line in enumerate(ax.get_lines()): - assert same_color(line.get_color(), f"C{i // 2}") - assert same_color(line.get_markeredgecolor(), f"C{i // 2}") - assert same_color(line.get_markerfacecolor(), f"C{i % 2}") + assert same_color(line.get_color(), colors[i // 2]) + assert same_color(line.get_markeredgecolor(), colors[i // 2]) + assert same_color(line.get_markerfacecolor(), colors[i % 2]) def test_color_with_alpha(self): @@ -168,10 +171,10 @@ def test_xy_data(self): assert_array_equal(verts[0], [5, 2]) assert_array_equal(verts[1], [4, 3]) - def test_props_direct(self): + def test_set_properties(self): x = y = [1, 2, 3] - m = Paths(color="r", linewidth=1, linestyle=(3, 1)) + m = Paths(color=".737", linewidth=1, linestyle=(3, 1)) p = Plot(x=x, y=y).add(m).plot() lines, = p._figure.axes[0].collections @@ -179,7 +182,7 @@ def test_props_direct(self): assert lines.get_linewidth().item() == m.linewidth assert lines.get_linestyle()[0] == (0, list(m.linestyle)) - def test_props_mapped(self): + def test_mapped_properties(self): x = y = [1, 2, 3, 4] g = ["a", "a", "b", "b"] @@ -243,6 +246,15 @@ def test_xy_data(self): assert_array_equal(verts[0], [2, 5]) assert_array_equal(verts[1], [3, 4]) + def test_single_orient_value(self): + + x = [1, 1, 1] + y = [1, 2, 3] + p = Plot(x, y).add(Lines()).plot() + lines, = p._figure.axes[0].collections + paths, = lines.get_paths() + assert paths.vertices.shape == (0, 2) + class TestRange: @@ -260,6 +272,17 @@ def test_xy_data(self): assert_array_equal(verts[0], [x[i], x[i]]) assert_array_equal(verts[1], [ymin[i], ymax[i]]) + def test_auto_range(self): + + x = [1, 1, 2, 2, 2] + y = [1, 2, 3, 4, 5] + + p = Plot(x=x, y=y).add(Range()).plot() + lines, = p._figure.axes[0].collections + paths = lines.get_paths() + assert_array_equal(paths[0].vertices, [(1, 1), (1, 2)]) + assert_array_equal(paths[1].vertices, [(2, 3), (2, 5)]) + def test_mapped_color(self): x = [1, 2, 1, 2] @@ -269,12 +292,13 @@ def test_mapped_color(self): p = Plot(x=x, ymin=ymin, ymax=ymax, color=group).add(Range()).plot() lines, = p._figure.axes[0].collections + colors = p._theme["axes.prop_cycle"].by_key()["color"] for i, path in enumerate(lines.get_paths()): verts = path.vertices.T assert_array_equal(verts[0], [x[i], x[i]]) assert_array_equal(verts[1], [ymin[i], ymax[i]]) - assert same_color(lines.get_colors()[i], f"C{i // 2}") + assert same_color(lines.get_colors()[i], colors[i // 2]) def test_direct_properties(self): @@ -282,7 +306,7 @@ def test_direct_properties(self): ymin = [1, 4] ymax = [2, 3] - m = Range(color="r", linewidth=4) + m = Range(color=".654", linewidth=4) p = Plot(x=x, ymin=ymin, ymax=ymax).add(m).plot() lines, = p._figure.axes[0].collections diff --git a/tests/_marks/test_text.py b/tests/_marks/test_text.py new file mode 100644 index 0000000000..241b1742e0 --- /dev/null +++ b/tests/_marks/test_text.py @@ -0,0 +1,129 @@ + +import numpy as np +from matplotlib.colors import to_rgba +from matplotlib.text import Text as MPLText + +from numpy.testing import assert_array_almost_equal + +from seaborn._core.plot import Plot +from seaborn._marks.text import Text + + +class TestText: + + def get_texts(self, ax): + if ax.texts: + return list(ax.texts) + else: + # Compatibility with matplotlib < 3.5 (I think) + return [a for a in ax.artists if isinstance(a, MPLText)] + + def test_simple(self): + + x = y = [1, 2, 3] + s = list("abc") + + p = Plot(x, y, text=s).add(Text()).plot() + ax = p._figure.axes[0] + for i, text in enumerate(self.get_texts(ax)): + x_, y_ = text.get_position() + assert x_ == x[i] + assert y_ == y[i] + assert text.get_text() == s[i] + assert text.get_horizontalalignment() == "center" + assert text.get_verticalalignment() == "center_baseline" + + def test_set_properties(self): + + x = y = [1, 2, 3] + s = list("abc") + color = "red" + alpha = .6 + fontsize = 6 + valign = "bottom" + + m = Text(color=color, alpha=alpha, fontsize=fontsize, valign=valign) + p = Plot(x, y, text=s).add(m).plot() + ax = p._figure.axes[0] + for i, text in enumerate(self.get_texts(ax)): + assert text.get_text() == s[i] + assert text.get_color() == to_rgba(m.color, m.alpha) + assert text.get_fontsize() == m.fontsize + assert text.get_verticalalignment() == m.valign + + def test_mapped_properties(self): + + x = y = [1, 2, 3] + s = list("abc") + color = list("aab") + fontsize = [1, 2, 4] + + p = Plot(x, y, color=color, fontsize=fontsize, text=s).add(Text()).plot() + ax = p._figure.axes[0] + texts = self.get_texts(ax) + assert texts[0].get_color() == texts[1].get_color() + assert texts[0].get_color() != texts[2].get_color() + assert ( + texts[0].get_fontsize() + < texts[1].get_fontsize() + < texts[2].get_fontsize() + ) + + def test_mapped_alignment(self): + + x = [1, 2] + p = Plot(x=x, y=x, halign=x, valign=x, text=x).add(Text()).plot() + ax = p._figure.axes[0] + t1, t2 = self.get_texts(ax) + assert t1.get_horizontalalignment() == "left" + assert t2.get_horizontalalignment() == "right" + assert t1.get_verticalalignment() == "top" + assert t2.get_verticalalignment() == "bottom" + + def test_identity_fontsize(self): + + x = y = [1, 2, 3] + s = list("abc") + fs = [5, 8, 12] + p = Plot(x, y, text=s, fontsize=fs).add(Text()).scale(fontsize=None).plot() + ax = p._figure.axes[0] + for i, text in enumerate(self.get_texts(ax)): + assert text.get_fontsize() == fs[i] + + def test_offset_centered(self): + + x = y = [1, 2, 3] + s = list("abc") + p = Plot(x, y, text=s).add(Text()).plot() + ax = p._figure.axes[0] + ax_trans = ax.transData.get_matrix() + for text in self.get_texts(ax): + assert_array_almost_equal(text.get_transform().get_matrix(), ax_trans) + + def test_offset_valign(self): + + x = y = [1, 2, 3] + s = list("abc") + m = Text(valign="bottom", fontsize=5, offset=.1) + p = Plot(x, y, text=s).add(m).plot() + ax = p._figure.axes[0] + expected_shift_matrix = np.zeros((3, 3)) + expected_shift_matrix[1, -1] = m.offset * ax.figure.dpi / 72 + ax_trans = ax.transData.get_matrix() + for text in self.get_texts(ax): + shift_matrix = text.get_transform().get_matrix() - ax_trans + assert_array_almost_equal(shift_matrix, expected_shift_matrix) + + def test_offset_halign(self): + + x = y = [1, 2, 3] + s = list("abc") + m = Text(halign="right", fontsize=10, offset=.5) + p = Plot(x, y, text=s).add(m).plot() + ax = p._figure.axes[0] + expected_shift_matrix = np.zeros((3, 3)) + expected_shift_matrix[0, -1] = -m.offset * ax.figure.dpi / 72 + ax_trans = ax.transData.get_matrix() + for text in self.get_texts(ax): + shift_matrix = text.get_transform().get_matrix() - ax_trans + assert_array_almost_equal(shift_matrix, expected_shift_matrix) diff --git a/tests/_stats/test_histogram.py b/tests/_stats/test_histogram.py index 123305a33e..f70865f0f2 100644 --- a/tests/_stats/test_histogram.py +++ b/tests/_stats/test_histogram.py @@ -122,6 +122,11 @@ def test_frequency_stat(self, long_df, single_args): out = h(long_df, *single_args) assert (out["y"] * out["space"]).sum() == len(long_df) + def test_invalid_stat(self): + + with pytest.raises(ValueError, match="The `stat` parameter for `Hist`"): + Hist(stat="invalid") + def test_cumulative_count(self, long_df, single_args): h = Hist(stat="count", cumulative=True) @@ -157,9 +162,15 @@ def test_common_norm_subset(self, long_df, triple_args): h = Hist(stat="percent", common_norm=["a"]) out = h(long_df, *triple_args) - for _, out_part in out.groupby(["a"]): + for _, out_part in out.groupby("a"): assert out_part["y"].sum() == pytest.approx(100) + def test_common_norm_warning(self, long_df, triple_args): + + h = Hist(common_norm=["b"]) + with pytest.warns(UserWarning, match="Undefined variable(s)"): + h(long_df, *triple_args) + def test_common_bins_default(self, long_df, triple_args): h = Hist() @@ -183,10 +194,16 @@ def test_common_bins_subset(self, long_df, triple_args): h = Hist(common_bins=False) out = h(long_df, *triple_args) bins = [] - for _, out_part in out.groupby(["a"]): + for _, out_part in out.groupby("a"): bins.append(tuple(out_part["x"])) assert len(set(bins)) == out["a"].nunique() + def test_common_bins_warning(self, long_df, triple_args): + + h = Hist(common_bins=["b"]) + with pytest.warns(UserWarning, match="Undefined variable(s)"): + h(long_df, *triple_args) + def test_histogram_single(self, long_df, single_args): h = Hist() diff --git a/tests/_stats/test_order.py b/tests/_stats/test_order.py new file mode 100644 index 0000000000..eaacdcab8b --- /dev/null +++ b/tests/_stats/test_order.py @@ -0,0 +1,87 @@ + +import numpy as np +import pandas as pd + +import pytest +from numpy.testing import assert_array_equal + +from seaborn._core.groupby import GroupBy +from seaborn._stats.order import Perc +from seaborn.external.version import Version + + +class Fixtures: + + @pytest.fixture + def df(self, rng): + return pd.DataFrame(dict(x="", y=rng.normal(size=30))) + + def get_groupby(self, df, orient): + # TODO note, copied from aggregation + other = {"x": "y", "y": "x"}[orient] + cols = [c for c in df if c != other] + return GroupBy(cols) + + +class TestPerc(Fixtures): + + def test_int_k(self, df): + + ori = "x" + gb = self.get_groupby(df, ori) + res = Perc(3)(df, gb, ori, {}) + percentiles = [0, 50, 100] + assert_array_equal(res["percentile"], percentiles) + assert_array_equal(res["y"], np.percentile(df["y"], percentiles)) + + def test_list_k(self, df): + + ori = "x" + gb = self.get_groupby(df, ori) + percentiles = [0, 20, 100] + res = Perc(k=percentiles)(df, gb, ori, {}) + assert_array_equal(res["percentile"], percentiles) + assert_array_equal(res["y"], np.percentile(df["y"], percentiles)) + + def test_orientation(self, df): + + df = df.rename(columns={"x": "y", "y": "x"}) + ori = "y" + gb = self.get_groupby(df, ori) + res = Perc(k=3)(df, gb, ori, {}) + assert_array_equal(res["x"], np.percentile(df["x"], [0, 50, 100])) + + def test_method(self, df): + + ori = "x" + gb = self.get_groupby(df, ori) + method = "nearest" + res = Perc(k=5, method=method)(df, gb, ori, {}) + percentiles = [0, 25, 50, 75, 100] + if Version(np.__version__) < Version("1.22.0"): + expected = np.percentile(df["y"], percentiles, interpolation=method) + else: + expected = np.percentile(df["y"], percentiles, method=method) + assert_array_equal(res["y"], expected) + + def test_grouped(self, df, rng): + + ori = "x" + df = df.assign(x=rng.choice(["a", "b", "c"], len(df))) + gb = self.get_groupby(df, ori) + k = [10, 90] + res = Perc(k)(df, gb, ori, {}) + for x, res_x in res.groupby("x"): + assert_array_equal(res_x["percentile"], k) + expected = np.percentile(df.loc[df["x"] == x, "y"], k) + assert_array_equal(res_x["y"], expected) + + def test_with_na(self, df): + + ori = "x" + df.loc[:5, "y"] = np.nan + gb = self.get_groupby(df, ori) + k = [10, 90] + res = Perc(k)(df, gb, ori, {}) + expected = np.percentile(df["y"].dropna(), k) + assert_array_equal(res["y"], expected) diff --git a/tests/conftest.py b/tests/conftest.py index 89375bcd38..0366d1643e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,27 +1,13 @@ import numpy as np import pandas as pd -import datetime -import matplotlib as mpl -import matplotlib.pyplot as plt import pytest -@pytest.fixture(scope="session", autouse=True) -def remove_pandas_unit_conversion(): - # Prior to pandas 1.0, it registered its own datetime converters, - # but they are less powerful than what matplotlib added in 2.2, - # and we rely on that functionality in seaborn. - # https://github.com/matplotlib/matplotlib/pull/9779 - # https://github.com/pandas-dev/pandas/issues/27036 - mpl.units.registry[np.datetime64] = mpl.dates.DateConverter() - mpl.units.registry[datetime.date] = mpl.dates.DateConverter() - mpl.units.registry[datetime.datetime] = mpl.dates.DateConverter() - - @pytest.fixture(autouse=True) def close_figs(): yield + import matplotlib.pyplot as plt plt.close("all") diff --git a/tests/test_axisgrid.py b/tests/test_axisgrid.py index ed1a1cd721..af8bf19da8 100644 --- a/tests/test_axisgrid.py +++ b/tests/test_axisgrid.py @@ -5,7 +5,7 @@ import pytest import numpy.testing as npt -from numpy.testing import assert_array_equal +from numpy.testing import assert_array_equal, assert_array_almost_equal try: import pandas.testing as tm except ImportError: @@ -1685,12 +1685,12 @@ def test_scatter(self): assert_array_equal(self.x, x) assert_array_equal(self.y, y) - assert_array_equal( + assert_array_almost_equal( [b.get_x() for b in g.ax_marg_x.patches], np.histogram_bin_edges(self.x, "auto")[:-1], ) - assert_array_equal( + assert_array_almost_equal( [b.get_y() for b in g.ax_marg_y.patches], np.histogram_bin_edges(self.y, "auto")[:-1], ) diff --git a/tests/test_categorical.py b/tests/test_categorical.py index 1bbc67c517..8b84c40801 100644 --- a/tests/test_categorical.py +++ b/tests/test_categorical.py @@ -342,7 +342,7 @@ def test_longform_groupby(self): p1 = cat._CategoricalPlotter() p1.establish_variables(self.g, self.y, hue=self.h) p2 = cat._CategoricalPlotter() - p2.establish_variables(self.g, self.y[::-1], self.h) + p2.establish_variables(self.g, self.y.iloc[::-1], self.h) for i, (d1, d2) in enumerate(zip(p1.plot_data, p2.plot_data)): assert np.array_equal(d1.sort_index(), d2.sort_index()) @@ -616,7 +616,7 @@ def test_nested_stats(self): y.groupby([g, h]).mean().unstack()) for ci_g, (_, grp_y) in zip(p.confint, y.groupby(g)): - for ci, hue_y in zip(ci_g, [grp_y[::2], grp_y[1::2]]): + for ci, hue_y in zip(ci_g, [grp_y.iloc[::2], grp_y.iloc[1::2]]): sem = hue_y.std() / np.sqrt(len(hue_y)) mean = hue_y.mean() half_ci = _normal_quantile_func(.975) * sem @@ -734,7 +734,7 @@ def test_nested_sd_error_bars(self): y.groupby([g, h]).mean().unstack()) for ci_g, (_, grp_y) in zip(p.confint, y.groupby(g)): - for ci, hue_y in zip(ci_g, [grp_y[::2], grp_y[1::2]]): + for ci, hue_y in zip(ci_g, [grp_y.iloc[::2], grp_y.iloc[1::2]]): mean = hue_y.mean() half_ci = np.std(hue_y) ci_want = mean - half_ci, mean + half_ci diff --git a/tests/test_core.py b/tests/test_core.py index 9f657f7fd2..798a8d61fa 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1140,12 +1140,12 @@ def test_attach_converters(self, long_df): p = VectorPlotter(data=long_df, variables={"x": "x", "y": "t"}) p._attach(ax) assert ax.xaxis.converter is None - assert isinstance(ax.yaxis.converter, mpl.dates.DateConverter) + assert "Date" in ax.yaxis.converter.__class__.__name__ _, ax = plt.subplots() p = VectorPlotter(data=long_df, variables={"x": "a", "y": "y"}) p._attach(ax) - assert isinstance(ax.xaxis.converter, mpl.category.StrCategoryConverter) + assert "CategoryConverter" in ax.xaxis.converter.__class__.__name__ assert ax.yaxis.converter is None def test_attach_facets(self, long_df): diff --git a/tests/test_distributions.py b/tests/test_distributions.py index 2b53d51a34..c4d62fa340 100644 --- a/tests/test_distributions.py +++ b/tests/test_distributions.py @@ -1060,6 +1060,15 @@ def test_contour_line_colors(self, long_df): for c in ax.collections: assert_colors_equal(get_contour_color(c), color) + def test_contour_line_cmap(self, long_df): + + color_list = color_palette("Blues", 12) + cmap = mpl.colors.ListedColormap(color_list) + ax = kdeplot(data=long_df, x="x", y="y", cmap=cmap) + for c in ax.collections: + color = to_rgb(get_contour_color(c).squeeze()) + assert color in color_list + def test_contour_fill_colors(self, long_df): n = 6 @@ -1421,8 +1430,8 @@ def test_unique_bins(self, wide_df): bars = bar_groups[i] start = bars[0].get_x() stop = bars[-1].get_x() + bars[-1].get_width() - assert start == wide_df[col].min() - assert stop == wide_df[col].max() + assert_array_almost_equal(start, wide_df[col].min()) + assert_array_almost_equal(stop, wide_df[col].max()) def test_weights_with_missing(self, missing_df): @@ -1559,7 +1568,6 @@ def test_kde_line_kws(self, flat_series): def test_kde_singular_data(self): - with warnings.catch_warnings(): warnings.simplefilter("error") ax = histplot(x=np.ones(10), kde=True) diff --git a/tests/test_objects.py b/tests/test_objects.py new file mode 100644 index 0000000000..5f7f5b9f91 --- /dev/null +++ b/tests/test_objects.py @@ -0,0 +1,14 @@ +import seaborn.objects +from seaborn._core.plot import Plot +from seaborn._core.moves import Move +from seaborn._core.scales import Scale +from seaborn._marks.base import Mark +from seaborn._stats.base import Stat + + +def test_objects_namespace(): + + for name in dir(seaborn.objects): + if not name.startswith("__"): + obj = getattr(seaborn.objects, name) + assert issubclass(obj, (Plot, Mark, Stat, Move, Scale)) diff --git a/tests/test_palettes.py b/tests/test_palettes.py index cda371c089..4d9e9f916e 100644 --- a/tests/test_palettes.py +++ b/tests/test_palettes.py @@ -416,9 +416,24 @@ def test_preserved_palette_length(self): pal_out = palettes.color_palette(pal_in) assert pal_in == pal_out - def test_html_rep(self): + def test_html_repr(self): pal = palettes.color_palette() html = pal._repr_html_() for color in pal.as_hex(): assert color in html + + def test_colormap_display_patch(self): + + orig_repr_png = getattr(mpl.colors.Colormap, "_repr_png_", None) + orig_repr_html = getattr(mpl.colors.Colormap, "_repr_html_", None) + + try: + palettes._patch_colormap_display() + cmap = mpl.cm.Reds + assert cmap._repr_html_().startswith('