Plots.jl: how to plot a function with different values for a parameter

Suppose I want to plot a certain function f(x) = a*x(1-x), for x in 0:0.001:1, and to consider, i.e., four different values for parameter a: a = 1, a = 2, a = 3.2, a = 3.8. I want to do this in one single plot. I know how to do it using either the plot! function or, as an alternative, creating four different functions and plotting all of them in one single plot plot([f1 ,f2, f3, f4], 0:0.001:1, kwarg). For example, the figure below shows what I have in mind:

logistic_map

The problem with the coding style I follow to produce that plot is that it is both ugly and inefficient (because we may have more parameter values than just 4). Trying to turn around these limitations, the best I could come up with was this piece of code:

using Plots, LaTeXStrings
#x ∈ -3:0.01:3
g(x, a) = a*x*(1-x);
fig = plot(); # produces an empty plot
    for a = [1, 2, 3.2, 3.8] # the for loop
        plot!(fig, [g(x, a) for x ∈ 0:0.01:1]) # the loop fills in the plot with this
    end
plot(fig, label= [L"a=1" L"a=2" L"a=3.2" L"a= 3.8"],
    fg_legend = :transparent, legend = :topleft) #just produces the plot

This code leads to the following plot:
logist_map_wrong

The two problems with this plot are:(i) the scale of the x-axis follows the number of iterations (from 1 to 101, instead of 0 to 1 as desired), (ii) it misrepresents the true function we intend to plot, and it becomes useless.

I tried many variations on that piece of code, but I was unsuccessful. I have carefully checked the net for hints but found no clues. Help would be very appreciated. This is for teaching, and I do not want my students to see ugly code.

2 Likes

Here’s a possible solution:

using Plots, LaTeXStrings

g(x, a) = a * x * (1 - x);

fig = plot(ratio=1, xlims=(0, 1), ylims=(0, 1)); # produces an empty plot
    
for a in (1, 2, 3.2, 3.8) 
    plot!(fig, 0:0.01:1, x -> g(x, a), label=L"a = %$a") # the loop fills in the plot with this
end

plot!(x -> x, ls=:dash, lab=false)

fig

The label is using the LaTeXStrings functionality.
fig at the end displays the fig object that plot! adds to.

Let me know if anything isn’t clear.

6 Likes

The result looks like this. The keyword argument ratio=1 fixes the aspect ratio to 1.

1 Like

Well, that’s it. Beautiful and simple. Thank’s a lot. Besides learning how to come up with the plot in the right way, I also learned that little thing of the LaTeXStrings functionality, very useful.

Thank you very much.

1 Like

@VivMendes: In case you weren’t aware, if you put this in a Pluto.jl notebook you can also animate plots interactively using @bind with a Slider.

I used to do this with Jupyter notebooks, but the Interact.jl package has been difficult to get working reliably recently. Here’s an example using Jupyter from a workshop I gave at JuliaCon:

Gotta love the logistic map! :wink:

2 Likes

@dpsanders Thank’s for the information. I was aware of your IntervalsJuliaCon2020 repository, and I actually watched your presentation at the JuliaCon. That little thing with Pluto is fantastic but is not available in the repository above. Yes, the logistic map has a special magic. So simple and so complex at the same time.

1 Like

If this example meets your needs, than the following will probably add dependencies you don’t really want.

Multi-dimensional parametric analysis

But if you actually want more than generate a simple plot over a single parametric sweep, you might want to consider the CMDimData.jl module:

CMDimData.jl was specifically created with multi-sweep parametric analyses like these. It has built-in support for multi-dimenisonal sweeps, and embeds both x & y values in the data (so you can do an “xshift” of y data, and it will correctly plot it at the correct x values).

But its main strength is that it focuses on the calculations. Plotting just works because the plot routines understand these multi-dimensional container structures.

Sample code

using CMDimData
using CMDimData.MDDatasets
using CMDimData.EasyPlot
CMDimData.@includepkg EasyPlotInspect

#Create "x" as a ::DataRS{DataF1} structure which stores vectors of .x & .y
#values for each one of the swept "α" variables:
αsweep = PSweep("α", [1,2,3.2,3.8])
x = fill(DataRS{DataF1}, αsweep) do α
	return DataF1(0:0.001:1)
end;

α = parameter(x, "α") #Obtain a DataRS structure with "α" values

#In case you really want to define the equation as a function:
f(x, α) = α*x*(1-x) #x & α here are merely function parameters - not the actual data (yet)

y = f(x, α) #Evaluate f(x, α) using special "x" & "α" data structures
dy_dx = deriv(y) #Example of other calculations
#NOTE: deriv() approximates derivative using x-values


#==Generate plots
===============================================================================#
plot = push!(cons(:plot, nstrips=2),
	cons(:wfrm, y, label="f(x)", strip=1),
	cons(:wfrm, dy_dx, label="f'(x)", strip=2),
)

EasyPlot.displaygui(:InspectDR, plot)

Sample output

image

Plot limitations

At the moment, focus is placed on supporting the InspectDR.jl backend - although there is decent support for PyPlot & Plots.jl.

Also note that it isn’t practical to use Plots.jl’s quick-and-easy command-line-focused API in CMDimData.jl. Instead, you would use the EasyPlot API - which targets scripting applications (see above example).

3 Likes

@MA_Laforge, this looks amazing. The package would deserve a gallery with the examples on how to use CMDimData provided in the [sample/] subdirectory.

Fair point. I added a few README.md files to some of the sample directories themselves.

As you suggested, I made the CMDimData.jl “gallery” available:
FileRepo/README.md at master · ma-laforge/FileRepo · GitHub

, and there also a CMDimCircuits.jl “gallery” (an additional circuit-specific package layer):
FileRepo/README.md at master · ma-laforge/FileRepo · GitHub

Note that the CMDimCircuits.jl gallery still basically shows what is possible with CMDimData.jl… if you add circuit-specific math.

1 Like

how to solve this:

Julia>plot!( x -> x, ls=:dash, lab=false)
ERROR: MethodError: no method matching Float64(::Array{Float64,1})
Closest candidates are:
  Float64(::UInt32) at float.jl:66
  Float64(::Int128) at float.jl:94
  Float64(::Float16) at float.jl:256
  ...
Stacktrace:
 [1] (::Plots.var"#102#103"{Symbol})(::Array{Float64,1}) at C:\Users\hermesr\.julia\packages\Plots\FQOz1\src\axes.jl:152
 [2] _broadcast_getindex_evalf at .\broadcast.jl:648 [inlined]
 [3] _broadcast_getindex at .\broadcast.jl:621 [inlined]
 [4] (::Base.Broadcast.var"#19#20"{Base.Broadcast.Broadcasted{Base.Broadcast.Style{Tuple},Nothing,Plots.var"#102#103"{Symbol},Tuple{Tuple{Array{Float64,1},Array{Float64,1}}}}})(::Int64) at .\broadcast.jl:1046
 [5] ntuple at .\ntuple.jl:42 [inlined]
 [6] copy(::Base.Broadcast.Broadcasted{Base.Broadcast.Style{Tuple},Nothing,Plots.var"#102#103"{Symbol},Tuple{Tuple{Array{Float64,1},Array{Float64,1}}}}) at .\broadcast.jl:1046
 [7] materialize at .\broadcast.jl:837 [inlined]
 [8] _scaled_adapted_grid(::Function, ::Symbol, ::Symbol, ::Int64, ::Int64) at C:\Users\hermesr\.julia\packages\Plots\FQOz1\src\series.jl:538
 [9] macro expansion at C:\Users\hermesr\.julia\packages\Plots\FQOz1\src\series.jl:525 [inlined]
 [10] apply_recipe(::Dict{Symbol,Any}, ::Function, ::Int64, ::Int64) at C:\Users\hermesr\.julia\packages\RecipesBase\zBoFG\src\RecipesBase.jl:275
 [11] _process_userrecipes(::Plots.Plot{Plots.GRBackend}, ::Dict{Symbol,Any}, ::Tuple{var"#27#28"}) at C:\Users\hermesr\.julia\packages\Plots\FQOz1\src\pipeline.jl:83
 [12] _plot!(::Plots.Plot{Plots.GRBackend}, ::Dict{Symbol,Any}, ::Tuple{var"#27#28"}) at C:\Users\hermesr\.julia\packages\Plots\FQOz1\src\plot.jl:178
 [13] plot!(::Plots.Plot{Plots.GRBackend}, ::Function; kw::Base.Iterators.Pairs{Symbol,Any,Tuple{Symbol,Symbol},NamedTuple{(:ls, :lab),Tuple{Symbol,Bool}}}) at C:\Users\hermesr\.julia\packages\Plots\FQOz1\src\plot.jl:158
 [14] plot!(::Function; kw::Base.Iterators.Pairs{Symbol,Any,Tuple{Symbol,Symbol},NamedTuple{(:ls, :lab),Tuple{Symbol,Bool}}}) at C:\Users\hermesr\.julia\packages\Plots\FQOz1\src\plot.jl:150
 [15] top-level scope at REPL[31]:1

I also had to change the lines of code "
plot!(fig, 0:0.01:1, x -> g(x, a), label=L"a = %$a")

for this one to get the legend!
plot!(fig, 0:0.01:1, x -> g(x, a), label=“a = %$a”)

what is wrong with my configuration?

@HerAdri, I am using Win10 Julia 1.5.3 and experiencing intermittent problems with LaTeXStrings. When @dpsanders posted his solution the plot displayed nicely (GR backend), however, today it won’t do it and I am getting the warning message:

julia> plot!(x -> x, ls=:dash, lab=false)
'latex' is not recognized as an internal or external command,
operable program or batch file.
latex: failed to create a dvi file

Looked at solution provided here but with no success. I think I will let it sleep another couple of days.

PS:

  • I am getting a GKS QtTerm plot but the LaTeXStrings are not displaying. Plots.GR.version() --> “0.53.0.post51”

  • LaTeX strings display well though in another laptop also with Win10 Julia 1.5.3 but where: Plots.GR.version --> “0.53.0”, and with other packages installed…

this code does not work for me:
Why?

plot!( x -> x, ls=:dash, lab=false)

ERROR: MethodError: no method matching Float64(::Array{Float64,1})
Closest candidates are:
  Float64(::UInt32) at float.jl:66
  Float64(::Int128) at float.jl:94
  Float64(::Float16) at float.jl:256
  ...
Stacktrace:
 [1] (::Plots.var"#102#103"{Symbol})(::Array{Float64,1}) at C:\Users\hermesr\.julia\packages\Plots\FQOz1\src\axes.jl:152
 [2] _broadcast_getindex_evalf at .\broadcast.jl:648 [inlined]
 [3] _broadcast_getindex at .\broadcast.jl:621 [inlined]
 [4] (::Base.Broadcast.var"#19#20"{Base.Broadcast.Broadcasted{Base.Broadcast.Style{Tuple},Nothing,Plots.var"#102#103"{Symbol},Tuple{Tuple{Array{Float64,1},Array{Float64,1}}}}})(::Int64) at .\broadcast.jl:1046
 [5] ntuple at .\ntuple.jl:42 [inlined]
 [6] copy(::Base.Broadcast.Broadcasted{Base.Broadcast.Style{Tuple},Nothing,Plots.var"#102#103"{Symbol},Tuple{Tuple{Array{Float64,1},Array{Float64,1}}}}) at .\broadcast.jl:1046
 [7] materialize at .\broadcast.jl:837 [inlined]
 [8] _scaled_adapted_grid(::Function, ::Symbol, ::Symbol, ::Int64, ::Int64) at C:\Users\hermesr\.julia\packages\Plots\FQOz1\src\series.jl:538
 [9] macro expansion at C:\Users\hermesr\.julia\packages\Plots\FQOz1\src\series.jl:525 [inlined]
 [10] apply_recipe(::Dict{Symbol,Any}, ::Function, ::Int64, ::Int64) at C:\Users\hermesr\.julia\packages\RecipesBase\zBoFG\src\RecipesBase.jl:275
 [11] _process_userrecipes(::Plots.Plot{Plots.GRBackend}, ::Dict{Symbol,Any}, ::Tuple{var"#12#13"}) at C:\Users\hermesr\.julia\packages\Plots\FQOz1\src\pipeline.jl:83
 [12] _plot!(::Plots.Plot{Plots.GRBackend}, ::Dict{Symbol,Any}, ::Tuple{var"#12#13"}) at C:\Users\hermesr\.julia\packages\Plots\FQOz1\src\plot.jl:178
 [13] plot!(::Plots.Plot{Plots.GRBackend}, ::Function; kw::Base.Iterators.Pairs{Symbol,Any,Tuple{Symbol,Symbol},NamedTuple{(:ls, :lab),Tuple{Symbol,Bool}}}) at C:\Users\hermesr\.julia\packages\Plots\FQOz1\src\plot.jl:158
 [14] plot!(::Function; kw::Base.Iterators.Pairs{Symbol,Any,Tuple{Symbol,Symbol},NamedTuple{(:ls, :lab),Tuple{Symbol,Bool}}}) at C:\Users\hermesr\.julia\packages\Plots\FQOz1\src\plot.jl:150
 [15] top-level scope at REPL[11]:100:

But with these changes, you get the result:

using Plots, LaTeXStrings

g(x, a) = a * x * (1 - x);

fig = plot(ratio=1, xlims=(0, 1), ylims=(0, 1)); # produces an empty plot

    

for a in (1, 2, 3.2, 3.8) 

    plot!(fig, 0:0.01:1, x -> g(x, a), label="a = %$a") # the loop fills in the plot with this

end

x=0:0.01:1

plot!( x,x, ls=:dash, lab=false)

fig

plottoForum

@HerAdri, in your workaround you are not using LaTeX syntax, which is the whole point of using LaTeXStrings

@HerAdri In a Windows 10 machine, with Julia 1.5.3 Plots v1.10.0 LaTeXStrings v1.2.0 the code provided by @dpsanders works with no problems in Atom, VsCode and Jupyter. I have done it just now. Try up your Julia system. Try build Plots and LaTeXStrings as well. I think the LaTeXStrings package does the LaTeX job by itself, but in case there is some problem I have a local installation of MiKTex. If you have an updated version of LaTeXStrings I think the L"..." constructor should do the job for you.

Why they don’t work for me:
plot!(x -> x, ls=:dash, lab=false)?

This doesn’t seem to have anything to do with the subject of this thread, which was originally about plotting a function for different parameter values. The LaTeXStrings issues are already an aside, but your issue also doesn’t seem to have anything to do with that.

FWIW:

julia> using Plots; unicodeplots()
Plots.UnicodePlotsBackend()

julia> plot!( x -> x, ls=:dash, lab=false)
┌ Warning: linestyle dash is unsupported with Plots.UnicodePlotsBackend().  Choose from: [:auto, :solid]
└ @ Plots ~/.julia/packages/Plots/IjNHT/src/args.jl:1227
        ┌────────────────────────────────────────────────────────────┐ 
    5.3 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠄⠀│ 
        │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠔⠉⠀⠀⠀│ 
        │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠔⠉⠀⠀⠀⠀⠀⠀│ 
        │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠔⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀│ 
        │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠔⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ 
        │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠔⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ 
        │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⡠⠔⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ 
        │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⡠⠔⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ 
        │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⣀⠔⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ 
        │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡧⠔⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ 
        │⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⡩⠝⠉⡏⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉│ 
        │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠔⠉⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ 
        │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠔⠊⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ 
        │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠔⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ 
        │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠔⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ 
        │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠔⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ 
        │⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠔⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ 
        │⠀⠀⠀⠀⠀⠀⡠⠔⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ 
        │⠀⠀⠀⡠⠔⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ 
   -5.3 │⠀⠐⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ 

(this also works fine with GR backend, I just used UnicodePlots here for copy-pasteability)

1 Like

The plot! command outside the for loop is the one that seems to launch the plot window. But the error or warning messages then issued may concern what fed the plot scene previously?

:dash is definitely supported by Plots.

If this is of any help, try this simple plot

using Plots, LaTeXStrings
f(x) = 5sin(x)
plot(f, -1.5π:0.01:1.5π, label = L"f(x)") 
plot!(x -> x, ls=:dash, label = L"45^o")

If you don’t get something like this you have a problem with some one of your packages.

aaa_test

1 Like

@VivMendes, in Plots’ issue#2659 the developer @jheinen clearly states that: "With recent versions of both GR (0.50.1) and Plots (1.5.3) everything should work fine without a local LaTeX installation, on all platforms. This is working for me in one Win10 laptop but for some reason, not in the other. None has prior local Latex installations.

@rafael.guerra Yeah, I remember that issue#2659 quite well. @HerAdri, I had a problem with Plots, and I solved it with

ENV["GRDIR"] = ""
] build GR