16  Plots und Datenvisualisierung in Julia: Plots.jl

Es gibt zahlreiche Grafikpakete für Julia. Zwei oft genutzte sind Makie.jl und Plots.jl. Bevor wir diese genauer vorstellen, seien noch einige andere Pakete aufgelistet.

16.1 Kurze Übersicht: einige Grafikpakete

Paket/Doku Tutorial Beispiele Bemerkungen
Plots.jl Tutorial Galerie konzipiert als einheitliches Interface zu verschiedenen backends (Grafikbibliotheken)
Makie.jl Basic tutorial Beautiful Makie “data visualization ecosystem for Julia”, Backends: Cairo (Vektorgrafik), OpenGL, WebGL
PlotlyJS.jl Getting started Examples Interface zur Plotly Javascript-Grafikbibliothek
Gadfly.jl Tutorial Galerie “a plotting and data visualization system written in Julia, influenced by R’s ggplot2
Bokeh.jl Galerie Julia-Frontend für Bokeh
VegaLite.jl Tutorial Examples Julia-Frontend für Vega-Lite
Luxor.jl Tutorial Examples Allgemeine Vektorgrafik/Illustrationen
Javis.jl Tutorials Examples Animierte Vektorgrafik
TidierPlots.jl Reference “is a 100% Julia implementation of the R package ggplot2 powered by Makie.jl”
PythonPlot.jl Examples (in Python) Interface zu Matplotlib (Python), 1:1-Übertragung der Python-API, deswegen s. Matplotlib-Dokumentation

16.2 Plots.jl

16.2.1 Einfache Plots

Die plot()-Funktion erwartet im einfachsten Fall:

  • als erstes Argument einen Vektor von \(x\)-Werten der Länge \(n\) und
  • als zweites Argument einen gleichlangen Vektor mit den dazugehörigen \(y\)-Werten.
  • Das zweite Argument kann auch eine \(n\times m\)-Matrix sein. Dann wird jeder Spaltenvektor als eigener Graph (in der Docu series genannt) angesehen und es werden \(m\) Kurven geplottet:
using Plots

x  = range(0, 8π; length = 100)
sx = @. sin(x)         # the @. macro broadcasts (vectorizes) every operation
cx = @. cos(2x^(1/2))

plot(x, [sx cx])
  • Die Funktionen des Plots.jl-Paketes wie plot(), scatter(), contour(), heatmap(), histogram(), bar(),... usw. starten alle einen neuen Plot.
  • Die Versionen plot!(), scatter!(), contour!(), heatmap!(), histogram!(), bar!(),... erweitern einen existierenden Plot:
plot(x, sx)          # plot only sin(x)
plot!(x, cx)         # add second graph
plot!(x, sqrt.(x))   # add a thirth one

Plots sind Objekte, die zugewiesen werden können. Dann kann man sie später weiterverwenden, kopieren und insbesondere mit den !-Funktionen erweitern:

plot1 = plot(x, [sx cx])
plot1a = deepcopy(plot1)   # plot objects are quite deep structures
scatter!(plot1, x, sx)     # add scatter plot, i.e. unconnected data points

Die kopierte Version plot1a ist durch die scatter!-Anweisung nicht modifiziert worden und kann unabhängig weiterverwendet werden:

plot!(plot1a, x, 2 .* sx)

Plot-Objekte kann man als Grafikdateien (PDF, SVG, PNG,…) abspeichern:

savefig(plot1, "plot.png")
"/home/hellmund/Julia/23/Book-ansipatch/chapters/plot.png"
;ls -l plot.png
-rw-r--r-- 1 hellmund hellmund 56158 Mar  3 17:09 plot.png

Plot-Objekte können auch als Teilplot in andere Plots eingefügt werden, siehe Abschnitt Kapitel 16.2.7.

16.2.2 Funktionsplots

Man kann plot() auch eine Funktion und einen Vektor mit \(x\)-Werten übergeben:

# https://mzrg.com/math/graphs.shtml

f(x) = abs(sin(x^x)/2^((x^x-π/2)/π))

plot(f, 0:0.01:3)

Die parametrische Form \(x = x(t),\ y = y(t)\) kann durch die Übergabe von zwei Funktionen und einen Vektor von \(t\)-Werten an plot() gezeichnet werden.

# https://en.wikipedia.org/wiki/Butterfly_curve_(transcendental)

xt(t) = sin(t) * (exp(cos(t))-2cos(4t)-sin(t/12)^5)
yt(t) = cos(t) * (exp(cos(t))-2cos(4t)-sin(t/12)^5) 

plot(xt, yt, 0:0.01:12π)

16.2.3 Plot-Themen

“PlotThemes is a package to spice up the plots made with Plots.jl.”
Hier geht es zur illustrierten Liste der Themen

oder:

using PlotThemes

# Liste der Themen
keys(PlotThemes._themes)
KeySet for a Dict{Symbol, PlotTheme} with 21 entries. Keys:
  :juno
  :default
  :dao
  :ggplot2
  :gruvbox_dark
  :dark
  :gruvbox_light
  :solarized
  :wong
  :dracula
  :solarized_light
  :mute
  :boxed
  :rose_pine
  :wong2
  :sand
  :lime
  :rose_pine_dawn
  :bright
  ⋮
Plots.showtheme(:juno)
using PlotThemes

theme(:juno)   # set a theme for all further plots

plot(x, [sx  cx  1 ./ (1 .+ x)])

16.2.4 Plot-Attribute

Die Funktionen des Plots.jl-Paketes haben eine große Anzahl von Optionen. Plots.jl teilt die Attribute in 4 Gruppen ein:

plotattr(:Plot)  # Attribute für den Gesamtplot

Defined Plot attributes are: background_color, background_color_outside, display_type, dpi, extra_kwargs, extra_plot_kwargs, fontfamily, foreground_color, html_output_format, inset_subplots, layout, link, overwrite_figure, plot_title, plot_titlefontcolor, plot_titlefontfamily, plot_titlefonthalign, plot_titlefontrotation, plot_titlefontsize, plot_titlefontvalign, plot_titleindex, plot_titlelocation, plot_titlevspan, pos, show, size, tex_output_standalone, thickness_scaling, warn_on_unsupported, window_title

plotattr(:Subplot) # Attribute für einen Teilplot

Defined Subplot attributes are: annotationcolor, annotationfontfamily, annotationfontsize, annotationhalign, annotationrotation, annotations, annotationvalign, aspect_ratio, background_color_inside, background_color_subplot, bottom_margin, camera, clims, color_palette, colorbar, colorbar_continuous_values, colorbar_discrete_values, colorbar_fontfamily, colorbar_formatter, colorbar_scale, colorbar_tickfontcolor, colorbar_tickfontfamily, colorbar_tickfonthalign, colorbar_tickfontrotation, colorbar_tickfontsize, colorbar_tickfontvalign, colorbar_ticks, colorbar_title, colorbar_title_location, colorbar_titlefontcolor, colorbar_titlefontfamily, colorbar_titlefonthalign, colorbar_titlefontrotation, colorbar_titlefontsize, colorbar_titlefontvalign, extra_kwargs, fontfamily_subplot, foreground_color_subplot, foreground_color_title, framestyle, left_margin, legend_background_color, legend_column, legend_font, legend_font_color, legend_font_family, legend_font_halign, legend_font_pointsize, legend_font_rotation, legend_font_valign, legend_foreground_color, legend_position, legend_title, legend_title_font, legend_title_font_color, legend_title_font_family, legend_title_font_halign, legend_title_font_pointsize, legend_title_font_rotation, legend_title_font_valign, margin, projection, projection_type, right_margin, subplot_index, title, titlefontcolor, titlefontfamily, titlefonthalign, titlefontrotation, titlefontsize, titlefontvalign, titlelocation, top_margin

plotattr(:Axis)  # Attribute für eine Achse

Defined Axis attributes are: discrete_values, draw_arrow, flip, foreground_color_axis, foreground_color_border, foreground_color_grid, foreground_color_guide, foreground_color_minor_grid, foreground_color_text, formatter, grid, gridalpha, gridlinewidth, gridstyle, guide, guide_position, guidefontcolor, guidefontfamily, guidefonthalign, guidefontrotation, guidefontsize, guidefontvalign, lims, link, minorgrid, minorgridalpha, minorgridlinewidth, minorgridstyle, minorticks, mirror, rotation, scale, showaxis, tick_direction, tickfontcolor, tickfontfamily, tickfonthalign, tickfontrotation, tickfontsize, tickfontvalign, ticks, unitformat, widen

plotattr(:Series) # Attribute für eine Serie, also zB ein Linienzug im Plot

Defined Series attributes are: arrow, bar_edges, bar_position, bar_width, bins, colorbar_entry, connections, contour_labels, contours, extra_kwargs, fill_z, fillalpha, fillcolor, fillrange, fillstyle, group, hover, label, levels, line_z, linealpha, linecolor, linestyle, linewidth, marker_z, markeralpha, markercolor, markershape, markersize, markerstrokealpha, markerstrokecolor, markerstrokestyle, markerstrokewidth, normalize, orientation, permute, primary, quiver, ribbon, series_annotations, seriesalpha, seriescolor, seriestype, show_empty_bins, smooth, stride, subplot, weights, x, xerror, y, yerror, z, z_order, zerror

Man kann auch nachfragen, was die einzelnen Attribute bedeuten und welche Werte zulässig sind:

plotattr("linestyle")
:linestyle

Style of the line (for path and bar stroke). Choose from [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]

Aliases: (:linestyles, :ls, :s, :style).

Type: Symbol.

`Series` attribute, defaults to `solid`.

Ein Beispiel:

theme(:default)  # zurück zum Standardthema 

x = 0:0.05:1
y = sin.(2π*x)

plot(x, y, seriestype = :sticks, linewidth = 4, seriescolor = "#00b300",
        marker = :circle, markersize = 8, markercolor = :green,
)

Viele Angaben können auch sehr weit abgekürzt werden, siehe z.B. die Angabe Aliases: in der obigen Ausgabe des Kommandos plotattr("linestyle").

Das folgende plot()-Kommando is äquivalent zum vorherigen:

plot(x, y, t = :sticks, w = 4, c = "#00b300", m = (:circle, 8, :green ))

16.2.5 Weitere Extras

using Plots                # Wiederholung schadet nicht
using Plots.PlotMeasures   # für Maßangaben in mm, cm,...
using LaTeXStrings         # für LaTeX-Konstrukte in Plot-Beschriftungen
using PlotThemes           # vorgefertigte Themen

Das Paket LaTeXStrings.jl stellt einen String-Konstruktor L"..." zur Verfügung. Diese Strings können LaTeX-Konstrukte, insbesondere Formeln, enthalten. Wenn der String keine expliziten Dollarzeichen enthält, wird er automatisch im LaTeX-Math-Modus interpretiert.

xs = range(0, 2π, length = 100)

data = [sin.(xs) cos.(xs) 2sin.(xs) (x->sin(x^2)).(xs)]   # 4 Funktionen

theme(:ggplot2)

plot10 = plot(xs, data,
           fontfamily="Computer Modern",
    
           # LaTeX-String L"..."   
           title = L"Winkelfunktionen $\sin(\alpha), \cos(\alpha), 2\sin(\alpha), \sin(\alpha^2)$",
           xlabel = L"Winkel $\alpha$",
           ylabel = "Funktionswert", 
    
           # 1x4-Matrizen mit Farben, Marker,...  für die 4 'Series' 
           color=[:black :green  RGB(0.3, 0.8, 0.2) :blue ],
           markers = [:rect :circle :utriangle :diamond],
           markersize = [2 1 0 4],
           linewidth = [1 3 1 2],
           linestyle = [:solid :dash :dot :solid ],
           
           # Achsen
           xlim = (0, 6.6),
           ylim = (-2, 2.3),
           yticks = -2:.4:2.3,       # mit Schrittweite
           
           # Legende 
           legend = :bottomleft,
           label = [ L"\sin(\alpha)" L"\cos(\alpha)" L"2\sin(\alpha)"  L"\sin(\alpha^2)"],
    
           top_margin = 5mm,     # hier wird Plots.PlotMeasures gebraucht
)

# Zusatztext:  annotate!(x-pos, y-pos, text("...", font, fontsize))

annotate!(plot10, 4.1, 1.8, text("nicht schön, aber viel","Computer Modern", 10) )

16.2.6 Andere Plot-Funktionen

Bisher haben wir vor allem Linien geplottet. Es gibt noch viele andere Typen wie scatter plot, contour, heatmap, histogram, stick,…

Dies kann man mit dem seriestype-Attribut steuern:

theme(:default)

x = range(0, 2π; length = 50)
plot(x, sin.(x), seriestype=:scatter)

oder indem man die spezielle Funktion benutzt, die so heißt wie der seriestype:

x = range(0, 2π; length = 50)
scatter(x, sin.(x))

16.2.7 Subplots und Layout

Mehrere Plots können zu einer Abbildung zusammengefasst werden. Die Anordnung bestimmt der layout-Parameter. Dabei bedeutet layout=(m,n), dass die Plots in einem \(m\times n\)-Schema angeordnet werden:

x = range(0, 2π; length = 100)
plots = []                    # vector of plot objects
for f in [sin, cos, tan, sinc]
   p = plot(x, f.(x))
   push!(plots, p)
end
plot(plots..., layout=(2,2), legend=false, title=["sin" "cos" "tan" "sinc"])
plot(plots..., layout=(4,1), legend=false, title=["sin" "cos" "tan" "sinc"])

Man kann Layouts auch schachteln und mit dem @layout-Macro explizite Breiten/Höhenanteile vorgeben:

mylayout = @layout [
               a{0.3w} [ b
                         c{0.2h}  ]  
               d{0.2h}
            ]

plot(plots..., layout=mylayout, legend=false, title=["sin" "cos" "tan" "sinc"])

16.2.8 Backends

Plots.jl ist konzipiert als ein einheitliches Interface zu verschiedenen backends (Grafik-Engines). Man kann zu einem anderen Backend wechseln und dieselben Plot-Kommandos und -Attribute verwenden.

Allerdings unterstützen nicht alle backends alle Plot-Typen und -Attribute. Einen Überblick gibt es hier.

Bisher wurde das Standard-Backend verwendet. Es heißt GR und ist eine am Forschungszentrum Jülich entwickelte und hauptsächlich in C geschriebene Grafik-Engine.

using Plots
backend()      # Anzeige des gewählten backends, GR ist der default
Plots.GRBackend()

Nochmal ein Beispiel

x = 1:30
y = rand(30)
plot(x, y, linecolor =:green, bg_inside =:lightblue1, line =:solid, label = "Wasserstand")

und hier derselbe Plot mit dem PlotlyJS-Backend.

plotlyjs()       # change plots backend
plot(x, y, linecolor =:green, bg_inside =:lightblue1, line =:solid, label = "Wasserstand")

Dieses Backend ermöglich mit Hilfe von Javascript eine gewisse Interaktivität. Wenn man die Maus in das Bild bewegt, kann man mit der Maus zoomen, verschieben und 3D-Plots auch drehen.

gr() # zurück zu GR als backend
Plots.GRBackend()

16.2.9 3D Plots

Die Funktionen surface() und contour() ermöglichen den Plot einer Funktion \(f(x,y)\). Als Argumente werden benötigt:

  • eine Menge (Vektor) \(X\) von \(x\)-Werten,
  • eine Menge (Vektor) \(Y\) von \(y\)-Werten und
  • eine Funktion von zwei Variablen, die dann auf \(X \times Y\) ausgewertet und geplottet wird.
f(x,y) = (1 - x/2 + x^5 + y^3) * exp(-x^2 - y^2)

surface( -3:0.02:3, -3:0.02:3,  f)
contour( -3:0.02:3, -3:0.02:3,  f, fill=true, colormap=:summer, levels=20, contour_labels=false)

Kurven (oder auch einfach Punktmengen) in drei Dimensionen lassen sich plotten, indem man plot() mit 3 Vektoren aufruft, die jeweils die \(x\), \(y\) und \(z\)-Koordinaten der Datenpunkte enthalten.

plotlyjs()

t = range(0, stop=8π, length=100)    # parameter t
x = @. t * cos(t)                    # x(t), y(t), z(t) 
y = @. 0.1 * t * sin(t)
z = @. 100 * t/8π
plot(x, y, z, zcolor=reverse(z), markersize=3, markershape= :circle, 
              linewidth=5, legend=false, colorbar=false)

Wir verwenden mal das plotlyjs-Backend, damit ist der Plot interaktiv und kann mit der Maus gedreht und gezoomt werden.

16.2.10 Plots.jl und recipes

Andere Pakete können die Möglichkeiten von Plots.jl erweitern, indem sie sogenannte recipes für spezielle Plots und Datenstrukturen definieren, siehe https://docs.juliaplots.org/latest/ecosystem/, z.B.:

16.2.11 Ein Säulendiagramm

Für das letzte Beispiel laden wir ein Paket, das über 700 freie (“public domain”) Datensätze, darunter z.B:

  • die Passagierliste der Titanic,
  • Verbrauchsdaten amerikanischer Autos aus den 70ern oder
  • historische Währungskurse

bereitstellt:

using RDatasets

Der Datensatz “Motor Trend Car Road Tests”

cars = dataset("datasets", "mtcars")
32×12 DataFrame
7 rows omitted
Row Model MPG Cyl Disp HP DRat WT QSec VS AM Gear Carb
String31 Float64 Int64 Float64 Int64 Float64 Float64 Float64 Int64 Int64 Int64 Int64
1 Mazda RX4 21.0 6 160.0 110 3.9 2.62 16.46 0 1 4 4
2 Mazda RX4 Wag 21.0 6 160.0 110 3.9 2.875 17.02 0 1 4 4
3 Datsun 710 22.8 4 108.0 93 3.85 2.32 18.61 1 1 4 1
4 Hornet 4 Drive 21.4 6 258.0 110 3.08 3.215 19.44 1 0 3 1
5 Hornet Sportabout 18.7 8 360.0 175 3.15 3.44 17.02 0 0 3 2
6 Valiant 18.1 6 225.0 105 2.76 3.46 20.22 1 0 3 1
7 Duster 360 14.3 8 360.0 245 3.21 3.57 15.84 0 0 3 4
8 Merc 240D 24.4 4 146.7 62 3.69 3.19 20.0 1 0 4 2
9 Merc 230 22.8 4 140.8 95 3.92 3.15 22.9 1 0 4 2
10 Merc 280 19.2 6 167.6 123 3.92 3.44 18.3 1 0 4 4
11 Merc 280C 17.8 6 167.6 123 3.92 3.44 18.9 1 0 4 4
12 Merc 450SE 16.4 8 275.8 180 3.07 4.07 17.4 0 0 3 3
13 Merc 450SL 17.3 8 275.8 180 3.07 3.73 17.6 0 0 3 3
21 Toyota Corona 21.5 4 120.1 97 3.7 2.465 20.01 1 0 3 1
22 Dodge Challenger 15.5 8 318.0 150 2.76 3.52 16.87 0 0 3 2
23 AMC Javelin 15.2 8 304.0 150 3.15 3.435 17.3 0 0 3 2
24 Camaro Z28 13.3 8 350.0 245 3.73 3.84 15.41 0 0 3 4
25 Pontiac Firebird 19.2 8 400.0 175 3.08 3.845 17.05 0 0 3 2
26 Fiat X1-9 27.3 4 79.0 66 4.08 1.935 18.9 1 1 4 1
27 Porsche 914-2 26.0 4 120.3 91 4.43 2.14 16.7 0 1 5 2
28 Lotus Europa 30.4 4 95.1 113 3.77 1.513 16.9 1 1 5 2
29 Ford Pantera L 15.8 8 351.0 264 4.22 3.17 14.5 0 1 5 4
30 Ferrari Dino 19.7 6 145.0 175 3.62 2.77 15.5 0 1 5 6
31 Maserati Bora 15.0 8 301.0 335 3.54 3.57 14.6 0 1 5 8
32 Volvo 142E 21.4 4 121.0 109 4.11 2.78 18.6 1 1 4 2

Wir brauchen für den Plot nur die beiden Spalten cars.Model und cars.MPG, den Benzinverbrauch in miles per gallon (Mehr heißt sparsamer!)

theme(:bright)

bar(cars.Model, cars.MPG,
           label = "Miles/Gallon",
           title = "Models and Miles/Gallon",
           xticks =:all,
           xrotation = 45,
           size = [600, 400],
           legend =:topleft,
           bottom_margin = 10mm
   )

16.2.12 Was noch fehlt: Animation

Hier sei auf die Dokumentation verwiesen und nur ein Beispiel (von https://www.juliafordatascience.com/animations-with-plots-jl/) angegeben:

using Plots, Random
theme(:default)

anim = @animate for i in 1:50
    Random.seed!(123)
    scatter(cumsum(randn(i)), ms=i, lab="", alpha = 1 - i/50, 
        xlim=(0,50), ylim=(-5, 7))
end

gif(anim, fps=50)