Bagian ini menyajikan statistik deskriptif untuk profil demografi responden, peta sebaran wilayah, dan statistik level item (Mean, SD, Skewness, Kurtosis).

3.1 Statistik Deskriptif Demografi

3.1.1 Jumlah Sampel

Kode
n_total <- nrow(data)
cat("Total Sampel (N):", n_total)
Total Sampel (N): 538

3.1.2 Jenis Kelamin

Kode
print_demo_table(
  data$Gender,
  c("1" = "Laki-laki", "2" = "Perempuan"),
  "Distribusi Jenis Kelamin"
)
Distribusi Jenis Kelamin
Kategori Frekuensi Persentase
Laki-laki 289 53.72%
Perempuan 249 46.28%

3.1.3 Usia

Kode
cat("Mean:", round(mean(data$Usia, na.rm = TRUE), 2), "tahun |",
    "SD:", round(sd(data$Usia, na.rm = TRUE), 2), "|",
    "Min:", min(data$Usia, na.rm = TRUE), "| Max:", max(data$Usia, na.rm = TRUE))
Mean: 19.74 tahun | SD: 1.39 | Min: 18 | Max: 24

3.1.4 Semester

Kode
print_demo_table(
    data$Semester,
    c("1" = "Semester 1-2", "2" = "Semester 2-3", "3" = "Semester 3-4",
      "4" = "Semester 5-6", "5" = "Semester 7 atau Lebih"),
    "Distribusi Semester"
)
Distribusi Semester
Kategori Frekuensi Persentase
Semester 1-2 174 32.34%
Semester 2-3 64 11.9%
Semester 3-4 157 29.18%
Semester 5-6 70 13.01%
Semester 7 atau Lebih 73 13.57%

3.1.4.1 Semester (Recode 3 Kategori)

Kode
data$Semester_Recode <- ifelse(data$Semester == 1, "Semester Awal (1-2)",
    ifelse(data$Semester %in% c(2, 3), "Semester Menengah (3-4)", "Semester Lanjut (>=5)"))
sem_recode_freq <- table(data$Semester_Recode, useNA = "ifany")
sem_recode_pct  <- prop.table(sem_recode_freq) * 100
knitr::kable(data.frame(
    Kategori = names(sem_recode_freq), Frekuensi = as.numeric(sem_recode_freq),
    Persentase = paste0(round(sem_recode_pct, 2), "%"), check.names = FALSE
), caption = "Semester (Recoded — 3 Kategori)", row.names = FALSE)
Semester (Recoded — 3 Kategori)
Kategori Frekuensi Persentase
Semester Awal (1-2) 174 32.34%
Semester Lanjut (>=5) 143 26.58%
Semester Menengah (3-4) 221 41.08%

3.1.5 Area Tempat Tinggal

Kode
print_demo_table(
  data$Area,
  c("1" = "Urban", "2" = "Suburban", "3" = "Rural"),
  "Distribusi Area Tempat Tinggal"
)
Distribusi Area Tempat Tinggal
Kategori Frekuensi Persentase
Urban 145 26.95%
Suburban 157 29.18%
Rural 236 43.87%

3.1.6 Tipe Hunian

Kode
print_demo_table(
  data$Hunian,
  c("1" = "Kos/Kontrakan", "2" = "Rumah Orang Tua/Wali", "3" = "Asrama", "4" = "Lainnya"),
  "Distribusi Tipe Hunian"
)
Distribusi Tipe Hunian
Kategori Frekuensi Persentase
Kos/Kontrakan 345 64.13%
Rumah Orang Tua/Wali 142 26.39%
Asrama 42 7.81%
Lainnya 9 1.67%

3.1.7 Pengetahuan Lingkungan

Kode
print_demo_table(
  data$Knowl,
  c("0" = "Tidak", "1" = "Ya"),
  "Pengetahuan tentang Lingkungan"
)
Pengetahuan tentang Lingkungan
Kategori Frekuensi Persentase
Tidak 333 61.9%
Ya 205 38.1%

3.1.8 Lama Bergabung dengan Organisasi Lingkungan

Kode
print_demo_table(
  data$Bergabung,
  c("1" = "1-2 Tahun", "2" = "2-3 Tahun", "3" = "Lebih dari 3 Tahun"),
  "Lama Bergabung"
)
Lama Bergabung
Kategori Frekuensi Persentase
1-2 Tahun 378 70.26%
2-3 Tahun 106 19.7%
Lebih dari 3 Tahun 54 10.04%

3.1.9 Provinsi

Kode
print_demo_table(
  data$Provinsi,
  c("1" = "Jawa Barat", "2" = "Jawa Tengah", "3" = "Jawa Timur", 
    "4" = "Banten", "5" = "DIY", "6" = "DKI Jakarta"),
  "Distribusi Provinsi"
)
Distribusi Provinsi
Kategori Frekuensi Persentase
Jawa Barat 33 6.13%
Jawa Tengah 263 48.88%
Jawa Timur 51 9.48%
Banten 3 0.56%
DIY 177 32.9%
DKI Jakarta 11 2.04%

3.1.10 Status Anggota

Kode
print_demo_table(
  data$Anggota,
  c("1" = "Pengurus", "2" = "Anggota Biasa"),
  "Status Anggota"
)
Status Anggota
Kategori Frekuensi Persentase
Pengurus 176 32.71%
Anggota Biasa 362 67.29%

3.2 Peta Sebaran Responden

Kode peta sebaran responden
library(sf)
library(geodata)
library(ggtext)

data_responden <- data.frame(
    NAME_1 = c("Jawa Barat", "Jawa Tengah", "Jawa Timur", "Banten", "Yogyakarta", "Jakarta Raya"),
    Nama_Dalam = c("Jawa Barat", "Jawa Tengah", "Jawa Timur", "Banten", "DIY", "DKI Jakarta"),
    Label_Luar = c(
        "italic(n)==33~~'(6.1%)'", "italic(n)==263~~'(48.9%)'",
        "italic(n)==51~~'(9.5%)'", "italic(n)==3~~'(0.6%)'",
        "italic(n)==177~~'(32.9%)'", "italic(n)==11~~'(2.0%)'"
    ),
    Frekuensi = c(33, 263, 51, 3, 177, 11),
    stringsAsFactors = FALSE
)

gadm_dir <- file.path(getwd(), "gadm_cache")
if (!dir.exists(gadm_dir)) dir.create(gadm_dir)
jawa_sf <- geodata::gadm("IDN", level = 1, path = gadm_dir) |>
    sf::st_as_sf() |>
    dplyr::filter(NAME_1 %in% data_responden$NAME_1) |>
    dplyr::left_join(data_responden, by = "NAME_1")

suppressWarnings(cents <- sf::st_centroid(jawa_sf))
coords <- sf::st_coordinates(cents)
jawa_sf$cx <- coords[, 1]; jawa_sf$cy <- coords[, 2]

offset <- list(
    "Banten" = c(105.87, -6.85, 105.40, -7.40),
    "Jakarta Raya" = c(106.83, -6.08, 107.00, -5.53),
    "Jawa Barat" = c(108.30, -6.23, 108.30, -5.53),
    "Jawa Tengah" = c(110.10, -6.84, 110.10, -5.53),
    "Yogyakarta" = c(110.45, -8.12, 110.45, -8.70),
    "Jawa Timur" = c(112.73, -7.00, 112.73, -5.53)
)

jawa_sf <- jawa_sf |> dplyr::mutate(
    ax = dplyr::case_when(NAME_1 == "Banten" ~ offset[["Banten"]][1], NAME_1 == "Jakarta Raya" ~ offset[["Jakarta Raya"]][1], NAME_1 == "Jawa Barat" ~ offset[["Jawa Barat"]][1], NAME_1 == "Jawa Tengah" ~ offset[["Jawa Tengah"]][1], NAME_1 == "Yogyakarta" ~ offset[["Yogyakarta"]][1], NAME_1 == "Jawa Timur" ~ offset[["Jawa Timur"]][1]),
    ay = dplyr::case_when(NAME_1 == "Banten" ~ offset[["Banten"]][2], NAME_1 == "Jakarta Raya" ~ offset[["Jakarta Raya"]][2], NAME_1 == "Jawa Barat" ~ offset[["Jawa Barat"]][2], NAME_1 == "Jawa Tengah" ~ offset[["Jawa Tengah"]][2], NAME_1 == "Yogyakarta" ~ offset[["Yogyakarta"]][2], NAME_1 == "Jawa Timur" ~ offset[["Jawa Timur"]][2]),
    lx = dplyr::case_when(NAME_1 == "Banten" ~ offset[["Banten"]][3], NAME_1 == "Jakarta Raya" ~ offset[["Jakarta Raya"]][3], NAME_1 == "Jawa Barat" ~ offset[["Jawa Barat"]][3], NAME_1 == "Jawa Tengah" ~ offset[["Jawa Tengah"]][3], NAME_1 == "Yogyakarta" ~ offset[["Yogyakarta"]][3], NAME_1 == "Jawa Timur" ~ offset[["Jawa Timur"]][3]),
    ly = dplyr::case_when(NAME_1 == "Banten" ~ offset[["Banten"]][4], NAME_1 == "Jakarta Raya" ~ offset[["Jakarta Raya"]][4], NAME_1 == "Jawa Barat" ~ offset[["Jawa Barat"]][4], NAME_1 == "Jawa Tengah" ~ offset[["Jawa Tengah"]][4], NAME_1 == "Yogyakarta" ~ offset[["Yogyakarta"]][4], NAME_1 == "Jawa Timur" ~ offset[["Jawa Timur"]][4]),
    nx = dplyr::case_when(NAME_1 == "Banten" ~ 106.20, NAME_1 == "Jakarta Raya" ~ cx + 0.05, NAME_1 == "Jawa Tengah" ~ cx - 0.25, NAME_1 == "Jawa Timur" ~ cx - 0.10, TRUE ~ cx),
    ny = dplyr::case_when(NAME_1 == "Banten" ~ -6.38, NAME_1 == "Jawa Tengah" ~ cy + 0.15, NAME_1 == "Jawa Timur" ~ cy - 0.10, TRUE ~ cy)
)

ggplot(jawa_sf) +
    geom_sf(aes(fill = Frekuensi), color = "#777777", linewidth = 0.30) +
    scale_fill_distiller(palette = "YlGnBu", direction = 1, name = "Jumlah Responden (n)",
        breaks = c(3, 50, 100, 177, 263), labels = scales::comma, limits = c(0, 270),
        guide = guide_colorbar(title.position = "top", title.hjust = 0.5,
                               barwidth = 17, barheight = 0.6)) +
    geom_text(aes(x = nx, y = ny, label = Nama_Dalam), size = 3.0, fontface = "bold", color = "#0a1628") +
    geom_segment(aes(x = ax, y = ay, xend = lx, yend = ly), color = "#888888", linewidth = 0.28) +
    geom_label(aes(x = lx, y = ly, label = Label_Luar), parse = TRUE, size = 3.0,
               fill = "white", color = "#1a1a2e", alpha = 0.95, linewidth = 0.20,
               label.r = unit(0.10, "lines"), label.padding = unit(0.25, "lines")) +
    labs(caption = "<i>Catatan.</i> N = 538. Peta menggunakan shapefile GADM v4.1.") +
    coord_sf(xlim = c(104.0, 115.5), ylim = c(-9.2, -5.0), expand = FALSE) +
    theme_minimal(base_size = 11) +
    theme(plot.caption = ggtext::element_markdown(size = 9, hjust = 0, color = "#666666"),
          legend.position = "bottom", panel.grid = element_blank(),
          axis.text = element_blank(), axis.ticks = element_blank(), axis.title = element_blank(),
          panel.background = element_rect(fill = "#EAF3FB", color = NA))


3.3 Statistik Deskriptif Level Item

Statistik deskriptif semua item P (PEB), C (CCS), dan O (OL) untuk data screening dan pengecekan normalitas univariat.

Kode
item_cols <- names(data)[grep("^P\\d+$|^C\\d+$|^O\\d*$", names(data))]
item_desc <- psych::describe(data[, item_cols], type = 2)
item_stats <- data.frame(
    Item = item_cols, N = item_desc$n,
    Mean = round(item_desc$mean, 3), SD = round(item_desc$sd, 3),
    Min = item_desc$min, Max = item_desc$max,
    Skewness = round(item_desc$skew, 3), Kurtosis = round(item_desc$kurtosis, 3),
    check.names = FALSE
)
knitr::kable(item_stats, caption = "Statistik Deskriptif Level Item", row.names = FALSE)
Statistik Deskriptif Level Item
Item N Mean SD Min Max Skewness Kurtosis
C1 538 3.307 1.484 1 7 0.157 -0.580
C2 538 3.273 1.606 1 7 0.391 -0.798
C3 538 2.115 1.254 1 7 1.122 0.634
C4 538 2.745 1.439 1 7 0.659 -0.174
C5 538 3.052 1.563 1 7 0.483 -0.473
C6 538 2.375 1.271 1 7 0.665 -0.259
C7 538 2.684 1.145 1 6 0.133 -1.062
C8 538 2.364 1.495 1 7 0.988 0.192
C9 538 2.688 1.591 1 7 0.788 -0.226
C10 538 2.946 1.547 1 7 0.545 -0.403
C11 538 2.463 1.547 1 7 1.029 0.323
C12 538 2.063 1.100 1 5 0.776 -0.634
P1 538 4.249 0.988 1 5 -1.259 0.915
P2 538 3.794 1.145 1 5 -0.665 -0.352
P3 538 3.487 1.187 1 5 -0.395 -0.684
P4 538 3.779 0.994 1 5 -0.265 -0.747
P5 538 4.424 0.845 1 5 -1.377 1.191
P6 538 4.145 0.879 1 5 -0.700 -0.273
P7 538 3.301 1.175 1 5 -0.284 -0.519
P8 538 3.916 1.026 1 5 -0.517 -0.506
P9 538 4.288 0.870 1 5 -0.900 -0.062
P10 538 3.561 1.054 1 5 -0.229 -0.356
P11 538 3.375 0.899 1 5 0.114 0.006
P12 538 3.593 0.852 1 5 0.034 -0.147
P13 538 3.416 0.885 1 5 0.101 0.031
P14 538 3.582 0.858 1 5 0.011 -0.076
P15 538 3.738 0.854 1 5 0.063 -0.732
P16 538 3.059 1.194 1 5 -0.128 -0.719
P17 538 3.517 0.969 1 5 -0.300 -0.080
P18 538 2.455 1.284 1 5 0.481 -0.817
P19 538 2.786 1.330 1 5 0.159 -1.071
P20 538 3.753 0.953 1 5 -0.459 0.095
O1 538 3.831 0.830 1 5 -0.225 -0.208
O2 538 3.848 0.823 1 5 -0.194 -0.347
O3 538 3.822 0.860 1 5 -0.354 0.002
O4 538 3.619 0.873 1 5 -0.157 -0.177
O5 538 3.894 0.813 1 5 -0.200 -0.376
O6 538 4.132 0.805 1 5 -0.437 -0.694
O7 538 4.024 0.826 1 5 -0.443 -0.131
O8 538 3.862 0.859 1 5 -0.281 -0.242
O9 538 4.030 0.797 1 5 -0.297 -0.550
O10 538 3.879 0.831 1 5 -0.161 -0.705
O11 538 3.978 0.818 1 5 -0.286 -0.535
O12 538 3.875 0.838 1 5 -0.201 -0.558
O13 538 3.974 0.807 1 5 -0.209 -0.749
O14 538 3.853 0.834 1 5 -0.280 -0.176
O15 538 3.987 0.773 1 5 -0.123 -0.828
CatatanCatatan Metodologis
  • Skewness & Kurtosis: Type 2 (psych::describe), konsisten dengan JASP/SPSS
  • Kurtosis = Excess Kurtosis (distribusi normal = 0)
  • Threshold (West et al., 1995): Skewness > |2.0|, Kurtosis > |7.0|

3.3.1 Visualisasi Deskriptif Item — Dot Plot

Kode plot deskriptif item
# ---- Bangun data frame plot dari item_stats ----
item_plot_df <- item_stats |>
    mutate(
        prefix = gsub("[0-9]", "", Item),
        Midpoint = ifelse(prefix == "C", 4, 3),
        MaxSkala = ifelse(prefix == "C", 7, 5),
        Skala = case_when(
            prefix == "C" ~ "CCS",
            prefix == "O" ~ "OL",
            prefix == "P" ~ "PEB"
        ),
        item_num = as.integer(gsub("[A-Za-z]", "", Item))
    ) |>
    arrange(prefix, Mean) |>
    mutate(
        Skala   = factor(Skala, levels = c("CCS", "OL", "PEB")),
        Item    = factor(Item, levels = unique(Item)),
        label_x = Mean + SD + 0.08
    )

# Data zona (di bawah / di atas midpoint)
zone_df <- item_plot_df |> distinct(Skala, Midpoint, MaxSkala)

# Batas x-axis (memaksa 1–MaxSkala per panel via geom_blank)
ref_items <- item_plot_df |>
    group_by(Skala) |>
    slice(1) |>
    ungroup() |>
    select(Skala, Item)

limits_df <- bind_rows(
    zone_df |> select(Skala) |> mutate(x_lim = 1),
    zone_df |> transmute(Skala, x_lim = MaxSkala)
) |>
    left_join(ref_items, by = "Skala")

# Anotasi: rentang mean + interpretasi per panel
range_annot <- item_plot_df |>
    summarise(
        lo = min(Mean),
        hi = max(Mean),
        Midpoint = first(Midpoint),
        MaxSkala = first(MaxSkala),
        .by = Skala
    ) |>
    mutate(
        skala_label = case_when(Skala == "CCS" ~ "Skala 1\u20137", TRUE ~ "Skala 1\u20135"),
        x_center    = (1 + MaxSkala) / 2,
        label       = sprintf("%s, Mean: %.2f\u2013%.2f", skala_label, lo, hi)
    )

# ---- Plot ----
p_item_means <- ggplot(item_plot_df, aes(x = Mean, y = Item)) +
    # Zona: di bawah midpoint (merah muda lembut)
    geom_rect(
        data = zone_df,
        aes(xmin = 1, xmax = Midpoint, ymin = -Inf, ymax = Inf),
        inherit.aes = FALSE, fill = "#FFF0EE"
    ) +
    # Zona: di atas midpoint (biru muda lembut)
    geom_rect(
        data = zone_df,
        aes(xmin = Midpoint, xmax = MaxSkala, ymin = -Inf, ymax = Inf),
        inherit.aes = FALSE, fill = "#EDF4FC"
    ) +
    # Paksa batas sumbu x sesuai rentang skala asli
    geom_blank(data = limits_df, aes(x = x_lim, y = Item), inherit.aes = FALSE) +
    # Garis midpoint
    geom_vline(
        data = zone_df, aes(xintercept = Midpoint),
        linetype = "longdash", color = "grey40", linewidth = 0.5
    ) +
    # Error bar \u00b11 SD
    geom_errorbarh(aes(xmin = Mean - SD, xmax = Mean + SD),
        height = 0.25, linewidth = 0.3, color = "grey55"
    ) +
    # Titik mean — warna berdasarkan posisi terhadap midpoint
    geom_point(aes(fill = Mean < Midpoint),
        shape = 21, size = 2.3,
        color = "grey30", stroke = 0.3
    ) +
    scale_fill_manual(
        values = c("TRUE" = "#E74C3C", "FALSE" = "#2980B9"),
        labels = c(
            "TRUE" = "Di bawah midpoint",
            "FALSE" = "Di atas midpoint"
        ),
        name = "Mean"
    ) +
    # Label nilai mean — di kanan ujung error bar (tidak menimpa titik/garis)
    geom_text(aes(x = label_x, label = sprintf("%.2f", Mean)),
        hjust = 0, vjust = 0.4, size = 2.2, color = "grey15",
        fontface = "bold"
    ) +
    # Anotasi skala + rentang mean (di luar panel, bawah x-axis)
    geom_text(
        data = range_annot,
        aes(x = x_center, y = -Inf, label = label),
        inherit.aes = FALSE, vjust = 3.8, hjust = 0.5,
        size = 2.8, color = "grey30", fontface = "bold"
    ) +
    coord_cartesian(clip = "off") +
    # Facet: 3 kolom (CCS | OL | PEB)
    facet_wrap(~Skala, scales = "free", ncol = 3) +
    # Integer breaks — tidak ada desimal di sumbu x
    scale_x_continuous(
        breaks = function(lims) seq(ceiling(lims[1]), floor(lims[2])),
        expand = expansion(mult = c(0.02, 0.12))
    ) +
    labs(
        title = "Item-Level Descriptive Statistics",
        x = NULL,
        y = NULL,
        caption = paste0(
            "N = ", nrow(data), ".  ",
            "Zona merah muda = di bawah midpoint (rendah); ",
            "zona biru muda = di atas midpoint (tinggi).\n",
            "Garis putus-putus = titik tengah skala (midpoint). ",
            "Garis horizontal = \u00b11 SD."
        )
    ) +
    theme_bw(base_size = 10) +
    theme(
        plot.title = element_text(face = "bold", size = 13, hjust = 0.5),
        plot.caption = element_text(
            size = 7.5, color = "grey45", hjust = 0,
            margin = margin(t = 8), lineheight = 1.2
        ),
        plot.margin = margin(10, 12, 35, 10),
        axis.text.y = element_text(size = 9, face = "bold"),
        axis.text.x = element_text(size = 9, face = "bold"),
        strip.text = element_text(face = "bold", size = 12),
        strip.background = element_rect(fill = "grey93", color = "grey70"),
        panel.grid.major.y = element_blank(),
        panel.grid.major.x = element_line(color = "grey92", linewidth = 0.25),
        panel.grid.minor = element_blank(),
        panel.spacing = unit(1.0, "lines"),
        legend.position = "bottom",
        legend.direction = "horizontal",
        legend.title = element_text(face = "bold", size = 9),
        legend.text = element_text(size = 9),
        legend.key.size = unit(0.5, "cm"),
        legend.margin = margin(t = 5, b = 0),
        legend.box.margin = margin(t = 0)
    )

print(p_item_means)