From b83044258605d821b35b1a29863c4303ed87bd6c Mon Sep 17 00:00:00 2001 From: Phillip Alday Date: Thu, 31 Aug 2023 14:10:40 -0500 Subject: [PATCH] doc tweaks and improvements (#16) * clean up the presentation of unconditional * mixed models tweaks --- docs/Project.toml | 1 + docs/src/index.md | 34 ++++++++++++++-------------------- docs/src/mixed-models.md | 15 ++++++++++++--- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/docs/Project.toml b/docs/Project.toml index 937c5bf..bf38072 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,4 +1,5 @@ [deps] +BoxCox = "1248164d-f7a6-4bdb-8e8d-8c4a187b3ce6" CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" diff --git a/docs/src/index.md b/docs/src/index.md index 73c7105..6ab35d1 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -18,18 +18,17 @@ We start with the square root of a normal distribution. using BoxCox using CairoMakie using Random +CairoMakie.activate!(; type="svg") + x = abs2.(randn(MersenneTwister(42), 1000)) -let f = Figure(; resolution=(400, 400)) +let f = Figure() ax = Axis(f[1,1]; xlabel="x", ylabel="density") density!(ax, x) - f -end -``` - -```@example Unconditional -let f = Figure(; resolution=(400, 400)) - ax = Axis(f[1,1]; xlabel="theoretical", ylabel="observed") + ax = Axis(f[1,2]; xlabel="theoretical quantiles", ylabel="observed values") qqnorm!(ax, x) + colsize!(f.layout, 1, Aspect(1, 1.0)) + colsize!(f.layout, 2, Aspect(1, 1.0)) + resize_to_layout!(f) f end ``` @@ -44,19 +43,14 @@ Note that the resulting transform isn't exactly a square root, even though our d Now that we've fit the transform, we use it like a function to transform the original data. ```@example Unconditional -let f = Figure(; resolution=(400, 400)) +let f = Figure(), bcx = bc.(x) ax = Axis(f[1,1]; xlabel="x", ylabel="density") - density!(ax, bc.(x)) - f -end -``` - -There is also a special method for `qqnorm` provided for objects of type `BoxCoxTransformation`, which shows the QQ plot of the transformation applied to the original data. - -```@example Unconditional -let f = Figure(; resolution=(400, 400)) - ax = Axis(f[1,1]; xlabel="theoretical", ylabel="observed") - qqnorm!(ax, bc) + density!(ax, bcx) + ax = Axis(f[1,2]; xlabel="theoretical quantiles", ylabel="observed values") + qqnorm!(ax, bcx; qqline=:fitrobust) + colsize!(f.layout, 1, Aspect(1, 1.0)) + colsize!(f.layout, 2, Aspect(1, 1.0)) + resize_to_layout!(f) f end ``` diff --git a/docs/src/mixed-models.md b/docs/src/mixed-models.md index 4ad3d80..6406c2b 100644 --- a/docs/src/mixed-models.md +++ b/docs/src/mixed-models.md @@ -42,13 +42,13 @@ bc = fit(BoxCoxTransformation, model) ## Choosing an appropriate transformation -Although we receive a single "best" value (approximately -1.0747) the fitting process, it is worthwhile to look at the profile likelihood plot for the transformation: +Although we receive a single "best" value (approximately -1.0747) from the fitting process, it is worthwhile to look at the profile likelihood plot for the transformation: ```@example Mixed boxcoxplot(bc; conf_level=0.95) ``` -Here we see that -1 is nearly as good. Moreover, ``\text{time}^-1`` has a natural interpretation as *speed*. +Here we see that -1 is nearly as good. Moreover, time``^{-1}`` has a natural interpretation as *speed*. In other words, we can model reaction speed instead of reaction time. Then instead of seeing whether participants take longer to respond with each passing day, we can see whether their speed increases or decreases. In both cases, we're looking at whether they respond *faster* or *slower* and even the terminology *fast* and *slow* suggests that speed is easily interpretable. @@ -69,12 +69,21 @@ While useful at times, speed has a natural interpretation and so we instead use ## Fitting a model to the transormed response +Because `reaction` is stored in milliseconds, we use `1000 / reaction` instead of `1 / reaction` so that our speed units are responses per second. + ```@example Mixed model_bc = fit(MixedModel, - @formula(1 / reaction ~ 1 + days + (1 + days | subj)), + @formula(1000 / reaction ~ 1 + days + (1 + days | subj)), dataset(:sleepstudy)) ``` +For our original model on the untransformed scale, the intercept was approximately 250, which means that the average response time was about 250 milliseconds. +For the model on the speed scale, we have an intercept about approximately 4, which means that the average response speed is about 4 responses per second, which implies that the the average response time is 250 milliseconds. +In other words, our new results are compatible with our previous estimates. + +!!! note + Because the Box-Cox transformation helps a model achieve normality of the *residuals*, it helps fulfill the model assumptions. When these assumptions are not fulfilled, we may still get similar estimates, but the standard errors and derived measures (e.g., confidence intervals and associated coverage) may not be correct. + Finally, let's take a look at our the residual diagnostics for our transformed and untransformed models: ## Impact of transformation