<p><div><div><div><div data-show-faces="false" data-height="21" data-width="100" data-layout="button_count" data-send="true" data-href="https://www.r-bloggers.com/ggplot2-sem-models-with-tidygraph-and-ggraph/"></div></div><div></div></div><div></div></div>
<div>
(This article was first published on <strong><a href="http://drsimonj.svbtle.com/ggsem-plot-sem-models-with-ggplot2"> blogR</a></strong>, and kindly contributed to <a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers)</a> </div>
<p><a rel="nofollow" href="https://twitter.com/drsimonj" target="_blank">@drsimonj</a> here to share a ggplot2-based function for plotting path analysis/structural equation models (SEM) fitted with <a rel="nofollow" href="http://lavaan.ugent.be/index.html" target="_blank">Yves Rosseel’s lavaan package</a>.</p>
<h2><a rel="nofollow" href="http://drsimonj.svbtle.com/ggsem-plot-sem-models-with-ggplot2#background_2" target="_blank"> </a>Background</h2>
<p>SEM and its related methods (path analysis, confirmatory factor analysis, etc.) can be visualized as Directed Acyclic Graphs with nodes representing variables (observed or latent), and edges representing the specified relationships between them. For this reason, we will use <a rel="nofollow" href="https://twitter.com/thomasp85" target="_blank">Thomas Lin Pedersen’s</a> <a rel="nofollow" href="https://github.com/thomasp85/tidygraph" target="_blank">tidygraph</a> and <a rel="nofollow" href="https://github.com/thomasp85/ggraph" target="_blank">ggraph</a> packages. These packages work together to work with relational structures in a tidy format and plot them using ggplot2.</p>
<h2><a rel="nofollow" href="http://drsimonj.svbtle.com/ggsem-plot-sem-models-with-ggplot2#the-function_2" target="_blank"> </a>The function</h2>
<p>Below is a function <code>ggsem()</code>, which takes a fitted lavaan object and returns a ggplot2 object representing the nodes, edges, and parameter values. It handles regression paths, correlations, latent factors, and factor loadings.</p>
<pre><code>library(tidyverse)
library(tidygraph)
library(ggraph)
library(lavaan)
</code></pre>
<pre><code># Plot a fitted lavaan object
ggsem &lt;- function(fit, layout = &quot;sugiyama&quot;) {

  # Extract standardized parameters
  params &lt;- lavaan::standardizedSolution(fit)

  # Edge properties
  param_edges &lt;- params %>% 
    filter(op %in% c(&quot;=~&quot;, &quot;~&quot;, &quot;~~&quot;), lhs != rhs, pvalue &lt; .10) %>%
    transmute(to = lhs,
              from = rhs,
              val = est.std,
              type = dplyr::case_when(
                op == &quot;=~&quot; ~ &quot;loading&quot;,
                op == &quot;~&quot;  ~ &quot;regression&quot;,
                op == &quot;~~&quot; ~ &quot;correlation&quot;,
                TRUE ~ NA_character_))

  # Identify latent variables for nodes
  latent_nodes &lt;- param_edges %>% 
    filter(type == &quot;loading&quot;) %>% 
    distinct(to) %>% 
    transmute(metric = to, latent = TRUE)

  # Node properties
  param_nodes &lt;- params %>% 
    filter(lhs == rhs) %>% 
    transmute(metric = lhs, e = est.std) %>% 
    left_join(latent_nodes) %>% 
    mutate(latent = if_else(is.na(latent), FALSE, latent))

  # Complete Graph Object
  param_graph &lt;- tidygraph::tbl_graph(param_nodes, param_edges)

  # Plot
  ggraph(param_graph, layout = layout) +
    # Latent factor Nodes
    geom_node_point(aes(alpha = as.numeric(latent)),
                                     shape = 16, size = 5) +
    geom_node_point(aes(alpha = as.numeric(latent)),
                                     shape = 16, size = 4, color = &quot;white&quot;) +
    # Observed Nodes
    geom_node_point(aes(alpha = as.numeric(!latent)),
                                     shape = 15, size = 5) +
    geom_node_point(aes(alpha = as.numeric(!latent)),
                                     shape = 15, size = 4, color = &quot;white&quot;) +
    # Regression Paths (and text)
    geom_edge_link(aes(color = val, label = round(val, 2),
                       alpha = as.numeric(type == &quot;regression&quot;)),
                   linetype = 1, angle_calc = &quot;along&quot;, vjust = -.5,
                   arrow = arrow(20, unit(.3, &quot;cm&quot;), type = &quot;closed&quot;)) +
    # Factor Loadings (no text)
    geom_edge_link(aes(color = val, alpha = as.numeric(type == &quot;loading&quot;)),
                   linetype = 3, angle_calc = &quot;along&quot;,
                   arrow = arrow(20, unit(.3, &quot;cm&quot;), ends = &quot;first&quot;, type = &quot;closed&quot;)) +
    # Correlation Paths (no text)
    geom_edge_link(aes(color = val, alpha = as.numeric(type == &quot;correlation&quot;)),
                   linetype = 2, angle_calc = &quot;along&quot;,
                   arrow = arrow(20, unit(.3, &quot;cm&quot;), type = &quot;closed&quot;, ends = &quot;both&quot;)) +
    # Node names
    geom_node_text(aes(label = metric),
                   nudge_y = .25, hjust = &quot;inward&quot;) +
    # Node residual error
    geom_node_text(aes(label = sprintf(&quot;%.2f&quot;, e)),
                   nudge_y = -.1, size = 3) +
    # Scales and themes
    scale_alpha(guide = FALSE, range = c(0, 1)) +
    scale_edge_alpha(guide = FALSE, range = c(0, 1)) +
    scale_edge_colour_gradient2(guide = FALSE, low = &quot;red&quot;, mid = &quot;darkgray&quot;, high = &quot;green&quot;) +
    scale_edge_linetype(guide = FALSE) +
    scale_size(guide = FALSE) +
    theme_graph()
}
</code></pre>
<p>To test this function, we’ll use the five, standardized variables from the <code>diamonds</code> data set:</p>
<pre><code>d &lt;- ggplot2::diamonds %>% 
  select(x, y, z, carat, price) %>% 
  mutate_all(funs((. - mean(.)) / sd(.)))
</code></pre>
<h2><a rel="nofollow" href="http://drsimonj.svbtle.com/ggsem-plot-sem-models-with-ggplot2#path-analysis_2" target="_blank"> </a>Path Analysis</h2>
<p>Let’s define a simple path model where diamond price is predicted by its carats, in turn, predicted by its x-axis length.</p>
<pre><code>model &lt;- ({&quot;
    price ~ carat
    carat ~ x
&quot;})

fit &lt;- sem(model, data = d)

ggsem(fit)
</code></pre>
<p><a rel="nofollow" href="https://i0.wp.com/svbtleusercontent.com/de9ti6iymjqha.png?ssl=1" target="_blank"><img alt="init-example-1.png" src="https://i2.wp.com/svbtleusercontent.com/de9ti6iymjqha_small.png?w=456&ssl=1" data-recalc-dims="1"></a></p>
<h2><a rel="nofollow" href="http://drsimonj.svbtle.com/ggsem-plot-sem-models-with-ggplot2#correlations_2" target="_blank"> </a>Correlations</h2>
<p>We can also extend the model to include the y-axis length, which we assume to correlate with the x-axis length.</p>
<pre><code>model &lt;- ({&quot;
    price ~ carat
    carat ~ x + y
    x ~~ y
&quot;})

fit &lt;- sem(model, data = d)

ggsem(fit)
</code></pre>
<p><a rel="nofollow" href="https://i0.wp.com/svbtleusercontent.com/ioveug8ncip9w.png?ssl=1" target="_blank"><img alt="unnamed-chunk-6-1.png" src="https://i1.wp.com/svbtleusercontent.com/ioveug8ncip9w_small.png?w=456&ssl=1" data-recalc-dims="1"></a></p>
<h2><a rel="nofollow" href="http://drsimonj.svbtle.com/ggsem-plot-sem-models-with-ggplot2#latent-factors_2" target="_blank"> </a>Latent Factors</h2>
<p>We will now model the x, y, and z lengths as a latent “size” factor, which predicts carat</p>
<pre><code>model &lt;- ({&quot;
    size =~ x + y + z

    price ~ carat
    carat ~ size
&quot;})

fit &lt;- sem(model, data = d)

ggsem(fit)
</code></pre>
<p><a rel="nofollow" href="https://i1.wp.com/svbtleusercontent.com/zdsiogcd3wuh8q.png?ssl=1" target="_blank"><img alt="unnamed-chunk-7-1.png" src="https://i0.wp.com/svbtleusercontent.com/zdsiogcd3wuh8q_small.png?w=456&ssl=1" data-recalc-dims="1"></a></p>
<p>To compare, here we model “size” as a common underlying factor of carat and price:</p>
<pre><code>model &lt;- ({&quot;
    size =~ x + y + z

    carat ~ size
    price ~ size

    price ~~ 0*carat
&quot;})

fit &lt;- sem(model, data = d)

ggsem(fit)
</code></pre>
<p><a rel="nofollow" href="https://i0.wp.com/svbtleusercontent.com/mvqrwty6plzg7q.png?ssl=1" target="_blank"><img alt="unnamed-chunk-8-1.png" src="https://i0.wp.com/svbtleusercontent.com/mvqrwty6plzg7q_small.png?w=456&ssl=1" data-recalc-dims="1"></a></p>
<h2><a rel="nofollow" href="http://drsimonj.svbtle.com/ggsem-plot-sem-models-with-ggplot2#color-for-strength-and-sign_2" target="_blank"> </a>Color for strength and sign</h2>
<p>Edges are also colored based on parameter strength and sign. For example, let’s reverse score price and see how this appears:</p>
<pre><code>d_rev &lt;- d %>% 
  mutate(price = max(price) - price)

fit &lt;- sem(model, data = d_rev)

ggsem(fit)
</code></pre>
<p><a rel="nofollow" href="https://i2.wp.com/svbtleusercontent.com/4iwnacvdus3jew.png?ssl=1" target="_blank"><img alt="unnamed-chunk-9-1.png" src="https://i2.wp.com/svbtleusercontent.com/4iwnacvdus3jew_small.png?w=456&ssl=1" data-recalc-dims="1"></a></p>
<h2><a rel="nofollow" href="http://drsimonj.svbtle.com/ggsem-plot-sem-models-with-ggplot2#back-to-ggplot2_2" target="_blank"> </a>Back to ggplot2</h2>
<p>By using ggraph, we can extend <code>ggsem()</code> with any ggplot2 syntax. For example, it’s easy to add a title:</p>
<pre><code>ggsem(fit) +
    ggtitle(&quot;SEM of the diamond data set&quot;,
            subtitle = &quot;Latent variable size as common cause of carat and price&quot;)
</code></pre>
<p><a rel="nofollow" href="https://i1.wp.com/svbtleusercontent.com/b3ixk5eig3ayg.png?ssl=1" target="_blank"><img alt="unnamed-chunk-11-1.png" src="https://i1.wp.com/svbtleusercontent.com/b3ixk5eig3ayg_small.png?w=456&ssl=1" data-recalc-dims="1"></a></p>
<p>And, of course, you can always tweak the <code>ggsem()</code> function itself to achieve the desired result!</p>
<h2><a rel="nofollow" href="http://drsimonj.svbtle.com/ggsem-plot-sem-models-with-ggplot2#a-note-about-semplot_2" target="_blank"> </a>A note about semPlot</h2>
<p>For those who know about it, you might be asking why all this is necessary when we have <a rel="nofollow" href="http://sachaepskamp.com/semPlot" target="_blank">Sacha Epskamp’s</a> awesome <a rel="nofollow" href="http://sachaepskamp.com/semPlot" target="_blank">semPlot</a> package? There are likely many cases where semPlot will do a better job of laying out the nodes and edges.</p>
<p>For me, there were two reasons. One was a practical business reason. In my work, we operate using a shared R package library. Compared to semPlot, tidygraph and ggraph solve a broader range of relevant problems for us and are, therefore, available in our shared library. I can use semPlot locally, but prefer to work with packages that help me to collaborate faster at work. The other reason was control over aesthetics. semPlot is amazing, but it doesn’t allow for the sort of control over the graph aesthetics that tidygraph and ggraph provide.</p>
<h2><a rel="nofollow" href="http://drsimonj.svbtle.com/ggsem-plot-sem-models-with-ggplot2#sign-off_2" target="_blank"> </a>Sign off</h2>
<p>Thanks for reading and I hope this was useful for you.</p>
<p>For updates of recent blog posts, follow <a rel="nofollow" href="https://twitter.com/drsimonj" target="_blank">@drsimonj</a> on Twitter, or email me at <a rel="nofollow" href="mailto:drsimonjackson@gmail.com" target="_blank">drsimonjackson@gmail.com</a> to get in touch.</p>
<p>If you’d like the code that produced this blog, check out the <a rel="nofollow" href="https://github.com/drsimonj/blogR" target="_blank">blogR GitHub repository</a>.</p>
	<p>
<div>
<div>To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="http://drsimonj.svbtle.com/ggsem-plot-sem-models-with-ggplot2"> blogR</a></strong>.</div>
<hr>
<a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers.com</a> offers <strong><a rel="nofollow" href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers">daily e-mail updates</a></strong> about <a rel="nofollow" href="https://www.r-project.org/" title="The R Project for Statistical Computing">R</a> news and <a rel="nofollow" href="https://www.r-bloggers.com/search/tutorial" title="R tutorials">tutorials</a> on topics such as: <a rel="nofollow" href="https://www.r-bloggers.com/search/data%20science" title="Data science">Data science</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/Big%20Data" title="Big Data">Big Data, <a rel="nofollow" href="https://www.r-users.com/" title="R jobs">R jobs</a>, visualization (<a rel="nofollow" href="https://www.r-bloggers.com/search/ggplot2" title="ggplot and ggplot2 tutorials">ggplot2</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/boxplot" title="Boxplots using lattice and ggplot2 tutorials">Boxplots</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/map" title="Maps and gis">maps</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/animation" title="Animation in R">animation</a>), programming (<a rel="nofollow" href="https://www.r-bloggers.com/search/RStudio" title="RStudio IDE for R">RStudio</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/sweave" title="Sweave and literate programming">Sweave</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/LaTeX" title="LaTeX in R">LaTeX</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/SQL" title="SQL and databases">SQL</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/eclipse" title="Eclipse IDE for R">Eclipse</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/git" title="git and github, Version Control System">git</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/hadoop" title="Large data in R using Hadoop">hadoop</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/Web+Scraping" title="Web Scraping of google, facebook, yahoo, twitter and more using R">Web Scraping</a>) statistics (<a rel="nofollow" href="https://www.r-bloggers.com/search/regression" title="Regressions and ANOVA analysis tutorials">regression</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/PCA" title="principal component analysis tutorial">PCA</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/time+series" title="Time series">time series</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/trading" title="finance trading">trading</a>) and more...
<img width="1" alt="" src="http://feeds.feedburner.com/~r/RBloggers/~4/c7zt326Fll4" height="1"></a></div>