Plotting histogram on the y-axis at the end of a time series

I came across a figure which had a visualization I really liked. It shows the time series of some simulations and then has the histogram of the end state of the system plotted on a new, rotated axis. I know this plot was done in Python, but don’t know how to approach it in either Python or Julia.

Is there any mechanic which could be used to generate such a plot? The trick it seems is that the two plots are sharing the y-axis of the time-series plot, but then the histogram is plotted as a seemingly separate subplot. I could not find any feature for which this is possible.

1 Like

Check out GitHub - JuliaPlots/StatsPlots.jl: Statistical plotting recipes for Plots.jl for some inspiration from similar plots produced using Plots.jl

Yeah you can write a Plots recipe for this, i’ll update tomorrrow unless someone beats me to it. But most Julia plotting packages should be able to do this.

Here is my quick attempt to produce a “similar” plot in Gadfly.

x = 1.0:5:500.0
D = DataFrame(x=x, y=x/250+randn(100))
pa = plot(D, layer(x=:x, y=:y, Geom.point, Geom.smooth),
    layer(x=[0], y=[4], label=["x10⁴"], Geom.label(position=:right)),
    Guide.xlabel("Time<sub> </sub>(yr)"), Guide.ylabel("Volume (km³)"), 
    Guide.title("Grounded Volume") )
pb = plot(D, y=:y, Geom.histogram(orientation=:horizontal, bincount=10, density=true), 
    Guide.xlabel("π(<i>V̂<sub>g</sub></i>)<sup>x10⁻⁵</sup>"), Guide.ylabel(""),
    Guide.title(" "))

M = Array{Compose.Context}(1,2)
M[1] = compose(context(0,0,2/3,1), render(pa))
M[2] = compose(context(0,0,1/3,1), render(pb))

draw(PNG(6.6inch, 3.3inch), hstack(M...))

fig1

3 Likes

Very nice. The Plots code is very similar, in fact:

sims = hcat((x->cumsum(randn(200))).(1:100)...) # produce 100 random walks with 200 steps

using Plots, LaTeXStrings
pyplot(grid = false, legend = false, color = :grey, 
          guidefont = font("serif", 18), titlefont = font("serif", 18)) 
# or plotlyjs(), gr(), pgfplots()... sessionwide defaults to plot can go here
# these defaults try to mimic the grey color and large font of the example

# produce the subplots
p1 = plot(sims, alpha = 0.3, lw = 2, 
              title = L"Grounded\;volume", xlabel = L"Time\;(yr)", 
              ylabel = L"Volume\;(km^3)");
p2 = histogram(sims[end,:], lw = 0, orientation = :horizontal, 
               bin = 10, xlim = (0,30), 
               xlabel = L"π(\hat{V}_g)^{x10^{-5}}");

plot(p1, p2, link = :y, layout = grid(1,2,widths = [0.7,0.3]))

example

4 Likes

I also promised a “recipe”. So, Plots is not really a plotting package, in that it does not do any plotting itself. It is a package that allows a user to specify a plot to many different plotting packages in a uniform syntax.
One smart aspect of that is “recipes” that allow a user to define a plot without depending on Plots or any other plotting package . Thus a package can define plots without causing conflicts among plotting packages, and without enforcing any plotting package on the user (though (s)he needs to use Plots to use the recipe).

Here’s how the above plot type could be used to generate a recipe:

using RecipesBase   # a tiny package defining the @recipe macro
@userplot SimPlot    # defines a plotting function called "simplot"

@recipe function f(h::SimPlot; xlabel1 = "", xlabel2 = "") # define extra keywords to use in the plotting
    mat = h.args[1]      # the x, y, z data to be plotted are stored in the args array

    legend := false       # specify the plot attributes
    link := :y
    grid := false
    layout := grid(1, 2, widths = [0.7, 0.3])

    @series begin         # send the different data to the different subplots
        subplot := 2
        seriestype := :histogram
        orientation := :h
        xlabel := xlabel2
        title := ""
        ylabel := ""
        mat[end,:]
    end

    linealpha --> 0.4    # this (specifying the opacity of the line) can be overridden by the user
    seriestype := :path
    subplot := 1
    xlabel := xlabel1
    mat                         # the recipe returns the data to be plotted
end

You can call this generic plotting function simply

using Plots; gr()
simplot(sims)

34

But you still have access to the full Plots machinery and can change settings etc to get the same plot as above.

3 Likes

Great, thanks all. This is really helpful and I like the idea of the recipe
since it’s something I’m going to be using in a large post-processing
workflow.

1 Like

I get an error when I try exactly this example, and I wonder if you have any clues as to why, or what I should vary to narrow this down.

I pasted the above inside a module, with using RecipesBase and without using Plots. In the REPL I load Plots and then my module, then define the fake data as above. And I get an error like so:

julia> simplot(sims)
ERROR: UndefVarError: grid not defined
Stacktrace:
 [1] macro expansion at /Users/me/.julia/v0.6/RI/src/plotrecipes.jl:378 [inlined]
 [2] apply_recipe(::Dict{Symbol,Any}, ::RI.SimPlot) at /Users/me/.julia/v0.6/RecipesBase/src/RecipesBase.jl:265
 [3] _process_userrecipes(::Plots.Plot{Plots.GRBackend}, ::Dict{Symbol,Any}, ::Tuple{RI.SimPlot}) at /Users/me/.julia/v0.6/Plots/src/pipeline.jl:81
 [4] _plot!(::Plots.Plot{Plots.GRBackend}, ::Dict{Symbol,Any}, ::Tuple{RI.SimPlot}) at /Users/me/.julia/v0.6/Plots/src/plot.jl:175
 [5] #simplot#101(::Array{Any,1}, ::Function, ::Array{Float64,2}, ::Vararg{Array{Float64,2},N} where N) at /Users/me/.julia/v0.6/RecipesBase/src/RecipesBase.jl:341
 [6] simplot(::Array{Float64,2}, ::Vararg{Array{Float64,2},N} where N) at /Users/me/.julia/v0.6/RecipesBase/src/RecipesBase.jl:341

If I add using Plots to the module then it works as expected, but if I understand right the point of this is to avoid that.

I was trying with layout := @layout [ left right{0.6w} ] before, and there too got errors unless I had using Plots in the module.

You’re right - we were going to move layouts to RecipesBase at the time, but didn’t. This means that RecipesBase does not ATM support complex layouts. So in this case you’ve got to replace layout := grid(1, 2, widths = [0.7, 0.3]) with layout := 2, which means you cannot control the sizes of individual subplots.

1 Like

OK, glad I’m not going mad!

Any thoughts on whether this is a great or a terrible idea? It does appear to work:

@require Plots begin

@userplot SimPlot  
@recipe function f(h::SimPlot; ...
  ...
  grid(1, 2, widths = [0.7, 0.3])
  ... 
end

end