Email: siregarbakti@gmail.com
Linkedin: https://www.linkedin.com/in/bakti-siregar-15955480/
Github: https://github.com/datazerotohero/
Pesatnya perkembangan teknologi informasi dan komunikasi berdampak pada perubahan di berbagai bidang, seperti sosial, ekonomi, politik, dan budaya, serta berdampak pada perubahan gaya hidup, termasuk pola konsumsi serta cara berjualan dan berbelanja masyarakat. Di era ini, masyarakat memanfaatkan teknologi informasi dan komunikasi untuk membeli dan/atau menjual barang dan/atau jasa melalui internet. Fenomena ini dikenal dengan perdagangan elektronik atau e-commerce. Fenomena e-commerce menyediakan pilihan cara berbelanja bagi masyarakat dengan tidak perlu datang langsung ke toko.
Berdasarkan riset yang dilakukan Badan Pusat Statistik (BPS), Total bisnis e-commerce Indonesia yan baru dimulai tahun 2019 sebesar 25.1%. Berdasar hal ini, saya berasumsi bahwa persaingan untuk mendapatkan customer akan semakin sulit. Sehinnga, problem utama yang terjadi adalah bukan tentang bagaimana mendapatkan customer baru, tapi bagaimana mengoptimalkan nilai customer yang sudah ada
Berikut datanya:
Penelitian yang lebih sfesifik yang dipublish oleh www.xendit.co, hasil riset Accenture mengatakan bahwa 40% konsumen meninggalkan suatu website bisnis dan melakukan pembelian di website atau toko online lain karena merasa kebingungan akibat terlalu banyaknya pilihan saat akan membuat keputusan pembelian.
Sumber: www.xendit.co, Badan Pusat Statistik Indonesia
Sehubungan dengan latar belakang informasi yang telah diuraikan pada bagian pendahuluan, tentu ada banyak sekali ecommerce yang merasa perlu untuk melakukan analisa atau kajian terhadap terhadap prilaku pelanggan mereka maupun kompetitornya. Salah satu metode analisis yang sering digunakan adalah Analisis RFM (Recency, Frequency, Monetary). Metode ini digunakan untuk menentukan secara kuantitatif pelanggan mana yang terbaik dengan memeriksa seberapa baru pelanggan membeli (keterkinian), seberapa sering mereka membeli (frekuensi), dan berapa banyak yang dibelanjakan pelanggan (uang).
Berdasarkan rumusan masalah diatas, tulisan ini diperuntukkan bagi anda yang ingin melakukan analisa terhadap data pelanggan ecommerce dengan menggunakan R. Hal serupa juga mungkin saja anda bisa terapkan pada data yang berbeda.
Adapun langkah-langkah atau metogologi pencarian kebenaran terhadap suatu fenomena atau fakta yang terkadung dalam data di projek ini adalah sebagai berikut:
Data yang digunakan adalah dalam projek ini menggunakan data transaksi yang terjadi antara 01/12/2010 sampai 09/12/2011 pada salah satu perusahaan online retail yang berbasis di Inggris. Banyak pelanggan dari perusahaan tersebut adalah grosir. Untuk mendownload datanya silahkan klik OnlineRetail.xlsx atau OnlineRetail.rds.
Berdasar informasi dari Online Retail Data Set berikut deskripsi datanya:
| Variabel | Deskripsi |
|---|---|
| invoice_no | Nomor invoice terdiri dari 6 digit angkat yang bersifat unik untuk setiap transaksi. Jika diawali huruf C maka mengindikasikan transaksi berstatus Cancel |
| stock_code | Kode Produk yang bersifat unik setiap produk, terdiri dari 5 digit angka yang bersifat unik untuk setiap produk. |
| description | Nama Produk |
| quantity | Jumlah produk yang dibeli |
| invoice_date | Tanggal dan waktu transaksi |
| unit_price | harga produk per unit |
| customer_id | ID Customer yang terdiri dari 5 digit angka yang bersifat unik untuk setiap customer. |
| country | Negara customer |
Pada bagian ini dilakukan suatu proses/langkah untuk membuat data mentah menjadi lebih mudah untuk dipahami dan berkualitas. Berikut ini adalah beberapa hal yang biasanya dilakukan pada tahap Persiapan Data (Data Preparation):
Berikut saya lampirkan beberapa packages yang digunakan dalam proses pengolahan data yang dilakukan dalam projek ini. Jika packages tersebut belum ter-install di PC anda, silahkan untuk melakukan instalasi sebelum anda mencoba mensumulasikan setiap program yang ada.
# Wrangling
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
library(tidyverse) # untuk wrangling
library(lubridate) # pengelolaan datetime
library(scales) # scalling variable
#visualization
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
library(plotly) # visualisasi chart
library(paletti) # warna
library(GGally) # Korelasi
library(glue) # pop up text pada chart
library(gridExtra) # Display 2 chart dalam 1 baris
#Segmentation
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
library(dbscan) # Clustering menggunakan metode DBScan
library(factoextra) # Elbow method, Shilloute methof dan Clustering menggunakan K-Means
# Kustomisasi Warna dan Visualisasi chart
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
my_color = c(
col1="#d3f2a3",
col2="#97e196",
col3="#6cc08b",
col4="#4c9b82",
col5="#217a79",
col6="#105965",
col7="#074050"
)
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=NA,colour = NA), #background plot
plot.title = element_text(size = rel(1.2), 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
)
}Dalam hal ini saya memberikan dua cara untuk import data. Cara yang pertama, import data dengan menggunakan fungsi read_excel():
library(readxl) # library digunakan untuk import data file excel
df_input <- read_excel("Data/raw/OnlineRetail.xlsx", trim_ws = TRUE)
tibble("total.data" = dim(df_input)[1],
"total.variabel" = dim(df_input)[2])## # A tibble: 1 x 2
## total.data total.variabel
## <int> <int>
## 1 541909 8
dan cara yang kedua dengan menggunakan fungsi readRDS():
df_input <- readRDS("Data/raw/OnlineRetail.rds")
tibble("total.data" = dim(df_input)[1],
"total.variabel" = dim(df_input)[2])## # A tibble: 1 x 2
## total.data total.variabel
## <int> <int>
## 1 541909 8
Jika harus memilih dari kedua cara ini, saya lebih menyarankan anda untuk menggunakan cara yang kedua karena proses loading datanya lebih cepat dibanding cara pertama. Dari hasil diatas kita tahu bahwa total data awal 541909 data dab 8 variabel. Untuk meilihat lebih detail data tersebut, anda bisa menggunakan cara berikut:
## # A tibble: 5 x 8
## InvoiceNo StockCode Description Quantity InvoiceDate UnitPrice
## <chr> <chr> <chr> <dbl> <dttm> <dbl>
## 1 536365 85123A WHITE HANG~ 6 2010-12-01 08:26:00 2.55
## 2 536365 71053 WHITE META~ 6 2010-12-01 08:26:00 3.39
## 3 536365 84406B CREAM CUPI~ 8 2010-12-01 08:26:00 2.75
## 4 536365 84029G KNITTED UN~ 6 2010-12-01 08:26:00 3.39
## 5 536365 84029E RED WOOLLY~ 6 2010-12-01 08:26:00 3.39
## # ... with 2 more variables: CustomerID <dbl>, Country <chr>
Setelah memperhatikan nama variabel atau nama kolom dari data set tersebut diatas, saya merasa perlu untuk merubah nama variabel untuk mempermudah para pembaca untuk mempelajarinya, dengan cara menerapkan lowercase dan setiap kata dipisahkan karakter underscore.
library(dplyr) # library ini digunakan untuk manipulasi data (dalam hal ini piping `%>%` dan `rename`)
colnames(df_input) <- tolower(colnames(df_input))
df_input <- df_input %>%
rename("invoice_no" = invoiceno,
"stock_code" = stockcode,
"invoice_date" = invoicedate,
"unit_price" = unitprice,
"customer_id" = customerid)
colnames(df_input)## [1] "invoice_no" "stock_code" "description" "quantity" "invoice_date"
## [6] "unit_price" "customer_id" "country"
Sebelum lebih jauh kearah analisa, ada baiknya kita meninjau struktur data set yang akan kita gunakan dengan cara berikut:
## Rows: 541,909
## Columns: 8
## $ invoice_no <chr> "536365", "536365", "536365", "536365", "536365", "536...
## $ stock_code <chr> "85123A", "71053", "84406B", "84029G", "84029E", "2275...
## $ description <chr> "WHITE HANGING HEART T-LIGHT HOLDER", "WHITE METAL LAN...
## $ quantity <dbl> 6, 6, 8, 6, 6, 2, 6, 6, 6, 32, 6, 6, 8, 6, 6, 3, 2, 3,...
## $ invoice_date <dttm> 2010-12-01 08:26:00, 2010-12-01 08:26:00, 2010-12-01 ...
## $ unit_price <dbl> 2.55, 3.39, 2.75, 3.39, 3.39, 7.65, 4.25, 1.85, 1.85, ...
## $ customer_id <dbl> 17850, 17850, 17850, 17850, 17850, 17850, 17850, 17850...
## $ country <chr> "United Kingdom", "United Kingdom", "United Kingdom", ...
data.frame(
invoice_unique = df_input$invoice_no %>% unique() %>% length(),
stock_code_unique = df_input$stock_code %>% unique() %>% length(),
description_unique = df_input$description %>% unique() %>% length(),
country_unique = df_input$country %>% unique() %>% length(),
customer_unique = df_input$customer_id %>% unique() %>% length()
)## invoice_unique stock_code_unique description_unique country_unique
## 1 25900 4070 4212 38
## customer_unique
## 1 4373
Dari data diatas kita melihat bahwa banyaknya country hanya berjumlah 38, sehingga kita dapat mengubah tipe datanya menjadi factor:
Pembersihan Data (Data cleansing) atau yang disebut juga dengan data scrubbing merupakan suatu proses analisa mengenai kualitas dari data dengan mengubah. Bisa juga pengelola mengoreksi ataupun menghapus data tersebut. Data yang dibersihkan tersebut adalah data yang salah, rusak, tidak akurat, tidak lengkap dan salah format. Beberapa hal yang saya lakukan dalam projek ini adalah:
Sesuai deskripsi variabel, jika data invoice_no diawali huruf C maka mengindikasikan transaksi berstatus Cancel. Data transaksi yang dibatalkan tidak sesuai untuk kebutuhan analisis ini, maka perlu kita dihapus.
df_input %>% filter(grepl("C", df_input$invoice_no)) %>%
summarise(total_cancelled_transaction = n())## # A tibble: 1 x 1
## total_cancelled_transaction
## <int>
## 1 9288
Terdapat 8928 transaksi yang dicancel. Data ini tidak kita gunakan maka data dapat kita abaikan (kecualikan).
Sesuai deskripsi data yang disampaikan, Invoice_No yang valid memiliki 6 digit angka. Untuk itu dilakukan pemeriksaan apakah ada faktur yang tidak sesuai.
## # A tibble: 3 x 8
## invoice_no stock_code description quantity invoice_date unit_price
## <chr> <chr> <chr> <dbl> <dttm> <dbl>
## 1 A563185 B Adjust bad~ 1 2011-08-12 14:50:00 11062.
## 2 A563186 B Adjust bad~ 1 2011-08-12 14:51:00 -11062.
## 3 A563187 B Adjust bad~ 1 2011-08-12 14:52:00 -11062.
## # ... with 2 more variables: customer_id <dbl>, country <fct>
Ternyata ditemukan 3 data transaksi yang tidak valid, maka data ini dapat kita abaikan (kecualikan).
Berikut ini dilakukan pemeriksaan apakah ada data yang memiliki quantity<=0 ? Berapa banyak? Jika ada maka akan dihapuskan.
## [1] 1336
Terdapat 1336 data transaksi yang memiliki quantity<=0, sehingga data ini perlu dibuang karena tidak valid jika suatu transaksi tidak memiliki quantity.
Apakah ada transaksi yang memiliki unit_price <=0 ?
## [1] 1179
Data ini memiliki 1178 transaksi dengan unit_price <=0. Dari informasi dataset tidak dijelaskan apakah ini menunjukan diskon, promo, gratis atau lainnya. Sehingga, transaksi ini bisa kita exclude.
Sesuai deskripsi variabel, stock code selalu diawali dengan 5 digit angka. Untuk hal ini, kita akan substring 5 digit pertama dari stock code kemudian kita convert ke tipe data numerik. Jika stock code tersebut menjadi NA Value, maka stock tersebut tidak valid.
df_input <- df_input %>% mutate(stock_code = toupper(stock_code))
df_input <- df_input %>% mutate(stock_code_nchar = nchar(stock_code))
# nchar stock code
df_input %>%
group_by(stock_code_nchar) %>%
summarise(row_count = n())## # A tibble: 10 x 2
## stock_code_nchar row_count
## <int> <int>
## 1 1 324
## 2 2 141
## 3 3 706
## 4 4 1129
## 5 5 477096
## 6 6 50246
## 7 7 383
## 8 8 20
## 9 9 15
## 10 12 43
Data diatas adalah kumpulan panjang karakter/digit dari stock_code. Informasi diatas menunjukan bahwa stock code tidak hanya terdiri dari 5 digit angka. mari kita bersihkan. Langkah Pertama, cek stock_code yang memiliki 1 sampai 4 karakter.
df_input %>% filter(stock_code_nchar %in% c(1,2,3,4)) %>%
select(stock_code,description) %>%
distinct() %>% arrange(stock_code)## # A tibble: 6 x 2
## stock_code description
## <chr> <chr>
## 1 C2 CARRIAGE
## 2 DOT DOTCOM POSTAGE
## 3 M Manual
## 4 PADS PADS TO MATCH ALL CUSHIONS
## 5 POST POSTAGE
## 6 S SAMPLES
Produk diatas terlihat tidak valid, maka bisa kita remove.
Langkah Kedua, cek stock_code yang memiliki 5 karakter.
df_input %>% filter(stock_code_nchar %in% c(5)) %>%
select(stock_code,description) %>%
distinct() %>% arrange(stock_code)## # A tibble: 3,077 x 2
## stock_code description
## <chr> <chr>
## 1 10002 INFLATABLE POLITICAL GLOBE
## 2 10080 GROOVY CACTUS INFLATABLE
## 3 10120 DOGGY RUBBER
## 4 10125 MINI FUNKY DESIGN TAPES
## 5 10133 COLOURING PENCILS BROWN TUBE
## 6 10135 COLOURING PENCILS BROWN TUBE
## 7 11001 ASSTD DESIGN RACING CAR PEN
## 8 15030 FAN BLACK FRAME
## 9 15034 PAPER POCKET TRAVELING FAN
## 10 15036 ASSORTED COLOURS SILK FAN
## # ... with 3,067 more rows
Data di atas merupakan data produk yang valid. Langkah Ketiga, mari kita cek stock_code yang terdiri dari 6 digit.
df_input %>% filter(stock_code_nchar %in% c(6)) %>%
select(stock_code,description) %>%
distinct() %>% arrange(stock_code)## # A tibble: 944 x 2
## stock_code description
## <chr> <chr>
## 1 10123C HEARTS WRAPPING TAPE
## 2 10124A SPOTS ON RED BOOKCOVER TAPE
## 3 10124G ARMY CAMO BOOKCOVER TAPE
## 4 15044A PINK PAPER PARASOL
## 5 15044B BLUE PAPER PARASOL
## 6 15044C PURPLE PAPER PARASOL
## 7 15044D RED PAPER PARASOL
## 8 15056N EDWARDIAN PARASOL NATURAL
## 9 15056P EDWARDIAN PARASOL PINK
## 10 15058A BLUE POLKADOT GARDEN PARASOL
## # ... with 934 more rows
Data di atas merupakan data produk dengan stock_code yan terdiri dari 6 karakter. Adapun terdiri dari 5 angka dan 1 huruf. Berdasarkan data di atas, huruf pada stock code terlihat seperti menunjukan karakter masing-masing produk. Coba dilihat pada stock_code 15056N dan 15056P, itu adalah produk yang sama namun memiliki warna yang berbeda. Untuk case ini kita bisa asumsikan produk tersebut sebenarnya berbeda, sehingga seluruh data diatas adalah produk yan valid.
Langkah Keempat, mari kita cek stock_code yang memiliki lebih dari 6 digit/karakter
df_input %>% filter(nchar(stock_code)>6) %>%
select(stock_code,description) %>%
distinct() %>% arrange(stock_code)## # A tibble: 15 x 2
## stock_code description
## <chr> <chr>
## 1 15056BL EDWARDIAN PARASOL BLACK
## 2 AMAZONFEE AMAZON FEE
## 3 BANK CHARGES Bank Charges
## 4 DCGS0003 BOXED GLASS ASHTRAY
## 5 DCGS0004 HAYNES CAMPER SHOULDER BAG
## 6 DCGS0069 OOH LA LA DOGS COLLAR
## 7 DCGS0070 CAMOUFLAGE DOG COLLAR
## 8 DCGS0076 SUNJAR LED NIGHT NIGHT LIGHT
## 9 DCGSSBOY BOYS PARTY BAG
## 10 DCGSSGIRL GIRLS PARTY BAG
## 11 GIFT_0001_10 Dotcomgiftshop Gift Voucher £10.00
## 12 GIFT_0001_20 Dotcomgiftshop Gift Voucher £20.00
## 13 GIFT_0001_30 Dotcomgiftshop Gift Voucher £30.00
## 14 GIFT_0001_40 Dotcomgiftshop Gift Voucher £40.00
## 15 GIFT_0001_50 Dotcomgiftshop Gift Voucher £50.00
Data di atas merupakan data stock_code yang memiliki lebih dari 6 digit/karakter. Kalau dilihat dari namanya, terdapat beberapa produk yang tidak valid seperti AMAZONFEE, BANK CHARGES, GIFT_0001_10, GIFT_0001_20, GIFT_0001_20, GIFT_0001_30, GIFT_0001_40 and GIFT_0001_50. Sehingga, mari kita remove produk-produk tersebut.
df_input <- df_input %>%
filter(!stock_code %in% c("AMAZONFEE", "BANK CHARGES","GIFT_0001_10","GIFT_0001_20",
"GIFT_0001_20", "GIFT_0001_30", "GIFT_0001_40","GIFT_0001_50",
"15056BL","DCGS0003","DCGS0004","DCGS0069","DCGS0070","DCGS0076",
"DCGSSBOY","DCGSSGIRL"))
df_input <- df_input %>% select(-invoice_no_check,-stock_code_nchar)Sampai sini data stock_code sudah clean. Mari kita lanjutkan.
Mengacu pada artikel yang dipublish oleh Diego Usai pada Mar 14, 2019 terkait Market Basket Analysis menggunakan data online retail ini, berhasil menemukan 54 deskripsi yang terkesan sengaja diinputkan secara manual. Dan telah diselidiki lebih lanjut mengenai kasus ini, ternyata seorang karyawan bahkan telah melampiaskan rasa frustrasinya pada salah satu rekan kerja mereka, dengan mengetikkan data sembarang, salah eja dan sebagainya!
Setelah saya melakukan penelusuran terhahadap data deskripsi tersebut, saya setuju dengan hasil temuannya, oleh karena itu data transaksi yang memiliki deskripsi diatas perlu dihapuskan. Catatan: Anda juga dapat melakukan penelusuran lebih lanjut jika dibutuhkan.
# Kode penyesuaian tambahan untuk dihapus
descr <- c( "check", "check?", "?", "??", "damaged", "found",
"adjustment", "Amazon", "AMAZON", "amazon adjust",
"Amazon Adjustment", "amazon sales", "Found", "FOUND",
"found box", "Found by jackie ","Found in w/hse","dotcom",
"dotcom adjust", "allocate stock for dotcom orders ta", "FBA",
"Dotcomgiftshop Gift Voucher £100.00", "on cargo order",
"wrongly sold (22719) barcode", "wrongly marked 23343",
"dotcomstock", "rcvd be air temp fix for dotcom sit",
"Manual", "John Lewis", "had been put aside",
"for online retail orders", "taig adjust", "amazon",
"incorrectly credited C550456 see 47", "returned",
"wrongly coded 20713", "came coded as 20713",
"add stock to allocate online orders", "Adjust bad debt",
"alan hodge cant mamage this section", "website fixed",
"did a credit and did not tick ret", "michel oops",
"incorrectly credited C550456 see 47", "mailout", "test",
"Sale error", "Lighthouse Trading zero invc incorr", "SAMPLES",
"Marked as 23343", "wrongly coded 23343","Adjustment",
"rcvd be air temp fix for dotcom sit", "Had been put aside." )## # A tibble: 1 x 8
## invoice_no stock_code description quantity invoice_date unit_price
## <chr> <chr> <chr> <dbl> <dttm> <dbl>
## 1 562113 22016 Dotcomgift~ 1 2011-08-02 16:11:00 83.3
## # ... with 2 more variables: customer_id <dbl>, country <fct>
Kita hanya memiliki 1 produk yang terkesan tidak valid. Produk diatas merupakan voucher. Mari kita remove.
Setiap produk seharusnya memiliki stock_code dan deksripsi yang unik. Mari kita cek apakah terdapat stock_code yang memiliki beberapa deskripsi atau sebaliknya.
data_frame(
stock_code_unique = df_input$stock_code %>% unique() %>% length(),
description_unique = df_input$description %>% unique() %>% length(),
stock_description_unique = df_input %>% select(stock_code,description) %>% distinct() %>% nrow()
)## Warning: `data_frame()` is deprecated as of tibble 1.1.0.
## Please use `tibble()` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_warnings()` to see where this warning was generated.
## # A tibble: 1 x 3
## stock_code_unique description_unique stock_description_unique
## <int> <int> <int>
## 1 3789 3992 4020
Data ditas mengindikasikan duplikasi, karena seharusnya jumlah unik dari stock_code = description = stock_code_description. Mari kita cek:
df_products <- df_input %>%
arrange(desc(invoice_date)) %>% #to get the last named of stock code and description
select(stock_code,description) %>%
distinct()
df_products %>%
group_by(stock_code) %>%
summarise(description_count=n()) %>%
ungroup() %>%
filter(description_count>1)## # A tibble: 212 x 2
## stock_code description_count
## <chr> <int>
## 1 16156L 2
## 2 17107D 3
## 3 20622 2
## 4 20725 2
## 5 20914 2
## 6 21109 2
## 7 21112 2
## 8 21175 2
## 9 21232 2
## 10 21243 2
## # ... with 202 more rows
Terdapat 212 stock_code yang memiliki lebih dari 1 deskripsi. Untuk case ini, kita perlu menyesuaikan deskripsi dari setiap produk dengan menggunakan deksripsi pada transaksi terakhir dari setiap produk. Mari kita bersihkan.
df_products <- df_products %>%
group_by(stock_code) %>%
slice(1)
df_input <- df_input %>% left_join(df_products , by=c("stock_code")) %>%
mutate(description=description.y) %>%
select(-description.x,-description.y)
data_frame(
stock_code_unique = df_input$stock_code %>% unique() %>% length(),
description_unique = df_input$description %>% unique() %>% length(),
stock_description_unique = df_input %>% select(stock_code,description) %>% distinct() %>% nrow()
)## # A tibble: 1 x 3
## stock_code_unique description_unique stock_description_unique
## <int> <int> <int>
## 1 3789 3762 3789
Setelah kita bersihkan, data di atas menunjukan terdapat deskripsi yang sama memiliki stock_code yang berbeda. Mari kita adjust menggunakan stock_code pada transaksi terakhir. Berikutnya, kita beleh melanjutkan memeriksa data country.
Berdasarkan informasi variabel, country adalah nama negara dimana customer berlokasi, jadi 1 customer seharusnya memiliki 1 negara.
data_frame(
customer_unik = df_input %>%
select(customer_id) %>%
distinct() %>%
nrow(),
customer_country_unik = df_input %>%
select(customer_id,country) %>%
distinct()
%>% nrow()
)## # A tibble: 1 x 2
## customer_unik customer_country_unik
## <int> <int>
## 1 4335 4351
Berdasar data diatas terdapat 1 customer yang terindikasi memiliki lebih dari 1 negara. Mungkin bisa saja karena customer tersebut pindah atau salah ketik, oleh karena itu kita bisa ambil negara customer berdasarkan negara terakhir ia melakukan transaksi.
df_country <- df_input %>%
arrange(desc(invoice_date,customer_id)) %>% #country that used at the last transaction.
select(customer_id, country) %>%
group_by(customer_id) %>%
slice(1)
df_input <- df_input %>% select(-country) %>%
left_join(df_country, by = c("customer_id"))
data_frame(
customer_unik = df_input %>% select(customer_id) %>% distinct() %>% nrow(),
customer_country_unik = df_input %>% select(customer_id,country) %>% distinct() %>% nrow()
)## # A tibble: 1 x 2
## customer_unik customer_country_unik
## <int> <int>
## 1 4335 4335
Sejauh ini, setiap data customer dan negara sudah sesuai. Sehingga kita dapat melanjutkan untuk memeriksa data duplikat.
Data transaksi yang kita miliki dari sampai dengan 2010-12-01 untill 2011-12-09. Berhubung kita tidak memiliki data transaksi lengkap pada Desembe 2011, maka saya memutuskan untuk take out data Desember 2011 dari analisa ini, sehingga kita memiliki transaksi full 1 tahun dari Desember 2010 sampai november 2011.
Apakah terdapat baris data yang duplikat?
data.frame(
row_of_data = df_input %>% nrow(),
row_of_unique.data = df_input %>% distinct() %>% nrow()
)## row_of_data row_of_unique.data
## 1 502321 497376
Berdasarkan hasil diatas kita menemukan bahwa data set ini pasti memiliki data yang duplikat, untuk itu perlu kita hapuskan.
## [1] 497376
Untuk projek ini, kita membutuhkan informasi total nilai setiap transaksi untuk dianalisa. Sehinga, kita bisa mengekstrak total nilai transaksi berdasarkan quantity * unit_price
Apakah terdapat missing value?
## invoice_no invoice_date customer_id country stock_code description
## 0 0 123477 0 0 0
## quantity unit_price total_amount
## 0 0 0
Dapat dilihat terdapat 123442 data yang hilang pada customer_id. Projek ini ditujukan untuk melakukan segmentasi pelanggan dan membuat personalisasi rekomendasi produk. Sehingga, pada kasus ini kita perlu membagi dataset kita menjadi 2 bagian sebagai berikut:
df_customer_segmentation : Kita tidak bisa melakukan segmentasi customer jika kita tidak mengetahui siapa customernya. Sehingga pada dataset ini, seluruh data yang tidak memiliki customer_id perlu kita remove.df_product_personalized : Dataset ini akan digunakan untuk membuat sistem rekomendasi. Dalam membuat sistem rekomendasi kita bisa menggunakan matriks customer-produk atau invoice-produk. Kedua matriks tersebut memberikan hasil yang berbeda, jika menggunakan matriks customer-produk maka artinya kita akan memberikan rekomendasi berdasarkan kemiripan histori produk yang dibeli customer dan hasil rekomendasi ini bisa digunakan kapanpun. Sedangkan, jika kita menggunakan matriks invoice-produk maka kita akan memberikan rekomendasi produk berdasarkan setiap histori produk yang dibeli pertransaksi tanpa mempedulikan siapa customernya dan hasil dari matriks ini hanya bisa digunakan jika customer sedang berbelanja atau ketika kita mengetahui isi keranjang belanja customer.library(tidyr)
df_customer_transaction <- drop_na(df_input)
df_product_personalized <- df_input %>%
select(customer_id,invoice_no,stock_code,description)
wd <- as.character(getwd())
saveRDS(object=df_customer_transaction, file=paste(paste(wd,"/Data/clean/",sep = ""),"df_customer_transaction.rds",sep=""))
saveRDS(object=df_product_personalized, file=paste(paste(wd,"/Data/clean/",sep = ""),"df_product_personalized.rds",sep=""))Berikut adalah lampiran data yang sudah bersih dan siap untuk kita lakukan proses analisa lebih lanjut.
## Rows: 373,899
## Columns: 9
## $ invoice_no <chr> "536365", "536365", "536365", "536365", "536365", "536...
## $ invoice_date <dttm> 2010-12-01 08:26:00, 2010-12-01 08:26:00, 2010-12-01 ...
## $ customer_id <dbl> 17850, 17850, 17850, 17850, 17850, 17850, 17850, 17850...
## $ country <fct> United Kingdom, United Kingdom, United Kingdom, United...
## $ stock_code <chr> "85123A", "71053", "84406B", "84029G", "84029E", "2275...
## $ description <chr> "CREAM HANGING HEART T-LIGHT HOLDER", "WHITE MOROCCAN ...
## $ quantity <dbl> 6, 6, 8, 6, 6, 2, 6, 6, 6, 32, 6, 6, 8, 6, 6, 3, 2, 3,...
## $ unit_price <dbl> 2.55, 3.39, 2.75, 3.39, 3.39, 7.65, 4.25, 1.85, 1.85, ...
## $ total_amount <dbl> 15.30, 20.34, 22.00, 20.34, 20.34, 15.30, 25.50, 11.10...
## Rows: 497,376
## Columns: 4
## $ customer_id <dbl> 17850, 17850, 17850, 17850, 17850, 17850, 17850, 17850,...
## $ invoice_no <chr> "536365", "536365", "536365", "536365", "536365", "5363...
## $ stock_code <chr> "85123A", "71053", "84406B", "84029G", "84029E", "22752...
## $ description <chr> "CREAM HANGING HEART T-LIGHT HOLDER", "WHITE MOROCCAN M...
Sebelum kita melakukan segmentasi dan membuat model untuk sistem rekomendasi, mari explore dulu data dari df_customer_transaction untuk mengetahui transaksi atas customer yang ada.
Ada berapa banyak pelanggan yang melakukan transaksi setiap Bulan?
library(lubridate) # parse tanggal dengan komponen tahun, bulan, dan hari
library(zoo) # infrastruktur rangkaian waktu
library(glue)
library(tidyverse)
library(scales)
library(plotly)
df_total_customer <- df_customer_transaction %>% select(invoice_date,customer_id) %>%
mutate( yearmonth = format(invoice_date, format="%Y-%m-1"),
yearmonth = ymd(yearmonth),
ym = as.yearmon(invoice_date)) %>%
select(customer_id,ym,yearmonth) %>%
distinct() %>%
group_by(ym,yearmonth) %>%
summarise(total_customer = n()) %>%
ungroup() %>%
mutate(
popup=glue("{ym}
Jumlah: {comma(total_customer)}")
)
df_max <- df_total_customer %>% arrange(total_customer) %>% tail(1)
df_min <- df_total_customer %>% arrange(total_customer) %>% head(1)
plot_customer <- df_total_customer %>%
ggplot(aes(x=yearmonth,total_customer))+
geom_line(size=1)+
geom_point(size=2, aes(text=popup))+
geom_point(data=df_max, aes(x=yearmonth, y=total_customer, text=popup), colour="green", size=3)+
geom_point(data=df_min, aes(x=yearmonth, y=total_customer, text=popup), colour="red", size=3)+
labs(
title = "Total Pelanggan per Bulan",
x = "Bulan-Tahun",
y = NULL
)+
scale_x_date(breaks=date_breaks('1 months'),
labels=date_format('%b %y'))+
my_plot_theme(10)+
theme(axis.text.x = element_text(angle = 45, hjust = 1))
ggplotly(plot_customer, tooltip="text") %>%
config(displayModeBar = F, scrollzoom = F)Visualisasi diatas menunjukan total customer yang melakukan transaksi setiap bulan. Bisa dilihat terendah pada Januari 2011 dan tertinggi pada November 2011. Kalau kita lihat, chart total transaksi, total nilai transaksi dan total customer yang berbelanja tiap bulan cenderung meningkat dan mirip. Bagaimana Pertumbuhan Pelanggan Baru disetiap Bulannya?
df_growth_customer <- df_customer_transaction %>% group_by(customer_id) %>%
summarise(first_order = min(invoice_date)) %>%
ungroup() %>%
mutate(yearmonth = format(first_order, format="%Y-%m-1"),
yearmonth = ymd(yearmonth),
ym = as.yearmon(first_order)) %>%
group_by(ym,yearmonth) %>%
summarise(total_new_customer = n()) %>%
ungroup() %>%
mutate(
popup=glue("{ym}
Jumlah Pelanggan Baru: {total_new_customer}"))
df_min <- df_growth_customer %>% arrange(total_new_customer) %>% head(1)
df_max <- df_growth_customer %>% arrange(total_new_customer) %>% tail(1)
plot_growth_customer <- df_growth_customer %>%
ggplot(aes(x=yearmonth,total_new_customer))+
geom_line(size=1)+
geom_point(size=2, aes(text=popup))+
geom_point(data=df_max, aes(x=yearmonth, y=total_new_customer, text=popup), colour="green", size=3)+
geom_point(data=df_min, aes(x=yearmonth, y=total_new_customer, text=popup), colour="red", size=3)+
labs(
title = "Total Pertumbuhan Pelanggan Baru per Bulan",
x = "Bulan-Tahun",
y = NULL
)+
scale_x_date(breaks=date_breaks('1 months'),
labels=date_format('%b %y'))+
my_plot_theme(10)+
theme(axis.text.x = element_text(angle = 45, hjust = 1))
ggplotly(plot_growth_customer, tooltip="text") %>%
config(displayModeBar = F, scrollzoom = F)Berdasarkan visualisasi diatas, total pertumbuhan customer baru justru cenderung menurun. Hal ini mengindikasi terdapat customer yang melakukan pembelian berulang, atau bisa kita katakan pada dataset ini terdapat customer yang loyal dan non loyal yang bisa kita harus kita ketahui. Dari hal ini, kita bisa melakukan segementasi customer supaya dapat memberikan approach yang berbeda untuk mengoptimalkan customer value dan mengefektifkan marketing cost.
library(grid)
library(gridExtra)
# Monthly
df_monthly_habbit <- df_customer_transaction %>% select(invoice_date,invoice_no) %>%
distinct() %>%
mutate(month = month(invoice_date),
day = day(invoice_date)) %>%
group_by(month,day) %>%
summarise(total = n()) %>%
ungroup() %>%
group_by(day) %>%
summarise(avg_monthly_trans = as.integer(median(total))) %>%
ungroup() %>%
mutate(popup = glue("Date : {day}
Total Transaction: {avg_monthly_trans}"))
df_monthly_habbit %>%
ggplot(aes(x=as.factor(day), y=avg_monthly_trans)) +
geom_bar(stat="identity", aes(fill=avg_monthly_trans),show.legend = FALSE)+
labs(title = "Bulanan",
x = "Date",
y = NULL)+
theme_minimal()+
theme(axis.title = element_blank(),
legend.position = "none",
plot.title = element_text(hjust = 0.5,size=12, face="bold"),
plot.subtitle = element_text(hjust = 0.5,size=10),
axis.text.y = element_blank(),
axis.text.x=element_text(size=11, face="bold"))+
scale_fill_gradient(low=my_theme_hex("col2") ,na.value = "#C0C0C0", high=my_theme_hex("col7"))+
#my_theme_fill()+
coord_polar() -> polar1
# Daily
df_wday_habbit <- df_customer_transaction %>% select(invoice_no,invoice_date) %>%
distinct() %>%
mutate(month = month(invoice_date),
wday = wday(invoice_date,week_start = getOption("lubridate.week.start", 1))) %>%
group_by(month,wday) %>%
summarise(total = n()) %>%
ungroup() %>%
group_by(wday) %>%
summarise(avg_wday_trans = as.integer(median(total))) %>%
ungroup() %>%
mutate(popup = glue("wday : {wday}
Total Transaction: {avg_wday_trans}"))
df_wday_habbit %>%
ggplot(aes(wday,avg_wday_trans))+
geom_bar(width=1, stat="identity", show.legend = FALSE, aes(fill=avg_wday_trans))+
labs(
title = "Harian",
x = "Day",
y = NULL)+
scale_x_continuous(breaks = c(1,2,3,4,5,6,7),
labels = c("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"))+
theme_minimal()+
theme(axis.title = element_blank(),
legend.position = "none",
plot.title = element_text(hjust = 0.5,size=12, face="bold"),
plot.subtitle = element_text(hjust = 0.5,size=10),
axis.text.y = element_blank(),
axis.text.x=element_text(size=11, face="bold"))+
scale_fill_gradient(low=my_theme_hex("col2") ,na.value = "#C0C0C0", high=my_theme_hex("col7"))+
coord_polar() -> polar2
#Hourly
df_hourly_habbit <- df_customer_transaction %>% select(invoice_date,invoice_no) %>%
distinct() %>%
mutate(day = day(invoice_date),
hour = hour(invoice_date)) %>%
group_by(day,hour) %>%
summarise(total = n()) %>%
ungroup() %>%
group_by(hour) %>%
summarise(avg_hourly_trans = as.integer(median(total))) %>%
ungroup() %>%
mutate(popup = glue("Hour of Day : {hour}
Total Transaction: {avg_hourly_trans}"))
time_range = data_frame(hour = c(0:23))
data_frame(hour = c(0:23)) %>%
left_join(
df_hourly_habbit ,by=c("hour")) %>%
mutate(hour = as.factor(hour)) %>%
ggplot(aes(x=hour,y=avg_hourly_trans))+
geom_bar(stat="identity",show.legend = FALSE, aes(fill=avg_hourly_trans))+
labs(title = "Setiap Jam",
x = "Hour of Day",
y = NULL)+
theme_minimal()+
theme(axis.title = element_blank(),
legend.position = "none",
plot.title = element_text(hjust = 0.5,size=12, face="bold"),
plot.subtitle = element_text(hjust = 0.5,size=10),
axis.text.y = element_blank(),
axis.text.x=element_text(size=11, face="bold"))+
scale_fill_gradient(low=my_theme_hex("col2") ,na.value = "#C0C0C0", high=my_theme_hex("col7"))+
coord_polar() -> polar3
grid.arrange(polar1,polar2,polar3, ncol = 3)Semakin gelap warna chart diatas maka menunjukan semakun banyak transaksi yang dilakukan. Berdasarkan chart ini, secara general kita bisa mengefektifkan campaign strategy berdasarkan habbit waktu customer paling berbelanja.
Berapa banyak Frekuensi Transaksi disetiap Bulannya?
df_monthly_transactions <- df_customer_transaction %>%
select(invoice_date,invoice_no) %>%
distinct() %>%
mutate(yearmonth = ymd(format(invoice_date, format="%Y-%m-1")),
ym = as.yearmon(invoice_date)) %>%
group_by(ym,yearmonth) %>%
summarise(total_transaction = n()) %>%
ungroup() %>%
mutate(
popup=glue("{ym}
{comma(total_transaction)}")
)
df_max <- df_monthly_transactions %>% arrange(total_transaction) %>% tail(1)
df_min <- df_monthly_transactions %>% arrange(total_transaction) %>% head(1)
plot_monthly_transactions <- df_monthly_transactions %>%
ggplot(aes(x=yearmonth,total_transaction))+
geom_line(size=1)+
geom_point(size=2, aes(text=popup))+
geom_point(data=df_max, aes(x=yearmonth, y=total_transaction,text=popup), colour="green", size=3)+
geom_point(data=df_min, aes(x=yearmonth, y=total_transaction,text=popup), colour="red", size=3)+
labs(title = "Frekuensi Transaksi Bulanan",
x = "Bulan-Tahun",
y = " ")+
scale_x_date(breaks=date_breaks('1 months'),
labels=date_format('%b %y'))+
my_plot_theme(10)+
theme(axis.text.x = element_text(angle = 45, hjust = 1))
ggplotly(plot_monthly_transactions,tooltip="text") %>%
config(displayModeBar = F)Banyaknya frekuensi transaksi tertinggi terjadi pada titik yang berwarna merah November 2011 dan transaksi terendah ada pada Januari 2011. Meski begitu, bisa kita katakan transaksi cenderung meningkat.
plot_most_frequency <- df_customer_transaction %>% group_by(stock_code,description) %>%
summarise(frequency = n()) %>%
ungroup() %>%
arrange(desc(frequency)) %>%
head(10) %>%
mutate(description = as.factor(description),
description = reorder(description,frequency)) %>%
ggplot(aes(x=description, y=frequency))+
geom_bar(stat="identity", aes(fill=description, text=frequency), show.legend = FALSE)+
labs(title="10 Produk Paling Populer berdasarkan Urutan Frekuensi",
x=NULL,
y="Frekuensi")+
coord_flip()+
my_plot_theme(9)+
my_theme_fill()
ggplotly(plot_most_frequency, tooltip="text") %>%
layout(showlegend=FALSE) %>%
config(displayModeBar = F, scrollzoom = F)plot_most_quantity <- df_customer_transaction %>%
group_by(stock_code,description) %>%
summarise(total_quantity = sum(quantity)) %>%
ungroup() %>%
arrange(desc(total_quantity)) %>%
head(10) %>%
mutate(description = as.factor(description),
description = reorder(description,total_quantity)) %>%
ggplot(aes(x=description, y=total_quantity))+
geom_bar(stat="identity", aes(fill=description, text=total_quantity), show.legend = FALSE)+
labs(title="10 Produk Paling Populer berdasarkan Jumlah Pesanan",
x=NULL,
y="Total Kuantitas")+
coord_flip()+
my_plot_theme(9)+
my_theme_fill()
ggplotly(plot_most_quantity, tooltip = "text") %>%
layout(showlegend=FALSE) %>%
config(displayModeBar = F, scrollzoom = F)plot_most_customer <- df_customer_transaction %>%
select(customer_id, stock_code,description) %>%
distinct() %>%
group_by(stock_code,description) %>%
summarise(total_customer = n()) %>%
ungroup() %>%
arrange(desc(total_customer)) %>%
head(10) %>%
mutate(description = as.factor(description),
description = reorder(description,total_customer)) %>%
ggplot(aes(x=description, y=total_customer))+
geom_bar(stat="identity", aes(fill=description, text=total_customer), show.legend = FALSE)+
labs(title="10 Produk Paling Populer menurut Total Pelanggan",
x=NULL,
y="Total Konsumen")+
coord_flip()+
my_plot_theme(9)+
my_theme_fill()
ggplotly(plot_most_customer, tooltip = "text") %>%
layout(showlegend=FALSE) %>%
config(displayModeBar = F, scrollzoom = F)plot_most_profit <- df_customer_transaction %>%
group_by(stock_code,description) %>%
summarise(total_amount = sum(total_amount)) %>%
ungroup() %>%
arrange(desc(total_amount)) %>%
head(10) %>%
mutate(description = as.factor(description),
description = reorder(description,total_amount)) %>%
ggplot(aes(x=description, y=total_amount))+
geom_bar(stat="identity", aes(fill=description, text=paste0("GBP ",total_amount)), show.legend = FALSE)+
labs(title="10 Produk Paling Populer berdasarkan Monetary",
x=NULL,
y="Total Nilai")+
coord_flip()+
my_plot_theme(9)+
my_theme_fill()
ggplotly(plot_most_profit, tooltip = "text") %>%
layout(showlegend=FALSE) %>%
config(displayModeBar = F, scrollzoom = F)getmodus <- function(v) {
uniqv <- unique(v)
uniqv[which.max(tabulate(match(v, uniqv)))]
}
df_item_different <- df_customer_transaction %>%
select(customer_id,stock_code,description) %>%
distinct() %>%
group_by(customer_id) %>%
summarise(item = n()) %>%
ungroup() %>%
mutate( customer_id = customer_id,
popup=glue("id : {customer_id}
Item Unik : {item}"))
plot_df_item_different <- df_item_different %>%
ggplot(aes(x=customer_id,y=item)) +
geom_point(aes(color=item, size=item, text=popup), show.legend = FALSE)+
geom_hline(yintercept=getmodus(df_item_different$item),linetype="dashed", color = "black",size=0.5,text=getmodus(df_item_different$item))+
annotate(geom="text",
x=max(df_item_different$customer_id)-2/10*length(df_item_different$customer_id),
y=getmodus(df_item_different$item)+100,size=3,
label=paste0("Modus: ",getmodus(df_item_different$item),
", Total: ",round(as.numeric(((tabulate(match(df_item_different$item, unique(df_item_different$item))) %>%
sort(decreasing = T) %>% .[1])/length(df_item_different$customer_id))*100),2), "% cust"),
color="black")+
labs(title="Item unik yang dibeli oleh setiap pelanggan",
x= "No id Konsumen",
y="Total Item")+
my_plot_theme(10)+
theme(
axis.text.x = element_blank()
)+
scale_color_gradient(low=my_theme_hex("col2") ,na.value = "#C0C0C0", high=my_theme_hex("col7"))
ggplotly(plot_df_item_different, tooltip="text") %>%
layout(showlegend=FALSE)Bisa kita lihat, hanya 2.14% customer yang membeli 1 tipe item. Hal ini baik karena semakin besar kemungkinan kita bisa membuat recommeder system.
Berapa rata-rata jumlah item per keranjang belanja dari setiap customer?
getmodus <- function(v) {
uniqv <- unique(v)
uniqv[which.max(tabulate(match(v, uniqv)))]
}
df_basket <- df_customer_transaction %>%
group_by(customer_id, invoice_no) %>%
summarise(baskets = sum(quantity)) %>%
ungroup() %>%
group_by(customer_id) %>%
summarise(
freq = n(),
baskets = median(baskets))%>%
ungroup() %>%
mutate( customer_id = customer_id,
popup=glue("id : {customer_id}
Frekuensi : {freq}
Rata-rata Kuantitas Pesanan : {baskets}"))
plot_baskets <- df_basket %>%
ggplot(aes(x=customer_id,y=baskets)) +
geom_point(aes(color=baskets, size=baskets, text=popup), show.legend = FALSE)+
geom_hline(yintercept=getmodus(df_basket$baskets),linetype="dashed", color = "black",size=0.5,text=getmodus(df_basket$baskets))+
annotate(geom="text",
x=max(df_basket$customer_id)-2/10*length(df_basket$customer_id),
y=getmodus(df_basket$baskets)+2000,size=3,
label=paste0("Modus: ",getmodus(df_basket$baskets),
", Total: ",round(as.numeric(((tabulate(match(df_basket$baskets, unique(df_basket$baskets))) %>%
sort(decreasing = T) %>% .[1])/length(df_basket$customer_id))*100),2), "% cust"),
color="black")+
labs(title="Rata-rata Kuantitas Pesanan untuk setiap pelanggan",
x="Konsumen",
y="Rata-rata")+
my_plot_theme(10)+
theme(
axis.text.x = element_blank()
)+
scale_color_gradient(low=my_theme_hex("col2") ,na.value = "#C0C0C0", high=my_theme_hex("col7"))
ggplotly(plot_baskets, tooltip="text") %>%
layout(showlegend=FALSE)Apakah Monetary penjulan Meningkat-Menurun?
df_monthly_value <- df_customer_transaction %>%
mutate( yearmonth = format(invoice_date, format="%Y-%m-1"),
yearmonth = ymd(yearmonth),
ym = as.yearmon(invoice_date)) %>%
group_by(ym,yearmonth) %>%
summarise(total_order_amount = sum(total_amount)) %>%
ungroup() %>%
mutate(
popup=glue("{ym}
Monetary Bulanan: £ {comma(total_order_amount)}")
)
df_max <- df_monthly_value %>% arrange(total_order_amount) %>% tail(1)
df_min <- df_monthly_value %>% arrange(total_order_amount) %>% head(1)
plot_monthly_value <- df_monthly_value %>%
ggplot(aes(x=yearmonth,total_order_amount))+
geom_line(size=1)+
geom_point(size=2,aes(text=popup))+
geom_point(data=df_max, aes(x=yearmonth, y=total_order_amount, text=popup), colour="green", size=3)+
geom_point(data=df_min, aes(x=yearmonth, y=total_order_amount, text=popup), colour="red", size=3)+
labs(
title = "Total Monetary per Bulan",
x = "Bulan-Tahun",
y = NULL
)+
scale_x_date(breaks=date_breaks('1 months'),
labels=date_format('%b %y'))+
my_plot_theme(10)+
theme(axis.text.x = element_text(angle = 45, hjust = 1))
ggplotly(plot_monthly_value, tooltip="text") %>%
#layout(showlegend=FALSE, margin = list(l = 1, r = 1, b = 1, t = 12)) %>%
config(displayModeBar = F, scrollzoom = F)Sama halnya dengan banyaknya frekuensi transaksi, total nilai transaksi bulanan tertinggi pada November 2011. Tetapi, total nilai transaksi bulanan terendah dalam kasus ini ternyata terjadi pada Februari 2011. Secara garis besar, bisa kita katakan nilai transaksi juga cenderung meningkat setiap bulannya.
Konsumen yang paling banyak berdasarkan Negaranya?
library(treemap)
library(gridBase)
treemap(df_customer_transaction,
index = c("country"),
vSize = "quantity",
title = "",
palette = my_color,
border.col = "grey40")Berdasarkan Treemap diatas, customer tersebar dibeberapa negara. Namun, customer paling banyak berada di UK kemudian dilanjutkan Netherland, EIRE, Germany, France dan Australia. Dengan mengetahui informasi ini, kita bisa mengoptimalkan chanel pada negara tertentu dan juga bisa digunakan sebagai landasan pertimbangan untuk mengembangkan strategy.
Kita sudah menyelasaikan proses data preparation dan exploratory analysis dengan baik. Berdasarkan dari analisa diataas, kita menemukan bahwa sebanarnya terdapat customer loyal dan non loyal pada perusahan ini. Sehingga, kita perlu mengsegmentasikan customer untuk membantu perusahaan atau tim merketing melakukan approcah strategy yang berebeda terhadap setiap konsumen. Hasil analisis ini tentunya bisa digunakan untuk mengoptimalkan biaya marketing dan mengoptimalkan waktu campaign. Selain itu, kita juga bisa menerapkan sistem rekomendasi untuk mempersonalisasi produk setiap customer. Coba kita bayangkan, jika kita mengetahui produk apa yang disukai oleh customer dan kita tahu customer tersebut ada di segment apa, maka kita bisa membuat personalisasi campaign untuk meningkatkan customer value, misalkan seperti “memberikan nilai diskon atau promosi yang berbeda-beda sesuai nilai customer terhadap produk-produk yang disukai oleh customer tersebut”.
Metode analisis Recency, Frequency, Monetary Value (RFM) adalah salah satu metode analisis dan segmentasi pelanggan berdasarkan kebiasan customer. Variabel yang digunakan untuk melakukan RFM analisis yaitu:
Variabel RFM bisa dianalisa sebagai single parameter ataupun dikombinasikan. Misalkan berdasarkan nilai monetary, maka kita bisa mengetahui siapa customer yang paling banyak menghabiskan uang untuk berbelanja. Namun, bagaimana jika customer tersebut hanya berbelanja satu kali? atau transaksi terakhirnya sudah lama? lantas bagaimana jika ternyata customer tersebut sudah tidak menggunakan produk kita lagi? Menilai pelanggan hanya dari 1 aspek akan memberikan insight yang kurang akurat, maka dari itu kita perlu mengkombinasikan nilai RFM ini.
Sesuai artikel yang dipublish oleh www.marketeers.com, GO-JEK adalah salah satu perusahaan yang menggunakan metode analisis RFM dalam menentukan segmentasi pelanggan. GO-JEK membagi segmentasi pelanggan ke dalam empat kelas, yaitu Gold, Silver, Bronze, dan Non-Profit. Segmentasi Gold memiliki kualifikasi konsumen dengan high monetary, high frequency, dan high recency. Silver memiliki kualifikasi konsumen dengan tingkat monetary yang tinggi, frequency yang rendah, dan recency yang tinggi. Kategori Bronze terdiri dari konsumen dengan tingkat monetary rendah, frequency, dan recency yang tinggi. Sementara segmentasi Non-Profit memiliki kualifikasi konsumen dengan kualifikasi monetary, frequency, dan recency yang rendah.
Mengutip dari artikel Customer Segmentation — RFM Analysis, beberapa pertanyaan yang bisa dijawab oleh RFM analisis antara lain:
Pada bagian sebelumnya kita sudah melakukan Analisa Deskriptif, selanjutakan akan dilakukan analisa yang lebih dalam dengan menggunakan Analisis RFM dan Analisis Kluster, berikut ini diperlihatkan 10 data teratas::
df_customer_segmentation <- readRDS("Data/clean/df_customer_transaction.rds")
head(df_customer_segmentation,10)## # A tibble: 10 x 9
## invoice_no invoice_date customer_id country stock_code description
## <chr> <dttm> <dbl> <fct> <chr> <chr>
## 1 536365 2010-12-01 08:26:00 17850 United~ 85123A CREAM HANG~
## 2 536365 2010-12-01 08:26:00 17850 United~ 71053 WHITE MORO~
## 3 536365 2010-12-01 08:26:00 17850 United~ 84406B CREAM CUPI~
## 4 536365 2010-12-01 08:26:00 17850 United~ 84029G KNITTED UN~
## 5 536365 2010-12-01 08:26:00 17850 United~ 84029E RED WOOLLY~
## 6 536365 2010-12-01 08:26:00 17850 United~ 22752 SET 7 BABU~
## 7 536365 2010-12-01 08:26:00 17850 United~ 21730 GLASS STAR~
## 8 536366 2010-12-01 08:28:00 17850 United~ 22633 HAND WARME~
## 9 536366 2010-12-01 08:28:00 17850 United~ 22632 HAND WARME~
## 10 536367 2010-12-01 08:34:00 13047 United~ 84879 ASSORTED C~
## # ... with 3 more variables: quantity <dbl>, unit_price <dbl>,
## # total_amount <dbl>
Dalam case ini, dataset berisikan data transaksi dari 1 Desember 2010 sampai 30 November 2011, sehingga diperlakukan RFM Value sebagai berikut:
Berikut proses dan sample 10 data teratas:
analysis_date = date(max(df_customer_segmentation$invoice_date))+days(1)
#first_order, last_order, avg_amount_order, age, recency, monetary
df_customer_rfm <- df_customer_segmentation %>%
group_by(customer_id,country) %>%
summarise(
first_order = as.Date(min(invoice_date)),
last_order = as.Date(max(invoice_date)),
#min_amount_order = min(total_amount),
avg_amount_order = median(total_amount),
#max_amount_order = max(total_amount),
age = as.integer(analysis_date-date(first_order)),
recency = as.integer(analysis_date-date(last_order)),
monetary = sum(total_amount)) %>%
ungroup()
#frequency
df_customer_rfm <- df_customer_segmentation %>%
select(customer_id,invoice_no) %>%
distinct() %>%
group_by(customer_id) %>%
summarise(frequency = n()) %>%
ungroup() %>%
left_join(df_customer_rfm,by=c("customer_id"))
# Baskets size
df_customer_rfm <- df_customer_segmentation %>%
group_by(customer_id,invoice_no) %>%
summarise(freq = n()) %>%
ungroup() %>%
group_by(customer_id) %>%
summarise(
# min_baskets = min(freq),
# max_baskets = max(freq),
avg_baskets = round(median(freq),0)) %>%
ungroup() %>%
left_join(df_customer_rfm, by = c("customer_id")) Berikut penjelasan variabel diatas:
| Variable | Description |
|---|---|
| customer_id | Merupakan id customer |
| country | Merupakan negara customer |
| first_order | Pertama kali customer melakukan transaksi |
| last_order | Terakhir kali customer melakukan transaksi |
| avg_amount_order | Rata-rata biaya yang dikeluarkan customer per-transaksi |
| avg_baskets | Rata-rata jumlah produk yang dibeli customer per-transaksi |
| avg_return | Rata-rata jarak customer melakukan transaksi. Jika nilainya 0 maka hanya terdapat 1 transaksi |
| age | Usia customer, didapatkan dari hari pertama customer bertransaksi dan hari melakukan analisis. Dalam case ini, hari analisis menggunakan data hari terakhir transaksi + 1. |
| recency | Selisih antara hari terakhir pelanggan melakukan transaksi dan hari melakukan analisis. Dalam case ini, hari analisis menggunakan data hari terakhir transaksi + 1. |
| frequency | Jumlah transaksi yang dilakukan oleh pelanggan. |
| monetary | Jumlah total order amount yang sudah dikeluarkan pelanggan. |
Recency menunjukan kapan customer terakhir kali melakukan transaksi. Data ini merupakan histori transksi selama 1 tahun dan pada case ini kita bisa mengsegmentasikan customer terhadap nilai Recency menggunakan teknik Quantile. Penggunaan teknik quantile yaitu membagi sekumpulan data berdasarkan sebarannya. Teknik ini powerfull dan mudah diimplementasikan, namun kita perlu menetapkan batasan-batasan nilainya.
Untuk data set yang digunakan dalam pojeck ini diterapkan ketentuan berikut:
recency_adjust <- data.frame(
active = 30,
warm = 90,
cold = 180,
inactive = Inf
)
df_customer_rfm <- df_customer_rfm %>% mutate(
recency_segment = case_when(recency <= recency_adjust$active ~ "active",
recency > recency_adjust$active & recency <= recency_adjust$warm ~ "warm",
recency > recency_adjust$warm & recency <= recency_adjust$cold ~ "cold",
TRUE ~ "inactive"),
recency_segment = factor(recency_segment, levels=c(names(recency_adjust)))
)
recency_count <- data.frame(table(df_customer_rfm$recency_segment))
recency_segmentation <- df_customer_rfm %>%
group_by(recency) %>% summarise(freq=n()) %>% ungroup() %>%
mutate(
popup=glue("Recency: {recency}
Total Customer: {freq}")
)
plot_recency_segmentation <- ggplot(recency_segmentation,aes(recency,freq))+
geom_area(fill=my_color[5])+
geom_point(aes(text=popup),size=0.2,alpha=0.05)+
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$active, y=10+50), colour="black", size=1)+
annotate("text", x = recency_adjust$active, y = 10+60,
color = "black", size=3, label=paste0(recency_adjust$active," days"))+
geom_point(aes(x=recency_adjust$warm, y=10+10), colour="black", size=1)+
annotate("text", x = recency_adjust$warm, y = 10+20,
color = "black", size=3, label=paste0(recency_adjust$warm," days"))+
geom_point(aes(x=recency_adjust$cold, y=15), colour="black", size=1)+
annotate("text", x = recency_adjust$cold, y = 10+15,
color = "black", size=3, label=paste0(recency_adjust$cold," days"))+
annotate("text", x = (0+recency_adjust$active)/2, y = max(recency_segmentation$freq)+10,
color = "black", size=3, label="Active")+
annotate("text", x = (0+recency_adjust$active)/2, y = max(recency_segmentation$freq)+20,
color = "black", size=3.2, label=prettyNum(recency_count %>% filter(Var1=="active") %>% .$Freq, big.mark=","))+
annotate("text", x = (0+recency_adjust$active)/2, y = max(recency_segmentation$freq)+2,
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)+10,
color = "black", size=3, label="Warm")+
annotate("text", x = median(recency_adjust$active:recency_adjust$warm), y = max(recency_segmentation$freq)+20,
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)+2,
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)+10,
color = "black", size=3, label="Cold")+
annotate("text", x = median(recency_adjust$warm:recency_adjust$cold), y = max(recency_segmentation$freq)+20,
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)+2,
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)), y = max(recency_segmentation$freq)+10,
color = "black", size=3, label="Inactive")+
annotate("text", x = median(recency_adjust$cold:max(recency_segmentation$recency)), y = max(recency_segmentation$freq)+20,
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)), y = max(recency_segmentation$freq)+2,
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 = "Transaksi Terkini dengan Menggunakan Metode Kuantil",
x = "Keterkinian (hari sejak transaksi terakhir)",
y = "Total Konsumen"
)+
my_plot_theme(10)
ggplotly(plot_recency_segmentation, tooltip = NULL)%>%
layout(showlegend=FALSE) %>%
config(displayModeBar = F)Chart diatas menggambarkan proporsi customer berdasarkan transaksi terkahirnya. Kita bisa melakukan fokus campaign yang berbeda terhadap segmen diatas, misalkan:
Setelah kita mendapatkan prioritas dan fokus campaign. Kita juga harus mengukur nilai customer, akan kurang optimal apabila customer yang bernilai rendah diberikan prioritas tinggi dan diberikan campaign seperti promosi yang besar-besaran. Hal ini dapat menyebabkan overcost budget. Kita bisa menggunakan nilai Monetary dan Frequency untuk menentukan nilai customer. Dalam hal ini, saya tidak mengetahui batasan-batasan nilai untuk Frequency dan Monetary sehingga saya memutuskan untuk menggunakan teknik K-Means Clustering. Berikut ini seluruh prosesnya.
Jika dilihat gambar matriks korelasi dibawah ini, kita tahu bahawa frequency dan monetary memiliki korelasi yang cukup kuat, dalam artian semakin tinggi frequency maka semakin besar nilai monetary. Sehingga, seharusnya jika penyebaran data x=frequency dan y=monetary divisualisasikan akan terlihat linier.
ggcorr(df_customer_rfm %>%
select(recency,frequency,monetary), label = TRUE, label_size = 4, vjust=1, hjust=0.5)+
labs(title="Matriks Korelasi pada Nilai RFM")+
my_plot_theme(11)+
scale_fill_gradient(low=my_theme_hex("col2") ,na.value = "#C0C0C0", high=my_theme_hex("col6"))Untuk memperkuat asumsi diatas, perlu dilakukan pemeriksaan sebaran data dan outliernya.
par(mfrow=c(2,2),cex=0.8)
#hist(cust_rfm$log_recency,main="log of recency")
hist(df_customer_rfm$frequency,main="frequency",breaks=200)
hist(df_customer_rfm$monetary,main="monetary", breaks=10000)
boxplot(df_customer_rfm$frequency,main="frequency")
boxplot(df_customer_rfm$monetary, main="monetary")Dari visualisasi diatas, nilai frequency dan monetary tidak berdistribusi normal dan juga memiliki outlier yang cukup banyak. Mari kita cek lebih detail lewat scatter plot.
ggplotly(
df_customer_rfm %>%
ggplot(aes(frequency,monetary))+
geom_jitter()+
geom_smooth(method = lm)+
labs(title = "Frequency vs Monetary pada setiap Transaksi")+
scale_x_continuous(breaks = seq(from = 0, to = 200, by = 10))+
scale_y_continuous(breaks = seq(from = 0, to = 300000, by = 10000)))erdapat customer-customer yang jauh dari pusat penyebaran data atau dapat kita sebut outlier/nilai eksrem. Interpretasi outlier disini yaitu terdapat customer yang sangat sering berbelanja ataupun sekalinya belanja langsung spending money sangat tinggi. Perlu kita ketahui bahwa teknik clustering sangat sensitif terhadap jarak, maka kita bisa mengexclude outlier kemudian kita kategorikan customer pada outlier ini sebagai special customer. Saya coba mengkexplore datanya dan mencoba exlude 5% customer yang memiliki nilai ekstrem, berikut ini hasilnya:
frequency_adjust = 18
monetary_adjust = 6000
df_customer_rfm_adjust <- df_customer_rfm %>%
filter(frequency <= frequency_adjust, monetary <=monetary_adjust)
txt_freqmon_range <- paste0("Range Frequency <= ",frequency_adjust, " and Monetary <=", monetary_adjust)
txt_data_total <- paste0("(",round((df_customer_rfm_adjust %>% nrow())/(df_customer_rfm %>% nrow())*100,1),"% Customer)")
ggplotly(
df_customer_rfm_adjust %>%
ggplot(aes(frequency,monetary))+
geom_jitter()+
geom_smooth(method = lm)+
labs(
title = "Frequency vs Monetary"
)) %>%
layout(title = list(text = paste0('Frequency vs Monetary',
'<br>',
'<sup>',
paste0(txt_freqmon_range," ",txt_data_total),
'</sup>')))95% customer berada pada rentang frequency <= 18 dan monetary <=6000. Maka keputusannya, 95% customer ini akan kita clustering mengunakan K-Means dan 5% customer yang termasuk outlier tadi akan kita buatkan segmen Special Customer.
Meskipun nilai ekstrem sudah kita exclude, tetap perlu diingat bahwa teknik clustering sangat sensitif terhadap jarak, maka perlu kita lakukan scalling dahulu. Disini saya melakukan scalling menggunakan min-max normalization untuk menjaga pola datanya tetap linear. Berikut hasil scalling nya:
norm_minmax <- function(x){
return ((x - min(x))/(max(x) - min(x)))
}
df_customer_rfm_adjust$mm_frequency <- norm_minmax(df_customer_rfm_adjust$frequency)
df_customer_rfm_adjust$mm_monetary <- norm_minmax(df_customer_rfm_adjust$monetary)
par(mfrow=c(2,2),cex=0.9)
#hist(cust_rfm$mm_recency,main="1 - minmax of log_recency")
hist(df_customer_rfm_adjust$mm_frequency,main="minmax of frequency")
hist(df_customer_rfm_adjust$mm_monetary,main="minmax of monetary")
#boxplot(cust_rfm$mm_recency, main="1 - minmax of log_recency")
boxplot(df_customer_rfm_adjust$mm_frequency,main="minmax of frequency")
boxplot(df_customer_rfm_adjust$mm_monetary, main="minmax of monetary")Bisa dilihat, distribusinya masih tidak normal dan masih terdapat outlier. Meski begitu, tidak masalah karena memang datanya seperti itu. Namun, jika hendak melakukan pemodelan yang bersifat supervised learning, maka hal ini perlu diperhatikan dengan baik. Lantas bagaimana jika kita “buta” terhadap batasannya, maka kita bisa menggunakan teknik machine learning.
Kita sudah melakukan segmentasi customer berdasarkan nilai RFM untuk melakukan approach yang berbeda dan optimal dengan Metode Quantile. Namun, proses segmentasi RFM ini juga bisa dilakukan dengan cara scoring atau nilai aslinya menggunakan teknik Machine Learning.
Metode machine learning bisa digunakan untuk segmentasi dan akrabnya dikenal Unsupervised Machine Learning. Pendekatan Unsupervised Machine Learning akan mempelajari pola data dan kemudian melakukan groupping setiap data kedalam cluster-cluster unik. Sangat banyak algoritma yang dapat digunakan untuk segmentasi, contohnya K-Means. Metode K-Means merupakan algoritma centroid-based clustering yang dimana proses clustering dilakukan berdasarkan jarak sehingga variabel-variabelnya harus bersifat numerik atau kuantitatif. Sebelum menggunakan teknik K-Means, kita harus menentukan jumlah cluster yang diingikan (K = Cluster). Secara sederhana, konsep K-Means clustering sebagai berikut:
Pada gambar di atas, misalkan kita memutuskan untuk membagi data ke dalam 2 cluster, data warna hijau merupakan data asli. Maka stepnya adalah:
Metode K-Means sangat sensitif terhadap jarak, sehingga kita harus berhati-hati ketika mengaplikasikannya. Adapun beberapa permasalahannya yaitu:
Pada dasarnya jumlah cluster harus kita tentukan sendiri, namun terdapat teknik untuk mengetahui jumlah cluster yang optimal seperti menggunakan Elbow Method, Silhouette Method, Gap Statistic atau pendekatan lainnya. Untuk case ini kita akan mencoba Elbow Method dan Silhouette Method sebagai referensi penentuan jumlah cluster.
Elbow method mengukur jumlah cluster optimal berdasarkan nilai SSE (Sum of Square Error). Secara visualisasi, jumlah cluster optimal bisa dilihat dari titik yang cenderung membentuk siku pertama kali atau titik yang memiliki jumlah perubahan tinggi/signifikan dari titk sebelumnya. Berikut visualisasinya:
fviz_nbclust(df_customer_rfm_adjust[,c("mm_frequency","mm_monetary")], kmeans, method = "wss", k.max = 15) +
labs(subtitle = "Elbow method")Berdasarkan visualisasi Elbow Method diatas, perubahan signifikan terjadi diantara K=2 dan dan K=3 (Terlihat tidak begitu jelas). Sehingga, kita perlu melakukan metode pencarian kluster dengan metode yang lain, dalam hal ini saya mencoba dengan metode Silhouette.
Metode Silhouette mengukur koefisien Silhouette dengan menghitung rata-rata jarak setiap data terhadap semua data pada cluster yang sama, kemudian menghitung rata-rata jarak setiap data dengan semua data pada cluster lain. Setelah itu, data akan dikelompokan kembali berdasarkan jarak minimum. Jika dalam perulangan/iterasi di dapati posisi centorid berubah, maka data akan dikelompokan ulang. Sama halnya dengan teknik K-Means.
Berdasarkan visualisasi Metode Silhouette, nilai K optimal dapat dilihat pada titik tertinggi. Berikut visualisasinya:
fviz_nbclust(df_customer_rfm_adjust[,c("mm_frequency","mm_monetary")], kmeans, "silhouette", k.max = 15) +
labs(subtitle = "Silhouette method")Berdasarkan visualisasi pencarian kluster dengan metode Silhouette diatas, terlihat jelas bahwa jumlah cluster paling optimum yaitu K=2 untuk data set tersebut. Jika diambil keputusan dari kedua metode diatas, sulit untuk menentukan berapa kluster yang harus diterapkan untuk data set diatas. Sehingga, saya memutuskan untuk melakukan pencarian kluster dengan metode Gap statistic.
Pada tahun 2001, para peneliti di Universitas Stanford-R. Tibshirani, G.Walther dan T. Hastie menerbitkan Metode Statistik Gap. Metode ini dapat digunakan menjadi salah satu metode pengelompokan seperti K-means, pengelompokan hierarkis, dll. Dengan menggunakan statistik gap, dapat dibandingkan variasi total intracluster untuk nilai k yang berbeda bersama dengan nilai yang diharapkan bahkan dibawah distribusi nol dari data. Dengan bantuan simulasi Monte Carlo, seseorang dapat menghasilkan dataset sampel. Untuk setiap variabel dalam dataset, dapat menghitung rentang antara min (\(x_i\)) dan maks (\(x_j\)) yang dapat menghasilkan nilai secara seragam dari interval batas bawah ke batas atas.
fviz_nbclust(df_customer_rfm_adjust[,c("mm_frequency","mm_monetary")], kmeans, "gap_stat", k.max = 15) +
labs(subtitle = "Statistik Kesenjangan (Gap statistic)")Berdasarkan visualisasi ketiga metode diatas, jumlah cluster paling optimum yaitu K=3. Namun, mengigat rentang penyebaran data sebelumnya, maka terkesan kurang bijak apabila kita hanya menggunakan 3 cluster karena karakter frekuensi dan monetary-nya kurang terlihat. Maka saya putuskan untuk mencoba menggunakan 3 cluster dan 4 cluster kemudian kita bandingkan hasilnya.
Notes: Jumlah cluster tidak tergantung penuh pada teknik-teknik penentuan K-Optimal. Jumlah cluster juga bisa kita tentukan sendiri sesuai dengan kebutuhan.
K-Means Clustering merupakan algoritma yang efektif untuk menentukan cluster dalam sekumpulan data, di mana pada algortima tersebut dilakukan analisis kelompok yang mengacu pada pemartisian N objek ke dalam K kelompok (Cluster) berdasarkan nilai rata-rata (means) terdekat.
Notes: Jika anda tertarik untuk memahami lebih detail mengenai metode ini silahkan kunjungi link ini.
set.seed(2020, sample.kind = "Rounding")
kmeans_mm_k3 <- kmeans(df_customer_rfm_adjust[,c("mm_frequency","mm_monetary")],centers = 3, iter.max = 20)
kmeans_mm_k3$size## [1] 951 2790 339
## [1] 75.9
# K-Means
cust_fm_mm_k3 <- cbind(df_customer_rfm_adjust[,c("customer_id","recency","recency_segment","frequency","monetary")],
fm_segment=kmeans_mm_k3$cluster) %>%
mutate(
fm_segment=as.factor(fm_segment))
perfom <- round((kmeans_mm_k3$betweenss/kmeans_mm_k3$totss)*100,1)
cluster_rfm_mm_profile_k3 <- cust_fm_mm_k3 %>% group_by(fm_segment) %>%
summarise(
min_recency = min(recency),
max_recency = max(recency),
avg_recency = round(mean(recency),2),
min_frequency = min(frequency),
max_frequency = max(frequency),
avg_frequency = round(mean(frequency),2),
med_frequency = round(median(frequency),2),
min_monetary = min(monetary),
max_monetary = max(monetary),
avg_monetary = round(mean(monetary),2),
med_monetary = round(median(monetary),2),
total_monetary = sum(monetary),
total_customer = n()
) %>% ungroup()
cluster_rfm_mm_profile_k3 %>%
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=3, color="white")+
coord_equal() +
labs(
title = "K-Means Clustering untuk 3 Segmen",
subtitle = paste("betweenss/totts:",perfom,"%"),
x = "Cluster"
)+
scale_fill_gradient(low=my_theme_hex("col3") ,na.value = "#C0C0C0", high=my_theme_hex("col6"))+
theme(text = element_text(size=14),
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_k3
grid.arrange(plot_kmeans_k3, nrow = 1)Hasil K-Means dengan 3 cluster memiliki proporsi jumlah cluster yaitu 951, 2790, 339 dengan nilai between_SS / total_SS = 75.9 %.
set.seed(2020, sample.kind = "Rounding")
kmeans_mm_k4 <- kmeans(df_customer_rfm_adjust[,c("mm_frequency","mm_monetary")],centers = 4, iter.max = 20)
kmeans_mm_k4$size## [1] 510 2333 216 1021
## [1] 81.5
# K-Means
cust_fm_mm_k4 <- cbind(df_customer_rfm_adjust[,c("customer_id","recency","recency_segment","frequency","monetary")],
fm_segment=kmeans_mm_k4$cluster) %>%
mutate(
fm_segment=as.factor(fm_segment))
perfom <- round((kmeans_mm_k4$betweenss/kmeans_mm_k4$totss)*100,1)
cluster_rfm_mm_profile_k4 <- cust_fm_mm_k4 %>% group_by(fm_segment) %>%
summarise(
min_recency = min(recency),
max_recency = max(recency),
avg_recency = round(mean(recency),2),
min_frequency = min(frequency),
max_frequency = max(frequency),
avg_frequency = round(mean(frequency),2),
med_frequency = round(median(frequency),2),
min_monetary = min(monetary),
max_monetary = max(monetary),
avg_monetary = round(mean(monetary),2),
med_monetary = round(median(monetary),2),
total_monetary = sum(monetary),
total_customer = n()
) %>% ungroup()
cluster_rfm_mm_profile_k4 %>%
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=3, color="white")+
coord_equal() +
labs(
title = "K-Means Clustering untuk 4 Segmen",
subtitle = paste("betweenss/totts:",perfom,"%"),
x = "Cluster"
)+
scale_fill_gradient(low=my_theme_hex("col3") ,na.value = "#C0C0C0", high=my_theme_hex("col6"))+
theme(text = element_text(size=14),
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_k4
grid.arrange(plot_kmeans_k4, nrow = 1)Berdasarkan proses clustering diatas, hasil cluster menggunakan metode K-Means dengan K=3 sudah cukup menunjukan hasil yang dimana setiap cluster memiliki indentitas yang berbeda. Maka dari itu dapat kita putuskan, hasil segmentasi ini terdiri dari 4 kelompok customer yang terdiri dari 3 cluster customer berdasarkan hasil hasil k-means dan 1 cluster berisikan 5% customer dengan nilai tertinggi yang kita exclude sebelumnya
df_segment_profile <- df_segment_result %>%
group_by(fm_segment) %>%
summarise(
total_customer = n(),
total_monetary = sum(monetary),
total_frequency = sum(frequency),
min_frequency = min(frequency),
max_frequency = max(frequency),
avg_frequency = as.integer(median(frequency)),
min_monetary = min(monetary),
max_monetary = max(monetary),
avg_monetary = median(monetary)
)cluster_fm_summary <- df_segment_profile %>% mutate(
popup = glue("Total Konsumen: {total_customer} ({round((total_customer/sum(total_customer))*100,1)})%
Total Monetary: {comma(total_monetary)} ({comma((total_monetary/sum(total_monetary))*100)}%)
Rata-rata Frekuensi: {avg_frequency} Order
Rata-rata Monetary: {avg_monetary}"),
fm_segment = as.factor(fm_segment)
) %>%
ggplot(aes(fm_segment,avg_frequency)) +
geom_bar(aes(fill=fm_segment, text=popup),stat="identity", show.legend = FALSE)+
geom_text(aes(label=paste0('Frek: ',round(avg_frequency,0)),y=avg_frequency+0.8),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: Rata-rata Frekuensi per Kluster",
x="Kluster",
y="Frekuensi Rata-rata")+
my_theme_fill()+
my_plot_theme(10)
ggplotly(cluster_fm_summary,tooltip="text") %>%
layout(showlegend=FALSE) cluster_fm_summary <- df_segment_profile %>% mutate(
popup = glue("Total Konsumen: {total_customer} ({round((total_customer/sum(total_customer))*100,1)})%
Total Monetary: £ {comma(total_monetary)} ({comma((total_monetary/sum(total_monetary))*100)}%)
Rata-rata Frekuensi: {avg_frequency} Order
Rata-rata Monetary: £ {comma(avg_monetary)}"),
fm_segment = as.factor(fm_segment)
) %>%
ggplot(aes(fm_segment,avg_monetary)) +
geom_bar(aes(fill=fm_segment, text=popup),stat="identity", show.legend = FALSE)+
geom_text(aes(label=paste0("£ ",comma(avg_monetary,1)),y=avg_monetary+500),size=3, color="black")+
labs(
title="Profiling: Rata-rata Monetary per Kluster",
x="Kluster",
y="Rata-rata Monetary"
)+
my_theme_fill()+
my_plot_theme(10)
ggplotly(cluster_fm_summary,tooltip="text") %>%
layout(showlegend=FALSE)Berdasarkan kedua visualisasi profiling cluster diatas, maka setiap cluster bisa artikan sebagai berikut:
Berikut ini data hasil segmentasi yang diterapkan:
df_cust_segment <- df_segment_result %>%
mutate(
fm_segment = case_when(fm_segment == "1" ~ "Medium",
fm_segment == "2" ~ "Low",
fm_segment == "3" ~ "High",
fm_segment == "4" ~ "Special"),
fm_segment = factor(fm_segment, levels=c("Low","Medium",
"High","Special")))
head(df_cust_segment,10)## # A tibble: 10 x 12
## customer_id avg_baskets frequency country first_order last_order
## <dbl> <dbl> <int> <fct> <date> <date>
## 1 12346 1 1 United~ 2011-01-18 2011-01-18
## 2 12347 26 6 Iceland 2010-12-07 2011-10-31
## 3 12348 4 4 Finland 2010-12-16 2011-09-25
## 4 12349 72 1 Italy 2011-11-21 2011-11-21
## 5 12350 16 1 Norway 2011-02-02 2011-02-02
## 6 12352 13 7 Norway 2011-02-16 2011-11-03
## 7 12353 4 1 Bahrain 2011-05-19 2011-05-19
## 8 12354 58 1 Spain 2011-04-21 2011-04-21
## 9 12355 13 1 Bahrain 2011-05-09 2011-05-09
## 10 12356 21 3 Portug~ 2011-01-18 2011-11-17
## # ... with 6 more variables: avg_amount_order <dbl>, age <int>, recency <int>,
## # monetary <dbl>, recency_segment <fct>, fm_segment <fct>
Berikut ini adalah visualisasi dari hasil segmentasi yang sudah dilakukan:
df_cust_segment %>% mutate(size_point=as.integer(fm_segment),size_point=size_point*10) %>%
plot_ly( x = ~frequency, y = ~monetary, 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("ID Konsumen: ", customer_id,
"<br>Klasifikasi: <b>",fm_segment,"</b>",
"<br>Frekuensi: <b>", frequency,"</b>",
"<br>Monetary: <b>£ ",monetary,"</b>"))%>%
#layout(margin = list(l = 10, r = 10, b = 10, t = 10)) %>%
config(displayModeBar = F) %>%
layout(font=list(size = 12)) %>%
layout(title="Klasifikasi Segmentasi Pelanggan")Sebelumnya kita sudah melakukan segmentasi customer berdasarkan Recency, Frequency dan Monetary. Berikut ini divisualisasikan hasil Analisis RFM Segmentasi secara umum:
plot_tile_rfm_segmentation <- df_cust_segment %>%
group_by(recency_segment,fm_segment) %>%
summarise(freq = n(),
total_monetary=sum(monetary),
avg_monetary = round(mean(monetary)),
avg_frequency = median(frequency),
avg_age = median(age)) %>%
ungroup() %>%
mutate(
popup=glue("Total Konsumen: {freq}
RFM Segment : {toupper(recency_segment)} - {toupper(fm_segment)}
Rata-rata Frekuensi : {avg_frequency}
Rata-rata Monetary : £ {comma(avg_monetary)}
Rata-rata Usia : {avg_age}")
) %>%
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="white", show.legend = FALSE)+
labs(y= NULL,
x= "Hari sejak pembelian terakhir")+
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="Hasil Segmentasi RFM")Visualisasi di atas menunjukan distribusi penyebaran setiap customer berdasarkan hasil RFM Segmentation. Jika dilihat terdapat 16 kombinasi dengan perspektif yang berbeda-beda. Contohnya, Customer pada segmen Low Value dan Active bisa kita simpulkan customer baru karena mereka baru mengorder 1-2 kali dalam waktu 30 hari.
Berikut ini diperlihatkan visualisasi Analisis RFM secara detail:
per_seribu <- function (x) { number_format(accuracy = 1,
scale = 1/1000,
suffix = "K",
big.mark = ",")(x) }
plot_fm_total_customer <- df_cust_segment %>% group_by(fm_segment) %>%
summarise(total_monetary = sum(monetary),
total_customer = n(),
avg_frequency = median(frequency),
avg_monetary = median(monetary)) %>%
ungroup() %>%
mutate(popup = glue("Total Konsumen: {total_customer} ({round((total_customer/sum(total_customer))*100,1)})%
Total Monetary: £ {comma(total_monetary)} ({comma((total_monetary/sum(total_monetary))*100)}%)")) %>%
ggplot(aes(fm_segment,total_customer)) +
geom_bar(aes(fill=fm_segment, text=popup),stat="identity", show.legend = FALSE)+
geom_text(aes(label=paste0(round(total_customer/sum(total_customer)*100,1),"%"),
y=total_customer+130),size=3, color="black")+
labs(
title = "Segmentasi Pelanggan berdasarkan Frekuensi",
x= NULL,
y="Total Konsumen"
)+
scale_y_continuous(labels = per_seribu)+
coord_flip()+
my_theme_fill()+
my_plot_theme(8)
ggplotly(plot_fm_total_customer,tooltip="text") %>%
layout(showlegend=FALSE) %>%
config(displayModeBar = F) Visualisasi di atas memberikan informasi total customer pada masing-masing segmen customer. Bisa dilihat bahwa 64.9% customer kita ada disegmen low value customer.
per_juta <- function (x) { number_format(accuracy = 1,
scale = 1/1000000,
suffix = "M",
big.mark = ",")(x) }
plot_recency_total_amount <- df_cust_segment %>% group_by(recency_segment) %>%
summarise(
total_customer = n(),
total_transaction = sum(frequency),
total_monetary = sum(monetary),
percent_monetary = round((total_monetary/sum(df_cust_segment$monetary))*100,1)
) %>%
ungroup() %>%
mutate(
popup = glue("Recency Segment : {recency_segment}
Total Konsumen : {comma(total_customer)}
Total Transaksi: {total_transaction}
Total Monetary: £ {comma(total_monetary)} ({percent_monetary}%)"),
recency_segment = factor(recency_segment, levels=c("inactive","cold","warm","active"))
) %>%
ggplot(aes(recency_segment,total_monetary))+
geom_bar(stat = "identity", aes(fill=recency_segment, text=popup), show.legend = FALSE)+
geom_text(aes(label=paste0(round((total_monetary/sum(total_monetary))*100,1),"%"),y=total_monetary+260000), size=3)+
scale_y_continuous(labels = per_juta)+
labs(
title = "Segmentasi Pelanggan berdasarkan Recency",
x = NULL,
y = "Total Monetary")+
coord_flip()+
my_theme_fill()+
my_plot_theme(8)
ggplotly(plot_recency_total_amount,tooltip="text") %>%
layout(showlegend=FALSE) %>%
config(displayModeBar = F) Visualisasi di atas memberikan informasi total monetary per-recency segment. Dalam 1 tahun, 70.7% transaksi terjadi pada 1 bulan atau 30 terakhir. Hal ini menjasi pertanyaan penting untuk kita amati, kemana aja selama 11 bulan? Dari hasil ini, maka perlu ada evaluasi terkait penjualan selama 11 bulan awal.
plot_fm_total_monetary <- df_cust_segment %>% group_by(fm_segment) %>%
summarise(total_monetary = sum(monetary),
total_customer = n(),
avg_frequency = median(frequency),
avg_monetary = median(monetary)) %>%
ungroup() %>%
mutate(popup = glue("Total Konsumen: {total_customer} ({round((total_customer/sum(total_customer))*100,1)})%
Total Monetary: £ {comma(total_monetary)} ({comma((total_monetary/sum(total_monetary))*100)}%)")) %>%
ggplot(aes(fm_segment,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+200000),size=3, color="black")+
labs(
title = "Segmentasi Pelanggan berdasarkan Monetary",
x= NULL,
y="Total Monetary"
)+
scale_y_continuous(labels = per_juta)+
coord_flip()+
my_theme_fill()+
my_plot_theme(8)
ggplotly(plot_fm_total_monetary,tooltip="text") %>%
layout(showlegend=FALSE) %>%
config(displayModeBar = F) Visualisasi di atas memberikan informasi total monetary yang dihasilkan setiap segmen customer. Bisa dilihat bahwa 35% customer (Special, High, Medium) menghasilkan penjualan sebesar 85% dalam satu tahun. Berdasarkan hal ini, kita bisa memfokuskan strategi cross selling dan up selling pada customer ini. Kemudian pada segmen Low Value customer kita bisa fokus ke strategi retention.
Kita sudah berhasil melakukan analisa dan segmentasi customer menggunakan RFM value. Tentunya masih banyak hal yang bisa diolah menggunakan nilai RFM dan bisa juga dikombinasikan dengan Average Return, Age, Transaction Quantity. Namun fokus kali ini untuk mengetahui nilai customer kita, contohnya apabila kamu semakin sering berbelanja pada sebuah toko maka bisa disimpulkan kamu semakin nyaman berbelanja disitu (Emotional Value) dan apabila kamu mengeluarkan uang semakin besar pada sebuah toko maka kamu semakin butuh produk yang dijual toko tersebut (functional value), namun jika kamu sudah tidak pernah berbelanja lagi di toko tersebut maka perlu dipertanyakan persoalnnya apa.
Hasil analisa RFM dapat membantu perusahaan untuk menentukan strategi-strategi yang bisa meningkatkan loyalitas customer yang berujung meningkatnya customer value. Meng-segmentasikan customer berdasarkan nilai Recency memberikan insight pada bisnis untuk menentukan strategi dalam memprioritaskan customer. Kemudian meng-segmentasikan customer berdasarkan nilai Frequency dan Monetary akan membantu bisnis untuk mengoptimalkan resources, budget dan waktu karena bisa fokus pada segmen tertentu. Namun kedua hasil ini harus dikombinasikan, misalnya akan kurang baik apabila perusahaan mengeluarkan banyak resources, budget dan waktu terhadap customer yang bernilai rendah dan sudah tidak aktif.
Berdasarkan hasil recency segmentation, sekitar 70% transaksi terjadi pada November 2011 (1 bulan terakhir) dan hanya 30% terjadi selama 11 bulan pertama. Padahal customer yang aktif bertransaksi pada November 2011 hanya sebesar 38.7%. Sehingga perlu dilakukan evaluasi kembali apa yang terjadi pada 11 bulan pertama. Rekomendasi yang bisa diberikan berdasarkan recency segementation yaitu:
Segmen Active : Fokus untuk meningkatkan pembelian customer sehingga perlu membentuk cross/Up Selling Strategy.
Segmen Warm : Fokus supaya customer melakukan pembelian kembali sehingga perlu membentuk Retention Strategy, Cross/Up Selling Strategy.
Segmen Cold : Customer pada segmen ini sangat beresiko untuk churn, maka fokus untuk mengaktifkan customer dan melakukan pembelian kembali dengan membentuk Reactivation Strategy, Retention Strategy.
Segmen Inactive : Customer pada segmen ini sudah churn, maka fokus campaign untuk mengaktifkan customer kembali dengan membentuk Reactivation strategy.
Berdarkan hasil Customer Value Segmentation, maka rekomendasi yang bisa diberikan yaitu:
Total 35% customer (Special, High, Medium) menghasilkan penjualan sebesar 85%, sehingga proporsi resources, budget dan waktu campaign harus dioptimalkan terhadap customer di segmen ini demi menjaga loyalitas mereka dan bisa meningkatkan value mereka.
Total 65% customer merupakan low value customer dan hanya menghasilkan 15% penjualan. Customer ini hanya berbelanja 1-2 kali, maka proporsi resources, budget dan waktu pada customer ini tidak perlu besar, tapi kita tidak boleh mengabaikan mereka, terutama pada low customer yang berda disegmen active karena mereka adalah customer baru dan sangat berpotensi.
Salah satu cara efektif untuk meningkatkan loyalitas dan customer value yaitu membuat customer loyality program. Sebagai contoh, setiap customer akan mendapatkan 1 poin setiap melakukan pembelian sebesar £ 3.0 dan setiap kelipatan poin tertentu bisa ditukarkan dengan cashback, diskon, special gift dan lainnya. Dengan begini, kemungkinan frekuensi belanja customer akan meningkat dan perusahaan tidak perlu mengeluarkan banyak budget untuk mengoptimalkan customer.
Strategi untuk meningkatkan loyalitas customer yang paling sering dijumpai yaitu program promosi, diskon atau potongan harga. Namun, perlu disadari bahwa seorang customer akan membeli produk karena dia sudah nyaman berbelanja disuatu penjual dan penjual tersebut menyediakan produk yang sesuai dengan ketertarikannya. Jadi, semua bentuk campaign atau program loyalitas yang seharusnya bisa meningkatkan customer value akan jadi percuma jika penjual tidak melakukannya sesuai dengan keinginan atau ketertarikan customer. Misalnya:
Seorang customer sering mendapat email promosi produk-produk dengan harapan customer melakukan pembelian kembali, namun karena produk-produk yang dipromosikan tidak sesuai dengan keinginannya, yang ada customer ini menjadi risih dan meninggalkan penjual.
Seorang customer mendapatkan diskon 35% terhadap produk yang sedang tren, sedangkan customer ini tidak tertarik dengan produk tersebut. Alhasil, diskon ini juga terabaikan.
Seorang customer mendapatkan diskon 20% atas produk apapun dengan harapan customer melakukan pembelian kembali. Namun, customer ini bingung mau membeli produk apa karena banyaknya produk yang dijual yang menyebabkan customer kesulitan mencari produk. Alhasil, diskon tersebut terabaikan. Kasus ini sesuai dengan artikel yang dipublish oleh www.xendit.co yang menginfokan hasil riset Accenture mengatakan bahwa 40% konsumen meninggalkan suatu website bisnis dan melakukan pembelian di website atau toko online lain karena merasa kebingungan akibat terlalu banyaknya pilihan saat akan membuat keputusan pembelian.
Solusi dari permasalahan diatas yaitu Product Personalization. Coba kita bayangkan, kita bisa mengetahui strategi prioritas dan alokasi resources, budget serta waktu melalui RFM Segmentation dan kita juga mengetahui produk apa yang disukai oleh masing-masing customer. Maka kita bisa memberikan diskon atau promosi yang berbeda-beda sesuai nilai customer terhadap produk-produk yang disukai oleh masing-masing customer.