Projek ini merupakan implementasi dari proposal E-Commerce Optimization Strategies yang sebelumnya telah dibuat. Proposal tersebut berisi tentang latar belakang projek hingga tujuan maupun luaran dari projek ini. Adapun projek ini terbagi menjadi beberapa bagian, yakni:
Pada bagian ini, kita akan berfokus pada Customer Segmentation with RFM method. Alangkah baiknya pembaca untuk membaca secara berurutan dari proposal hingga bagian terakhir, sehingga dapat memahami dengan sempurna.
Metode Recency, Frequency, Monetary Value (RFM) merupakan salah sartu metode yang sering digunakan untuk segmentasi pelanggan. Metode ini menggunakan pendekatan kebiasaan/perilaku pelanggan dalam bertransaksi melalui riwayat belanja pelanggan. Nilai atau definisi dari parameter metode analisis RFM ini sebagai berikut;
Melalui parameter metode RFM, kita dapat mengetahui karakteristik pelanggan, sehingga memudahkan pelaku usaha dalam memberikan perlakuan yang spesial sesuai karakter pelanggan. Pelanggaan akan dikelompokkan berdasarkan pada kemiripan sifat-sifatnya berdasarkan parameter RFM tersebut.
Terdapat beberapa Bussines Question yang bisa terjawab melalui analisis RFM ini, yakni:
Proses persiapan data telah dilakukan pada Part 1 dari projek ini, sehingga kita dapat langsung menggunakannya. Dataset kita merupakan riwayat transaksi dari E-Commerce di pakistan pada periode Maret 2016 hingga Agustus 2018.
library(dplyr)
library(lubridate)
library(ggplot2)
library(padr)
library(tidyr)
library(GGally)
library(factoextra)
library(plotly)
library(stats)
library(clustree)
library(glue)
library(tidyverse)
library(scales)
library(gridExtra)
library(zoo)
library(paletti)
library(rfm)
library(highcharter)
library(treemap)
# Kustomisasi Tema Visualisasi
# Kustomisasi Warna dan Visualisasi chart
my_color <- c(
col1="#fcf800",
col2="#fce700",
col3="#fcdb03",
col4="#e3c502",
col5="#fcbe03",
col6="#fc9d03",
col7="#fc5d00"
)
my_theme_fill <- get_scale_fill(get_pal(my_color))
my_theme_color <- get_scale_color(get_pal(my_color))
my_theme_hex <- get_hex(my_color)
color_dark_text = "#222629"
# MY PLOT THEME
my_plot_theme <- function (base_size, base_family="Segoe UI Semibold"){
dark_color="#222629"
facet_header = "#78767647"
dark_text = "#222629"
half_line <- base_size/2
theme_algoritma <- theme(
plot.background = element_rect(fill= "#faf6e3", colour = "#faf6e3"), #background plot
plot.title = element_text(size = rel(1.5), margin = margin(b = half_line * 1.2),
color= dark_text, hjust = 0, family=base_family, face = "bold"),
plot.subtitle = element_text(size = rel(1.0), margin = margin(b = half_line * 1.2), color= dark_text, hjust=0),
plot.margin=unit(c(0.5,0.5,0.5,0.5),"cm"),
#plot.margin=unit(c(0.5,r=5,1,0.5),"cm"),
panel.background = element_rect(fill="#18181800",colour = "#e8e8e8"), #background chart
panel.border = element_rect(fill=NA,color = NA),
panel.grid.minor.x = element_blank(),
panel.grid.major.x = element_blank(),
panel.grid.major.y = element_line(color="#e8e8e8", linetype=2),
panel.grid.minor.y = element_blank(),
#panel.margin = unit(0.8*half_line, "mm"),
panel.margin.x = NULL,
panel.margin.y = NULL,
panel.ontop = FALSE,
panel.spacing = unit(1.2,"lines"),
legend.background = element_rect(fill="#18181800",colour = NA),
legend.text = element_text(size = rel(0.7),color=dark_text),
legend.title = element_text(colour = dark_text, size = base_size, lineheight = 0.8),
legend.box = NULL,
# text = element_text(colour = "white", size = base_size, lineheight = 0.9,
# angle = 0, margin = margin(), debug = FALSE),
axis.text = element_text(size = rel(0.8), color=dark_text),
axis.text.x = element_text(colour = dark_text, size = base_size, margin = margin(t = 0.8 * half_line/2)),
axis.text.y = element_text(colour = dark_text, size = base_size, margin = margin(r = 0.8 * half_line/2)),
axis.title.x = element_text(colour = dark_text, size = base_size, lineheight = 0.8,
margin = margin(t = 0.8 * half_line, b = 0.8 * half_line/2)),
axis.title.y = element_text(colour = dark_text, size = base_size, lineheight = 0.8,
angle = 90, margin = margin(r = 0.8 * half_line, l = 0.8 * half_line/2)),
axis.ticks = element_blank(),
strip.background = element_rect(fill=facet_header,colour = NA),
strip.text = element_text(colour = dark_text, size = rel(0.8)),
strip.text.x = element_text(margin = margin(t = half_line*0.8, b = half_line*0.8)),
strip.text.y = element_text(angle = -90, margin = margin(l = half_line, r = half_line)),
strip.switch.pad.grid = unit(0.1, "cm"),
strip.switch.pad.wrap = unit(0.1, "cm"),
complete = TRUE
)
}
data <- readRDS("data_clean.RDS")
data <- data %>% mutate(Month_Year = as.yearmon(paste(Month, Year)))
data
Deskiripsi kolom:
Order_Status
: Status dari pembelian (Completed, Cancelled, Refund)
Date_of_Order
: Tanggal pemesanan produk
SKU
: Barcode, kode, atau nama unik yang mewakili produk tertentu
Price
: Harga produk
Quantity
: Jumlah dari produk yang dibeli
Grand_Total
: Total pembayaran yang diberikan oleh pelanggan dalam satu Invoice
invoice_ID
: Id dari invoice
Category
: Kategori atau jenis E-Commerce
Discount_Amount
: Nilai diskon yang didapatkan pelanggan pada tiap produk
Payment_Method
: Metode pembayaran
BI.Status
: Representasi kolom Order_Status
BI.Status
= “Gross” ====> Order_Status
= “canceled”BI.Status
= “Net” ====> Order_Status
= “complete”BI.Status
= “Valid” ====> Order_Status
= “refund”.Revenue
: hasil perkalian antara kolom Price
dengan Quantity
Year
: Tahun, nilai ini didapat dari Date_of_Order
Month
: Bulan, nilai ini didapat dari Date_of_Order
Customer.Since
: Umur pelanggan, didapat dari riwayat belanja pertama kali yang dilakukan oleh pelanggan
M.Y
: Bulan-Tahun, nilai ini didapat dari Date_of_Order
yang merupakan gabungan dari kolom Year
dan Month
Customer_ID
: ID dari pelanggan
Pada analisis RFM, kita harus melakukan Feature Engineering terlebih dahulu. Kita akan melakukan perhitungan nilai Recency, Frequency, dan Monetary dari setiap pelanggan. Nilai Recency dapat diketahui dengan menghitung jarak antara hari melakukan analisis dengan riwayat terakhir belanja setiap pelanggan. Kemudian, nilai Frequency dari pelanggan akan kita dapatkan dengan menghitung jumlah transaksi yang telah dilakukan oleh pelanggan. Selanjutnya, nilai Monetary didapatkan dengan menjumlahkan semua uang yang telah dibayarkan oleh pelanggan.
Pada kesempatan ini, Feature Engineering akan dibantu dengan package rfm untuk mempermudah pekerjaan. Package ini telah menyediakan Feature Engineering untuk perhitungan tiap parameter RFM.
pkg_rfm_df <- data %>%
filter(Order_Status == "complete") %>%
select(Date_of_Order, Customer_ID, Invoice_ID, Grand_Total) %>%
distinct() %>%
select(Date_of_Order, Customer_ID, Grand_Total)
rfm_result <- rfm_table_order(
data = pkg_rfm_df,
customer_id = Customer_ID,
revenue = Grand_Total,
order_date = Date_of_Order,
analysis_date = as.Date(max(pkg_rfm_df$Date_of_Order) + 1)
)
rfm_result$rfm
Deskripsi kolom:
customer_id
: ID unik pelanggandate_most_recent
: Periode terakhir pelanggan melakukan transaksirecency_days
: nilai recency pelanggantransaction_count
: nilai frequency pelangganamount
: nilai monetary pelangganrecency_score
: skor dari recency pelangganfrequency_score
: skor dari frequency pelangganmonetary_score
: skor dari monetary pelangganrfm_score
: total skor dari nilai RFM pelangganPada data frame diatas kita menemukan indeks skor dari parameter RFM pelanggan. Nilai skor tiap parameter pelanggan didapat dengan teknik quantile dengan ketentuan sebagai berikut:
as.data.frame(quantile(rfm_result$rfm$recency_days,
probs = seq(0, 1, 0.20))) %>%
rename("quantile_recency" = "quantile(rfm_result$rfm$recency_days, probs = seq(0, 1, 0.2))") %>%
mutate(recency_score = c(NA,5,4,3,2,1),
quantile_frequency = quantile(rfm_result$rfm$transaction_count,probs = seq(0, 1, 0.20)),
frequency_score = c(NA,1,2,3,4,5),
quantile_monetary = quantile(rfm_result$rfm$amount,probs = seq(0, 1, 0.20)),
monetary_score = c(NA,1,2,3,4,5)) %>%
drop_na()
Pada persebaran data frequency pelanggan, melalui nilai quantile dapat kita lihat bersama bahwa lebih dari 60% data memiliki nilai frequency adalah satu. Hal itu bermakna bahwa lebih dari 60% pelanggan hanya pernah melakukan sekali transaksi saja.
Kemudian, nilai RFM Score didapat dengan rumus sebagai berikut:
RFM Score = recency_score x 100 + frequency_score x 10 + monetary_score
Jika kita melihat ketentuan penetapan skor dari parameter rfm, dengan auto quantile, menunjukkan hasil yang kurang baik, karena persebaran data kita. Sehingga kita akan membuat batasan quantile tersendiri untuk parameter recency dan frequency. Adapun pada recency, kita akan menggunakan ketetapan quantile sebagai berikut:
Selanjutnya, pada nilai frequency, kita akan membagi nilai tertinggi dengan 5 dan menetapkan ketepan skor frequency dengan tiap kelipatan hasil bagi lima dari nilai max frequency.
#> [1] 314
Okey, jadi ketetapan skor quantile dari frequency adalah sebagai berikut:
baiklah, mari kita buat kembali dengan melakukan custom pada penetapan skor parameter frequency dan recency-nya.
rfm_result <- rfm_table_order(
data = pkg_rfm_df,
customer_id = Customer_ID,
revenue = Grand_Total,
order_date = Date_of_Order,
analysis_date = as.Date(max(pkg_rfm_df$Date_of_Order) + 1),
frequency_bins = c(314, 314*2, 314*3, 314*4),
recency_bins = c(30, 90, 180, 270)
)
rfm_result$rfm
Ingat!. Tujuan melakukan kustom adalah menyesuakan persebaran dari data kita agar nilai skor atau pengelompokan pelanggan pada tahap selanjutnya lebih merepresentasikan karakteristik sebenarnya.
Selanjutnya, mari kita lihat hubungan antara tiap parameter dari metode RFM.
scatter_rf <- rfm_result$rfm %>%
ggplot(aes(x = transaction_count, y = recency_days), fill = my_color["col5"], color = my_color["col5"] ) +
geom_point(aes(text = glue("Recency: {recency_days}
Frequency: {transaction_count}"))) +
labs(title = "Recency vs Frequency",
y = "Recency",
x = "Frequency") +
my_plot_theme(10)
scatter_rf
Dari visualisasi hubungan antara nilai recency dengan frequency, kita lihat bersama bahwa antara dua parameter tersebut tidak terdapat hubungan yang kentara. Yah, hal tersebut dapat diterima karena nilai kebaharuan pelanggan melakukan transaksi memang tidak dapat merepsentasikan nilai tingkat frekuensi belanja pelanggan tersebut.
Selanjutnya, coba kita lihat pada hubungan nilai recency dengan monetary pelanggan. Mari kita buat dugaan bersama bahwa kedua nilai tersebut tidak memiliki hubungan.
scatter_rm <- rfm_result$rfm %>%
ggplot(aes(x = amount, y = recency_days), fill = my_color["col5"], color = my_color["col5"]) +
geom_point(aes(text = glue("Recency: {recency_days}
Monetary: Rs {amount}"))) +
labs(title = "Recency vs Monetary",
y = "Recency",
x = "Monetary") +
my_plot_theme(10)
scatter_rm
Yups, benar sebagaimana yang kita duga bersama. Visualisasi hubungan antara nilai recency dengan monetary melalui scatter plot menunjukkan bahwa antara dua parameter tersebut juga tidak terdapat hubungan yang kentara. Memang benar, nilai yang merepresentasikan periode hari kapan terkahir kali belanja tidak dapat menunjukkan seberapa besar pelanggan telah membelanjakan uangnya di perusahaan tersebut.
Mari kita coba renungkan, jika kita sebagai pelanggan baru yang masih sekitar satu minggu melakukan belanja namun telah membelanjakan uang dalam jumlah yang signifikan atau ada pelanggan yang telah lama tidak melakukan belanja dan hanya melakukan belanja dalam jumlah kecil, Bagaimana kita dapat menemukan hubungan antara keduanya? Jelas secara rasional kita dapat menyimpulkan bahwa memang seharusnya tidak ada hubungan antara kedua parameter tersebut.
Mari kita lanjutkan pada bagian parameter frequency dengan monetary. Jika kita renungkan, seharusnya terdapat hubungan antara keduanya. Semakin sering seseorang belanja maka semakin banyak juga uang yang telah dibelanjakan dalam suatu tempat belanja tersebut. Sekarang coba kita buktikan renungan kita bersama.
scatter_fm <- rfm_result$rfm %>%
ggplot(aes(x = amount, y = transaction_count), fill = my_color["col5"], color = my_color["col5"]) +
geom_point(aes(text = glue("Frequency: {transaction_count}
Monetary: Rs {amount}"))) +
labs(title = "Frequency vs Monetary",
y = "Frequency",
x = "Monetary") +
my_plot_theme(10)
scatter_fm
Yups, antara parameter frequency dengan monetary, terlihat hubungan positif yang cukup kentara. Akan tetapi kita dapat melihat bahwasannya terdapat beberapa outlier, yang nantinya jika kita lihat persebarannya pasti hasilnya ialah persebaran yang skewed. Persebaran yang skewed ini cukup dapat membuat bias pada hasil segmentasi pelanggan kedepannya. Sehingga, harus ada manipulasi data agar jarak atau simpangan antara data tidak terlalu jauh dan memberikan hasil segmentasi pelanggan yang baik.
Okey, untuk memperkuat pemahaman yang kita dapatkan dari scatter plot, mari kita tinjau nilai korelasi antar parameter RFM.
ggcorr(rfm_result$rfm %>% select(recency_days,transaction_count,amount), label = TRUE, label_size = 4, vjust=1, hjust=0.5)+
labs(
title="Correlation Matrix on RFM Value"
)+
my_plot_theme(11)+
scale_fill_gradient(low=my_theme_hex("col2") ,na.value = "#C0C0C0", high=my_theme_hex("col6"))
Sebagaimana hasil visualisasi scatter, nilai recency bisa dikatakan tidak berhubungan dengan parameter frequency maupun monetary. Sedangkan parameter frequency berhubungan positif dengan parameter monetary. Hasil ini memperkuat pemahaman kita dari analisis melalui visualisasi scatter plot sebelumnya.
Primary Insight
Ada pemahaman penting yang kita dapatkan melalui Scatter Plot dan Correlation RFM Value. Keputusan kita untuk membuat segmentasi berdasarkan recency secara terpisah, sebagaimana disebutkan pada proposal E-Commerce Optimization Strategies adalah keputusan yang tepat. Tidak adanya hubungan antara parameter recency dengan frequency maupun monetary adalah alasannya. Sedangkan, adanya hubungan parameter frequency dan monetary menjadi alasan yang baik untuk melakukan segmentasi pelanggan dengan kombinasi dua parameter tersebut secara bersamaan. Hasil segmentasi pelanggan yang bias hingga membuat segmen pelanggan tidak memiliki karakter yang terlihat jelas dapat terhindarkan.
Penting bagi kita untuk mengetahui distribusi dari tiap parameter RFM. Tentu, tujuannya untuk menghindari hasil segmentasi yang tidak mampu membedakan segmen pelanggan dengan baik. Mari kita coba lihat bersama.
hist_rfm <- rfm_histograms(rfm_result, hist_bins = 12, print_plot = F, hist_color = "orange") +
my_plot_theme(10)
ggplotly(hist_rfm)
Pada parameter recency, data kita memiliki distribusi yang cukup baik, namun sangat berbeda dengan persebaran data untuk parameter frequency dan monetary. Data dari dua parameter tersebut sangat tersebar secara skewed positif. Kedepannya, kita perlu melakukan scalling pada data kita ketika melukakan segmentasi dengan algoritma k-means pada dua parameter tersebut.
Pada segmentasi pelanggan, kita akan mencoba pengelompokan dengan menggunakan algoritma unsupevised learning k-means dan juga teknik quantile. Dengan memanfaatkan karakter pelanggan yang didapatkan dari metode RFM, kita akan mencoba membandingkan antara hasil segmentasi yang murni dengan metode quantile dengan hasil segmentasi dari kombinasi k-means dan metode quantile. Kita akan memanfaan nilai RFM score yang kita dapatkan untuk segmentasi pelanggan dengan metode quantile secara keseluruhan. Sedangkan, segmentasi kombinasi ialah algoritma k-means untuk parameter frequency dan monetary, dan teknik quantile untuk parameter recency.
1. Customer Value Segmentation (k-means)
Pada k-means kita akan mengelompokkan berdasarkan nilai customer dari frequency dan monetary. Hal ini dilakukan karena dua parameter ini meliki korelasi, harapannya, dapat memberikan hasil yang baik.
Ingat! kita perlu melakukan scalling pada data kita karena persebarannya yang sangat skewed sedangkan algoritma pengelompokkan dengan k-means sangat sensitif terhadap jarak.
Kita akan mencoba dua metode, yakni dengan teknik scale
dan menggunakan nilai minimal serta maksimal data untuk melakukan scalling.
norm_minmax <- function(x){
return ((x - min(x))/(max(x) - min(x)))
}
par(mfrow=c(2,2),cex=0.9)
hist(norm_minmax(rfm_result$rfm$transaction_count),main="minmax of frequency")
hist(norm_minmax(rfm_result$rfm$amount),main="minmax of monetary")
hist(scale(rfm_result$rfm$transaction_count), main="scale of frequency")
hist(scale(rfm_result$rfm$amount), main="scale of monetary")
Walaupun telah dilakukan scalling, dapat kita lihat bersama bahwa persebaran datanya masih sangat terlalu buruk. Hal tersebut mungkin didasari oleh adanya nilai-nilai outlier yang tersebar sangat jauh dari dimana data berpusat. Kita dapat mencoba mendeteksi nilai-nilai outlier tersebut dan menjadikan segmen tersendiri. Adapun data sisanya akan kita lakukan scalling dan segmentasi dengan k-means.
scat_fm <- rfm_result$rfm %>%
ggplot(aes(transaction_count, amount)) +
geom_jitter() +
geom_smooth(method = lm) +
labs(
title = "Frequency vs Monetary of all data"
) +
my_plot_theme(10)
ggplotly(scat_fm)
frequency_adjust = 18
monetary_adjust = 40000
rfm_result_adjust <- rfm_result$rfm %>%
filter(transaction_count <= frequency_adjust, amount <= monetary_adjust)
scat_fm_adjust <- rfm_result_adjust %>%
ggplot(aes(transaction_count, amount)) +
geom_jitter() +
geom_smooth(method = lm) +
labs(
title = "Frequency vs Monetary of 92% data"
) +
my_plot_theme(10)
ggplotly(scat_fm_adjust)
Alhamdulillah. Setelah kita menyeleksi 92% dari data, persebaran data menjadi lebih baik.
par(mfrow=c(2,2),cex=0.9)
hist(norm_minmax(rfm_result_adjust$transaction_count), main="minmax of frequency")
hist(norm_minmax(rfm_result_adjust$amount), main="minmax of monetary")
hist(scale(rfm_result_adjust$transaction_count), main="scale of frequency")
hist(scale(rfm_result_adjust$amount), main="scale of monetary")
Dari visualisasi histogram, kita tidak mendapatkan distibusi yang baik, karena memang data sebenarnya lebih tersebar ke skala yang kecil. Namun plot histogram diatas lebih baik daripada sebelum dilakukan penyeleksian.
Selanjutnya, kita akan mencoba menggunakan kedua teknik normalisasi data kita untuk melakukan segmentasi dan kita evaluasi karakter dari tiap segmen. Kemudian, kita akan memilih segmen yang mampu membedakan karakter pelanggan dengan baik.
Sebelumnya, kita akan melihat/mengevaluasi jumlah cluster paling maksimal dalam mengkarakterisasi tiap segmen pelanggan yang akan kita gunakan dengan menggunakan elbow method.
set.seed(123)
inertia_values_mm <- numeric(length = 10)
for (i in 1:10) {
kmeans_result <- kmeans(norm_minmax(rfm_result_adjust[,c("transaction_count", "amount")]), centers = i)
inertia_values_mm[i] <- kmeans_result$tot.withinss
}
elbow_data_mm <- data.frame(K = 1:10, inertia = inertia_values_mm)
ggplot(elbow_data_mm, aes(x = K, y = inertia)) +
geom_line() +
geom_point() +
labs(subtitle="normalized by min max value", x = "Number of Clusters (K)", y = "Inertia") +
ggtitle("Elbow Method for Optimal K") +
scale_x_continuous(minor_breaks = NULL,
n.breaks = 10) +
theme_minimal()
set.seed(123)
inertia_values_sc <- numeric(length = 10)
for (i in 1:10) {
kmeans_result <- kmeans(scale(rfm_result_adjust[,c("transaction_count", "amount")]), centers = i)
inertia_values_sc[i] <- kmeans_result$tot.withinss
}
elbow_data_sc <- data.frame(K = 1:10, inertia = inertia_values_sc)
ggplot(elbow_data_sc, aes(x = K, y = inertia)) +
geom_line() +
geom_point() +
labs(subtitle="normalized by scale()", x = "Number of Clusters (K)", y = "Inertia") +
ggtitle("Elbow Method for Optimal K") +
scale_x_continuous(minor_breaks = NULL,
n.breaks = 10) +
theme_minimal()
Dari dua teknik normalisasi data yang kita gunakan, keduanya memberikan nilai cluster maksimal diantara 3 dan 4.
set.seed(2020, sample.kind = "Rounding")
kmeans_k3_mm <- kmeans(norm_minmax(rfm_result_adjust[,c("transaction_count", "amount")]),centers = 3, iter.max = 20)
kmeans_k4_mm <- kmeans(norm_minmax(rfm_result_adjust[,c("transaction_count", "amount")]),centers = 4, iter.max = 20)
kmeans_k3_sc <- kmeans(scale(rfm_result_adjust[,c("transaction_count", "amount")]),centers = 3, iter.max = 20)
kmeans_k4_sc <- kmeans(scale(rfm_result_adjust[,c("transaction_count", "amount")]),centers = 4, iter.max = 20)
#> [1] 48444 7790 3714
#> [1] 2796 5119 8657 43376
#> [1] 50153 2696 7099
#> [1] 1297 6508 5626 46517
#> [1] 89.9
#> [1] 94.2
#> [1] 69.1
#> [1] 77.8
# K-Means
k3_k4_mm_sc <- rfm_result_adjust %>%
cbind(
k3_mm_segment = kmeans_k3_mm$cluster) %>%
mutate(
k3_mm_segment = as.factor(k3_mm_segment)
) %>%
cbind(
k4_mm_segment = kmeans_k4_mm$cluster) %>%
mutate(
k4_mm_segment = as.factor(k4_mm_segment)
) %>%
cbind(
k3_sc_segment = kmeans_k3_sc$cluster) %>%
mutate(
k3_sc_segment = as.factor(k3_sc_segment)
) %>%
cbind(
k4_sc_segment = kmeans_k4_sc$cluster) %>%
mutate(
k4_sc_segment = as.factor(k4_sc_segment)
)
k3_k4_mm_sc
k3_mm_profile <- k3_k4_mm_sc %>% group_by(k3_mm_segment) %>%
summarise(
min_recency = min(recency_days),
max_recency = max(recency_days),
avg_recency = round(mean(recency_days),2),
min_frequency = min(transaction_count),
max_frequency = max(transaction_count),
avg_frequency = round(mean(transaction_count),2),
med_frequency = round(median(transaction_count),2),
min_monetary = min(amount),
max_monetary = max(amount),
avg_monetary = round(mean(amount),2),
med_monetary = round(median(amount),2),
total_monetary = sum(amount),
total_customer = n()
) %>% ungroup()
k4_mm_profile <- k3_k4_mm_sc %>% group_by(k4_mm_segment) %>%
summarise(
min_recency = min(recency_days),
max_recency = max(recency_days),
avg_recency = round(mean(recency_days),2),
min_frequency = min(transaction_count),
max_frequency = max(transaction_count),
avg_frequency = round(mean(transaction_count),2),
med_frequency = round(median(transaction_count),2),
min_monetary = min(amount),
max_monetary = max(amount),
avg_monetary = round(mean(amount),2),
med_monetary = round(median(amount),2),
total_monetary = sum(amount),
total_customer = n()
) %>% ungroup()
k3_sc_profile <- k3_k4_mm_sc %>% group_by(k3_sc_segment) %>%
summarise(
min_recency = min(recency_days),
max_recency = max(recency_days),
avg_recency = round(mean(recency_days),2),
min_frequency = min(transaction_count),
max_frequency = max(transaction_count),
avg_frequency = round(mean(transaction_count),2),
med_frequency = round(median(transaction_count),2),
min_monetary = min(amount),
max_monetary = max(amount),
avg_monetary = round(mean(amount),2),
med_monetary = round(median(amount),2),
total_monetary = sum(amount),
total_customer = n()
) %>% ungroup()
k4_sc_profile <- k3_k4_mm_sc %>% group_by(k4_sc_segment) %>%
summarise(
min_recency = min(recency_days),
max_recency = max(recency_days),
avg_recency = round(mean(recency_days),2),
min_frequency = min(transaction_count),
max_frequency = max(transaction_count),
avg_frequency = round(mean(transaction_count),2),
med_frequency = round(median(transaction_count),2),
min_monetary = min(amount),
max_monetary = max(amount),
avg_monetary = round(mean(amount),2),
med_monetary = round(median(amount),2),
total_monetary = sum(amount),
total_customer = n()
) %>% ungroup()
k3_mm_perform <- round((kmeans_k3_mm$betweenss/kmeans_k3_mm$totss)*100,1)
k4_mm_perform <- round((kmeans_k4_mm$betweenss/kmeans_k4_mm$totss)*100,1)
k3_sc_perform <- round((kmeans_k3_sc$betweenss/kmeans_k3_sc$totss)*100,1)
k4_sc_perform <- round((kmeans_k4_sc$betweenss/kmeans_k4_sc$totss)*100,1)
plot_k3_mm_profile <- k3_mm_profile %>%
gather("features","values",max_frequency,min_frequency,avg_frequency,med_frequency,
min_monetary,max_monetary,avg_monetary,med_monetary) %>%
ggplot(aes(x = k3_mm_segment, y = features)) +
#scale_x_continuous(breaks = seq(min(cluster), max(cluster), by = 1)) +
geom_tile(aes(fill = values), show.legend = FALSE) +
geom_text(aes(label=round(values,2)), size=4, color="black")+
coord_equal() +
labs(
title = "K-Means Clustering for 3 Segment",
subtitle = paste("betweenss/totts:",k3_mm_perform,"% (normalized by min max value)"),
x = "Cluster",
y = NULL
)+
scale_fill_gradient(low=my_theme_hex("col3") ,na.value = "#C0C0C0", high=my_theme_hex("col6"))+
theme(text = element_text(size=14, color = "black"),
plot.title =element_text(size=16),
legend.position = "bottom",
axis.text.y = element_text(size=14),
axis.title.x = element_text(size=14))
plot_k4_mm_profile <- k4_mm_profile %>%
gather("features","values",max_frequency,min_frequency,avg_frequency,med_frequency,
min_monetary,max_monetary,avg_monetary,med_monetary) %>%
ggplot(aes(x = k4_mm_segment, y = features)) +
#scale_x_continuous(breaks = seq(min(cluster), max(cluster), by = 1)) +
geom_tile(aes(fill = values), show.legend = FALSE) +
geom_text(aes(label=round(values,2)), size=4, color="black")+
coord_equal() +
labs(
title = "K-Means Clustering for 4 Segment",
subtitle = paste("betweenss/totts:",k4_mm_perform,"% (normalized by min max value)"),
x = "Cluster",
y = NULL
)+
scale_fill_gradient(low=my_theme_hex("col3") ,na.value = "#C0C0C0", high=my_theme_hex("col6"))+
theme(text = element_text(size=14, color = "black"),
plot.title =element_text(size=16),
legend.position = "bottom",
axis.text.y = element_text(size=14),
axis.title.x = element_text(size=14))
plot_k3_sc_profile <- k3_sc_profile %>%
gather("features","values",max_frequency,min_frequency,avg_frequency,med_frequency,
min_monetary,max_monetary,avg_monetary,med_monetary) %>%
ggplot(aes(x = k3_sc_segment, y = features)) +
#scale_x_continuous(breaks = seq(min(cluster), max(cluster), by = 1)) +
geom_tile(aes(fill = values), show.legend = FALSE) +
geom_text(aes(label=round(values,2)), size=4, color="black")+
coord_equal() +
labs(
title = "K-Means Clustering for 3 Segment",
subtitle = paste("betweenss/totts:",k3_sc_perform,"% (normalized by scale method)"),
x = "Cluster",
y = NULL
)+
scale_fill_gradient(low=my_theme_hex("col3") ,na.value = "#C0C0C0", high=my_theme_hex("col6"))+
theme(text = element_text(size=14, color = "black"),
plot.title =element_text(size=16),
legend.position = "bottom",
axis.text.y = element_text(size=14),
axis.title.x = element_text(size=14))
plot_k4_sc_profile <- k4_sc_profile %>%
gather("features","values",max_frequency,min_frequency,avg_frequency,med_frequency,
min_monetary,max_monetary,avg_monetary,med_monetary) %>%
ggplot(aes(x = k4_sc_segment, y = features)) +
#scale_x_continuous(breaks = seq(min(cluster), max(cluster), by = 1)) +
geom_tile(aes(fill = values), show.legend = FALSE) +
geom_text(aes(label=round(values,2)), size=4, color="black")+
coord_equal() +
labs(
title = "K-Means Clustering for 4 Segment",
subtitle = paste("betweenss/totts:",k4_sc_perform,"% (normalized by scale method)"),
x = "Cluster",
y = NULL
)+
scale_fill_gradient(low=my_theme_hex("col3") ,na.value = "#C0C0C0", high=my_theme_hex("col6"))+
theme(text = element_text(size=14, color = "black"),
plot.title =element_text(size=16),
legend.position = "bottom",
axis.text.y = element_text(size=14),
axis.title.x = element_text(size=14))
Dapat kita lihat bersama bahwasanya teknik normalisasi dengan nilai minimal dan maksimal dari data mampu memberikan hasil segmentasi yang lebih baik daripada menggunakan teknik scale() method. Hasil segmentasi dengan nilai minimal maksimal mampu memberikan kelompok pelanggan dengan karakter yang sama dengan lebih baik. Adapun jumlah klaster 3 sudah mampu mengelompokkan pelanggan sesuai karakter sama dengan baik. Sehingga, kita akan menggunakan hasli klaster dari data yang ternormalisasi dengan teknik min max value dan banyak klaster yang digunakan ialah 3.
Selanjutnya, mari kita gabungkan 8% data yang kita eliminasi sebelumnya.
kmeans_segment <- rfm_result$rfm %>%
left_join(k3_k4_mm_sc %>% select(customer_id, k3_mm_segment), by=c("customer_id")) %>%
mutate(k3_mm_segment = ifelse(is.na(k3_mm_segment),4, k3_mm_segment)) %>%
rename("fm_segment" = "k3_mm_segment")
kmeans_segment
Kemudian, mari kita lihat profil karakter dari tiap klaster yang dihasilkan dan kemudian kita beri nama terhadap tiap klaster.
kmeans_profile <- kmeans_segment %>%
group_by(fm_segment) %>%
summarise(
min_frequency = min(transaction_count),
max_frequency = max(transaction_count),
avg_frequency = round(mean(transaction_count),2),
med_frequency = round(median(transaction_count),2),
min_monetary = min(amount),
max_monetary = max(amount),
avg_monetary = round(mean(amount),2),
med_monetary = round(median(amount),2),
total_monetary = sum(amount),
total_customer = n()
) %>% ungroup()
cluster_kmeans_summary <- kmeans_profile %>% mutate(
popup = glue("Total Customer: {total_customer} ({round((total_customer/sum(total_customer))*100,1)})%
Total Monetary: PKR {comma(total_monetary)} ({comma((total_monetary/sum(total_monetary))*100)}%)
Med. Frequency: {med_frequency} Order
Med. Monetary: PKR {med_monetary}"),
fm_segment = as.factor(fm_segment)
) %>%
ggplot(aes(fm_segment,med_frequency)) +
geom_bar(aes(fill=fm_segment, text=popup),stat="identity", show.legend = FALSE)+
geom_text(aes(label = paste0('Med: ',round(med_frequency,0)), y = med_frequency + 0.2),size=3, color="black")+
#geom_text(aes(label=paste0(round((total_customer/sum(total_customer))*100,1),"% cust"),
# y=total_monetary+220000),size=3, color="black")+
labs(
title="Profiling: Median Frequency per Cluster",
x="Cluster",
y="Median Frequency") +
my_theme_fill()+
my_plot_theme(10)
ggplotly(cluster_kmeans_summary, tooltip="text") %>%
layout(showlegend=FALSE)
cluster_kmeans_summary <- kmeans_profile %>% mutate(
popup = glue("Total Customer: {total_customer} ({round((total_customer/sum(total_customer))*100,1)})%
Total Monetary: Rs {comma(total_monetary)} ({comma((total_monetary/sum(total_monetary))*100)}%)
Med. Frequency: {med_frequency} Order
Med. Monetary: Rs {comma(med_monetary)}"),
fm_segment = as.factor(fm_segment)
) %>%
ggplot(aes(fm_segment,med_monetary)) +
geom_bar(aes(fill=fm_segment, text=popup),stat="identity", show.legend = FALSE)+
geom_text(aes(label=paste0("Rs ",comma(med_monetary,1)),y=med_monetary + 3000), size=3, color="black")+
labs(
title="Profiling: Median Monetary per Cluster",
x="Cluster",
y="Median Monetary"
) +
my_theme_fill()+
my_plot_theme(10)
ggplotly(cluster_kmeans_summary,tooltip="text") %>%
layout(showlegend=FALSE)
plot_kmeans_profile <- kmeans_profile %>%
gather("features","values",max_frequency,min_frequency,avg_frequency,med_frequency,
min_monetary,max_monetary,avg_monetary,med_monetary) %>%
ggplot(aes(x = fm_segment, y = features)) +
#scale_x_continuous(breaks = seq(min(cluster), max(cluster), by = 1)) +
geom_tile(aes(fill = values), show.legend = FALSE) +
geom_text(aes(label=round(values,2)), size=4, color="black")+
coord_equal() +
labs(
title = "K-Means Clustering Profile",
# subtitle = paste("betweenss/totts:",k4_sc_perform,"% (normalized by scale method)"),
x = "Cluster",
y = NULL
)+
scale_fill_gradient(low=my_theme_hex("col3") ,na.value = "#C0C0C0", high=my_theme_hex("col6"))+
theme(text = element_text(size=14, color = "black"),
plot.title =element_text(size=16),
legend.position = "bottom",
axis.text.y = element_text(size=14),
axis.title.x = element_text(size=14))
plot_kmeans_profile
Berdasarkan profiling yang kita lakukan, kita dapat mengetahui bahwa secara umum hasil clustering yang kita lakukan lebih mengarah pada karakter monetary pelanggan. Adapun karakter frequency pelanggan tidak terlalu kentara. Adapun klaster 1 memiliki anggota sekitar 76% dari total pelanggan perusahaan e-commerce ini.
Baiklah, mari kita beri nama terhadap semua klaster yang kita dapatkan dari algoritma kmeans.
Cluster 1 : Very low Frequency - Very Low Monetary. Kita sebut segmen Low Value Customer.
Cluster 2 : Very low Frequency - Medium Monetary. Kita sebut segmen Medium Value Customer.
Cluster 3 : Low Frequency - High Monetary. Kita sebut segmen High Value Customer.
Cluster 4 : Medium Frequency - Very High Monetary. Kita sebut segmen Special Value Customer.
kmeans_segment <- kmeans_segment%>%
mutate(
fm_segment = case_when(
fm_segment == "1" ~ "Low Value",
fm_segment == "2" ~ "Medium Value",
fm_segment == "3" ~ "High Value",
fm_segment == "4" ~ "Special Value"),
fm_segment = factor(fm_segment, levels=c("Low Value","Medium Value",
"High Value","Special Value"))
)
# kmeans_segment %>%
# mutate(size_point = as.integer(fm_segment),
# size_point = size_point*10) %>%
# plot_ly( x = ~transaction_count, y = ~amount, color = ~as.factor(fm_segment), size = ~size_point,
# colors=c(my_theme_hex("col3"),my_theme_hex("col4"),my_theme_hex("col5"),my_theme_hex("col7")),
# hoverinfo = 'text', text = ~paste("Customer ID: ", customer_id,
# "<br>Value Segment: <b>",fm_segment,"</b>",
# "<br>Frequency: <b>", transaction_count,"</b>",
# "<br>Monetary: <b>Rs ",amount,"</b>")) %>%
# #layout(margin = list(l = 10, r = 10, b = 10, t = 10)) %>%
# config(displayModeBar = F) %>%
# layout(font=list(size = 12)) %>%
# layout(title="Customer Value Segmentation")
2. Recency Segmentation (Quantile) Pada segmentasi nilai recency, nilai yang menunjukkan jarak hari dari waktu terakhir belanja pelanggan dengan waktu dilakukan analisis, kita akan membaginya dengan teknik quantile. Periode data kita memiliki periode dua tahun, sehingga akan membaginya sebagai berikut:
recency_adjust <- data.frame(
`new customer` = 30,
active = 90,
warm = 180,
cold = 270,
inactive = Inf
)
df_cust_segment <- kmeans_segment %>%
mutate(
recency_segment = case_when(recency_days <= recency_adjust$new.customer ~ "new.customer",
recency_days > recency_adjust$new.customer & recency_days <= recency_adjust$active ~ "active",
recency_days > recency_adjust$active & recency_days <= recency_adjust$warm ~ "warm",
recency_days > recency_adjust$warm & recency_days <= recency_adjust$cold ~ "cold",
TRUE ~ "inactive"),
recency_segment = factor(recency_segment, levels=c(names(recency_adjust)))
)
recency_count <- data.frame(table(df_cust_segment$recency_segment))
recency_segmentation <- df_cust_segment %>%
group_by(recency_days) %>%
summarise(freq=n()) %>% ungroup() %>%
mutate(popup=glue("Recency: {recency_days}
Total Customer: {freq}"))
plot_recency_segmentation <- ggplot(recency_segmentation,aes(recency_days, freq))+
geom_area(fill = my_color[5])+
geom_point(aes(text = popup), size=0.2, alpha=0.05)+
geom_vline(xintercept = recency_adjust$new.customer,linetype="dotted",color = "black", size=1,alpha=0.2)+
geom_vline(xintercept = recency_adjust$active,linetype="dotted",color = "black", size=1,alpha=0.2)+
geom_vline(xintercept = recency_adjust$warm,linetype="dotted",color = "black", size=1,alpha=0.2)+
geom_vline(xintercept = recency_adjust$cold,linetype="dotted",color = "black", size=1, alpha=0.2)+
geom_point(aes(x=recency_adjust$new.customer, y=10+50), colour="black", size=1)+
annotate("text", x = recency_adjust$new.customer, y = 10+120,
color = "black", size=3, label=paste0(recency_adjust$new.customer," days"))+
geom_point(aes(x=recency_adjust$active, y=370), colour="black", size=1)+
annotate("text", x = recency_adjust$active, y = 440,
color = "black", size=3, label=paste0(recency_adjust$active," days"))+
geom_point(aes(x=recency_adjust$warm, y=470), colour="black", size=1)+
annotate("text", x = recency_adjust$warm, y = 540,
color = "black", size=3, label=paste0(recency_adjust$warm," days"))+
geom_point(aes(x=recency_adjust$cold, y=1670), colour="black", size=1)+
annotate("text", x = recency_adjust$cold, y = 1740,
color = "black", size=3, label=paste0(recency_adjust$cold," days"))+
annotate("text", x = (0+recency_adjust$new.customer)/2, y = max(recency_segmentation$freq),
color = "black", size=3, label="New")+
annotate("text", x = (0+recency_adjust$new.customer)/2, y = max(recency_segmentation$freq)+140,
color = "black", size=3.2, label=prettyNum(recency_count %>% filter(Var1=="new.customer") %>% .$Freq, big.mark=","))+
annotate("text", x = (0+recency_adjust$new.customer)/2, y = max(recency_segmentation$freq)-140,
color = "#676767", size=2.2, label=paste0(round(((recency_count %>% filter(Var1=="new.customer") %>%
.$Freq)/sum(recency_count$Freq))*100,1),"%"))+
annotate("text", x = median(recency_adjust$new.customer:recency_adjust$active), y = max(recency_segmentation$freq),
color = "black", size=3, label="Active")+
annotate("text", x = median(recency_adjust$new.customer:recency_adjust$active), y = max(recency_segmentation$freq)+140,
color = "black", size=3.2, label=prettyNum(recency_count %>% filter(Var1=="active") %>% .$Freq, big.mark=","))+
annotate("text", x = median(recency_adjust$new.customer:recency_adjust$active), y = max(recency_segmentation$freq)-140,
color = "#676767", size=2.2, label=paste0(round(((recency_count %>% filter(Var1=="active") %>%
.$Freq)/sum(recency_count$Freq))*100,1),"%"))+
annotate("text", x = median(recency_adjust$active:recency_adjust$warm), y = max(recency_segmentation$freq),
color = "black", size=3, label="Warm")+
annotate("text", x = median(recency_adjust$active:recency_adjust$warm), y = max(recency_segmentation$freq)+140,
color = "black", size=3.2, label=prettyNum(recency_count %>% filter(Var1=="warm") %>% .$Freq, big.mark=","))+
annotate("text", x = median(recency_adjust$active:recency_adjust$warm), y = max(recency_segmentation$freq)-140,
color = "#676767", size=2.2, label=paste0(round(((recency_count %>% filter(Var1=="warm") %>%
.$Freq)/sum(recency_count$Freq))*100,1),"%"))+
annotate("text", x = median(recency_adjust$warm:recency_adjust$cold), y = max(recency_segmentation$freq),
color = "black", size=3, label="Cold")+
annotate("text", x = median(recency_adjust$warm:recency_adjust$cold), y = max(recency_segmentation$freq)+140,
color = "black", size=3.2, label=prettyNum(recency_count %>% filter(Var1=="cold") %>% .$Freq, big.mark=","))+
annotate("text", x = median(recency_adjust$warm:recency_adjust$cold), y = max(recency_segmentation$freq)-140,
color = "#676767", size=2.2, label=paste0(round(((recency_count %>% filter(Var1=="cold") %>%
.$Freq)/sum(recency_count$Freq))*100,1),"%"))+
annotate("text", x = median(recency_adjust$cold:max(recency_segmentation$recency_days)), y = max(recency_segmentation$freq),
color = "black", size=3, label="Inactive")+
annotate("text", x = median(recency_adjust$cold:max(recency_segmentation$recency_days)), y = max(recency_segmentation$freq)+140,
color = "black", size=3.2, label=prettyNum(recency_count %>% filter(Var1=="inactive") %>% .$Freq, big.mark=","))+
annotate("text", x = median(recency_adjust$cold:max(recency_segmentation$recency_days)), y = max(recency_segmentation$freq)-140,
color = "#676767", size=2.2, label=paste0(round(((recency_count %>% filter(Var1=="inactive") %>%
.$Freq)/sum(recency_count$Freq))*100,1),"%"))+
labs(
#title= "Order Value by Recency",
title = "Recency Segmentation using Quantile Method",
x = "Recency (days since last transaction)",
y = "Total Customer"
)+
my_plot_theme(10)
ggplotly(plot_recency_segmentation, tooltip = NULL)%>%
layout(showlegend=FALSE) %>%
config(displayModeBar = F)
Sebanyak 60% dari pelanggan e-commerce berstatus inactive alias sudah lebih dari 9 bulan tidak melakukan belanja. Sedangkan pelanggan baru hanya berjumlah sekitar 30 orang.
Sekarang mari kita lakukan segmentasi menggunakan nilai skor dari tiap parameter RFM. Adapun kategori pengelompokan sebagai berikut.
segment <- c("Champions", "Loyal Customers", "Potential Loyalist",
"New Customers", "Promising", "Need Attention", "About To Sleep",
"At Risk", "Can't Lose Them", "Hibernating", "Lost")
description <- c("Bought recently, buy often and spend the most",
"Spend good money. Responsive to promotions",
"Recent customers, spent good amount, bought more than once",
"Bought more recently, but not often",
"Recent shoppers, but haven’t spent much",
"Above average recency, frequency & monetary values",
"Below average recency, frequency & monetary values",
"Spent big money, purchased often but long time ago",
"Made big purchases and often, but long time ago",
"Low spenders, low frequency, purchased long time ago",
"Lowest recency, frequency & monetary scores")
recency <- c("4-5","2-5","3-5","4-5","3-4","2-3","2-3","1-2","1","2-3","1-2")
frequency <- c("4-5","3-5","1-3","1","1","2-3","1-2","2-5","4-5","2-3","1-2")
monetary <- c("4-5","3-5","1-3","1","1","2-3","1-2","2-5","4-5","2-3","1-2")
coba <- data.frame(segment, description, recency, frequency, monetary)
knitr::kable(coba, "pipe")
segment | description | recency | frequency | monetary |
---|---|---|---|---|
Champions | Bought recently, buy often and spend the most | 4-5 | 4-5 | 4-5 |
Loyal Customers | Spend good money. Responsive to promotions | 2-5 | 3-5 | 3-5 |
Potential Loyalist | Recent customers, spent good amount, bought more than once | 3-5 | 1-3 | 1-3 |
New Customers | Bought more recently, but not often | 4-5 | 1 | 1 |
Promising | Recent shoppers, but haven’t spent much | 3-4 | 1 | 1 |
Need Attention | Above average recency, frequency & monetary values | 2-3 | 2-3 | 2-3 |
About To Sleep | Below average recency, frequency & monetary values | 2-3 | 1-2 | 1-2 |
At Risk | Spent big money, purchased often but long time ago | 1-2 | 2-5 | 2-5 |
Can’t Lose Them | Made big purchases and often, but long time ago | 1 | 4-5 | 4-5 |
Hibernating | Low spenders, low frequency, purchased long time ago | 2-3 | 2-3 | 2-3 |
Lost | Lowest recency, frequency & monetary scores | 1-2 | 1-2 | 1-2 |
# We label the various segments
segment_names <- c("Champions", "Loyal Customers", "Potential Loyalist",
"New Customers", "Promising", "Need Attention", "About To Sleep",
"At Risk", "Can't Lose Them", "Hibernating", "Lost")
# We set the upper and lower bounds for recency, frequency, and monetary for the above segments
recency_lower <- c(4, 2, 3, 4, 3, 2, 2, 1, 1, 2, 1)
recency_upper <- c(5, 5, 5, 5, 4, 3, 3, 2, 1, 3, 2)
frequency_lower <- c(4, 3, 1, 1, 1, 2, 1, 2, 4, 2, 1)
frequency_upper <- c(5, 5, 3, 1, 1, 3, 2, 5, 5, 3, 2)
monetary_lower <- c(4, 3, 1, 1, 1, 2, 1, 2, 4, 2, 1)
monetary_upper <- c(5, 5, 3, 1, 1, 3, 2, 5, 5, 3, 2)
# We use the segments and the bounds we previously established to group our users into different segments
segment <- rfm_segment(rfm_result,
segment_names,
recency_lower,
recency_upper,
frequency_lower,
frequency_upper,
monetary_lower,
monetary_upper)
Dapat kita lihat bersama, bahwasannya data histori belanja pelanggan kita hanya memiliki 6 segmen dengan segment Others
merupakan kategori pelanggan yang tidak memenuhi semua kategori yang telah kita definisikan. Adapun anggota segmen terbanyak ialah Others
. Hanya terdapat 4 pelanggan yang terdeteksi sebagai Loyal Customer
dan sebanyak 4.721 pelanggan terdeteksi sebagai Potential Loyalist
. Adapun banyaknya pelanggan yang terdeteksi churn atau Lost
sejumlah 18.563.
Selanjutnya, mari kita coba lihat karakter RFM dari tiap segmen pelanggan.
df_cust_segment <- df_cust_segment %>%
select(customer_id, recency_segment, fm_segment) %>%
left_join(segment, by = "customer_id") %>%
mutate(
segment = factor(segment, levels = c("Others", "Lost", "At Risk", "About To Sleep", "Potential Loyalist", "Loyal Customers"))) %>%
select(customer_id, recency_segment, fm_segment, segment, rfm_score, transaction_count, recency_days, amount)
rfmscore_profile <- df_cust_segment %>%
group_by(segment) %>%
summarise(
total_customer = n(),
total_monetary = sum(amount),
total_frequency = sum(transaction_count),
min_frequency = min(transaction_count),
max_frequency = max(transaction_count),
avg_frequency = as.integer(median(transaction_count)),
min_monetary = min(amount),
max_monetary = max(amount),
avg_monetary = median(amount),
min_recency = min(recency_days),
max_recency = max(recency_days),
avg_recency = median(recency_days)
) %>%
mutate(
persen_customer = round(total_customer/sum(total_customer)*100,2)
)
rfmscore_summary <- rfmscore_profile %>% mutate(
popup = glue("Total Customer: {total_customer} ({round((total_customer/sum(total_customer))*100,1)})%
Med. Recency: {avg_recency} days")
) %>%
ggplot(aes(y = segment, x = avg_recency)) +
geom_bar(aes(fill=segment, text=popup),stat="identity", show.legend = FALSE)+
geom_text(aes(label = paste0('Med: ',round(avg_recency,0)),
x = avg_recency + 35),
size = 3,
color = "black")+
#geom_text(aes(label=paste0(round((total_customer/sum(total_customer))*100,1),"% cust"),
# y=total_monetary+220000),size=3, color="black")+
labs(
title="Profiling: Median Recency per Cluster",
x = "Median Recency",
y= NULL) +
my_theme_fill()+
my_plot_theme(10)
ggplotly(rfmscore_summary, tooltip="text") %>%
layout(showlegend=FALSE)
rfmscore_summary <- rfmscore_profile %>% mutate(
popup = glue("Total Customer: {total_customer} ({round((total_customer/sum(total_customer))*100,1)})%
Total Frequency: {total_frequency}
Med. Frequency: {avg_frequency} Order")
) %>%
ggplot(aes(y = segment, x = avg_frequency)) +
geom_bar(aes(fill=segment, text=popup), stat="identity", show.legend = FALSE)+
geom_text(aes(label = paste0('Med: ',round(avg_frequency,0)), x = avg_frequency + 40),size=3, color="black")+
#geom_text(aes(label=paste0(round((total_customer/sum(total_customer))*100,1),"% cust"),
# y=total_monetary+220000),size=3, color="black")+
labs(
title="Profiling: Median Frequency per Cluster",
x = "Median Frequency",
y= NULL) +
my_theme_fill()+
my_plot_theme(10)
ggplotly(rfmscore_summary, tooltip="text") %>%
layout(showlegend=FALSE)
rfmscore_summary <- rfmscore_profile %>% mutate(
popup = glue("Total Customer: {total_customer} ({round((total_customer/sum(total_customer))*100,1)})%
Total Monetary: Rs {comma(total_monetary)}
Med. Monetary: Rs {comma(avg_monetary)}")
) %>%
ggplot(aes(y = segment, x = avg_monetary)) +
geom_bar(aes(fill= segment, text = popup), stat="identity", show.legend = FALSE)+
geom_text(aes(label=paste0("Rs ",comma(avg_monetary,1)),x =avg_monetary + 100000),size=3, color="black")+
labs(
title="Profiling: Median Monetary per Cluster",
x = "Median Monetary",
y = NULL) +
my_theme_fill()+
my_plot_theme(10)
ggplotly(rfmscore_summary,tooltip="text") %>%
layout(showlegend=FALSE)
Dapat kita lihat bersama bahwa profiling dari karakter segmen tiap pelanggan yang dihasilkan melalui RFM score masih belum mampu membedakan karakter pelanggan dengan baik. Kemudian, kebanyakan pelanggan tidak memasuki semua segmen yang kita kategorikan.
Selanjutnya, mari kita bandingkan hasil segmentasi dari kombinasi k-means dan quantile dengan segmentasi RFM score.
plot_tile_rfm_segmentation <- df_cust_segment %>%
group_by(recency_segment,fm_segment) %>%
summarise(freq = n(),
total_monetary=sum(amount),
avg_monetary = round(mean(amount)),
avg_frequency = median(transaction_count)) %>%
ungroup() %>%
mutate(
popup=glue("Total Customer: {freq}
RFM Segment : {toupper(recency_segment)} - {toupper(fm_segment)}
Med. Frequency : {avg_frequency}
Avg. Monetary : Rs {comma(avg_monetary)}")
) %>%
mutate(percent_freq = round((freq/sum(freq))*100,2)) %>%
ggplot(aes(recency_segment, fm_segment))+
geom_tile(aes(fill = percent_freq, text=popup), colour = "white", show.legend = FALSE) +
geom_text(aes(label=paste0(percent_freq,"%"),text=popup), size=4, color="black", show.legend = FALSE)+
labs(
y= NULL,
x= "Days since last purchase",
fill = "Avg. Monetary"
)+
scale_fill_gradient(low = my_theme_hex("col3"),na.value = "#C0C0C0", high=my_theme_hex("col6"))+
theme(legend.title=element_text(size=9),
legend.position = "bottom")+
my_plot_theme(8)
ggplotly(plot_tile_rfm_segmentation, tooltip = "text")%>%
#layout(showlegend=FALSE, margin = list(l = 10, r = 10, b = 10, t = 10)) %>%
layout(showlegend=FALSE) %>%
config(displayModeBar = F) %>%
layout(title="RFM Segmentation Result by Quantile & K-means")
rfmscore_profile %>%
mutate(popup = glue("Segment: {segment}
Total Customer: {total_customer}
Avg. Recency: {avg_recency}
Avg. Frequency: {avg_frequency}
Avg. Monetary: Rs {comma(avg_monetary)}"),
popup = str_replace_all(popup, pattern = "\n", replacement = "<br>")) %>%
hchart(
"treemap",
hcaes(x = segment,
value = persen_customer,
color = persen_customer,
text = popup),
dataLabels = list(enabled = TRUE, format='{point.segment}<br/>{point.persen_customer}%<br/>'),
tooltip = list(pointFormat = "{point.popup}")
) %>%
hc_title(text = "RFM Segmentation Result by RFM Score") %>%
hc_colorAxis(stops = color_stops(colors = my_color)) %>%
hc_legend(enabled = F)
nb: nilai persen menyatakan persentase pelaggan anggota klaster dari total pelanggan
Secara pasti, kita dapat melihat bahwa untuk data histori belanja e-commerce pakistan ini, metode segmentasi dengan RFM score kurang relevan. Hampir 60% pelanggan tidak terdeteksi atau memenuhi kriteria dari 11 segmen yang telah dibuat sebelumnya. Adapun segmentasi dengan kombinasi k-means dan quantile memberikan kelompok pelanggan sebanyak 5 berdasarkan nilai recency dan 4 berdasarkan nilai frequency dan monetary. Segmentasi kombinasi k-means dan quantile menghasilkan sebanyak 20 kelompok pelanggan. Sehingga, pada kesempatan ini kita akan menggunakan segmentasi dari kombinasi k-means dan quantile.
Selanjutnya mari kita coba ketahui total monetary yang telah diberikan oleh tiap segmen, baik dari segmen recency maupun segmen customer value.
ks <- function (x) { number_format(accuracy = 1,
scale = 1/1000000,
suffix = "M",
big.mark = ",")(x) }
ks_thousand <- function (x) { number_format(accuracy = 1,
scale = 1/1000,
suffix = "K",
big.mark = ",")(x) }
plot_recency_total_amount <- df_cust_segment %>% group_by(recency_segment) %>%
summarise(
total_customer = n(),
total_transaction = sum(transaction_count),
total_monetary = sum(amount),
percent_monetary = round((total_monetary/sum(df_cust_segment$amount))*100,1)
) %>%
ungroup() %>%
mutate(
popup = glue("Total Customer : {comma(total_customer)} ({round((total_customer/sum(total_customer))*100,1)})%
Total Monetary: Rs {comma(total_monetary)} ({percent_monetary}%)"),
recency_segment = factor(recency_segment, levels=c("inactive","cold","warm","active","new.customer"))
) %>%
ggplot(aes(recency_segment,total_monetary))+
geom_bar(stat = "identity", aes(fill=recency_segment, text=popup), show.legend = FALSE)+
#geom_text(aes(label=paste0(comma(total_customer)," (",round((total_customer/sum(total_customer))*100,1),"%) Cust"),
# y=total_monetary+450000),size=3,color="black")+
geom_text(aes(label=paste0(round((total_monetary/sum(total_monetary))*100,1),"%"),y=total_monetary+30999999), size=3)+
scale_y_continuous(labels = ks, limits = c(0, 421000000))+
labs(
title = "Total Monetary by Recency Segment",
x = NULL,
y = "Total Monetary"
)+
coord_flip()+
my_theme_fill()+
my_plot_theme(10)
ggplotly(plot_recency_total_amount,tooltip="text") %>%
layout(showlegend=FALSE) %>%
config(displayModeBar = F)
Secara recency segmen, kita dapatkan pemahaman bahwasannya sebanyak 40,5% dari total monetary yang telah didapatkan selama dua tahun ialah berasal dari segmen warm. Adapun segmen active, yang mana meiliki nilai recency di rentang 30-90 hari, masih belum berpartisipasi aktif dalam menyumbangkan kenaikan pendapatan bagi perusahaan, begitu juga dengan segmen new.customer. Namun hal itu dapat diterima mengingat nilai frequency-nya yang masih kecil. Selain itu, jumlah anggota segmen active dan new.customer sangat sedikit, sekitar 0,2% dari total pelanggan.
Terdapat hal menarik dari hasil visualisasi di atas, yakni walaupun segmen warm memiliki persentase jumlah anggota sekitar 15%, namun mampu menyumbangkan 40,5% dari total pendapatan yang diterima perusahaan. Segmen warm ini menjadi segmen yang potensial, namun perlu hati-hati karena telah tiga bulan lebih tidak melakukan belanja. Perusahaan dapat menerapkan strategi untuk menarik pelanggan warm ini agar melakukan transaksi lebih sering.
Pada segmen new.customer dan active, perusahaan perlu menreapkan strategi yang berfokus kepada peningkatan frekuensi berbelanja dari pelanggan. Sehingga, pendapatan perusahaan dapat meningkat.
plot_fm_total_monetary <- df_cust_segment %>% group_by(fm_segment) %>%
summarise(total_monetary = sum(amount),
total_customer = n()) %>%
ungroup() %>%
mutate(
popup = glue("Total Customer: {total_customer} ({round((total_customer/sum(total_customer))*100,1)})%
Total Monetary: Rs {comma(total_monetary)} ({comma((total_monetary/sum(total_monetary))*100)}%)")) %>%
ggplot(aes(x = fm_segment, y = total_monetary)) +
geom_bar(aes(fill = fm_segment, text=popup), stat="identity", show.legend = FALSE)+
geom_text(aes(label=paste0(round(total_monetary/sum(total_monetary)*100,1),"%"),
y=total_monetary+ 49999999),size=3, color="black")+
labs(
#title="Order Value by Freqmon Segment",
title = "Total Monetary per Customer Value Segmen",
x= NULL,
y="Monetary"
)+
scale_y_continuous(labels = ks, limits = c(0, 700000000))+
coord_flip() +
my_theme_fill() +
my_plot_theme(10)
ggplotly(plot_fm_total_monetary, tooltip= "text") %>%
layout(showlegend=FALSE) %>%
config(displayModeBar = F)
Wow,.. walaupun anggota segmen special value hanya 7,7%, namun hampir 68% total monetary perusahaan berasal dari segmen ini. Adapun presentase pelanggan tertinggi berada di segmen low value yang mana hampir 75%. Walupun demikian, antara semua segmen, selain special value, memberikan kontribusi total monetary yang hampir sama.
Selanjunya, mari kita coba lihat dari segi nilai frequency-nya.
plot_fm_total_freq <- df_cust_segment %>% group_by(fm_segment) %>%
summarise(total_monetary = sum(amount),
total_customer = n(),
total_frequency = sum(transaction_count),
avg_frequency = median(transaction_count),
avg_monetary = median(amount)) %>%
ungroup() %>%
mutate(popup = glue("Total Customer: {total_customer} ({round((total_customer/sum(total_customer))*100,1)})%
Total Frequency: {comma(total_frequency)} ({comma((total_frequency/sum(total_frequency))*100)}%)")) %>%
ggplot(aes(fm_segment, total_frequency)) +
geom_bar(aes(fill=fm_segment, text=popup),stat="identity", show.legend = FALSE)+
geom_text(aes(label=paste0(round(total_frequency/sum(total_frequency)*100,1),"%"),
y=total_frequency+ 4800), size=3, color="black")+
scale_y_continuous(limits = c(0, 76000))+
labs(
title = "Total Transaction per Customer Value Segmen",
x= NULL,
y="Frequency Transaction"
)+
coord_flip()+
my_theme_fill()+
my_plot_theme(10)
ggplotly(plot_fm_total_freq,tooltip="text") %>%
layout(showlegend=FALSE) %>%
config(displayModeBar = F)
Jika kita melihat visualisasi terkait jumlah total transaksi yang dilakukan berdasarkan customer value segmen, kita dapatkan kembali hal menarik dimana meskipun total pelanggan segmen special value hanya 7,7% namun menyumbangkan 37,9% dari total transaksi di perusahaan. Kita juga perlu memperhatikan segmen Low Value, meskipun nilai dari anggota segmen ini rendah tetapi jumlah persentase total transaksi yang dilakukan oleh pelanggan segmen ini hampir 50%. Hal itu berkaitan juga dengan banyaknya pelanggan yang tergolong sebagai Low Value, yakni hampir 75% daritotal pelanggan perusahaan.
plot_recency_total_tran <- df_cust_segment %>% group_by(recency_segment) %>%
summarise(
total_customer = n(),
total_transaction = sum(transaction_count),
total_monetary = sum(amount),
percent_monetary = round((total_monetary/sum(df_cust_segment$amount))*100,1)
) %>%
ungroup() %>%
mutate(
popup = glue("Total Customer : {comma(total_customer)} ({round((total_customer/sum(total_customer))*100,1)})%
Total Frequency: {total_transaction} order"),
recency_segment = factor(recency_segment, levels=c("inactive","cold","warm","active","new.customer"))
) %>%
ggplot(aes(recency_segment,total_transaction))+
geom_bar(stat = "identity", aes(fill=recency_segment, text=popup), show.legend = FALSE)+
geom_text(aes(label=paste0(round((total_transaction/sum(total_transaction))*100,1),"%"),
y=total_transaction + 7000), size=3)+
scale_y_continuous(limits = c(0, 90000)) +
labs(
title = "Total Transaction by Recency Segment",
x = NULL,
y = "Frequency Transaction"
)+
coord_flip()+
my_theme_fill()+
my_plot_theme(10)
ggplotly(plot_recency_total_tran,tooltip="text") %>%
layout(showlegend=FALSE) %>%
config(displayModeBar = F)
Begitu juga pada total transaksi persegmen pelanggan dari Recency Segmentation. Perbandingan persentase dari total transaksi tiap segmen selaras dengan jumlah total pelanggan yang termasuk dalam segmen tersebut, sehingga tidak terdapat fakta menarik yang bisa ditekankan. Lebih dari itu, kita perlu memprhatikan hal yang sangat fundamental yakni mengembalikan segmen pelanggan inactive menjadi active kembali, mengingat banyaknya pelangga perusahaan ini yang telah tergolong Inactive. Selain itu, sebanyak 48,7% total transaksi di perusahaan ini adalah berasal dari segmen inactive.
Sekarang mari kita ringkas dari semua kegiatan yang kita lakukan pada topik Customer Segmentation di perusahaan E-commperce Pakistan ini.
Pelanggan Perusahaan: E-commerce Pakistan ini memiliki total jumlah pelanggan dari Maret 2016 hingga Agustus 2018 sebanyak 98.576 pelanggan.
Segmentasi Pelanggan: Hasil segmentasi pelanggan menggunakan metode RFM dibagi menjadi segmentasi berdasarkan nilai Recency (Recency Segmentation) dengan metode Quantile dan berdasarkan kombinasi nilai Frequency-Monetary (Customer Value Segmentation) dengan k-means algorithm. Hasil Recency Segmentation menghasilkan 5 segmen yakni new customer, active, warm, cold dan inactive. Sedangkan Customer Value Segmentation menghasilkan 4 segmen, yakni Special Value, High Value, Medium Value, dan Low Value.
Profil Segmen Pelanggan: Berikut adakah ringkasan karakter dari tiap segmen pelanggan yang telah dibuat serta distribusi dari total anggota segmennya.
Kita bersama telah berhasil menganalisis pelanggan dari perusahaan e-commerce Pakistan dan telah menghasilkan beberapa segmen pelanggan. Sekarang mari kita berikan beberapa rekomendasi strategi bisnis yang dapat diterapkan terhadap tiap segmen pelanggan dan sesuai dengan karakter segmen tersebut.
Strategi Bisnis Berdasarkan Recency Segmentation:
Kampanye Selamat Datang: Kirimkan pesan selamat datang yang menawarkan diskon pertama atau insentif khusus untuk transaksi pertama mereka.
Program Loyalitas: Tarik mereka ke dalam program loyalitas dengan insentif berkelanjutan untuk berbelanja lebih sering.
Kampanye Promosi Berkala: Kirimkan penawaran eksklusif atau diskon berkala untuk menjaga mereka terlibat.
Cross-Selling: Tawarkan produk terkait yang sesuai dengan pembelian sebelumnya.
Kampanye Reaktivasi: Kirimkan pesan persuasif yang menawarkan insentif besar untuk mengembalikan mereka.
Survei Pelanggan: Tanyakan alasannya mengapa mereka tidak aktif dan perbaiki layanan Anda berdasarkan umpan balik mereka.
Strategi Bisnis Berdasarkan Customer Value Segmentation
Kampanye Upselling: Tawarkan produk atau layanan tambahan dengan harga terjangkau untuk meningkatkan nilai transaksi mereka.
Promosi Cross-Selling: Dorong pembelian produk tambahan yang sesuai dengan pembelian mereka.
Diskon untuk Frekuensi: Berikan insentif untuk pembelian berulang, seperti diskon untuk setiap pembelian berikutnya.
Penawaran Bundel: Tawarkan bundel produk dengan harga yang menarik untuk meningkatkan nilai transaksi.
Program VIP: Ini adalah pelanggan berharga, jadi tawarkan program VIP eksklusif dengan layanan prioritas, diskon eksklusif, atau akses ke produk terbatas.
Program Referral: Ajak mereka untuk merujuk teman dan keluarga untuk meningkatkan frekuensi pembelian.
Penghargaan Berkelanjutan: Berikan penghargaan berkelanjutan, hadiah eksklusif, atau pengiriman gratis untuk menjaga loyalitas mereka.
Program Eksklusif: Berikan akses eksklusif ke produk atau layanan tertentu yang hanya tersedia untuk segmen ini.
Dengan strategi yang sesuai untuk masing-masing segmen ini, Perusahaan dapat meningkatkan performa bisnis dalam hal Recency dan Frequency-Monetary, menjaga pelanggan yang ada dan menarik pelanggan baru.
Kita telah menyelesaikan part Customer Segmentation ini, dengan ditutup strategi bisnis yang dijamin Top Markotop :). Selanjutnya, mari kita coba me-remind kembali tentang strategi kita dalam mengurangi masalah pembatalan pesanan dan meningkatan pendapatan perusahaan ini. Benar, yakni Personalisasi Produk Rekomendasi. Kita akan menganalisis terkait produk manakah yang kemungkinan besar disukai dan akan dibeli oleh pelanggan. Nantinya, setelah kita bersama mendapatkan hasilnya, penerapan berbagai strategi bisnis yang telah kita tentukan sebelumnya dapat diterapkan pada produk barang-barang tersebut. Misalkan diambil contoh, kita aplikasikan terhadap strategi Promosi Cross-Selling: Dorong pembelian produk tambahan yang sesuai dengan pembelian mereka pada segmen Low Value. Kita gunakan hasil analisis Personalisasi Produk Rekomendasi untuk promosi cross-selling.
Tau ngak sih? Tidak cukup disitu, terdapat beberapa keuntungan yang lain dengan menggunakan strategi Personalisasi Produk Rekomendasi yang akan kita terapkan nanti. Pertama, pemasaran atau kampanye yang dilakukan oleh perusahaan akan lebih akurat. Kedua, meningkatkan engagement dengan pelanggan. Pelanggan akan merasa perusahaan dapat memenuhi kebutuhan dengan kasus unik mereka sehingga membentuk pandangan yang baik terhadap perusahaan. Ketiga, optimasi nilai Return of Investment. Dan yang Terakhir ialah dapat meningkatkan nilai penjualan.
Kata pepatah “Sekali Tepuk, Dua lalat kena”. Yups, pepatah tersebut mengambarkan manfaat strategi yang akan kita gunakan. Yuks…. Cap Cuss…..