df_hr <- read_csv("WA_Fn-UseC_-HR-Employee-Attrition.csv", show_col_types = FALSE)

df_model <- df_hr %>%
  mutate(
    Attrition_num     = ifelse(Attrition == "Yes", 1, 0),
    OverTime_num      = ifelse(OverTime  == "Yes", 1, 0),
  )

cat(sprintf("Jumlah observasi  : %d karyawan\n", nrow(df_model)))
## Jumlah observasi  : 1470 karyawan
cat(sprintf("Jumlah variabel   : %d kolom\n",  ncol(df_model)))
## Jumlah variabel   : 37 kolom
cat(sprintf("Missing values    : %d total\n",   sum(is.na(df_model))))
## Missing values    : 0 total

0.1 Statistik Deskriptif

vars_display <- c(
  # Kompensasi
  "MonthlyIncome",
  
  "YearsAtCompany", "YearsInCurrentRole","JobLevel", "YearsWithCurrManager",
  
  "Age", "TotalWorkingYears",
  
  "OverTime_num",  
  
  "Attrition_num"
)


desc_stats <- df_model %>%
  select(all_of(vars_display)) %>%
  describe() %>%
  select(n, mean, sd, min, max, skew, kurtosis) %>%
  round(3)

desc_stats$Konstruk <- c(
  rep("Kompensasi", 1),         
  rep("Pengembangan Karir", 4),   
  rep("Kapasitas Individu", 2),
  rep("Beban Kerja",         1),
  "Turnover Intention"
)

desc_stats <- desc_stats %>%
  select(Konstruk, n, mean, sd, min, max, skew, kurtosis)

kable(desc_stats,
      caption = "Tabel 1. Statistik Deskriptif Variabel Model HR",
      align   = "c") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                full_width = FALSE) %>%
  pack_rows("Kompensasi",          1, 1) %>%
  pack_rows("Pengembangan Karir",  2, 5) %>%
  pack_rows("Kapasitas Individu",  6, 7) %>%
  pack_rows("Beban Kerja",         8, 8) %>%
  pack_rows("Turnover Intention",  9, 9)
Tabel 1. Statistik Deskriptif Variabel Model HR
Konstruk n mean sd min max skew kurtosis
Kompensasi
MonthlyIncome Kompensasi 1470 6502.931 4707.957 1009 19999 1.367 0.992
Pengembangan Karir
YearsAtCompany Pengembangan Karir 1470 7.008 6.127 0 40 1.761 3.909
YearsInCurrentRole Pengembangan Karir 1470 4.229 3.623 0 18 0.915 0.467
JobLevel Pengembangan Karir 1470 2.064 1.107 1 5 1.023 0.389
YearsWithCurrManager Pengembangan Karir 1470 4.123 3.568 0 17 0.832 0.162
Kapasitas Individu
Age Kapasitas Individu 1470 36.924 9.135 18 60 0.412 -0.410
TotalWorkingYears Kapasitas Individu 1470 11.280 7.781 0 40 1.115 0.906
Beban Kerja
OverTime_num Beban Kerja 1470 0.283 0.451 0 1 0.963 -1.074
Turnover Intention
Attrition_num Turnover Intention 1470 0.161 0.368 0 1 1.841 1.389

1 Normalisasi Data

# Normalisasi Data
df_norm <- df_model %>%
  mutate(
    across(all_of(vars_display), ~ as.numeric(scale(.)))
  )

cat("Range setelah normalisasi (contoh 3 variabel):\n")
## Range setelah normalisasi (contoh 3 variabel):
cat(sprintf("  MonthlyIncome : [%.2f, %.2f]\n", min(df_norm$MonthlyIncome), max(df_norm$MonthlyIncome)))
##   MonthlyIncome : [-1.17, 2.87]
cat(sprintf("  YearsAtCompany  : [%.2f, %.2f]\n", min(df_norm$YearsAtCompany), max(df_norm$YearsAtCompany)))
##   YearsAtCompany  : [-1.14, 5.39]
cat(sprintf("  Attrition_num   : [%.2f, %.2f]\n", min(df_norm$Attrition_num), max(df_norm$Attrition_num)))
##   Attrition_num   : [-0.44, 2.28]

2 Spesifikasi Model SEM-PLS

2.1 Data untuk seminr

data_seminr <- df_norm %>% select(all_of(vars_display)) %>% as.data.frame()

cat(sprintf("Dimensi data seminr: %d observasi x %d indikator\n",
            nrow(data_seminr), ncol(data_seminr)))
## Dimensi data seminr: 1470 observasi x 9 indikator

2.2 Model Pengukuran (Outer)

measurement_model <- constructs(
  composite("Kompensasi", c("MonthlyIncome"), mode_A),
  
  composite("Karir", c("YearsAtCompany", "YearsInCurrentRole","JobLevel", "YearsWithCurrManager"), mode_A),
  
  composite("Beban_Kerja", c("OverTime_num"), mode_A),

  composite("Pengalaman", c("Age", "TotalWorkingYears"), mode_A),
  
  composite("Turnover_Intention", c("Attrition_num"), mode_A)
)

2.3 Model Struktural (Inner)

structural_model <- relationships(
  paths(from = "Pengalaman", to = c("Karir", "Kompensasi", "Beban_Kerja" )),

  paths(from = c("Karir", "Kompensasi", "Beban_Kerja"), to = "Turnover_Intention"),
  
  paths(from = "Pengalaman", to = "Turnover_Intention")
)

3 Estimasi Model PLS

# Estimasi Model PLS
cat("Estimasi model PLS untuk HR Analytics...\n")
## Estimasi model PLS untuk HR Analytics...
set.seed(2024)

pls_model <- estimate_pls(
  data              = data_seminr,
  measurement_model = measurement_model,
  structural_model  = structural_model,
  missing           = mean_replacement,
  missing_value     = NA
)

cat("Bootstrapping 1000 sampel...\n")
## Bootstrapping 1000 sampel...
boot_model <- bootstrap_model(
  seminr_model = pls_model,
  nboot        = 1000,
  cores        = 2,
  seed         = 2024
)

cat("Estimasi selesai.\n")
## Estimasi selesai.
sum_pls  <- summary(pls_model)
sum_boot <- summary(boot_model)

3.1 Reliabilitas & Validitas Konvergen

reliability_table <- sum_pls$reliability %>%
  as.data.frame() %>% round(3)

rel_display <- reliability_table %>%
  rownames_to_column("Konstruk") %>%
  filter(!Konstruk %in% c("Kompensasi", "Turnover_Intention")) %>%
  mutate(
    Status_Alpha = ifelse(alpha >= 0.70, "OK", "Tidak OK"),
    Status_RhoC  = ifelse(rhoC >= 0.70, "OK", "Tidak OK"), 
    Status_AVE   = ifelse(AVE   >= 0.50, "OK", "Tidak OK")
  )

kable(rel_display, caption = "Tabel 2. Reliabilitas & Validitas Konvergen", align = "c") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
  footnote(general = "Kriteria: alpha >= 0.70 | rhoC >= 0.70 | AVE >= 0.50", general_title = "Catatan:")
Tabel 2. Reliabilitas & Validitas Konvergen
Konstruk alpha rhoA rhoC AVE Status_Alpha Status_RhoC Status_AVE
Pengalaman 0.810 0.954 0.909 0.833 OK OK OK
Karir 0.852 0.887 0.896 0.684 OK OK OK
Beban_Kerja 1.000 1.000 1.000 1.000 OK OK OK
Catatan:
Kriteria: alpha >= 0.70 | rhoC >= 0.70 | AVE >= 0.50

3.2 Outer Loadings

loadings_df <- sum_pls$loadings %>%
  as.data.frame() %>% round(3) %>% rownames_to_column("Indikator")

kable(loadings_df, caption = "Tabel 3. Outer Loadings", align = "c") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
  footnote(general = "Target: Loading >= 0.70", general_title = "Catatan:")
Tabel 3. Outer Loadings
Indikator Pengalaman Karir Kompensasi Beban_Kerja Turnover_Intention
MonthlyIncome 0.000 0.000 1 0 0
YearsAtCompany 0.000 0.906 0 0 0
YearsInCurrentRole 0.000 0.813 0 0 0
JobLevel 0.000 0.774 0 0 0
YearsWithCurrManager 0.000 0.809 0 0 0
OverTime_num 0.000 0.000 0 1 0
Age 0.870 0.000 0 0 0
TotalWorkingYears 0.954 0.000 0 0 0
Attrition_num 0.000 0.000 0 0 1
Catatan:
Target: Loading >= 0.70

3.3 Validitas Diskriminan (HTMT)

htmt_matrix <- sum_pls$validity$htmt %>% as.data.frame() %>% round(3)

kable(htmt_matrix, caption = "Tabel 4. HTMT (Validitas Diskriminan)", align = "c") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Tabel 4. HTMT (Validitas Diskriminan)
Pengalaman Karir Kompensasi Beban_Kerja Turnover_Intention
Pengalaman NA NA NA NA NA
Karir 0.703 NA NA NA NA
Kompensasi 0.770 0.707 NA NA NA
Beban_Kerja 0.025 0.027 0.006 NA NA
Turnover_Intention 0.200 0.202 0.160 0.246 NA

3.4 VIF (Collinearity)

vif_inner <- sum_pls$vif_antecedents %>%
  as.data.frame() %>% round(3) %>% rownames_to_column("Konstruk")

kable(vif_inner, caption = "Tabel 5. VIF Inner Model (Collinearity)", align = "c") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
  footnote(general = "Kriteria ideal: VIF < 3.3 (Max < 5.0)", general_title = "Catatan:")
Tabel 5. VIF Inner Model (Collinearity)
Konstruk Karir Kompensasi Beban_Kerja Turnover_Intention
1 NA NA NA 2.387
2 NA NA NA 2.745
3 NA NA NA 1.003
4 NA NA NA 2.329
Catatan:
Kriteria ideal: VIF < 3.3 (Max < 5.0)

4 Evaluasi Model Struktural (Inner Model)

4.1 Koefisien Jalur & Signifikansi

paths_boot <- sum_boot$bootstrapped_paths %>%
  as.data.frame() %>%
  rownames_to_column("Jalur") %>%
  mutate(`p-value` = 2 * (1 - pnorm(abs(`T Stat.`)))) %>%
  mutate(across(where(is.numeric), ~ round(., 3)))

paths_display <- paths_boot %>%
  mutate(
    Sig = case_when(
      `p-value` < 0.001 ~ "***",
      `p-value` < 0.010 ~ "**",
      `p-value` < 0.050 ~ "*",
      TRUE              ~ "ns"
    ),
    Keputusan = ifelse(`p-value` < 0.05, "Signifikan", "Tidak Signifikan")
  ) %>%
  select(Jalur, `Original Est.`, `T Stat.`, `p-value`, Sig, Keputusan)

kable(paths_display, caption = "Tabel 6. Koefisien Jalur Struktural", align = "c") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = TRUE)
Tabel 6. Koefisien Jalur Struktural
Jalur Original Est. T Stat. p-value Sig Keputusan
Pengalaman -> Karir 0.674 57.976 0.000 *** Signifikan
Pengalaman -> Kompensasi 0.725 59.245 0.000 *** Signifikan
Pengalaman -> Beban_Kerja 0.020 0.767 0.443 ns Tidak Signifikan
Pengalaman -> Turnover_Intention -0.112 -2.952 0.003 ** Signifikan
Karir -> Turnover_Intention -0.108 -3.317 0.001 ** Signifikan
Kompensasi -> Turnover_Intention 0.000 -0.013 0.990 ns Tidak Signifikan
Beban_Kerja -> Turnover_Intention 0.246 8.956 0.000 *** Signifikan

4.2 R-squared & Relevansi Prediktif

r2_matrix <- pls_model$rSquared
r2_tbl <- data.frame(
  Konstruk = colnames(r2_matrix),
  R2       = round(as.numeric(r2_matrix[1, ]), 3)
) %>%
  filter(!is.na(R2) & R2 > 0)

kable(r2_tbl, caption = "Tabel 7. Koefisien Determinasi (R-squared)", align = "c") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Tabel 7. Koefisien Determinasi (R-squared)
Konstruk R2
Karir 0.454
Kompensasi 0.526
Turnover_Intention 0.101

4.3 Efek Mediasi

cat("=== Efek Tidak Langsung Spesifik (Mediasi melalui Karir & Kompensasi) ===\n")
## === Efek Tidak Langsung Spesifik (Mediasi melalui Karir & Kompensasi) ===
specific_ie <- sum_boot$bootstrapped_specific_indirect_effects
if (!is.null(specific_ie)) {
  specific_df <- as.data.frame(specific_ie) %>% round(3) %>% rownames_to_column("Jalur_Mediasi")
  kable(specific_df, caption = "Tabel 8. Efek Mediasi Spesifik", align = "c") %>%
    kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>% print()
}

5 Visualisasi

theme_sem <- theme_minimal(base_size = 12) +
  theme(
    plot.title       = element_text(face = "bold", size = 13, hjust = 0.5),
    plot.subtitle    = element_text(size = 9, hjust = 0.5, color = "#555"),
    panel.grid.minor = element_blank(),
    panel.border     = element_rect(color = "#cccccc", fill = NA, linewidth = 0.5)
  )

5.1 Plot 1: Koefisien Jalur (Forest Plot)

paths_plot <- paths_boot %>%
  rename(beta = `Original Est.`, pval = `p-value`) %>%
  {
    df <- .
    if ("2.5% CI" %in% names(df)) {
      df <- rename(df, ci_low = `2.5% CI`, ci_up = `97.5% CI`)
    } else if ("0.05% CI" %in% names(df)) {
      df <- rename(df, ci_low = `0.05% CI`, ci_up = `99.95% CI`)
    } else {
      # Fallback: estimasi CI dari T-stat
      df <- mutate(df, ci_low = beta - 1.96 * (beta / `T Stat.`),
                       ci_up  = beta + 1.96 * (beta / `T Stat.`))
    }
    df
  } %>%
  mutate(Warna = ifelse(pval < 0.05, ifelse(beta > 0, "#1a9850", "#d73027"), "#999999"))

p1 <- ggplot(paths_plot, aes(x = reorder(Jalur, beta), y = beta)) +
  geom_hline(yintercept = 0, linetype = "dashed", color = "#999", linewidth = 0.7) +
  geom_errorbar(aes(ymin = ci_low, ymax = ci_up, color = Warna), width = 0.2, linewidth = 1.3) +
  geom_point(aes(color = Warna), size = 5) +
  scale_color_identity() + coord_flip() +
  labs(title = "Signifikansi Jalur terhadap Turnover Intention", x = "Jalur", y = "Koefisien (Beta)") +
  theme_sem
print(p1)

5.2 Plot 2: Matriks Korelasi Indikator HR

cor_matrix <- cor(df_norm %>% select(all_of(vars_display)), use = "pairwise.complete.obs")
p2 <- ggcorrplot(cor_matrix, method = "square", type = "lower", lab = TRUE, lab_size = 3,
                 colors = c("#d73027", "#ffffbf", "#1a9850"),
                 title = "Matriks Korelasi Indikator Karyawan")
print(p2)

5.3 Plot 3: Diagram Jalur (Path Diagram)

plot(pls_model)