Short Introduction to Plots.jl

Idea

Plots.jl is a non-traditional plotting library

  • It does not implement a "plotting backend" itself, it's a plotting API
  • The API is easily extendable via recipes

Documentation

The rapidly growing documentation is at http://docs.juliaplots.org/latest/

Backends

Plots.jl uses other plotting libraries as backends

  • PyPlot (matplotlib): Slow but dependable. Matplotlib is a very mature product. However it can be hard to install it at times.
  • GR: Feature-rich and fast, new, easy to install. Default option.
  • Plotly/PlotlyJS: Interactive and good for web
  • PGFPlots: Native LaTeX rendering
  • UnicodePlots: Plots to unicode for no-display situations

  • Each of those is a separate plotting package, with a separate API. Each has their strengths and weaknesses. However, you'd have to learn 5 API's to use them all. Plots.jl reduces that to 1 API.

  • If there is some fine grained functionality that doesn't work with Plots, you could always use the respective plotting package directly.

We will mostly rely on the default option, which is GR. More about Backends.

In [2]:
using Plots
gr()
plot(rand(10,4))  # will use the default backend: GR
Out[2]:
2 4 6 8 10 0.2 0.4 0.6 0.8 1.0 y1 y2 y3 y4
In [3]:
pyplot()  # change backend to pyplot
plot(rand(10,4))
Out[3]:
In [4]:
# plotlyjs.jl is currently having an unresolved bug for notebooks.
# this stuff only works in the terminal.
# https://github.com/sglyon/PlotlyJS.jl/issues/255
# plotlyjs() # change backend
# plot(rand(4,4))
  • The PGFPlots backend can emit native .tex code.
  • It's just plain beautiful.
In [5]:
# pgfplots()
# plot(rand(4,4))  
# not in notebook!
# using PGFPlots
# x = [1,2,3]
# y = [2,4,1]
# PGFPlots.plot(x, y)
In [6]:
using LaTeXStrings
plot([plot(sin,0,10,label=L"$\sin(x)$"),plot(x->sqrt(2x),0,10,label=L"$\sqrt{2x}$")]...)
Out[6]:
0.0 2.5 5.0 7.5 10.0 -1.0 -0.5 0.0 0.5 1.0 0.0 2.5 5.0 7.5 10.0 0 1 2 3 4
In [7]:
gr()
dd = vcat([[sin(i)  sqrt(2i)] for i in 0:0.01:10]...);
p = plot(dd,label=[L"$\sin(x)$",L"$\sqrt{2x}$"])
Out[7]:
0 250 500 750 1000 -1 0 1 2 3 4
In [8]:
# save this as:
# savefig("myfile.tex")
# savefig("myfile.pdf")
# savefig("myfile.svg")

Attributes

The attributes work with each of the backends: http://docs.juliaplots.org/latest/attributes/

Compatibility of attributes is found in this chart: http://docs.juliaplots.org/latest/supported/

I find it easiest to use this page to find the right attributes: http://docs.juliaplots.org/latest/examples/pyplot/

In [10]:
gr()  # back to GR
plot(rand(4,4),title="Test Title",label=["First" "Second" "Third" "Fourth"])
Out[10]:
1 2 3 4 0.2 0.4 0.6 0.8 1.0 Test Title First Second Third Fourth

Some Example useage

Let's try this out. Most of those examples come from the examples section of the plots website, so check it out for more.

In [9]:
# lesson 1: every column is a series
plot(rand(10))  # 1 col = 1 series
Out[9]:
2 4 6 8 10 0.2 0.4 0.6 0.8 y1
In [10]:
plot(rand(10,2))  # 2 cols = ...
Out[10]:
2 4 6 8 10 0.2 0.4 0.6 0.8 1.0 y1 y2
In [11]:
# different linetypes
plot(rand(10,2),line=(:dot,:auto),marker=([:circle :diamond]),color=[:green :orange])
Out[11]:
2 4 6 8 10 0.2 0.4 0.6 0.8 y1 y2
In [12]:
# histogram
histogram(randn(1000),nbins=20,legend=false,title="My Histogram!",ylabel="counts")
Out[12]:
-3 -2 -1 0 1 2 3 0 50 100 150 200 My Histogram! counts
In [13]:
# add to an existing plot later...
plot(rand(100) / 3,reg=true,fill=(0,:red))
Out[13]:
0 25 50 75 100 0.0 0.1 0.2 0.3 y1
In [14]:
# ... with plot! or scatter!
scatter!(rand(100),marker=(2,:circle),color=:black)
Out[14]:
0 25 50 75 100 0.00 0.25 0.50 0.75 1.00 y1 y2

Subplots

  • We often want to build subplots, ie multiple plots in one figure.
  • Plots.jl has a convenient layout argument that you can specify.
In [15]:
plot(rand(100,4),layout = 4,legend=false)  # make 4 equal sized subplots
Out[15]:
0 25 50 75 100 0.00 0.25 0.50 0.75 1.00 0 25 50 75 100 0.00 0.25 0.50 0.75 1.00 0 25 50 75 100 0.00 0.25 0.50 0.75 1.00 0 25 50 75 100 0.00 0.25 0.50 0.75 1.00
In [16]:
# specify the size of subplots
l = @layout([a{0.1h};b [c d; e]])
plot(randn(100,5),layout=l,t=[:line :histogram :scatter :steppre :bar],leg=false,ticks=nothing,border=false)
Out[16]:
In [17]:
# we can also sequentially build plots and then stack them together
ty = [:line :histogram :scatter :steppre :bar]
p = Any[]
for typ in ty
    push!(p,plot(rand(100),t=typ,title="$typ plot"))
end
plot(p...)
Out[17]:
0 25 50 75 100 0.00 0.25 0.50 0.75 1.00 line plot y1 0.00 0.25 0.50 0.75 1.00 0 5 10 15 20 histogram plot y1 0 25 50 75 100 0.00 0.25 0.50 0.75 1.00 scatter plot y1 0 25 50 75 100 0.00 0.25 0.50 0.75 1.00 steppre plot y1 0 25 50 75 100 0.00 0.25 0.50 0.75 1.00 bar plot y1
In [18]:
# ... and we can also add to the subplots in the same way
plot!(rand(100,5),t=:scatter)
Out[18]:
0 25 50 75 100 0.00 0.25 0.50 0.75 1.00 line plot y1 y6 0 25 50 75 100 0 5 10 15 20 histogram plot y1 y7 0 25 50 75 100 0.00 0.25 0.50 0.75 1.00 scatter plot y1 y8 0 25 50 75 100 0.00 0.25 0.50 0.75 1.00 steppre plot y1 y9 0 25 50 75 100 0.00 0.25 0.50 0.75 1.00 bar plot y1 y10
In [19]:
# 3D plots
n = 100
ts = range(0,stop = 8Ï€, length = n)
x = ts .* map(cos,ts)
y = (0.1ts) .* map(sin,ts)
z = 1:n
plot(x,y,z,zcolor=reverse(z),m=(10,0.8,:blues,stroke(0)),leg=false,cbar=true,w=5)
plot!(zeros(n),zeros(n),1:n,w=10)
Out[19]:
10 20 30 40 50 60 70 80 90 100
In [20]:
# plotlyjs is hard to beat for 3D
# unfortunately...
# plotlyjs()
# plot(x,y,z,zcolor=reverse(z),m=(10,0.8,:blues,stroke(0)),leg=false,cbar=true,w=5)
# plot!(zeros(n),zeros(n),1:n,w=10)

Animations

Any plot can be animated: see https://juliaplots.github.io

Recipes

Recipes are abstract instructions for how to "build a plot" from data. There are multiple kinds of recipes. In execution order:

  • User Recipes: Provides dispatches to plotting
  • Type Recipes: Says how to interpret the data of an abstract type
  • Plot Recipes: A pre-processing recipe which builds a set of series plots and defaults
  • Series Recipes: What most would think of as a "type of plot", i.e. scatter, histogram, etc.

Since these extend Plots.jl itself, all of Plots.jl is accessible from the plotting commands that these make, and these recipes are accessible from each other.

[Series recipes are used to extend the compatibility of backends itself!]

Check out of the Plots Ecosystem!

Type Recipe Example

  • Plots.jl allows you to define type-specific plots
  • For example, here we visualize a solution obtained from DifferentialEquations.jl by just calling plot(sol)
    • This is a very powerful package by the way, so you should have a look at the excellent documentation.
  • This is a very convenient feature to plot your custom types.
In [5]:
# CMS: Not working with Jupyter
#using DifferentialEquations
#f(u,p,t) = 1.01*u
#u0 = 1/2
#tspan = (0.0,1.0)
#prob = ODEProblem(f,u0,tspan)
#sol = solve(prob, Tsit5(), reltol=1e-8, abstol=1e-8)
In [6]:
#plot(sol,linewidth=5,title="Solution to the linear ODE with a thick line",
#     xaxis="Time (t)",yaxis="u(t) (in μm)",label="My Thick Line!") # legend=false
#plot!(sol.t, t->0.5*exp(1.01t),lw=3,ls=:dash,label="True Solution!")
In [7]:
# the plot function still accepts keywords to set attributes:
#plot(sol,linewidth=5,title="Solution to the linear ODE with a thicker line",
 #    xaxis="Time (t)",yaxis="u(t) (in μm)",label="My Thick Line!")
In [8]:
# and we can add to the plot with plot!
#plot!(sol.t, t->0.5*exp(1.01t),lw=3,ls=:dash,label="True Solution!")

MomentOpt.jl

Plot and Type Recipes Together

  • StatPlots.jl is a package based on Plots.jl to produce statistical plots
  • It has dataframe support via the @df macro
  • Let's check it out.
In [20]:
# basic example
using DataFrames, StatsPlots
df = DataFrame(a = 1:10, b = 10*rand(10), c = 10 * rand(10))
@df df plot(:a, [:b :c], colour = [:red :blue])
Out[20]:
2 4 6 8 10 2 4 6 8 10 b c
In [21]:
@df df scatter(:a, :b, markersize = 4 * log.(:c .+ 0.1))
Out[21]:
2 4 6 8 10 2 4 6 8 y1
  • StatPlots provides some very nice recipes for dataframes and matrics
  • we can look at correlational patterns in a dataframe or a matrix
In [22]:
using RDatasets
iris = dataset("datasets","iris")
@df iris marginalhist(:PetalLength,:PetalWidth,bins=30)
Out[22]:
1 2 3 4 5 6 7 0.0 0.5 1.0 1.5 2.0 2.5
In [23]:
# correlation plot from a dataframe
@df iris corrplot([:SepalLength :SepalWidth :PetalLength :PetalWidth], grid = false, bins=20)
Out[23]:
0 5 10 15 SepalLength 2.0 2.5 3.0 3.5 4.0 4.5 SepalWidth 1 2 3 4 5 6 7 PetalLength 4 5 6 7 8 0.0 0.5 1.0 1.5 2.0 2.5 SepalLength PetalWidth 2.0 2.5 3.0 3.5 4.0 4.5 SepalWidth 1 2 3 4 5 6 7 PetalLength 0.0 0.5 1.0 1.5 2.0 2.5 PetalWidth
In [10]:
# corrplot from a matrix
M = randn(1000,4)
M[:,2] += 0.8sqrt.(abs.(M[:,1])) - 0.5M[:,3] .+ 5
M[:,3] -= 0.7M[:,1].^2 .+ 2
corrplot(M, label = ["x$i" for i=1:4])
Out[10]:
0 50 100 150 200 x1 2 4 6 8 x2 -10.0 -7.5 -5.0 -2.5 0.0 x3 -4 -2 0 2 -2 0 2 4 x1 x4 2 4 6 8 x2 -10.0 -7.5 -5.0 -2.5 0.0 x3 -2 0 2 4 x4
In [11]:
# cornerplot for same matrix
cornerplot(M)
Out[11]:
-4 -2 0 2 x1 2 4 6 8 x2 -10.0 -7.5 -5.0 -2.5 0.0 x3 -4 -2 0 2 -2 0 2 4 x1 x4 2 4 6 8 x2 -10.0 -7.5 -5.0 -2.5 0.0 x3 -2 0 2 4 x4
In [12]:
school = RDatasets.dataset("mlmRev","Hsb82")
println(first(school,6))
@df school density(:MAch, group = :Sx)
6×8 DataFrame
│ Row │ School       │ Minrty       │ Sx           │ SSS     │ MAch    │ MeanSES   │ Sector       │ CSES      │
│     │ Categorical… │ Categorical… │ Categorical… │ Float64 │ Float64 │ Float64   │ Categorical… │ Float64   │
├─────┼──────────────┼──────────────┼──────────────┼─────────┼─────────┼───────────┼──────────────┼───────────┤
│ 1   │ 1224         │ No           │ Female       │ -1.528  │ 5.876   │ -0.434383 │ Public       │ -1.09362  │
│ 2   │ 1224         │ No           │ Female       │ -0.588  │ 19.708  │ -0.434383 │ Public       │ -0.153617 │
│ 3   │ 1224         │ No           │ Male         │ -0.528  │ 20.349  │ -0.434383 │ Public       │ -0.093617 │
│ 4   │ 1224         │ No           │ Male         │ -0.668  │ 8.781   │ -0.434383 │ Public       │ -0.233617 │
│ 5   │ 1224         │ No           │ Male         │ -0.158  │ 17.898  │ -0.434383 │ Public       │ 0.276383  │
│ 6   │ 1224         │ No           │ Male         │ 0.022   │ 4.583   │ -0.434383 │ Public       │ 0.456383  │
Out[12]:
0 10 20 30 0.00 0.01 0.02 0.03 0.04 0.05 Female Male
In [13]:
# use tuple of col names to group by more
@df school density(:MAch, group = (:Sx, :Sector), legend = :topleft)
Out[13]:
0 10 20 30 0.00 0.01 0.02 0.03 0.04 0.05 Male Public Male Catholic Female Public Female Catholic
In [14]:
singers = RDatasets.dataset("lattice","singer")
@df singers violin(:VoicePart,:Height,marker=(0.2,:blue,stroke(0)))
# @df singers boxplot!(:VoicePart,:Height)
Out[14]:
Alto 1 Alto 2 Bass 1 Bass 2 Soprano 1 Soprano 2 Tenor 1 Tenor 2 60 65 70 75 y1
In [15]:
# there is great support to plot distributinos
using Distributions
plot(Normal(3,5), fill=(0, .5,:orange))
Out[15]:
-10 0 10 20 0.00 0.02 0.04 0.06 0.08 y1
In [16]:
dist = Gumbel(2)
plot(dist,lw=3,label="pdf",legend=:right,title="Dig that plot!")
scatter!(dist,func=cdf,alpha=0.3,label="cdf",xlabel="x")
vline!([median(dist)],color=:red,lw=3,label="median")
Out[16]:
0.0 2.5 5.0 7.5 10.0 0.00 0.25 0.50 0.75 1.00 Dig that plot! x pdf cdf median
In [17]:
groupedbar(rand(10,3), bar_position = :dodge, bar_width=0.7)
Out[17]:
2 4 6 8 10 0.0 0.2 0.4 0.6 0.8 y1 y2 y3
In [18]:
groupedbar(rand(10,3), bar_position = :dodge, bar_width=0.7)
Out[18]:
2 4 6 8 10 0.0 0.2 0.4 0.6 0.8 y1 y2 y3

Plots and Query

  • can combine a plot with a Query.jl pipeline
  • very nice in terms of readability:
In [19]:
df = DataFrame(a = 1:50, b = 10*rand(50), c = 10 * rand(50))
@df df scatter(:a, :b, markersize = 4 * log.(:c .+ 0.1))  # like before
Out[19]:
0 10 20 30 40 50 0.0 2.5 5.0 7.5 10.0 y1
In [21]:
using Query
df |>
    @filter(_.a > 5) |>
    @map({_.b, d = _.c-10}) |>
    @df scatter(:b, :d)
Out[21]:
0.0 2.5 5.0 7.5 10.0 -10.0 -7.5 -5.0 -2.5 0.0 y1
In [ ]: