Analisis Statistik Deskriptif untuk Segmentasi Risiko Asuransi: Fokus pada Kecenderungan Pusat dan Dispersi

Laporan Ujian Tengah Semester (UTS) Statistika Dasar: Eksplorasi Volatilitas Klaim, Korelasi BMI, dan Identifikasi Prediktor Risiko Utama

 Tim Anggota Kelompok


Ringkasan Video Presentasi UTS

Video di bawah ini menyajikan ringkasan eksekutif dan temuan kunci dari Analisis Asuransi ini.



1 Pendahuluan

1.1 Latar Belakang

Laporan ini menyajikan analisis eksploratif mendalam terhadap dataset pemegang polis asuransi. Dalam industri ini, pengambilan keputusan berbasis data sangat penting untuk mengelola risiko, menetapkan harga polis, dan memprediksi klaim pelanggan. Analisis ini didasarkan pada prinsip-prinsip Statistik Deskriptif untuk eksplorasi data, yang mencakup tendensi pusat dan dispersi data.

1.2 Rumusan Masalah

  1. Bagaimana distribusi variabel kunci numerik (Age dan ClaimAmount), dan apa implikasinya terhadap asimetri data (skewness), diukur melalui perbandingan Mean dan Median?

  2. Variabel mana yang menunjukkan variabilitas data paling besar, dan apa yang diungkapkan oleh ukuran dispersi (Range, Variance, SD, IQR) mengenai ketidakpastian risiko?

  3. Pola atau hubungan signifikan apa yang dapat diidentifikasi dari visualisasi multivariat yang melibatkan semua variabel dalam dataset, termasuk kategori produk asuransi (InsurancePlan) dan status kesehatan (SmokerStatus, BMI)?

1.3 Tujuan Analisis

  1. Melakukan pembersihan data awal, khususnya penanganan nilai negatif yang anomali dalam variabel AnnualIncome.

  2. Menghitung dan menginterpretasikan metrik kecenderungan pusat (Mean, Median) dan ukuran dispersi untuk variabel-variabel kunci asuransi (Age dan ClaimAmount).

  3. Membuat dan menginterpretasikan minimal lima jenis visualisasi data eksploratif berkualitas tinggi menggunakan ggplot2.

  4. Menyediakan ringkasan eksekutif yang merangkum temuan-temuan signifikan dari analisis.


2 Persiapan Data dan Deskripsi Variabel

2.1 Pemuatan dan Penanganan Data

Langkah pembersihan data krusial adalah penanganan anomali pada AnnualIncome, di mana nilai negatif diubah menjadi nol (0) untuk memastikan perhitungan statistik yang akurat. Variabel kategori dikonversi menjadi tipe faktor. Proses ini merupakan langkah awal penting dalam eksplorasi data (dsciencelabs 2024b).

2.2 Landasan Teori

Bagian ini menyajikan landasan teori statistik yang digunakan dalam analisis deskriptif data,
memberikan definisi dan rumus matematis sebagai kerangka kerja metodologis.

2.2.1 Kecenderungan Sentral (Central Tendency)

Kecenderungan sentral adalah ukuran statistik yang menunjukkan nilai pusat atau rata-rata
dari suatu set data, tempat nilai data cenderung berkumpul.

Kecenderungan sentral adalah ukuran statistik yang menunjukkan nilai pusat atau rata-rata
dari suatu set data, tempat nilai data cenderung berkumpul.

  1. Mean (Rata-rata)

    • Mean populasi: \[ \mu = \frac{1}{N}\sum_{i=1}^{N} x_i \]
    • Mean sampel: \[ \bar{x} = \frac{1}{n}\sum_{i=1}^{n} x_i \]
  2. Median (Nilai Tengah)
    Median adalah nilai tengah setelah data diurutkan. Secara definisi: \[ \text{Median} = \begin{cases} x_{\frac{n+1}{2}}, & \text{jika } n \text{ ganjil} \\ \dfrac{x_{\frac{n}{2}} + x_{\frac{n}{2}+1}}{2}, & \text{jika } n \text{ genap} \end{cases} \]

  3. Mode (Modus) Mode adalah nilai atau kategori yang paling sering muncul dalam set data.
    Tidak terpengaruh oleh outlier dan cocok untuk data kategorik.

  • Mode Data sampel

    \[\text{Mode} = \text{nilai dengan frekuensi tertinggi}\]

  • Mode Data populasi

    \[ \text{Mode} = L + \left(\frac{d_1}{d_1 + d_2}\right)\cdot c\]

dengan:

  • \(L\) = batas bawah kelas modus
  • \(d_1 = f_1 - f_0\)
  • \(d_2 = f_1 - f_2\)
  • \(c\) = lebar kelas

2.2.2 Dispersi (Sebaran Data)

Dispersi adalah ukuran yang menunjukkan tingkat variasi atau sebaran data dari nilai pusatnya. Dispersi yang besar menunjukkan risiko atau heterogenitas yang tinggi.

  1. Variance (Ragam)

    • Variance populasi: \[ \sigma^2 = \frac{1}{N}\sum_{i=1}^{N}(x_i - \mu)^2 \]
    • Variance sampel: \[ s^2 = \frac{1}{n-1}\sum_{i=1}^{n}(x_i - \bar{x})^2 \]
  2. Standard Deviation (Simpangan Baku)

    • Populasi: \[ \sigma = \sqrt{\frac{1}{N}\sum_{i=1}^{N}(x_i - \mu)^2} \]
    • Sampel: \[ s = \sqrt{\frac{1}{n-1}\sum_{i=1}^{n}(x_i - \bar{x})^2} \]
  3. Interquartile Range (IQR — Rentang Antar Kuartil)
    IQR mengukur sebaran 50% data tengah: \[ \mathrm{IQR} = Q_3 - Q_1 \] di mana \(Q_1\) dan \(Q_3\) berturut-turut adalah kuartil pertama dan ketiga.

  4. Range (Rentang)
    \[ \mathrm{Range} = \max(x) - \min(x) \]

  5. Coefficient of Variation (Koefisien Variasi)
    CV mengukur sebaran relatif dengan membandingkan simpangan baku terhadap mean.

    • Poupulasi

\[CV = \frac{\sigma}{\mu} \times 100\%\] - Sampel

\[CV = \frac{s}{\bar{x}} \times 100\%\]

Keterangan:

  • \(CV\) = Coefficient of Variation
  • \(\sigma\) = Simpangan baku populasi
  • \(\bar{x}\) = Rata-rata sampel
  • \(\mu\) = Rata-rata populasi
  • \(s\) = Simpangan baku sampel

2.2.3 Central Density

Central Density menunjukkan tingkat kepadatan dan bentuk distribusi data (misalnya, simetris atau miring/skewed) di sekitar nilai pusat. Analisis ini sangat penting untuk memahami apakah Mean, Median, atau Mode yang paling tepat untuk mewakili data.

  1. Contoh: PDF Distribusi Normal
    Fungsi densitas distribusi normal: \[ f(x)=\frac{1}{\sigma\sqrt{2\pi}} \exp\!\left(-\frac{(x-\mu)^2}{2\sigma^2}\right) \] Nilai central density pada pusat (mean \(\mu\)) adalah: \[ f(\mu)=\frac{1}{\sigma\sqrt{2\pi}} \]

  2. Kernel Density Estimation (KDE) — untuk data empiris
    Estimasi densitas di titik \(x\): \[ \hat{f}(x)=\frac{1}{n h}\sum_{i=1}^{n} K\!\left(\frac{x - x_i}{h}\right) \] di mana:

    • \(K(\cdot)\) = kernel (mis. Gaussian kernel),
    • \(h\) = bandwidth (parameter smoothing),
    • \(n\) = jumlah sampel.

2.3 Tampilan Data Mentah

# Memuat data
df <- read_csv("insurances.csv", show_col_types = FALSE)

# Menghapus kolom indeks yang tidak bernama
if (colnames(df)[1] %in% c("...", "Unnamed: 0", "X1", "1")) {
  df <- df %>% select(-1)
}

# Daftar lengkap kolom numerik
numerical_cols <- c("Age", "BMI", "Dependents", "AnnualIncome", "ExerciseHours", "HealthScore", "ClaimAmount", "RiskScore")

for (col in numerical_cols) {
  df[[col]] <- as.numeric(df[[col]])
}

# Penanganan Anomali AnnualIncome
df <- df %>%
  mutate(AnnualIncome = replace(AnnualIncome, AnnualIncome < 0, 0))

# Mengkonversi kolom kategori ke factor
categorical_cols <- c("Region", "InsurancePlan", "Gender", "SmokerStatus", "EmploymentType")
for (col in categorical_cols) {
  df[[col]] <- as.factor(df[[col]])
}

# Untuk Membuat kategori polis
df <- df %>%
  mutate(PolicyCategory = factor(case_when(
    InsurancePlan %in% c("Platinum", "Gold") ~ "Premium (Gold/Platinum)",
    TRUE ~ "Standar (Basic/Silver)"
  )))

# Tampilan Data
datatable(
  df %>% select(-PolicyCategory),  
  caption = 'Data Mentah Asuransi',
  extensions = 'Buttons',
  rownames = FALSE,
  options = list(
    pageLength = 10,
    dom = 'Bfrtip',
    buttons = c('copy', 'csv', 'excel'),
    scrollX = FALSE,
    autoWidth = TRUE,  
    scrollCollapse = TRUE,
    columnDefs = list(list(className = 'dt-center', targets = '_all'))
  ),
  class = 'cell-border stripe nowrap', 
  width = '100%'                        
) %>%
  formatRound(columns = numerical_cols, digits = 2)

2.4 Deskripsi Variabel (Data Dictionary)

Bagian ini menyajikan deskripsi rinci dari setiap variabel yang digunakan dalam analisis, mengelompokkannya berdasarkan tipe data dan peran dalam konteks polis asuransi.

2.4.1 Variabel Kategori (Faktor)

Variabel-variabel ini merepresentasikan karakteristik kualitatif dari pemegang polis.

# Data Frame untuk Variabel Kategori
df_kategori <- data.frame(
  Variabel = c("Region", "InsurancePlan", "Gender", "SmokerStatus", "EmploymentType", "PolicyCategory"),
  Deskripsi = c(
    "Wilayah geografis pemegang polis.",
    "Jenis paket asuransi yang dimiliki.",
    "Jenis kelamin pemegang polis.",
    "Status merokok pemegang polis.",
    "Jenis pekerjaan pemegang polis.",
    "Variabel turunan yang mengelompokkan plan menjadi dua kategori untuk analisis."
  ),
  Level.Kategori.Kunci = c(
    "Urban, Suburban, Rural",
    "Basic, Silver, Gold, Platinum",
    "Male, Female",
    "Smoker, Non-Smoker",
    "Private, Government, Self-Employed, Unemployed",
    "Premium (Gold/Platinum), Standar (Basic/Silver)"
  )
)

# Menampilkan tabel 
datatable(
  df_kategori,
  caption = 'Deskripsi Variabel Kategori',
  rownames = FALSE,
  colnames = c('Variabel', 'Deskripsi', 'Level / Kategori Kunci'),
  options = list(
    dom = 't', 
    ordering = FALSE
  )
)

2.4.2 Variabel Numerik (Kuantitatif)

Variabel-variabel ini merepresentasikan data terukur atau terhitung yang akan menjadi fokus utama analisis Kecenderungan Sentral dan Dispersi.

# Data Frame untuk Variabel Numerik
df_numerik <- data.frame(
  Variabel = c("Age", "BMI", "Dependents", "AnnualIncome", "ExerciseHours", "HealthScore", "ClaimAmount", "RiskScore"),
  Deskripsi = c(
    "Usia pemegang polis.",
    "Body Mass Index (Indeks Massa Tubuh).",
    "Jumlah tanggungan dalam keluarga (anak, dll.).",
    "Pendapatan tahunan pemegang polis.",
    "Rata-rata jam olahraga per minggu.",
    "Skor kesehatan gabungan yang ditetapkan oleh perusahaan asuransi.",
    "Jumlah klaim asuransi yang diajukan oleh pemegang polis.",
    "Skor risiko keseluruhan yang dihitung."
  ),
  Satuan.Skala = c("Tahun", "Skor (kg/m²)", "Jumlah", "Ribuan USD", "Jam", "Skor (0-100)", "Ribuan USD", "Skor"),
  Relevansi.dalam.Asuransi = c(
    "Faktor utama dalam risiko dan penentuan premi.",
    "Indikator kesehatan; nilai tinggi (>25) dapat meningkatkan risiko.",
    "Mempengaruhi ukuran keluarga dan potensi klaim.",
    "Indikator kemampuan finansial.",
    "Indikator gaya hidup sehat; nilai tinggi dapat menurunkan risiko.",
    "Penilaian risiko kesehatan secara umum.",
    "Variabel hasil utama (target); mengukur biaya asuransi.",
    "Variabel hasil; hasil akhir dari semua faktor risiko."
  )
)

# Menampilkan tabel
datatable(
  df_numerik,
  caption = 'Deskripsi Variabel Numerik',
  rownames = FALSE,
  colnames = c('Variabel', 'Deskripsi', 'Satuan / Skala', 'Relevansi dalam Asuransi'),
  options = list(
    dom = 't', 
    ordering = FALSE
  )
)

3 Analisis Statistik Deskriptif

3.1 Ukuran Kecenderungan Pusat (Central Tendency)

Kami menyajikan Mean (\(\bar{x}\)), Median, dan Modus untuk semua variabel numerik (Tabel 1). Perbandingan nilai-nilai ini merupakan indikator utama untuk mengevaluasi kemencengan (Skewness) distribusi data, sesuai dengan prinsip statistik dasar (dsciencelabs 2024a).

Rumus Rata-Rata Sampel (Mean): \[ \bar{x} = \frac{\sum_{i=1}^{n} x_i}{n} \]

# Fungsi untuk menghitung Mode
get_mode <- function(v) {
  v_filtered <- v[!is.na(v)] 
  if(length(v_filtered) == 0) return(NA)
  uniq_v <- unique(v_filtered)
  mode_val <- uniq_v[which.max(tabulate(match(v_filtered, uniq_v)))]
  return(mode_val[1]) 
}

# Menghitung metrik untuk semua kolom numerik
central_tendency_all <- df %>%
  summarise(
    across(all_of(numerical_cols),  
            list(
              Mean = ~mean(., na.rm = TRUE), 
              Median = ~median(., na.rm = TRUE),
              Mode = ~get_mode(.)
            )
    )
  ) %>%
  pivot_longer(everything(), names_to = c("Variable", "Metric"), names_sep = "_", values_to = "Value") %>%
  pivot_wider(names_from = Metric, values_from = Value)

# Reformat untuk tampilan DT
central_tendency_dt <- central_tendency_all %>%
  mutate(
    Skewness = case_when(
      (Mean - Median) > 0.05 ~ "Positif (Kanan)", 
      (Mean - Median) < -0.05 ~ "Negatif (Kiri)",  
      TRUE ~ "Simetris"
    )
  )

# Menampilkan hasil
datatable(central_tendency_dt, caption = 'Tabel 1: Ukuran Kecenderungan Pusat untuk Semua Variabel Numerik', 
          options = list(dom = 't', columnDefs = list(list(className = 'dt-center', targets = '_all')))) %>% 
  formatRound(columns = c('Mean', 'Median', 'Mode'), digits = 2)

3.2 Ukuran Dispersi (Measures of Dispersion)

Analisis Standar Deviasi (SD), Varians, Range, dan IQR (Tabel 2) diterapkan untuk mengukur sebaran absolut data. Koefisien Variasi (CV) digunakan sebagai metrik non-dimensi untuk membandingkan variabilitas relatif antar variabel dengan satuan yang berbeda (dsciencelabs 2024c).

  • Rumus Standar Deviasi Sampel (\(s\)): \[ s = \sqrt{\frac{1}{n-1} \sum_{i=1}^{n} (x_i - \bar{x})^2} \]

  • Rumus Koefisien Variasi (CV): \[ CV = \frac{s}{\bar{x}} \]

# Menghitung metrik dispersi untuk semua kolom numerik
dispersion_metrics_all <- df %>%
  summarise(
    across(all_of(numerical_cols), 
            list(
              Range = ~max(., na.rm = TRUE) - min(., na.rm = TRUE), 
              Variance = ~var(., na.rm = TRUE), 
              SD = ~sd(., na.rm = TRUE), 
              IQR = ~IQR(., na.rm = TRUE)
            )
    )
  ) %>%
  pivot_longer(everything(), names_to = c("Variable", "Metric"), 
               names_sep = "_", values_to = "Value") %>%
  pivot_wider(names_from = Metric, values_from = Value)

# Mengambil nilai mean dari tabel central_tendency_all
mean_vals <- central_tendency_all %>% 
  select(Variable, Mean)

# Menggabungkan dan menghitung CV
dispersion_metrics_cv <- dispersion_metrics_all %>%
  left_join(mean_vals, by = "Variable") %>%
  mutate(CV = SD / Mean) %>%
  arrange(desc(CV)) %>% 
  select(Variable, Range, Variance, SD, IQR, CV)

# Output tabel DT
datatable(dispersion_metrics_cv, caption = 'Tabel 2: Ukuran Dispersi dan Koefisien Variasi (CV) - Diurutkan berdasarkan Variabilitas', 
          options = list(dom = 't', columnDefs = list(list(className = 'dt-center', targets = '_all')))) %>% 
  formatRound(columns = c('Range', 'Variance', 'SD', 'IQR', 'CV'), digits = 2)
Koefisien Variasi (CV) menunjukkan variabilitas relatif :
  • Variabel dengan Variabilitas Terbesar: Dependents. Dispersi relatif tertinggi didominasi oleh AnnualIncome, diikuti oleh ClaimAmount (CV=0.53). Tingginya CV menunjukkan ketidakpastian tinggi dan kesulitan prediksi.

  • Variabel yang Paling Konsisten: HealthScore (Age, CV=0.37). Nilai ini menunjukkan sebaran data yang paling ketat di sekitar Mean, membuktikan bahwa variabel ini adalah yang paling stabil dalam populasi.

3.3 Ukuran Bentuk Distribusi (Skewness & Kurtosis)

Skewness mengukur simetri distribusi (kemencengan), di mana nilai \(>0\) berarti menceng ke kanan (ekor kanan lebih panjang), dan nilai \(<0\) berarti menceng ke kiri. Kurtosis mengukur “puncak” distribusi (keruncingan), di mana nilai \(>3\) (atau \(>0\) dalam beberapa software) menunjukkan distribusi leptokurtic (puncak runcing, ekor tebal), yang dapat mengindikasikan adanya outlier.

  • Rumus Skewness Sampel (\(\text{Sk}\)): \[ \text{Sk} = \frac{\frac{1}{n} \sum_{i=1}^{n} (x_i - \bar{x})^3}{s^3} \]

  • Rumus Kurtosis Sampel (\(\text{Ku}\)): \[ \text{Ku} = \frac{\frac{1}{n} \sum_{i=1}^{n} (x_i - \bar{x})^4}{s^4} \]

# Menghitung metric skewness dan kurtosis
shape_metrics <- df %>%
  summarise(
    across(all_of(numerical_cols), 
            list(
              Skewness = ~skewness(., na.rm = TRUE),
              Kurtosis = ~kurtosis(., na.rm = TRUE)
            )
    )
  ) %>%
  pivot_longer(everything(), names_to = c("Variable", "Metric"), 
               names_sep = "_", values_to = "Value") %>%
  pivot_wider(names_from = Metric, values_from = Value)

# Output tabel DT
datatable(shape_metrics, caption = 'Tabel 3: Ukuran Bentuk Distribusi (Skewness & Kurtosis)', 
          options = list(dom = 't', columnDefs = list(list(className = 'dt-center', targets = '_all')))) %>% 
  formatRound(columns = c('Skewness', 'Kurtosis'), digits = 3)

4 Visualisasi Data Eksploratif

4.1 Visualisasi Distribusi dan Kecenderungan Pusat

Visualisasi di bagian ini mengkonfirmasi dan mengeksplorasi bentuk distribusi, serta memvisualisasikan posisi pusat data (mean, median, mode) dari variabel kunci.

4.1.1 Distribusi Jumlah Klaim (ClaimAmount)

# Hitung Mean, Median, dan Modus ClaimAmount
mean_claim <- mean(df$ClaimAmount, na.rm = TRUE)
median_claim <- median(df$ClaimAmount, na.rm = TRUE)
mode_claim <- get_mode(df$ClaimAmount) 

  
# Membuat data frame untuk Garis Vertikal
vlines_df <- data.frame(
  Value = c(mean_claim, median_claim, mode_claim),
  Label = c("Mean", "Median", "Mode"),
  Color = c("#0072B2", "#009E73", "#FF6347"),
  Linetype = c("dashed", "solid", "dotted")
)

# Fungsi untuk menghitung Mode
get_mode <- function(v) {
  v_filtered <- v[!is.na(v)] 
  if(length(v_filtered) == 0) return(NA)
  uniq_v <- unique(v_filtered)
  mode_val <- uniq_v[which.max(tabulate(match(v_filtered, uniq_v)))]
  return(mode_val[1]) 
}

# Hitung Mean, Median, dan Modus ClaimAmount
mean_claim <- mean(df$ClaimAmount, na.rm = TRUE)
median_claim <- median(df$ClaimAmount, na.rm = TRUE)
mode_claim <- get_mode(df$ClaimAmount) 

  
# Membuat data frame untuk Garis Vertikal
vlines_df <- data.frame(
  Value = c(mean_claim, median_claim, mode_claim),
  Label = c("Mean", "Median", "Mode"),
  Color = c("#0072B2", "#009E73", "#FF6347"),
  Linetype = c("dashed", "solid", "dotted")
)

# Plot Dasar dengan ggplot
p_dist <- ggplot(df, aes(x = ClaimAmount)) + 
  
  # Histogram
  geom_histogram(
    aes(y = after_stat(density),    
        text = paste0("Jumlah Data: ",
                      after_stat(count))),   
    binwidth = 3,    
    fill = "#337AB7",    
    color = "white",    
    alpha = 0.7
  ) +
  
  # Kurva Kepadatan (Density)
  geom_density(aes(text = paste0("Kepadatan: ",
                                 round(after_stat(density),
                                       4))),
    fill = "#D9534F",
    color = "#D9534F",
    linewidth = 1.2,
    alpha = 0.2
  )+
  
  # Penyesuaian batas axis-Y
  ylim(0, 0.043) +
  
  # Garis Vertikal: Memetakan color dan linetype ke Label
  geom_vline(
    data = vlines_df,
    aes(xintercept = Value, color = Label, linetype = Label, 
        text = paste0(Label, ": ", round(Value, 2))), 
    linewidth = 1
  ) +
  
  # Kustomisasi Skala Warna (Untuk Legenda)
  scale_color_manual(
    name = "Tendensi Sentral",
    values = setNames(vlines_df$Color, vlines_df$Label)
  ) +
  
  # Kustomisasi Skala Tipe Garis 
  scale_linetype_manual(
    name = "Tendensi Sentral",
    values = setNames(vlines_df$Linetype, vlines_df$Label)
  ) +
  
  # Label dan Tema
  labs(
    title = "Distribusi Jumlah Klaim",
    subtitle = paste0("Distribusi Condong Kanan: Mode (", round(mode_claim, 2), ") < Median (", round(median_claim, 2), ") < Mean (", round(mean_claim, 2), ")"),
    x = "Jumlah Klaim (Rp Ribuan)",
    y = "Kepadatan Probabilitas"
  ) +
  theme_minimal(base_size = 14) +
  theme(
    plot.title = element_text(hjust = 0.5, face = "bold"),
    plot.subtitle = element_text(hjust = 0.5, face = "italic"),
    legend.position = "right"   
  )

# Konversi ke Plotly
p_plotly <- ggplotly(p_dist, tooltip = "text", width = 750, height = 500)

# Penghapus duplikat legenda 
num_traces <- length(p_plotly$x$data)
# Trace untuk 'linetype'
traces_to_hide <- (num_traces - 2):num_traces

for (i in traces_to_hide) {
  p_plotly$x$data[[i]]$showlegend = FALSE
}

# Tampilkan Plotly
p_plotly %>%
  layout(hovermode = 'x unified') %>%
  config(displayModeBar = TRUE, modeBarButtonsToRemove = c('zoomIn2d', 'zoomOut2d', 'autoScale2d', 'toggleSpikelines', 'select2d', 'lasso2d'))
Interpretasi Visualisasi:

Kurva kepadatan menunjukkan kemencengan positif kuat (positive skew), yang dikonfirmasi oleh urutan metrik sentral: Mode < Median < Mean. Sebagian besar klaim berlokasi di nilai rendah, tetapi keberadaan klaim bernilai ekstrem (ekor panjang) menarik Mean ke kanan, menunjukkan volatilitas tinggi dan risiko outlier yang signifikan dalam biaya klaim.

4.1.2 Distribusi Usia (Age)

# Hitung Mean,Median dan Modus Age
mean_age <- mean(df$Age, na.rm = TRUE)
median_age <- median(df$Age, na.rm = TRUE)
mode_age <- get_mode(df$Age)

ggplot(df, aes(x = Age)) +
  
  # Histogram
  geom_histogram(
    aes(y = after_stat(density)), 
    binwidth = 5, 
    fill = "#5CB85C",
    color = "white", 
    alpha = 0.7
  ) +
  
  # Kurva Kepadatan
  geom_density(color = "#D9534F",
               fill = "#D9534F",
               alpha = 0.2,
               linewidth = 1.2) +
  
  # Garis Vertikal Mean 
  geom_vline(
    aes(xintercept = mean_age, color = "Mean"),
    linetype = "dashed",
    linewidth = 1
  ) +
  
  # Garis Vertikal Median
  geom_vline(
    aes(xintercept = median_age, color = "Median"),
    linetype = "solid",
    linewidth = 1
  ) +
  
  # Garis Vertikal MODUS 
  geom_vline(
    aes(xintercept = mode_age, color = "Mode"),
    linetype = "dotted", 
    linewidth = 1
  ) +
  
  # Penyesuaian batas axis-Y
  ylim(0, 0.021) +
  
  # Anotasi Mean dan Median
  annotate("text", 
           x = mean_age + 5, y = 0.018, 
           label = paste0("Mean: ", round(mean_age, 2)), 
           color = "#0072B2", fontface = "bold", size = 4) +
  annotate("text", 
           x = median_age - 5, y = 0.02,
           label = paste0("Median: ", round(median_age, 2)), 
           color = "#009E73", fontface = "bold", size = 4) +
  
  # Anotasi MODUS 
  annotate("text", 
           x = mode_age + 5, y = 0.013,
           label = paste0("Mode: ", round(mode_age, 2)), 
           color = "#FF6347", fontface = "bold", size = 4) + 
  
  # Kustomisasi Skala Warna untuk Garis VLine
  scale_color_manual(
    name = "Tendensi Sentral",
    values = c("Mean" = "#0072B2", "Median" = "#009E73", "Mode" = "#FF6347")
  ) +
  
  # Label dan Tema
  labs(
    title = "Distribusi Usia (Age)",
    subtitle = paste0("Mean (", round(mean_age, 2), ") ≈ Median (", round(median_age, 2), ") ≈ Mode (", round(mode_age, 2), ")"),
    x = "Usia Pemegang Polis",
    y = "Kepadatan Probabilitas"
  ) +
  theme_minimal(base_size = 14) +
  theme(
    plot.title = element_text(hjust = 0.5, face = "bold"),
    plot.subtitle = element_text(hjust = 0.5, face = "italic"),
    legend.position = "none"
  )

Interpretasi Visualisasi:

Plot ini menunjukkan distribusi yang relatif simetris dan berbentuk gundukan. Penambahan Modus memvalidasi temuan ini karena Mode, Median, dan Mean berada pada nilai yang sangat berdekatan. Distribusi simetris ini berkontribusi pada CV Age yang rendah (Variabilitas Paling Konsisten), membuktikan populasi pemegang polis berdasarkan usia adalah yang paling stabil dan terdistribusi merata.

4.1.3 Tren ClaimAmount Rata-rata Seiring Usia (Line Chart)

# Hitung rata-rata klaim per kelompok usia (bin 5 tahun)
df_agg <- df %>%
  mutate(age_group = cut(Age, breaks = seq(15, 85, by = 5), include.lowest = TRUE, right = FALSE)) %>%
  group_by(age_group, PolicyCategory) %>%
  summarise(
    mean_claim = mean(ClaimAmount, na.rm = TRUE),
    .groups = 'drop'
  )

# Plot Dasar dengan ggplot
p <- ggplot(df_agg, aes(
    x = age_group, 
    y = mean_claim, 
    group = PolicyCategory, 
    color = PolicyCategory,
    # Menambahkan teks untuk tooltip yang lebih informatif
    text = paste0(
      "Grup Usia: ", age_group, "<br>",
      "Kategori: ", PolicyCategory, "<br>",
      "Klaim Rata-rata: ", scales::dollar(mean_claim, prefix = "Rp", scale = 1000, big.mark = ".")
    )
  )) +
  geom_line(linewidth = 1.2) +
  geom_point(size = 3) +
  scale_y_continuous(labels = scales::label_dollar(prefix = "Rp", scale = 1000, big.mark = ".")) +
  labs(
    title = "Tren Biaya Klaim Rata-rata Seiring Usia",
    subtitle = "Segmentasi Premium (Gold/Platinum) vs. Standar (Basic/Silver)",
    x = "Kelompok Usia (Tahun)",
    y = "Rata-rata Jumlah Klaim (Rp Ribuan)",
    color = "Kategori Polis"
  ) +
  theme_minimal(base_size = 14) +
  theme(
    plot.title = element_text(hjust = 0.5, face = "bold"),
    axis.text.x = element_text(angle = 45, hjust = 1)
  ) +
  scale_color_manual(values = c("Premium (Gold/Platinum)" = "#FFC300", "Standar (Basic/Silver)" = "#0057B7"))

# Konversi ke Plotly
ggplotly(p, tooltip = "text", width = 800, height = 500) %>%
  layout(
    xaxis = list(title = "Kelompok Usia (Tahun)"),
    yaxis = list(title = "Rata-rata Jumlah Klaim (Rp Ribuan)")
  )
Interpretasi Visualisasi:

Line chart menunjukkan tren risiko yang meningkat seiring usia. Selain itu, polis Premium secara konsisten memiliki rata-rata ClaimAmount yang lebih tinggi di hampir semua kelompok usia dibandingkan polis Standar, menunjukkan bahwa polis premium menarik profil risiko yang lebih tinggi atau biaya klaim yang lebih besar.

4.2 Visualisasi Dispersi dan Perbandingan Kelompok Kategorial

Visualisasi di bagian ini menampilkan penyebaran data (range, IQR, outlier, variasi) dan membandingkan distribusi metrik kunci di antara kelompok kategorial (SmokerStatus, InsurancePlan).

4.2.1 Jumlah Klaim Berdasarkan Status Perokok

# Variabel 1: Median Smoker
median_smoker <- df %>%
  filter(SmokerStatus == "Smoker") %>%
  summarise(median_value = median(ClaimAmount, na.rm = TRUE)) %>%
  pull(median_value)

# Variabel 2: Median Non-Smoker 
median_non_smoker <- df %>%
  filter(SmokerStatus == "Non-Smoker") %>%
  summarise(median_value = median(ClaimAmount, na.rm = TRUE)) %>%
  pull(median_value)

# Variabel 3: Mean Smoker
mean_smoker <- df %>%
  filter(SmokerStatus == "Smoker") %>%
  summarise(mean_value = mean(ClaimAmount, na.rm = TRUE)) %>%
  pull(mean_value)


# Hitung Mean dan Median per kelompok untuk anotasi 
claim_stats_values <- df %>%
    group_by(SmokerStatus) %>%
    summarise(
      Median_Value = median(ClaimAmount, na.rm = TRUE),
      Mean_Value = mean(ClaimAmount, na.rm = TRUE),
      .groups = 'drop'
    ) %>%
    mutate(
      Median_Label = paste0("Median: Rp", round(Median_Value, 1), "K"),
      Mean_Label = paste0("Mean: Rp", round(Mean_Value, 1), "K")
    )

# Plot Dasar 
p_smoker <- ggplot(df, aes(x = SmokerStatus, y = ClaimAmount, fill = SmokerStatus)) +
  
  # Violin Plot
  geom_violin(
    trim = FALSE,
    alpha = 0.2, 
    color = "gray50",
    lwd = 0.5
  ) +
  
  # Box Plot Utama
  geom_boxplot(
    width = 0.25, 
    alpha = 0.7, 
    fill = "white",
    outlier.color = "#FF4500", 
    outlier.shape = 19, 
    color = "black"
  ) +
  
  # Titik Median
  geom_point(
    data = claim_stats_values,
    aes(y = Median_Value, 
        text = Median_Label), 
    shape = 18, 
    size = 4,
    color = "#3498db" 
  ) +
  
  # Titik Mean
  geom_point(
    data = claim_stats_values,
    aes(y = Mean_Value,
        text = Mean_Label),
    shape = 17, 
    size = 4,
    color = "#FF9800" 
  ) +
  
  # Transformasi Skala Y Logaritmik
  scale_y_log10(
    labels = scales::label_dollar(prefix = "Rp", scale = 1000, big.mark = "."), 
    breaks = c(10, 20, 50, 100, 200, 500) 
  ) +
  
  # Label dan Tema
  labs(
    title = "Klaim Berdasarkan Status Perokok",
    subtitle = "Dispersi Klaim Perokok Jauh Lebih Tinggi & Sangat Miring ke Kanan",
    x = "Status Perokok",
    y = "Jumlah Klaim (Rp)"
  ) +
  theme_minimal(base_size = 14) + 
  theme(
    legend.position = "none", 
    plot.title = element_text(hjust = 0.5, face = "bold", size = 18),
    plot.subtitle = element_text(hjust = 0.5, face = "italic", color = "gray50"),
    axis.title.y = element_text(face = "bold")
  ) +
  scale_fill_manual(values = c("Non-Smoker" = "#76d7c4", "Smoker" = "#f1c40f"))


# Konversi ke Plotly dan Anotasi 
p_plotly <- ggplotly(p_smoker, tooltip = "text", width = 800, height = 600) %>%
  layout(hovermode = 'closest') %>%
  config(
    displayModeBar = TRUE, 
    modeBarButtonsToRemove = c('zoomIn2d', 'zoomOut2d', 'autoScale2d', 'toggleSpikelines', 'select2d', 'lasso2d')
  )

p_plotly
Interpretasi Visualisasi:

Median klaim untuk kelompok Smoker jauh lebih tinggi (Rp30.9K) dibandingkan Non-Smoker (Rp22.1K). Lebih lanjut, terdapat perbedaan ekstrem antara Mean Smoker (Rp33.5K) dan Median Smoker (Rp30.9K), yang dikonfirmasi oleh ekor panjan pada Violin Plot. Sebaran data klaim (IQR dan Range) untuk perokok jauh lebih lebar, menunjukkan volatilitas risiko yang ekstrem. Status merokok terbukti sebagai faktor risiko utama yang meningkatkan biaya klaim rata-rata dan ketidakpastian risiko.

4.2.2 Distribusi Usia Berdasarkan Rencana Asuransi

# Hitung nilai mean per InsurancePlan
mean_age_by_plan <- df %>%
  group_by(InsurancePlan) %>%
  summarise(Mean_Age = mean(Age, na.rm = TRUE))

ggplot(df, aes(x = InsurancePlan, y = Age, fill = InsurancePlan)) +
  
  # Violin Plot
  geom_violin(
    trim = FALSE, 
    alpha = 0.4, 
    color = "gray40"
  ) +
  
  # Box Plot
  geom_boxplot(
    width = 0.25, 
    color = "black", 
    alpha = 0.8, 
    outlier.shape = NA
  ) +
  
  # Titik Mean 
  geom_point(
    data = mean_age_by_plan, 
    aes(y = Mean_Age, fill = InsurancePlan), 
    shape = 23, 
    size = 3.5, 
    color = "black"
  ) +
  
  # Label Angka Mean
  geom_text(
    data = mean_age_by_plan,
    aes(y = Mean_Age, label = paste0("Mean: ", round(Mean_Age, 1))) ,
    color = "blue", size = 3.5, fontface = "bold",
    vjust = -1.5
  ) +
  
  # Label dan Tema
  labs(
    title = "Distribusi Usia Berdasarkan Rencana Asuransi",
    subtitle = "Angka di atas titik menunjukkan Mean Usia",
    x = "Rencana Asuransi",
    y = "Usia Pemegang Polis (Tahun)"
  ) +
  theme_minimal(base_size = 14) +
  theme(
    axis.text.x = element_text(angle = 60, hjust = 1, face = "bold"),
    legend.position = "none",
    plot.title = element_text(hjust = 0.5, face = "bold"),
    plot.subtitle = element_text(hjust = 0.5, face = "italic", color = "gray50")
  ) +
  scale_fill_brewer(palette = "Set2")

Interpretasi Visualisasi:

Distribusi usia relatif serupa di antara berbagai jenis polis. Bentuk plot yang cenderung simetris pada sebagian besar kelompok mendukung temuan CV Age yang rendah (Tabel 2), menunjukkan bahwa usia tidak terlalu membedakan segmen polis secara drastis.

4.2.3 Proporsi Pemegang Polis Berdasarkan Kelompok Usia

# Mengelompokkan (Binning) variabel Age dengan lebih detail
df_binned <- df %>%
  mutate(
    AgeGroup = case_when(
      Age < 25 ~ "1. Young Adult (< 25 thn)",
      Age >= 25 & Age < 35 ~ "2. Adult (25-34 thn)",
      Age >= 35 & Age < 50 ~ "3. Middle Age (35-49 thn)",
      Age >= 50 & Age < 65 ~ "4. Senior (50-64 thn)",
      Age >= 65 ~ "5. Elderly (≥ 65 thn)",
      TRUE ~ "Lainnya"
    ),
    AgeGroup = factor(AgeGroup, levels = c("1. Young Adult (< 25 thn)", "2. Adult (25-34 thn)", 
                                          "3. Middle Age (35-49 thn)", "4. Senior (50-64 thn)", 
                                          "5. Elderly (≥ 65 thn)"))
  )

# Agregasi Data dengan lebih banyak metrik
age_plan_data <- df_binned %>%
  group_by(AgeGroup) %>%
  summarise(
    Count = n(),
    TotalClaimAmount = sum(ClaimAmount, na.rm = TRUE),
    MeanAge = mean(Age, na.rm = TRUE),
    AvgClaimAmount = mean(ClaimAmount, na.rm = TRUE),
    MaxClaimAmount = max(ClaimAmount, na.rm = TRUE),
    MinClaimAmount = min(ClaimAmount, na.rm = TRUE),
    .groups = 'drop'
  ) %>%
  # Hitung berbagai persentase dan metrik
  mutate(
    Percentage = Count / sum(Count) * 100,
    ClaimPercentage = TotalClaimAmount / sum(TotalClaimAmount) * 100,
    TotalClaimFormatted = paste0("Rp", format(round(TotalClaimAmount/1000, 0), big.mark = ".", scientific = FALSE), "K"),
    AvgClaimFormatted = paste0("Rp", format(round(AvgClaimAmount/1000, 0), big.mark = ".", scientific = FALSE), "K"),
    TooltipText = paste0(
      "<b>", AgeGroup, "</b><br>",
      "Jumlah Polis: <b>", Count, "</b> (", round(Percentage, 1), "%)<br>",
      "Total Klaim: <b>", TotalClaimFormatted, "</b><br>",
      "Rata-rata Klaim: ", AvgClaimFormatted, "<br>",
      "Klaim Tertinggi: Rp", format(round(MaxClaimAmount/1000, 0), big.mark = ".", scientific = FALSE), "K<br>",
      "Rata-rata Usia: ", round(MeanAge, 1), " tahun"
    )
  )

# Warna custom yang lebih menarik
custom_colors <- c('#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b')

# Buat Donut Chart Interaktif dengan Plotly
age_donut_interactive <- plot_ly(
  age_plan_data,
  labels = ~AgeGroup,
  values = ~Count,
  width = 800,
  height = 500,
  type = 'pie',
  hole = 0.6,
  hoverinfo = 'text',
  text = ~TooltipText,
  textinfo = 'percent',
  textposition = 'inside',
  insidetextorientation = 'radial',
  marker = list(
    colors = custom_colors,
    line = list(color = '#FFFFFF', width = 2),
    pattern = list(
      shape = c("/", "x", "-", "\\", "|", "+"),
      solidity = 0.7,
      fgcolor = "white",
      fgopacity = 0.5
    )
  ),
  sort = FALSE,
  direction = 'clockwise',
  rotation = -90
) %>%
layout(
  title = list(
    text = '<b>DISTRIBUSI PEMEGANG POLIS BERDASARKAN KELOMPOK USIA</b>',
    x = 0.5,
    y = 0.98,
    font = list(size = 20, family = "Arial", color = '#2c3e50')
  ),
  font = list(family = "Arial", size = 12),
  showlegend = TRUE,
  legend = list(
    orientation = "v",
    x = 1.05,
    y = 0.5,
    bgcolor = 'rgba(255,255,255,0.8)',
    bordercolor = '#CCCCCC',
    borderwidth = 1,
    font = list(size = 12, family = "Arial"),
    itemclick = 'toggle',
    itemdoubleclick = 'toggleothers'
  ),
  
  # Annotations untuk ringkasan di tengah donut
  annotations = list(
    list(
      text = paste0(
        "TOTAL<br>",
        "<b>", sum(age_plan_data$Count), "</b><br>",
        "POLIS"
      ),
      x = 0.5, y = 0.5,
      font = list(size = 18, color = '#2c3e50', family = "Arial"),
      showarrow = FALSE,
      xref = 'paper', yref = 'paper'
    )
  ),
  margin = list(l = 50, r = 150, b = 50, t = 100),
  paper_bgcolor = 'rgba(0,0,0,0)',
  plot_bgcolor = 'rgba(0,0,0,0)'
) %>%
  
# Tambahkan dropdown menu untuk metrik berbeda
layout(
  updatemenus = list(
    list(
      type = "dropdown",
      x = 1.15, y = 0.9,
      buttons = list(
        list(
          method = "restyle",
          args = list("values", list(age_plan_data$Count)),
          label = "Jumlah Polis"
        ),
        list(
          method = "restyle",
          args = list("values", list(age_plan_data$TotalClaimAmount)),
          label = "Total Klaim"
        ),
        list(
          method = "restyle",
          args = list("values", list(age_plan_data$AvgClaimAmount)),
          label = "Rata-rata Klaim"
        )
      ),
      bgcolor = '#f8f9fa',
      bordercolor = '#dee2e6',
      font = list(size = 11)
    )
  )
) %>%
  
# Konfigurasi mode bar
config(
  displayModeBar = TRUE,
  modeBarButtonsToAdd = list(
    "drawline",
    "drawopenpath",
    "drawcircle",
    "drawrect",
    "eraseshape"
  ),
  modeBarButtonsToRemove = c(
    'sendDataToCloud', 'hoverClosestPie', 'toggleSpikelines',
    'select2d', 'lasso2d', 'zoomIn2d', 'zoomOut2d', 'autoScale2d'
  ),
  scrollZoom = TRUE,
  displaylogo = FALSE
)

# efek highlight on click
age_donut_interactive <- age_donut_interactive %>%
  highlight(
    on = "plotly_click",
    off = "plotly_doubleclick",
    persistent = TRUE,
    dynamic = TRUE,
    selectize = FALSE,
    color = "yellow",
    opacityDim = 0.1
  )

# event handler untuk interaksi tambahan
age_donut_interactive <- age_donut_interactive %>%htmlwidgets::onRender("
    function(el, x) {
      var id = el.getAttribute('id');
      var gd = document.getElementById(id);
      
      // Custom hover effects
      gd.on('plotly_hover', function(data) {
        var annotations = [];
        if(data.points && data.points.length > 0) {
          var point = data.points[0];
          annotations.push({
            text: '<b>' + point.label + '</b><br>' + point.value + ' polis',
            x: 0.5,
            y: 0.3,
            xref: 'paper',
            yref: 'paper',
            showarrow: false,
            font: {size: 14, color: point.fullData.marker.colors[point.i]}
          });
        }
        Plotly.relayout(gd, {annotations: annotations});
      });
      
      // Clear annotation on unhover
      gd.on('plotly_unhover', function(data) {
        Plotly.relayout(gd, {
          annotations: [{
            text: 'TOTAL<br><b>' + x.data[0].values.reduce((a, b) => a + b, 0) + '</b><br>POLIS',
            x: 0.5,
            y: 0.5,
            xref: 'paper',
            yref: 'paper',
            showarrow: false,
            font: {size: 18, color: '#2c3e50'}
          }]
        });
      });
    }
  ")

# Tampilkan plot
age_donut_interactive
Interpretasi Visualisasi:
Pie Chart ini mengkonfirmasi segmentasi demografis portofolio polis berdasarkan kelompok usia yang terkelompok, yang melengkapi temuan dari Violin Plot Usia.
  1. Target Pasar Utama: Identifikasi kelompok usia mana yang memiliki persentase pemegang polis terbesar (irisan Donut Chart terbesar). Kelompok ini harus menjadi fokus utama dari strategi penetapan harga dan layanan produk.
  2. Konsentrasi Klaim/Risiko: Melalui hover pada setiap irisan, Anda dapat melihat Total Klaim yang terkait dengan kelompok usia tersebut. Perbandingan antara Persentase Polis dan Total Klaim dapat membantu menilai apakah risiko klaim tertinggi terkonsentrasi pada kelompok usia tertentu (misalnya, jika kelompok Senior hanya 10% polis tetapi menyumbang 30% dari Total Klaim).
  3. **Keterwakilan Data: Visualisasi ini menunjukkan bahwa variabel Usia memiliki distribusi yang relatif merata atau tidak terlalu terpusat pada satu kategori, sehingga mendukung temuan bahwa Usia mungkin bukan faktor tunggal yang mendominasi variabilitas risiko.
Gunakan insight ini untuk menyesuaikan strategi akuisisi, retensi, dan peninjauan produk asuransi.

4.3 Analisis Hubungan dan Korelasi (Relationship/Correlation Analysis)

Bagian ini mengeksplorasi hubungan bivariat atau multivariat antar variabel, yang tidak termasuk dalam kategori tendensi sentral atau dispersi.

4.3.1 Korelasi BMI vs. Risk Score

# Hitung Koefisien Korelasi Pearson dan statistik lainnya
correlation_value <- cor(df$BMI, df$RiskScore, use = "complete.obs")
cor_test <- cor.test(df$BMI, df$RiskScore)
n_obs <- nrow(na.omit(df[, c("BMI", "RiskScore")]))

# Buat model untuk mendapatkan persamaan garis
lm_model <- lm(RiskScore ~ BMI, data = df)
lm_eq <- paste0("y = ", round(coef(lm_model)[1], 2), " + ", round(coef(lm_model)[2], 2), "x")
r_squared <- round(summary(lm_model)$r.squared, 3)

# Plot Dasar
bmi_risk_ggplot <- ggplot(df, aes(x = BMI, y = RiskScore, color = ExerciseHours)) +
  
  # Scatter Plot Utama dengan size berdasarkan ExerciseHours
  geom_point(
    alpha = 0.7,
    size = 4,
    aes(text = paste0(
      "ID: ", rownames(df), "<br>",
      "BMI: ", round(BMI, 1), "<br>",
      "Risk Score: ", round(RiskScore, 1), "<br>",
      "Jam Olahraga: ", ExerciseHours, " jam/minggu<br>",
      "Kategori BMI: ", cut(BMI, breaks = c(0, 18.5, 25, 30, Inf), 
                          labels = c("Underweight", "Normal", "Overweight", "Obese"))
    ))
  ) +
  
  # Garis Tren Linear (LM)
  geom_smooth(
    method = "lm", 
    se = TRUE, 
    alpha = 0.2, 
    color = "#292B2C", 
    linewidth = 1.2,
    linetype = "solid",
    aes(fill = "Linear Trend")
  ) +
  
  # Garis Tren LOESS 
  geom_smooth(
    method = "loess", 
    se = TRUE, 
    alpha = 0.2, 
    color = "#D9534F", 
    linetype = "dashed", 
    linewidth = 1,
    aes(fill = "Local Trend")
  ) +
  
  # Scale untuk fill
  scale_fill_manual(
    name = "Trend Lines",
    values = c("Linear Trend" = "#292B2C", "Local Trend" = "#D9534F")
  ) +

  # Kustomisasi Gradien Warna
  scale_color_gradientn(
    colors = c("#5BC0DE", "#292B2C", "#D9534F"),  
    name = "Exercise Hours\n(per week)",
    breaks = seq(0, max(df$ExerciseHours, na.rm = TRUE), by = 2)
  ) +
  
  # Labels dan tema
  labs(
    title = paste0("BMI vs Risk Score Correlation (r = ", round(correlation_value, 2), ")"),
    subtitle = paste0("Linear: ", lm_eq, " | R² = ", r_squared, " | p-value = ", round(cor_test$p.value, 4)),
    x = "Body Mass Index (BMI)",
    y = "Risk Score",
    caption = paste("N =", n_obs, "observations | Click and drag to zoom | Double-click to reset")
  ) +
  
  theme_minimal(base_size = 14) +
  theme(
    plot.title = element_text(hjust = 0.5, face = "bold", size = 16),
    plot.subtitle = element_text(hjust = 0.5, size = 12),
    legend.position = "right",
    panel.grid.minor = element_blank()
  )

# Konversi ke Plotly
bmi_risk_interactive <- ggplotly(bmi_risk_ggplot, tooltip = "text", width = 800, height = 600) %>%
  layout(
    title = list(
      text = paste0(
        "BMI vs Risk Score Correlation<br>",
        "<sub>r = ", round(correlation_value, 2), 
        " | p = ", round(cor_test$p.value, 4),
        " | N = ", n_obs, "</sub>"
      ),
      x = 0.05
    ),
    hovermode = 'closest',
    xaxis = list(
      title = "Body Mass Index (BMI)",
      spikemode = "across",
      showspikes = TRUE,
      spikethickness = 1,
      spikecolor = "#999999"
    ),
    yaxis = list(
      title = "Risk Score",
      spikemode = "across", 
      showspikes = TRUE,
      spikethickness = 1,
      spikecolor = "#999999"
    ),
    annotations = list(
      list(
        x = 1, y = 0, 
        text = "Source: Your Dataset",
        showarrow = FALSE, 
        xref = "paper", yref = "paper",
        xanchor = "right", yanchor = "bottom",
        font = list(size = 10, color = "gray")
      )
    )
  ) %>%
  config(
    displayModeBar = TRUE,
    modeBarButtonsToAdd = list(
      "drawline",
      "drawopenpath",
      "drawcircle",
      "drawrect",
      "eraseshape"
    ),
    modeBarButtonsToRemove = c(
      'zoomIn2d', 'zoomOut2d', 'autoScale2d', 
      'toggleSpikelines', 'select2d', 'lasso2d'
    ),
    scrollZoom = TRUE,
    displaylogo = FALSE
  )

# range slider untuk BMI
bmi_risk_interactive <- bmi_risk_interactive %>%
  layout(
    xaxis = list(
      rangeslider = list(
        type = "linear",
        thickness = 0.08,
        bgcolor = "lightgray"
      )
    )
  )

# fitur highlight on click
bmi_risk_interactive <- bmi_risk_interactive %>%
  highlight(
    on = "plotly_click",
    off = "plotly_doubleclick",
    color = "red",
    opacityDim = 0.3
  )

# fitur selection
bmi_risk_interactive <- bmi_risk_interactive %>%
  layout(
    dragmode = "select"
  ) %>%
  event_register("plotly_selected")

# Function untuk menangani event selection
observe_selection <- function(p) {
  p %>% htmlwidgets::onRender("
    function(el, x) {
      var id = el.getAttribute('id');
      var gd = document.getElementById(id);
      
      gd.on('plotly_click', function(data) {
        var point = data.points[0];
        var customText = 'Clicked: BMI=' + point.x + ', Risk=' + point.y;
        console.log(customText);
      });
      
      gd.on('plotly_selected', function(data) {
        var selectedPoints = data.points;
        console.log('Selected points:', selectedPoints.length);
      });
    }
  ")
}

# observasi selection
bmi_risk_interactive <- observe_selection(bmi_risk_interactive)

# Tampilkan plot
bmi_risk_interactive
Interpretasi Visualisasi:

Terdapat korelasi positif yang jelas antara BMI dan Risk Score. Meskipun Jam Olahraga tidak menunjukkan pola interaksi yang sangat kuat, BMI tetap menjadi prediktor risiko yang signifikan.Selain itu, terlihat bahwa titik-titik dengan ‘Jam Olahraga’ yang lebih tinggi (biru muda) cenderung tersebar lebih dekat ke bagian bawah plot, mengisyaratkan bahwa aktivitas fisik mungkin menjadi faktor penurun risiko, terlepas dari BMI.


5 Ringkasan Temuan dan Kesimpulan

Analisis deskriptif terhadap delapan variabel numerik menunjukkan perbedaan tingkat variabilitas yang signifikan antar variabel.

5.1 Variabel yang Paling Konsisten (Low Dispersion)

Variabel Age adalah yang paling konsisten, terbukti dengan Koefisien Variasi (CV) terendah (0.37) dan distribusi yang simetris (Visualisasi 2), menunjukkan populasi pemegang polis yang relatif stabil.

5.2 Variabel dengan Variasi Terbesar

ClaimAmount (CV 0.53) dan AnnualIncome (CV 0.60) menunjukkan variasi terbesar. Hal ini mengindikasikan ketidakpastian tinggi dalam nilai klaim serta pendapatan pelanggan, yang didorong oleh adanya outlier ekstrem (dikonfirmasi oleh kemencengan positif pada Visualisasi 1).

5.3 Pola dan Insight yang Ditemukan Melalui Visualisasi

  • Status Perokok (SmokerStatus) terbukti menjadi prediktor yang paling signifikan terhadap kenaikan jumlah klaim (Visualisasi 3). Kelompok perokok memiliki median klaim dan dispersi klaim yang jauh lebih luas.

  • BMI berkorelasi positif dengan RiskScore (Visualisasi 4), menegaskan BMI sebagai variabel penting dalam penilaian risiko kesehatan.

  • Hubungan antara Age dan ClaimAmount meningkat seiring bertambahnya usia, namun pola sebaran klaim tetap didominasi oleh faktor perilaku (merokok, BMI), bukan hanya faktor demografi.

Temuan ini menunjukkan bahwa risiko klaim tidak hanya dipengaruhi oleh faktor demografi yang stabil, tetapi terutama oleh faktor perilaku dan gaya hidup. Oleh karena itu, model penetapan premi (pricing) yang berbasis segmentasi risiko sangat direkomendasikan untuk memitigasi dampak variabilitas klaim yang tinggi.


Daftar Pustaka

Daftar Pustaka ini berisi sumber-sumber yang digunakan sebagai referensi konseptual dalam analisis statistik deskriptif.