How to make multi-panel plots?

I want to make multi-panel plots in Pumas, but I am looking for something simple that can be reproduced by someone else on a different dataset. The dataframe can be thought of as having three columns: id, time, dv_ipred; where id is the faceting column, time is the x-axis and dv_ipred is the y-axis.
I looked at the AlgebraOfGraphics piece of code:

f = data(df) * mapping(:time, :dv_ipred, layout=:id => nonnumeric)
draw(f)

However, if there are any missing rows in dv_ipred it just prints the column out like a string in the plot even though it is type of Float64. AoG is very constrained and does not provide lot of flexibility.

So, I tried something in CairoMakie but the code (attached) got long very quickly. Would appreciate if you have any better ideas of doing this.

unique_subjects = unique(df.id)

num_subjects = length(unique_subjects)
num_columns = Int(ceil(sqrt(num_subjects)))
num_rows = Int(ceil(num_subjects/num_columns))

resolution = (1000,1000)
f = Figure(; resolution)
id_counter = 1
for (row, col) in Base.product(1:num_rows, 1:num_columns)
    if id_counter <= num_subjects
        a1 = Axis(f[row, col], title="ID = " * string(unique_subjects[id_counter]))
        subject_df = filter(x -> x.id == unique_subjects[id_counter], df)
        lines!(a1, subject_df.time, subject_df.dv_ipred, color=:black)
        id_counter += 1
    end
end
plottitle = Label(f[0, :], "PK Profiles", textsize = 30)
yaxis_lab = Label(f[:, 0], "Concentrations", rotation = pi/2, textsize=20)
xaxis_lab = Label(f[(num_rows+1):(num_rows+2), :], "Time", textsize=20,
                padding = (0, 0, 0, resolution[1]/20))
f

Hi @Rahulub3r, AlgebraOfGraphics doesn’t automatically handle missing values and so will treat the column as nonnumeric data when plotting. Using dropmissing to remove the missing values from that column with data(dropmissing(df, :dv_ipred)) should be sufficient to treat the column’s data as numeric instead.

Thank you for your response @Michael . AoG shows the x-axis and y-axis separately for each plot. I want to do like a scales=“free” or scales=“free_x” kind of thing to not have each sub-plot show me an axis with title and labels.

The current version of AlgebraOfGraphics that we provide with Pumas does not provide an out-of-the-box handling of faceted labels. Once we ship a newer version of AlgebraOfGraphics then it will provide that feature automatically. Some amount of what you’re after may be achieved by the following:

fg = 
	draw(
		data(dropmissing(df, :dv_ipred)) *
		mapping(:time, :dv_ipred; layout = :id => nonnumeric);
		axis = (; xlabel = "", ylabel = ""),
	)
Label(fg.figure[:, 0], "Concentrations"; rotation = pi/2)
Label(fg.figure[end+1, 2:end], "Time")
Label(fg.figure[0, 2:end], "PK Profiles")
fg
1 Like

Wonderful. Thanks for your response Michael!

@Rahulub3r @Michael
I would like to follow up on this question. In the above code, fg =..., is there a way to get the individual plots in multiple pages? like using paginate = true option.
Right now, I have a similar code to get individual fits, however using the paginate = true option, gives me this error

ERROR: MethodError: no method matching draw(::Layers; paginate::Bool, separate::Bool, axis::NamedTuple{(:title, :xlabel, :ylabel, :xticks), Tuple{String, String, String, Vector{Int64}}}, legend::NamedTuple{(:position, :titleposition, :framevisible), Tuple{Symbol, Symbol, Bool}}, figure::NamedTuple{(:fontsize,), Tuple{Int64}})

Closest candidates are:
  draw(::AlgebraOfGraphics.AbstractDrawable; axis, figure, palettes, facet, legend, colorbar) got unsupported keyword arguments "paginate", "separate"
   @ AlgebraOfGraphics C:\a\PumasSystemImages\PumasSystemImages\julia_depot\packages\AlgebraOfGraphics\yhdjr\src\draw.jl:44
  draw(::AlgebraOfGraphics.PaginatedLayers; kws...)
   @ AlgebraOfGraphics C:\a\PumasSystemImages\PumasSystemImages\julia_depot\packages\AlgebraOfGraphics\yhdjr\src\paginate.jl:29
  draw(::AlgebraOfGraphics.PaginatedLayers, ::Int64; kws...)
   @ AlgebraOfGraphics C:\a\PumasSystemImages\PumasSystemImages\julia_depot\packages\AlgebraOfGraphics\yhdjr\src\paginate.jl:39

Stacktrace:
 [1] kwerr(::NamedTuple{(:paginate, :separate, :axis, :legend, :figure), Tuple{Bool, Bool, NamedTuple{(:title, :xlabel, :ylabel, :xticks), Tuple{String, String, String, Vector{Int64}}}, NamedTuple{(:position, :titleposition, :framevisible), Tuple{Symbol, Symbol, Bool}}, NamedTuple{(:fontsize,), Tuple{Int64}}}}, ::Function, ::Layers)
   @ Base .\error.jl:165
 [2] top-level scope

Hi @MathangiCTM - i don’t believe there’s a way to paginate plots in AoG (although internal Pumas functions, such as subject_fits have the option to paginate).
I would suggest to create plots for each ID in a vector and then use the report function in Pumas to write the vector of plots to output. The code might looks as follows:

f_vec = map(unique(df.id)) do id
    @chain df begin
        @rsubset :id == id
        data(_) * mapping(:time, :dv_ipred) * visual(Lines)
        draw(; axis = (title = "ID = $(id)",),)
    end
end

report(f_vec)

Thanks @Rahulub3r
Any suggestion on how I can add time vs dv mapping also in this loop.
And when I try to run report(f_vec) I get this error:

[ Info: Calculating required `inspect` results.
ERROR: Unknown argument type `AlgebraOfGraphics.FigureGrid`.
Stacktrace:
  [1] error(s::String)
    @ Base .\error.jl:35
  [2] (::PumasReports.var"#categorise#4"{Vector{Any}, Vector{Any}, Vector{Any}, Vector{Any}})(f::AlgebraOfGraphics.FigureGrid)
    @ PumasReports C:\a\PumasSystemImages\PumasSystemImages\julia_depot\packages\PumasReports\y38l5\src\PumasReports.jl:273
  [3] (::Base.var"#59#60"{PumasReports.var"#categorise#4"{Vector{Any}, Vector{Any}, Vector{Any}, Vector{Any}}})(#unused#::Nothing, x::AlgebraOfGraphics.FigureGrid)
    @ Base .\tuple.jl:602
  [4] BottomRF
    @ .\reduce.jl:81 [inlined]
  [5] afoldl(op::Base.BottomRF{Base.var"#59#60"{PumasReports.var"#categorise#4"{Vector{Any}, Vector{Any}, Vector{Any}, Vector{Any}}}}, a::Nothing, bs::AlgebraOfGraphics.FigureGrid)
    @ Base .\operators.jl:535
  [6] _foldl_impl(op::Base.BottomRF{Base.var"#59#60"{PumasReports.var"#categorise#4"{Vector{Any}, Vector{Any}, Vector{Any}, Vector{Any}}}}, init::Nothing, itr::Tuple{AlgebraOfGraphics.FigureGrid})
    @ Base .\tuple.jl:329
  [7] foldl_impl(op::Base.BottomRF{Base.var"#59#60"{PumasReports.var"#categorise#4"{Vector{Any}, Vector{Any}, Vector{Any}, Vector{Any}}}}, nt::Nothing, itr::Tuple{AlgebraOfGraphics.FigureGrid})
    @ Base .\reduce.jl:48
  [8] mapfoldl_impl(f::typeof(identity), op::Base.var"#59#60"{PumasReports.var"#categorise#4"{Vector{Any}, Vector{Any}, Vector{Any}, Vector{Any}}}, nt::Nothing, itr::Tuple{AlgebraOfGraphics.FigureGrid})
    @ Base .\reduce.jl:44
  [9] mapfoldl(f::Function, op::Function, itr::Tuple{AlgebraOfGraphics.FigureGrid}; init::Nothing)
    @ Base .\reduce.jl:170
 [10] mapfoldl
    @ .\reduce.jl:170 [inlined]
 [11] #foldl#289
    @ .\reduce.jl:193 [inlined]
 [12] foreach(f::PumasReports.var"#categorise#4"{Vector{Any}, Vector{Any}, Vector{Any}, Vector{Any}}, itr::Tuple{AlgebraOfGraphics.FigureGrid})
    @ Base .\tuple.jl:602
 [13] PumasReports.ModelBundle(args::Tuple{AlgebraOfGraphics.FigureGrid}; inspect::Bool, infer::Bool)
    @ PumasReports C:\a\PumasSystemImages\PumasSystemImages\julia_depot\packages\PumasReports\y38l5\src\PumasReports.jl:274
 [14] PumasReports.ModelBundle(args::AlgebraOfGraphics.FigureGrid; inspect::Bool, infer::Bool)
    @ PumasReports C:\a\PumasSystemImages\PumasSystemImages\julia_depot\packages\PumasReports\y38l5\src\PumasReports.jl:320
 [15] (::PumasReports.var"#14#15"{Base.Pairs{Symbol, Bool, Tuple{Symbol, Symbol}, NamedTuple{(:inspect, :infer), Tuple{Bool, Bool}}}})(::Tuple{Int64, AlgebraOfGraphics.FigureGrid})
    @ PumasReports .\none:0
 [16] iterate
    @ .\generator.jl:47 [inlined]
 [17] collect
    @ .\array.jl:782 [inlined]
 [18] #_named_objects#13
    @ C:\a\PumasSystemImages\PumasSystemImages\julia_depot\packages\PumasReports\y38l5\src\PumasReports.jl:330 [inlined]
 [19] _named_objects
    @ C:\a\PumasSystemImages\PumasSystemImages\julia_depot\packages\PumasReports\y38l5\src\PumasReports.jl:330 [inlined]
 [20] report(fitted_models::Vector{AlgebraOfGraphics.FigureGrid}; output::String, title::String, version::VersionNumber, date::DateTime, author::String, categorical::Vector{Any}, force::Bool, inspect::Bool, infer::Bool, clean::Bool, header::String, footer::String, plot_fontsize::Int64, plot_resolution::Tuple{Int64, Int64})
    @ PumasReports C:\a\PumasSystemImages\PumasSystemImages\julia_depot\packages\PumasReports\y38l5\src\PumasReports.jl:162
 [21] report(fitted_models::Vector{AlgebraOfGraphics.FigureGrid})
    @ PumasReports C:\a\PumasSystemImages\PumasSystemImages\julia_depot\packages\PumasReports\y38l5\src\PumasReports.jl:140

What happens if you wrap it with a vector?

report([f_vec])

@storopoli
report([f_vec]) returns this error:

ERROR: Unknown argument type `Vector{AlgebraOfGraphics.FigureGrid}`.
Stacktrace:
  [1] error(s::String)
    @ Base .\error.jl:35
  [2] (::PumasReports.var"#categorise#4"{Vector{Any}, Vector{Any}, Vector{Any}, Vector{Any}})(f::Vector{AlgebraOfGraphics.FigureGrid})
    @ PumasReports C:\a\PumasSystemImages\PumasSystemImages\julia_depot\packages\PumasReports\y38l5\src\PumasReports.jl:273
  [3] (::Base.var"#59#60"{PumasReports.var"#categorise#4"{Vector{Any}, Vector{Any}, Vector{Any}, Vector{Any}}})(#unused#::Nothing, x::Vector{AlgebraOfGraphics.FigureGrid})
    @ Base .\tuple.jl:602
  [4] BottomRF
    @ .\reduce.jl:81 [inlined]
  [5] afoldl(op::Base.BottomRF{Base.var"#59#60"{PumasReports.var"#categorise#4"{Vector{Any}, Vector{Any}, Vector{Any}, Vector{Any}}}}, a::Nothing, bs::Vector{AlgebraOfGraphics.FigureGrid})
    @ Base .\operators.jl:535
  [6] _foldl_impl(op::Base.BottomRF{Base.var"#59#60"{PumasReports.var"#categorise#4"{Vector{Any}, Vector{Any}, Vector{Any}, Vector{Any}}}}, init::Nothing, itr::Tuple{Vector{AlgebraOfGraphics.FigureGrid}})
    @ Base .\tuple.jl:329
  [7] foldl_impl(op::Base.BottomRF{Base.var"#59#60"{PumasReports.var"#categorise#4"{Vector{Any}, Vector{Any}, Vector{Any}, Vector{Any}}}}, nt::Nothing, itr::Tuple{Vector{AlgebraOfGraphics.FigureGrid}})
    @ Base .\reduce.jl:48
  [8] mapfoldl_impl(f::typeof(identity), op::Base.var"#59#60"{PumasReports.var"#categorise#4"{Vector{Any}, Vector{Any}, Vector{Any}, Vector{Any}}}, nt::Nothing, itr::Tuple{Vector{AlgebraOfGraphics.FigureGrid}})
    @ Base .\reduce.jl:44
  [9] mapfoldl(f::Function, op::Function, itr::Tuple{Vector{AlgebraOfGraphics.FigureGrid}}; init::Nothing)
    @ Base .\reduce.jl:170
 [10] mapfoldl
    @ .\reduce.jl:170 [inlined]
 [11] #foldl#289
    @ .\reduce.jl:193 [inlined]
 [12] foreach(f::PumasReports.var"#categorise#4"{Vector{Any}, Vector{Any}, Vector{Any}, Vector{Any}}, itr::Tuple{Vector{AlgebraOfGraphics.FigureGrid}})
    @ Base .\tuple.jl:602
 [13] PumasReports.ModelBundle(args::Tuple{Vector{AlgebraOfGraphics.FigureGrid}}; inspect::Bool, infer::Bool)
    @ PumasReports C:\a\PumasSystemImages\PumasSystemImages\julia_depot\packages\PumasReports\y38l5\src\PumasReports.jl:274
 [14] PumasReports.ModelBundle(args::Vector{AlgebraOfGraphics.FigureGrid}; inspect::Bool, infer::Bool)
    @ PumasReports C:\a\PumasSystemImages\PumasSystemImages\julia_depot\packages\PumasReports\y38l5\src\PumasReports.jl:320
 [15] (::PumasReports.var"#14#15"{Base.Pairs{Symbol, Bool, Tuple{Symbol, Symbol}, NamedTuple{(:inspect, :infer), Tuple{Bool, Bool}}}})(::Tuple{Int64, Vector{AlgebraOfGraphics.FigureGrid}})
    @ PumasReports .\none:0
 [16] iterate
    @ .\generator.jl:47 [inlined]
 [17] collect
    @ .\array.jl:782 [inlined]
 [18] #_named_objects#13
    @ C:\a\PumasSystemImages\PumasSystemImages\julia_depot\packages\PumasReports\y38l5\src\PumasReports.jl:330 [inlined]
 [19] _named_objects
    @ C:\a\PumasSystemImages\PumasSystemImages\julia_depot\packages\PumasReports\y38l5\src\PumasReports.jl:330 [inlined]
 [20] report(fitted_models::Vector{Vector{AlgebraOfGraphics.FigureGrid}}; output::String, title::String, version::VersionNumber, date::DateTime, author::String, categorical::Vector{Any}, force::Bool, inspect::Bool, infer::Bool, clean::Bool, header::String, footer::String, plot_fontsize::Int64, plot_resolution::Tuple{Int64, Int64})
    @ PumasReports C:\a\PumasSystemImages\PumasSystemImages\julia_depot\packages\PumasReports\y38l5\src\PumasReports.jl:162
 [21] report(fitted_models::Vector{Vector{AlgebraOfGraphics.FigureGrid}})
    @ PumasReports C:\a\PumasSystemImages\PumasSystemImages\julia_depot\packages\PumasReports\y38l5\src\PumasReports.jl:140
 [22] top-level scope

@MathangiCTM the report function in Pumas uses a Figure as an input and AoG by default gives a FigureGrid here. All you need to do is extract the Figure from each element and then create the vector. Here is a full reproducer that you can use.


# Load the necessary Libraries
using Pumas
using PumasUtilities
using PharmaDatasets
using AlgebraOfGraphics
using DataFramesMeta
using CairoMakie
# Read data
pkdata = dataset("iv_sd_3")

f_vec = map(unique(pkdata.id)) do id
  p =  @chain pkdata begin
        dropmissing(:dv)
        @rsubset :id == id
        data(_) * mapping(:time, :dv) * visual(Lines)
        draw(; axis = (title = "ID = $(id)",),)
    end
    p.figure
end

report(f_vec, title = "test")


1 Like

Thank you very much @vijay
That perfectly worked.