diyPaths: Manually plot path diagrams for structural equation models

View source: R/diyPaths.R

diyPathsR Documentation

Manually plot path diagrams for structural equation models

Description

Plot fully customisable path diagrams for structural equation models fitted with lavaan, rendered using ggplot2.

To render the ggplot object of the diagram, the function only requires users to supply a fitted lavaan model, specify node positions using x-y coordinates, and detail where on the perimeter of each node (top, bottom, left, or right) each path should begin and end. The render automatically inserts estimates centred on the midpoint and adjusts each node's shape to match its variable type. A range of optional fine-tuning arguments are included, facilitating the creation of a path diagram exactly as you envision it, entirely within R.

Usage

diyPaths(
  fit,
  node_positions,
  path_positions,
  standardised = FALSE,
  digits = 3,
  est_stars = FALSE,
  est_p = FALSE,
  est_ci = FALSE,
  variance_stars = FALSE,
  variance_p = FALSE,
  variance_ci = FALSE,
  sig_linetype = FALSE,
  p_threshold = 0.05,
  show_variances = FALSE,
  show_grid = FALSE,
  grid_axis_scale = 1,
  non_transparent_text = TRUE,
  latent_node_text_size = 4,
  observed_node_text_size = 4,
  path_text_size = 3.5,
  line_thickness = 0.6,
  arrow_size = 0.2,
  node_width = 1.5,
  node_height = 1,
  latent_node_size_adjust = 1,
  observed_node_size_adjust = 1,
  show_group_labels = FALSE,
  panel_titles = NULL,
  panel_cols = NULL,
  margin_x = 0.5,
  margin_y = 0.5,
  look_up_table = FALSE,
  panel_notes = NULL,
  note_text_size = NULL,
  title_text_size = NULL,
  font_family = "",
  title_note_font_family = NULL
)

Arguments

fit

A fitted model object of class lavaan.

node_positions

Specify a list of node position objects. Use the node() helper function to assist with this.

path_positions

Specify a list of path configuration objects. Use the path() helper function to assist with this.

standardised

Logical. If TRUE, uses standardised parameter estimates (est.std). Default is FALSE.

digits

Number of decimal places to display for estimates. Default is 3.

est_stars

Logical. Whether to display significance stars on path estimates. Default is FALSE.

est_p

Logical. Whether to display p-values on path estimates. Default is FALSE.

est_ci

Logical. Whether to display confidence intervals on path estimates. Default is FALSE.

variance_stars

Logical. Whether to display significance stars on variance paths. Default is FALSE.

variance_p

Logical. Whether to display p-values on variance paths. Default is FALSE.

variance_ci

Logical. Whether to display confidence intervals on variance paths. Default is FALSE.

sig_linetype

Logical. If TRUE, renders non-significant paths with dashed lines. Default is FALSE.

p_threshold

Statistical significance threshold used to determine non-significant paths when sig_linetype = TRUE. Default is 0.05.

show_variances

Logical. Whether to display variance and residual paths. Default is FALSE.

show_grid

Logical. Whether to overlay a coordinate grid. Default is FALSE.

grid_axis_scale

Sets the spacing of grid-lines when show_grid = TRUE. Default is 1.

non_transparent_text

Logical. If TRUE, path estimates receive a white background mask, if FALSE, they have a transparent background. Default is TRUE.

latent_node_text_size

Text font size for latent node labels. Default is 4.

observed_node_text_size

Text font size for observed node labels. Default is 4.

path_text_size

Text font size for path estimate labels. Default is 3.5.

line_thickness

Sets thickness of paths. Default is 0.6.

arrow_size

Sets the size of arrow heads. Default is 0.2.

node_width

Base width for nodes. Default is 1.5.

node_height

Base height for node shapes. Default is 1.

latent_node_size_adjust

Numeric multiplier scaling latent node ellipses. Default is 1.

observed_node_size_adjust

Numeric multiplier scaling observed node rectangles. Default is 1.

show_group_labels

Logical. For multi-group models, whether to annotate each panel with its panel number and the raw group value it represents (e.g. "Panel 1: Group = male"). Needed to identify which diagram represents each group and its internal panel number when creating panel titles and notes. Default is FALSE.

panel_titles

Specify a list of titles for panels. Use the panel_title() helper function to assist with this. Any panel not referenced is left untitled. Default is NULL.

panel_cols

Integer for number of columns to use when arranging multi-group panels. Default is NULL.

margin_x

Padding for plot limits along the x-axis. Default is 0.5.

margin_y

Padding for plot limits along the y-axis. Default is 0.5.

look_up_table

Logical. If TRUE, also returns a look-up table detailing the width (x scale) and height (y scale) of latent and observed nodes, along with the text sizes used for latent, observed, and path labels. Default is FALSE.

panel_notes

Specify a list of notes, displayed as captions beneath each panel. Use the panel_note() helper function to assist with this. Any panel not referenced is left without a note. Default is NULL.

note_text_size

Font size for panel notes (captions). Default is NULL (ggplot2's default caption size).

title_text_size

Font size for panel titles. Default is NULL (ggplot2's default title size).

font_family

Font family used for all text in the diagram. Default is "", the graphics device's default font.

title_note_font_family

Font family for panel titles and notes. Default is NULL, which uses font_family.

Details

The easiest way to start is with template(), which prints ready-to-run code for your fitted model. Edit the node positions and path sides it generates, render with diyPaths(), then fine-tune and re-render until the layout looks right. See vignette("diy.sem.plot") for worked examples.

Value

A ggplot (or patchwork, for multi-group models) object representing the SEM path diagram(s). If look_up_table = TRUE, a list containing the diagram(s) (⁠$plot⁠) and a look-up data.frame of node width/height and text sizes for latent, observed, and path labels (⁠$look_up_table⁠).

See Also

template() to generate starting code for node_positions, path_positions, panel_titles and panel_notes; node(), path(), panel_title(), and panel_note() for the helper functions used within them.

Examples


#full SEM example, this model may not make theoretical sense.

library(lavaan)
library(ggplot2)

data(HolzingerSwineford1939, package = "lavaan")

#specify the model

sem_model <- '
  visual  =~ x1 + x2 + x3
  textual =~ x4 + x5 + x6
  speed   =~ x7 + x8 + x9

  speed ~ visual + textual
  visual ~~ textual
'

#fit the model

fit <- sem(sem_model, data = HolzingerSwineford1939)

#generate a starting template

temp <- template(fit = fit, diyPaths_arguments = "max", quiet = TRUE)

#### edited template ####

node_list <- list(
  node(name = "visual", x = 1, y = 1, label = "Visual"),
  node(name = "textual", x = 1, y = 2, label = "Textual"),
  node(name = "speed", x = 4, y = 1.5, label = "Speed"),
  node(name = "x1", x = -0.06, y = -0.5, label = "Visual\nPerception"),
  node(name = "x2", x = 1, y = -0.5, label = "Cubes"),
  node(name = "x3", x = 2.06, y = -0.5, label = "Lozenges"),
  node(name = "x4", x = -0.06, y = 3.5, label = "Paragraph\nComprehension"),
  node(name = "x5", x = 1, y = 3.5, label = "Sentence\nCompletion"),
  node(name = "x6", x = 2.06, y = 3.5, label = "Word\nMeaning"),
  node(name = "x7", x = 6, y = 0.5, label = "Speeded\nAddition"),
  node(name = "x8", x = 6, y = 1.5, label = "Speeded\nCounting"),
  node(name = "x9", x = 6, y = 2.5, label = "Speeded\nDiscrimination")
)

path_list <- list(
  # regression paths
  path(from = "visual", to = "speed", side_from = "right", side_to = "left"),
  path(from = "textual", to = "speed", side_from = "right", side_to = "left"),

  # loading paths
  path(from = "visual", to = "x1", side_from = "bottom", side_to = "top", nudge_text_x = -0.1),
  path(from = "visual", to = "x2", side_from = "bottom", side_to = "top"),
  path(from = "visual", to = "x3", side_from = "bottom", side_to = "top", nudge_text_x = 0.1),
  path(from = "textual", to = "x4", side_from = "top", side_to = "bottom", nudge_text_x = -0.1),
  path(from = "textual", to = "x5", side_from = "top", side_to = "bottom"),
  path(from = "textual", to = "x6", side_from = "top", side_to = "bottom", nudge_text_x = 0.1),
  path(from = "speed", to = "x7", side_from = "right", side_to = "left"),
  path(from = "speed", to = "x8", side_from = "right", side_to = "left"),
  path(from = "speed", to = "x9", side_from = "right", side_to = "left"),

  # covariance/correlation paths
  path(from = "visual", to = "textual", side_from = "left", side_to = "left", cov_curve = -0.6)
)

title_list <- list(
  panel_title(panel_num = 1, title = "My SEM Plot")
)

note_list <- list(
  panel_note(panel_num = 1, note_text = "Standardised estimates shown.")
)

p <- diyPaths(fit = fit, node_positions = node_list, path_positions = path_list,
              standardised = TRUE,
              digits = 3,
              est_stars = TRUE,
              est_p = FALSE,
              est_ci = FALSE,
              show_variances = FALSE,
              variance_stars = FALSE,
              variance_p = FALSE,
              variance_ci = FALSE,
              sig_linetype = FALSE,
              p_threshold = 0.05,
              panel_titles = title_list,
              panel_notes = note_list,
              show_grid = TRUE,
              grid_axis_scale = 0.5,
              non_transparent_text = TRUE,
              latent_node_text_size = 4,
              observed_node_text_size = 3,
              path_text_size = 3.5,
              line_thickness = 0.6,
              arrow_size = 0.2,
              node_width = 1.5, node_height = 1,
              latent_node_size_adjust = 0.8,
              observed_node_size_adjust = 0.55,
              show_group_labels = FALSE,
              panel_cols = NULL,
              font_family = "sans",
              title_note_font_family = "serif",
              margin_x = 0.5,
              margin_y = 0.5,
              look_up_table = TRUE)

print(p)

diy.sem.plot documentation built on Nov. 11, 2026, 1:11 a.m.