diy.sem.plot allows users to manually plot fully customisable path
diagrams for structural equation models (SEM) in R. It avoids the
inflexibility of automated SEM plotting tools and the tedium of drawing
diagrams in external applications.
Users manually specify node positions using x-y coordinates, and where
on the perimeter of each node (top, bottom, left, or right) a paths
should begin and end. From there, the primary function diyPaths()
renders the diagram, automatically inserting estimates centered on the
midpoint of paths and adjusting each node’s shape to match its variable
type. A range of fine-tuning options are included, facilitating the
creation of a path diagram exactly as envisioned, entirely within R.
Install the released version of diy.sem.plot from CRAN:
install.packages("diy.sem.plot")
Or install the development version from GitHub to get the latest features.
install.packages("remotes")
remotes::install_github("snagy86/diy.sem.plot", build_vignettes = TRUE)
Below is a brief example of using the package. For more examples, plus a full explanation of the workflow and available arguments, see the package’s vignette.
In this example, I use template() to generate the starting code,
saving time and avoiding syntax errors. It takes the fitted lavaan
model and prints the node list, path list, title list, note list and
diyPaths() call with default values. I then edited these values to
position everything.
library(lavaan)
#> This is lavaan 0.7-2
#> lavaan is FREE software! Please report any bugs.
library(diy.sem.plot)
# Specify and fit the SEM
sem_model <- '
visual =~ x1 + x2 + x3
textual =~ x4 + x5 + x6
speed =~ x7 + x8 + x9
speed ~ visual + textual
visual ~~ textual
'
fit_sem <- sem(model = sem_model, data = HolzingerSwineford1939)
# Generate starting code with every diyPaths() argument
template(fit = fit_sem, include_variance_paths = TRUE, diyPaths_arguments = "max", quiet = TRUE) # quiet = TRUE stores the template without printing it
# Position and relabel each node
node_list <- list(
#node(s) for latent variable(s) (<- section comments like this are generated by template())
node(name = "visual", x = 1, y = 1, label = "Visual"),
node(name = "textual", x = 1, y = 3, label = "Textual"),
node(name = "speed", x = 4, y = 2, label = "Speed"),
#node(s) for observed variables loading onto latent variable visual
node(name = "x1", x = 0, y = -0.5, label = "Visual\nPerception"),
node(name = "x2", x = 1, y = -0.5, label = "Cubes"),
node(name = "x3", x = 2, y = -0.5, label = "Lozenges"),
#node(s) for observed variables loading onto latent variable textual
node(name = "x4", x = 0, y = 4.5, label = "Paragraph\nComprehension"),
node(name = "x5", x = 1, y = 4.5, label = "Sentence\nCompletion"),
node(name = "x6", x = 2, y = 4.5, label = "Word\nMeaning"),
#node(s) for observed variables loading onto latent variable speed
node(name = "x7", x = 6, y = 1.25, label = "Speeded\nAddition"),
node(name = "x8", x = 6, y = 2, label = "Speeded\nCounting"),
node(name = "x9", x = 6, y = 2.75, label = "Speeded\nDiscrimination")
)
# Set which side of each node every path starts and ends on
path_list <- list(
# regression path(s)
path(from = "visual", to = "speed", side_from = "right", side_to = "left", nudge_text_x = 0, nudge_text_y = 0),
path(from = "textual", to = "speed", side_from = "right", side_to = "left", nudge_text_x = 0, nudge_text_y = 0),
#loading path(s) from visual
path(from = "visual", to = "x1", side_from = "bottom", side_to = "top", nudge_text_x = -0.1, nudge_text_y = 0),
path(from = "visual", to = "x2", side_from = "bottom", side_to = "top", nudge_text_x = 0, nudge_text_y = 0),
path(from = "visual", to = "x3", side_from = "bottom", side_to = "top", nudge_text_x = 0.1, nudge_text_y = 0),
#loading path(s) from textual
path(from = "textual", to = "x4", side_from = "top", side_to = "bottom", nudge_text_x = -0.1, nudge_text_y = 0),
path(from = "textual", to = "x5", side_from = "top", side_to = "bottom", nudge_text_x = 0, nudge_text_y = 0),
path(from = "textual", to = "x6", side_from = "top", side_to = "bottom", nudge_text_x = 0.1, nudge_text_y = 0),
#loading path(s) from speed
path(from = "speed", to = "x7", side_from = "right", side_to = "left", nudge_text_x = 0, nudge_text_y = -0.1),
path(from = "speed", to = "x8", side_from = "right", side_to = "left", nudge_text_x = 0, nudge_text_y = 0),
path(from = "speed", to = "x9", side_from = "right", side_to = "left", nudge_text_x = 0, nudge_text_y = 0.1),
# covariance/correlation path(s)
path(from = "textual", to = "visual", side_from = "left", side_to = "left", nudge_text_x = 0, nudge_text_y = 0, cov_curve = -0.6), # cov_curve sets how strongly the path curves; its sign sets the direction
# variance path(s)
# REMEMBER TO SET show_variances IN diyPaths CALL = TRUE TO INCLUDE IN DIAGRAM!
path(from = "x1", to = "x1", nudge_text_x = 0, nudge_text_y = 0, variance_position = "bottom"),
path(from = "x2", to = "x2", nudge_text_x = 0, nudge_text_y = 0, variance_position = "bottom"),
path(from = "x3", to = "x3", nudge_text_x = 0, nudge_text_y = 0, variance_position = "bottom"),
path(from = "x4", to = "x4", nudge_text_x = 0, nudge_text_y = 0, variance_position = "top"),
path(from = "x5", to = "x5", nudge_text_x = 0, nudge_text_y = 0, variance_position = "top"),
path(from = "x6", to = "x6", nudge_text_x = 0, nudge_text_y = 0, variance_position = "top"),
path(from = "x7", to = "x7", nudge_text_x = 0, nudge_text_y = 0, variance_position = "right"),
path(from = "x8", to = "x8", nudge_text_x = 0, nudge_text_y = 0, variance_position = "right"),
path(from = "x9", to = "x9", nudge_text_x = 0, nudge_text_y = 0, variance_position = "right"),
path(from = "visual", to = "visual", nudge_text_x = 0, nudge_text_y = 0, variance_position = "top"),
path(from = "textual", to = "textual", nudge_text_x = 0, nudge_text_y = 0, variance_position = "bottom"),
path(from = "speed", to = "speed", nudge_text_x = 0, nudge_text_y = 0, variance_position = "top")
)
#creating a title for diagram, (supports Markdown and HTML)
title_list <- list(
panel_title(panel_num = 1, title = "**Figure 1**<br>*Visual and Textual Ability Predicting Speeded Cognitive Processing*")
)
# Add a note, displayed as a caption beneath the diagram
note_list <- list(
panel_note(panel_num = 1, note_text = "*Note.* Standardised estimates with 95% confidence intervals. Dashed lines indicate non-significant paths.")
)
# All arguments are shown to illustrate the fine-tuning available, though most are at their defaults and could be omitted
my_sem_diagram <- diyPaths(fit = fit_sem, node_positions = node_list, path_positions = path_list,
standardised = TRUE,
digits = 3,
est_stars = TRUE,
est_p = FALSE,
est_ci = TRUE,
show_variances = TRUE,
variance_stars = FALSE,
variance_p = FALSE,
variance_ci = FALSE,
sig_linetype = TRUE,
p_threshold = 0.05,
node_width = 1.5, node_height = 1,
latent_node_size_adjust = 1,
observed_node_size_adjust = 0.5,
latent_node_text_size = 6,
observed_node_text_size = 4.5,
non_transparent_text = TRUE,
path_text_size = 4.5,
line_thickness = 0.6,
arrow_size = 0.2,
panel_cols = NULL,
show_group_labels = FALSE,
title_text_size = 22,
panel_titles = title_list,
panel_notes = note_list,
note_text_size = 16,
font_family = "sans",
title_note_font_family = "serif",
show_grid = FALSE,
grid_axis_scale = 1,
margin_x = 0.3,
margin_y = 0.3,
look_up_table = FALSE)
print(my_sem_diagram)

Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.