PAYATHON 2026

ggalluvial 中如何调整 axis 的矩形顺序?factor levels 不生效

排障赵师傅

保留 factor levels 的设置。如果不同 axis 中有同名类别,还需要在各图层中统一设置 discern = TRUE,避免它们在内部转换时共用一套顺序。再用 decreasing = NA 按类别排序,并指定排列方向。

以下按从上到下依次显示 0、2、3 和 low、int、high 设置。没有实际数据,还无法确定具体原因,可以先检查不同 axis 是否存在同名类别。

为什么单独设置 factor levels 可能无效?

使用 aes(axis1 = ..., axis2 = ..., ...) 时,ggalluvial 会在内部将宽表转换成长表,把各 axis 的类别合并到一个 stratum 因子中。

默认情况下,不同 axis 中的同名类别不会被区分。合并后的 levels 会按各轴 levels 首次出现的顺序去重。例如,前面的 axis 已经按 3、0、2 引入了这些类别,即使 axis3 的 levels 设为 0、2、3,也可能受合并后顺序的影响。具体逻辑见 to_lodes_form() 的实现。

discern = TRUE 会区分不同 axis 中的同名类别,让各轴保留自己的类别顺序。相应的统计图层可以将该参数传给 to_lodes_form(),参见参数说明。

原代码中的另外两处设置各有用途,都不能直接指定各 axis 内矩形的上下顺序:

  • order = as.numeric(int_score) 控制同一个矩形内部的流线排列,参见 order 的说明。
  • scale_x_discrete(limits = ...) 控制四个 axis 的横向顺序。

在所有图层中统一设置排序参数

先设置 axis3 和 axis4 的 levels,再重新创建图形:

library(ggplot2)
library(ggalluvial)

df$axis3_score <- factor(
  df$axis3_score,
  levels = c("0", "2", "3")
)

df$axis4_score <- factor(
  df$axis4_score,
  levels = c("low", "int", "high")
)

p <- ggplot(
  df,
  aes(
    axis1 = axis1_score,
    axis2 = axis2_score,
    axis3 = axis3_score,
    axis4 = axis4_score,
    y = total_score
  )
) +
  geom_flow(
    aes(fill = int_score, order = as.numeric(int_score)),
    width = 0.2,
    discern = TRUE,
    decreasing = NA,
    reverse = TRUE
  ) +
  geom_stratum(
    width = 0.2,
    fill = "grey",
    discern = TRUE,
    decreasing = NA,
    reverse = TRUE
  ) +
  geom_text(
    stat = "stratum",
    aes(label = after_stat(stratum)),
    discern = TRUE,
    decreasing = NA,
    reverse = TRUE
  ) +
  scale_x_discrete(
    limits = c("axis1", "axis2", "axis3", "axis4"),
    expand = c(0.1, 0.1)
  ) +
  theme_minimal()

p

这几个参数分别控制类别区分、排序依据和显示方向:

  • discern = TRUE:区分不同 axis 中的同名类别。
  • decreasing = NA:按类别顺序排列,不按矩形总量大小排序。
  • reverse = TRUE:在权重非负、y 轴未反转的通常情况下,让 levels 按从上到下的方向显示。

如果希望指定顺序从下到上显示,将三个图层的 reverse 都改为 FALSE。流线、矩形和文字分别计算位置,排序参数必须一致,否则可能错位。参见排序方向与图层一致性说明。

启用 discern 后,标签出现后缀怎么办?

discern = TRUE 可能将重复类别命名为 "0"、"0.1" 等,因此用 after_stat(stratum) 直接显示标签时,也可能显示这些后缀。

如果需要保留原始标签,可以先显式转换成长表,同时保存原始轴值。以下代码接在前面的 factor 设置之后运行:

axis_cols <- c(
  "axis1_score", "axis2_score",
  "axis3_score", "axis4_score"
)

lodes <- to_lodes_form(
  df,
  axes = axis_cols,
  key = "axis",
  value = "stratum",
  id = "alluvium",
  discern = TRUE,
  diffuse = TRUE
)

# diffuse = TRUE 保留原始轴列,用于恢复显示标签
lodes$stratum_label <- NA_character_

for (nm in axis_cols) {
  idx <- as.character(lodes$axis) == nm
  lodes$stratum_label[idx] <- as.character(lodes[[nm]][idx])
}

ggplot(
  lodes,
  aes(
    x = axis,
    stratum = stratum,
    alluvium = alluvium,
    y = total_score
  )
) +
  geom_flow(
    aes(fill = int_score, order = as.numeric(int_score)),
    width = 0.2,
    decreasing = NA,
    reverse = TRUE
  ) +
  geom_stratum(
    width = 0.2,
    fill = "grey",
    decreasing = NA,
    reverse = TRUE
  ) +
  geom_text(
    stat = "stratum",
    aes(label = stratum_label),
    decreasing = NA,
    reverse = TRUE
  ) +
  scale_x_discrete(
    limits = axis_cols,
    labels = c("axis1", "axis2", "axis3", "axis4"),
    expand = c(0.1, 0.1)
  ) +
  theme_minimal()

数据已在绘图前完成转换,图层中不再需要传入 discern。diffuse = TRUE 保留了原始轴列,可用于恢复显示标签;只要每个 stratum 内的原始标签一致,就可以直接用于文字图层。参见数据转换说明和标签映射规则。

如果仍不符合预期,检查实际值和绘图数据

先确认 levels 与数据值是否匹配:

levels(df$axis3_score)
levels(df$axis4_score)

table(df$axis3_score, useNA = "ifany")
table(df$axis4_score, useNA = "ifany")

还需要检查以下情况:

  • "low " 与 "low"、"Low" 与 "low" 是不同的值。未列入 levels 的值会在 factor() 转换时变成 NA,应在转换前核对原始数据。
  • 修改 df 后,要重新运行创建 ggplot() 对象的代码。只打印之前创建的图对象,可能仍会使用旧数据。
  • 某个类别没有观测,或总权重为零时,即使保留了 level,也不会显示有可见高度的矩形。
  • as.numeric(int_score) 对 factor 返回的是内部编码。如果保留这项流线排序,需要确认 int_score 的 levels 顺序符合预期。

备注:内容仅供参考。