plotnine-Beispiele#

Einfaches Streudiagramm#

  1. Importe

[1]:
from plotnine import *
from plotnine.data import mtcars
  1. Streudiagramm

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

plotnine.mapping.aes erstellt ästhetische Zuordnungen mit Meilen je Gallone mpg auf der y-Achse und Gewicht der Autos wt auf der x-Achse. plotnine.geoms.geom_point erstellt dann ein Streudiagramm.

  1. Farbliche Unterscheidung der Variablen

[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. Geglättetes lineares Modell mit Konfidenzintervallen

    Mit plotnine.stats.stat_smooth lassen sich geglättete bedingte Mittelwerte berechnen, wobei lm ein lineares Modell zugrunde liegt:

[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. Darstellung in separierten Feldern

    Mit plotnine.facets.facet_wrap lassen sich die Felder trennen.

[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)>

Interaktive Diagramme#

Zusammen mit ipywidgets lassen sich auch interaktive Diagramme erstellen.

  1. Importe

[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. Interaktives Streudiagramm erstellen

    Im folgenden betrachten wir PS auf der x-Achse, Meilen je Gallone auf der y-Achse und unterscheiden farblich das Gewicht der Autos:

[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. Nun wählen wir die Autos anhand der Zylinderanzahl aus.

    1. Zunächst bereiten wir die Liste vor, die wir verwenden werden, um Teilmengen von Daten auf der Grundlage der Anzahl von Zylindern auszuwählen:

[8]:
cylList = np.unique(mtcars["cyl"])
    1. Diese Liste verwenden wir nun für ein Dropdown-Menü mit der Anzahl der Zylinder:

[9]:
cylSelect = widgets.Dropdown(
    options=list(cylList),
    value=cylList[1],
    description="Cylinders:",
    disabled=False,
)
  1. Die Widgets sollen dieselbe Darstellung aktualisieren können und nicht bei jeder Änderung einen neuen Plot erstellen.

    1. Zunächst ermitteln wir die maximalen Bereiche der relevanten Variablen, um die Achsen bei Aktualisierungen konstant zu halten.

      Mit minWt und maxWt erhalten wir die Spanne der Gewichte.

      Mit wtOptions wandeln wir das NumPy-Array in eine sortierte Python-Liste von eindeutigen Werte um.

      In cylSelect wird die erste Auswahl für das Dropdown-Menü der Zylinder-Anzahl festgelegt.

[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. Mit get_current_artists ermitteln wir dann alle Objekte, die gerendert werden sollen:

[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. Nun erstellen wir die Funktion plotUpdate, die aufgerufen wird, um die Darstellung jedes Mal zu aktualisieren, wenn wir eine Auswahl ändern.

[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. Nun beobachten wir, ob sich der Wert in unserem Dropdown-Menü der Zylinder-Anzahl ändert:

[13]:
cylSelect.observe(plotUpdate, "value")
    1. Mit display wird das Widget dargestellt:

[14]:
display(cylSelect)
    1. Nun plotten wir das erste Bild mit Anfangswerten.

[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