Skip to content

Commit 05849cd

Browse files
authored
Add plot.jn.points argument to plot_jn() (#114)
* Add `plot.jn.points` argument to `plot_jn()` * Remove unused test-file
1 parent c42fa62 commit 05849cd

File tree

4 files changed

+53
-32
lines changed

4 files changed

+53
-32
lines changed

R/plot_interaction.R

Lines changed: 48 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ plot_interaction <- function(x, z, y, model, vals_x = seq(-3, 3, .001),
173173
#' @param standardized Should coefficients be standardized beforehand?
174174
#' @param xz The name of the interaction term. If not specified, it will be created using \code{x} and \code{z}.
175175
#' @param greyscale Logical. If \code{TRUE} the plot is plotted in greyscale.
176+
#' @param plot.jn.points Logical. If \code{TRUE}, omit the numeric annotations for the JN-points from the plot.
176177
#' @param ... Additional arguments (currently not used).
177178
#'
178179
#' @return A \code{ggplot} object showing the interaction plot with regions of significance.
@@ -209,26 +210,28 @@ plot_interaction <- function(x, z, y, model, vals_x = seq(-3, 3, .001),
209210
plot_jn <- function(x, z, y, model, min_z = -3, max_z = 3,
210211
sig.level = 0.05, alpha = 0.2, detail = 1000,
211212
sd.line = 2, standardized = FALSE, xz = NULL,
212-
greyscale = FALSE, ...) {
213+
greyscale = FALSE, plot.jn.points = TRUE, ...) {
213214

214215
stopif(!inherits(model, c("modsem_da", "modsem_mplus", "modsem_pi", "lavaan")),
215216
"model must be of class 'modsem_pi', 'modsem_da', 'modsem_mplus', or 'lavaan'")
216217

217-
if (standardized) {
218-
parTable <- standardized_estimates(model, correction = TRUE)
219-
} else parTable <- parameter_estimates(model)
218+
if (standardized) parTable <- standardized_estimates(model, correction = TRUE)
219+
else parTable <- parameter_estimates(model)
220220

221+
group.label <- modsem_inspect(model, what = "group.label")
221222
parTable <- addMissingGroups(getMissingLabels(parTable))
222223

223224
plots <- list()
224225
for (g in getGroupsParTable(parTable)) {
226+
label.g <- if (length(group.label)) group.label[[g]] else NULL
225227
parTable.g <- parTable[parTable$group == g, , drop = FALSE]
226228

227229
plots[[g]] <- plotJN_Group(
228230
x = x, z = z, y = y, parTable = parTable.g, model = model,
229231
min_z = min_z, max_z = max_z, sig.level = sig.level,
230232
alpha = alpha, detail = detail, sd.line = sd.line,
231-
standardized = standardized, xz = xz, greyscale = greyscale, ...
233+
standardized = standardized, xz = xz, greyscale = greyscale,
234+
plot.jn.points = plot.jn.points, group = g, group.label = label.g, ...
232235
)
233236
}
234237

@@ -245,14 +248,14 @@ plot_jn <- function(x, z, y, model, min_z = -3, max_z = 3,
245248
}
246249

247250
if (requireNamespace("ggpubr", quietly = TRUE)) { # Make R CMD check happy
248-
group.label <- modsem_inspect(model, what = "group.label")
249251
ggpubr::ggarrange(plotlist = plots, labels = group.label)
250252
} else stop2("The `ggpubr` package is needed to arrange Johnson-Neyman plots in multigroup models!\n")
251253
}
252254

253255

254256
plotJN_Group <- function(x, z, y, parTable, model, min_z, max_z, sig.level, alpha,
255-
detail, sd.line, standardized, xz, greyscale, ...) {
257+
detail, sd.line, standardized, xz, greyscale,
258+
plot.jn.points = TRUE, group = NULL, group.label = NULL, ...) {
256259
if (is.null(xz))
257260
xz <- paste(x, z, sep = ":")
258261

@@ -308,9 +311,11 @@ plotJN_Group <- function(x, z, y, parTable, model, min_z, max_z, sig.level, alph
308311
disc <- B^2 - 4 * A * C
309312

310313
significant_everywhere <- FALSE
314+
jn_points <- numeric(0)
311315
if (A == 0) {
312316
if (B != 0) {
313317
z_jn <- -C / B; z_lower <- z_jn; z_upper <- z_jn
318+
jn_points <- z_jn
314319
} else {
315320
message("No regions where the effect transitions between significant and non-significant.")
316321
significant_everywhere <- TRUE
@@ -320,10 +325,30 @@ plotJN_Group <- function(x, z, y, parTable, model, min_z, max_z, sig.level, alph
320325
significant_everywhere <- TRUE
321326
} else if (disc == 0) {
322327
z_jn <- -B / (2 * A); z_lower <- z_jn; z_upper <- z_jn
328+
jn_points <- z_jn
323329
} else {
324330
z1 <- (-B + sqrt(disc)) / (2 * A)
325331
z2 <- (-B - sqrt(disc)) / (2 * A)
326332
z_lower <- min(z1, z2); z_upper <- max(z1, z2)
333+
jn_points <- c(z_lower, z_upper)
334+
}
335+
jn_points <- jn_points[is.finite(jn_points)]
336+
337+
if (length(jn_points) && !significant_everywhere) {
338+
format_num <- function(val) formatC(val, format = "f", digits = 2)
339+
format_sig <- function(val) sub("^0\\.", ".", formatC(val, format = "f", digits = 2))
340+
341+
interval <- sprintf("[%s, %s]", format_num(min(jn_points)), format_num(max(jn_points)))
342+
343+
if (!is.null(group.label)) {
344+
header <- sprintf("Johnson-Neyman Interval (group %s):", group.label)
345+
} else {
346+
header <- "Johnson-Neyman Interval:"
347+
}
348+
349+
body <- sprintf("When %s is outside the interval %s, the slope of %s is p < %s.",
350+
z, interval, x, format_sig(sig.level))
351+
message(sprintf("%s\n %s", header, body))
327352
}
328353

329354
# grid and simple slopes
@@ -412,31 +437,22 @@ plotJN_Group <- function(x, z, y, parTable, model, min_z, max_z, sig.level, alph
412437

413438
# only show JN lines if there is a transition in the plotted window
414439
has_transition_in_window <- any(diff(as.integer(df_plot$significant)) != 0, na.rm = TRUE)
415-
if (!significant_everywhere && has_transition_in_window) {
416-
top_y <- suppressWarnings(max(df_plot$slope[is.finite(df_plot$slope)], na.rm = TRUE))
417-
if (!is.finite(top_y)) top_y <- y_range[2]
418-
419-
hline_colour <- if (greyscale) "black" else "red"
420-
421-
if (exists("z_jn")) {
422-
if (is.finite(z_jn) && z_jn >= min_z && z_jn <= max_z) {
423-
p <- p + ggplot2::geom_vline(xintercept = z_jn, linetype = "dashed", color = hline_colour) +
424-
ggplot2::annotate("text", x = z_jn, y = top_y,
425-
label = paste("JN point:", round(z_jn, 2)),
426-
hjust = -0.1, vjust = 1, color = "black")
427-
}
428-
} else {
429-
if (is.finite(z_lower) && z_lower >= min_z && z_lower <= max_z) {
430-
p <- p + ggplot2::geom_vline(xintercept = z_lower, linetype = "dashed", color = hline_colour) +
431-
ggplot2::annotate("text", x = z_lower, y = top_y,
432-
label = paste("JN point:", round(z_lower, 2)),
433-
hjust = -0.1, vjust = 1, color = "black")
434-
}
435-
if (is.finite(z_upper) && z_upper >= min_z && z_upper <= max_z) {
436-
p <- p + ggplot2::geom_vline(xintercept = z_upper, linetype = "dashed", color = hline_colour) +
437-
ggplot2::annotate("text", x = z_upper, y = top_y,
438-
label = paste("JN point:", round(z_upper, 2)),
439-
hjust = -0.1, vjust = 1, color = "black")
440+
if (!significant_everywhere && has_transition_in_window && length(jn_points)) {
441+
jn_points_in_window <- jn_points[jn_points >= min_z & jn_points <= max_z]
442+
if (length(jn_points_in_window)) {
443+
top_y <- suppressWarnings(max(df_plot$slope[is.finite(df_plot$slope)], na.rm = TRUE))
444+
if (!is.finite(top_y)) top_y <- y_range[2]
445+
446+
hline_colour <- if (greyscale) "black" else "red"
447+
448+
for (point in jn_points_in_window) {
449+
p <- p + ggplot2::geom_vline(xintercept = point, linetype = "dashed", color = hline_colour)
450+
451+
if (plot.jn.points) {
452+
p <- p + ggplot2::annotate("text", x = point, y = top_y,
453+
label = paste("JN point:", round(point, 2)),
454+
hjust = -0.1, vjust = 1, color = "black")
455+
}
440456
}
441457
}
442458
}

man/plot_jn.Rd

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test_multigroup.R

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ standardized_estimates(est, correction = TRUE)
5959
standardized_estimates(est, correction = TRUE, std.errors = "delta")
6060
summary(est, standardized = TRUE, center = TRUE)
6161
plot_jn(x = "X", z = "Z", y = "Y", model = est)
62+
plot_jn(x = "X", z = "Z", y = "Y", model = est, plot.jn.points = FALSE)
6263
plot_interaction(x = "X", z = "Z", y = "Y", model = est, vals_z = c(1, 0))
6364
testthat::expect_warning(
6465
plot_surface(x = "X", z = "Z", y = "Y", model = est),

tests/testthat/test_simple_slopes.R

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ plot_interaction(x = "X", z = "Z", y = "Y",
1818
plot_interaction(x = "X", z = "Z", y = "Y", xz = "X:Z",
1919
vals_z = c(1, 0), model = est1, ci_type = "prediction")
2020
plot_jn(x = "X", z = "Z", y = "Y", model = est1, greyscale = TRUE)
21+
plot_jn(x = "X", z = "Z", y = "Y", model = est1, greyscale = TRUE, plot.jn.points = FALSE)
2122

2223
plot_surface(x = "X", z = "Z", y = "Y", model = est1, colorscale = "Greys", grid = TRUE, grid_color = "black")
2324
# check input length validation

0 commit comments

Comments
 (0)