using Plots
using LaTeXStrings
using Measures
using Random
Random.seed!(1);Making Plots with Julia
Overview
This tutorial will give some examples of plotting and plotting features in Julia, as well as providing references to some relevant resources. The main plotting library is Plots.jl, but there are some others that provide useful features.
Some Resources
Plots.jluseful tipsPlots.jlexamples- Plot attributes
- Axis attributes
- Color names
Making a Basic Plot
Let’s walk through making a basic line or scatter plot in Julia, using Plots.jl1.
1 There are some other plotting libraries in Julia, but Plots.jl is a standard starting point. Feel free to experiment, though! You could look into Makie.jl or VegaLite.jl
Plots.jl Backends
Plots.jl is more of a common interface for several different plotting ecosystems (called backends) than a self-contained plotting package. By default, Plots.jl uses the GR backend, which is pretty basic and fast; I will be using this in all my examples in this tutorial and in class for simplicity.
You can and should feel free to use other backends if you find them more intuitive or useful for the plot(s) you are trying to make. In particular, you might find PythonPlot simple to use if you feel comfortable with matplotlib in Python. The downside to PythonPlot is that it relies on Python to render the plots, which could create setup problems if you don’t already have Python installed.
Since we’ll be generating random numbers, let’s import Random.jl to allow us set a seed to reproduce the same plot every time we run this code. We’ll also import Plots.jl, LaTeXStrings.jl (which allows us to use LaTeX for mathematical markup of variables in axis labels and titles), and Measures.jl (which lets us adjust margins using intuitive units, such as in and mm).
First, to generate a basic line plot, use plot():
x = 1:5
y = rand(length(x))
plot(x, y, label="Original Data", legend=:topleft)- 1
-
This creates
xas a range of integers between 1 and 5 (inclusive of both endpoints). You can use arbitrary steps with syntax likex = 1:0.1:5. To turnxinto a vector instead of a range, you can usecollect(x), but this is not needed for plotting (Julia does this under the hood). - 2
-
yis a vector of random values (uniformly distributed between 0 and 1) with the same length asx. This type of syntax is better than hard-coding the length intoy, since you might want to use different lengths. - 3
-
We use two different arguments related to constructing the legend:
labelsets the text associated with the line we just created (to not include an element in the legend, uselabel=false), andlegendeither sets the position of the legend (in this case, at the top left) or can be used to turn off the legend (withlabel=false). But there are many others you could use to customize the plot.
Here, we explicitly passed in x to provide the \(x\) coordinates for the plotted values. If only one array is passed2, Plots.jl will interpret the values as \(y\) coordinates and use their indices for the \(x\) positions.
2 This syntax might look like plot(y, ...) instead of plot(x, y, ...).
Now we can add some other lines and point markers. We will use plot! and scatter! to add another line and some points.
The exclamation mark says that plot!() is a mutating function, which changes an existing variable instead of creating a new one. In this case, these functions change or add to an existing plot, instead of creating a new one. Try to remove the exclamation mark: you will get a new plot that no longer contains the old elements. You can contrast this explicit behavior with a language like R, which often requires an argument within the same function to add an element to an existing plot.
Notice the arguments in plot!() and scatter!(), which let us set properties such as color , size, and shape, for the line and scatterplot markers.
y2 = rand(5)
y3 = rand(5)
plot!(y2, color=:red, linewidth=2, linestyle=:dot, label="New Data")
scatter!(y3, markercolor=:black, markershape=:square, markersize=5, label="Point Data")Let’s now add axis labels. We can use LaTeX syntax to add mathematical markup elements to labels (such as superscripts, subscripts, Greek letters, and mathematical symbols) using the LaTeXStrings.jl package. If we indicate that a string should be interpreted as a LaTeXString using the L"..." syntax, it will render content inside $..$ using LaTeX, as seen below.
xlabel!("Regular String (days)")
ylabel!(L"LaTeX String $x_2$ (m$^3$)")Removing Plot Elements
Sometimes we want to remove legends, axes, grid lines, and/or ticks.
plot!(legend=false, axis=false, grid=false, ticks=false)Notice that this unintentionally modified the image dimensions to move the axis labels off the page. If we wanted to keep them, we could modify the dimensions with plot!(size=...).
plot!(size=(400, 400))The lesson is that sometimes the Plots.jl defaults don’t look ideal, and we need to adjust sizes and margins. Don’t shy away from these tweaks if they make your figures easier to read or interpret!
Aspect Ratio
If we want to have a square aspect ratio, use ratio = 1.
v = rand(5)
plot(v, ratio=1, legend=false)
scatter!(v)Plot Demos
This section includes some examples of how to make other types of plots.
Heatmaps
A heatmap is effectively a plotted matrix with colors chosen according to the values. Use clim to specify a fixed range for the color limits.
A = rand(10, 10)
heatmap(A, clim=(0, 1), ratio=1, legend=false, axis=false, ticks=false)- 1
- Create a random 10x10 matrix, but this could come from actual data.
M = [ 0 1 0; 0 0 0; 1 0 0]
whiteblack = [RGBA(1,1,1,0), RGB(0,0,0)]
heatmap(M, c=whiteblack, aspect_ratio = 1, ticks=.5:3.5, lims=(.5,3.5), gridalpha=1, legend=false, axis=false, ylabel="i", xlabel="j")- 1
-
This creates a vector of colors, so
0(the lower value) will map towhiteblack[1](which is white) and1will map towhiteblack[2](black). The specific0and1values don’t matter for this syntax, just that there are two distinct values; the lower one will always be mapped to white. Try changing the values ofMto1and2!
Custom Colors
using Colors
mycolors = [colorant"lightslateblue",colorant"limegreen",colorant"red"]
A = [i for i=50:300, j=1:100]
heatmap(A, c=mycolors, clim=(1,300))- 1
-
Colors.jlprovides a lot more granular control over colors, including many named colors and the ability to generate your own colormaps and scales. Thecolorantsyntax converts an interpretable string (such asred) to an RGB value. You don’t often need to explicitly loadColors.jlas much of this functionality is available through thePlots.jlinterface, which includesColors.jlas a dependency, but sometimes it’s useful to create your own colormaps or functions. - 2
-
This is an example of a comprehension, which creates a vector based on the loop(s) inside the array. This comprehension creates a 251x100 array where the values of each row (indexed by
i) increase from 50 to 300 in order.
Plotting Areas Under Curves
We can plot the area between a curve and the \(x\)-axis using areaplot().
x = -3:.01:3
areaplot(x, exp.(-x.^2/2)/√(2π), alpha=0.25, legend=false)- 1
-
alphasets the transparency of the plotted color: 1 is totally opaque. Lower alpha values are useful when you want to show an area around a line or if you have a lot of points on a scatterplot.
We can also use this functionality for stacked area plots. The snippet below is good for you to experiment with to see what changes break the plot and why.
M = [1 2 3; 7 8 9; 4 5 6; 0 .5 1.5]
areaplot(1:3, M, seriescolor = [:red :green :blue], fillalpha = [0.2 0.3 0.4])- 1
-
The shape of the
seriescolorandfillalphaarguments is very important, and also extends to multiple legend labels.
The fillrange option lets us color the area between two arbitrary lines/curves if we only want to treat one of those curves as a boundary. fillcolor and fillalpha let you change the color and transparency of the filled area.
y = rand(10)
plot(y, fillrange= y.*0 .+ .5, label= "above/below 1/2", fillcolor=:red, legend =:top)Here’s a similar example to plot something like a confidence interval around a central estimate.
x = LinRange(0,2,100)
y1 = exp.(x)
y2 = exp.(1.3 .* x)
plot(x, y1, fillrange = y2, fillalpha = 0.35, c = 1, label = "Confidence band", legend = :topleft)- 1
-
LinRangecreates a range between the first two arguments (0 and 2 in this case) with the specified number of steps (here, 100). This is slightly different than0:0.01:2, which will create a length 101 array, not 100, but has steps that are more predictable.
We can also get more creative and color different parts of a curve differently. Here, we divide a normal distribution into 100 quantiles and alternate red and blue stripes. We’ll do this using the erfinv() function from SpecialFunctions.jl to calculate the quantiles using the inverse cumulative distribution function, but there are other approaches using Distributions.jl.
using SpecialFunctions
# write a function for the normal distribution density
f(x) = exp(-x^2/2)/√(2π)
# get the edges of the quantiles
δ = .01
x = √2 .* erfinv.(2 .* (δ/2 : δ : 1) .- 1)
# make the plot and draw the density line in black
areaplot(x, f.(x), seriescolor=[ :red,:blue], legend=false)
plot!(x, f.(x),c=:black)Plotting Shapes
We can also draw shapes more directly, such as rectangles and circles.
rectangle(w, h, x, y) = Shape(x .+ [0,w,w,0], y .+ [0,0,h,h])
circle(r,x,y) = (θ = LinRange(0,2π,500); (x.+r.*cos.(θ), y.+r.*sin.(θ)))
plot(circle(5,0,0), ratio=1, c=:red, fill=true)
plot!(rectangle(5*√2,5*√2,-2.5*√2,-2.5*√2),c=:white,fill=true,legend=false)Plotting Distributions
The StatsPlots.jl package is very useful for making various plots of probability distributions.
using Distributions, StatsPlots
plot(Normal(2, 5))scatter(LogNormal(0.8, 1.5))We can also use this functionality to plot distributions of data in tabular data structures like DataFrames.
using DataFrames
dat = DataFrame(a = 1:10, b = 10 .+ rand(10), c = 10 .* rand(10))
@df dat density([:b :c], color=[:black :red])- 1
-
@dfis an example of a macro, which modifies the subsequent function. There are many other examples of macros in Julia.
Log-Scaled Axes
xx = 0.1:0.1:10
plot(xx.^2, xaxis=:log, yaxis=:log)plot(exp.(x), yaxis=:log)Editing Plots Manually
Now let’s look at how to modify plot attributes directly.
pl = plot(1:4,[1, 4, 9, 16])To get a list of properties we can modify, use p.attr:
pl.attrRecipesPipeline.DefaultsDict with 30 entries:
:dpi => 96
:background_color_outside => :match
:plot_titlefontvalign => :vcenter
:warn_on_unsupported => true
:background_color => RGBA{Float64}(1.0, 1.0, 1.0, 1.0)
:inset_subplots => nothing
:size => (672, 480)
:display_type => :auto
:overwrite_figure => true
:html_output_format => :auto
:plot_titlefontfamily => :match
:plot_titleindex => 0
:foreground_color => RGB{N0f8}(0.0, 0.0, 0.0)
:window_title => "Plots.jl"
:plot_titlefontrotation => 0.0
:extra_plot_kwargs => Dict{Any, Any}()
:pos => (0, 0)
:plot_titlefonthalign => :hcenter
:tex_output_standalone => false
⋮ => ⋮
We can also directly access properties of the plotted series.
pl.series_list[1]Plots.Series(RecipesPipeline.DefaultsDict(:plot_object => Plot{Plots.GRBackend() n=1}, :subplot => Subplot{1}, :label => "y1", :fillalpha => nothing, :linealpha => nothing, :linecolor => RGBA{Float64}(0.0, 0.6056031704619725, 0.9786801190138923, 1.0), :x_extrema => (NaN, NaN), :series_index => 1, :markerstrokealpha => nothing, :markeralpha => nothing…))
pl[:size]=(300,200)- 1
-
This is a little contrived: we could just use
plot!(pl, size=(300, 200))to do the same thing.
(300, 200)
pl