{
 "cells": [
  {
   "cell_type": "raw",
   "id": "35110bb9-6889-4bd5-b9d6-5a0479131433",
   "metadata": {},
   "source": [
    ".. _objects_tutorial:\n",
    "\n",
    ".. currentmodule:: seaborn.objects\n",
    "\n",
    "The seaborn.objects interface\n",
    "=============================\n",
    "\n",
    "The `seaborn.objects` namespace was introduced in version 0.12 as a completely new interface for making seaborn plots. It offers a more consistent and flexible API, comprising a collection of composable classes for transforming and plotting data. In contrast to the existing `seaborn` functions, the new interface aims to support end-to-end plot specification and customization without dropping down to matplotlib (although it will remain possible to do so if necessary).\n",
    "\n",
    ".. note::\n",
    "    The objects interface is currently experimental and incomplete. It is stable enough for serious use, but there certainly are some rough edges and missing features."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "706badfa-58be-4808-9016-bd0ca3ebaf12",
   "metadata": {
    "tags": [
     "hide"
    ]
   },
   "outputs": [],
   "source": [
    "import seaborn as sns\n",
    "import matplotlib as mpl\n",
    "tips = sns.load_dataset(\"tips\")\n",
    "penguins = sns.load_dataset(\"penguins\").dropna()\n",
    "diamonds = sns.load_dataset(\"diamonds\")\n",
    "healthexp = sns.load_dataset(\"healthexp\").sort_values([\"Country\", \"Year\"]).query(\"Year <= 2020\")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "dd1ceae5-f930-41c2-8a18-f3cf94a161ad",
   "metadata": {},
   "source": [
    "Specifying a plot and mapping data\n",
    "----------------------------------\n",
    "\n",
    "The objects interface should be imported with the following convention:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1c113156-20ad-4612-a9f5-0071d7fd35dd",
   "metadata": {},
   "outputs": [],
   "source": [
    "import seaborn.objects as so"
   ]
  },
  {
   "cell_type": "raw",
   "id": "6518484e-828b-4e7c-8529-ed6c9e61fa69",
   "metadata": {},
   "source": [
    "The `seaborn.objects` namespace will provide access to all of the relevant classes. The most important is :class:`Plot`. You specify plots by instantiating a :class:`Plot` object and calling its methods. Let's see a simple example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2e7f8ad0-9831-464b-9825-60733f110f34",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(penguins, x=\"bill_length_mm\", y=\"bill_depth_mm\")\n",
    "    .add(so.Dot())\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "52785052-6c80-4f35-87e4-b27df499bd5c",
   "metadata": {},
   "source": [
    "This code, which produces a scatter plot, should look reasonably familiar. Just as when using :func:`seaborn.scatterplot`, we passed a tidy dataframe (`penguins`) and assigned two of its columns to the `x` and `y` coordinates of the plot. But instead of starting with the type of chart and then adding some data assignments, here we started with the data assignments and then added a graphical element.\n",
    "\n",
    "Setting properties\n",
    "~~~~~~~~~~~~~~~~~~\n",
    "\n",
    "The :class:`Dot` class is an example of a :class:`Mark`: an object that graphically represents data values. Each mark will have a number of properties that can be set to change its appearance:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "310bac42-cfe4-4c45-9ddf-27c2cb200a8a",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(penguins, x=\"bill_length_mm\", y=\"bill_depth_mm\")\n",
    "    .add(so.Dot(color=\"g\", pointsize=4))\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "3f817822-dd96-4263-a42e-824f9ca4083a",
   "metadata": {},
   "source": [
    "Mapping properties\n",
    "~~~~~~~~~~~~~~~~~~\n",
    "\n",
    "As with seaborn's functions, it is also possible to *map* data values to various graphical properties:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6267e411-1f75-461e-a189-ead4452b2ec6",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(\n",
    "        penguins, x=\"bill_length_mm\", y=\"bill_depth_mm\",\n",
    "        color=\"species\", pointsize=\"body_mass_g\",\n",
    "    )\n",
    "    .add(so.Dot())\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "b6bfc0bf-cae1-44ed-9f52-e9f748c3877d",
   "metadata": {},
   "source": [
    "While this basic functionality is not novel, an important difference from the function API is that properties are mapped using the same parameter names that would set them directly (instead of having `hue` vs. `color`, etc.). What matters is *where* the property is defined: passing a value when you initialize :class:`Dot` will set it directly, whereas assigning a variable when you set up the :class:`Plot` will *map* the corresponding data.\n",
    "\n",
    "Beyond this difference, the objects interface also allows a much wider range of mark properties to be mapped:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b8637528-4e17-4a41-be1c-2cb4275a5586",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(\n",
    "        penguins, x=\"bill_length_mm\", y=\"bill_depth_mm\",\n",
    "        edgecolor=\"sex\", edgewidth=\"body_mass_g\",\n",
    "    )\n",
    "    .add(so.Dot(color=\".8\"))\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "220930c4-410c-4452-a89e-95045f325cc0",
   "metadata": {},
   "source": [
    "Defining groups\n",
    "~~~~~~~~~~~~~~~\n",
    "\n",
    "The :class:`Dot` mark represents each data point independently, so the assignment of a variable to a property only has the effect of changing each dot's appearance. For marks that group or connect observations, such as :class:`Line`, it also determines the number of distinct graphical elements:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "95f892e1-8adc-43d3-8b30-84d8c848040a",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(healthexp, x=\"Year\", y=\"Life_Expectancy\", color=\"Country\")\n",
    "    .add(so.Line())\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "6665552c-674b-405e-a3ee-237517649349",
   "metadata": {},
   "source": [
    "It is also possible to define a grouping without changing any visual properties, by using `group`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f9287beb-7a66-4dcb-bccf-9c5cab2790f4",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(healthexp, x=\"Year\", y=\"Life_Expectancy\", group=\"Country\")\n",
    "    .add(so.Line())\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "be097dfa-e33c-41f5-8b5a-09013cb33e6e",
   "metadata": {},
   "source": [
    "Transforming data before plotting\n",
    "---------------------------------\n",
    "\n",
    "Statistical transformation\n",
    "~~~~~~~~~~~~~~~~~~~~~~~~~~\n",
    "\n",
    "As with many seaborn functions, the objects interface supports statistical transformations. These are performed by :class:`Stat` objects, such as :class:`Agg`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0964d2af-ce53-48b5-b79a-3277b05584dd",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(penguins, x=\"species\", y=\"body_mass_g\")\n",
    "    .add(so.Bar(), so.Agg())\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "5ac229b2-3692-4d35-8ba3-e35262f198ce",
   "metadata": {},
   "source": [
    "In the function interface, statistical transformations are possible with some visual representations (e.g. :func:`seaborn.barplot`) but not others (e.g. :func:`seaborn.scatterplot`). The objects interface more cleanly separates representation and transformation, allowing you to compose :class:`Mark` and :class:`Stat` objects:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5c2f917d-1cb7-4d33-b8c4-2126a4f91ccc",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(penguins, x=\"species\", y=\"body_mass_g\")\n",
    "    .add(so.Dot(pointsize=10), so.Agg())\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "1b9d7688-22f5-4f4a-b58e-71d8ff550b48",
   "metadata": {},
   "source": [
    "When forming groups by mapping properties, the :class:`Stat` transformation is applied to each group separately:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "734f9dac-4663-4e51-8070-716c0c0296c6",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(penguins, x=\"species\", y=\"body_mass_g\", color=\"sex\")\n",
    "    .add(so.Dot(pointsize=10), so.Agg())\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "e60a8e83-c34c-4769-b34f-e0c23c80b870",
   "metadata": {},
   "source": [
    "Resolving overplotting\n",
    "~~~~~~~~~~~~~~~~~~~~~~\n",
    "\n",
    "Some seaborn functions also have mechanisms that automatically resolve overplotting, as when :func:`seaborn.barplot` \"dodges\" bars once `hue` is assigned. The objects interface has less complex default behavior. Bars representing multiple groups will overlap by default:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "96653815-7da3-4a77-877a-485b5e7578a4",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(penguins, x=\"species\", y=\"body_mass_g\", color=\"sex\")\n",
    "    .add(so.Bar(), so.Agg())\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "06ee3b9f-0ae9-467f-8a40-e340e6f3ce7d",
   "metadata": {},
   "source": [
    "Nevertheless, it is possible to compose the :class:`Bar` mark with the :class:`Agg` stat and a second transformation, implemented by :class:`Dodge`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e29792ae-c238-4538-952a-5af81adcefe0",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(penguins, x=\"species\", y=\"body_mass_g\", color=\"sex\")\n",
    "    .add(so.Bar(), so.Agg(), so.Dodge())\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "a27dcb37-be58-427b-a722-9039b91b6503",
   "metadata": {},
   "source": [
    "The :class:`Dodge` class is an example of a :class:`Move` transformation, which is like a :class:`Stat` but only adjusts `x` and `y` coordinates. The :class:`Move` classes can be applied with any mark, and it's not necessary to use a :class:`Stat` first:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c4509ea7-36fe-4ffb-b784-e945d13fb93c",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(penguins, x=\"species\", y=\"body_mass_g\", color=\"sex\")\n",
    "    .add(so.Dot(), so.Dodge())\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "a62e44ae-d6e7-4ab5-af2e-7b49a2031b1d",
   "metadata": {},
   "source": [
    "It's also possible to apply multiple :class:`Move` operations in sequence:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "07536818-9ddd-46d1-b10c-b034fa257335",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(penguins, x=\"species\", y=\"body_mass_g\", color=\"sex\")\n",
    "    .add(so.Dot(), so.Dodge(), so.Jitter(.3))\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "fd8ed5cc-6ba4-4d03-8414-57a782971d4c",
   "metadata": {},
   "source": [
    "Creating variables through transformation\n",
    "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n",
    "\n",
    "The :class:`Agg` stat requires both `x` and `y` to already be defined, but variables can also be *created* through statistical transformation. For example, the :class:`Hist` stat requires only one of `x` *or* `y` to be defined, and it will create the other by counting observations:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4b1f2c61-d294-4a85-a383-384d92523c36",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(penguins, x=\"species\")\n",
    "    .add(so.Bar(), so.Hist())\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "9b33ea0c-f11d-48d7-be7c-13e9993906d8",
   "metadata": {},
   "source": [
    "The :class:`Hist` stat will also create new `x` values (by binning) when given numeric data:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "25123abd-75d4-4550-ac86-5281fdabc023",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(penguins, x=\"flipper_length_mm\")\n",
    "    .add(so.Bars(), so.Hist())\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "0dd84c56-eeb3-4904-b957-1677eaebd33c",
   "metadata": {},
   "source": [
    "Notice how we used :class:`Bars`, rather than :class:`Bar` for the plot with the continuous `x` axis. These two marks are related, but :class:`Bars` has different defaults and works better for continuous histograms. It also produces a different, more efficient matplotlib artist. You will find the pattern of singular/plural marks elsewhere. The plural version is typically optimized for cases with larger numbers of marks.\n",
    "\n",
    "Some transforms accept both `x` and `y`, but add *interval* data for each coordinate. This is particularly relevant for plotting error bars after aggregating:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6bc29e9d-d660-4638-80fd-8d77e15d9109",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(penguins, x=\"body_mass_g\", y=\"species\", color=\"sex\")\n",
    "    .add(so.Range(), so.Est(errorbar=\"sd\"), so.Dodge())\n",
    "    .add(so.Dot(), so.Agg(), so.Dodge())\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "3aecc891-1abb-45b2-bf15-c6944820b242",
   "metadata": {},
   "source": [
    "Orienting marks and transforms\n",
    "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n",
    "\n",
    "When aggregating, dodging, and drawing a bar, the `x` and `y` variables are treated differently. Each operation has the concept of an *orientation*. The :class:`Plot` tries to determine the orientation automatically based on the data types of the variables. For instance, if we flip the assignment of `species` and `body_mass_g`, we'll get the same plot, but oriented horizontally:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1dd7ebeb-893e-4d27-aeaf-a8ff0cd2cc15",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(penguins, x=\"body_mass_g\", y=\"species\", color=\"sex\")\n",
    "    .add(so.Bar(), so.Agg(), so.Dodge())\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "382603cb-9ae9-46ed-bceb-b48456781092",
   "metadata": {},
   "source": [
    "Sometimes, the correct orientation is ambiguous, as when both the `x` and `y` variables are numeric. In these cases, you can be explicit by passing the `orient` parameter to :meth:`Plot.add`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "75277dda-47c4-443c-9454-b8d97fc399e2",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(tips, x=\"total_bill\", y=\"size\", color=\"time\")\n",
    "    .add(so.Bar(), so.Agg(), so.Dodge(), orient=\"y\")\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "dc845c14-03e5-495d-9dc8-3a90f7879346",
   "metadata": {},
   "source": [
    "Building and displaying the plot\n",
    "--------------------------------\n",
    "\n",
    "Most examples this far have produced a single subplot with just one kind of mark on it. But :class:`Plot` does not limit you to this.\n",
    "\n",
    "Adding multiple layers\n",
    "~~~~~~~~~~~~~~~~~~~~~~\n",
    "\n",
    "More complex single-subplot graphics can be created by calling :meth:`Plot.add` repeatedly. Each time it is called, it defines a *layer* in the plot. For example, we may want to add a scatterplot (now using :class:`Dots`) and then a regression fit:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "922b6d3d-7a81-4921-97f2-953a1fbc69ec",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(tips, x=\"total_bill\", y=\"tip\")\n",
    "    .add(so.Dots())\n",
    "    .add(so.Line(), so.PolyFit())\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "f0309733-a86a-4952-bc3b-533d639f0b52",
   "metadata": {},
   "source": [
    "Variable mappings that are defined in the :class:`Plot` constructor will be used for all layers:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "604d16b9-383b-4b88-9ed7-fdefed55039a",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(tips, x=\"total_bill\", y=\"tip\", color=\"time\")\n",
    "    .add(so.Dots())\n",
    "    .add(so.Line(), so.PolyFit())\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "eb56fb8d-aaa3-4b6e-b311-0354562174b5",
   "metadata": {},
   "source": [
    "Layer-specific mappings\n",
    "~~~~~~~~~~~~~~~~~~~~~~~\n",
    "\n",
    "You can also define a mapping such that it is used only in a specific layer. This is accomplished by defining the mapping within the call to :class:`Plot.add` for the relevant layer:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f69a3a38-97e8-40fb-b7d4-95a751ebdcfb",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(tips, x=\"total_bill\", y=\"tip\")\n",
    "    .add(so.Dots(), color=\"time\")\n",
    "    .add(so.Line(color=\".2\"), so.PolyFit())\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "b3f94f01-23d4-4f7a-98f8-de93dafc230a",
   "metadata": {},
   "source": [
    "Alternatively, define the layer for the entire plot, but *remove* it from a specific layer by setting the variable to `None`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "45706bec-3453-4a7e-9ac7-c743baff4da6",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(tips, x=\"total_bill\", y=\"tip\", color=\"time\")\n",
    "    .add(so.Dots())\n",
    "    .add(so.Line(color=\".2\"), so.PolyFit(), color=None)\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "295013b3-7d91-4a59-b63b-fe50e642954c",
   "metadata": {},
   "source": [
    "To recap, there are three ways to specify the value of a mark property: (1) by mapping a variable in all layers, (2) by mapping a variable in a specific layer, and (3) by setting the property directly:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2341eafd-4d6f-4530-835a-a409d2057d74",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "from io import StringIO\n",
    "from IPython.display import SVG\n",
    "C = sns.color_palette(\"deep\")\n",
    "f = mpl.figure.Figure(figsize=(7, 3))\n",
    "ax = f.subplots()\n",
    "fontsize = 18\n",
    "ax.add_artist(mpl.patches.Rectangle((.13, .53), .45, .09, color=C[0], alpha=.3))\n",
    "ax.add_artist(mpl.patches.Rectangle((.22, .43), .235, .09, color=C[1], alpha=.3))\n",
    "ax.add_artist(mpl.patches.Rectangle((.49, .43), .26, .09, color=C[2], alpha=.3))\n",
    "ax.text(.05, .55, \"Plot(data, 'x', 'y', color='var1')\", size=fontsize, color=\".2\")\n",
    "ax.text(.05, .45, \".add(Dot(pointsize=10), marker='var2')\", size=fontsize, color=\".2\")\n",
    "annots = [\n",
    "    (\"Mapped\\nin all layers\", (.35, .65), (0, 45)),\n",
    "    (\"Set directly\", (.35, .4), (0, -45)),\n",
    "    (\"Mapped\\nin this layer\", (.63, .4), (0, -45)),\n",
    "]\n",
    "for i, (text, xy, xytext) in enumerate(annots):\n",
    "    ax.annotate(\n",
    "        text, xy, xytext,\n",
    "        textcoords=\"offset points\", fontsize=14, ha=\"center\", va=\"center\",\n",
    "        arrowprops=dict(arrowstyle=\"->\", color=C[i]), color=C[i],\n",
    "    )\n",
    "ax.set_axis_off()\n",
    "f.subplots_adjust(0, 0, 1, 1)\n",
    "f.savefig(s:=StringIO(), format=\"svg\")\n",
    "SVG(s.getvalue())"
   ]
  },
  {
   "cell_type": "raw",
   "id": "cf2d8e39-d332-41f4-b327-2ac352878e58",
   "metadata": {},
   "source": [
    "Faceting and pairing subplots\n",
    "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n",
    "\n",
    "As with seaborn's figure-level functions (:func:`seaborn.displot`, :func:`seaborn.catplot`, etc.), the :class:`Plot` interface can also produce figures with multiple \"facets\", or subplots containing subsets of data. This is accomplished with the :meth:`Plot.facet` method:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "af737dfd-1cb2-418d-9f52-1deb93154a92",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(penguins, x=\"flipper_length_mm\")\n",
    "    .facet(\"species\")\n",
    "    .add(so.Bars(), so.Hist())\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "81c2a445-5ae1-4272-8a6c-8bfe1f3b907f",
   "metadata": {},
   "source": [
    "Call :meth:`Plot.facet` with the variables that should be used to define the columns and/or rows of the plot:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b7b3495f-9a38-4976-b718-ce3672b8c186",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(penguins, x=\"flipper_length_mm\")\n",
    "    .facet(col=\"species\", row=\"sex\")\n",
    "    .add(so.Bars(), so.Hist())\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "8b7fe085-acd2-46d2-81f6-a806dec338d3",
   "metadata": {},
   "source": [
    "You can facet using a variable with a larger number of levels by \"wrapping\" across the other dimension:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d62d2310-ae33-4b42-bdea-7b7456afd640",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(healthexp, x=\"Year\", y=\"Life_Expectancy\")\n",
    "    .facet(col=\"Country\", wrap=3)\n",
    "    .add(so.Line())\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "86ecbeee-3ac2-41eb-b79e-9d6ed026061d",
   "metadata": {},
   "source": [
    "All layers will be faceted unless you explicitly exclude them, which can be useful for providing additional context on each subplot:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c38be724-8564-4fa0-861c-1d96ffbbda20",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(healthexp, x=\"Year\", y=\"Life_Expectancy\")\n",
    "    .facet(\"Country\", wrap=3)\n",
    "    .add(so.Line(alpha=.3), group=\"Country\", col=None)\n",
    "    .add(so.Line(linewidth=3))\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "f97dad75-65e6-47fd-9fc4-08a8f2cb49ee",
   "metadata": {},
   "source": [
    "An alternate way to produce subplots is :meth:`Plot.pair`. Like :class:`seaborn.PairGrid`, this draws all of the data on each subplot, using different variables for the x and/or y coordinates:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d6350e99-2c70-4a96-87eb-74756a0fa335",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(penguins, y=\"body_mass_g\", color=\"species\")\n",
    "    .pair(x=[\"bill_length_mm\", \"bill_depth_mm\"])\n",
    "    .add(so.Dots())\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "4deea650-b4b9-46ea-876c-2e5a3a258649",
   "metadata": {},
   "source": [
    "You can combine faceting and pairing so long as the operations add subplots on opposite dimensions:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9de7948c-4c43-4116-956c-cbcb84d8652c",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(penguins, y=\"body_mass_g\", color=\"species\")\n",
    "    .pair(x=[\"bill_length_mm\", \"bill_depth_mm\"])\n",
    "    .facet(row=\"sex\")\n",
    "    .add(so.Dots())\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "0a0febe3-9daf-4271-aef9-9637d59aaf10",
   "metadata": {},
   "source": [
    "Integrating with matplotlib\n",
    "~~~~~~~~~~~~~~~~~~~~~~~~~~~\n",
    "\n",
    "There may be cases where you want multiple subplots to appear in a figure with a more complex structure than what :meth:`Plot.facet` or :meth:`Plot.pair` can provide. The current solution is to delegate figure setup to matplotlib and to supply the matplotlib object that :class:`Plot` should use with the :meth:`Plot.on` method. This object can be either a :class:`matplotlib.axes.Axes`, :class:`matplotlib.figure.Figure`, or :class:`matplotlib.figure.SubFigure`; the latter is most useful for constructing bespoke subplot layouts:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b046466d-f6c2-43fa-9ae9-f40a292a82b7",
   "metadata": {},
   "outputs": [],
   "source": [
    "f = mpl.figure.Figure(figsize=(8, 4))\n",
    "sf1, sf2 = f.subfigures(1, 2)\n",
    "(\n",
    "    so.Plot(penguins, x=\"body_mass_g\", y=\"flipper_length_mm\")\n",
    "    .add(so.Dots())\n",
    "    .on(sf1)\n",
    "    .plot()\n",
    ")\n",
    "(\n",
    "    so.Plot(penguins, x=\"body_mass_g\")\n",
    "    .facet(row=\"sex\")\n",
    "    .add(so.Bars(), so.Hist())\n",
    "    .on(sf2)\n",
    "    .plot()\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "7074f599-8b9f-4b77-9e15-55349592c747",
   "metadata": {},
   "source": [
    "Building and displaying the plot\n",
    "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n",
    "\n",
    "An important thing to know is that :class:`Plot` methods clone the object they are called on and return that clone instead of updating the object in place. This means that you can define a common plot spec and then produce several variations on it.\n",
    "\n",
    "So, take this basic specification:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b79b2148-b867-4e96-9b84-b3fc44ad0c82",
   "metadata": {},
   "outputs": [],
   "source": [
    "p = so.Plot(healthexp, \"Year\", \"Spending_USD\", color=\"Country\")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "135f89e5-c41e-4c6c-9865-5413787bdc62",
   "metadata": {},
   "source": [
    "We could use it to draw a line plot:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "10722a20-dc8c-4421-a433-8ff21fed9495",
   "metadata": {},
   "outputs": [],
   "source": [
    "p.add(so.Line())"
   ]
  },
  {
   "cell_type": "raw",
   "id": "f9db1184-f352-41b8-a45a-02ff6eb85071",
   "metadata": {},
   "source": [
    "Or perhaps a stacked area plot:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ea2ad629-c718-44a9-92af-144728094cd5",
   "metadata": {},
   "outputs": [],
   "source": [
    "p.add(so.Area(), so.Stack())"
   ]
  },
  {
   "cell_type": "raw",
   "id": "17fb2676-6199-4a2c-9f10-3d5aebb7a285",
   "metadata": {},
   "source": [
    "The :class:`Plot` methods are fully declarative. Calling them updates the plot spec, but it doesn't actually do any plotting. One consequence of this is that methods can be called in any order, and many of them can be called multiple times.\n",
    "\n",
    "When does the plot actually get rendered? :class:`Plot` is optimized for use in notebook environments. The rendering is automatically triggered when the :class:`Plot` gets displayed in the Jupyter REPL. That's why we didn't see anything in the example above, where we defined a :class:`Plot` but assigned it to `p` rather than letting it return out to the REPL.\n",
    "\n",
    "To see a plot in a notebook, either return it from the final line of a cell or call Jupyter's built-in `display` function on the object. The notebook integration bypasses :mod:`matplotlib.pyplot` entirely, but you can use its figure-display machinery in other contexts by calling :meth:`Plot.show`.\n",
    "\n",
    "You can also save the plot to a file (or buffer) by calling :meth:`Plot.save`."
   ]
  },
  {
   "cell_type": "raw",
   "id": "abfa0384-af88-4409-a119-912601a14f13",
   "metadata": {},
   "source": [
    "Customizing the appearance\n",
    "--------------------------\n",
    "\n",
    "The new interface aims to support a deep amount of customization through :class:`Plot`, reducing the need to switch gears and use matplotlib functionality directly. (But please be patient; not all of the features needed to achieve this goal have been implemented!)\n",
    "\n",
    "Parameterizing scales\n",
    "~~~~~~~~~~~~~~~~~~~~~\n",
    "\n",
    "All of the data-dependent properties are controlled by the concept of a :class:`Scale` and the :meth:`Plot.scale` method. This method accepts several different types of arguments. One possibility, which is closest to the use of scales in matplotlib, is to pass the name of a function that transforms the coordinates:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5acfe6d2-144a-462d-965b-2900fb619eac",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(diamonds, x=\"carat\", y=\"price\")\n",
    "    .add(so.Dots())\n",
    "    .scale(y=\"log\")\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "ccff884b-53cb-4c15-aab2-f5d4e5551d72",
   "metadata": {},
   "source": [
    ":meth:`Plot.scale` can also control the mappings for semantic properties like `color`. You can directly pass it any argument that you would pass to the `palette` parameter in seaborn's function interface:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4f243a31-d7da-43d2-8dc4-aad1b584ff48",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(diamonds, x=\"carat\", y=\"price\", color=\"clarity\")\n",
    "    .add(so.Dots())\n",
    "    .scale(color=\"flare\")\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "4fdf291e-a008-4a8e-8ced-a24f78d9b49f",
   "metadata": {},
   "source": [
    "Another option is to provide a tuple of `(min, max)` values, controlling the range that the scale should map into. This works both for numeric properties and for colors:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4cdc12ee-83f9-4472-b198-85bfe5cf0e4f",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(diamonds, x=\"carat\", y=\"price\", color=\"clarity\", pointsize=\"carat\")\n",
    "    .add(so.Dots())\n",
    "    .scale(color=(\"#88c\", \"#555\"), pointsize=(2, 10))\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "e326bf46-a296-4997-8e91-6531a7eef304",
   "metadata": {},
   "source": [
    "For additional control, you can pass a :class:`Scale` object. There are several different types of :class:`Scale`, each with appropriate parameters. For example, :class:`Continuous` lets you define the input domain (`norm`), the output range (`values`), and the function that maps between them (`trans`), while :class:`Nominal` allows you to specify an ordering:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "53682db4-2ba4-4dfd-80c2-1fef466cfab2",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(diamonds, x=\"carat\", y=\"price\", color=\"carat\", marker=\"cut\")\n",
    "    .add(so.Dots())\n",
    "    .scale(\n",
    "        color=so.Continuous(\"crest\", norm=(0, 3), trans=\"sqrt\"),\n",
    "        marker=so.Nominal([\"o\", \"+\", \"x\"], order=[\"Ideal\", \"Premium\", \"Good\"]),\n",
    "    )\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "7bf112fe-136d-4e63-a397-1e7d2ff4f543",
   "metadata": {},
   "source": [
    "Customizing legends and ticks\n",
    "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n",
    "\n",
    "The :class:`Scale` objects are also how you specify which values should appear as tick labels / in the legend, along with how they appear. For example, the :meth:`Continuous.tick` method lets you control the density or locations of the ticks, and the :meth:`Continuous.label` method lets you modify the format:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4f8e821f-bd19-4af1-bb66-488593b3c968",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(diamonds, x=\"carat\", y=\"price\", color=\"carat\")\n",
    "    .add(so.Dots())\n",
    "    .scale(\n",
    "        x=so.Continuous().tick(every=0.5),\n",
    "        y=so.Continuous().label(like=\"${x:.0f}\"),\n",
    "        color=so.Continuous().tick(at=[1, 2, 3, 4]),\n",
    "    )\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "4f6646c9-084b-49ae-ad6f-39c0bd12fc4e",
   "metadata": {},
   "source": [
    "Customizing limits, labels, and titles\n",
    "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n",
    "\n",
    ":class:`Plot` has a number of methods for simple customization, including :meth:`Plot.label`, :meth:`Plot.limit`, and :meth:`Plot.share`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e9586669-35ea-4784-9594-ea375a06aec0",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(penguins, x=\"body_mass_g\", y=\"species\", color=\"island\")\n",
    "    .facet(col=\"sex\")\n",
    "    .add(so.Dot(), so.Jitter(.5))\n",
    "    .share(x=False)\n",
    "    .limit(y=(2.5, -.5))\n",
    "    .label(\n",
    "        x=\"Body mass (g)\", y=\"\",\n",
    "        color=str.capitalize,\n",
    "        title=\"{} penguins\".format,\n",
    "    )\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "3b38607a-9b41-49c0-8031-e05bc87701c8",
   "metadata": {},
   "source": [
    "Theme customization\n",
    "~~~~~~~~~~~~~~~~~~~\n",
    "\n",
    "Finally, :class:`Plot` supports data-independent theming through the :class:`Plot.theme` method. Currently, this method accepts a dictionary of matplotlib rc parameters. You can set them directly and/or pass a package of parameters from seaborn's theming functions:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2df40831-fd41-4b76-90ff-042aecd694d4",
   "metadata": {},
   "outputs": [],
   "source": [
    "from seaborn import axes_style\n",
    "theme_dict = {**axes_style(\"whitegrid\"), \"grid.linestyle\": \":\"}\n",
    "so.Plot().theme(theme_dict)"
   ]
  },
  {
   "cell_type": "raw",
   "id": "475d5157-5e88-473e-991f-528219ed3744",
   "metadata": {},
   "source": [
    "To change the theme for all :class:`Plot` instances, update the settings in :attr:`Plot.config`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "41ac347c-766f-495c-8a7f-43fee8cad29a",
   "metadata": {},
   "outputs": [],
   "source": [
    "so.Plot.config.theme.update(theme_dict)"
   ]
  }
 ],
 "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.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
