plotnine examples

Simple scatter plot

  1. Imports

[1]:
from plotnine import *
from plotnine.data import mtcars
  1. Scatter plot

[2]:
(
    ggplot(mtcars, aes("wt", "mpg"))
    + geom_point()
)
../../_images/matplotlib_plotnine_examples_5_0.png
[2]:
<Figure Size: (640 x 480)>

plotnine.aes creates aesthetic mappings with miles per gallon mpg on the y-axis and weight of cars wt on the x-axis. plotnine.geom_point then creates a scatter plot.

  1. Colour differentiation of the variables

[3]:
(
    ggplot(mtcars, aes("wt", "mpg", color="factor(gear)"))
    + geom_point()
)
../../_images/matplotlib_plotnine_examples_8_0.png
[3]:
<Figure Size: (640 x 480)>
  1. Smoothed linear model with confidence intervals

    With plotnine.stats.stat_smooth, smoothed conditional means can be calculated, whereby lm is based on a linear model:

[4]:
(
    ggplot(mtcars, aes("wt", "mpg", color="factor(gear)"))
    + geom_point()
    + stat_smooth(method="lm")
)
../../_images/matplotlib_plotnine_examples_10_0.png
[4]:
<Figure Size: (640 x 480)>
  1. Display in separated fields

    The fields can be separated with plotnine.facet_wrap.

[5]:
(
    ggplot(mtcars, aes("wt", "mpg", color="factor(gear)"))
    + geom_point()
    + stat_smooth(method="lm")
    + facet_wrap("~gear")
)
../../_images/matplotlib_plotnine_examples_12_0.png
[5]:
<Figure Size: (640 x 480)>

Interactive diagrams

Interactive diagrams can also be created with ipywidgets.

  1. Imports

[6]:
import itertools

from copy import copy

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import plotnine as p9

from IPython.display import display
from ipywidgets import widgets
from plotnine.data import mtcars
  1. Create an interactive scatter diagram

    In the following we look at horsepower on the x-axis, miles per gallon on the y-axis and differentiate the weight of the cars by colour:

[7]:
%matplotlib notebook

p = (ggplot(mtcars, aes(x="hp", y="mpg", color="wt"))
     + p9.geom_point()
     + p9.theme_linedraw()
    )
p
[7]:
<Figure Size: (640 x 480)>
  1. Now we select the cars based on the number of cylinders.

    1. First, we prepare the list that we will use to select subsets of data based on the number of cylinders:

[8]:
cylList = np.unique(mtcars["cyl"])
    1. We will now use this list for a drop-down menu with the number of cylinders:

[9]:
cylSelect = widgets.Dropdown(
    options=list(cylList),
    value=cylList[1],
    description="Cylinders:",
    disabled=False,
)
  1. The widgets should be able to update the same display and not create a new plot every time a change is made.

    1. First, we determine the maximum ranges of the relevant variables in order to keep the axes constant during updates.

      With minWt and maxWt we obtain the range of the weights.

      With wtOptions we convert the NumPy array into a sorted Python list of unique values.

      In cylSelect we define the first selection for the dropdown menu of the number of cylinders.

[10]:
minWt = min(mtcars["wt"])
maxWt = max(mtcars["wt"])
wtOptions = list(
    np.sort(np.unique(mtcars.loc[mtcars["cyl"] == cylList[0], "wt"]))
)

minHP = min(mtcars["hp"])
maxHP = max(mtcars["hp"])

minMPG = min(mtcars["mpg"])
maxMPG = max(mtcars["mpg"])

cylSelect = widgets.Dropdown(
    options=list(cylList),
    value=cylList[1],
    description="Cylinders:",
    disabled=False,
)
    1. We then use get_current_artists to determine all the objects that are to be rendered:

[11]:
def get_current_artists():
    # Return artists attached to all the matplotlib axes
    axes = plt.gca()
    return itertools.chain(
        axes.lines, axes.collections, axes.artists, axes.patches, axes.texts
    )
    1. Now we create the plotUpdate function, which is called to update the plot every time we change a selection.

[12]:
fig = None
axs = None


def plotUpdate(*args):
    # Use global variables for matplotlib’s figure and axis.
    global fig, axs

    # Get current values of the selection widget
    cylValue = cylSelect.value

    # Create a temporary dataset that is constrained by the user's selections.
    tmpDat = mtcars.loc[(mtcars["cyl"] == cylValue), :]

    # Create plotnine's plot

    # Using the maximum and minimum values we gatehred before, we can keep the plot axis from
    # changing with the cyinder selection
    p = (
        ggplot(tmpDat, aes(x="hp", y="mpg", color="wt"))
        + p9.geom_point()
        + p9.theme_linedraw()
    )
    if fig is None:
        # If this is the first time a plot is made in the notebook, we let plotnine create a new
        # matplotlib figure and axis.
        fig = p.draw()
        axs = fig.axes
    else:
        # p = copy(p)
        # This helps keeping old selected data from being visualized after a new selection is made.
        # We delete all previously reated artists from the matplotlib axis.
        for artist in get_current_artists():
            artist.remove()

        # If a plot is being updated, we re-use the figure an axis created before.
        p._draw_using_figure(fig, axs)
    1. Now we observe whether the value in our drop-down menu for the number of cylinders changes:

[13]:
cylSelect.observe(plotUpdate, "value")
    1. The widget is displayed with display:

[14]:
display(cylSelect)
    1. Now we plot the first image with initial values.

[15]:
plotUpdate()
    1. Mit der Matplotlib-Funktion tight_layout() passen wir die Figur an die Abmessungen des Plots an:

[16]:
plt.tight_layout()
/var/folders/hk/s8m0bblj0g10hw885gld52mc0000gn/T/ipykernel_23701/2925123646.py:2: UserWarning: The figure layout has changed to tight
    1. Trick, damit das erste gerenderte Bild dem tight_layout entspricht. Ohne diesen Befehl würde die Figur erst nach der ersten Aktualisierung in ihre Abmessungen passen.

[17]:
cylSelect.value = cylList[0]
  1. Bereichsregler hinzufügen

    Nun schränken wir mit einem Bereichsregler die Daten basierend auf dem Fahrzeuggewicht ein:

[18]:
# The first selection is a drop-down menu for number of cylinders
cylSelect = widgets.Dropdown(
    options=list(cylList),
    value=cylList[1],
    description="Cylinders:",
    disabled=False,
)

# The second selection is a range of weights
wtSelect = widgets.SelectionRangeSlider(
    options=wtOptions,
    index=(0, len(wtOptions) - 1),
    description="Weight",
    disabled=False,
)

widgetsCtl = widgets.HBox([cylSelect, wtSelect])


# The range of weights needs to always be dependent on the cylinder selection.
def updateRange(*args):
    """Updates the selection range from the slider depending on the cylinder selection."""
    cylValue = cylSelect.value

    wtOptions = list(
        np.sort(np.unique(mtcars.loc[mtcars["cyl"] == cylValue, "wt"]))
    )

    wtSelect.options = wtOptions
    wtSelect.index = (0, len(wtOptions) - 1)


cylSelect.observe(updateRange, "value")

# For the widgets to update the same plot, instead of creating one new image every time
# a selection changes. We keep track of the matplotlib image and axis, so we create only one
# figure and set of axis, for the first plot, and then just re-use the figure and axis
# with plotnine's "_draw_using_figure" function.
fig = None
axs = None


# This is the main function that is called to update the plot every time we chage a selection.
def plotUpdate(*args):
    # Use global variables for matplotlib's figure and axis.
    global fig, axs

    # Get current values of the selection widgets
    cylValue = cylSelect.value
    wrRange = wtSelect.value

    # Create a temporary dataset that is constrained by the user's selections.
    tmpDat = mtcars.loc[
        (mtcars["cyl"] == cylValue)
        & (mtcars["wt"] >= wrRange[0])
        & (mtcars["wt"] <= wrRange[1]),
        :,
    ]

    # Create plotnine's plot

    p = (
        ggplot(tmpDat, aes(x="hp", y="mpg", color="wt"))
        + p9.geom_point()
        + p9.theme_linedraw()
        + p9.lims(x=[minHP, maxHP], y=[minMPG, maxMPG])
        + p9.scale_color_continuous(limits=(minWt, maxWt))
    )

    if fig is None:
        fig = p.draw()
        axs = fig.axes
    else:
        for artist in get_current_artists():
            artist.remove()
        p._draw_using_figure(fig, axs)


cylSelect.observe(plotUpdate, "value")
wtSelect.observe(plotUpdate, "value")

# Display the widgets
display(widgetsCtl)

# Plots the first image, with inintial values.
plotUpdate()

# Matplotlib function to make the image fit within the plot dimensions.
plt.tight_layout()

# Trick to get the first rendered image to follow the previous "tight_layout" command.
# without this, only after the first update would the figure be fit inside its dimensions.
cylSelect.value = cylList[0]
/var/folders/hk/s8m0bblj0g10hw885gld52mc0000gn/T/ipykernel_23701/1448775928.py:89: UserWarning: The figure layout has changed to tight
  1. Diagramm optimieren

    Schließlich ändern wir noch einige Diagrammeigenschaften, um eine verständlichere Abbildung zu erhalten:

[19]:
# The first selection is a drop-down menu for number of cylinders
cylSelect = widgets.Dropdown(
    options=list(cylList),
    value=cylList[1],
    description="Cylinders:",
    disabled=False,
)

# The second selection is a range of weights
wtSelect = widgets.SelectionRangeSlider(
    options=wtOptions,
    index=(0, len(wtOptions) - 1),
    description="Weight",
    disabled=False,
)

widgetsCtl = widgets.HBox([cylSelect, wtSelect])


# The range of weights needs to always be dependent on the cylinder selection.
def updateRange(*args):
    """Updates the selection range from the slider depending on the cylinder selection."""
    cylValue = cylSelect.value

    wtOptions = list(
        np.sort(np.unique(mtcars.loc[mtcars["cyl"] == cylValue, "wt"]))
    )

    wtSelect.options = wtOptions
    wtSelect.index = (0, len(wtOptions) - 1)


cylSelect.observe(updateRange, "value")

fig = None
axs = None


# This is the main function that is called to update the plot every time we chage a selection.
def plotUpdate(*args):
    # Use global variables for matplotlib's figure and axis.
    global fig, axs

    # Get current values of the selection widgets
    cylValue = cylSelect.value
    wrRange = wtSelect.value

    # Create a temporary dataset that is constrained by the user's selections of
    # number of cylinders and weight.
    tmpDat = mtcars.loc[
        (mtcars["cyl"] == cylValue)
        & (mtcars["wt"] >= wrRange[0])
        & (mtcars["wt"] <= wrRange[1]),
        :,
    ]

    # Create plotnine's plot showing all data ins smaller grey points, and
    # the selected data with coloured points.
    p = (
        ggplot(tmpDat, aes(x="hp", y="mpg", color="wt"))
        + p9.geom_point(mtcars, color="grey")
        + p9.geom_point(size=3)
        + p9.theme_linedraw()
        + p9.xlim([minHP, maxHP])
        + p9.ylim([minMPG, maxMPG])
        + p9.scale_color_continuous(
            name="spring", limits=(np.floor(minWt), np.ceil(maxWt))
        )
        + p9.labs(x="Horse-Power", y="Miles Per Gallon", color="Weight")
    )

    if fig is None:
        fig = p.draw()
        axs = fig.axes
    else:
        for artist in get_current_artists():
            artist.remove()
        p._draw_using_figure(fig, axs)


cylSelect.observe(plotUpdate, "value")
wtSelect.observe(plotUpdate, "value")

# Display the widgets
display(widgetsCtl)

# Plots the first image, with inintial values.
plotUpdate()

# Matplotlib function to make the image fit within the plot dimensions.
plt.tight_layout()

# Trick to get the first rendered image to follow the previous "tight_layout" command.
# without this, only after the first update would the figure be fit inside its dimensions.
cylSelect.value = cylList[0]
/var/folders/hk/s8m0bblj0g10hw885gld52mc0000gn/T/ipykernel_23701/207462609.py:91: UserWarning: The figure layout has changed to tight