概要

このノートブックでは、サプライチェーンリスク管理データを再解析します。 データは医療機器の生産受注を想定した合成データで、450件の受注レベル 観測値を含みます。

PLS-SEMモデルでは、以下の7つの潜在変数を設定します。

  • SCR_Risk: サプライチェーンリスクの露出度
  • SCRM_Practices: リスク管理実践
  • Trace_Info: トレーサビリティと情報品質
  • Cost_Var: 単位コストの変動性
  • NoQ_Cost: 非品質コストと不良関連指標
  • Op_Perf: オペレーション成果
  • Econ_Perf: 経済的成果

構造モデルでは、リスク露出度とSCRM実践がトレーサビリティ、コスト変動、 品質コスト、オペレーション成果、経済的成果に影響すると仮定します。 また、トレーサビリティを媒介変数として扱い、コスト・品質関連の成果が 最終的な業績に影響する構造を確認します。

パッケージ

rm(list = ls(all = TRUE))

library(lvsem)
library(ggplot2)
library(knitr)

データ読み込み

data_file <- "SCRM_Orders_Dataset_450.csv"
data_candidates <- c(
  file.path("data003", data_file),
  data_file,
  file.path("..", "data003", data_file)
)
data_path <- data_candidates[file.exists(data_candidates)][1]

if (is.na(data_path)) {
  stop("CSVファイルが見つかりません: ", data_file)
}

data_dir <- dirname(data_path)
raw_data <- read.csv(data_path, check.names = TRUE)

dim(raw_data)
## [1] 450  42
head(raw_data)
##    order_id year month quarter supplier_id supplier_type transport_mode
## 1 ORDR-0001 2025     9       3          18 internacional          aereo
## 2 ORDR-0002 2025    12       4           4 internacional      terrestre
## 3 ORDR-0003 2024    11       4          18 internacional      terrestre
## 4 ORDR-0004 2025     9       3           7 internacional      terrestre
## 5 ORDR-0005 2024     4       2           7      nacional      terrestre
## 6 ORDR-0006 2025    12       4          10      nacional      terrestre
##   product_complexity order_qty_units shock_event regulatory_intensity
## 1                  1             175           0                0.046
## 2                  3             109           0                0.125
## 3                  1             101           0                0.049
## 4                  3             211           0                0.084
## 5                  1             252           0                0.647
## 6                  2             286           0                0.349
##   crit_dep_index supplier_delay_rate inbound_damage_rate storage_loss_rate
## 1          0.341               0.157               0.032             0.037
## 2          0.422               0.201               0.015             0.047
## 3          0.249               0.220               0.089             0.033
## 4          0.390               0.351               0.089             0.076
## 5          0.376               0.290               0.111             0.074
## 6          0.286               0.223               0.043             0.064
##   process_rework_trigger_rate distribution_delay_rate stockout_rate
## 1                       0.068                   0.101         0.162
## 2                       0.153                   0.201         0.073
## 3                       0.115                   0.159         0.164
## 4                       0.237                   0.259         0.244
## 5                       0.245                   0.199         0.234
## 6                       0.131                   0.198         0.134
##   supplier_audit_score monitoring_frequency_per_month
## 1                  100                             13
## 2                  100                              6
## 3                  100                              9
## 4                  100                             12
## 5                  100                             11
## 6                  100                              4
##   contingency_maturity_likert collaboration_index traceability_completeness
## 1                           4               0.811                     0.939
## 2                           3               0.681                     0.626
## 3                           4               0.568                     0.382
## 4                           4               0.318                     0.318
## 5                           3               0.711                     0.324
## 6                           4               0.236                     0.319
##   data_accuracy inspection_coverage unit_cost_cop unit_cost_sd_cop
## 1         0.920               0.920         93897             1000
## 2         0.471               0.339        206957             5665
## 3         0.281               0.233         85000            11463
## 4         0.305               0.273        234624            43506
## 5         0.695               0.784        131497            16139
## 6         0.669               0.485        134377            22862
##   cost_var_metric no_quality_cost_cop scrap_rate rework_rate noq_metric
## 1          0.0100                   0     0.0103      0.0282     0.0000
## 2          0.0274               10806     0.0000      0.0912     0.0591
## 3          0.1349                3715     0.0096      0.0575     0.0387
## 4          0.1854               27006     0.0562      0.1260     0.1298
## 5          0.1227                8077     0.0663      0.0926     0.0564
## 6          0.1701                   0     0.0236      0.0506     0.0000
##   margin_pct breakeven_units on_time_delivery cycle_time_hours
## 1      38.00             897            0.833            13.13
## 2      29.36             527            0.962            32.90
## 3      27.21            1200            0.874             8.15
## 4      14.83             920            0.730            28.75
## 5      31.30             777            0.763            13.61
## 6      26.79             889            0.791            21.69
##   cycle_time_hours_inv nonconformity_rate RiskIndex SCRM_Practices TraceIndex
## 1               0.4179               2.68    0.2697         0.7183     0.9440
## 2               0.0188               0.33    0.3560         0.4316     0.4597
## 3               0.8244               6.12    0.4430         0.5499     0.2696
## 4               0.0571               6.96    0.7973         0.5313     0.2711
## 5               0.3947              12.10    0.7546         0.5503     0.6031
## 6               0.1559              10.98    0.4630         0.3300     0.4788
##   cluster
## 1       4
## 2       1
## 3       1
## 4       3
## 5       2
## 6       1

モデル定義

minmax <- function(x) {
  rng <- range(x, na.rm = TRUE)
  if (!is.finite(rng[1]) || diff(rng) == 0) {
    return(rep(0, length(x)))
  }
  (x - rng[1]) / diff(rng)
}

raw_data$breakeven_efficiency <- 1 - minmax(raw_data$breakeven_units)

blocks <- list(
  SCR_Risk = c(
    "supplier_delay_rate",
    "inbound_damage_rate",
    "storage_loss_rate",
    "process_rework_trigger_rate",
    "distribution_delay_rate",
    "stockout_rate"
  ),
  SCRM_Practices = c(
    "supplier_audit_score",
    "monitoring_frequency_per_month",
    "contingency_maturity_likert",
    "collaboration_index"
  ),
  Trace_Info = c(
    "traceability_completeness",
    "data_accuracy",
    "inspection_coverage"
  ),
  Cost_Var = c(
    "cost_var_metric",
    "unit_cost_sd_cop"
  ),
  NoQ_Cost = c(
    "noq_metric",
    "scrap_rate",
    "rework_rate",
    "nonconformity_rate"
  ),
  Op_Perf = c(
    "on_time_delivery",
    "cycle_time_hours_inv"
  ),
  Econ_Perf = c(
    "margin_pct",
    "breakeven_efficiency"
  )
)

block_summary <- data.frame(
  block = names(blocks),
  item_count = vapply(blocks, length, integer(1))
)
kable(block_summary, caption = "測定ブロック")
測定ブロック
block item_count
SCR_Risk SCR_Risk 6
SCRM_Practices SCRM_Practices 4
Trace_Info Trace_Info 3
Cost_Var Cost_Var 2
NoQ_Cost NoQ_Cost 4
Op_Perf Op_Perf 2
Econ_Perf Econ_Perf 2
latent_vars <- names(blocks)
path <- matrix(
  0,
  nrow = length(latent_vars),
  ncol = length(latent_vars),
  dimnames = list(latent_vars, latent_vars)
)

path["Trace_Info", c("SCR_Risk", "SCRM_Practices")] <- 1
path["Cost_Var", c("SCR_Risk", "SCRM_Practices", "Trace_Info")] <- 1
path["NoQ_Cost", c("SCR_Risk", "SCRM_Practices", "Trace_Info")] <- 1
path["Op_Perf", c("SCR_Risk", "SCRM_Practices", "Trace_Info", "Cost_Var", "NoQ_Cost")] <- 1
path["Econ_Perf", c("SCR_Risk", "SCRM_Practices", "Trace_Info", "Cost_Var", "NoQ_Cost", "Op_Perf")] <- 1

path
##                SCR_Risk SCRM_Practices Trace_Info Cost_Var NoQ_Cost Op_Perf
## SCR_Risk              0              0          0        0        0       0
## SCRM_Practices        0              0          0        0        0       0
## Trace_Info            1              1          0        0        0       0
## Cost_Var              1              1          1        0        0       0
## NoQ_Cost              1              1          1        0        0       0
## Op_Perf               1              1          1        1        1       0
## Econ_Perf             1              1          1        1        1       1
##                Econ_Perf
## SCR_Risk               0
## SCRM_Practices         0
## Trace_Info             0
## Cost_Var               0
## NoQ_Cost               0
## Op_Perf                0
## Econ_Perf              0
score_relation <- path + t(path)
score_relation
##                SCR_Risk SCRM_Practices Trace_Info Cost_Var NoQ_Cost Op_Perf
## SCR_Risk              0              0          1        1        1       1
## SCRM_Practices        0              0          1        1        1       1
## Trace_Info            1              1          0        1        1       1
## Cost_Var              1              1          1        0        0       1
## NoQ_Cost              1              1          1        0        0       1
## Op_Perf               1              1          1        1        1       0
## Econ_Perf             1              1          1        1        1       1
##                Econ_Perf
## SCR_Risk               1
## SCRM_Practices         1
## Trace_Info             1
## Cost_Var               1
## NoQ_Cost               1
## Op_Perf                1
## Econ_Perf              0

前処理

model_vars <- unique(unlist(blocks))
model_data <- raw_data[, model_vars, drop = FALSE]

model_data[] <- lapply(model_data, function(x) {
  x <- as.numeric(as.character(x))
  x[!is.finite(x)] <- NA_real_
  x
})

too_constant <- vapply(model_data, function(x) {
  observed <- x[!is.na(x)]
  if (!length(observed)) {
    return(TRUE)
  }
  stats::sd(observed) == 0 || max(table(observed)) / length(observed) >= 0.95
}, logical(1))

if (any(too_constant)) {
  model_data <- model_data[, !too_constant, drop = FALSE]
  blocks <- lapply(blocks, function(x) intersect(x, names(model_data)))
}

empty_blocks <- vapply(blocks, length, integer(1)) == 0
if (any(empty_blocks)) {
  stop("前処理後に空になった測定ブロックがあります: ",
       paste(names(blocks)[empty_blocks], collapse = ", "))
}

model_data[] <- lapply(model_data, function(x) {
  ifelse(is.na(x), stats::median(x, na.rm = TRUE), x)
})

data.frame(
  samples = nrow(model_data),
  variables = ncol(model_data),
  removed_constant_variables = sum(too_constant),
  removed_variable_names = paste(names(too_constant)[too_constant], collapse = ", ")
)
##   samples variables removed_constant_variables removed_variable_names
## 1     450        22                          1   supplier_audit_score

潜在変数スコア

lv_scores <- make_lv_scores(model_data, blocks, score_relation)

head(lv_scores)
##     SCR_Risk SCRM_Practices  Trace_Info     Cost_Var   NoQ_Cost    Op_Perf
## 1 -1.4787182    0.186639126  0.69391626 -0.687880803 -1.0625360  0.1396651
## 2 -1.0728984   -0.134687965 -0.23688248 -0.543675447 -0.6784427  0.2626310
## 3 -0.7108776   -0.049827657 -0.60696727 -0.022310113 -0.5371827  0.6925531
## 4  0.9152761   -0.132365916 -0.60552375  0.693232651  0.9256472 -0.6130851
## 5  0.7225212   -0.003695428  0.02745328  0.004140457  0.7812865 -0.1580423
## 6 -0.5880825   -0.362396321 -0.20600965  0.300905085 -0.3297714 -0.2779505
##     Econ_Perf
## 1  0.57455553
## 2  0.24103099
## 3 -0.09904178
## 4 -0.66099808
## 5  0.25983109
## 6 -0.01679692
round(cor(lv_scores), 3)
##                SCR_Risk SCRM_Practices Trace_Info Cost_Var NoQ_Cost Op_Perf
## SCR_Risk          1.000         -0.089     -0.111    0.696    0.822  -0.775
## SCRM_Practices   -0.089          1.000      0.556   -0.265   -0.302   0.195
## Trace_Info       -0.111          0.556      1.000   -0.466   -0.486   0.342
## Cost_Var          0.696         -0.265     -0.466    1.000    0.821  -0.756
## NoQ_Cost          0.822         -0.302     -0.486    0.821    1.000  -0.827
## Op_Perf          -0.775          0.195      0.342   -0.756   -0.827   1.000
## Econ_Perf        -0.521          0.465      0.635   -0.770   -0.778   0.630
##                Econ_Perf
## SCR_Risk          -0.521
## SCRM_Practices     0.465
## Trace_Info         0.635
## Cost_Var          -0.770
## NoQ_Cost          -0.778
## Op_Perf            0.630
## Econ_Perf          1.000

構造モデル

path_coefficients <- estimate_paths(lv_scores, path, scale_scores = FALSE)
path_effects <- effects_from_beta(path_coefficients)

path_coefficients
##                            path        beta
## 1        SCR_Risk_to_Trace_Info -0.02936821
## 2  SCRM_Practices_to_Trace_Info  0.96856014
## 3          SCR_Risk_to_Cost_Var  0.28248861
## 4    SCRM_Practices_to_Cost_Var  0.02769148
## 5        Trace_Info_to_Cost_Var -0.37165989
## 6          SCR_Risk_to_NoQ_Cost  0.61375588
## 7    SCRM_Practices_to_NoQ_Cost -0.04800540
## 8        Trace_Info_to_NoQ_Cost -0.65779625
## 9           SCR_Risk_to_Op_Perf -0.13223527
## 10    SCRM_Practices_to_Op_Perf -0.04225107
## 11        Trace_Info_to_Op_Perf  0.02435557
## 12          Cost_Var_to_Op_Perf -0.22351924
## 13          NoQ_Cost_to_Op_Perf -0.23534124
## 14        SCR_Risk_to_Econ_Perf  0.06477441
## 15  SCRM_Practices_to_Econ_Perf  0.20579620
## 16      Trace_Info_to_Econ_Perf  0.13086033
## 17        Cost_Var_to_Econ_Perf -0.33663551
## 18        NoQ_Cost_to_Econ_Perf -0.24738297
## 19         Op_Perf_to_Econ_Perf -0.02465957
path_effects
##                   relationships      direct     indirect       total
## 1         Cost_Var -> Econ_Perf -0.33663551  0.005511889 -0.33112362
## 2           Cost_Var -> Op_Perf -0.22351924  0.000000000 -0.22351924
## 3         NoQ_Cost -> Econ_Perf -0.24738297  0.005803414 -0.24157955
## 4           NoQ_Cost -> Op_Perf -0.23534124  0.000000000 -0.23534124
## 5          Op_Perf -> Econ_Perf -0.02465957  0.000000000 -0.02465957
## 6          SCR_Risk -> Cost_Var  0.28248861  0.010914985  0.29340359
## 7         SCR_Risk -> Econ_Perf  0.06477441 -0.250655265 -0.18588086
## 8          SCR_Risk -> NoQ_Cost  0.61375588  0.019318297  0.63307418
## 9           SCR_Risk -> Op_Perf -0.13223527 -0.215285085 -0.34752035
## 10       SCR_Risk -> Trace_Info -0.02936821  0.000000000 -0.02936821
## 11   SCRM_Practices -> Cost_Var  0.02769148 -0.359974951 -0.33228348
## 12  SCRM_Practices -> Econ_Perf  0.20579620  0.402744320  0.60854052
## 13   SCRM_Practices -> NoQ_Cost -0.04800540 -0.637115222 -0.68512062
## 14    SCRM_Practices -> Op_Perf -0.04225107  0.259098714  0.21684764
## 15 SCRM_Practices -> Trace_Info  0.96856014  0.000000000  0.96856014
## 16       Trace_Info -> Cost_Var -0.37165989  0.000000000 -0.37165989
## 17      Trace_Info -> Econ_Perf  0.13086033  0.281374891  0.41223522
## 18       Trace_Info -> NoQ_Cost -0.65779625  0.000000000 -0.65779625
## 19        Trace_Info -> Op_Perf  0.02435557  0.237879715  0.26223528
endogenous_vars <- rownames(path)[rowSums(path) > 0]
r2_values <- sapply(endogenous_vars, function(dv) {
  preds <- names(which(path[dv, ] == 1))
  fit <- lm(
    as.formula(paste(dv, "~", paste(preds, collapse = " + "))),
    data = as.data.frame(lv_scores)
  )
  summary(fit)$r.squared
})

kable(
  data.frame(
    construct = names(r2_values),
    R2 = round(as.numeric(r2_values), 3)
  ),
  caption = "内生潜在変数のR2"
)
内生潜在変数のR2
construct R2
Trace_Info 0.313
Cost_Var 0.637
NoQ_Cost 0.833
Op_Perf 0.728
Econ_Perf 0.749

Bootstrap

set.seed(123)
boot_paths <- bootstrap_paths(
  model_data,
  blocks,
  path,
  br = 1000,
  seed = 123,
  score_matrix = score_relation
)

kable(
  data.frame(
    path = boot_paths$path,
    round(boot_paths[, c("beta", "boot_se", "t_value")], 3)
  ),
  caption = "Bootstrapによるパス推定"
)
Bootstrapによるパス推定
path beta boot_se t_value
SCR_Risk_to_Trace_Info -0.063 0.042 1.486
SCRM_Practices_to_Trace_Info 0.551 0.032 17.131
SCR_Risk_to_Cost_Var 0.652 0.024 26.822
SCRM_Practices_to_Cost_Var 0.017 0.033 0.513
Trace_Info_to_Cost_Var -0.403 0.034 11.840
SCR_Risk_to_NoQ_Cost 0.777 0.019 40.618
SCRM_Practices_to_NoQ_Cost -0.016 0.023 0.692
Trace_Info_to_NoQ_Cost -0.391 0.025 15.576
SCR_Risk_to_Op_Perf -0.291 0.053 5.526
SCRM_Practices_to_Op_Perf -0.025 0.031 0.790
Trace_Info_to_Op_Perf 0.025 0.039 0.640
Cost_Var_to_Op_Perf -0.213 0.040 5.325
NoQ_Cost_to_Op_Perf -0.409 0.059 6.910
SCR_Risk_to_Econ_Perf 0.169 0.055 3.062
SCRM_Practices_to_Econ_Perf 0.143 0.030 4.814
Trace_Info_to_Econ_Perf 0.160 0.041 3.905
Cost_Var_to_Econ_Perf -0.379 0.049 7.676
NoQ_Cost_to_Econ_Perf -0.509 0.065 7.884
Op_Perf_to_Econ_Perf -0.029 0.045 0.655

測定モデル

measures <- measurement_summary(model_data, blocks, lv_scores)

kable(
  data.frame(
    block = measures$reliability$block,
    round(measures$reliability[, c("alpha", "composite_reliability", "ave")], 3)
  ),
  caption = "信頼性とAVE"
)
信頼性とAVE
block alpha composite_reliability ave
SCR_Risk 0.927 0.943 0.733
SCRM_Practices 0.822 0.889 0.727
Trace_Info 0.918 0.948 0.859
Cost_Var 0.908 0.956 0.915
NoQ_Cost 0.887 0.922 0.748
Op_Perf 0.609 0.831 0.713
Econ_Perf 0.599 0.787 0.661
kable(
  data.frame(
    block = measures$loadings$block,
    item = measures$loadings$item,
    loading = round(measures$loadings$loading, 3)
  ),
  caption = "外部負荷量"
)
外部負荷量
block item loading
SCR_Risk supplier_delay_rate 0.844
SCR_Risk inbound_damage_rate 0.885
SCR_Risk storage_loss_rate 0.876
SCR_Risk process_rework_trigger_rate 0.826
SCR_Risk distribution_delay_rate 0.860
SCR_Risk stockout_rate 0.847
SCRM_Practices monitoring_frequency_per_month 0.853
SCRM_Practices contingency_maturity_likert 0.807
SCRM_Practices collaboration_index 0.896
Trace_Info traceability_completeness 0.938
Trace_Info data_accuracy 0.938
Trace_Info inspection_coverage 0.905
Cost_Var cost_var_metric 0.963
Cost_Var unit_cost_sd_cop 0.950
NoQ_Cost noq_metric 0.847
NoQ_Cost scrap_rate 0.879
NoQ_Cost rework_rate 0.869
NoQ_Cost nonconformity_rate 0.863
Op_Perf on_time_delivery 0.906
Op_Perf cycle_time_hours_inv 0.778
Econ_Perf margin_pct 0.979
Econ_Perf breakeven_efficiency 0.604

モデル図

node_df <- data.frame(
  block = c("SCR_Risk", "SCRM_Practices", "Trace_Info", "Cost_Var", "NoQ_Cost", "Op_Perf", "Econ_Perf"),
  label = c(
    "サプライチェーン\nリスク\n(SCR_Risk)",
    "SCRM実践\n(SCRM_Practices)",
    "トレーサビリティ\n情報品質\n(Trace_Info)",
    "コスト変動\n(Cost_Var)",
    "非品質コスト\n(NoQ_Cost)",
    "オペレーション\n成果\n(Op_Perf)",
    "経済的成果\n(Econ_Perf)"
  ),
  x = c(0, 0, 2.0, 4.0, 4.0, 6.0, 8.0),
  y = c(2.2, 0.8, 1.5, 2.25, 0.75, 1.5, 1.5),
  stringsAsFactors = FALSE
)

edge_df <- path_coefficients
edge_split <- strsplit(edge_df$path, "_to_")
edge_df$from <- vapply(edge_split, `[`, character(1), 1)
edge_df$to <- vapply(edge_split, `[`, character(1), 2)
edge_df$label <- sprintf("beta = %.3f", edge_df$beta)

edge_df <- merge(
  edge_df,
  node_df[, c("block", "x", "y")],
  by.x = "from",
  by.y = "block"
)
names(edge_df)[names(edge_df) %in% c("x", "y")] <- c("x_from", "y_from")

edge_df <- merge(
  edge_df,
  node_df[, c("block", "x", "y")],
  by.x = "to",
  by.y = "block"
)
names(edge_df)[names(edge_df) %in% c("x", "y")] <- c("x_to", "y_to")

edge_df$x_label <- (edge_df$x_from + edge_df$x_to) / 2
edge_df$y_label <- (edge_df$y_from + edge_df$y_to) / 2

r2_df <- data.frame(
  block = names(r2_values),
  r2_label = sprintf("R2 = %.3f", as.numeric(r2_values)),
  stringsAsFactors = FALSE
)
r2_df <- merge(r2_df, node_df[, c("block", "x", "y")], by = "block")

model_plot <- ggplot() +
  geom_segment(
    data = edge_df,
    aes(x = x_from + 0.52, y = y_from, xend = x_to - 0.52, yend = y_to),
    arrow = arrow(length = unit(0.20, "cm")),
    linewidth = 0.65,
    color = "#374151",
    alpha = 0.85
  ) +
  geom_label(
    data = node_df,
    aes(x = x, y = y, label = label),
    size = 3.7,
    fontface = "bold",
    label.size = 0.35,
    fill = "#ffffff",
    color = "#111827"
  ) +
  geom_label(
    data = edge_df,
    aes(x = x_label, y = y_label + 0.12, label = label),
    size = 2.5,
    label.size = 0,
    fill = "#ffffff",
    color = "#111827"
  ) +
  geom_label(
    data = r2_df,
    aes(x = x, y = y - 0.48, label = r2_label),
    size = 2.9,
    label.size = 0.25,
    fill = "#eef7f2",
    color = "#111827"
  ) +
  coord_cartesian(xlim = c(-0.9, 8.9), ylim = c(0.0, 3.0), clip = "off") +
  theme_void() +
  labs(title = "SCRM受注データのlvsemモデル")

model_plot

ggsave(file.path(data_dir, "data003_lvsem_scrm_model.png"),
       model_plot, width = 12, height = 8, dpi = 180)

最終出力

cat("サンプルサイズ:", nrow(model_data), "\n")
## サンプルサイズ: 450
cat("除外した定数に近い変数:",
    paste(names(too_constant)[too_constant], collapse = ", "), "\n")
## 除外した定数に近い変数: supplier_audit_score
print(round(r2_values, 3))
## Trace_Info   Cost_Var   NoQ_Cost    Op_Perf  Econ_Perf 
##      0.313      0.637      0.833      0.728      0.749

参考情報

  • Hernandez Palma, H. G. (2026). Supply Chain Risk Management and Order-Based Costing: A Synthetic Industrial Dataset for Structural Equation Modeling (PLS-SEM). Mendeley Data, V1. https://doi.org/10.17632/bh5y663jnd.1
  • 使用パッケージ: lvsem
  • 使用データ: SCRM_Orders_Dataset_450.csv