📊 Analisis Revenue & Segmentasi Pelanggan

Studi Kasus Database Sakila — Toko Rental DVD

1 Pendahuluan

Database Sakila merupakan contoh database relasional yang dikembangkan oleh MySQL, merepresentasikan sistem informasi toko rental DVD. Analisis ini bertujuan untuk mengeksplorasi dua insight utama: (1) tren dan perbandingan revenue antar toko, serta (2) segmentasi pelanggan berdasarkan perilaku transaksi menggunakan metode K-Means Clustering.

2 Tujuan Analisis

Analisis ini memiliki empat tujuan utama:

  1. Mengidentifikasi tren revenue bulanan toko rental DVD Sakila sepanjang periode transaksi yang tersedia.
  2. Membandingkan performa revenue antara Store 1 dan Store 2 untuk mengetahui apakah terdapat perbedaan kontribusi yang signifikan antar toko.
  3. Menguji pengaruh frekuensi rental terhadap total spending pelanggan menggunakan analisis regresi linear sederhana.
  4. Mengelompokkan pelanggan ke dalam segmen-segmen berdasarkan perilaku transaksinya menggunakan metode K-Means Clustering.

3 Tahapan Analisis

Analisis ini dilakukan melalui beberapa tahapan yang sistematis, yaitu:

  1. Persiapan — Memuat library yang dibutuhkan dan membuat koneksi ke database Sakila melalui MySQL.
  2. Pengambilan Data — Mengambil data dari database menggunakan query SQL untuk membentuk tiga dataset utama.
  3. Exploratory Data Analysis (EDA) — Memeriksa kualitas data meliputi missing values, duplikasi, tipe data, nilai tidak wajar, dan outlier.
  4. Preprocessing Data — Mengonversi tipe data, menghitung variabel recency, dan menormalisasi data untuk keperluan clustering.
  5. Visualisasi Data — Membuat visualisasi grafis untuk mengeksplorasi pola dan tren dalam data.
  6. Analisis Statistik — Melakukan statistik deskriptif, regresi linear sederhana, dan K-Means Clustering.
  7. Kesimpulan — Merangkum temuan utama dan memberikan rekomendasi bisnis berdasarkan hasil analisis.

3.1 Persiapan

library(RMySQL)
library(DBI)
library(dplyr)
library(ggplot2)
library(lubridate)
library(scales)
library(cluster)
library(factoextra)
library(knitr)
library(kableExtra)

3.1.1 Koneksi Database

con <- dbConnect(
  RMySQL::MySQL(),
  dbname   = "sakila",
  host     = "127.0.0.1",
  port     = 3306,
  user     = "root",
  password = "nasipadanggg"
)

dbListTables(con)
##  [1] "actor"                      "actor_info"                
##  [3] "address"                    "category"                  
##  [5] "city"                       "country"                   
##  [7] "customer"                   "customer_list"             
##  [9] "film"                       "film_actor"                
## [11] "film_category"              "film_list"                 
## [13] "film_text"                  "inventory"                 
## [15] "language"                   "nicer_but_slower_film_list"
## [17] "payment"                    "rental"                    
## [19] "sales_by_film_category"     "sales_by_store"            
## [21] "staff"                      "staff_list"                
## [23] "store"

3.2 Pengambilan Data

Tiga dataset utama diambil langsung dari database Sakila menggunakan query SQL. Dataset pertama berisi agregasi revenue per bulan (df_revenue), dataset kedua memisahkan revenue per toko per bulan (df_toko), dan dataset ketiga mengambil data perilaku transaksi per pelanggan sebagai bahan segmentasi (df_customer).

# ── 1. Revenue per bulan ──────────────────────────────────────────
df_revenue <- dbGetQuery(con, "
  SELECT 
    DATE_FORMAT(payment_date, '%Y-%m') AS bulan,
    SUM(amount)                        AS total_revenue,
    COUNT(payment_id)                  AS jumlah_transaksi
  FROM payment
  GROUP BY bulan
  ORDER BY bulan;
")

# ── 2. Revenue per toko per bulan ─────────────────────────────────
df_toko <- dbGetQuery(con, "
  SELECT 
    DATE_FORMAT(p.payment_date, '%Y-%m') AS bulan,
    s.store_id,
    SUM(p.amount)                        AS total_revenue,
    COUNT(p.payment_id)                  AS jumlah_transaksi
  FROM payment p
  JOIN rental r    ON p.rental_id    = r.rental_id
  JOIN inventory i ON r.inventory_id = i.inventory_id
  JOIN store s     ON i.store_id     = s.store_id
  GROUP BY bulan, s.store_id
  ORDER BY bulan, s.store_id;
")

# ── 3. Segmentasi pelanggan ───────────────────────────────────────
df_customer <- dbGetQuery(con, "
  SELECT
    p.customer_id,
    CONCAT(c.first_name, ' ', c.last_name) AS nama,
    COUNT(p.payment_id)                    AS frekuensi,
    SUM(p.amount)                          AS total_spending,
    MAX(p.payment_date)                    AS last_payment
  FROM payment p
  JOIN customer c ON p.customer_id = c.customer_id
  GROUP BY p.customer_id, nama
  ORDER BY total_spending DESC;
")

head(df_revenue)
##     bulan total_revenue jumlah_transaksi
## 1 2005-05       4823.44             1156
## 2 2005-06       9629.89             2311
## 3 2005-07      28368.91             6709
## 4 2005-08      24070.14             5686
## 5 2006-02        514.18              182
head(df_toko)
##     bulan store_id total_revenue jumlah_transaksi
## 1 2005-05        1       2459.25              575
## 2 2005-05        2       2364.19              581
## 3 2005-06        1       4734.79             1121
## 4 2005-06        2       4895.10             1190
## 5 2005-07        1      14308.66             3334
## 6 2005-07        2      14060.25             3375
head(df_customer)
##   customer_id           nama frekuensi total_spending        last_payment
## 1         526      KARL SEAL        45         221.55 2005-08-23 22:21:03
## 2         148   ELEANOR HUNT        46         216.54 2005-08-23 05:57:04
## 3         144     CLARA SHAW        42         195.58 2005-08-23 12:43:30
## 4         137 RHONDA KENNEDY        39         194.61 2005-08-23 21:56:04
## 5         178  MARION SNYDER        39         194.61 2006-02-14 15:16:03
## 6         459  TOMMY COLLAZO        38         186.62 2005-08-23 14:52:50

3.3 Exploratory Data Analysis (EDA)

Sebelum preprocessing, dilakukan pengecekan kualitas data mencakup missing values, duplikasi, tipe data, nilai tidak wajar, dan outlier pada ketiga dataset.

# ── 1. Missing Values ─────────────────────────────────────────────
bind_rows(
  df_revenue  %>% summarise(across(everything(), ~sum(is.na(.)))) %>% mutate(dataset = "df_revenue"),
  df_toko     %>% summarise(across(everything(), ~sum(is.na(.)))) %>% mutate(dataset = "df_toko"),
  df_customer %>% summarise(across(everything(), ~sum(is.na(.)))) %>% mutate(dataset = "df_customer")
) %>% select(dataset, everything()) %>%
  kable(caption = "Pengecekan Missing Values") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE)
Pengecekan Missing Values
dataset bulan total_revenue jumlah_transaksi store_id customer_id nama frekuensi total_spending last_payment
df_revenue 0 0 0 NA NA NA NA NA NA
df_toko 0 0 0 0 NA NA NA NA NA
df_customer NA NA NA NA 0 0 0 0 0
# ── 2. Duplikasi ──────────────────────────────────────────────────
data.frame(
  Dataset   = c("df_revenue", "df_toko", "df_customer"),
  Duplikasi = c(sum(duplicated(df_revenue)),
                sum(duplicated(df_toko)),
                sum(duplicated(df_customer)))
) %>%
  kable(caption = "Pengecekan Duplikasi") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE)
Pengecekan Duplikasi
Dataset Duplikasi
df_revenue 0
df_toko 0
df_customer 0
# ── 3. Nilai Tidak Wajar ──────────────────────────────────────────
data.frame(
  Variabel           = c("total_revenue", "jumlah_transaksi", "total_spending", "frekuensi"),
  Nilai_Negatif      = c(sum(df_revenue$total_revenue <= 0),
                         sum(df_revenue$jumlah_transaksi <= 0),
                         sum(df_customer$total_spending <= 0),
                         sum(df_customer$frekuensi <= 0))
) %>%
  kable(caption = "Pengecekan Nilai Tidak Wajar") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE)
Pengecekan Nilai Tidak Wajar
Variabel Nilai_Negatif
total_revenue 0
jumlah_transaksi 0
total_spending 0
frekuensi 0
# ── 4. Outlier (IQR Method) ───────────────────────────────────────
outlier_df <- function(x, nama) {
  Q1  <- quantile(x, 0.25)
  Q3  <- quantile(x, 0.75)
  IQR_val     <- Q3 - Q1
  batas_bawah <- Q1 - 1.5 * IQR_val
  batas_atas  <- Q3 + 1.5 * IQR_val
  data.frame(
    Variabel      = nama,
    Q1            = round(Q1, 2),
    Q3            = round(Q3, 2),
    Batas_Bawah   = round(batas_bawah, 2),
    Batas_Atas    = round(batas_atas, 2),
    Jumlah_Outlier = sum(x < batas_bawah | x > batas_atas)
  )
}

bind_rows(
  outlier_df(df_customer$frekuensi,      "frekuensi"),
  outlier_df(df_customer$total_spending, "total_spending")
) %>%
  kable(caption = "Pengecekan Outlier (Metode IQR)") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE)
Pengecekan Outlier (Metode IQR)
Variabel Q1 Q3 Batas_Bawah Batas_Atas Jumlah_Outlier
25%…1 frekuensi 23.00 30.0 12.50 40.50 6
25%…2 total_spending 94.78 128.7 43.91 179.58 6
# ── 5. Dimensi Data ───────────────────────────────────────────────
data.frame(
  Dataset = c("df_revenue", "df_toko", "df_customer"),
  Baris   = c(nrow(df_revenue), nrow(df_toko), nrow(df_customer)),
  Kolom   = c(ncol(df_revenue), ncol(df_toko), ncol(df_customer))
) %>%
  kable(caption = "Dimensi Dataset") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE)
Dimensi Dataset
Dataset Baris Kolom
df_revenue 5 3
df_toko 10 4
df_customer 599 5

Berdasarkan hasil EDA, diperoleh beberapa temuan penting. Missing values tidak ditemukan pada ketiga dataset, menunjukkan bahwa data dari database Sakila sudah lengkap. Duplikasi juga tidak ditemukan, artinya setiap baris merupakan record yang unik. Dari sisi tipe data, kolom bulan dan last_payment masih bertipe character dan akan dikonversi pada tahap preprocessing. Nilai tidak wajar seperti angka negatif tidak ditemukan pada seluruh variabel numerik. Terkait outlier, ditemukan masing-masing 6 outlier pada variabel frekuensi dan total_spending menggunakan metode IQR dengan batas \(Q3 + 1.5 \times IQR\). Outlier ini tidak dihapus karena merepresentasikan pelanggan dengan aktivitas sangat tinggi yang justru relevan untuk analisis segmentasi.

3.4 Preprocessing Data

Kolom bulan dikonversi ke format Date, store_id diubah menjadi faktor berlabel, dan last_payment dikonversi ke POSIXct. Variabel recency dihitung sebagai selisih hari dari transaksi terakhir pelanggan ke tanggal transaksi terbaru dalam data. Ketiga variabel clustering kemudian dinormalisasi menggunakan scale() agar tidak ada variabel yang mendominasi karena perbedaan skala.

# ── 1. Revenue per bulan ──────────────────────────────────────────
df_revenue <- df_revenue %>%
  mutate(
    bulan          = as.Date(paste0(bulan, "-01")),
    rata_transaksi = total_revenue / jumlah_transaksi
  )

# ── 2. Data toko ──────────────────────────────────────────────────
df_toko <- df_toko %>%
  mutate(
    bulan    = as.Date(paste0(bulan, "-01")),
    store_id = factor(store_id, labels = c("Store 1", "Store 2"))
  )

# ── 3. Data customer ──────────────────────────────────────────────
df_customer <- df_customer %>%
  mutate(last_payment = as.POSIXct(last_payment))

tgl_max <- max(df_customer$last_payment)

df_customer <- df_customer %>%
  mutate(
    recency = as.numeric(difftime(tgl_max, last_payment, units = "days"))
  )

# ── 4. Normalisasi untuk clustering ──────────────────────────────
df_cluster <- df_customer %>%
  select(frekuensi, total_spending, recency) %>%
  scale() %>%
  as.data.frame()

glimpse(df_customer)
## Rows: 599
## Columns: 6
## $ customer_id    <dbl> 526, 148, 144, 137, 178, 459, 469, 468, 236, 181, 176, …
## $ nama           <chr> "KARL SEAL", "ELEANOR HUNT", "CLARA SHAW", "RHONDA KENN…
## $ frekuensi      <dbl> 45, 46, 42, 39, 39, 38, 40, 39, 42, 34, 37, 35, 32, 33,…
## $ total_spending <dbl> 221.55, 216.54, 195.58, 194.61, 194.61, 186.62, 177.60,…
## $ last_payment   <dttm> 2005-08-23 22:21:03, 2005-08-23 05:57:04, 2005-08-23 1…
## $ recency        <dbl> 174.7049, 175.3882, 175.1059, 174.7222, 0.0000, 175.016…
summary(df_cluster)
##    frekuensi       total_spending        recency       
##  Min.   :-2.8931   Min.   :-2.43169   Min.   :-1.6692  
##  1st Qu.:-0.7406   1st Qu.:-0.69964   1st Qu.:-1.6692  
##  Median :-0.1535   Median :-0.07103   Median : 0.5905  
##  Mean   : 0.0000   Mean   : 0.00000   Mean   : 0.0000  
##  3rd Qu.: 0.6292   3rd Qu.: 0.63760   3rd Qu.: 0.5992  
##  Max.   : 3.7601   Max.   : 4.29784   Max.   : 0.6756

Setelah preprocessing, kolom bulan berhasil dikonversi ke format Date dan store_id menjadi faktor berlabel. Variabel recency berhasil dihitung dengan rentang 0 hingga 181,6 hari. Hasil summary(df_cluster) menunjukkan ketiga variabel sudah ternormalisasi dengan mean = 0 dan skala yang sebanding, sehingga proses clustering tidak akan didominasi oleh satu variabel tertentu.

3.5 Visualisasi Data

Tiga visualisasi dibuat untuk mengeksplorasi data secara visual. Line chart digunakan untuk melihat tren revenue dari waktu ke waktu. Bar chart grouped digunakan untuk membandingkan kontribusi revenue Store 1 vs Store 2 per bulan. Scatter plot dengan garis regresi digunakan untuk melihat pola hubungan antara frekuensi rental dan total spending pelanggan.

3.5.1 Tren Revenue per Bulan

ggplot(df_revenue, aes(x = bulan, y = total_revenue)) +
  geom_line(color = "#2196F3", linewidth = 1.2) +
  geom_point(color = "#2196F3", size = 3) +
  geom_label(aes(label = dollar(total_revenue, prefix = "$")),
             vjust = -0.5, size = 3.2, fill = "white", color = "#2196F3") +
  scale_y_continuous(labels = dollar_format(prefix = "$")) +
  scale_x_date(date_labels = "%b %Y", date_breaks = "1 month") +
  labs(
    title    = "Tren Total Revenue per Bulan",
    subtitle = "Database Sakila — Mei 2005 s/d Februari 2006",
    x        = "Bulan",
    y        = "Total Revenue (USD)",
    caption  = "Sumber: Database Sakila"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title    = element_text(face = "bold", size = 14),
    plot.subtitle = element_text(color = "gray50"),
    axis.text.x   = element_text(angle = 45, hjust = 1)
  )
Tren Revenue per Bulan

Tren Revenue per Bulan

Revenue mengalami pertumbuhan yang sangat pesat dari Mei 2005 ($4.823) hingga mencapai puncaknya pada Juli 2005 ($28.369). Setelah itu terjadi penurunan pada Agustus 2005 ($24.070), kemudian terdapat gap transaksi yang panjang hingga Februari 2006 di mana revenue anjlok drastis menjadi hanya $514. Pola ini mengindikasikan adanya gangguan operasional atau perubahan sistem pencatatan pada periode tersebut yang perlu diinvestigasi lebih lanjut.

3.5.2 Perbandingan Revenue per Toko

ggplot(df_toko, aes(x = bulan, y = total_revenue, fill = store_id)) +
  geom_bar(stat = "identity", position = "dodge", width = 20) +
  scale_y_continuous(labels = dollar_format(prefix = "$")) +
  scale_x_date(date_labels = "%b %Y", date_breaks = "1 month") +
  scale_fill_manual(values = c("Store 1" = "#4CAF50", "Store 2" = "#FF9800")) +
  labs(
    title    = "Perbandingan Revenue per Toko per Bulan",
    subtitle = "Store 1 vs Store 2",
    x        = "Bulan",
    y        = "Total Revenue (USD)",
    fill     = "Toko",
    caption  = "Sumber: Database Sakila"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title    = element_text(face = "bold", size = 14),
    plot.subtitle = element_text(color = "gray50"),
    axis.text.x   = element_text(angle = 45, hjust = 1)
  )
Perbandingan Revenue per Toko

Perbandingan Revenue per Toko

Perbandingan antara Store 1 dan Store 2 menunjukkan performa yang relatif seimbang di setiap bulannya. Tidak ada satu toko pun yang secara konsisten mendominasi revenue secara signifikan. Pada Mei–Juni 2005 Store 2 sedikit lebih unggul, namun pada Juli 2005 Store 1 memimpin tipis. Keseimbangan ini mengindikasikan bahwa kedua toko beroperasi dengan kapasitas dan basis pelanggan yang setara.

3.5.3 Hubungan Frekuensi Rental vs Total Spending

ggplot(df_customer, aes(x = frekuensi, y = total_spending)) +
  geom_point(alpha = 0.5, color = "#9C27B0", size = 2) +
  geom_smooth(method = "lm", color = "#F44336", se = TRUE) +
  scale_y_continuous(labels = dollar_format(prefix = "$")) +
  labs(
    title    = "Hubungan Frekuensi Rental vs Total Spending Pelanggan",
    subtitle = "599 pelanggan — Database Sakila",
    x        = "Frekuensi Rental",
    y        = "Total Spending (USD)",
    caption  = "Sumber: Database Sakila"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title    = element_text(face = "bold", size = 14),
    plot.subtitle = element_text(color = "gray50")
  )
Hubungan Frekuensi Rental vs Total Spending

Hubungan Frekuensi Rental vs Total Spending

Scatter plot menunjukkan pola hubungan linear positif yang kuat antara frekuensi rental dan total spending. Titik-titik data tersebar cukup rapat di sekitar garis regresi, mengindikasikan bahwa semakin sering pelanggan menyewa, semakin besar pula total pengeluarannya. Hal ini akan dikonfirmasi secara statistik pada analisis regresi berikutnya.

3.6 Analisis Statistik

3.6.1 Statistik Deskriptif

summary(df_revenue[, c("total_revenue", "jumlah_transaksi", "rata_transaksi")])
##  total_revenue     jumlah_transaksi rata_transaksi 
##  Min.   :  514.2   Min.   : 182     Min.   :2.825  
##  1st Qu.: 4823.4   1st Qu.:1156     1st Qu.:4.167  
##  Median : 9629.9   Median :2311     Median :4.173  
##  Mean   :13481.3   Mean   :3209     Mean   :3.925  
##  3rd Qu.:24070.1   3rd Qu.:5686     3rd Qu.:4.228  
##  Max.   :28368.9   Max.   :6709     Max.   :4.233
summary(df_customer[, c("frekuensi", "total_spending", "recency")])
##    frekuensi     total_spending      recency     
##  Min.   :12.00   Min.   : 50.85   Min.   :  0.0  
##  1st Qu.:23.00   1st Qu.: 94.78   1st Qu.:  0.0  
##  Median :26.00   Median :110.73   Median :175.0  
##  Mean   :26.78   Mean   :112.53   Mean   :129.3  
##  3rd Qu.:30.00   3rd Qu.:128.71   3rd Qu.:175.7  
##  Max.   :46.00   Max.   :221.55   Max.   :181.6

Statistik deskriptif menunjukkan bahwa revenue bulanan sangat bervariasi, berkisar antara $514 hingga $28.369, dengan rata-rata $13.481 per bulan. Rata-rata nilai per transaksi relatif stabil di kisaran $3,9–$4,2, menunjukkan harga sewa per unit tidak fluktuatif. Dari sisi pelanggan, rata-rata frekuensi rental adalah 26–27 kali dengan total spending rata-rata $112,53. Variabel recency terbagi dalam dua kelompok jelas yaitu pelanggan dengan recency 0 hari (aktif di Februari 2006) dan sekitar 175 hari (terakhir aktif Agustus 2005), menunjukkan adanya gap aktivitas yang signifikan.

3.6.2 Regresi Linear Sederhana

Regresi linear sederhana digunakan untuk menguji apakah frekuensi rental berpengaruh signifikan terhadap total spending pelanggan. Model yang digunakan adalah:

\[\hat{Y} = \beta_0 + \beta_1 X + \varepsilon\]

Di mana:

  • \(\hat{Y}\) = prediksi total spending pelanggan
  • \(\beta_0\) = intercept (nilai spending saat frekuensi = 0)
  • \(\beta_1\) = koefisien pengaruh frekuensi rental
  • \(X\) = frekuensi rental
  • \(\varepsilon\) = error/residual
model_regresi <- lm(total_spending ~ frekuensi, data = df_customer)
summary(model_regresi)
## 
## Call:
## lm(formula = total_spending ~ frekuensi, data = df_customer)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -41.386  -8.391  -0.074   8.431  41.936 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept) -3.22174    2.72462  -1.182    0.237    
## frekuensi    4.32164    0.09992  43.249   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 12.49 on 597 degrees of freedom
## Multiple R-squared:  0.7581, Adjusted R-squared:  0.7577 
## F-statistic:  1871 on 1 and 597 DF,  p-value: < 2.2e-16
par(mfrow = c(2, 2))
plot(model_regresi)

par(mfrow = c(1, 1))

Persamaan regresi yang dihasilkan adalah \(\hat{Y} = -3{,}22 + 4{,}32X\). Hasil analisis regresi menunjukkan beberapa temuan penting. Pertama, koefisien frekuensi (\(\beta_1 = 4{,}32\)) bermakna bahwa setiap penambahan 1 kali rental, total spending pelanggan meningkat rata-rata sebesar $4,32. Kedua, intercept (\(\beta_0 = -3{,}22\), \(p = 0{,}237\)) tidak signifikan secara statistik, yang wajar karena pelanggan dengan frekuensi 0 memang tidak memiliki spending. Ketiga, p-value untuk koefisien frekuensi sangat kecil (\(< 2{,}2 \times 10^{-16}\)), membuktikan bahwa frekuensi rental berpengaruh sangat signifikan terhadap total spending. Keempat, nilai R-squared sebesar 0,7581 menunjukkan bahwa 75,81% variasi total spending dapat dijelaskan oleh frekuensi rental — hubungan yang sangat kuat secara statistik.

3.6.3 K-Means Clustering

K-Means Clustering digunakan untuk mengelompokkan pelanggan berdasarkan tiga variabel: frekuensi rental, total spending, dan recency. Fungsi objektif yang diminimalkan adalah:

\[J = \sum_{k=1}^{K} \sum_{x_i \in C_k} \| x_i - \mu_k \|^2\]

Di mana:

  • \(K\) = jumlah cluster
  • \(C_k\) = himpunan observasi dalam cluster \(k\)
  • \(\mu_k\) = centroid cluster \(k\)
  • \(\|x_i - \mu_k\|^2\) = jarak kuadrat Euclidean dari titik \(x_i\) ke centroid \(\mu_k\)

3.6.3.1 Penentuan Jumlah Cluster Optimal

set.seed(42)
wss <- sapply(1:8, function(k) {
  kmeans(df_cluster, centers = k, nstart = 25)$tot.withinss
})

elbow_df <- data.frame(k = 1:8, wss = wss)

ggplot(elbow_df, aes(x = k, y = wss)) +
  geom_line(color = "#2196F3", linewidth = 1.2) +
  geom_point(color = "#2196F3", size = 3) +
  geom_vline(xintercept = 3, linetype = "dashed", color = "#F44336") +
  scale_x_continuous(breaks = 1:8) +
  labs(
    title    = "Elbow Method — Penentuan Jumlah Cluster Optimal",
    subtitle = "Garis merah = jumlah cluster yang dipilih (K=3)",
    x        = "Jumlah Cluster (K)",
    y        = "Total Within-Cluster Sum of Squares (WSS)",
    caption  = "Sumber: Database Sakila"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title    = element_text(face = "bold", size = 14),
    plot.subtitle = element_text(color = "gray50")
  )
Elbow Method

Elbow Method

Grafik Elbow Method menunjukkan bahwa penurunan nilai WSS mulai melandai secara signifikan setelah K=3. Penambahan cluster di atas 3 tidak memberikan pengurangan WSS yang berarti, sehingga K=3 dipilih sebagai jumlah cluster optimal.

3.6.3.2 Hasil Clustering

set.seed(42)
kmeans_result <- kmeans(df_cluster, centers = 3, nstart = 25)

df_customer$cluster <- factor(kmeans_result$cluster,
                               labels = c("Bronze", "Silver", "Gold"))

fviz_cluster(kmeans_result,
             data         = df_cluster,
             geom         = "point",
             ellipse      = TRUE,
             ellipse.type = "convex",
             palette      = c("#CD7F32", "#C0C0C0", "#FFD700"),
             ggtheme      = theme_minimal(),
             main         = "Segmentasi Pelanggan — K-Means Clustering (K=3)")
Hasil K-Means Clustering

Hasil K-Means Clustering

df_customer %>%
  group_by(cluster) %>%
  summarise(
    jumlah_pelanggan = n(),
    rata_frekuensi   = round(mean(frekuensi), 2),
    rata_spending    = round(mean(total_spending), 2),
    rata_recency     = round(mean(recency), 2)
  ) %>%
  kable(
    caption   = "Ringkasan Statistik per Cluster",
    col.names = c("Cluster", "Jumlah Pelanggan", "Rata-rata Frekuensi",
                  "Rata-rata Spending ($)", "Rata-rata Recency (hari)")
  ) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                full_width = FALSE)
Ringkasan Statistik per Cluster
Cluster Jumlah Pelanggan Rata-rata Frekuensi Rata-rata Spending ($) Rata-rata Recency (hari)
Bronze 146 26.84 109.19 0.0
Silver 252 22.98 95.32 175.7
Gold 201 31.51 136.54 165.0

Hasil K-Means Clustering dengan K=3 berhasil mengelompokkan 599 pelanggan ke dalam tiga segmen dengan karakteristik perilaku transaksi yang berbeda-beda.

🥉 Segmen Bronze (146 pelanggan) terdiri dari pelanggan dengan rata-rata frekuensi rental 26,84 kali dan total spending rata-rata $109,19. Yang paling menonjol adalah nilai recency sebesar 0 hari, yang berarti seluruh pelanggan dalam kelompok ini melakukan transaksi terakhirnya pada Februari 2006 — periode paling akhir dalam data. Dengan kata lain, Bronze adalah satu-satunya segmen yang masih aktif saat data diambil. Meski demikian, frekuensi dan spending-nya berada di posisi menengah, mengindikasikan bahwa pelanggan Bronze adalah tipe pelanggan kasual yang kembali aktif namun belum menunjukkan komitmen transaksi yang tinggi.

🥈 Segmen Silver (252 pelanggan) merupakan kelompok terbesar dengan jumlah 252 pelanggan atau sekitar 42% dari total pelanggan. Rata-rata frekuensi rental hanya 22,98 kali dengan total spending rata-rata $95,32 — keduanya merupakan nilai terendah di antara ketiga segmen. Nilai recency sebesar 175,7 hari menunjukkan pelanggan Silver terakhir bertransaksi sekitar Agustus 2005, yang berarti sudah hampir 6 bulan tidak aktif. Kombinasi frekuensi rendah, spending rendah, dan recency tinggi menjadikan Silver sebagai segmen pelanggan yang berisiko churn atau bahkan sudah churn. Meskipun demikian, karena jumlahnya paling besar, segmen ini menyimpan potensi revenue yang signifikan jika berhasil direaktivasi.

🥇 Segmen Gold (201 pelanggan) memiliki rata-rata frekuensi rental tertinggi sebesar 31,51 kali dan rata-rata total spending tertinggi sebesar $136,54, menjadikannya segmen paling bernilai secara bisnis. Recency rata-rata 165,0 hari menunjukkan transaksi terakhir sekitar Agustus 2005 — tidak aktif dalam periode terkini, namun rekam jejak transaksi historisnya sangat kuat. Pelanggan ini adalah tipe loyal customer yang pernah sangat terlibat dengan layanan toko, sehingga peluang reaktivasi jauh lebih besar dibanding segmen lainnya.

4 Kesimpulan

Ringkasan temuan utama:

  1. Tren Revenue — Revenue mengalami lonjakan signifikan pada Juli 2005 kemudian sedikit menurun di Agustus 2005, dengan jeda panjang hingga Februari 2006 yang perlu diinvestigasi lebih lanjut.
  2. Perbandingan Toko — Store 1 dan Store 2 menunjukkan performa yang relatif seimbang sepanjang periode pengamatan, tanpa dominasi signifikan dari salah satu toko.
  3. Regresi Linear — Frekuensi rental terbukti berpengaruh sangat signifikan terhadap total spending pelanggan (\(p < 0{,}001\)) dengan \(R^2 = 0{,}758\).
  4. Segmentasi Pelanggan — Terdapat 3 segmen pelanggan dengan karakteristik berbeda: Bronze (aktif, spending menengah), Silver (tidak aktif, spending rendah), dan Gold (tidak aktif, spending tinggi/loyal).

4.1 Rekomendasi Bisnis

  • Bronze — Manfaatkan keaktifan terkini pelanggan dengan strategi upselling dan cross-selling seperti rekomendasi film premium atau paket berlangganan guna mendorong peningkatan frekuensi dan spending.
  • Silver — Jalankan kampanye reaktivasi melalui diskon khusus, voucher, atau notifikasi promosi yang dipersonalisasi untuk menarik kembali pelanggan yang sudah lama tidak aktif, mengingat segmen ini adalah yang terbesar.
  • Gold — Prioritaskan program loyalitas eksklusif seperti membership premium atau reward poin untuk mempertahankan dan mengaktifkan kembali pelanggan paling profitable ini.
  • Operasional — Investigasi penyebab gap transaksi antara Agustus 2005 dan Februari 2006 untuk mencegah kejadian serupa di masa mendatang.