@@ -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),
209210plot_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
254256plotJN_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 }
0 commit comments