library(tidyverse)
library(seminr)
library(kableExtra)
library(readxl)
library(janitor)
library(viridis)
library(DescTools)
library(tidygraph)
library(ggraph)

1 Introduction

This supplementary material documents the workflow for estimating the Partial Least Squares Structural Equation Model (PLS-SEM) and implementing robustness checks that complement the main article. It reports (i) data preparation and outlier handling, (ii) measurement model evaluation, (iii) bootstrapped structural model results, and (iv) robustness analyses using alternative outlier treatments (winsorization and listwise deletion). All procedures follow established guidelines for formative PLS-SEM, applying two-tailed inference where appropriate (Hair et al., 2019; Hair et al., 2021).

Methodological note. PLS-SEM was chosen in line with recommendations by Hair et al. (2021). The seminr package was employed due to its transparent syntax for specifying constructs and estimating paths. To guarantee reproducibility of bootstrap procedures, the random seed was fixed at set.seed(35).

2 Data Screening

Data Import and Cleaning. The dataset includes N = 5,570 municipal-level observations. A selection of 32 indicators was extracted and renamed to align with the construct structure: Infrastructure and Support (INFRA), Environmental Governance (ENVIR), Ecosystems Conservation Practices (ECOSY), Farming Systems and Practices (FARMI), Food Environment (FOODENV), Nutritional Outcomes (NUTRI), Climate Change (CLIMA), and Food Security (FOODSEC).

Single-item constructs with inherently negative indicators (CLIMA: GHG emissions; FOODSEC: poverty rates) were reversed so that higher values consistently indicate more favorable conditions. For multi-item constructs, indicators were kept in their original orientation, allowing the estimation procedure to determine the direction of their contribution.

Variables were classified by type to facilitate subsequent preprocessing:

data_raw <- read_excel("basePCAcompleta.xlsx") %>%
  clean_names()

data_raw <- data_raw %>%
  select(
    infra_1 = ig8, infra_2 = ii5, infra_3 = ii6,
    infra_4 = ii10, infra_5 = d1,
    envir_1 = w1, envir_2 = w2, envir_3 = o6,
    ecosy_1 = ig51, ecosy_2 = ig53, ecosy_3 = ii1,
    farmi_1 = ig52, farmi_2 = n1, farmi_3 = n2,
    farmi_4 = pa1, farmi_5 = pa3, farmi_6 = r2,
    farmi_7 = v3, farmi_8 = v4, farmi_9 = c2, farmi_10 = v6,
    foodenv_1 = b3, foodenv_2 = b4, foodenv_3 = b5,
    foodenv_4 = ii3,
    nutri_1 = f1, nutri_2 = f2, nutri_3 = f3,
    nutri_4 = h1, nutri_5 = h2,
    clima = p1,
    foodsec = im1) %>%
  mutate(
    clima   = -clima,
    foodsec = 1-foodsec
  )

indicator_vars <- data_raw %>%
  select(matches("^(infra|envir|ecosy|farmi|foodenv|nutri)_[0-9]+$"), clima, foodsec) %>%
  names()

prop_vars <- indicator_vars %>%
  keep(~ str_detect(.x, "^(infra|ecosy|farmi|nutri)_")) %>%
  union(intersect(c("foodenv_4", "foodsec"), indicator_vars)) %>%
  unique()
count_vars <- c("foodenv_1","foodenv_2","foodenv_3")
signed_vars <- c("clima")
dummy_vars <- c("envir_1","envir_2","envir_3")

Descriptive Statistics and Missing Data. Summary statistics, skewness, and kurtosis were computed for all indicators to provide an overview of their distributional properties. Histograms complement the summary tables by visualizing distributional shapes and detecting extreme deviations. These diagnostics establish a baseline understanding of the data before estimation and guide subsequent outlier treatment.

Indicators with more than 5% missing values are typically excluded, as imputation may compromise PLS-SEM validity. In this dataset, envir_3 (o6) (12%) and farmi_10 (v6) (10%) exceeded this threshold and were removed. The table below reports summary statistics, distributional shape, and missing-data percentages, with problematic indicators highlighted in red, while the histograms illustrate skewness and kurtosis to help detect severely distorted variables.

desc_df <- data_raw %>%
  select(all_of(indicator_vars)) %>%
  pivot_longer(everything(),
               names_to = "Variable",
               values_to = "Value") %>%
  group_by(Variable) %>%
  summarize(
    Min = min(Value, na.rm=TRUE),
    Q1 = quantile(Value, .25, na.rm=TRUE),
    Median = median(Value, na.rm=TRUE),
    Mean = mean(Value, na.rm=TRUE),
    Q3 = quantile(Value, .75, na.rm=TRUE),
    Max = max(Value, na.rm=TRUE),
    Skew = psych::skew(Value, na.rm=TRUE),
    Kurt = psych::kurtosi(Value, na.rm=TRUE),
    Hist_list = list(Value),
    Hist = "",
    Count = sum(is.na(Value)),
    "%" = 100 * Count / n(),
    .groups = "drop"
  )%>%
  mutate(Type = case_when(
      Variable %in% prop_vars ~ "Proportion",
      Variable %in% count_vars ~ "Count",
      Variable %in% signed_vars ~ "Continuous",
      Variable %in% dummy_vars ~ "Dummy",
      TRUE ~ "other"))%>%
  select(Variable, Type, everything())

perc_bg <- ifelse(desc_df$`%` > 5, "firebrick", "transparent")
perc_text <- ifelse(desc_df$`%` > 5, "white", "black")

kable(desc_df %>% select(-Hist_list),
    caption = "<b><span style='color:black'>Descriptive Statistics and Missing Data of Indicators</span></b><br>
             <span style='font-weight:normal; color:black; font-size:90%'>
             Red highlighting indicates variables with more than 5% missing observations.
             </span>",
    digits  = 2,
    format = "html",
    escape = FALSE,
    align = c("l", rep("c", ncol(desc_df) - 1))) %>%
  kable_styling(
    bootstrap_options = c("striped", "hover", "condensed", "responsive"),
    fixed_thead = TRUE,
    position = "center",
    full_width = FALSE
  ) %>%
  add_header_above(c(
    " " = 2,
    "Descriptive Statistics" = 6,
    "Distribution" =3,
    "Missing Data" = 2
  ))%>%
  column_spec(11, image = spec_hist(desc_df$Hist_list, same_lim = FALSE))%>%
  column_spec(13, background = perc_bg, color = perc_text)
Descriptive Statistics and Missing Data of Indicators
Red highlighting indicates variables with more than 5% missing observations.
Descriptive Statistics
Distribution
Missing Data
Variable Type Min Q1 Median Mean Q3 Max Skew Kurt Hist Count %
clima Continuous -35247300 -222227.25 -92783.00 -329749.70 -44329.50 1264224.00 -14.25 298.86 0 0.00
ecosy_1 Proportion 0 0.00 0.01 0.04 0.04 1.00 4.42 28.44 7 0.13
ecosy_2 Proportion 0 0.00 0.01 0.04 0.04 0.76 3.97 23.13 7 0.13
ecosy_3 Proportion 0 0.11 0.20 0.24 0.32 0.98 1.19 1.27 17 0.31
envir_1 Dummy 0 0.00 0.00 0.28 1.00 1.00 0.96 -1.08 108 1.94
envir_2 Dummy 0 0.00 0.00 0.07 0.00 1.00 3.33 9.08 108 1.94
envir_3 Dummy 0 0.00 1.00 0.53 1.00 1.00 -0.10 -1.99 670 12.03
farmi_1 Proportion 0 0.00 0.01 0.05 0.06 1.00 3.19 13.43 7 0.13
farmi_10 Proportion 0 0.01 0.03 0.09 0.10 1.00 2.85 9.23 571 10.25
farmi_2 Proportion 0 0.08 0.21 0.34 0.58 1.00 0.82 -0.75 14 0.25
farmi_3 Proportion 0 0.14 0.43 0.43 0.69 1.00 0.10 -1.30 14 0.25
farmi_4 Proportion 0 0.05 0.14 0.17 0.26 0.82 0.92 0.25 7 0.13
farmi_5 Proportion 0 0.42 0.68 0.63 0.89 1.00 -0.55 -0.76 77 1.38
farmi_6 Proportion 0 0.16 0.33 0.36 0.54 1.00 0.49 -0.74 56 1.01
farmi_7 Proportion 0 0.12 0.30 0.36 0.56 1.00 0.56 -0.81 7 0.13
farmi_8 Proportion 0 0.00 0.00 0.02 0.02 0.93 7.62 94.49 7 0.13
farmi_9 Proportion 0 0.02 0.06 0.11 0.15 1.00 2.18 5.79 0 0.00
foodenv_1 Count 0 0.70 4.11 7.19 11.25 74.43 1.73 4.26 12 0.22
foodenv_2 Count 0 1.45 3.82 6.16 8.16 87.50 2.40 9.25 12 0.22
foodenv_3 Count 0 0.00 0.00 0.91 1.00 912.00 62.76 4342.62 7 0.13
foodenv_4 Proportion 0 0.53 0.76 0.71 0.94 1.00 -0.64 -0.51 258 4.63
foodsec Proportion 0 0.50 0.71 0.67 0.85 1.00 -0.45 -0.69 0 0.00
infra_1 Proportion 0 0.02 0.06 0.09 0.12 0.77 2.20 6.05 7 0.13
infra_2 Proportion 0 0.80 0.90 0.84 0.95 1.00 -2.01 4.44 13 0.23
infra_3 Proportion 0 0.10 0.22 0.24 0.36 1.00 0.61 -0.27 47 0.84
infra_4 Proportion 0 0.21 0.37 0.39 0.56 1.00 0.43 -0.41 88 1.58
infra_5 Proportion 0 0.23 0.38 0.42 0.59 1.00 0.44 -0.54 68 1.22
nutri_1 Proportion 0 0.16 0.26 0.27 0.37 0.86 0.44 -0.29 0 0.00
nutri_2 Proportion 0 0.01 0.04 0.07 0.10 0.51 1.72 3.60 0 0.00
nutri_3 Proportion 0 0.06 0.12 0.14 0.20 0.77 1.34 2.83 0 0.00
nutri_4 Proportion 0 0.11 0.21 0.23 0.32 0.79 0.64 -0.08 0 0.00
nutri_5 Proportion 0 0.02 0.05 0.06 0.08 0.46 1.74 4.71 0 0.00
raw_long <- data_raw %>%
  select(all_of(indicator_vars)) %>%
  pivot_longer(everything(),
               names_to = "Variable",
               values_to = "Value")

ggplot(raw_long, aes(x=Value)) +
  geom_histogram(bins=30,
                 aes(fill=..count..),
                 color="white") +
  scale_fill_viridis_c() +
  facet_wrap(~ Variable,
             scales = "free", ncol = 3) +
  labs(title="Histograms of Raw Indicators",
       x=NULL, y="Count") +
  theme_minimal()

Key Points (Data Screening). Regarding the missing data, envir_3 (o6) and farmi_10 (v6) had 12% and 10% missing values, respectively, and were removed. All other indicators had less than 5% missing values and were retained.

For the distributional diagnostics, nonnormality is considered severe whenthe absolute value of skewness exceeds 2 or when kurtosis exceeds about 8. Several indicators surpassed these thresholds, notably clima (p1), ecosy_1-2 (ig51, ig53), envir_2 (w2) (binary, high kurtosis expected), farmi_1 (ig52), farmi_8 (v4), farmi_9 (c2), foodenv_2-3 (b4–b5), and infra_1-2 (ig8, ii5).

3 Data Refinement and Robustness Scenarios

Pre-estimation adjustments were implemented to address distributional challenges affecting the OLS regressions embedded in PLS-SEM. While the method is robust to nonnormality, influential outliers and heteroscedasticity may still bias inference. variance-stabilizing transformations were applied to properly identify outliers and to construct alternative estimation scenarios to assessing robustness. After the remove og the indicators due to their missingvalues, the subsequent analyses rely on the remaining 30 indicators.

data_clean <- data_raw %>% select(-envir_3, -farmi_10)
dummy_vars <- setdiff(dummy_vars, "envir_3")
prop_vars <- setdiff(prop_vars, "farmi_10")

Transformation Strategy. Transformations are used to stabilize variance (especially for bounded proportions) and to reduce tail heaviness in skewed distributions. This steps aim to reduce leverage, rather than to achieve perfect normality, and to improve comparability across measurement:

skew_lookup <- setNames(desc_df$Skew, desc_df$Variable)
prop_skew_neg <- intersect(prop_vars, names(skew_lookup[skew_lookup < 0]))

data_transformed <- data_clean %>%
  mutate(
    across(all_of(prop_vars),
           ~ if (cur_column() %in% prop_skew_neg) {
               -asin(sqrt(pmax(pmin(1 - .x, 1), 0)))
             } else {
               asin(sqrt(pmax(pmin(.x, 1), 0)))
             },
           .names = "t_{.col}"),
    across(all_of(count_vars), ~ log(.x + 1), .names = "t_{.col}"),
    across(all_of(signed_vars), ~ sign(.x) * log(abs(.x)+1), .names = "t_{.col}")
)

t_vars <- grep("^t_", names(data_transformed), value = TRUE)

data_transformed <- data_transformed %>%
  mutate(
    across(all_of(t_vars),
           ~ (. - mean(., na.rm = TRUE)) / sd(., na.rm = TRUE),
           .names = "z_{.col}"),
    across(all_of(dummy_vars),
           ~ (. - mean(., na.rm = TRUE)) / sd(., na.rm = TRUE),
           .names = "z_{.col}"))

z_vars <- grep("^z_", names(data_transformed), value = TRUE)
z_t_vars <- grep("^z_t_", names(data_transformed), value = TRUE)

Post-transform distributions. As expected, extreme values are compressed and mass is redistributed toward the center.

trans_long <- data_transformed %>%
  select(all_of(t_vars)) %>%
  pivot_longer(cols = everything(), names_to="Variable", values_to="Value")
ggplot(trans_long, aes(x=Value)) +
  geom_histogram(bins=30, aes(fill = ..count..), color="white") +
  scale_fill_viridis_c() +
  facet_wrap(~Variable, scales = "free", ncol = 3) +
  labs(title = "Histograms of Transformed Indicators", x = "Transformed Value", y = "Count") +
  theme_minimal()

Standardized indicators. After z-standardization, all indicators share a common scale, facilitating comparison. Some residual skew may remain, which is acceptable given the nonparametric nature of PLS-SEM.

z_long <- data_transformed %>%
  select(all_of(z_t_vars)) %>%
  pivot_longer(cols = everything(), names_to="Variable", values_to="Value")
ggplot(z_long, aes(x=Value)) +
  geom_histogram(bins=30, aes(fill = ..count..), color="white") +
  scale_fill_viridis_c() +
  facet_wrap(~Variable, scales = "free", ncol = 3) +
  labs(title = "Histograms of Transformed & Standardized Indicators", x = "Transformed & Standardized Value", y = "Count") +
  theme_minimal()

Outlier Tratment. Univariate screening was performed on standardized indicators before structural estimation. The main PLS-SEM model uses the full sample, and outlier treatments are implemented only in robustness scenarios. The boxplots of standardized indicators above highlight tails and potential extremes (points beyond whiskers). This screening was not applied to binary indicators, for which outliers are not defined.

z_long <- data_transformed %>%
  select(all_of(z_t_vars)) %>%
  pivot_longer(cols = everything(), names_to = "Variable", values_to = "Z")

ggplot(z_long, aes(x = Variable, y = Z)) +
  geom_boxplot(outlier.colour = "firebrick", fill = "grey90", notch = TRUE) +
  coord_flip() +
  labs(
    title = "Boxplots of Standardized Indicators",
    x = NULL,
    y = "Z-score") +
  theme_minimal(base_size = 12) +
  theme(
    plot.title = element_text(face = "bold", hjust = 0.5),
    axis.text.y = element_text(face = "bold"))

Although, a formal flag protocol was defined based on three rules:

For each indicator, the share of flagged cases is computed under IQR, HI, and Z4. When any rule identifies more than 5% of observations, targeted adjustments (winsorization and listwise deletion) are applied: unilateral outliers are addressed at 5th or 95th percentile, and bilateral or ambiguous extremes are symmetrically adjusted at both tails. Indicators below all thresholds remain unchanged.

flag_iqr <- function(x){
  q <- quantile(x, c(.25, .75), na.rm=TRUE);
  i <- diff(q)
  x < (q[1] - 1.5*i) | x > (q[2] + 1.5*i)
}
flag_tail <- function(x, side=c("low","high")){
  side <- match.arg(side)
  q <- quantile(x, c(.25, .75), na.rm=TRUE);
  i <- diff(q)
  if (side == "low") x < (q[1] - 1.5*i) else x > (q[2] + 1.5*i)
}
flag_hampel <- function(x){
  m <- median(x, na.rm=TRUE);
  mad <- median(abs(x - m), na.rm=TRUE) * 1.4826
  abs(x - m) > 3*mad
}

z_data <- select(data_transformed, all_of(z_t_vars))

iqr_p <- sapply(z_data, function(v) mean(flag_iqr(v), na.rm=TRUE) * 100)
hi_p <- sapply(z_data, function(v) mean(flag_hampel(v), na.rm=TRUE) * 100)
z4_p <- sapply(z_data, function(v) mean(abs(v)>4, na.rm=TRUE) * 100)
z3_p <- sapply(z_data, function(v) mean(abs(v)>3, na.rm=TRUE) * 100)
low_p <- sapply(z_data, function(v) mean(flag_tail(v,"low"), na.rm=TRUE) * 100)
high_p <- sapply(z_data, function(v) mean(flag_tail(v,"high"), na.rm=TRUE) * 100)

z_list <- as.list(z_data)

outlier_summary <- tibble(
  Variable = names(iqr_p),
  Boxplot = "",
  IQR = iqr_p,
  HI = hi_p,
  Z4 = z4_p,
  Z3 = z3_p,
  `Lower` = low_p,
  `Upper` = high_p) %>%
  mutate(
    IQR_gt5 = IQR > 5,
    HI_gt5 = HI > 5,
    Z4_gt5 = Z4 > 5,
    Trigger = ifelse(IQR_gt5 | HI_gt5 | Z4_gt5, "Yes", "No"),
    Side = case_when(
      Trigger == "No" ~ "-",
      `Lower` > 0 & `Upper` == 0 ~ "Low",
      `Upper` > 0 & `Lower` == 0 ~ "High",
      `Lower` > 0 & `Upper` > 0 ~ "Both",
      TRUE ~ "Both"))

iqr_bg <- ifelse(outlier_summary$IQR > 5, "firebrick", "transparent")
iqr_text <- ifelse(outlier_summary$IQR > 5, "white", "black")
hi_bg <- ifelse(outlier_summary$HI > 5, "firebrick", "transparent")
hi_text <- ifelse(outlier_summary$HI > 5, "white", "black")
z4_bg <- ifelse(outlier_summary$Z4 > 5, "firebrick", "transparent")
z4_text <- ifelse(outlier_summary$Z4 > 5, "white", "black")

outlier_summary %>%
  select(
    Variable,
    IQR, HI, Z4, Z3,
    Trigger, Side,
    Boxplot, `Lower`, `Upper`
  ) %>%
  kable(
    caption = "<b><span style='color:black'>Univariate outlier flags (% of sample size)</span></b>",
    digits = 2,
    format = "html",
    escape = FALSE,
    align = c("l", rep("c", 9)) 
  ) %>%
  kable_styling(
    bootstrap_options = c("striped", "hover", "condensed", "responsive"),
    fixed_thead = TRUE,
    position = "center",
    full_width = FALSE
  ) %>%
  add_header_above(
    c(" " = 1, "Outlier Flags (%)" = 4, "Action" = 2, "Tail-specific (%)" = 3)
  ) %>%
  column_spec(2, background = iqr_bg, color = iqr_text) %>%
  column_spec(3, background = hi_bg, color = hi_text) %>%
  column_spec(4, background = z4_bg, color = z4_text) %>%
  column_spec(8,image = spec_boxplot(z_list, same_lim = FALSE))
Univariate outlier flags (% of sample size)
Outlier Flags (%)
Action
Tail-specific (%)
Variable IQR HI Z4 Z3 Trigger Side Boxplot Lower Upper
z_t_infra_1 2.82 2.26 0.14 1.08 No
0.00 2.82
z_t_infra_2 4.48 4.41 0.29 1.48 No
4.48 0.00
z_t_infra_3 0.09 0.04 0.02 0.05 No
0.00 0.09
z_t_infra_4 1.82 1.73 0.00 1.73 No
0.00 1.82
z_t_infra_5 5.82 5.18 0.00 0.00 Yes High 0.00 5.82
z_t_ecosy_1 3.95 4.19 0.67 1.71 No
0.00 3.95
z_t_ecosy_2 3.20 2.28 0.45 1.56 No
0.00 3.20
z_t_ecosy_3 2.65 1.78 0.07 0.56 No
0.00 2.65
z_t_farmi_1 3.88 5.30 0.40 1.71 Yes High 0.00 3.88
z_t_farmi_2 0.00 2.05 0.00 0.00 No
0.00 0.00
z_t_farmi_3 0.00 0.00 0.00 0.00 No
0.00 0.00
z_t_farmi_4 0.14 0.05 0.00 0.11 No
0.00 0.14
z_t_farmi_5 0.00 0.00 0.00 0.00 No
0.00 0.00
z_t_farmi_6 0.13 0.09 0.00 0.13 No
0.00 0.13
z_t_farmi_7 0.00 0.00 0.00 0.00 No
0.00 0.00
z_t_farmi_8 3.58 2.84 0.86 1.76 No
0.00 3.58
z_t_farmi_9 2.35 2.30 0.22 1.04 No
0.00 2.35
z_t_nutri_1 0.41 0.22 0.00 0.22 No
0.34 0.07
z_t_nutri_2 0.63 0.34 0.04 0.54 No
0.00 0.63
z_t_nutri_3 1.04 0.70 0.09 0.70 No
0.00 1.04
z_t_nutri_4 0.14 0.05 0.00 0.05 No
0.00 0.14
z_t_nutri_5 1.06 0.63 0.11 0.65 No
0.00 1.06
z_t_foodenv_4 0.40 0.00 0.00 0.40 No
0.40 0.00
z_t_foodsec 0.31 0.31 0.23 0.34 No
0.31 0.00
z_t_foodenv_1 0.00 0.00 0.00 0.00 No
0.00 0.00
z_t_foodenv_2 0.02 0.00 0.00 0.02 No
0.00 0.02
z_t_foodenv_3 1.74 31.89 0.95 1.51 Yes High 0.00 1.74
z_t_clima 2.53 1.69 0.20 0.31 No
2.24 0.29

Three indicators exceed the 5% cutoff in right-tail diagnostics: z_t_infra_5 (d1), z_t_farmi_1 (ig52), and z_t_foodenv_3 (b5). As these were unilateral outliers (right tails), two robustness scenarios were introduced: Scenario A applies winsorization at the 95th percentile, while Scenario B removes cases at or above the same cutoff. This yielded three datasets, enabling sensitivity analyses of outlier handling while preserving an identical model structure across scenarios.

Main PLS-SEM Estimation. The baseline specification relies on the complete post-screening dataset (N = 5,570; 30 indicators). Transformed variables are standardized, with no winsorization or deletion, thereby preserving the full distributional variation.

data_main <- select(data_transformed, all_of(z_vars))
names(data_main) <- gsub("^z_t_|^z_", "", names(data_main))
data_main <- data_main[, sort(names(data_main))]

Scenario A: Winsorization. This minimally invasive robustness check caps values above the 95th percentile for three flagged indicators, preserving the full sample (N = 5,570; 30 indicators). Winsorization reduces the impact of extreme values while retaining all observations.

data_winsorize <- data_transformed %>%
  select(all_of(z_vars)) %>%
  mutate(
    w_z_t_infra_5 = Winsorize(z_t_infra_5, val = quantile(z_t_infra_5, probs = c(0, 0.95), na.rm = TRUE)),
    w_z_t_farmi_1 = Winsorize(z_t_farmi_1, val = quantile(z_t_farmi_1, probs = c(0, 0.95), na.rm = TRUE)),
    w_z_t_foodenv_3 = Winsorize(z_t_foodenv_3, val = quantile(z_t_foodenv_3, probs = c(0, 0.95), na.rm = TRUE))) %>%
  select(-c(z_t_infra_5, z_t_farmi_1, z_t_foodenv_3))

w_vars <- grep("^w_", names(data_winsorize), value = TRUE)
w_long <- data_winsorize %>%
  select(all_of(w_vars)) %>%
  pivot_longer(cols = everything(), names_to = "Variable", values_to = "Z")

ggplot(w_long, aes(x = Variable, y = Z)) +
  geom_boxplot(outlier.colour = "firebrick", fill = "grey90", notch = TRUE) +
  coord_flip() +
  labs(
    title = "Boxplot of Winsorized Indicators",
    x = NULL,
    y = "Z-score") +
  theme_minimal(base_size = 12) +
  theme(
    plot.title = element_text(face = "bold", hjust = 0.5),
    axis.text.y = element_text(face = "bold"))

names(data_winsorize) <- gsub("^z_t_|^z_|^w_z_t_", "", names(data_winsorize))
data_winsorize <- data_winsorize[, sort(names(data_winsorize))]

Scenario B: Listwise Deletion. This conservative robustness check excludes all rows with any flagged indicator at or above the 95th-percentile cutoff, reducing sample size but removing extreme-value influence (N = 4,641; 30 indicators). Deletion serves as a stricter remedy when outliers risk distorting estimation results.

The diagnostics table above summarizes removed cases by indicator and overall, and the boxplot displays the retained values after deletion.

data_deletion <- select(data_transformed, all_of(z_vars))

flagged_indicators <- c("z_t_infra_5", "z_t_farmi_1", "z_t_foodenv_3")

stopifnot(exists("flagged_indicators"))
use_flagged <- intersect(flagged_indicators, names(data_deletion))
stopifnot(length(use_flagged) > 0)

q95 <- vapply(
  data_deletion[use_flagged],
  function(x) quantile(x, probs = 0.95, na.rm = TRUE, type = 8),
  numeric(1))

extreme_row <- Reduce(`|`,
  Map(function(col, thr) {
    v <- data_deletion[[col]] >= thr
    v[is.na(v)] <- FALSE
    v
  }, use_flagged, q95))

n_before <- nrow(data_deletion)
n_drop <- sum(extreme_row, na.rm = TRUE)
n_after <- n_before - n_drop

per_indicator_removed <- vapply(
  use_flagged,
  function(col) sum(data_transformed[[col]] >= q95[[col]], na.rm = TRUE),
  numeric(1))

diagnostics <- data.frame(
  indicator = use_flagged,
  p95_cutoff = q95[use_flagged],
  n_removed = per_indicator_removed,
  stringsAsFactors = FALSE)

data_deletion <- data_deletion[!extreme_row, , drop = FALSE]
overall_row <- data.frame(
  Indicator = "Overall (any flagged indicator ≥ p95)",
  `p95 Cutoff` = NA_real_,
  `Removed (n)` = n_drop,
  `Removed (%)` = round(100 * n_drop / n_before, 2),
  `N Before` = n_before,
  `N After` = n_after,
  check.names = FALSE)

per_rows <- data.frame(
  Indicator = diagnostics$indicator,
  `p95 Cutoff` = as.numeric(diagnostics$p95_cutoff),
  `Removed (n)` = as.integer(diagnostics$n_removed),
  `Removed (%)` = round(100 * diagnostics$n_removed / n_before, 2),
  `N Before` = NA_integer_,
  `N After` = NA_integer_,
  check.names = FALSE)

deletion_report <- rbind(overall_row, per_rows) %>%
  mutate(`p95 Cutoff` = ifelse(is.na(`p95 Cutoff`),NA,round(as.numeric(`p95 Cutoff`), 3)))%>%
  mutate(across(everything(),~ ifelse(is.na(.x), "-", .x)))

kable(deletion_report,
  format = "html",
  digits = 3,
  align = c("l", rep("c", 5)),
  caption = "<b><span style='color:black'>Scenario B: Listwise deletion diagnostics (95th percentile rule)</span></b>",
  escape = FALSE) %>%
  kable_styling(
    bootstrap_options = c("striped", "hover", "condensed", "responsive"),
    full_width = FALSE,
    position = "center",
    fixed_thead = TRUE) %>%
  add_header_above(
    c(" " = 1, "Deletion threshold" = 1, "Removed" = 2, "Sample size" = 2)) %>%
  row_spec(1, bold = TRUE)
Scenario B: Listwise deletion diagnostics (95th percentile rule)
Deletion threshold
Removed
Sample size
Indicator p95 Cutoff Removed (n) Removed (%) N Before N After
Overall (any flagged indicator ≥ p95)
929 16.68 5570 4641
z_t_infra_5 2.326 278 4.99
z_t_farmi_1 2.036 278 4.99
z_t_foodenv_3 1.567 436 7.83
d_long <- data_deletion %>%
  select(all_of(flagged_indicators)) %>%
  pivot_longer(cols = everything(), names_to = "Variable", values_to = "Z")

ggplot(d_long, aes(x = Variable, y = Z)) +
  geom_boxplot(outlier.colour = "firebrick", fill = "grey90", notch = TRUE) +
  coord_flip() +
  labs(
    title = "Boxplot of flagged indicators (after listwise deletion)",
    x = NULL,
    y = "Z-score") +
  theme_minimal(base_size = 12) +
  theme(
    plot.title = element_text(face = "bold", hjust = 0.5),
    axis.text.y = element_text(face = "bold"))

names(data_deletion) <- gsub("^z_t_|^z_|^w_z_t_", "", names(data_deletion))
data_deletion <- data_deletion[, sort(names(data_deletion))]

Key Points (Data Refinement and Robustness Scenarios). After excluding envir_3 (o6) and farmi_10 (v6) due to excess missing values, the analyses rely on 30 indicators. Variance-stabilizing transformations were applied to reduce skewness and stabilize variance, while binary items were retained as coded. All variables were standardized to z-scores for comparability. Outlier screening based on IQR, Hampel, and Z-score rules flagged three variables above the 5% cutoff in the right tail: z_t_infra_5 (d1), z_t_farmi_1 (ig52), and z_t_foodenv_3 (b5).

To assess robustness, three estimation datasets were constructed:

  • Main PLS-SEM: full sample with standardized indicators, no outlier adjustments (N = 5,570).
  • Scenario A (Winsorization): caps flagged values at the 95th percentile, preserving N = 5,570.
  • Scenario B (Deletion): removes observations with any flagged indicator at or above the 95th percentile, reducing the sample to N = 4,641.

All scenarios retain the same 30 indicators and identical model structure, enabling assessment of robustness of structural estimates to alternative outlier treatments.

4 Model Specification and Estimation

The analysis employs variance-based structural equation modeling (PLS-SEM), with both measurement and structural components explicitly specified. Formative constructs are estimated using regression weights, in line with recommendations for indicators that represent distinct facets rather than interchangeable reflections.

# Customize SEMinR diagram theme
theme <- seminr_theme_get()
theme$sm.edge.label.show <- TRUE
theme$sm.edge.boot.show_t_value <- TRUE
theme$sm.edge.boot.show_p_value <- TRUE
theme$sm.edge.boot.show_p_stars <- TRUE
theme$mm.edge.label.show <- TRUE
theme$mm.edge.boot.show_p_value  <- TRUE
theme$mm.edge.boot.show_p_stars  <- TRUE
theme$sm.edge.positive.color <- "steelblue"
theme$sm.edge.negative.color <- "firebrick"
theme$construct.compositeA.arrow <- "none"
seminr_theme_set(theme)

Measurement model. Latent constructs and their indicators are defined with the constructs() function from the seminr package, all specified as composites with Mode B weights.

model_constructs <- constructs(
  composite("CLIMA", single_item("clima")),
  composite("ECOSY", multi_items("ecosy_", 1:3), weights = mode_B),
  composite("ENVIR", multi_items("envir_", 1:2), weights = mode_B),
  composite("FARMI", multi_items("farmi_", 1:9), weights = mode_B),
  composite("FOODENV", multi_items("foodenv_", 1:4), weights = mode_B),
  composite("FOODSEC", single_item("foodsec")),
  composite("INFRA", multi_items("infra_", 1:5), weights = mode_B),
  composite("NUTRI", multi_items("nutri_", 1:5), weights = mode_B)
)

plot(model_constructs, title = "Measurement model specification")

Structural Model. Directional relationships among constructs are specified with the relationships() function.

model_paths <- relationships(
  paths(from = c("ENVIR"), to = c("INFRA","ECOSY","FARMI","FOODENV")),
  paths(from = c("INFRA"), to = c("ECOSY","FARMI","FOODENV")),
  paths(from = c("ECOSY"), to = c("FARMI","CLIMA")),
  paths(from = c("FARMI"), to = c("FOODENV","NUTRI","CLIMA","FOODSEC")),
  paths(from = c("FOODENV"), to = c("NUTRI","CLIMA","FOODSEC"))
)

plot(model_paths, title = "Structural model specification")

Model Estimation. The PLS path model is estimated with the estimate_pls() function for the main dataset and the two robustness scenarios (A: winsorization; B: listwise deletion). Inner weights follow the path weighting scheme, and missing data are handled by mean replacement.

model_pls_main <- estimate_pls(
  data = data_main,
  measurement_model = model_constructs,
  structural_model = model_paths,
  inner_weights = path_weighting,
  missing = mean_replacement)

model_pls_scenarioA <- estimate_pls(
  data = data_winsorize,
  measurement_model = model_constructs,
  structural_model = model_paths,
  inner_weights = path_weighting,
  missing = mean_replacement)

model_pls_scenarioB <- estimate_pls(
  data = data_deletion,
  measurement_model = model_constructs,
  structural_model = model_paths,
  inner_weights = path_weighting,
  missing = mean_replacement)

s_main <- summary(model_pls_main)
s_A <- summary(model_pls_scenarioA)
s_B <- summary(model_pls_scenarioB)

5 Measurement Model Evaluation

Formative measurement models are evaluated in terms of multicollinearity and the significance and relevance of indicator weights and loadings, rather than internal consistency (Cronbach’s alpha, rhoA, CR) or convergent validity (AVE). Results are reported for the main estimation (full standardized dataset) and for robustness checks (Scenario A: winsorization at the top 5%; Scenario B: listwise deletion of flagged outliers).

add_pval <- function(df_stats, data_model) {
  df <- as.data.frame(df_stats, stringsAsFactors = FALSE)
  t_col <- grep("T Stat\\.", names(df), ignore.case = TRUE, value = TRUE)[1]
  if (is.null(t_col)) stop("T-stat not found.")
  df_model <- nrow(data_model)
  df %>%
    select(-matches("^0\\.5% CI|99\\.5% CI$")) %>%
    mutate(`p-value` = ifelse(
      is.na(.data[[t_col]]), NA_real_,
      round(2 * pt(abs(.data[[t_col]]), df = df_model, lower.tail = FALSE), 3)))
}

format_vif_cells <- function(x, thr = 5, digits = 3) {
  out <- ifelse(is.na(x), "", sprintf(paste0("%.", digits, "f"), x))
  bold_idx <- !is.na(x) & x >= thr
  out[bold_idx] <- cell_spec(out[bold_idx], bold = TRUE)
  out
}

kable_group_by_construct <- function(df_fmt, caption, scen_cols) {
  idx <- df_fmt %>% 
    mutate(row_id = row_number()) %>%
    group_by(Construct) %>%
    summarise(start = min(row_id), end = max(row_id), .groups = "drop")
  kb <- kable(df_fmt %>% select(-Construct),
              format = "html", escape = FALSE,
              align = c("l", rep("c", length(scen_cols))),
              caption = caption, col.names = c("Indicator", scen_cols)) %>%
        kable_styling(bootstrap_options = c("striped","hover","condensed"),
                      full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
        add_header_above(c(" " = 1, "Scenarios" = length(scen_cols)))
  if (nrow(idx) > 0) for (i in seq_len(nrow(idx))) {
    kb <- kb %>% group_rows(idx$Construct[i], idx$start[i], idx$end[i])}
  kb
}

print_boot_tbl <- function(boot_list, caption){
  boot_results_all <- map_dfr(names(boot_list), ~{
    boot_list[[.x]] %>%
      rownames_to_column("Indicator") %>%
      mutate(Scenario = .x)
  })
  idx <- boot_results_all %>% mutate(row_id = row_number()) %>%
    group_by(Scenario) %>% summarise(start = min(row_id), end = max(row_id), .groups = "drop")
  boot_results_all <- boot_results_all %>%
    mutate(signif = case_when(
      `p-value` < 0.01 ~ "<sup>***</sup>",
      `p-value` < 0.05 ~ "<sup>**</sup>",
      `p-value` < 0.1 ~ "<sup>*</sup>",
      TRUE ~ ""),
      `Original Est.` = paste0(round(`Original Est.`,3), signif))
  kb <- kable(boot_results_all %>% select(-c(Scenario, signif)),
              caption = caption, digits = 3, format = "html", escape = FALSE, booktabs = TRUE,
              align = c("l", rep("c", 5))) %>%
        kable_styling(bootstrap_options = c("striped","hover","condensed"),
                      full_width = FALSE, position = "center", fixed_thead = TRUE)
  if (nrow(idx) > 0) for(i in seq_len(nrow(idx))){
    kb <- kb %>% group_rows(idx$Scenario[i], idx$start[i], idx$end[i], bold = TRUE)}
  kb
}

Collinearity assessment (VIF). The baseline VIF evaluation indicated critical collinearity (\(VIF > 5\)) for nutri_3 (f3), nutri_4 (h1), and nutri_5 (h2), with cautionary values (between \(3\) and \(5\)) for nutri_2 (f2), farmi_2 (n1), and farmi_3 (n2). Non-critical indicators were retained, while iterative refinements were performed to address the critical cases:

After these adjustments, all remaining indicators had VIF values below the critical threshold, confirming the absence of multicollinearity.

vif_baseline <- list(
  "Main PLS-SEM" = s_main$validity$vif_items,
  "Scenario A" = s_A$validity$vif_items,
  "Scenario B" = s_B$validity$vif_items)

drop_constructs <- c("CLIMA","FOODSEC")

vif_iter0 <- imap_dfr(
  vif_baseline,
  function(vif_list, sc_name){
    valid_cons <- setdiff(names(vif_list), drop_constructs)
    imap_dfr(vif_list[valid_cons], function(vec, cn){
      tibble(
        Scenario = sc_name,
        Construct = cn,
        Indicator = names(vec),
        VIF = as.numeric(vec))})}) %>%
  filter(!is.na(VIF), is.finite(VIF)) %>%
  mutate(Scenario = factor(Scenario, levels = c("Main PLS-SEM","Scenario A","Scenario B"))) %>%
  arrange(Construct, Indicator, Scenario) %>%
  select(Construct, Indicator, Scenario, VIF) %>%
  pivot_wider(names_from = Scenario, values_from = VIF) %>%
  arrange(Construct, Indicator)

scen_cols0 <- setdiff(names(vif_iter0), c("Construct","Indicator"))
vif_fmt0 <- vif_iter0
vif_fmt0[scen_cols0] <- lapply(vif_fmt0[scen_cols0], format_vif_cells)

kable_group_by_construct(
  vif_fmt0,
  paste0(
    "<b><span style='color:black'>Item-level VIF values by scenario (formative indicators)</span></b><br>",
    "<span style='font-size:90%;color:gray'>Iteration 0: Baseline.</span>"),
  scen_cols0
)
Item-level VIF values by scenario (formative indicators)
Iteration 0: Baseline.
Scenarios
Indicator Main PLS-SEM Scenario A Scenario B
ECOSY
ecosy_1 2.955 2.955 2.753
ecosy_2 2.948 2.948 2.747
ecosy_3 1.004 1.004 1.004
ENVIR
envir_1 1.188 1.188 1.184
envir_2 1.188 1.188 1.184
FARMI
farmi_1 1.450 1.536 1.517
farmi_2 4.607 4.636 4.661
farmi_3 3.886 3.891 4.010
farmi_4 1.340 1.346 1.278
farmi_5 1.325 1.327 1.345
farmi_6 1.358 1.377 1.399
farmi_7 1.839 1.843 1.723
farmi_8 1.070 1.075 1.071
farmi_9 1.134 1.135 1.115
FOODENV
foodenv_1 1.183 1.180 1.170
foodenv_2 1.003 1.003 1.003
foodenv_3 1.019 1.008 1.001
foodenv_4 1.187 1.182 1.167
INFRA
infra_1 1.280 1.280 1.243
infra_2 1.143 1.143 1.152
infra_3 1.252 1.252 1.220
infra_4 1.115 1.115 1.114
infra_5 1.069 1.069 1.051
NUTRI
nutri_1 2.895 2.895 2.923
nutri_2 3.924 3.924 3.812
nutri_3 10.372 10.372 10.434
nutri_4 6.260 6.260 6.470
nutri_5 9.838 9.838 9.706
model_constructs_iter1 <- constructs(
  composite("CLIMA", single_item("clima")),
  composite("ECOSY", multi_items("ecosy_", 1:3), weights = mode_B),
  composite("ENVIR", multi_items("envir_", 1:2), weights = mode_B),
  composite("FARMI", multi_items("farmi_", 1:9), weights = mode_B),
  composite("FOODENV", multi_items("foodenv_", 1:4), weights = mode_B),
  composite("FOODSEC", single_item("foodsec")),
  composite("INFRA", multi_items("infra_", 1:5), weights = mode_B),
  composite("NUTRI", multi_items("nutri_", c(1,2,4,5)), weights = mode_B))

model_pls_iter1 <- list(
  "Main" = estimate_pls(data_main, model_constructs_iter1, structural_model = model_paths, inner_weights = path_weighting, missing = mean_replacement),
  "ScenarioA" = estimate_pls(data_winsorize, model_constructs_iter1, structural_model = model_paths, inner_weights = path_weighting, missing = mean_replacement),
  "ScenarioB" = estimate_pls(data_deletion, model_constructs_iter1, structural_model = model_paths, inner_weights = path_weighting, missing = mean_replacement))

s_main_iter1 <- summary(model_pls_iter1$Main)
s_A_iter1 <- summary(model_pls_iter1$ScenarioA)
s_B_iter1 <- summary(model_pls_iter1$ScenarioB)

vif_iter1 <- list(
  "Main PLS-SEM" = s_main_iter1$validity$vif_items,
  "Scenario A" = s_A_iter1$validity$vif_items,
  "Scenario B" = s_B_iter1$validity$vif_items)

vif_iter1 <- imap_dfr(
  vif_iter1,
  function(vif_list, sc_name){
    valid_cons <- setdiff(names(vif_list), drop_constructs)
    imap_dfr(vif_list[valid_cons], function(vec, cn){
      tibble(
        Scenario = sc_name,
        Construct = cn,
        Indicator = names(vec),
        VIF = as.numeric(vec))})}) %>%
  filter(!is.na(VIF), is.finite(VIF)) %>%
  mutate(Scenario = factor(Scenario, levels = c("Main PLS-SEM","Scenario A","Scenario B"))) %>%
  arrange(Construct, Indicator, Scenario) %>%
  select(Construct, Indicator, Scenario, VIF) %>%
  pivot_wider(names_from = Scenario, values_from = VIF) %>%
  arrange(Construct, Indicator)

scen_cols1 <- setdiff(names(vif_iter1), c("Construct","Indicator"))
vif_fmt1 <- vif_iter1
vif_fmt1[scen_cols1] <- lapply(vif_fmt1[scen_cols1], format_vif_cells)

kable_group_by_construct(
  vif_fmt1,
  paste0(
    "<b><span style='color:black'>Item-level VIF values by scenario (formative indicators)</span></b><br>",
    "<span style='font-size:90%;color:gray'>Iteration 1: Exclude nutri_3.</span>"),
  scen_cols1)
Item-level VIF values by scenario (formative indicators)
Iteration 1: Exclude nutri_3.
Scenarios
Indicator Main PLS-SEM Scenario A Scenario B
ECOSY
ecosy_1 2.955 2.955 2.753
ecosy_2 2.948 2.948 2.747
ecosy_3 1.004 1.004 1.004
ENVIR
envir_1 1.188 1.188 1.184
envir_2 1.188 1.188 1.184
FARMI
farmi_1 1.450 1.536 1.517
farmi_2 4.607 4.636 4.661
farmi_3 3.886 3.891 4.010
farmi_4 1.340 1.346 1.278
farmi_5 1.325 1.327 1.345
farmi_6 1.358 1.377 1.399
farmi_7 1.839 1.843 1.723
farmi_8 1.070 1.075 1.071
farmi_9 1.134 1.135 1.115
FOODENV
foodenv_1 1.183 1.180 1.170
foodenv_2 1.003 1.003 1.003
foodenv_3 1.019 1.008 1.001
foodenv_4 1.187 1.182 1.167
INFRA
infra_1 1.280 1.280 1.243
infra_2 1.143 1.143 1.152
infra_3 1.252 1.252 1.220
infra_4 1.115 1.115 1.114
infra_5 1.069 1.069 1.051
NUTRI
nutri_1 2.843 2.843 2.861
nutri_2 3.889 3.889 3.779
nutri_4 3.668 3.668 3.779
nutri_5 5.646 5.646 5.551
model_constructs_iter2 <- constructs(
  composite("CLIMA", single_item("clima")),
  composite("ECOSY", multi_items("ecosy_", 1:3), weights = mode_B),
  composite("ENVIR", multi_items("envir_", 1:2), weights = mode_B),
  composite("FARMI", multi_items("farmi_", 1:9), weights = mode_B),
  composite("FOODENV", multi_items("foodenv_", 1:4), weights = mode_B),
  composite("FOODSEC", single_item("foodsec")),
  composite("INFRA", multi_items("infra_", 1:5), weights = mode_B),
  composite("NUTRI", multi_items("nutri_", c(1,2,4)), weights = mode_B))

model_pls_iter2 <- list(
  "Main" = estimate_pls(data_main, model_constructs_iter2, structural_model = model_paths, inner_weights = path_weighting, missing = mean_replacement),
  "ScenarioA" = estimate_pls(data_winsorize, model_constructs_iter2, structural_model = model_paths, inner_weights = path_weighting, missing = mean_replacement),
  "ScenarioB" = estimate_pls(data_deletion, model_constructs_iter2, structural_model = model_paths, inner_weights = path_weighting, missing = mean_replacement))

s_main_iter2 <- summary(model_pls_iter2$Main)
s_A_iter2 <- summary(model_pls_iter2$ScenarioA)
s_B_iter2 <- summary(model_pls_iter2$ScenarioB)

vif_iter2 <- list(
  "Main PLS-SEM" = s_main_iter2$validity$vif_items,
  "Scenario A" = s_A_iter2$validity$vif_items,
  "Scenario B" = s_B_iter2$validity$vif_items)

vif_iter2 <- imap_dfr(
  vif_iter2,
  function(vif_list, sc_name){
    valid_cons <- setdiff(names(vif_list), drop_constructs)
    imap_dfr(vif_list[valid_cons], function(vec, cn){
      tibble(
        Scenario = sc_name,
        Construct = cn,
        Indicator = names(vec),
        VIF = as.numeric(vec))})}) %>%
  filter(!is.na(VIF), is.finite(VIF)) %>%
  mutate(Scenario = factor(Scenario, levels = c("Main PLS-SEM","Scenario A","Scenario B"))) %>%
  arrange(Construct, Indicator, Scenario) %>%
  select(Construct, Indicator, Scenario, VIF) %>%
  pivot_wider(names_from = Scenario, values_from = VIF) %>%
  arrange(Construct, Indicator)

scen_cols2 <- setdiff(names(vif_iter2), c("Construct","Indicator"))
vif_fmt2 <- vif_iter2
vif_fmt2[scen_cols2] <- lapply(vif_fmt2[scen_cols2], format_vif_cells)

kable_group_by_construct(
  vif_fmt2,
  paste0(
    "<b><span style='color:black'>Item-level VIF values by scenario (formative indicators)</span></b><br>",
    "<span style='font-size:90%;color:gray'>Iteration 2: Exclude nutri_3 & nutri_5.</span>"),
  scen_cols2)
Item-level VIF values by scenario (formative indicators)
Iteration 2: Exclude nutri_3 & nutri_5.
Scenarios
Indicator Main PLS-SEM Scenario A Scenario B
ECOSY
ecosy_1 2.955 2.955 2.753
ecosy_2 2.948 2.948 2.747
ecosy_3 1.004 1.004 1.004
ENVIR
envir_1 1.188 1.188 1.184
envir_2 1.188 1.188 1.184
FARMI
farmi_1 1.450 1.536 1.517
farmi_2 4.607 4.636 4.661
farmi_3 3.886 3.891 4.010
farmi_4 1.340 1.346 1.278
farmi_5 1.325 1.327 1.345
farmi_6 1.358 1.377 1.399
farmi_7 1.839 1.843 1.723
farmi_8 1.070 1.075 1.071
farmi_9 1.134 1.135 1.115
FOODENV
foodenv_1 1.183 1.180 1.170
foodenv_2 1.003 1.003 1.003
foodenv_3 1.019 1.008 1.001
foodenv_4 1.187 1.182 1.167
INFRA
infra_1 1.280 1.280 1.243
infra_2 1.143 1.143 1.152
infra_3 1.252 1.252 1.220
infra_4 1.115 1.115 1.114
infra_5 1.069 1.069 1.051
NUTRI
nutri_1 2.842 2.842 2.861
nutri_2 1.329 1.329 1.317
nutri_4 2.986 2.986 3.030

Subsequent analyses are performed on the Refined Model, excluding indicators with critical multicollinearity. The Full Model, including all 30 indicators, was also evaluated to verify the validity of the results, given the theoretical importance of those indicators.

Indicator weights and loadings. Bootstrapped weights and loadings (6,000 resamples) were used to assess significance across scenarios. Following Hair et al. (2021, Fig. 5.2), indicators with significant weights were retained. Non-significant weights were then evaluated by loadings: items with loadings of 0.50 or higher were retained (regardless of significance); items with non-signigicant loadings below 0.50 were candidates for exclusion. Consistent with Hair et al. (2019, 2021), theoretical and content validy was also considered, since formative indicatiors are not interchangeable and their removal may compromise construct validity.

Across models (Refined, Full, and robustness scenarios), three indicators showed non-significant weights: ecosy_3 (ii1), envir_2 (w2) and farmi_4 (pa1). In addition, foodenv_3 (b5) was non-significant in the main specification, and envir_1 (w1) was flagged in Scenario B. Indicators envir_1 (w1), envir_2 (w2) and foodenv_3 (b5) were retained given their loadings exceeded 0.50, while farmi_4 (pa1) was kept due to significant loading. Although ecosy_3 (ii1) had negligible empirical contribution, it captures a distinct dimension of ecosystem conservation. To preserve content validity, it was retained in both the Refined and Full Models.

The table below summarize these decisions, with full bootstrap results reported subsequently.

Indicator Weight Loading Decision
ecosy_3 (ii1) Non-significant (all scenarios) Low (<0.03, non-significant) Retained (content validity despite negligible empirical contribution)
envir_1 (w1) Mixed: significant in Main/A, non-significant in B High (0.7–0.8, significant) Retained (significant loading)
envir_2 (w2) Non-significant (all scenarios) High (0.7–0.9, significant) Retained (significant loading)
farmi_4 (pa1) Non-significant (all scenarios) Moderate (0.3–0.4, significant) Retained (significant loading)
foodenv_3 (b5) Non-significant (Main only) Low (0.09, significant) Retained (significant loading)
# Refined Model (after VIF exclusion)
boot_model_refined <- list(
  "Main PLS-SEM" = bootstrap_model(seminr_model = model_pls_iter2$Main, nboot = 6000, seed = 35),
  "Scenario A" = bootstrap_model(seminr_model = model_pls_iter2$ScenarioA, nboot = 6000, seed = 35),
  "Scenario B" = bootstrap_model(seminr_model = model_pls_iter2$ScenarioB, nboot = 6000, seed = 35))

s_boot_refined <- list(
  "Main PLS-SEM" = summary(boot_model_refined$`Main PLS-SEM`, alpha = 0.01),
  "Scenario A" = summary(boot_model_refined$`Scenario A`, alpha = 0.01),
  "Scenario B" = summary(boot_model_refined$`Scenario B`, alpha = 0.01))

# Full Model (all 30 indicators)
boot_model_full <- list(
  "Main PLS-SEM" = bootstrap_model(seminr_model = model_pls_main, nboot = 6000, seed = 35),
  "Scenario A" = bootstrap_model(seminr_model = model_pls_scenarioA, nboot = 6000, seed = 35),
  "Scenario B" = bootstrap_model(seminr_model = model_pls_scenarioB, nboot = 6000, seed = 35))

s_boot_full <- list(
  "Main PLS-SEM" = summary(boot_model_full$`Main PLS-SEM`, alpha = 0.01),
  "Scenario A" = summary(boot_model_full$`Scenario A`, alpha = 0.01),
  "Scenario B" = summary(boot_model_full$`Scenario B`, alpha = 0.01))
boot_results_refined_weights <- list(
  "Main PLS-SEM" = add_pval(s_boot_refined$`Main PLS-SEM`$bootstrapped_weights, data_main),
  "Scenario A" = add_pval(s_boot_refined$`Scenario A`$bootstrapped_weights, data_winsorize),
  "Scenario B" = add_pval(s_boot_refined$`Scenario B`$bootstrapped_weights, data_deletion)
)

boot_results_refined_loadings <- list(
  "Main PLS-SEM" = add_pval(s_boot_refined$`Main PLS-SEM`$bootstrapped_loadings, data_main),
  "Scenario A" = add_pval(s_boot_refined$`Scenario A`$bootstrapped_loadings, data_winsorize),
  "Scenario B" = add_pval(s_boot_refined$`Scenario B`$bootstrapped_loadings, data_deletion)
)

print_boot_tbl(boot_results_refined_weights,
               paste0("<b><span style='color:black'>Bootstrapped Indicator Weights (Refined Model)</span></b><br>",
                      "<span style='font-size:90%;color:gray'>Significance levels are denoted as follows: ***p < 0.01, **p < 0.05, *p < 0.10.</span>"))
Bootstrapped Indicator Weights (Refined Model)
Significance levels are denoted as follows: p < 0.01, p < 0.05, p < 0.10.
Indicator Original Est. Bootstrap Mean Bootstrap SD T Stat. p-value
Main PLS-SEM
clima -> CLIMA 1 1.000 0.000 NA NA
ecosy_1 -> ECOSY 0.53*** 0.531 0.035 15.185 0.000
ecosy_2 -> ECOSY 0.521*** 0.520 0.035 14.852 0.000
ecosy_3 -> ECOSY 0.024 0.024 0.019 1.312 0.190
envir_1 -> ENVIR 0.757** 0.689 0.342 2.212 0.027
envir_2 -> ENVIR 0.418 0.366 0.408 1.026 0.305
farmi_1 -> FARMI 0.426*** 0.425 0.017 25.719 0.000
farmi_2 -> FARMI -0.635*** -0.636 0.030 -21.218 0.000
farmi_3 -> FARMI -0.489*** -0.489 0.027 -17.933 0.000
farmi_4 -> FARMI 0.01 0.009 0.013 0.723 0.470
farmi_5 -> FARMI 0.172*** 0.172 0.014 12.299 0.000
farmi_6 -> FARMI -0.194*** -0.194 0.014 -13.463 0.000
farmi_7 -> FARMI 0.24*** 0.240 0.017 14.038 0.000
farmi_8 -> FARMI 0.194*** 0.195 0.013 15.227 0.000
farmi_9 -> FARMI 0.153*** 0.153 0.013 11.948 0.000
foodenv_1 -> FOODENV 0.927*** 0.927 0.008 110.920 0.000
foodenv_2 -> FOODENV -0.105*** -0.104 0.015 -6.939 0.000
foodenv_3 -> FOODENV -0.014 -0.016 0.013 -1.057 0.291
foodenv_4 -> FOODENV 0.162*** 0.162 0.015 10.582 0.000
foodsec -> FOODSEC 1 1.000 0.000 NA NA
infra_1 -> INFRA 0.242*** 0.241 0.023 10.374 0.000
infra_2 -> INFRA 0.186*** 0.185 0.020 9.094 0.000
infra_3 -> INFRA 0.289*** 0.289 0.021 13.632 0.000
infra_4 -> INFRA 0.683*** 0.684 0.018 38.851 0.000
infra_5 -> INFRA 0.08*** 0.080 0.020 4.072 0.000
nutri_1 -> NUTRI -0.277*** -0.276 0.057 -4.819 0.000
nutri_2 -> NUTRI -0.1*** -0.100 0.037 -2.710 0.007
nutri_4 -> NUTRI 1.252*** 1.251 0.042 29.702 0.000
Scenario A
clima -> CLIMA 1 1.000 0.000 NA NA
ecosy_1 -> ECOSY 0.517*** 0.518 0.036 14.240 0.000
ecosy_2 -> ECOSY 0.535*** 0.533 0.037 14.468 0.000
ecosy_3 -> ECOSY 0.028 0.028 0.018 1.559 0.119
envir_1 -> ENVIR 0.73** 0.662 0.364 2.004 0.045
envir_2 -> ENVIR 0.452 0.379 0.434 1.041 0.298
farmi_1 -> FARMI 0.465*** 0.465 0.016 28.886 0.000
farmi_2 -> FARMI -0.609*** -0.609 0.029 -21.123 0.000
farmi_3 -> FARMI -0.474*** -0.474 0.026 -18.140 0.000
farmi_4 -> FARMI -0.002 -0.002 0.013 -0.162 0.871
farmi_5 -> FARMI 0.163*** 0.163 0.014 11.972 0.000
farmi_6 -> FARMI -0.173*** -0.173 0.014 -12.385 0.000
farmi_7 -> FARMI 0.227*** 0.227 0.017 13.600 0.000
farmi_8 -> FARMI 0.182*** 0.182 0.012 14.657 0.000
farmi_9 -> FARMI 0.148*** 0.148 0.013 11.812 0.000
foodenv_1 -> FOODENV 0.928*** 0.928 0.008 111.853 0.000
foodenv_2 -> FOODENV -0.104*** -0.103 0.015 -6.956 0.000
foodenv_3 -> FOODENV -0.045*** -0.045 0.013 -3.395 0.001
foodenv_4 -> FOODENV 0.162*** 0.162 0.015 10.660 0.000
foodsec -> FOODSEC 1 1.000 0.000 NA NA
infra_1 -> INFRA 0.241*** 0.239 0.023 10.403 0.000
infra_2 -> INFRA 0.189*** 0.189 0.020 9.307 0.000
infra_3 -> INFRA 0.29*** 0.290 0.021 13.647 0.000
infra_4 -> INFRA 0.682*** 0.682 0.018 38.835 0.000
infra_5 -> INFRA 0.078*** 0.078 0.020 3.947 0.000
nutri_1 -> NUTRI -0.271*** -0.271 0.058 -4.695 0.000
nutri_2 -> NUTRI -0.102*** -0.103 0.037 -2.761 0.006
nutri_4 -> NUTRI 1.249*** 1.248 0.042 29.427 0.000
Scenario B
clima -> CLIMA 1 1.000 0.000 NA NA
ecosy_1 -> ECOSY 0.53*** 0.531 0.041 12.888 0.000
ecosy_2 -> ECOSY 0.526*** 0.525 0.042 12.511 0.000
ecosy_3 -> ECOSY 0.025 0.025 0.020 1.261 0.207
envir_1 -> ENVIR 0.52 0.515 0.437 1.190 0.234
envir_2 -> ENVIR 0.673 0.472 0.523 1.289 0.198
farmi_1 -> FARMI 0.492*** 0.491 0.017 28.479 0.000
farmi_2 -> FARMI -0.619*** -0.620 0.032 -19.175 0.000
farmi_3 -> FARMI -0.459*** -0.459 0.029 -15.606 0.000
farmi_4 -> FARMI -0.015 -0.015 0.014 -1.096 0.273
farmi_5 -> FARMI 0.175*** 0.174 0.015 11.895 0.000
farmi_6 -> FARMI -0.152*** -0.153 0.015 -10.074 0.000
farmi_7 -> FARMI 0.207*** 0.206 0.017 11.915 0.000
farmi_8 -> FARMI 0.153*** 0.153 0.013 11.406 0.000
farmi_9 -> FARMI 0.14*** 0.140 0.014 10.153 0.000
foodenv_1 -> FOODENV 0.93*** 0.930 0.009 105.794 0.000
foodenv_2 -> FOODENV -0.094*** -0.093 0.016 -5.904 0.000
foodenv_3 -> FOODENV -0.064*** -0.064 0.014 -4.711 0.000
foodenv_4 -> FOODENV 0.154*** 0.154 0.016 9.488 0.000
foodsec -> FOODSEC 1 1.000 0.000 NA NA
infra_1 -> INFRA 0.211*** 0.208 0.027 7.922 0.000
infra_2 -> INFRA 0.207*** 0.206 0.024 8.785 0.000
infra_3 -> INFRA 0.29*** 0.291 0.025 11.703 0.000
infra_4 -> INFRA 0.694*** 0.694 0.020 35.198 0.000
infra_5 -> INFRA 0.089*** 0.090 0.024 3.703 0.000
nutri_1 -> NUTRI -0.257*** -0.258 0.064 -4.048 0.000
nutri_2 -> NUTRI -0.096** -0.095 0.040 -2.414 0.016
nutri_4 -> NUTRI 1.237*** 1.236 0.047 26.296 0.000
print_boot_tbl(boot_results_refined_loadings,
               paste0("<b><span style='color:black'>Bootstrapped Indicator Loadings (Refined Model)</span></b><br>",
                      "<span style='font-size:90%;color:gray'>Significance levels are denoted as follows: ***p < 0.01, **p < 0.05, *p < 0.10.</span>"))
Bootstrapped Indicator Loadings (Refined Model)
Significance levels are denoted as follows: p < 0.01, p < 0.05, p < 0.10.
Indicator Original Est. Bootstrap Mean Bootstrap SD T Stat. p-value
Main PLS-SEM
clima -> CLIMA 1 1.000 0.000 NA NA
ecosy_1 -> ECOSY 0.952*** 0.952 0.008 126.419 0.000
ecosy_2 -> ECOSY 0.951*** 0.951 0.007 128.747 0.000
ecosy_3 -> ECOSY -0.02 -0.021 0.024 -0.833 0.405
envir_1 -> ENVIR 0.923*** 0.834 0.228 4.059 0.000
envir_2 -> ENVIR 0.72** 0.640 0.305 2.356 0.019
farmi_1 -> FARMI 0.788*** 0.787 0.011 73.400 0.000
farmi_2 -> FARMI -0.651*** -0.651 0.011 -57.938 0.000
farmi_3 -> FARMI 0.354*** 0.355 0.016 22.493 0.000
farmi_4 -> FARMI 0.391*** 0.390 0.016 24.032 0.000
farmi_5 -> FARMI 0.501*** 0.500 0.015 33.495 0.000
farmi_6 -> FARMI -0.352*** -0.353 0.020 -17.639 0.000
farmi_7 -> FARMI 0.596*** 0.595 0.015 40.819 0.000
farmi_8 -> FARMI 0.331*** 0.331 0.018 18.571 0.000
farmi_9 -> FARMI 0.384*** 0.383 0.017 23.098 0.000
foodenv_1 -> FOODENV 0.983*** 0.983 0.003 367.843 0.000
foodenv_2 -> FOODENV -0.053** -0.053 0.020 -2.584 0.010
foodenv_3 -> FOODENV 0.098*** 0.096 0.020 4.868 0.000
foodenv_4 -> FOODENV 0.519*** 0.518 0.016 32.600 0.000
foodsec -> FOODSEC 1 1.000 0.000 NA NA
infra_1 -> INFRA 0.56*** 0.559 0.021 27.007 0.000
infra_2 -> INFRA 0.5*** 0.499 0.020 25.344 0.000
infra_3 -> INFRA 0.581*** 0.581 0.018 31.714 0.000
infra_4 -> INFRA 0.85*** 0.850 0.012 70.562 0.000
infra_5 -> INFRA 0.282*** 0.282 0.023 12.406 0.000
nutri_1 -> NUTRI 0.683*** 0.683 0.025 27.834 0.000
nutri_2 -> NUTRI 0.388*** 0.387 0.031 12.456 0.000
nutri_4 -> NUTRI 0.981*** 0.980 0.007 146.647 0.000
Scenario A
clima -> CLIMA 1 1.000 0.000 NA NA
ecosy_1 -> ECOSY 0.95*** 0.949 0.009 110.079 0.000
ecosy_2 -> ECOSY 0.954*** 0.953 0.007 139.044 0.000
ecosy_3 -> ECOSY -0.016 -0.017 0.024 -0.658 0.510
envir_1 -> ENVIR 0.91*** 0.813 0.243 3.738 0.000
envir_2 -> ENVIR 0.742** 0.643 0.326 2.280 0.023
farmi_1 -> FARMI 0.824*** 0.823 0.009 90.033 0.000
farmi_2 -> FARMI -0.646*** -0.646 0.011 -57.954 0.000
farmi_3 -> FARMI 0.353*** 0.353 0.016 22.616 0.000
farmi_4 -> FARMI 0.385*** 0.385 0.016 23.883 0.000
farmi_5 -> FARMI 0.497*** 0.496 0.015 33.487 0.000
farmi_6 -> FARMI -0.352*** -0.352 0.020 -17.870 0.000
farmi_7 -> FARMI 0.588*** 0.587 0.015 40.438 0.000
farmi_8 -> FARMI 0.328*** 0.329 0.018 18.683 0.000
farmi_9 -> FARMI 0.379*** 0.378 0.016 23.054 0.000
foodenv_1 -> FOODENV 0.983*** 0.982 0.003 364.787 0.000
foodenv_2 -> FOODENV -0.052** -0.051 0.020 -2.539 0.011
foodenv_3 -> FOODENV 0.023 0.021 0.020 1.164 0.245
foodenv_4 -> FOODENV 0.516*** 0.516 0.016 32.553 0.000
foodsec -> FOODSEC 1 1.000 0.000 NA NA
infra_1 -> INFRA 0.559*** 0.557 0.021 27.210 0.000
infra_2 -> INFRA 0.503*** 0.502 0.020 25.654 0.000
infra_3 -> INFRA 0.582*** 0.582 0.018 31.719 0.000
infra_4 -> INFRA 0.849*** 0.849 0.012 70.533 0.000
infra_5 -> INFRA 0.28*** 0.280 0.023 12.280 0.000
nutri_1 -> NUTRI 0.685*** 0.685 0.025 27.847 0.000
nutri_2 -> NUTRI 0.387*** 0.386 0.031 12.370 0.000
nutri_4 -> NUTRI 0.981*** 0.980 0.007 147.534 0.000
Scenario B
clima -> CLIMA 1 1.000 0.000 NA NA
ecosy_1 -> ECOSY 0.948*** 0.947 0.010 96.248 0.000
ecosy_2 -> ECOSY 0.948*** 0.947 0.008 115.274 0.000
ecosy_3 -> ECOSY -0.029 -0.028 0.026 -1.086 0.277
envir_1 -> ENVIR 0.785*** 0.701 0.301 2.609 0.009
envir_2 -> ENVIR 0.878** 0.674 0.400 2.194 0.028
farmi_1 -> FARMI 0.833*** 0.832 0.010 84.479 0.000
farmi_2 -> FARMI -0.661*** -0.661 0.012 -55.059 0.000
farmi_3 -> FARMI 0.392*** 0.392 0.017 23.150 0.000
farmi_4 -> FARMI 0.319*** 0.319 0.018 17.365 0.000
farmi_5 -> FARMI 0.524*** 0.523 0.016 33.157 0.000
farmi_6 -> FARMI -0.396*** -0.397 0.021 -18.836 0.000
farmi_7 -> FARMI 0.564*** 0.563 0.016 34.886 0.000
farmi_8 -> FARMI 0.323*** 0.323 0.020 16.531 0.000
farmi_9 -> FARMI 0.342*** 0.342 0.019 18.164 0.000
foodenv_1 -> FOODENV 0.983*** 0.983 0.003 347.643 0.000
foodenv_2 -> FOODENV -0.04* -0.039 0.022 -1.824 0.068
foodenv_3 -> FOODENV -0.064*** -0.064 0.020 -3.163 0.002
foodenv_4 -> FOODENV 0.502*** 0.502 0.018 28.438 0.000
foodsec -> FOODSEC 1 1.000 0.000 NA NA
infra_1 -> INFRA 0.527*** 0.525 0.024 22.403 0.000
infra_2 -> INFRA 0.524*** 0.523 0.022 24.362 0.000
infra_3 -> INFRA 0.56*** 0.560 0.022 25.761 0.000
infra_4 -> INFRA 0.857*** 0.857 0.013 63.680 0.000
infra_5 -> INFRA 0.261*** 0.261 0.027 9.699 0.000
nutri_1 -> NUTRI 0.697*** 0.696 0.027 26.192 0.000
nutri_2 -> NUTRI 0.391*** 0.392 0.033 11.721 0.000
nutri_4 -> NUTRI 0.984*** 0.982 0.007 146.002 0.000
boot_results_full_weights <- list(
  "Main PLS-SEM" = add_pval(s_boot_full$`Main PLS-SEM`$bootstrapped_weights, data_main),
  "Scenario A" = add_pval(s_boot_full$`Scenario A`$bootstrapped_weights, data_winsorize),
  "Scenario B" = add_pval(s_boot_full$`Scenario B`$bootstrapped_weights, data_deletion)
)

boot_results_full_loadings <- list(
  "Main PLS-SEM"       = add_pval(s_boot_full$`Main PLS-SEM`$bootstrapped_loadings, data_main),
  "Scenario A" = add_pval(s_boot_full$`Scenario A`$bootstrapped_loadings, data_winsorize),
  "Scenario B" = add_pval(s_boot_full$`Scenario B`$bootstrapped_loadings, data_deletion)
)

print_boot_tbl(boot_results_full_weights,
               paste0("<b><span style='color:black'>Bootstrapped Indicator Weights (Full Model)</span></b><br>",
    "<span style='font-size:90%;color:gray'>Significance levels are denoted as follows: ***p < 0.01, **p < 0.05, *p < 0.10.</span>"))
Bootstrapped Indicator Weights (Full Model)
Significance levels are denoted as follows: p < 0.01, p < 0.05, p < 0.10.
Indicator Original Est. Bootstrap Mean Bootstrap SD T Stat. p-value
Main PLS-SEM
clima -> CLIMA 1 1.000 0.000 NA NA
ecosy_1 -> ECOSY 0.531*** 0.531 0.035 15.141 0.000
ecosy_2 -> ECOSY 0.521*** 0.520 0.035 14.803 0.000
ecosy_3 -> ECOSY 0.024 0.024 0.019 1.297 0.195
envir_1 -> ENVIR 0.758** 0.683 0.357 2.123 0.034
envir_2 -> ENVIR 0.417 0.360 0.425 0.983 0.326
farmi_1 -> FARMI 0.412*** 0.412 0.017 24.915 0.000
farmi_2 -> FARMI -0.658*** -0.658 0.030 -21.762 0.000
farmi_3 -> FARMI -0.502*** -0.502 0.028 -18.254 0.000
farmi_4 -> FARMI 0.004 0.004 0.014 0.309 0.757
farmi_5 -> FARMI 0.172*** 0.171 0.014 12.254 0.000
farmi_6 -> FARMI -0.205*** -0.205 0.014 -14.323 0.000
farmi_7 -> FARMI 0.236*** 0.236 0.017 13.738 0.000
farmi_8 -> FARMI 0.19*** 0.190 0.013 14.484 0.000
farmi_9 -> FARMI 0.161*** 0.161 0.013 12.502 0.000
foodenv_1 -> FOODENV 0.924*** 0.924 0.008 109.051 0.000
foodenv_2 -> FOODENV -0.081*** -0.080 0.015 -5.353 0.000
foodenv_3 -> FOODENV -0.026** -0.028 0.013 -2.003 0.045
foodenv_4 -> FOODENV 0.173*** 0.173 0.015 11.220 0.000
foodsec -> FOODSEC 1 1.000 0.000 NA NA
infra_1 -> INFRA 0.238*** 0.236 0.023 10.192 0.000
infra_2 -> INFRA 0.185*** 0.184 0.021 9.012 0.000
infra_3 -> INFRA 0.293*** 0.293 0.021 13.776 0.000
infra_4 -> INFRA 0.685*** 0.685 0.018 38.958 0.000
infra_5 -> INFRA 0.078*** 0.078 0.020 3.928 0.000
nutri_1 -> NUTRI -0.245*** -0.244 0.044 -5.545 0.000
nutri_2 -> NUTRI -1.037*** -1.037 0.044 -23.706 0.000
nutri_3 -> NUTRI -0.162* -0.160 0.085 -1.899 0.058
nutri_4 -> NUTRI 0.599*** 0.598 0.064 9.293 0.000
nutri_5 -> NUTRI 1.5*** 1.498 0.077 19.564 0.000
Scenario A
clima -> CLIMA 1 1.000 0.000 NA NA
ecosy_1 -> ECOSY 0.517*** 0.518 0.036 14.212 0.000
ecosy_2 -> ECOSY 0.534*** 0.533 0.037 14.425 0.000
ecosy_3 -> ECOSY 0.028 0.028 0.018 1.540 0.124
envir_1 -> ENVIR 0.726* 0.655 0.376 1.933 0.053
envir_2 -> ENVIR 0.457 0.379 0.445 1.027 0.305
farmi_1 -> FARMI 0.452*** 0.451 0.016 27.896 0.000
farmi_2 -> FARMI -0.632*** -0.633 0.029 -21.647 0.000
farmi_3 -> FARMI -0.488*** -0.488 0.026 -18.436 0.000
farmi_4 -> FARMI -0.007 -0.008 0.013 -0.565 0.572
farmi_5 -> FARMI 0.163*** 0.163 0.014 11.934 0.000
farmi_6 -> FARMI -0.184*** -0.185 0.014 -13.254 0.000
farmi_7 -> FARMI 0.223*** 0.223 0.017 13.295 0.000
farmi_8 -> FARMI 0.177*** 0.178 0.013 13.924 0.000
farmi_9 -> FARMI 0.156*** 0.156 0.013 12.407 0.000
foodenv_1 -> FOODENV 0.924*** 0.924 0.008 109.509 0.000
foodenv_2 -> FOODENV -0.08*** -0.079 0.015 -5.356 0.000
foodenv_3 -> FOODENV -0.058*** -0.059 0.013 -4.468 0.000
foodenv_4 -> FOODENV 0.172*** 0.172 0.015 11.303 0.000
foodsec -> FOODSEC 1 1.000 0.000 NA NA
infra_1 -> INFRA 0.236*** 0.235 0.023 10.212 0.000
infra_2 -> INFRA 0.189*** 0.188 0.020 9.219 0.000
infra_3 -> INFRA 0.294*** 0.294 0.021 13.800 0.000
infra_4 -> INFRA 0.684*** 0.684 0.018 38.942 0.000
infra_5 -> INFRA 0.076*** 0.076 0.020 3.802 0.000
nutri_1 -> NUTRI -0.24*** -0.240 0.044 -5.437 0.000
nutri_2 -> NUTRI -1.043*** -1.043 0.044 -23.934 0.000
nutri_3 -> NUTRI -0.167* -0.165 0.085 -1.960 0.050
nutri_4 -> NUTRI 0.595*** 0.593 0.064 9.231 0.000
nutri_5 -> NUTRI 1.51*** 1.507 0.076 19.746 0.000
Scenario B
clima -> CLIMA 1 1.000 0.000 NA NA
ecosy_1 -> ECOSY 0.53*** 0.531 0.041 12.877 0.000
ecosy_2 -> ECOSY 0.526*** 0.524 0.042 12.487 0.000
ecosy_3 -> ECOSY 0.025 0.025 0.020 1.253 0.210
envir_1 -> ENVIR 0.518 0.515 0.442 1.172 0.241
envir_2 -> ENVIR 0.675 0.464 0.533 1.268 0.205
farmi_1 -> FARMI 0.479*** 0.478 0.017 27.489 0.000
farmi_2 -> FARMI -0.642*** -0.643 0.033 -19.742 0.000
farmi_3 -> FARMI -0.475*** -0.475 0.030 -16.065 0.000
farmi_4 -> FARMI -0.02 -0.021 0.014 -1.491 0.136
farmi_5 -> FARMI 0.173*** 0.173 0.015 11.816 0.000
farmi_6 -> FARMI -0.163*** -0.164 0.015 -10.763 0.000
farmi_7 -> FARMI 0.206*** 0.205 0.017 11.859 0.000
farmi_8 -> FARMI 0.148*** 0.148 0.014 10.712 0.000
farmi_9 -> FARMI 0.147*** 0.146 0.014 10.687 0.000
foodenv_1 -> FOODENV 0.926*** 0.926 0.009 103.525 0.000
foodenv_2 -> FOODENV -0.071*** -0.070 0.016 -4.478 0.000
foodenv_3 -> FOODENV -0.072*** -0.072 0.014 -5.291 0.000
foodenv_4 -> FOODENV 0.162*** 0.162 0.016 9.911 0.000
foodsec -> FOODSEC 1 1.000 0.000 NA NA
infra_1 -> INFRA 0.207*** 0.205 0.027 7.778 0.000
infra_2 -> INFRA 0.206*** 0.204 0.024 8.690 0.000
infra_3 -> INFRA 0.294*** 0.295 0.025 11.857 0.000
infra_4 -> INFRA 0.695*** 0.695 0.020 35.231 0.000
infra_5 -> INFRA 0.087*** 0.088 0.024 3.603 0.000
nutri_1 -> NUTRI -0.214*** -0.215 0.047 -4.537 0.000
nutri_2 -> NUTRI -1.027*** -1.026 0.045 -22.654 0.000
nutri_3 -> NUTRI -0.228** -0.228 0.090 -2.535 0.011
nutri_4 -> NUTRI 0.584*** 0.583 0.069 8.431 0.000
nutri_5 -> NUTRI 1.543*** 1.542 0.080 19.278 0.000
print_boot_tbl(boot_results_full_loadings,
               paste0("<b><span style='color:black'>Bootstrapped Indicator Loadings (Full Model)</span></b><br>",
    "<span style='font-size:90%;color:gray'>Significance levels are denoted as follows: ***p < 0.01, **p < 0.05, *p < 0.10.</span>"))
Bootstrapped Indicator Loadings (Full Model)
Significance levels are denoted as follows: p < 0.01, p < 0.05, p < 0.10.
Indicator Original Est. Bootstrap Mean Bootstrap SD T Stat. p-value
Main PLS-SEM
clima -> CLIMA 1 1.000 0.000 NA NA
ecosy_1 -> ECOSY 0.952*** 0.952 0.008 126.253 0.000
ecosy_2 -> ECOSY 0.951*** 0.951 0.007 128.326 0.000
ecosy_3 -> ECOSY -0.02 -0.021 0.024 -0.832 0.406
envir_1 -> ENVIR 0.924*** 0.826 0.238 3.883 0.000
envir_2 -> ENVIR 0.719** 0.632 0.318 2.259 0.024
farmi_1 -> FARMI 0.78*** 0.779 0.011 71.782 0.000
farmi_2 -> FARMI -0.657*** -0.657 0.011 -58.671 0.000
farmi_3 -> FARMI 0.358*** 0.358 0.016 22.767 0.000
farmi_4 -> FARMI 0.382*** 0.382 0.016 23.397 0.000
farmi_5 -> FARMI 0.503*** 0.503 0.015 33.779 0.000
farmi_6 -> FARMI -0.362*** -0.362 0.020 -18.318 0.000
farmi_7 -> FARMI 0.592*** 0.592 0.015 40.776 0.000
farmi_8 -> FARMI 0.327*** 0.327 0.018 18.049 0.000
farmi_9 -> FARMI 0.388*** 0.387 0.017 23.524 0.000
foodenv_1 -> FOODENV 0.984*** 0.984 0.003 376.503 0.000
foodenv_2 -> FOODENV -0.029 -0.029 0.021 -1.404 0.160
foodenv_3 -> FOODENV 0.087*** 0.085 0.020 4.375 0.000
foodenv_4 -> FOODENV 0.526*** 0.526 0.016 33.308 0.000
foodsec -> FOODSEC 1 1.000 0.000 NA NA
infra_1 -> INFRA 0.557*** 0.555 0.021 26.816 0.000
infra_2 -> INFRA 0.5*** 0.499 0.020 25.205 0.000
infra_3 -> INFRA 0.583*** 0.583 0.018 31.794 0.000
infra_4 -> INFRA 0.851*** 0.851 0.012 70.831 0.000
infra_5 -> INFRA 0.279*** 0.279 0.023 12.234 0.000
nutri_1 -> NUTRI 0.557*** 0.556 0.022 25.513 0.000
nutri_2 -> NUTRI 0.309*** 0.308 0.026 12.112 0.000
nutri_3 -> NUTRI 0.794*** 0.793 0.016 50.399 0.000
nutri_4 -> NUTRI 0.793*** 0.791 0.016 49.509 0.000
nutri_5 -> NUTRI 0.741*** 0.739 0.017 43.206 0.000
Scenario A
clima -> CLIMA 1 1.000 0.000 NA NA
ecosy_1 -> ECOSY 0.95*** 0.949 0.009 110.296 0.000
ecosy_2 -> ECOSY 0.954*** 0.953 0.007 138.121 0.000
ecosy_3 -> ECOSY -0.016 -0.017 0.024 -0.661 0.509
envir_1 -> ENVIR 0.908*** 0.806 0.252 3.600 0.000
envir_2 -> ENVIR 0.746** 0.640 0.334 2.234 0.025
farmi_1 -> FARMI 0.816*** 0.816 0.009 87.485 0.000
farmi_2 -> FARMI -0.652*** -0.652 0.011 -58.663 0.000
farmi_3 -> FARMI 0.356*** 0.357 0.016 22.880 0.000
farmi_4 -> FARMI 0.377*** 0.376 0.016 23.273 0.000
farmi_5 -> FARMI 0.499*** 0.499 0.015 33.758 0.000
farmi_6 -> FARMI -0.361*** -0.361 0.019 -18.528 0.000
farmi_7 -> FARMI 0.585*** 0.584 0.014 40.425 0.000
farmi_8 -> FARMI 0.324*** 0.325 0.018 18.149 0.000
farmi_9 -> FARMI 0.383*** 0.382 0.016 23.493 0.000
foodenv_1 -> FOODENV 0.983*** 0.983 0.003 368.997 0.000
foodenv_2 -> FOODENV -0.028 -0.027 0.020 -1.357 0.175
foodenv_3 -> FOODENV 0.01 0.008 0.019 0.494 0.621
foodenv_4 -> FOODENV 0.524*** 0.524 0.016 33.263 0.000
foodsec -> FOODSEC 1 1.000 0.000 NA NA
infra_1 -> INFRA 0.556*** 0.554 0.021 27.004 0.000
infra_2 -> INFRA 0.503*** 0.501 0.020 25.512 0.000
infra_3 -> INFRA 0.584*** 0.584 0.018 31.808 0.000
infra_4 -> INFRA 0.85*** 0.850 0.012 70.801 0.000
infra_5 -> INFRA 0.277*** 0.277 0.023 12.108 0.000
nutri_1 -> NUTRI 0.557*** 0.556 0.022 25.547 0.000
nutri_2 -> NUTRI 0.308*** 0.307 0.026 12.056 0.000
nutri_3 -> NUTRI 0.793*** 0.792 0.016 50.223 0.000
nutri_4 -> NUTRI 0.791*** 0.790 0.016 49.399 0.000
nutri_5 -> NUTRI 0.74*** 0.739 0.017 43.178 0.000
Scenario B
clima -> CLIMA 1 1.000 0.000 NA NA
ecosy_1 -> ECOSY 0.948*** 0.947 0.010 96.401 0.000
ecosy_2 -> ECOSY 0.948*** 0.947 0.008 114.751 0.000
ecosy_3 -> ECOSY -0.029 -0.028 0.026 -1.082 0.279
envir_1 -> ENVIR 0.784** 0.697 0.304 2.581 0.010
envir_2 -> ENVIR 0.879** 0.666 0.409 2.149 0.032
farmi_1 -> FARMI 0.826*** 0.825 0.010 81.998 0.000
farmi_2 -> FARMI -0.666*** -0.666 0.012 -55.534 0.000
farmi_3 -> FARMI 0.394*** 0.394 0.017 23.324 0.000
farmi_4 -> FARMI 0.312*** 0.312 0.018 16.903 0.000
farmi_5 -> FARMI 0.524*** 0.524 0.016 33.426 0.000
farmi_6 -> FARMI -0.403*** -0.403 0.021 -19.239 0.000
farmi_7 -> FARMI 0.564*** 0.563 0.016 35.064 0.000
farmi_8 -> FARMI 0.317*** 0.318 0.020 15.912 0.000
farmi_9 -> FARMI 0.347*** 0.347 0.019 18.613 0.000
foodenv_1 -> FOODENV 0.984*** 0.983 0.003 352.374 0.000
foodenv_2 -> FOODENV -0.017 -0.016 0.022 -0.777 0.437
foodenv_3 -> FOODENV -0.072*** -0.072 0.020 -3.562 0.000
foodenv_4 -> FOODENV 0.509*** 0.509 0.018 28.967 0.000
foodsec -> FOODSEC 1 1.000 0.000 NA NA
infra_1 -> INFRA 0.525*** 0.522 0.024 22.265 0.000
infra_2 -> INFRA 0.523*** 0.522 0.022 24.214 0.000
infra_3 -> INFRA 0.563*** 0.562 0.022 25.897 0.000
infra_4 -> INFRA 0.857*** 0.857 0.013 63.722 0.000
infra_5 -> INFRA 0.259*** 0.259 0.027 9.579 0.000
nutri_1 -> NUTRI 0.564*** 0.563 0.023 24.153 0.000
nutri_2 -> NUTRI 0.311*** 0.311 0.027 11.520 0.000
nutri_3 -> NUTRI 0.792*** 0.791 0.017 47.915 0.000
nutri_4 -> NUTRI 0.789*** 0.788 0.017 46.016 0.000
nutri_5 -> NUTRI 0.751*** 0.750 0.018 42.283 0.000

Consistency of Indicator Signs (Formative Weights). The signs of formative weights are not validity criteria, but provide a useful theory check. The assessment by construct shows the following patterns:

indicator_summary <- tribble(
  ~Construct, ~Indicator, ~"Theoretical relation", ~"Empirical consistency",
  "ECOSY", "ecosy_1 (ig51)", "Direct", "ok",
  "ECOSY", "ecosy_2 (ig53)", "Direct", "ok",
  "ECOSY", "ecosy_3 (ii1)", "Direct", "ok",
  "ENVIR", "envir_1 (w1)", "Direct", "ok",
  "ENVIR", "envir_2 (w2)", "Direct", "ok",
  "FARMI", "farmi_1 (ig52)", "Direct", "ok",
  "FARMI", "farmi_2 (n1)", "Direct", "contrary",
  "FARMI", "farmi_3 (n2)", "Inverse", "ok",
  "FARMI", "farmi_4 (pa1)", "Direct", "mix, insignificant",
  "FARMI", "farmi_5 (pa3)", "Inverse", "contrary",
  "FARMI", "farmi_6 (r2)", "Direct", "contrary",
  "FARMI", "farmi_7 (v3)", "Inverse", "contrary",
  "FARMI", "farmi_8 (v4)", "Direct", "ok",
  "FARMI", "farmi_9 (c2)", "Direct", "ok",
  "FOODENV", "foodenv_1 (b3)", "Inverse", "contrary",
  "FOODENV", "foodenv_2 (b4)", "Direct", "contrary",
  "FOODENV", "foodenv_3 (b5)", "Direct", "contrary",
  "FOODENV", "foodenv_4 (ii3)", "Direct", "ok",
  "INFRA", "infra_1 (ig8)", "Direct", "ok",
  "INFRA", "infra_2 (ii5)", "Direct", "ok",
  "INFRA", "infra_3 (ii6)", "Direct", "ok",
  "INFRA", "infra_4 (ii10)", "Direct", "ok",
  "INFRA", "infra_5 (d1)", "Direct", "ok",
  "NUTRI", "nutri_1 (f1)", "Direct", "contrary",
  "NUTRI", "nutri_2 (f2)", "Direct", "contrary",
  "NUTRI", "nutri_3 (f3)", "Direct", "contrary",
  "NUTRI", "nutri_4 (h1)", "Direct", "ok",
  "NUTRI", "nutri_5 (h2)", "Direct", "ok"
)

scen_cols <- setdiff(names(indicator_summary), c("Construct","Indicator"))

kable_group_by_construct(
  indicator_summary,
 "<b><span style='color:black'>Consistency between theoretical and empirical directions of formative indicator weights</span></b>",
 scen_cols
 )
Consistency between theoretical and empirical directions of formative indicator weights
Scenarios
Indicator Theoretical relation Empirical consistency
ECOSY
ecosy_1 (ig51) Direct ok
ecosy_2 (ig53) Direct ok
ecosy_3 (ii1) Direct ok
ENVIR
envir_1 (w1) Direct ok
envir_2 (w2) Direct ok
FARMI
farmi_1 (ig52) Direct ok
farmi_2 (n1) Direct contrary
farmi_3 (n2) Inverse ok
farmi_4 (pa1) Direct mix, insignificant
farmi_5 (pa3) Inverse contrary
farmi_6 (r2) Direct contrary
farmi_7 (v3) Inverse contrary
farmi_8 (v4) Direct ok
farmi_9 (c2) Direct ok
FOODENV
foodenv_1 (b3) Inverse contrary
foodenv_2 (b4) Direct contrary
foodenv_3 (b5) Direct contrary
foodenv_4 (ii3) Direct ok
INFRA
infra_1 (ig8) Direct ok
infra_2 (ii5) Direct ok
infra_3 (ii6) Direct ok
infra_4 (ii10) Direct ok
infra_5 (d1) Direct ok
NUTRI
nutri_1 (f1) Direct contrary
nutri_2 (f2) Direct contrary
nutri_3 (f3) Direct contrary
nutri_4 (h1) Direct ok
nutri_5 (h2) Direct ok

Key Points (Measurement Evaluation). The measurement model evaluation identified critical collinearity only for nutri_3 (f3) and nutri_5 (h2), which were excluded in the refined specification. All remaining indicators showed acceptable VIF values, and bootstrapped weights and loadings confirmed their statistical robustness across scenarios. Although ecosy_3 (ii1) displayed negligible empirical contribution (non-significant weight and loading), it was retained in both models to preserve content validity, as it represents a distinct dimension of ecosystem conservation.

6 Structural Model Evaluation

Structural relations were assessed under standard PLS-SEM guidelines, covering collinearity among antecedent constructs, significance and substanteive relevance of path coefficients, explanatory power (\(R^2\), \(f^2\)), and predictive validity (PLSpredict). Results are reported for the Refined Model, which excludes indicators with critical multicollinearity, and the Full Model, which retains all theoretically motivated indicators. For both specifications, estimates are shown for the main dataset and two robustness scenarios (Scenario A: winsorization; Scenario B: listwise deletion).

make_struct_vif <- function(s_list, caption_suffix){
  vif_all <- imap_dfr(s_list,
    function(vif_list, sc_name){
      imap_dfr(vif_list, function(vec, cn){
        tibble(
          Scenario = sc_name,
          Construct = cn,
          Predictor = names(vec),
          VIF = as.numeric(vec))})
    }) %>%
    filter(!is.na(VIF), is.finite(VIF)) %>%
    mutate(Scenario = factor(Scenario,levels = c("Main PLS-SEM","Scenario A","Scenario B"))) %>%
    arrange(Construct, Predictor, Scenario) %>%
    select(Construct, Predictor, Scenario, VIF) %>%
    pivot_wider(names_from = Scenario, values_from = VIF) %>%
    arrange(Construct, Predictor)
  scen_cols <- setdiff(names(vif_all), c("Construct","Predictor"))
  fmt <- vif_all
  fmt[scen_cols] <- lapply(fmt[scen_cols], format_vif_cells)
  idx <- fmt %>%
    mutate(row_id = row_number()) %>%
    group_by(Construct) %>%
    summarise(start = min(row_id), end = max(row_id), .groups = "drop")
  kb <- kable(fmt %>% select(-Construct),
    format = "html",
    escape = FALSE,
    align = c("l", rep("c", length(scen_cols))),
    caption = paste0(
      "<b><span style='color:black'>VIF - Structural Model Predictors (",
      caption_suffix, ")</span></b>"),
    col.names = c("Predictor", scen_cols)) %>%
    kable_styling(
      bootstrap_options = c("striped","hover","condensed"),
      full_width = FALSE,
      position = "center",
      fixed_thead = TRUE) %>%
    add_header_above(c(" " = 1, "Scenarios" = length(scen_cols)))
  if (nrow(idx) > 0){
    for (i in seq_len(nrow(idx))){
      kb <- kb %>% group_rows(idx$Construct[i], idx$start[i], idx$end[i])
    }
  }
  kb
}

print_paths_tbl <- function(df_list, caption){
  boot_results_all <- map_dfr(names(df_list), ~{
    add_pval(df_list[[.x]]$df, df_list[[.x]]$data) %>%
      rownames_to_column("Path") %>%
      mutate(Panel = .x)
  })
  idx <- boot_results_all %>% mutate(row_id = row_number()) %>%
    group_by(Panel) %>% summarise(start = min(row_id), end = max(row_id), .groups = "drop")

  boot_results_all <- boot_results_all %>%
    mutate(signif = case_when(`p-value` < 0.01 ~ "<sup>***</sup>",
                              `p-value` < 0.05 ~ "<sup>**</sup>",
                              `p-value` < 0.10 ~ "<sup>*</sup>",
                              TRUE ~ ""),
           `Original Est.` = paste0(round(`Original Est.`, 3), signif))

  kb <- kable(boot_results_all %>% select(-c(Panel, signif)),
              caption = caption, digits = 3, format = "html",
              escape = FALSE, booktabs = TRUE,
              align = c("l", rep("c", 5))) %>%
    kable_styling(bootstrap_options = c("striped","hover","condensed"),
                  full_width = FALSE, position = "center", fixed_thead = TRUE)
  if (nrow(idx) > 0) {
    for (i in seq_len(nrow(idx))) {
      kb <- kb %>% group_rows(idx$Panel[i], idx$start[i], idx$end[i], bold = TRUE)
    }
  }
  kb
}

Collinearity (antecedent). Variance inflation factors (VIF) were computed for all predictors of each andogenous construct. All values remaind below 3, indicating absence of critical collinearity.

make_struct_vif(
  list(
    "Main PLS-SEM" = s_main_iter2$vif_antecedents,
    "Scenario A"   = s_A_iter2$vif_antecedents,
    "Scenario B"   = s_B_iter2$vif_antecedents
  ),
  "Refined Model"
)
VIF - Structural Model Predictors (Refined Model)
Scenarios
Predictor Main PLS-SEM Scenario A Scenario B
CLIMA
ECOSY 1.904 1.942 1.978
FARMI 2.322 2.400 2.449
FOODENV 1.534 1.554 1.596
ECOSY
ENVIR 1.003 1.003 1.005
INFRA 1.003 1.003 1.005
FARMI
ECOSY 1.181 1.182 1.152
ENVIR 1.004 1.004 1.007
INFRA 1.185 1.185 1.158
FOODENV
ENVIR 1.003 1.003 1.006
FARMI 1.540 1.540 1.439
INFRA 1.544 1.544 1.446
FOODSEC
FARMI 1.525 1.548 1.587
FOODENV 1.525 1.548 1.587
NUTRI
FARMI 1.525 1.548 1.587
FOODENV 1.525 1.548 1.587
make_struct_vif(
  list(
    "Main PLS-SEM" = s_main$vif_antecedents,
    "Scenario A"   = s_A$vif_antecedents,
    "Scenario B"   = s_B$vif_antecedents
  ),
  "Full Model"
)
VIF - Structural Model Predictors (Full Model)
Scenarios
Predictor Main PLS-SEM Scenario A Scenario B
CLIMA
ECOSY 1.886 1.924 1.960
FARMI 2.307 2.385 2.432
FOODENV 1.540 1.560 1.601
ECOSY
ENVIR 1.003 1.003 1.005
INFRA 1.003 1.003 1.005
FARMI
ECOSY 1.182 1.182 1.153
ENVIR 1.004 1.004 1.007
INFRA 1.185 1.186 1.159
FOODENV
ENVIR 1.003 1.003 1.006
FARMI 1.537 1.537 1.440
INFRA 1.541 1.541 1.446
FOODSEC
FARMI 1.531 1.554 1.591
FOODENV 1.531 1.554 1.591
NUTRI
FARMI 1.531 1.554 1.591
FOODENV 1.531 1.554 1.591

Path coefficients. Bootstrapping (6,000 resamples) shows that several hypothesized relations are robust and substantively meaningful (\(|\beta| \geq 0.20\)), while smaller but significant effects are interpreted with caution.

In sum, the model is well supported for the main conduits (INFRA; ECOSY -> FARMI -> FOODENV -> FOODSEC) while systematic deviations in NUTRI and CLIMA parallel the formative indicator reversals, pointing to empirical trade-offs that deserve further exploration.

The table below summarize these analyses, with full results reported subsequently.

path_summary <- tribble(
  ~Path, ~Summary,
  "ENVIR -> INFRA",   "Small but significant negative effect (contrary to theory).",
  "ENVIR -> ECOSY",   "Small positive, consistent with theory.",
  "ENVIR -> FARMI",   "Non-significant, contrary to theory.",
  "ENVIR -> FOODENV", "Small positive, consistent with theory.",
  "INFRA -> ECOSY",   "Strong positive, robust across scenarios.",
  "INFRA -> FARMI",   "Strong positive, robust across scenarios.",
  "INFRA -> FOODENV", "Moderate positive, robust across scenarios.",
  "ECOSY -> FARMI",   "Strongest effect, robust.",
  "ECOSY -> CLIMA",   "Inconsistent, mix of null/small negative.",
  "FARMI -> FOODENV", "Strong positive, robust across scenarios.",
  "FARMI -> CLIMA",   "Small negative, contrary to theory.",
  "FARMI -> NUTRI",   "Small-to-moderate negative, contrary to theory.",
  "FARMI -> FOODSEC", "Positive and robust across scenarios.",
  "FOODENV -> CLIMA", "Mixed, weak and unstable across scenarios.",
  "FOODENV -> NUTRI", "Consistently negative, contrary to theory.",
  "FOODENV -> FOODSEC", "Strong positive, robust across scenarios."
)

kable(path_summary,
      format = "html",
      caption = "<b><span style='color:black'>Summary of Path Coefficient Results (Refined and Full Models)</span></b>",
      escape = FALSE, booktabs = TRUE,
      col.names = c("Path (theory: positive)", "Summary of empirical results")) %>%
  kable_styling(bootstrap_options = c("striped","hover","condensed"),
                full_width = FALSE, position = "center", fixed_thead = TRUE)
Summary of Path Coefficient Results (Refined and Full Models)
Path (theory: positive) Summary of empirical results
ENVIR -> INFRA Small but significant negative effect (contrary to theory).
ENVIR -> ECOSY Small positive, consistent with theory.
ENVIR -> FARMI Non-significant, contrary to theory.
ENVIR -> FOODENV Small positive, consistent with theory.
INFRA -> ECOSY Strong positive, robust across scenarios.
INFRA -> FARMI Strong positive, robust across scenarios.
INFRA -> FOODENV Moderate positive, robust across scenarios.
ECOSY -> FARMI Strongest effect, robust.
ECOSY -> CLIMA Inconsistent, mix of null/small negative.
FARMI -> FOODENV Strong positive, robust across scenarios.
FARMI -> CLIMA Small negative, contrary to theory.
FARMI -> NUTRI Small-to-moderate negative, contrary to theory.
FARMI -> FOODSEC Positive and robust across scenarios.
FOODENV -> CLIMA Mixed, weak and unstable across scenarios.
FOODENV -> NUTRI Consistently negative, contrary to theory.
FOODENV -> FOODSEC Strong positive, robust across scenarios.
print_paths_tbl(
  list(
    "Main PLS-SEM" = list(
      df   = s_boot_refined[["Main PLS-SEM"]]$bootstrapped_paths,
      data = data_main
    ),
    "Scenario A (Winsorization)" = list(
      df   = s_boot_refined[["Scenario A"]]$bootstrapped_paths,
      data = data_winsorize
    ),
    "Scenario B (Deletion)" = list(
      df   = s_boot_refined[["Scenario B"]]$bootstrapped_paths,
      data = data_deletion
    )
  ),
  caption = "<b><span style='color:black'>Bootstrapped Path Coefficients - Refined Model</span></b>"
)
Bootstrapped Path Coefficients - Refined Model
Path Original Est. Bootstrap Mean Bootstrap SD T Stat. p-value
Main PLS-SEM
ENVIR -> INFRA -0.057*** -0.054 0.022 -2.640 0.008
ENVIR -> ECOSY 0.029** 0.026 0.014 2.045 0.041
ENVIR -> FARMI -0.013 -0.012 0.011 -1.180 0.238
ENVIR -> FOODENV 0.065*** 0.060 0.021 3.143 0.002
INFRA -> ECOSY 0.392*** 0.393 0.012 33.354 0.000
INFRA -> FARMI 0.381*** 0.381 0.015 25.665 0.000
INFRA -> FOODENV 0.247*** 0.246 0.015 16.536 0.000
ECOSY -> FARMI 0.538*** 0.538 0.014 37.126 0.000
ECOSY -> CLIMA 0 0.000 0.030 -0.011 0.991
FARMI -> FOODENV 0.443*** 0.444 0.014 32.101 0.000
FARMI -> CLIMA -0.085*** -0.087 0.023 -3.783 0.000
FARMI -> NUTRI -0.097*** -0.097 0.017 -5.854 0.000
FARMI -> FOODSEC 0.406*** 0.406 0.011 37.501 0.000
FOODENV -> CLIMA -0.033* -0.033 0.017 -1.941 0.052
FOODENV -> NUTRI -0.351*** -0.351 0.015 -22.853 0.000
FOODENV -> FOODSEC 0.482*** 0.482 0.011 44.701 0.000
Scenario A (Winsorization)
ENVIR -> INFRA -0.058** -0.054 0.022 -2.590 0.010
ENVIR -> ECOSY 0.029** 0.026 0.015 2.015 0.044
ENVIR -> FARMI -0.01 -0.009 0.012 -0.898 0.369
ENVIR -> FOODENV 0.06*** 0.054 0.020 2.908 0.004
INFRA -> ECOSY 0.393*** 0.393 0.012 33.417 0.000
INFRA -> FARMI 0.377*** 0.377 0.015 25.716 0.000
INFRA -> FOODENV 0.24*** 0.239 0.015 16.137 0.000
ECOSY -> FARMI 0.548*** 0.547 0.014 38.835 0.000
ECOSY -> CLIMA 0.013 0.013 0.032 0.397 0.691
FARMI -> FOODENV 0.455*** 0.456 0.014 33.335 0.000
FARMI -> CLIMA -0.113*** -0.114 0.025 -4.513 0.000
FARMI -> NUTRI -0.102*** -0.103 0.017 -6.189 0.000
FARMI -> FOODSEC 0.408*** 0.408 0.011 37.733 0.000
FOODENV -> CLIMA -0.019 -0.018 0.016 -1.195 0.232
FOODENV -> NUTRI -0.345*** -0.344 0.015 -22.234 0.000
FOODENV -> FOODSEC 0.479*** 0.479 0.011 44.145 0.000
Scenario B (Deletion)
ENVIR -> INFRA -0.073** -0.065 0.028 -2.579 0.010
ENVIR -> ECOSY 0.04* 0.036 0.022 1.854 0.064
ENVIR -> FARMI -0.013 -0.011 0.012 -1.065 0.287
ENVIR -> FOODENV 0.032 0.032 0.025 1.273 0.203
INFRA -> ECOSY 0.364*** 0.365 0.013 27.703 0.000
INFRA -> FARMI 0.343*** 0.342 0.016 21.066 0.000
INFRA -> FOODENV 0.234*** 0.234 0.015 15.144 0.000
ECOSY -> FARMI 0.578*** 0.577 0.015 37.708 0.000
ECOSY -> CLIMA -0.058** -0.058 0.023 -2.583 0.010
FARMI -> FOODENV 0.48*** 0.480 0.014 34.098 0.000
FARMI -> CLIMA -0.126*** -0.128 0.026 -4.897 0.000
FARMI -> NUTRI -0.126*** -0.126 0.018 -6.958 0.000
FARMI -> FOODSEC 0.405*** 0.405 0.012 33.749 0.000
FOODENV -> CLIMA 0.048*** 0.048 0.015 3.105 0.002
FOODENV -> NUTRI -0.329*** -0.329 0.018 -18.744 0.000
FOODENV -> FOODSEC 0.473*** 0.473 0.012 39.092 0.000
print_paths_tbl(
  list(
    "Main PLS-SEM" = list(
      df   = s_boot_full[["Main PLS-SEM"]]$bootstrapped_paths,
      data = data_main
    ),
    "Scenario A (Winsorization)" = list(
      df   = s_boot_full[["Scenario A"]]$bootstrapped_paths,
      data = data_winsorize
    ),
    "Scenario B (Deletion)" = list(
      df   = s_boot_full[["Scenario B"]]$bootstrapped_paths,
      data = data_deletion
    )
  ),
  caption = "<b><span style='color:black'>Bootstrapped Path Coefficients - Full Model</span></b>"
)
Bootstrapped Path Coefficients - Full Model
Path Original Est. Bootstrap Mean Bootstrap SD T Stat. p-value
Main PLS-SEM
ENVIR -> INFRA -0.057** -0.053 0.022 -2.573 0.010
ENVIR -> ECOSY 0.029** 0.026 0.014 2.025 0.043
ENVIR -> FARMI -0.013 -0.012 0.012 -1.140 0.254
ENVIR -> FOODENV 0.064*** 0.058 0.022 2.978 0.003
INFRA -> ECOSY 0.393*** 0.393 0.012 33.367 0.000
INFRA -> FARMI 0.382*** 0.382 0.015 25.914 0.000
INFRA -> FOODENV 0.24*** 0.239 0.015 16.061 0.000
ECOSY -> FARMI 0.534*** 0.534 0.014 37.114 0.000
ECOSY -> CLIMA 0.002 0.002 0.030 0.053 0.958
FARMI -> FOODENV 0.449*** 0.450 0.014 32.784 0.000
FARMI -> CLIMA -0.088*** -0.089 0.022 -3.907 0.000
FARMI -> NUTRI -0.171*** -0.172 0.016 -10.999 0.000
FARMI -> FOODSEC 0.407*** 0.406 0.011 37.481 0.000
FOODENV -> CLIMA -0.034** -0.034 0.017 -2.030 0.042
FOODENV -> NUTRI -0.392*** -0.392 0.014 -27.467 0.000
FOODENV -> FOODSEC 0.481*** 0.481 0.011 44.372 0.000
Scenario A (Winsorization)
ENVIR -> INFRA -0.058** -0.053 0.023 -2.547 0.011
ENVIR -> ECOSY 0.029** 0.026 0.015 1.991 0.046
ENVIR -> FARMI -0.01 -0.009 0.012 -0.884 0.377
ENVIR -> FOODENV 0.058*** 0.053 0.021 2.785 0.005
INFRA -> ECOSY 0.393*** 0.393 0.012 33.429 0.000
INFRA -> FARMI 0.378*** 0.378 0.015 25.937 0.000
INFRA -> FOODENV 0.233*** 0.232 0.015 15.647 0.000
ECOSY -> FARMI 0.544*** 0.543 0.014 38.704 0.000
ECOSY -> CLIMA 0.014 0.014 0.032 0.441 0.659
FARMI -> FOODENV 0.461*** 0.462 0.014 34.012 0.000
FARMI -> CLIMA -0.114*** -0.116 0.025 -4.590 0.000
FARMI -> NUTRI -0.174*** -0.175 0.015 -11.329 0.000
FARMI -> FOODSEC 0.409*** 0.409 0.011 37.717 0.000
FOODENV -> CLIMA -0.02 -0.020 0.015 -1.327 0.185
FOODENV -> NUTRI -0.388*** -0.389 0.014 -27.102 0.000
FOODENV -> FOODSEC 0.478*** 0.477 0.011 43.801 0.000
Scenario B (Deletion)
ENVIR -> INFRA -0.073** -0.064 0.029 -2.531 0.011
ENVIR -> ECOSY 0.04* 0.036 0.022 1.845 0.065
ENVIR -> FARMI -0.013 -0.010 0.012 -1.047 0.295
ENVIR -> FOODENV 0.032 0.032 0.026 1.250 0.211
INFRA -> ECOSY 0.365*** 0.365 0.013 27.707 0.000
INFRA -> FARMI 0.344*** 0.344 0.016 21.276 0.000
INFRA -> FOODENV 0.228*** 0.227 0.016 14.668 0.000
ECOSY -> FARMI 0.573*** 0.573 0.015 37.531 0.000
ECOSY -> CLIMA -0.058** -0.057 0.023 -2.561 0.010
FARMI -> FOODENV 0.485*** 0.485 0.014 34.611 0.000
FARMI -> CLIMA -0.124*** -0.126 0.026 -4.878 0.000
FARMI -> NUTRI -0.196*** -0.197 0.017 -11.859 0.000
FARMI -> FOODSEC 0.406*** 0.406 0.012 33.920 0.000
FOODENV -> CLIMA 0.043*** 0.043 0.015 2.797 0.005
FOODENV -> NUTRI -0.376*** -0.377 0.016 -23.621 0.000
FOODENV -> FOODSEC 0.472*** 0.472 0.012 38.919 0.000
plot(model_pls_iter2[["Main"]], title = "Structural model diagram - Main PLS-SEM (Refined Model)")
plot(model_pls_iter2[["ScenarioA"]],    title = "Structural model diagram - Scenario A (Refined Model)")
plot(model_pls_iter2[["ScenarioB"]],    title = "Structural model diagram - Scenario B (Refined Model)")
plot(model_pls_main, title = "Structural model diagram - Main PLS-SEM (Full Model)")
plot(model_pls_scenarioA,    title = "Structural model diagram - Scenario A (Full Model)")
plot(model_pls_scenarioB,    title = "Structural model diagram - Scenario B (Full Model)")

Total effects. Because indirect paths can attenuate, amplify, or flip the sign of direct effects, total effects (direct + indirect) provide the relevant substative interpretation of how upstream constructs influence downstream outcomes. Bootstrapped estimates (6,000 resemples) for the Refined and Full models indicate that most total effects are highly significant and consistent across robustness scenarios.

In sum, the total effects confirm a coherent transmission chain (INFRA -> ECOSY -> FARMI -> FOODENV -> FOODSEC) that reproduces the hypothesized systemic pathways. Deviations for CLIMA and NUTRI remain modest and theoretically interpretable as structural trade-offs rather than model misspecification.

total_summary <- tribble(
  ~Path, ~Summary,
  "ENVIR -> INFRA",   "Small but significant negative total effect (contrary to theory).",
  "ENVIR -> ECOSY",   "Small positive, consistent with theory but not significant.",
  "ENVIR -> FARMI",   "Non-significant, contrary to theory.",
  "ENVIR -> FOODENV", "Small positive, non-significant; consistent with theory.",
  "ENVIR -> CLIMA",   "Null, consistent with theory.",
  "ENVIR -> NUTRI",   "Non-significant and contrary to theory.",
  "ENVIR -> FOODSEC", "Mixed signs, not significant across scenarios.",
  "INFRA -> ECOSY",   "Strong positive, robust across scenarios.",
  "INFRA -> FARMI",   "Strong positive, robust across scenarios.",
  "INFRA -> FOODENV", "Moderate-to-strong positive, robust across scenarios.",
  "INFRA -> CLIMA",   "Small negative, contrary to theory.",
  "INFRA -> NUTRI",   "Large negative, contrary to theory.",
  "INFRA -> FOODSEC", "Strong positive, robust across scenarios.",
  "ECOSY -> FARMI",   "Strongest positive total effect robust.",
  "ECOSY -> FOODENV", "Moderate positive, robust across scenarios.",
  "ECOSY -> CLIMA",   "Small negative, contrary to theory.",
  "ECOSY -> NUTRI",   "Moderate negative, contrary to theory.",
  "ECOSY -> FOODSEC", "Moderate-to-strong positive, robust.",
  "FARMI -> FOODENV", "Strong positive, robust across scenarios.",
  "FARMI -> CLIMA",   "Small negative, contrary to theory.",
  "FARMI -> NUTRI",   "Small-to-moderate negative contrary to theory.",
  "FARMI -> FOODSEC", "Strong positive, robust across scenarios.",
  "FOODENV -> CLIMA", "Mixed: weak negative to small positive depending on scenario.",
  "FOODENV -> NUTRI", "Consistently negative, contrary to theory.",
  "FOODENV -> FOODSEC", "Strong positive, robust across scenarios."
)

kable(total_summary,
      format = "html",
      caption = "<b><span style='color:black'>Summary of Total Effects (Refined and Full Models)</span></b>",
      escape = FALSE, booktabs = TRUE,
      col.names = c("Path (theory: positive)", "Summary of empirical results")) %>%
  kable_styling(bootstrap_options = c("striped","hover","condensed"),
                full_width = FALSE, position = "center", fixed_thead = TRUE)
Summary of Total Effects (Refined and Full Models)
Path (theory: positive) Summary of empirical results
ENVIR -> INFRA Small but significant negative total effect (contrary to theory).
ENVIR -> ECOSY Small positive, consistent with theory but not significant.
ENVIR -> FARMI Non-significant, contrary to theory.
ENVIR -> FOODENV Small positive, non-significant; consistent with theory.
ENVIR -> CLIMA Null, consistent with theory.
ENVIR -> NUTRI Non-significant and contrary to theory.
ENVIR -> FOODSEC Mixed signs, not significant across scenarios.
INFRA -> ECOSY Strong positive, robust across scenarios.
INFRA -> FARMI Strong positive, robust across scenarios.
INFRA -> FOODENV Moderate-to-strong positive, robust across scenarios.
INFRA -> CLIMA Small negative, contrary to theory.
INFRA -> NUTRI Large negative, contrary to theory.
INFRA -> FOODSEC Strong positive, robust across scenarios.
ECOSY -> FARMI Strongest positive total effect robust.
ECOSY -> FOODENV Moderate positive, robust across scenarios.
ECOSY -> CLIMA Small negative, contrary to theory.
ECOSY -> NUTRI Moderate negative, contrary to theory.
ECOSY -> FOODSEC Moderate-to-strong positive, robust.
FARMI -> FOODENV Strong positive, robust across scenarios.
FARMI -> CLIMA Small negative, contrary to theory.
FARMI -> NUTRI Small-to-moderate negative contrary to theory.
FARMI -> FOODSEC Strong positive, robust across scenarios.
FOODENV -> CLIMA Mixed: weak negative to small positive depending on scenario.
FOODENV -> NUTRI Consistently negative, contrary to theory.
FOODENV -> FOODSEC Strong positive, robust across scenarios.
print_paths_tbl( list( "Main PLS-SEM" = list( df = s_boot_refined[["Main PLS-SEM"]]$bootstrapped_total_paths,
      data = data_main
    ),
    "Scenario A (Winsorization)" = list(
      df   = s_boot_refined[["Scenario A"]]$bootstrapped_total_paths, data = data_winsorize ), "Scenario B (Deletion)" = list( df = s_boot_refined[["Scenario B"]]$bootstrapped_total_paths, data = data_deletion ) ), caption = "<b>Bootstrapped Total Effects - Refined Model</b>" )
Bootstrapped Total Effects - Refined Model
Path Original Est. Bootstrap Mean Bootstrap SD T Stat. p-value
Main PLS-SEM
ENVIR -> INFRA -0.057*** -0.054 0.022 -2.640 0.008
ENVIR -> ECOSY 0.007 0.005 0.016 0.418 0.676
ENVIR -> FARMI -0.032 -0.030 0.022 -1.402 0.161
ENVIR -> FOODENV 0.037 0.033 0.028 1.327 0.185
ENVIR -> CLIMA 0.001 0.001 0.003 0.497 0.619
ENVIR -> NUTRI -0.01 -0.010 0.011 -0.888 0.375
ENVIR -> FOODSEC 0.005 0.005 0.021 0.252 0.801
INFRA -> ECOSY 0.392*** 0.393 0.012 33.354 0.000
INFRA -> FARMI 0.592*** 0.592 0.011 55.332 0.000
INFRA -> FOODENV 0.509*** 0.509 0.011 47.082 0.000
INFRA -> CLIMA -0.067*** -0.068 0.012 -5.807 0.000
INFRA -> NUTRI -0.236*** -0.236 0.009 -26.376 0.000
INFRA -> FOODSEC 0.486*** 0.486 0.009 54.405 0.000
ECOSY -> FARMI 0.538*** 0.538 0.014 37.126 0.000
ECOSY -> FOODENV 0.238*** 0.239 0.010 23.805 0.000
ECOSY -> CLIMA -0.054** -0.055 0.024 -2.276 0.023
ECOSY -> NUTRI -0.136*** -0.136 0.009 -15.209 0.000
ECOSY -> FOODSEC 0.333*** 0.333 0.010 34.906 0.000
FARMI -> FOODENV 0.443*** 0.444 0.014 32.101 0.000
FARMI -> CLIMA -0.1*** -0.101 0.023 -4.339 0.000
FARMI -> NUTRI -0.252*** -0.253 0.014 -17.500 0.000
FARMI -> FOODSEC 0.619*** 0.619 0.010 64.044 0.000
FOODENV -> CLIMA -0.033* -0.033 0.017 -1.941 0.052
FOODENV -> NUTRI -0.351*** -0.351 0.015 -22.853 0.000
FOODENV -> FOODSEC 0.482*** 0.482 0.011 44.701 0.000
Scenario A (Winsorization)
ENVIR -> INFRA -0.058** -0.054 0.022 -2.590 0.010
ENVIR -> ECOSY 0.007 0.005 0.017 0.402 0.688
ENVIR -> FARMI -0.029 -0.027 0.023 -1.232 0.218
ENVIR -> FOODENV 0.033 0.029 0.028 1.150 0.250
ENVIR -> CLIMA 0.003 0.002 0.003 0.866 0.386
ENVIR -> NUTRI -0.008 -0.008 0.012 -0.722 0.470
ENVIR -> FOODSEC 0.004 0.004 0.021 0.187 0.851
INFRA -> ECOSY 0.393*** 0.393 0.012 33.417 0.000
INFRA -> FARMI 0.592*** 0.592 0.011 56.224 0.000
INFRA -> FOODENV 0.509*** 0.509 0.011 47.163 0.000
INFRA -> CLIMA -0.071*** -0.072 0.011 -6.417 0.000
INFRA -> NUTRI -0.236*** -0.236 0.009 -26.635 0.000
INFRA -> FOODSEC 0.485*** 0.485 0.009 54.690 0.000
ECOSY -> FARMI 0.548*** 0.547 0.014 38.835 0.000
ECOSY -> FOODENV 0.249*** 0.249 0.010 24.708 0.000
ECOSY -> CLIMA -0.054** -0.054 0.024 -2.221 0.026
ECOSY -> NUTRI -0.142*** -0.142 0.009 -15.841 0.000
ECOSY -> FOODSEC 0.343*** 0.343 0.009 36.142 0.000
FARMI -> FOODENV 0.455*** 0.456 0.014 33.335 0.000
FARMI -> CLIMA -0.121*** -0.123 0.025 -4.791 0.000
FARMI -> NUTRI -0.259*** -0.260 0.014 -18.269 0.000
FARMI -> FOODSEC 0.626*** 0.626 0.010 65.787 0.000
FOODENV -> CLIMA -0.019 -0.018 0.016 -1.195 0.232
FOODENV -> NUTRI -0.345*** -0.344 0.015 -22.234 0.000
FOODENV -> FOODSEC 0.479*** 0.479 0.011 44.145 0.000
Scenario B (Deletion)
ENVIR -> INFRA -0.073** -0.065 0.028 -2.579 0.010
ENVIR -> ECOSY 0.013 0.012 0.023 0.581 0.562
ENVIR -> FARMI -0.03 -0.026 0.027 -1.118 0.264
ENVIR -> FOODENV 0.001 0.004 0.037 0.018 0.986
ENVIR -> CLIMA 0.003 0.003 0.003 0.916 0.360
ENVIR -> NUTRI 0.004 0.002 0.015 0.238 0.812
ENVIR -> FOODSEC -0.012 -0.009 0.027 -0.438 0.661
INFRA -> ECOSY 0.364*** 0.365 0.013 27.703 0.000
INFRA -> FARMI 0.553*** 0.553 0.012 44.925 0.000
INFRA -> FOODENV 0.5*** 0.500 0.012 41.381 0.000
INFRA -> CLIMA -0.067*** -0.068 0.012 -5.629 0.000
INFRA -> NUTRI -0.234*** -0.234 0.009 -25.549 0.000
INFRA -> FOODSEC 0.46*** 0.460 0.010 45.497 0.000
ECOSY -> FARMI 0.578*** 0.577 0.015 37.708 0.000
ECOSY -> FOODENV 0.277*** 0.277 0.011 24.363 0.000
ECOSY -> CLIMA -0.118*** -0.119 0.019 -6.115 0.000
ECOSY -> NUTRI -0.164*** -0.164 0.010 -16.344 0.000
ECOSY -> FOODSEC 0.365*** 0.365 0.010 34.972 0.000
FARMI -> FOODENV 0.48*** 0.480 0.014 34.098 0.000
FARMI -> CLIMA -0.103*** -0.105 0.025 -4.077 0.000
FARMI -> NUTRI -0.283*** -0.284 0.015 -18.975 0.000
FARMI -> FOODSEC 0.632*** 0.632 0.010 65.072 0.000
FOODENV -> CLIMA 0.048*** 0.048 0.015 3.105 0.002
FOODENV -> NUTRI -0.329*** -0.329 0.018 -18.744 0.000
FOODENV -> FOODSEC 0.473*** 0.473 0.012 39.092 0.000
print_paths_tbl( list( "Main PLS-SEM" = list( df = s_boot_full[["Main PLS-SEM"]]$bootstrapped_total_paths,
      data = data_main
    ),
    "Scenario A (Winsorization)" = list(
      df   = s_boot_full[["Scenario A"]]$bootstrapped_total_paths, data = data_winsorize ), "Scenario B (Deletion)" = list( df = s_boot_full[["Scenario B"]]$bootstrapped_total_paths, data = data_deletion ) ), caption = "<b><span style='color:black'>Bootstrapped Total Effects - Full Model<span></b>" )
Bootstrapped Total Effects - Full Model
Path Original Est. Bootstrap Mean Bootstrap SD T Stat. p-value
Main PLS-SEM
ENVIR -> INFRA -0.057** -0.053 0.022 -2.573 0.010
ENVIR -> ECOSY 0.007 0.005 0.017 0.415 0.678
ENVIR -> FARMI -0.031 -0.030 0.023 -1.365 0.172
ENVIR -> FOODENV 0.036 0.032 0.029 1.250 0.211
ENVIR -> CLIMA 0.001 0.001 0.003 0.438 0.661
ENVIR -> NUTRI -0.011 -0.009 0.014 -0.783 0.434
ENVIR -> FOODSEC 0.007 0.005 0.021 0.350 0.727
INFRA -> ECOSY 0.393*** 0.393 0.012 33.367 0.000
INFRA -> FARMI 0.591*** 0.591 0.011 55.602 0.000
INFRA -> FOODENV 0.505*** 0.505 0.011 46.480 0.000
INFRA -> CLIMA -0.068*** -0.069 0.012 -5.930 0.000
INFRA -> NUTRI -0.299*** -0.300 0.009 -35.157 0.000
INFRA -> FOODSEC 0.483*** 0.483 0.009 54.129 0.000
ECOSY -> FARMI 0.534*** 0.534 0.014 37.114 0.000
ECOSY -> FOODENV 0.24*** 0.240 0.010 24.124 0.000
ECOSY -> CLIMA -0.053** -0.054 0.024 -2.250 0.025
ECOSY -> NUTRI -0.185*** -0.186 0.009 -20.887 0.000
ECOSY -> FOODSEC 0.332*** 0.332 0.010 34.938 0.000
FARMI -> FOODENV 0.449*** 0.450 0.014 32.784 0.000
FARMI -> CLIMA -0.103*** -0.105 0.023 -4.490 0.000
FARMI -> NUTRI -0.347*** -0.348 0.014 -25.664 0.000
FARMI -> FOODSEC 0.623*** 0.623 0.010 64.740 0.000
FOODENV -> CLIMA -0.034** -0.034 0.017 -2.030 0.042
FOODENV -> NUTRI -0.392*** -0.392 0.014 -27.467 0.000
FOODENV -> FOODSEC 0.481*** 0.481 0.011 44.372 0.000
Scenario A (Winsorization)
ENVIR -> INFRA -0.058** -0.053 0.023 -2.547 0.011
ENVIR -> ECOSY 0.007 0.005 0.017 0.397 0.691
ENVIR -> FARMI -0.029 -0.027 0.023 -1.219 0.223
ENVIR -> FOODENV 0.032 0.028 0.029 1.092 0.275
ENVIR -> CLIMA 0.003 0.002 0.003 0.807 0.420
ENVIR -> NUTRI -0.01 -0.008 0.014 -0.672 0.502
ENVIR -> FOODSEC 0.006 0.004 0.021 0.290 0.772
INFRA -> ECOSY 0.393*** 0.393 0.012 33.429 0.000
INFRA -> FARMI 0.592*** 0.592 0.010 56.475 0.000
INFRA -> FOODENV 0.506*** 0.506 0.011 46.554 0.000
INFRA -> CLIMA -0.072*** -0.073 0.011 -6.528 0.000
INFRA -> NUTRI -0.299*** -0.300 0.008 -35.442 0.000
INFRA -> FOODSEC 0.483*** 0.483 0.009 54.427 0.000
ECOSY -> FARMI 0.544*** 0.543 0.014 38.704 0.000
ECOSY -> FOODENV 0.251*** 0.251 0.010 25.038 0.000
ECOSY -> CLIMA -0.053** -0.054 0.024 -2.197 0.028
ECOSY -> NUTRI -0.192*** -0.193 0.009 -21.715 0.000
ECOSY -> FOODSEC 0.342*** 0.342 0.009 36.126 0.000
FARMI -> FOODENV 0.461*** 0.462 0.014 34.012 0.000
FARMI -> CLIMA -0.123*** -0.125 0.025 -4.908 0.000
FARMI -> NUTRI -0.353*** -0.354 0.013 -26.790 0.000
FARMI -> FOODSEC 0.629*** 0.629 0.009 66.461 0.000
FOODENV -> CLIMA -0.02 -0.020 0.015 -1.327 0.185
FOODENV -> NUTRI -0.388*** -0.389 0.014 -27.102 0.000
FOODENV -> FOODSEC 0.478*** 0.477 0.011 43.801 0.000
Scenario B (Deletion)
ENVIR -> INFRA -0.073** -0.064 0.029 -2.531 0.011
ENVIR -> ECOSY 0.013 0.012 0.023 0.576 0.564
ENVIR -> FARMI -0.03 -0.026 0.027 -1.108 0.268
ENVIR -> FOODENV 0.001 0.005 0.038 0.024 0.981
ENVIR -> CLIMA 0.003 0.003 0.003 0.884 0.377
ENVIR -> NUTRI 0.006 0.003 0.019 0.299 0.765
ENVIR -> FOODSEC -0.012 -0.008 0.028 -0.432 0.666
INFRA -> ECOSY 0.365*** 0.365 0.013 27.707 0.000
INFRA -> FARMI 0.553*** 0.554 0.012 45.273 0.000
INFRA -> FOODENV 0.496*** 0.496 0.012 40.839 0.000
INFRA -> CLIMA -0.069*** -0.069 0.012 -5.766 0.000
INFRA -> NUTRI -0.295*** -0.296 0.009 -33.312 0.000
INFRA -> FOODSEC 0.459*** 0.459 0.010 45.373 0.000
ECOSY -> FARMI 0.573*** 0.573 0.015 37.531 0.000
ECOSY -> FOODENV 0.278*** 0.278 0.011 24.551 0.000
ECOSY -> CLIMA -0.117*** -0.118 0.019 -6.070 0.000
ECOSY -> NUTRI -0.217*** -0.217 0.010 -21.935 0.000
ECOSY -> FOODSEC 0.364*** 0.364 0.010 34.836 0.000
FARMI -> FOODENV 0.485*** 0.485 0.014 34.611 0.000
FARMI -> CLIMA -0.104*** -0.105 0.025 -4.126 0.000
FARMI -> NUTRI -0.378*** -0.379 0.014 -27.820 0.000
FARMI -> FOODSEC 0.635*** 0.635 0.010 65.707 0.000
FOODENV -> CLIMA 0.043*** 0.043 0.015 2.797 0.005
FOODENV -> NUTRI -0.376*** -0.377 0.016 -23.621 0.000
FOODENV -> FOODSEC 0.472*** 0.472 0.012 38.919 0.000

Explanatory power. \(R^2\) values assess the model’s explanatory strength, with thresholds of 0.75, 0.5 and 0.25 indicate substantial, moderate, and weak power, respectively. Effect sizes \(f^2\) quantify the relative contribution of each exogenous construct, with 0.35, 0.15, and 0.02 representing large, medium, and small effects.

FARMI and FOODSEC show substantial explanatory power (approx. 0.60), while FOODENV is moderate (approx. 0.40). ECOSY and NUTRI are weak-to-moderate (approx. 0.15 to 0.26), and CLIMA and INFRA remain exogenous (below 0.05). Large \(f^2\) effects occur for ECOSY -> FARMI, FARMI -> FOODENV, and FOODENV -> FOODSEC, confirming that FARMI and FOODENV are central conduits through which upstream constructs (e.g., INFRA, ECOSY) influence FOODSEC outcomes.

tidy_r2 <- function(s, label, which = "R^2"){
  p <- s$paths
  df <- as.data.frame(p, stringsAsFactors = FALSE)
  stopifnot(!is.null(rownames(df)), !is.null(colnames(df)))
  stopifnot(which %in% rownames(df))
  row <- df[which, , drop = TRUE]
  tibble(Construct = names(row),
         !!label := suppressWarnings(as.numeric(row))) %>% 
    filter(!is.na(!!sym(label))) }

format_r2 <- function(x, digits = 3, bold_thr = 0.25){
  out <- rep("-", length(x))
  not_na <- !is.na(x)
  if (any(not_na)){
    base <- sprintf(paste0("%.", digits, "f"), x[not_na])
    out[not_na] <- base
    bold_idx <- x[not_na] >= bold_thr
    out[not_na][bold_idx] <- cell_spec(base[bold_idx], bold = TRUE)
  }
  out
}

num_cols_r2 <- c("Main sample", "Scenario A (Winsorized)", "Scenario B (Deletion)")

r2_refined <- tidy_r2(s_main_iter2, "Main sample") %>%
  full_join(tidy_r2(s_A_iter2, "Scenario A (Winsorized)"), by = "Construct") %>%
  full_join(tidy_r2(s_B_iter2, "Scenario B (Deletion)"), by = "Construct") %>%
  arrange(Construct) %>%
  mutate(across(all_of(num_cols_r2), ~ suppressWarnings(as.numeric(.x)))) %>%
  mutate(across(all_of(num_cols_r2), ~ format_r2(.x, digits = 3, bold_thr = 0.25)))

r2_full <- tidy_r2(s_main, "Main sample") %>%
  full_join(tidy_r2(s_A, "Scenario A (Winsorized)"), by = "Construct") %>%
  full_join(tidy_r2(s_B, "Scenario B (Deletion)"), by = "Construct") %>%
  arrange(Construct) %>%
  mutate(across(all_of(num_cols_r2), ~ suppressWarnings(as.numeric(.x)))) %>%
  mutate(across(all_of(num_cols_r2), ~ format_r2(.x, digits = 3, bold_thr = 0.25)))

r2_combined <- bind_rows(
  r2_refined %>% mutate(Model = "Refined Model"),
  r2_full    %>% mutate(Model = "Full Model")
)

kbl(r2_combined %>%
      select(Construct, all_of(num_cols_r2)),
    format = "html", escape = FALSE,
    booktabs = TRUE,
    align = c("l", "l", rep("c", length(num_cols_r2))),
    caption = paste0("<b><span style='color:black'>$R^2$ of endogenous constructs - Refined and Full Models<span></b><br>",
                     "Bold indicates $R^2 \\geq 0.25$.")
) %>%
  kable_styling(
    bootstrap_options = c("striped", "hover", "condensed", "responsive"),
    full_width = FALSE, position = "center", fixed_thead = TRUE
  ) %>%
  pack_rows("Refined Model", 1, nrow(r2_refined)) %>%
  pack_rows("Full Model", nrow(r2_refined)+1, nrow(r2_combined))
\(R^2\) of endogenous constructs - Refined and Full Models
Bold indicates \(R^2 \geq 0.25\).
Construct Main sample Scenario A (Winsorized) Scenario B (Deletion)
Refined Model
CLIMA 0.012 0.013 0.022
ECOSY 0.154 0.154 0.132
FARMI 0.596 0.604 0.595
FOODENV 0.387 0.394 0.408
FOODSEC 0.626 0.628 0.621
INFRA 0.003 0.003 0.005
NUTRI 0.172 0.171 0.174
Full Model
CLIMA 0.012 0.014 0.022
ECOSY 0.154 0.154 0.132
FARMI 0.591 0.600 0.591
FOODENV 0.387 0.394 0.408
FOODSEC 0.627 0.628 0.621
INFRA 0.003 0.003 0.005
NUTRI 0.262 0.262 0.270
f2_list <- list(
  "Main sample" = s_main_iter2$fSquare,
  "Scenario A"  = s_A_iter2$fSquare,
  "Scenario B"  = s_B_iter2$fSquare
)

format_f2 <- function(x, digits = 3, bold_thr = 0.15) {
  x <- suppressWarnings(as.numeric(x))
  formatted <- ifelse(is.na(x), "", formatC(x, digits = digits, format = "f"))
  formatted <- ifelse(
    !is.na(x) & x >= bold_thr,
    paste0("<b>", formatted, "</b>"),
    formatted)
  formatted
}

f2_panel_table <- function(f2_list,
                           digits = 3, bold_thr = 0.15,
                           caption_prefix =
                             "<b><span style='color:black'>$f^2$ effect sizes - Refined Model ") {

  constructs <- colnames(as.data.frame(f2_list[[1]], check.names = FALSE))

  panel_df <- imap_dfr(f2_list, function(mat, scen) {
    m <- as.matrix(mat)
    m[m < 0] <- 0
    keep <- row(m) <= col(m)
    m[!keep] <- NA

    as.data.frame(m, check.names = FALSE) %>%
      rownames_to_column(var = "Construct") %>%
      mutate(Scenario = scen) %>%
      relocate(Scenario, Construct, all_of(constructs))
  })

  num_cols <- setdiff(names(panel_df), c("Scenario","Construct"))
  panel_df[num_cols] <- lapply(panel_df[num_cols], format_f2,
                               digits = digits, bold_thr = bold_thr)
  
  panel_df <- panel_df %>%
    mutate(Construct = factor(Construct, levels = constructs, ordered = TRUE)) %>%
    arrange(Scenario, Construct)

  idx <- panel_df %>%
    mutate(row_id = row_number()) %>%
    group_by(Scenario) %>%
    summarise(start = min(row_id), end = max(row_id), .groups = "drop")

  kb <- kable(
    panel_df %>%
      select(-Scenario),
    format  = "html",
    escape  = FALSE,
    align   = c("l", rep("r", length(num_cols))),
    caption = paste0(
      caption_prefix, "<span></b><br>",
      "Bold indicates $f^2 \\geq 0.15$."
    )
  ) %>%
    kable_styling(
      bootstrap_options = c("striped","hover","condensed"),
      full_width = FALSE, position = "center", fixed_thead = TRUE
    )

  if (nrow(idx) > 0) {
    for (i in seq_len(nrow(idx))) {
      kb <- kb %>%
        group_rows(idx$Scenario[i], idx$start[i], idx$end[i], bold = TRUE)
    }
  }

  kb
}

f2_panel_table(f2_list,
  digits   = 3,
  bold_thr = 0.15
)
\(f^2\) effect sizes - Refined Model
Bold indicates \(f^2 \geq 0.15\).
Construct ENVIR INFRA ECOSY FARMI FOODENV CLIMA NUTRI FOODSEC
Main sample
ENVIR 0.000 0.003 0.001 0.000 0.005 0.000 0.000 0.000
INFRA 0.000 0.181 0.189 0.066 0.000 0.000 0.000
ECOSY 0.000 0.489 0.000 0.000 0.000 0.000
FARMI 0.000 0.202 0.003 0.008 0.290
FOODENV 0.000 0.001 0.097 0.410
CLIMA 0.000 0.000 0.000
NUTRI 0.000 0.000
FOODSEC 0.000
Scenario A
ENVIR 0.000 0.003 0.001 0.000 0.005 0.000 0.000 0.000
INFRA 0.000 0.181 0.195 0.063 0.000 0.000 0.000
ECOSY 0.000 0.518 0.000 0.000 0.000 0.000
FARMI 0.000 0.215 0.005 0.008 0.290
FOODENV 0.000 0.000 0.092 0.399
CLIMA 0.000 0.000 0.000
NUTRI 0.000 0.000
FOODSEC 0.000
Scenario B
ENVIR 0.000 0.005 0.002 0.000 0.002 0.000 0.000 0.000
INFRA 0.000 0.152 0.158 0.065 0.000 0.000 0.000
ECOSY 0.000 0.580 0.000 0.002 0.000 0.000
FARMI 0.000 0.260 0.007 0.012 0.273
FOODENV 0.000 0.002 0.081 0.372
CLIMA 0.000 0.000 0.000
NUTRI 0.000 0.000
FOODSEC 0.000
f2_list <- list(
  "Main sample" = s_main$fSquare,
  "Scenario A"  = s_A$fSquare,
  "Scenario B"  = s_B$fSquare
)

f2_panel_table <- function(f2_list,
                           digits = 3, bold_thr = 0.15,
                           caption_prefix =
                             "<b><span style='color:black'>$f^2$ effect sizes - Full Model ") {

  constructs <- colnames(as.data.frame(f2_list[[1]], check.names = FALSE))

  panel_df <- imap_dfr(f2_list, function(mat, scen) {
    m <- as.matrix(mat)
    m[m < 0] <- 0
    keep <- row(m) <= col(m)
    m[!keep] <- NA

    as.data.frame(m, check.names = FALSE) %>%
      rownames_to_column(var = "Construct") %>%
      mutate(Scenario = scen) %>%
      relocate(Scenario, Construct, all_of(constructs))
  })

  num_cols <- setdiff(names(panel_df), c("Scenario","Construct"))
  panel_df[num_cols] <- lapply(panel_df[num_cols], format_f2,
                               digits = digits, bold_thr = bold_thr)
  
  panel_df <- panel_df %>%
    mutate(Construct = factor(Construct, levels = constructs, ordered = TRUE)) %>%
    arrange(Scenario, Construct)

  idx <- panel_df %>%
    mutate(row_id = row_number()) %>%
    group_by(Scenario) %>%
    summarise(start = min(row_id), end = max(row_id), .groups = "drop")

  kb <- kable(
    panel_df %>%
      select(-Scenario),
    format  = "html",
    escape  = FALSE,
    align   = c("l", rep("r", length(num_cols))),
    caption = paste0(
      caption_prefix, "<span></b><br>",
      "Bold indicates $f^2 \\geq 0.15$."
    )
  ) %>%
    kable_styling(
      bootstrap_options = c("striped","hover","condensed"),
      full_width = FALSE, position = "center", fixed_thead = TRUE
    )

  if (nrow(idx) > 0) {
    for (i in seq_len(nrow(idx))) {
      kb <- kb %>%
        group_rows(idx$Scenario[i], idx$start[i], idx$end[i], bold = TRUE)
    }
  }

  kb
}

f2_panel_table(f2_list,
  digits   = 3,
  bold_thr = 0.15
)
\(f^2\) effect sizes - Full Model
Bold indicates \(f^2 \geq 0.15\).
Construct ENVIR INFRA ECOSY FARMI FOODENV CLIMA NUTRI FOODSEC
Main sample
ENVIR 0.000 0.003 0.001 0.000 0.005 0.000 0.000 0.000
INFRA 0.000 0.182 0.194 0.063 0.000 0.000 0.000
ECOSY 0.000 0.484 0.000 0.000 0.000 0.000
FARMI 0.000 0.209 0.003 0.026 0.290
FOODENV 0.000 0.001 0.134 0.407
CLIMA 0.000 0.000 0.000
NUTRI 0.000 0.000
FOODSEC 0.000
Scenario A
ENVIR 0.000 0.003 0.001 0.000 0.005 0.000 0.000 0.000
INFRA 0.000 0.182 0.199 0.061 0.000 0.000 0.000
ECOSY 0.000 0.514 0.000 0.000 0.000 0.000
FARMI 0.000 0.222 0.006 0.026 0.290
FOODENV 0.000 0.000 0.130 0.396
CLIMA 0.000 0.000 0.000
NUTRI 0.000 0.000
FOODSEC 0.000
Scenario B
ENVIR 0.000 0.005 0.002 0.000 0.002 0.000 0.000 0.000
INFRA 0.000 0.152 0.163 0.062 0.000 0.000 0.000
ECOSY 0.000 0.575 0.000 0.002 0.000 0.000
FARMI 0.000 0.266 0.006 0.033 0.274
FOODENV 0.000 0.001 0.120 0.370
CLIMA 0.000 0.000 0.000
NUTRI 0.000 0.000
FOODSEC 0.000

Predictive power. Out-of-sample predictive was assessed with PLSpredict using the Direct Antecedents scheme and 10-fold cross-validation with 10 repetitions across all specifications (Refined and Full models) and scenarios (Main, Scenario A, and Scenario B). A linear model (LM) served as benchmark, and indicator-level RMSE and MAE were computed for in- and out-of-sample data. The tables below report PLS/LM error ratios (ratio less than 1 favors PLS; greater than 1 favors LM).

Overall, LM predicts better than PLS-SEM for most indicators and scenarios: the PLS/LM ratios are typically above 1, with differences concentrated around +5% to +25%, and occasional larger gaps near +30% for INFRA and sobre ECOSY indicators. Close-to-parity cases (ratios approx. 1.05) appear for a few items (e.g., ecosy_3 (ii1), farmi_8 (v4), foodenv_3 (n2)), and this pattern is stable across Main, Scenario A and Scenario B as well as between Refined and Full models. For outcomes like foodsec and clima, PLS error are generally about 10% to 20% higher than LM. Taken together, the models show limited incremental predictive advantage of PLS-SEM over a simple LM baseline.

run_predict <- function(model, folds = 10, reps = 10) {
  out <- predict_pls(model, technique = predict_DA, noFolds = folds, reps = reps)
  summary(out)
}

models_refined <- list(
  "Main sample" = model_pls_iter2$Main,
  "Scenario A"  = model_pls_iter2$ScenarioA,
  "Scenario B"  = model_pls_iter2$ScenarioB
)

models_full <- list(
  "Main sample" = model_pls_main,
  "Scenario A"  = model_pls_scenarioA,
  "Scenario B"  = model_pls_scenarioB
)

s_predict_refined <- lapply(models_refined, run_predict)
s_predict_full    <- lapply(models_full,    run_predict)
tidy_metrics <- function(mat, scenario, model, sample){
  as.data.frame(mat, check.names = FALSE) %>%
    rownames_to_column("metric") %>%
    pivot_longer(-metric, names_to = "indicator", values_to = "value") %>%
    mutate(scenario = scenario, model = model, sample = sample)
}

tidy_from_scenario <- function(x_sce, sce_name){
  bind_rows(
    tidy_metrics(x_sce$PLS_in_sample,     sce_name, "PLS", "in"),
    tidy_metrics(x_sce$PLS_out_of_sample, sce_name, "PLS", "out"),
    tidy_metrics(x_sce$LM_in_sample,      sce_name, "LM",  "in"),
    tidy_metrics(x_sce$LM_out_of_sample,  sce_name, "LM",  "out")
  )
}

tidy_from_root <- function(obj_root){
  imap_dfr(obj_root, ~ tidy_from_scenario(.x, .y)) %>%
    mutate(
      sample = factor(sample, levels = c("in","out")),
      model  = factor(model,  levels = c("PLS","LM")),
      metric = factor(metric, levels = c("RMSE","MAE"))
    )
}

# --------- função principal ---------
predict_panel_table <- function(s_predict_obj,
                                digits = 3,
                                caption_prefix = "Predictive errors ",
                                scenario_order = c("MAIN","A","B")) {

  df <- tidy_from_root(s_predict_obj)

  # se algum cenário não estiver na lista, mantém no fim na ordem em que aparece
  scenario_levels <- unique(c(scenario_order, setdiff(unique(df$scenario), scenario_order)))
  df$scenario <- factor(df$scenario, levels = scenario_levels)

  wide <- df %>%
    group_by(scenario, indicator, sample, model, metric) %>%
    summarise(value = mean(value), .groups = "drop") %>%
    unite(col = key, sample, metric, model, sep = "_") %>%
    pivot_wider(names_from = key, values_from = value)

  need <- c("in_RMSE_PLS","in_RMSE_LM","in_MAE_PLS","in_MAE_LM",
            "out_RMSE_PLS","out_RMSE_LM","out_MAE_PLS","out_MAE_LM")
  for (cc in need) if (!cc %in% names(wide)) wide[[cc]] <- NA_real_

  ratio <- function(num, den) ifelse(is.finite(num) & is.finite(den), num/den, NA_real_)
  fmt   <- function(x) ifelse(is.na(x), "", sprintf(paste0("%.", digits, "f"), x))

  out_df <- wide %>%
    arrange(scenario, indicator) %>%
    mutate(
      in_RMSE_R  = ratio(in_RMSE_PLS,  in_RMSE_LM),
      in_MAE_R   = ratio(in_MAE_PLS,   in_MAE_LM),
      out_RMSE_R = ratio(out_RMSE_PLS, out_RMSE_LM),
      out_MAE_R  = ratio(out_MAE_PLS,  out_MAE_LM)
    ) %>%
    transmute(
      scenario, indicator,
      in_RMSE_PLS  = fmt(in_RMSE_PLS),
      in_RMSE_LM   = fmt(in_RMSE_LM),
      in_RMSE_R    = fmt(in_RMSE_R),
      in_MAE_PLS   = fmt(in_MAE_PLS),
      in_MAE_LM    = fmt(in_MAE_LM),
      in_MAE_R     = fmt(in_MAE_R),
      out_RMSE_PLS = fmt(out_RMSE_PLS),
      out_RMSE_LM  = fmt(out_RMSE_LM),
      out_RMSE_R   = fmt(out_RMSE_R),
      out_MAE_PLS  = fmt(out_MAE_PLS),
      out_MAE_LM   = fmt(out_MAE_LM),
      out_MAE_R    = fmt(out_MAE_R)
    )

  # índices de agrupamento (antes de remover 'scenario')
  idx <- out_df %>%
    mutate(row_id = row_number()) %>%
    group_by(scenario) %>%
    summarise(start = min(row_id), end = max(row_id), .groups = "drop")

  kb <- out_df %>%
    select(-scenario) %>%
    rename(Indicator = indicator) %>%
    kable(
      format    = "html",
      escape    = FALSE,
      align     = c("l", rep("r", 12)),
      col.names = c(
        "Indicator",
        "PLS","LM","PLS/LM","PLS","LM","PLS/LM",
        "PLS","LM","PLS/LM","PLS","LM","PLS/LM"
      ),
      caption   = paste0(
        "<b><span style='color:black'>", caption_prefix,
        "<span></b><br> In-sample and out-of-sample; PLS/LM columns represent ratios."
      )
    ) %>%
    kable_styling(
      bootstrap_options = c("striped","hover","condensed"),
      full_width = FALSE, position = "center", fixed_thead = TRUE
    ) %>%
    add_header_above(c(" " = 1, "RMSE" = 3, "MAE" = 3, "RMSE" = 3, "MAE" = 3)) %>%
    add_header_above(c(" " = 1, "in-sample" = 6, "out-sample" = 6))

  # agrupa por cenário com NOME EM NEGRITO
  if (nrow(idx) > 0) {
    for (i in seq_len(nrow(idx))) {
      kb <- kb %>%
        group_rows(idx$scenario[i], idx$start[i], idx$end[i], bold = TRUE, italic = FALSE)
    }
  }

  kb
}



# Refined
predict_panel_table(
  s_predict_refined,
  caption_prefix = "Predictive errors (RMSE/MAE) - Refined Model"
)
Predictive errors (RMSE/MAE) - Refined Model
In-sample and out-of-sample; PLS/LM columns represent ratios.
in-sample
out-sample
RMSE
MAE
RMSE
MAE
Indicator PLS LM PLS/LM PLS LM PLS/LM PLS LM PLS/LM PLS LM PLS/LM
Main sample
clima 0.994 0.879 1.131 0.623 0.505 1.235 0.995 0.885 1.124 0.624 0.508 1.227
ecosy_1 0.929 0.649 1.430 0.665 0.408 1.629 0.930 0.654 1.422 0.666 0.411 1.621
ecosy_2 0.926 0.644 1.437 0.683 0.419 1.629 0.927 0.648 1.429 0.684 0.422 1.620
ecosy_3 0.998 0.935 1.067 0.765 0.713 1.072 0.999 0.942 1.060 0.765 0.718 1.066
farmi_1 0.689 0.619 1.113 0.479 0.390 1.228 0.689 0.623 1.107 0.480 0.392 1.223
farmi_2 0.901 0.802 1.123 0.736 0.636 1.157 0.902 0.806 1.118 0.737 0.639 1.152
farmi_3 0.972 0.900 1.079 0.817 0.740 1.104 0.972 0.905 1.074 0.818 0.744 1.099
farmi_4 0.944 0.897 1.053 0.779 0.726 1.072 0.945 0.900 1.049 0.779 0.729 1.068
farmi_5 0.939 0.860 1.092 0.729 0.665 1.095 0.939 0.864 1.087 0.729 0.669 1.090
farmi_6 0.969 0.838 1.157 0.787 0.668 1.179 0.970 0.844 1.149 0.788 0.671 1.173
farmi_7 0.897 0.732 1.225 0.734 0.588 1.248 0.897 0.735 1.221 0.735 0.591 1.244
farmi_8 0.980 0.942 1.041 0.678 0.643 1.054 0.981 0.947 1.036 0.678 0.646 1.050
farmi_9 0.954 0.886 1.078 0.732 0.674 1.087 0.955 0.891 1.072 0.733 0.677 1.082
foodenv_1 0.797 0.666 1.197 0.636 0.518 1.227 0.798 0.670 1.192 0.637 0.521 1.222
foodenv_2 0.996 0.937 1.064 0.812 0.757 1.073 0.997 0.941 1.059 0.813 0.761 1.068
foodenv_3 1.001 0.943 1.061 0.759 0.701 1.083 1.001 0.949 1.055 0.759 0.705 1.078
foodenv_4 0.923 0.854 1.081 0.752 0.683 1.100 0.924 0.859 1.075 0.752 0.687 1.095
foodsec 0.611 0.539 1.134 0.465 0.410 1.133 0.612 0.543 1.128 0.466 0.413 1.128
infra_1 0.996 0.766 1.302 0.770 0.592 1.300 0.997 0.771 1.293 0.770 0.596 1.292
infra_2 0.997 0.893 1.117 0.754 0.674 1.118 0.997 0.898 1.110 0.754 0.677 1.113
infra_3 0.995 0.833 1.195 0.813 0.658 1.234 0.995 0.838 1.188 0.813 0.662 1.227
infra_4 0.992 0.799 1.242 0.766 0.580 1.322 0.993 0.803 1.235 0.766 0.583 1.315
infra_5 0.994 0.927 1.072 0.734 0.695 1.056 0.994 0.932 1.067 0.734 0.699 1.050
nutri_1 0.959 0.895 1.071 0.760 0.704 1.080 0.959 0.901 1.065 0.760 0.708 1.074
nutri_2 0.987 0.965 1.023 0.810 0.789 1.027 0.987 0.970 1.017 0.811 0.793 1.022
nutri_4 0.913 0.853 1.071 0.713 0.654 1.089 0.914 0.857 1.066 0.713 0.658 1.084
Scenario A
clima 0.993 0.885 1.122 0.623 0.510 1.222 0.994 0.891 1.115 0.623 0.513 1.215
ecosy_1 0.929 0.655 1.418 0.665 0.403 1.649 0.929 0.660 1.409 0.665 0.405 1.641
ecosy_2 0.926 0.642 1.442 0.683 0.413 1.651 0.926 0.646 1.434 0.683 0.416 1.642
ecosy_3 0.998 0.935 1.067 0.765 0.713 1.072 0.999 0.942 1.060 0.765 0.717 1.067
farmi_1 0.588 0.530 1.109 0.441 0.364 1.211 0.589 0.533 1.104 0.441 0.366 1.206
farmi_2 0.901 0.805 1.119 0.736 0.639 1.153 0.902 0.809 1.115 0.737 0.641 1.149
farmi_3 0.972 0.903 1.076 0.817 0.743 1.099 0.972 0.907 1.072 0.818 0.746 1.096
farmi_4 0.945 0.897 1.053 0.779 0.727 1.072 0.945 0.901 1.049 0.780 0.730 1.068
farmi_5 0.939 0.860 1.091 0.729 0.666 1.095 0.940 0.865 1.087 0.729 0.669 1.091
farmi_6 0.968 0.838 1.155 0.787 0.669 1.176 0.969 0.844 1.148 0.787 0.673 1.170
farmi_7 0.898 0.732 1.227 0.736 0.588 1.251 0.899 0.736 1.222 0.736 0.591 1.245
farmi_8 0.980 0.943 1.039 0.678 0.644 1.053 0.981 0.948 1.034 0.678 0.647 1.048
farmi_9 0.955 0.886 1.078 0.733 0.674 1.088 0.956 0.890 1.074 0.733 0.677 1.084
foodenv_1 0.793 0.666 1.191 0.632 0.518 1.220 0.795 0.670 1.185 0.634 0.522 1.215
foodenv_2 0.996 0.936 1.064 0.812 0.757 1.073 0.996 0.941 1.059 0.813 0.761 1.068
foodenv_3 0.739 0.713 1.037 0.665 0.626 1.062 0.739 0.717 1.031 0.665 0.630 1.056
foodenv_4 0.923 0.854 1.081 0.752 0.683 1.101 0.924 0.859 1.075 0.753 0.687 1.095
foodsec 0.610 0.538 1.133 0.465 0.410 1.134 0.611 0.542 1.126 0.465 0.413 1.127
infra_1 0.996 0.766 1.301 0.770 0.593 1.299 0.997 0.771 1.294 0.770 0.596 1.293
infra_2 0.997 0.892 1.117 0.754 0.674 1.119 0.997 0.898 1.110 0.754 0.677 1.113
infra_3 0.995 0.832 1.195 0.812 0.658 1.235 0.995 0.838 1.188 0.813 0.662 1.228
infra_4 0.992 0.800 1.241 0.766 0.580 1.320 0.993 0.805 1.234 0.766 0.584 1.313
infra_5 0.994 0.927 1.073 0.734 0.695 1.056 0.995 0.932 1.067 0.734 0.699 1.051
nutri_1 0.959 0.895 1.071 0.760 0.704 1.080 0.959 0.900 1.066 0.760 0.707 1.075
nutri_2 0.987 0.965 1.022 0.811 0.790 1.026 0.987 0.970 1.018 0.811 0.794 1.021
nutri_4 0.914 0.853 1.072 0.713 0.655 1.090 0.914 0.857 1.067 0.714 0.658 1.085
Scenario B
clima 0.978 0.875 1.118 0.611 0.506 1.207 0.980 0.881 1.112 0.612 0.510 1.201
ecosy_1 0.810 0.573 1.414 0.597 0.356 1.676 0.812 0.577 1.406 0.598 0.359 1.666
ecosy_2 0.827 0.575 1.439 0.623 0.374 1.668 0.828 0.578 1.433 0.624 0.376 1.660
ecosy_3 1.017 0.950 1.070 0.782 0.727 1.075 1.018 0.957 1.063 0.782 0.733 1.068
farmi_1 0.507 0.463 1.097 0.378 0.318 1.189 0.508 0.465 1.092 0.378 0.320 1.184
farmi_2 0.912 0.811 1.124 0.747 0.647 1.155 0.913 0.816 1.120 0.748 0.651 1.150
farmi_3 0.980 0.907 1.081 0.824 0.748 1.102 0.981 0.912 1.076 0.824 0.751 1.098
farmi_4 0.945 0.900 1.050 0.779 0.730 1.066 0.945 0.904 1.045 0.779 0.734 1.062
farmi_5 0.943 0.863 1.093 0.731 0.667 1.096 0.943 0.867 1.088 0.732 0.671 1.091
farmi_6 0.953 0.833 1.144 0.772 0.663 1.165 0.953 0.839 1.136 0.773 0.667 1.159
farmi_7 0.891 0.732 1.217 0.728 0.589 1.235 0.892 0.736 1.211 0.728 0.593 1.228
farmi_8 0.957 0.931 1.028 0.657 0.631 1.042 0.958 0.937 1.022 0.657 0.634 1.036
farmi_9 0.950 0.879 1.080 0.727 0.667 1.090 0.950 0.883 1.076 0.728 0.670 1.087
foodenv_1 0.779 0.664 1.174 0.622 0.520 1.196 0.781 0.669 1.167 0.623 0.523 1.190
foodenv_2 1.011 0.950 1.064 0.827 0.770 1.074 1.011 0.956 1.058 0.828 0.775 1.068
foodenv_3 0.592 0.582 1.017 0.524 0.507 1.033 0.592 0.585 1.012 0.524 0.510 1.028
foodenv_4 0.916 0.851 1.076 0.741 0.677 1.094 0.917 0.857 1.071 0.741 0.682 1.088
foodsec 0.613 0.544 1.126 0.466 0.414 1.126 0.613 0.548 1.119 0.467 0.417 1.120
infra_1 0.946 0.756 1.252 0.732 0.585 1.253 0.947 0.761 1.244 0.732 0.588 1.245
infra_2 1.017 0.912 1.116 0.771 0.691 1.115 1.018 0.918 1.110 0.771 0.695 1.109
infra_3 1.000 0.851 1.175 0.816 0.674 1.211 1.001 0.857 1.168 0.817 0.678 1.204
infra_4 0.983 0.804 1.223 0.757 0.586 1.293 0.984 0.809 1.216 0.758 0.589 1.286
infra_5 0.864 0.813 1.063 0.645 0.618 1.044 0.864 0.818 1.057 0.646 0.622 1.039
nutri_1 0.954 0.895 1.065 0.754 0.702 1.075 0.955 0.903 1.058 0.755 0.707 1.068
nutri_2 0.988 0.966 1.023 0.812 0.790 1.028 0.989 0.972 1.017 0.812 0.795 1.022
nutri_4 0.909 0.853 1.066 0.706 0.653 1.082 0.910 0.859 1.059 0.707 0.658 1.075
# Full
predict_panel_table(
  s_predict_full,
  caption_prefix = "Predictive errors (RMSE/MAE) - Full Model"
)
Predictive errors (RMSE/MAE) - Full Model
In-sample and out-of-sample; PLS/LM columns represent ratios.
in-sample
out-sample
RMSE
MAE
RMSE
MAE
Indicator PLS LM PLS/LM PLS LM PLS/LM PLS LM PLS/LM PLS LM PLS/LM
Main sample
clima 0.994 0.878 1.132 0.623 0.504 1.237 0.995 0.885 1.124 0.623 0.508 1.228
ecosy_1 0.929 0.649 1.431 0.665 0.408 1.629 0.929 0.654 1.420 0.665 0.411 1.618
ecosy_2 0.926 0.643 1.439 0.683 0.419 1.629 0.926 0.648 1.430 0.683 0.422 1.619
ecosy_3 0.998 0.935 1.068 0.765 0.713 1.073 0.999 0.941 1.061 0.765 0.718 1.066
farmi_1 0.691 0.619 1.117 0.482 0.390 1.234 0.692 0.624 1.109 0.482 0.393 1.227
farmi_2 0.901 0.802 1.123 0.736 0.636 1.157 0.902 0.806 1.118 0.736 0.639 1.153
farmi_3 0.972 0.900 1.080 0.817 0.740 1.104 0.972 0.905 1.074 0.818 0.744 1.099
farmi_4 0.944 0.897 1.053 0.779 0.726 1.073 0.945 0.901 1.049 0.780 0.730 1.068
farmi_5 0.939 0.860 1.092 0.728 0.665 1.095 0.939 0.864 1.087 0.729 0.668 1.091
farmi_6 0.969 0.834 1.162 0.787 0.662 1.188 0.969 0.838 1.156 0.787 0.665 1.183
farmi_7 0.896 0.731 1.226 0.734 0.588 1.249 0.897 0.734 1.221 0.735 0.590 1.244
farmi_8 0.980 0.941 1.041 0.678 0.643 1.054 0.981 0.947 1.035 0.679 0.647 1.049
farmi_9 0.954 0.884 1.080 0.732 0.671 1.091 0.955 0.888 1.075 0.733 0.674 1.086
foodenv_1 0.796 0.665 1.196 0.635 0.518 1.227 0.797 0.670 1.190 0.636 0.521 1.221
foodenv_2 0.997 0.933 1.069 0.813 0.754 1.077 0.998 0.939 1.063 0.813 0.759 1.071
foodenv_3 1.000 0.942 1.061 0.759 0.701 1.083 1.001 0.948 1.055 0.759 0.705 1.077
foodenv_4 0.922 0.854 1.081 0.751 0.682 1.101 0.923 0.858 1.076 0.752 0.686 1.096
foodsec 0.611 0.519 1.176 0.465 0.395 1.177 0.612 0.523 1.169 0.466 0.398 1.171
infra_1 0.996 0.765 1.302 0.770 0.591 1.302 0.997 0.770 1.295 0.770 0.594 1.295
infra_2 0.997 0.891 1.119 0.754 0.672 1.121 0.997 0.897 1.112 0.754 0.676 1.114
infra_3 0.995 0.832 1.195 0.813 0.658 1.235 0.995 0.837 1.189 0.813 0.661 1.229
infra_4 0.992 0.799 1.242 0.766 0.580 1.322 0.993 0.804 1.234 0.767 0.583 1.315
infra_5 0.994 0.927 1.072 0.734 0.695 1.056 0.995 0.932 1.067 0.734 0.699 1.050
nutri_1 0.958 0.895 1.071 0.759 0.703 1.079 0.959 0.901 1.064 0.760 0.708 1.073
nutri_2 0.987 0.965 1.023 0.811 0.789 1.027 0.988 0.970 1.018 0.811 0.794 1.022
nutri_3 0.914 0.860 1.062 0.699 0.650 1.077 0.914 0.865 1.057 0.700 0.653 1.071
nutri_4 0.914 0.852 1.072 0.713 0.654 1.090 0.914 0.858 1.065 0.714 0.659 1.083
nutri_5 0.925 0.879 1.052 0.726 0.685 1.060 0.926 0.884 1.048 0.726 0.688 1.055
Scenario A
clima 0.993 0.884 1.123 0.623 0.509 1.223 0.994 0.891 1.115 0.623 0.513 1.215
ecosy_1 0.929 0.655 1.418 0.665 0.403 1.649 0.930 0.659 1.411 0.666 0.406 1.640
ecosy_2 0.926 0.640 1.445 0.683 0.414 1.650 0.927 0.644 1.438 0.684 0.416 1.643
ecosy_3 0.998 0.935 1.068 0.765 0.713 1.073 0.999 0.941 1.061 0.765 0.717 1.066
farmi_1 0.590 0.530 1.113 0.443 0.364 1.218 0.591 0.533 1.108 0.443 0.366 1.213
farmi_2 0.901 0.805 1.120 0.736 0.638 1.153 0.902 0.809 1.115 0.737 0.641 1.149
farmi_3 0.972 0.903 1.076 0.817 0.743 1.100 0.972 0.908 1.071 0.817 0.747 1.095
farmi_4 0.945 0.897 1.053 0.780 0.727 1.073 0.945 0.902 1.048 0.780 0.731 1.067
farmi_5 0.939 0.860 1.091 0.729 0.666 1.095 0.939 0.864 1.088 0.729 0.668 1.091
farmi_6 0.969 0.834 1.161 0.786 0.663 1.186 0.969 0.841 1.152 0.787 0.667 1.179
farmi_7 0.898 0.732 1.228 0.736 0.588 1.251 0.899 0.736 1.221 0.736 0.591 1.245
farmi_8 0.980 0.943 1.040 0.678 0.644 1.053 0.980 0.948 1.034 0.678 0.648 1.047
farmi_9 0.955 0.884 1.080 0.733 0.671 1.092 0.956 0.889 1.075 0.733 0.675 1.087
foodenv_1 0.792 0.666 1.190 0.632 0.518 1.220 0.794 0.671 1.184 0.633 0.522 1.213
foodenv_2 0.997 0.933 1.069 0.813 0.754 1.077 0.998 0.939 1.062 0.813 0.759 1.071
foodenv_3 0.739 0.712 1.037 0.665 0.626 1.062 0.739 0.716 1.031 0.665 0.629 1.057
foodenv_4 0.923 0.854 1.081 0.752 0.682 1.102 0.923 0.859 1.075 0.752 0.686 1.096
foodsec 0.610 0.519 1.175 0.464 0.395 1.177 0.610 0.523 1.167 0.465 0.398 1.169
infra_1 0.996 0.766 1.302 0.770 0.592 1.301 0.997 0.770 1.294 0.770 0.595 1.294
infra_2 0.997 0.891 1.120 0.754 0.672 1.121 0.997 0.897 1.112 0.754 0.677 1.114
infra_3 0.995 0.832 1.196 0.812 0.658 1.235 0.995 0.837 1.189 0.813 0.661 1.229
infra_4 0.992 0.800 1.241 0.766 0.580 1.320 0.993 0.805 1.234 0.766 0.584 1.313
infra_5 0.994 0.927 1.073 0.734 0.695 1.056 0.994 0.932 1.066 0.734 0.699 1.050
nutri_1 0.958 0.895 1.070 0.759 0.704 1.079 0.959 0.901 1.064 0.760 0.708 1.074
nutri_2 0.987 0.965 1.023 0.811 0.790 1.026 0.988 0.970 1.018 0.811 0.794 1.022
nutri_3 0.914 0.861 1.062 0.700 0.650 1.077 0.915 0.865 1.057 0.701 0.653 1.072
nutri_4 0.914 0.853 1.072 0.714 0.655 1.090 0.915 0.858 1.067 0.714 0.658 1.085
nutri_5 0.925 0.880 1.052 0.726 0.685 1.060 0.926 0.884 1.047 0.727 0.689 1.055
Scenario B
clima 0.978 0.874 1.119 0.611 0.505 1.209 0.980 0.881 1.112 0.612 0.509 1.202
ecosy_1 0.810 0.572 1.416 0.597 0.357 1.673 0.811 0.577 1.406 0.598 0.359 1.662
ecosy_2 0.827 0.574 1.441 0.623 0.374 1.669 0.828 0.579 1.431 0.624 0.376 1.660
ecosy_3 1.017 0.950 1.071 0.782 0.727 1.075 1.017 0.957 1.063 0.782 0.732 1.068
farmi_1 0.509 0.462 1.100 0.380 0.318 1.195 0.510 0.466 1.095 0.380 0.320 1.189
farmi_2 0.912 0.811 1.125 0.747 0.647 1.155 0.913 0.816 1.119 0.748 0.651 1.149
farmi_3 0.980 0.907 1.081 0.824 0.747 1.102 0.981 0.912 1.075 0.824 0.752 1.097
farmi_4 0.945 0.900 1.050 0.779 0.730 1.066 0.945 0.905 1.045 0.779 0.734 1.061
farmi_5 0.942 0.862 1.093 0.731 0.667 1.096 0.943 0.868 1.087 0.732 0.671 1.090
farmi_6 0.953 0.829 1.149 0.772 0.658 1.174 0.954 0.836 1.142 0.773 0.662 1.167
farmi_7 0.890 0.732 1.217 0.727 0.589 1.235 0.891 0.736 1.210 0.728 0.593 1.228
farmi_8 0.957 0.930 1.029 0.657 0.631 1.042 0.958 0.937 1.023 0.658 0.634 1.037
farmi_9 0.950 0.878 1.082 0.727 0.665 1.093 0.950 0.882 1.077 0.727 0.668 1.088
foodenv_1 0.779 0.663 1.174 0.621 0.519 1.196 0.780 0.669 1.167 0.622 0.523 1.190
foodenv_2 1.011 0.946 1.069 0.827 0.767 1.079 1.012 0.954 1.062 0.828 0.773 1.072
foodenv_3 0.592 0.582 1.017 0.524 0.507 1.033 0.592 0.585 1.011 0.524 0.510 1.028
foodenv_4 0.916 0.851 1.076 0.740 0.677 1.094 0.916 0.857 1.070 0.741 0.681 1.087
foodsec 0.612 0.525 1.165 0.466 0.400 1.166 0.613 0.530 1.156 0.467 0.403 1.157
infra_1 0.946 0.755 1.253 0.732 0.584 1.254 0.947 0.761 1.243 0.732 0.588 1.245
infra_2 1.017 0.910 1.118 0.771 0.690 1.117 1.018 0.916 1.111 0.771 0.694 1.110
infra_3 1.000 0.851 1.175 0.816 0.674 1.211 1.001 0.857 1.168 0.817 0.678 1.205
infra_4 0.983 0.803 1.224 0.757 0.585 1.294 0.984 0.811 1.214 0.758 0.590 1.284
infra_5 0.864 0.813 1.063 0.646 0.618 1.044 0.864 0.818 1.056 0.646 0.623 1.037
nutri_1 0.953 0.895 1.064 0.753 0.702 1.073 0.954 0.902 1.058 0.754 0.706 1.068
nutri_2 0.989 0.966 1.023 0.812 0.790 1.028 0.990 0.972 1.018 0.813 0.795 1.022
nutri_3 0.910 0.859 1.059 0.694 0.647 1.073 0.910 0.865 1.052 0.694 0.652 1.066
nutri_4 0.909 0.853 1.066 0.707 0.653 1.082 0.910 0.859 1.059 0.707 0.658 1.076
nutri_5 0.916 0.873 1.050 0.717 0.677 1.059 0.917 0.878 1.045 0.718 0.682 1.053

Key Points (Structure Evaluation). The structural model shows strong internal consistency and theoretical alignment. Collinearity diagnostics confirmed confirmed no critical issues, with all VIF below 3. The main sustemic pathway (INFRA -> ECOSY -> FARMI -> FOODENV -> FOODSEC), is robust and consistent across scenarios, with large and significant effects for INFRA and ECOSY as upstream drivers, and FARMI and FOODENV as key transmission channels. Deviations concentrate in CLIMA and NUTRI, where small or negative coefficients (e.g., FARMI -> CLIMA/NUTRI, FOODEV -> NUTRI) mirror mixed formative weights and indicate contextual trade-offs rather than model misspecification. Explanatory power is substantial for FARMI and FOODSEC (\(R^2\) approx. 0.60), moderate for FOODENV (approx. 0.40), and weak-to-moderate for ECOSY and NUTRI (approx. 0.15 to 0.26). Large \(f^2\) effects for ECOSY -> FARMI, FARMI -> FOODENV, and FOODENV -> FOODSEC confirm the centrality of these links. Predictive assessment using PLSpredict shows that the linear benchmark (LM) generally achieves lower RMSE and MAE errors, while PLS-SEM reaches near-parity for a few indicators. Overall, the structure demonstrates coherent and stable relationshios across specifications, with the results consistent between the Refined and Full models and across robustness scenarios.

7 Reproducibility

All analyses were performed in R, using the packages listed in the setup section of this document. The session information below allows full reproducibility of the computational environment.

sessionInfo()
## R version 4.5.1 (2025-06-13 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 11 x64 (build 26100)
## 
## Matrix products: default
##   LAPACK version 3.12.1
## 
## locale:
## [1] LC_COLLATE=Portuguese_Brazil.utf8  LC_CTYPE=Portuguese_Brazil.utf8   
## [3] LC_MONETARY=Portuguese_Brazil.utf8 LC_NUMERIC=C                      
## [5] LC_TIME=Portuguese_Brazil.utf8    
## 
## time zone: America/Sao_Paulo
## tzcode source: internal
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
##  [1] ggraph_2.2.2      tidygraph_1.3.1   DescTools_0.99.60 viridis_0.6.5    
##  [5] viridisLite_0.4.2 janitor_2.2.1     readxl_1.4.5      kableExtra_1.4.0 
##  [9] seminr_2.3.1      lubridate_1.9.4   forcats_1.0.0     stringr_1.5.1    
## [13] dplyr_1.1.4       purrr_1.1.0       readr_2.1.5       tidyr_1.3.1      
## [17] tibble_3.3.0      ggplot2_4.0.0     tidyverse_2.0.0  
## 
## loaded via a namespace (and not attached):
##  [1] tidyselect_1.2.1   Exact_3.3          rootSolve_1.8.2.4  farver_2.1.2      
##  [5] S7_0.2.0           fastmap_1.2.0      tweenr_2.0.3       digest_0.6.37     
##  [9] timechange_0.3.0   lifecycle_1.0.4    lmom_3.2           magrittr_2.0.3    
## [13] compiler_4.5.1     rlang_1.1.6        sass_0.4.10        tools_4.5.1       
## [17] igraph_2.1.4       yaml_2.3.10        data.table_1.17.8  knitr_1.50        
## [21] htmlwidgets_1.6.4  graphlayouts_1.2.2 xml2_1.4.0         RColorBrewer_1.1-3
## [25] expm_1.0-0         withr_3.0.2        grid_4.5.1         polyclip_1.10-7   
## [29] e1071_1.7-16       scales_1.4.0       MASS_7.3-65        dichromat_2.0-0.1 
## [33] cli_3.6.5          mvtnorm_1.3-3      DiagrammeR_1.0.11  rmarkdown_2.30    
## [37] generics_0.1.4     rstudioapi_0.17.1  httr_1.4.7         tzdb_0.5.0        
## [41] visNetwork_2.1.2   gld_2.6.7          cachem_1.1.0       ggforce_0.5.0     
## [45] proxy_0.4-27       parallel_4.5.1     cellranger_1.1.0   vctrs_0.6.5       
## [49] boot_1.3-31        Matrix_1.7-3       jsonlite_2.0.0     hms_1.1.3         
## [53] ggrepel_0.9.6      systemfonts_1.2.3  jquerylib_0.1.4    glue_1.8.0        
## [57] codetools_0.2-20   stringi_1.8.7      gtable_0.3.6       pillar_1.11.0     
## [61] htmltools_0.5.8.1  R6_2.6.1           textshaping_1.0.1  evaluate_1.0.5    
## [65] lattice_0.22-7     haven_2.5.5        memoise_2.0.1      snakecase_0.11.1  
## [69] bslib_0.9.0        class_7.3-23       Rcpp_1.1.0         svglite_2.2.1     
## [73] gridExtra_2.3      xfun_0.52          fs_1.6.6           pkgconfig_2.0.3

8 References

Hair, J. F., Black, W. C., Babin, B. J., & Anderson, R. E. (2019). Multivariate Data Analysis (8th ed.). Cengage.

Hair, J. F., Hult, G. T. M., Ringle, C. M., Sarstedt, M., Danks, N. P., & Ray, S. (2021). PLS-SEM Using R: A Workbook. Springer.