Drawing simple mediation diagram in R

You can use the 'psych' package to test your moderation/mediation and it will give you a plot as well.

mediate(Output ~ Independent1 + (Mediator), data =mydata)

Here are two versions of a mediation diagram built with DiagrammeR and a third built with TikZ. Each has strengths and weaknesses:

  • DiagrammeR + Graphviz:

    • Pros: easy to create diagrams with provided function, lots of options to adjust design of nodes.
    • Cons: few options to adjust text on edges, output is an htmlwidget object that can print to pdf but may require conversion under specific circumstances such as within a for-loop.
  • TikZ

    • Pros: highly customizable, including text on edges; easy to generate diagram with provided function
    • Cons: if you need HTML output, it's designed for LaTeX; syntax can be confusing
  • DiagrammeR native syntax (offered just for completeness)

    • Pros: code very easy to read, edit for R users
    • Cons: even fewer apparent ways to adjust text on edges

I've coded the first and second examples with a function med_diagram that uses the glue package to assemble the relevant graphviz or TikZ code. The function requires a data.frame that expects one row with columns for relevant labels and coefficients. For the DiagrammeR-specific function, there are also arguments to adjust various elements of the design.

Diagrammer + Graphviz

med_data <-
  data.frame(
    lab_x   = "Math\\nAbility",
    lab_m   = "Math\\nself-efficacy",
    lab_y   = "Interest in the\\nmath major",
    coef_xm = ".47*",
    coef_my = ".36*",
    coef_xy = "0.33* (.16)"
  )


med_diagram <- function(data, height = .75, width = 2, graph_label = NA, node_text_size = 12, edge_text_size = 12, color = "black", ranksep = .2, minlen = 3){
  
  require(glue)
  require(DiagrammeR)
  
  data$height  <- height   # node height
  data$width   <- width    # node width
  data$color   <- color    # node + edge border color
  data$ranksep <- ranksep  # separation btwn mediator row and x->y row
  data$minlen  <- minlen   # minimum edge length
  
  data$node_text_size  <- node_text_size
  data$edge_text_size  <- edge_text_size
  
  data$graph_label <- ifelse(is.na(graph_label), "", paste0("label = '", graph_label, "'"))

diagram_out <- glue::glue_data(data,
  "digraph flowchart {
      fontname = Helvetica
      <<graph_label>>
      graph [ranksep = <<ranksep>>]

      # node definitions with substituted label text
      node [fontname = Helvetica, shape = rectangle, fixedsize = TRUE, width = <<width>>, height = <<height>>, fontsize = <<node_text_size>>, color = <<color>>]        
        mm [label = '<<lab_m>>']
        xx [label = '<<lab_x>>']
        yy [label = '<<lab_y>>']

      # edge definitions with the node IDs
      edge [minlen = <<minlen>>, fontname = Helvetica, fontsize = <<edge_text_size>>, color = <<color>>]
        mm -> yy [label = '<<coef_my>>'];
        xx -> mm [label = '<<coef_xm>>'];
        xx -> yy [label = '<<coef_xy>>'];
      
      { rank = same; mm }
      { rank = same; xx; yy }
      
      }

      ", .open = "<<", .close = ">>")  


DiagrammeR::grViz(diagram_out)  
}

med_diagram(med_data)

Mediation diagram generated by function

TikZ

TikZ and PGF need to be loaded as LaTeX packages in the preamble. The sample code below includes both those packages and some additional commands that, for example, set a global specification for 'mynode' used in the diagram. I received a warning to include \pgfplotsset{compat=1.17} in my preamble but that may not be necessary for others. Note, this code builds on the example provided here: https://tex.stackexchange.com/a/225940/34597

---
title: "Sample Rmd"
author: "Your name here"
output: pdf_document
header-includes:
  - \usepackage{tikz}
  - \usepackage{pgfplots}
  - \pgfplotsset{compat=1.17}
  - \tikzset{mynode/.style={draw,text width=1in,align=center} }
  - \usetikzlibrary{positioning}
---

```{r, load_packages, include = FALSE}
library(glue)
```
  
```{r, load_function}
med_diagram_tikz <- function(data) {
  glue::glue_data(med_data, 
"
\\begin{figure}
\\begin{center}
\\begin{tikzpicture}[font=\\sffamily]
    \\node[mynode] (m){<<lab_m>>};
    \\node[mynode,below left=of m](x) {<<lab_x>>};
    \\node[mynode,below right=of m](y) {<<lab_y>>};
    \\draw[-latex] (x.north) -- node[auto] {<<coef_xm>>} (m.west);
    \\draw[-latex] (m.east) -- node[auto] {<<coef_my>>} (y.north);
    \\draw[-latex] (x.east) --
            node[below=2mm, align=center] {<<coef_xy>>} (y.west);
\\end{tikzpicture}
\\end{center}
\\end{figure}
", 
.open = "<<", .close = ">>"
)
}
```
  
  
```{r create_diagram, echo = FALSE, results = 'asis'}
med_data <-
  data.frame(
    lab_x   = "Math\\\\Ability",
    lab_m   = "Math\\\\self-efficacy",
    lab_y   = "Interest in the\\\\math major",
    coef_xm = ".47*",
    coef_my = ".36*",
    coef_xy = "0.33* (.16)"
  )

tikz_diagram_out <- med_diagram_tikz(med_data)

# requires chunk header to be set to results = 'asis'
cat("\n", tikz_diagram_out, "\n")

```

Mediation diagram drawn in TikZ

Diagrammer native syntax

This third version of a mediation diagram uses syntax that's more easily understood by R users but I don't love how the edge label text gets placed oddly (hence the two alternative versions above).

library(DiagrammeR)

# Create a node data frame (ndf)
ndf <- create_node_df(
  n         = 3,
  label     = c( "Math\nself-efficacy","Math\nability", "Interest in\nthe math major"),
  shape     = rep("rectangle", 3),
  style     = "empty",
  fontsize  = 6,
  fixedsize = TRUE,
  height    = .5,
  width     = .75,
  color     = "gray80",
  x         = c(1, 2, 3),
  y         = c(1, 2, 1)
)

# Create an edge data frame (edf)
edf <- create_edge_df(
  from     = c(1, 1, 2),
  to       = c(2, 3, 3),
  label    = c(".47*", ".33* (.16)", ".36*"),
  fontsize = 6,
  minlen   = 1,
  color    = "gray80",
  )

# Create a graph with the ndf and edf
graph <- create_graph(
  nodes_df = ndf,
  edges_df = edf
  )

graph %>%
  render_graph()

Example mediation diagram showing edge label text not lining up properly


While @baptiste solution might work as well, I was looking for a publishable format.

Function plotmat from library(diagram) is the one that got me the closest to my example:

path diagram

For a reproducible example use:

library(diagram)
data <- c(0, "'.47*'", 0,
          0, 0, 0, 
          "'.36*'", "'.33* (.16)'", 0)
M<- matrix (nrow=3, ncol=3, byrow = TRUE, data=data)
plot<- plotmat (M, pos=c(1,2), 
                name= c( "Math self-efficacy","Math ability", "Interest in the math major"), 
                box.type = "rect", box.size = 0.12, box.prop=0.5,  curve=0)

Tags:

Diagram

R

Ggplot2