1 引言
2 结构方程模型结果数据表
3 数据变为图
4 自动布局
5 手动布局
6 结语
1 引言
纯代码,画一个结构方程模型路径图。来自最近一篇文章的 SEM 路径图,类似下图,
此案例来自以下文章,
Tree species richness and N-fixing tree species enhance the chemical stability of soil organic carbon in subtropical plantations
2 加载包
library(igraph)
library(ggraph)
library(tidygraph)
3 结构方程模型结果数据表
因原文没公开代码、数据,不显著路径被假设宽度为
0.1
。
dat <- read.csv("sbb-sem-table.csv")
dat
from to weight p note
1 FBR LLCN 0.100 1.000
2 FBR PL -0.281 0.010 **
3 TSR FBR 0.100 1.000
4 TSR PL 0.366 0.001 ***
5 TSR LLCN 0.100 1.000
6 LLCN PL 0.388 0.040 *
7 DB PL 0.100 1.000
8 pH PL -0.262 0.010 **
9 ML PL 0.741 0.001 ***
10 CN ML 0.100 1.000
11 PL ChemStbSOC 0.850 0.001 ***
12 ML ChemStbSOC -0.791 0.001 ***
4 数据变为图
ggr <- dat |>
graph_from_data_frame(
directed = TRUE
) |>
as_tbl_graph(gr)
ggr
# A tbl_graph: 9 nodes and 12 edges
#
# A directed acyclic simple graph with 1 component
#
# Node Data: 9 × 1 (active)
name
<chr>
1 FBR
2 TSR
3 LLCN
4 DB
5 pH
6 ML
7 CN
8 PL
9 ChemStbSOC
#
# Edge Data: 12 × 5
from to weight p note
<int> <int> <dbl> <dbl> <chr>
1 1 3 0.1 1 ""
2 1 8 -0.281 0.01 "**"
3 2 1 0.1 1 ""
# ℹ 9 more rows
5 自动布局
其实这样可能就差不多了,如需要手动调整,可使用我制作的 SEM 可视化 APP
,见文末。
原文没有给代码、数据,不显著路径假设宽度为
0.1
。
font <- "Open Sans"
fig1 <- ggr |>
ggraph(layout = "sugiyama") +
geom_edge_link(
aes(
label = weight,
color = weight > 0,
linetype = p < 0.05,
linewidth = abs(weight),
end_cap = label_rect(
node2.name,
margin(5, 0, 0, 0, "mm")
)
),
arrow = arrow(60, unit(5, "mm"))
) +
geom_node_label(
aes(label = name),
family = font
) +
scale_edge_color_brewer(
name = "path coefs > 0",
palette = "Set2",
direction = -1
) +
scale_edge_linetype_manual(
name = "p < 0.05",
values = c(2, 1)
) +
scale_edge_width_continuous(
name = "abs path coefs",
range = c(0.5, 5)
) +
coord_cartesian(
clip = "off"
) +
theme_graph(
base_family = font
)
fig1
ggsave(
"fig1.png",
width = 9,
height = 5,
dpi = 600
)
6 手动布局
手动给出变量节点的坐标位置。
原文没有给代码、数据,不显著路径假设宽度为
0.1
。
new_layout <- read.csv(
"sem-layout.csv"
)
name x y
1 FBR -10 10
2 TSR 0 10
3 LLCN 10 10
4 DB -3 2
5 pH -3 -2
6 ML 10 0
7 CN 5 -2
8 PL -10 0
9 ChemStbSOC 0 -10
使用 create_layout
函数制作新布局。
ggr_new_layout <- create_layout(
ggr,
layout = "manual",
x = new_layout$x,
y = new_layout$y
)
arrow_1_head <- arrow(
angle = 60,
length = unit(4, "mm")
)
arrow_2_heads <- arrow(
angle = 60,
length = unit(4, "mm"),
ends = "both"
)
scale_3_sets <- list(
scale_edge_color_brewer(
name = "path coefs > 0",
palette = "Set2",
direction = -1
),
scale_edge_linetype_manual(
name = "p < 0.05",
values = c(2, 1)
),
scale_edge_width_continuous(
name = "abs path coefs",
range = c(0.4, 4)
)
)
fig2 <- ggr_new_layout |>
ggraph() +
geom_edge_arc(
strength = 0.7,
aes(
filter =
(node1.name == "FBR" & node2.name == "LLCN"),
label = weight,
color = weight > 0,
linetype = p < 0.05,
linewidth = abs(weight),
start_cap = label_rect(
node2.name,
margin(7, 0, 7, 0, "mm")
),
end_cap = label_rect(
node2.name,
margin(7, 0, 7, 0, "mm")
)
),
arrow = arrow_2_heads,
linejoin = "mitre",
linemitre = 5,
label_size = 3,
family = font,
show.legend = FALSE
) +
geom_edge_link(
aes(
filter =
(node1.name == "ML" & node2.name == "PL"),
label = weight,
color = weight > 0,
linetype = p < 0.05,
linewidth = abs(weight),
start_cap = label_rect(
node2.name,
margin(0, 5, 0, 5, "mm")
),
end_cap = label_rect(
node2.name,
margin(0, 5, 0, 5, "mm")
)
),
arrow = arrow_2_heads,
linejoin = "mitre",
linemitre = 5,
label_size = 3,
family = font,
show.legend = FALSE
) +
geom_edge_elbow(
strength = 1,
flipped = TRUE,
aes(
direction = "don't know why",
filter =
(node1.name == "PL" & node2.name == "ChemStbSOC"),
label = weight,
color = weight > 0,
linetype = p < 0.05,
linewidth = abs(weight),
end_cap = label_rect(
node2.name,
margin(7, 0, 7, 0, "mm")
)
),
arrow = arrow_1_head,
linejoin = "mitre",
linemitre = 5,
label_size = 3,
family = font
) +
geom_edge_elbow(
strength = 1,
flipped = TRUE,
aes(
direction = "don't know why",
filter =
(node1.name == "ML" & node2.name == "ChemStbSOC"),
label = weight,
color = weight > 0,
linetype = p < 0.05,
linewidth = abs(weight),
end_cap = label_rect(
node2.name,
margin(7, 0, 7, 0, "mm")
)
),
arrow = arrow_1_head,
linejoin = "mitre",
linemitre = 5,
label_size = 3,
family = font
) +
geom_edge_link(
aes(
filter =
!(node1.name == "FBR" & node2.name == "LLCN") &
!(node1.name == "ML" & node2.name == "PL") &
!(node1.name == "ML" & node2.name == "ChemStbSOC") &
!(node1.name == "PL" & node2.name == "ChemStbSOC"),
label = weight,
color = weight > 0,
linetype = p < 0.05,
linewidth = abs(weight),
end_cap = label_rect(
node2.name,
margin(7, 0, 7, 0, "mm")
)
),
arrow = arrow_1_head,
linejoin = "mitre",
linemitre = 5,
label_size = 3,
family = font
) +
geom_node_label(
aes(label = name),
family = font,
alpha = 0.6
) +
scale_3_sets +
coord_equal(
clip = "off"
) +
theme_graph(
base_family = font
)
fig2
ggsave(
"fig2.png",
width = 6,
height = 6,
dpi = 600
)
7 结语
此例可能只适合作为一个代码小练习,实际运用起来比较复杂。但也许有一天,此功能会加入到我制作的 SEM 可视化 APP
。
👉 可视化非常复杂的 SEM 路径图|Manuel Delgado-Baquerizo 风格
👉 复现 Science 论文 piecewiseSEM 结构方程模型|但不用写 R 代码
👉 同时做 20 个 lavaan SEM 的模型选择|但不用写 R 代码
👉 结构方程模型可视化案例|Nature 子刊 SEM
👉 使用鼠标交互式可视化 SEM 路径图|独家 shiny APP 工具
👉 近期关于 SEM 的推送|共 10 篇
(点击小手 👉 即可跳转。)