diff --git a/Project.toml b/Project.toml index 18b7206..22a34a0 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "MixedModelsMakie" uuid = "b12ae82c-6730-437f-aff9-d2c38332a376" authors = ["Phillip Alday ", "Douglas Bates ", "contributors"] -version = "0.4.3" +version = "0.4.4" [deps] BSplineKit = "093aae92-e908-43d7-9660-e50ee39d5a0a" @@ -19,7 +19,7 @@ StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" [compat] Aqua = "0.5, 0.6" BSplineKit = "0.15, 0.16, 0.17" -CairoMakie = "0.11" +CairoMakie = "0.11, 0.12" DataFrames = "1" Distributions = "0.25" KernelDensity = "0.6.3" diff --git a/src/coefplot.jl b/src/coefplot.jl index f06112c..649ade1 100644 --- a/src/coefplot.jl +++ b/src/coefplot.jl @@ -3,13 +3,20 @@ coefplot!(fig::$(Indexable), x::Union{MixedModel,MixedModelBootstrap}; kwargs...) coefplot!(ax::Axis, Union{MixedModel,MixedModelBootstrap}; - conf_level=0.95, vline_at_zero=true, show_intercept=true, attributes...) + conf_level=0.95, vline_at_zero=true, show_intercept=true, + scatter_attributes=(;), + errorbars_attributes=(;), + attributes...) Create a coefficient plot of the fixed-effects and associated confidence intervals. Inestimable coefficients (coefficients removed by pivoting in the rank deficient case) are excluded. -`attributes` are passed onto `scatter!` and `errorbars!`. +`attributes` are passed onto both `scatter!` and `errorbars!`, while +`scatter_attributes` and `errorbars_attributes` are passed only onto `scatter!` and +`errorbars!`, respectively. (Starting with Makie 0.21, unsupported attributes for a +given plottype are no longer silently ignored, so it's necessary so separate out the +attributes that are only valid for a single plot type.) The mutating methods return the original object. @@ -36,6 +43,8 @@ function coefplot!(ax::Axis, x::Union{MixedModel,MixedModelBootstrap}; conf_level=0.95, vline_at_zero=true, show_intercept=true, + scatter_attributes=(;), + errorbars_attributes=(;), attributes...) ci = confint_table(x, conf_level; show_intercept) y = nrow(ci):-1:1 @@ -44,9 +53,9 @@ function coefplot!(ax::Axis, x::Union{MixedModel,MixedModelBootstrap}; ax.xlabel = xlabel - scatter!(ax, xvals, y; attributes...) + scatter!(ax, xvals, y; attributes..., scatter_attributes...) errorbars!(ax, xvals, y, xvals .- ci.lower, ci.upper .- xvals; - direction=:x, attributes...) + direction=:x, attributes..., errorbars_attributes...) vline_at_zero && vlines!(ax, 0; color=(:black, 0.75), linestyle=:dash) reset_limits!(ax) diff --git a/src/ridge.jl b/src/ridge.jl index efd6f21..ae4e264 100644 --- a/src/ridge.jl +++ b/src/ridge.jl @@ -3,7 +3,12 @@ ridgeplot!(fig::$(Indexable), x::Union{MixedModel,MixedModelBootstrap}; kwargs...) ridgeplot!(ax::Axis, Union{MixedModel,MixedModelBootstrap}; - conf_level=0.95, vline_at_zero=true, show_intercept=true, attributes...) + conf_level=0.95, vline_at_zero=true, show_intercept=true, + scatter_attributes=(;), + errorbars_attributes=(;), + band_attributes=(;), + lines_attributes=(;), + attributes...) Create a ridge plot for the bootstrap samples of the fixed effects. @@ -13,6 +18,12 @@ The highest density interval corresponding to `conf_level` is marked with a bar Setting `conf_level=missing` removes the markings for the highest density interval. `attributes` are passed onto [`coefplot`](@ref), `band!` and `lines!`. +`scatter_attributes` and `errorbars_attributes` are passed only onto [`coefplot`](@ref). +`band_attributes` and `lines_attributes` are passed only onto `band!` and +`linesbars!`, respectively. +(Starting with Makie 0.21, unsupported attributes for a +given plottype are no longer silently ignored, so it's necessary so separate out the +attributes that are only valid for a single plot type.) The mutating methods return the original object. @@ -47,6 +58,10 @@ function ridgeplot!(ax::Axis, x::MixedModelBootstrap; conf_level=0.95, vline_at_zero=true, show_intercept=true, + scatter_attributes=(;), + errorbars_attributes=(;), + band_attributes=(;), + lines_attributes=(;), attributes...) xlabel = if !ismissing(conf_level) @sprintf "Normalized bootstrap density and %g%% confidence interval" (conf_level * @@ -57,11 +72,11 @@ function ridgeplot!(ax::Axis, x::MixedModelBootstrap; if !ismissing(conf_level) coefplot!(ax, x; conf_level, vline_at_zero, show_intercept, color=:black, - attributes...) + scatter_attributes, errorbars_attributes, attributes...) end attributes = merge((; color=:black), attributes) - band_attributes = merge(attributes, (; color=(_color(attributes.color), 0.3))) + band_attributes = merge((; color=(_color(attributes.color), 0.3)), band_attributes) ax.xlabel = xlabel @@ -78,8 +93,8 @@ function ridgeplot!(ax::Axis, x::MixedModelBootstrap; dd = 0.95 * row.kde.density ./ maximum(row.kde.density) lower = Point2f.(row.kde.x, offset) upper = Point2f.(row.kde.x, dd .+ offset) - band!(ax, lower, upper; band_attributes...) - lines!(ax, upper; attributes...) + band!(ax, lower, upper; attributes..., band_attributes...) + lines!(ax, upper; attributes..., lines_attributes...) end # check conf_level so that we don't double print @@ -96,75 +111,3 @@ function ridgeplot!(ax::Axis, x::MixedModelBootstrap; return ax end - -# """ -# ridgeplot!(ax::Axis, df::AbstractDataFrame, densvar::Symbol, group::Symbol; normalize=false) -# ridgeplot!(f::Union{Makie.FigureLike,Makie.GridLayout}, args...; pos=(1,1) kwargs...) -# ridgeplot(args...; kwargs...) - -# Create a "ridge plot". - -# A ridge plot is stacked plot of densities for a given variable (`densvar`) grouped by a different variable (`group`). Because densities can very widely in scale, it is sometimes useful to `normalize` the densities so that each density has a maximum of 1. - -# The non-mutating method creates a Figure before calling the method for Figure. -# The method for Figure places the ridge plot in the grid position specified by `pos`, default is (1,1). -# """ -# function ridgeplot!(ax::Axis, df::AbstractDataFrame, densvar::Symbol, group::Symbol; sort_by_group=false, vline_at_zero=true, normalize=false) -# # `normalize` makes it so that the max density is always 1 -# # `normalize` works on the density not the area/mass -# gdf = groupby(df, group) -# dens = combine(gdf, densvar => kde => :kde) -# sort_by_group && sort!(dens, group) -# spacing = normalize ? 1.0 : 0.9 * maximum(dens[!, :kde]) do val -# return maximum(val.density) -# end - -# nticks = length(gdf) - -# for (idx, row) in enumerate(eachrow(dens)) -# dd = normalize ? row.kde.density ./ maximum(row.kde.density) : row.kde.density - -# offset = idx * spacing - -# lower = Node(Point2f.(row.kde.x, offset)) -# upper = Node(Point2f.(row.kde.x, dd .+ offset)) -# band!(ax, lower, upper; color=(:black, 0.3)) -# lines!(ax, upper; color=(:black, 1.0)) -# end - -# vline_at_zero && vlines!(ax, 0; color=(:black, 0.75), linestyle=:dash) - -# ax.yticks[] = (1:spacing:(nticks*spacing), string.(dens[!, group])) -# ylims!(ax, 0, (nticks + 2) * spacing) -# ax.xlabel[] = string(densvar) -# ax.ylabel[] = string(group) - -# ax -# end - -# function ridgeplot!(f::Union{Makie.FigureLike,Makie.GridLayout}, args...; pos=(1,1), kwargs...) -# ridgeplot!(Axis(f[pos...]), args...; kwargs...) -# return f -# end - -# """ -# ridgeplot(args...; kwargs...) - -# See [ridgeplot!](@ref). -# """ -# ridgeplot(args...; kwargs...) = ridgeplot!(Figure(), args...; kwargs...) - -# """ -# ridgeplot!(Union{Figure, Axis}, bstrp::MixedModelBootstrap, args...; show_intercept=true, kwargs...) -# ridgeplot(bstrp::MixedModelBootstrap, args...; show_intercept=true, kwargs...) - -# Convenience methods that call `DataFrame(bstrp.β)` and pass that onto the primary `ridgeplot[!]` methods. -# By default, the intercept is shown, but this can be disabled. -# """ -# function ridgeplot!(axis::Axis, bstrp::MixedModelBootstrap, args...; -# show_intercept=true, normalize=true, kwargs...) - -# df = DataFrame(bstrp.β) -# show_intercept || filter!(:coefname => !=(Symbol("(Intercept)")), df) -# ridgeplot!(axis, df, :β, :coefname, args...; sort_by_group=false, normalize, kwargs...) -# end diff --git a/test/ridgeplot.jl b/test/ridgeplot.jl index edc5feb..d63e4fa 100644 --- a/test/ridgeplot.jl +++ b/test/ridgeplot.jl @@ -1,5 +1,5 @@ f = ridgeplot(b1) @test save(joinpath(OUTDIR, "ridge_sleepstudy.png"), f) -f = ridgeplot(br) +f = ridgeplot(br; color=(:blue, 0.3), errorbars_attributes=(; whiskerwidth=15)) @test save(joinpath(OUTDIR, "ridge_rankdeficient.png"), f) diff --git a/test/runtests.jl b/test/runtests.jl index 3eb1aea..b6cdbe2 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,54 +1,4 @@ -using Aqua -using CairoMakie -using DataFrames -using MixedModels -using MixedModelsMakie -using Random # we don't depend on exact PRNG vals, so no need for StableRNGs -using Statistics -using Suppressor -using Test -using TestSetExtensions - -using MixedModelsMakie: confint_table - -const OUTDIR = joinpath(pkgdir(MixedModelsMakie), "test", "output") -const progress = false - -function save(path, obj, args...; kwargs...) - isfile(path) && rm(path) - Makie.save(path, obj, args...; kwargs...) - return isfile(path) -end - -m1 = fit(MixedModel, - @formula(1000 / reaction ~ 1 + days + (1 + days | subj)), - MixedModels.dataset(:sleepstudy); progress) - -m1zc = fit(MixedModel, - @formula(1000 / reaction ~ 1 + days + zerocorr(1 + days | subj)), - MixedModels.dataset(:sleepstudy); progress) - -m2 = fit(MixedModel, - @formula(rt_trunc ~ 1 + spkr * prec * load + - (1 + spkr + prec + load | subj) + - (1 + spkr | item)), - MixedModels.dataset(:kb07); progress) - -b1 = parametricbootstrap(MersenneTwister(42), 500, m1; progress, - optsum_overrides=(; ftol_rel=1e-6)) -g1 = fit(MixedModel, - @formula(r2 ~ 1 + anger + gender + btype + situ + - (1 | subj) + (1 + gender | item)), - MixedModels.dataset(:verbagg), - Bernoulli(); progress) - -rng = MersenneTwister(0) -x = rand(rng, 100) -a = rand(rng, 100) -data = (; x, x2=1.5 .* x, a, y=rand(rng, [0, 1], 100), g=repeat('A':'T', 5)) -mr = @suppress fit(MixedModel, @formula(y ~ a + x + x2 + (1 | g)), data; progress) -br = @suppress parametricbootstrap(MersenneTwister(42), 500, mr; progress, - optsum_overrides=(; ftol_rel=1e-6)) +include("setup_tests.jl") @testset ExtendedTestSet "MixedModelsMakie.jl" begin @testset "Aqua" begin diff --git a/test/setup_tests.jl b/test/setup_tests.jl new file mode 100644 index 0000000..101390c --- /dev/null +++ b/test/setup_tests.jl @@ -0,0 +1,51 @@ +using Aqua +using CairoMakie +using DataFrames +using MixedModels +using MixedModelsMakie +using Random # we don't depend on exact PRNG vals, so no need for StableRNGs +using Statistics +using Suppressor +using Test +using TestSetExtensions + +using MixedModelsMakie: confint_table + +const OUTDIR = joinpath(pkgdir(MixedModelsMakie), "test", "output") +const progress = false + +function save(path, obj, args...; kwargs...) + isfile(path) && rm(path) + Makie.save(path, obj, args...; kwargs...) + return isfile(path) +end + +m1 = fit(MixedModel, + @formula(1000 / reaction ~ 1 + days + (1 + days | subj)), + MixedModels.dataset(:sleepstudy); progress) + +m1zc = fit(MixedModel, + @formula(1000 / reaction ~ 1 + days + zerocorr(1 + days | subj)), + MixedModels.dataset(:sleepstudy); progress) + +m2 = fit(MixedModel, + @formula(rt_trunc ~ 1 + spkr * prec * load + + (1 + spkr + prec + load | subj) + + (1 + spkr | item)), + MixedModels.dataset(:kb07); progress) + +b1 = parametricbootstrap(MersenneTwister(42), 500, m1; progress, + optsum_overrides=(; ftol_rel=1e-6)) +g1 = fit(MixedModel, + @formula(r2 ~ 1 + anger + gender + btype + situ + + (1 | subj) + (1 + gender | item)), + MixedModels.dataset(:verbagg), + Bernoulli(); progress) + +rng = MersenneTwister(0) +x = rand(rng, 100) +a = rand(rng, 100) +data = (; x, x2=1.5 .* x, a, y=rand(rng, [0, 1], 100), g=repeat('A':'T', 5)) +mr = @suppress fit(MixedModel, @formula(y ~ a + x + x2 + (1 | g)), data; progress) +br = @suppress parametricbootstrap(MersenneTwister(42), 500, mr; progress, + optsum_overrides=(; ftol_rel=1e-6))