Dự án này được thực hiện nhằm xây dựng và phát triển một mô hình dự báo hành vi mua sắm của khách hàng dựa trên dữ liệu lịch sử về giao dịch. Bộ dữ liệu thô được cung cấp bao gồm các thông tin sau:
Trước hết đọc dữ liệu thô và xem qua:
# Đọc dữ liệu:
rm(list = ls())
library(tidyverse)
library(knitr)
df_tran <- read_csv("C:\\Users\\ADMIN\\Desktop\\VinIDRecruitChallenge\\VinIDRecruitChallenge_MLTrack_DataSet.csv")
# Xem qua một số giao dịch:
df_tran %>%
slice(c(1, 8)) %>%
kable()| csn | date | transaction_info |
|---|---|---|
| Y2NgaWJoYw== | 2018-03-02 | [{‘article’: ‘10020163’, ‘salesquantity’: 2.0, ‘price’: 18250.0}] |
| Y2NgamRpZA== | 2018-02-08 | [{‘article’: ‘10225636’, ‘salesquantity’: 1.1420000000000001, ‘price’: 45900.18}, {‘article’: ‘10014829’, ‘salesquantity’: 1.0, ‘price’: 12000.0}, {‘article’: ‘10020649’, ‘salesquantity’: 1.0, ‘price’: 8200.0}, {‘article’: ‘10008535’, ‘salesquantity’: 1.0, ‘price’: 65400.0}, {‘article’: ‘10008553’, ‘salesquantity’: 1.0, ‘price’: 4000.0}, {‘article’: ‘10008611’, ‘salesquantity’: 2.0, ‘price’: 7000.0}, {‘article’: ‘10008895’, ‘salesquantity’: 1.0, ‘price’: 27700.0}, {‘article’: ‘10009067’, ‘salesquantity’: 1.0, ‘price’: 38000.0}, {‘article’: ‘10009156’, ‘salesquantity’: 1.0, ‘price’: 14500.0}, {‘article’: ‘10010004’, ‘salesquantity’: 1.0, ‘price’: 16000.0}, {‘article’: ‘10010264’, ‘salesquantity’: 3.0, ‘price’: 12333.33}, {‘article’: ‘10010266’, ‘salesquantity’: 1.0, ‘price’: 46400.0}, {‘article’: ‘10020822’, ‘salesquantity’: 2.0, ‘price’: 14900.0}, {‘article’: ‘10234347’, ‘salesquantity’: 1.0, ‘price’: 8500.0}, {‘article’: ‘10322779’, ‘salesquantity’: 1.0, ‘price’: 6700.0}, {‘article’: ‘10322780’, ‘salesquantity’: 1.0, ‘price’: 6700.0}] |
Như vậy khách hàng có mã Y2NgaWJoYw== vào ngày 2018-03-02 chỉ mua một Item có mã là 10020163, số lượng mua là 2 và giá bán (cho một sản phẩm) là 18250. Với khách hàng có mã là Y2NgamRpZA== vào ngày 2018-02-08 thì người này mua nhiều hơn một sản phẩm và một một sản phẩm trong giỏ hàng được ngăn cách nhau bởi }, {. Đáng chú ý là với hàng hóa có mã 10225636 thì lượng mua là 1.1420000000000001.
Lượng mua là một số nguyên dương. Các chuỗi cửa hàng, nếu có bán nửa cân mực khô chẳng hạn, thì nó sẽ đóng thành gói và hệ thống lưu thông tin sẽ ghi nhận là “bán ra 1 gói mực”. Do vậy có thể suy luận rằng 1.1420000000000001 là một lỗi của hệ thống cơ sở dữ liệu.
Dựa trên những phân tích dữ liệu thô ở trên chúng ta viết hàm để extract ra các thông tin về một giao dịch như sau:
n <- nrow(df_tran) # Số lượng các giao dịch.
tran_inf <- df_tran$transaction_info # Thông tin chung về giao dịch của khách hàng.
tran_date <- df_tran$date # Thời điểm thực hiện giao dịch.
cusID = df_tran$csn # Mã khách hàng.
# Viết hàm Extract các thông tin về: (1) Mã hàng, (2) số lượng, và (3) giá:
extract_transactionInfo <- function(k) {
str_split(tran_inf[k], "\\}\\,", simplify = TRUE) %>%
str_split(":", simplify = TRUE) %>%
data.frame() %>%
select(-X1) %>%
mutate_all(function(x) {str_replace_all(x, "[^0-9]", "")}) %>%
mutate(StockCode = as.character(X2), Quantity = as.integer(X3) / 10, UnitPrice = as.numeric(X4) / 10) %>%
select(-contains("X")) %>%
mutate(InvoiceDate = tran_date[k], CustomerID = cusID[k]) %>%
select(CustomerID, InvoiceDate, everything()) %>%
return()
}Hàm trên trả ra kết quả là một Data Frame. Ví dụ với giao dịch thứ 1 và thứ 8:
| CustomerID | InvoiceDate | StockCode | Quantity | UnitPrice |
|---|---|---|---|---|
| Y2NgaWJoYw== | 2018-03-02 | 10020163 | 2 | 18250 |
| CustomerID | InvoiceDate | StockCode | Quantity | UnitPrice |
|---|---|---|---|---|
| Y2NgamRpZA== | 2018-02-08 | 10225636 | NA | 459001.8 |
| Y2NgamRpZA== | 2018-02-08 | 10014829 | 1 | 12000.0 |
| Y2NgamRpZA== | 2018-02-08 | 10020649 | 1 | 8200.0 |
| Y2NgamRpZA== | 2018-02-08 | 10008535 | 1 | 65400.0 |
| Y2NgamRpZA== | 2018-02-08 | 10008553 | 1 | 4000.0 |
| Y2NgamRpZA== | 2018-02-08 | 10008611 | 2 | 7000.0 |
| Y2NgamRpZA== | 2018-02-08 | 10008895 | 1 | 27700.0 |
| Y2NgamRpZA== | 2018-02-08 | 10009067 | 1 | 38000.0 |
| Y2NgamRpZA== | 2018-02-08 | 10009156 | 1 | 14500.0 |
| Y2NgamRpZA== | 2018-02-08 | 10010004 | 1 | 16000.0 |
| Y2NgamRpZA== | 2018-02-08 | 10010264 | 3 | 123333.3 |
| Y2NgamRpZA== | 2018-02-08 | 10010266 | 1 | 46400.0 |
| Y2NgamRpZA== | 2018-02-08 | 10020822 | 2 | 14900.0 |
| Y2NgamRpZA== | 2018-02-08 | 10234347 | 1 | 8500.0 |
| Y2NgamRpZA== | 2018-02-08 | 10322779 | 1 | 6700.0 |
| Y2NgamRpZA== | 2018-02-08 | 10322780 | 1 | 6700.0 |
Giao dịch thứ 8 có Missing Data về lượng hàng mua cho mã 10225636 mà nguyên nhân bắt nguồn từ việc dữ liệu gốc ghi nhận số lượng của mã hàng này là 1.1420000000000001. Sử dụng hàm đã có để xử lí dữ liệu:
lapply(1:n, extract_transactionInfo) -> data_list
do.call("bind_rows", data_list) -> data_df
save(data_df, file = "data_df.RData") # Lưu lại dữ liệu. Với các thông tin extract được chúng ta có thể xem qua:
load("C:\\Users\\ADMIN\\Desktop\\data_df.RData") # Load dữ liệu đã lưu.
all_obj <- ls()
rm(list = all_obj[!str_detect(all_obj, "data_df")]) # Xóa các objects thừa.
# Xem qua dữ liệu:
data_df %>%
head() %>%
kable()| CustomerID | InvoiceDate | StockCode | Quantity | UnitPrice |
|---|---|---|---|---|
| Y2NgaWJoYw== | 2018-03-02 | 10020163 | 2 | 18250 |
| Y2NgaWJoYw== | 2018-03-04 | 10026562 | 3 | 13000 |
| Y2NgaWJoYw== | 2018-03-04 | 10320883 | 2 | 43000 |
| Y2NgaWlpYA== | 2018-02-27 | 10013531 | 1 | 17800 |
| Y2NgaWlpYA== | 2018-02-27 | 10015613 | 1 | 5600 |
| Y2NgaWlpYA== | 2018-02-27 | 10320578 | 1 | 5600 |
Đánh giá mức độ thiếu của dữ liệu:
# Hàm đánh giá dữ liệu trống:
na_rate <- function(x) {100*sum(is.na(x)) / length(x)}
# Sử dụng hàm:
data_df %>%
summarise_all(na_rate) %>%
kable()| CustomerID | InvoiceDate | StockCode | Quantity | UnitPrice |
|---|---|---|---|---|
| 0 | 0 | 0 | 8.975835 | 0 |
Có gần 9% dữ liệu là Missing ở cột biến Quantity. Như đã phân tích ở trên chúng ta có thể giả định rằng nguyên nhân đến từ việc nghi nhận không phù hợp của hệ thống cơ sở dữ liệu. Việc xử lí các dữ liệu trống này bằng các phương pháp thông thường (như thay thế bằng mean hay median) là không phù hợp. Nếu được cung cấp chi tiết hơn những thông tin về dữ liệu thô, về cách thức hệ thống ghi nhận cơ sở dữ liệu thì chúng ta có thể có cách xử lí Missing Data này phù hợp hơn.
Chúng ta cũng giả định rằng các Missing thuộc kiểu MAR (Missing at Random) và do vậy việc loại các Missing không ảnh hưởng đến việc xây dựng mô hình vì việc này là tương đương với việc mẫu quan sát được lấy ít hơn 9% so với ban đầu:
# Loại các Missing:
data_df %>% filter(!is.na(Quantity)) -> my_df
# Tạo thêm một số biến số mà có thể sử dụng sau này:
library(lubridate)
my_df %>%
rename(time_ymd = InvoiceDate) %>%
mutate(w_day = wday(time_ymd, label = TRUE, abbr = TRUE),
time_mon = month(time_ymd, label = TRUE, abbr = TRUE)) -> my_df
# Xem qua:
my_df %>%
head() %>%
kable()| CustomerID | time_ymd | StockCode | Quantity | UnitPrice | w_day | time_mon |
|---|---|---|---|---|---|---|
| Y2NgaWJoYw== | 2018-03-02 | 10020163 | 2 | 18250 | Fri | Mar |
| Y2NgaWJoYw== | 2018-03-04 | 10026562 | 3 | 13000 | Sun | Mar |
| Y2NgaWJoYw== | 2018-03-04 | 10320883 | 2 | 43000 | Sun | Mar |
| Y2NgaWlpYA== | 2018-02-27 | 10013531 | 1 | 17800 | Tue | Feb |
| Y2NgaWlpYA== | 2018-02-27 | 10015613 | 1 | 5600 | Tue | Feb |
| Y2NgaWlpYA== | 2018-02-27 | 10320578 | 1 | 5600 | Tue | Feb |
Dữ liệu được làm sạch ở trên giờ có thể được sử dụng cho phân tích và xây dựng mô hình dự báo hành vi mua sắm của khách hàng.
Bước phân tích khám phá dữ liệu này được thực hiện nhằm tìm kiếm một số insights có thể là quan trọng cho việc thiết kế - xây dựng mô hình dự báo hành vi của khách hàng sau này. Ví dụ chúng ta có thể thấy có một thời điểm đâu đó giữa Feb và Mar sản lượng bán ra tăng đột ngột. Đây có thể là kết quả của việc Rainbow Store thực hiện các chiến dịch khuyến mãi kích cầu mua sắm:
library(hrbrthemes)
theme_set(theme_modern_rc())
my_df %>%
group_by(time_ymd) %>%
summarise(sales = sum(Quantity)) %>%
ungroup() -> sales_byTime_hm
sales_byTime_hm %>%
mutate(sales = sales / 1000) %>%
ggplot(aes(time_ymd, sales)) +
geom_line() +
geom_point(color = "firebrick") +
scale_y_continuous(limits = c(0, 300)) +
labs(title = "Figure 1: Daily Quantity Sales (Thousands)", x = "Time", y = "Quantity")Tương tự là doanh thu của Rainbow Store:
my_df %>%
mutate(money = Quantity*UnitPrice) -> my_df
my_df %>%
group_by(time_ymd) %>%
summarise(moneySales = sum(money)) %>%
ungroup() -> sales_byTime_money
sales_byTime_money %>%
mutate(moneySales = moneySales / 1000000000) %>%
ggplot(aes(time_ymd, moneySales)) +
geom_line() +
geom_point(color = "firebrick") +
labs(title = "Figure 2: Daily Monetary Sales (Billions)", x = "Time", y = "")Sản lượng bán ra của Rainbow Store cao nhất ở Jun:
my_df %>%
group_by(time_mon) %>%
summarise_each(funs(sum), Quantity) %>%
mutate(Quantity = Quantity / 1000) %>%
ggplot(aes(time_mon, Quantity)) +
geom_col() +
theme(panel.grid.major.x = element_blank()) +
labs(title = "Figure 3: Quantity Sales in Thousands by Month", x = "Time", y = "Quanlity") +
scale_y_continuous(limits = c(0, 4000))Tuy nhiên doanh thu thì có xu hướng giảm nhẹ qua các tháng ngoại trừ tháng May có doanh thu tăng lên:
my_df %>%
group_by(time_mon) %>%
summarise_each(funs(sum), money) %>%
mutate(money = money / 1000000000) %>%
ggplot(aes(time_mon, money)) +
geom_col() +
theme(panel.grid.major.x = element_blank()) +
labs(title = "Figure 4: Monetary Sales in Billions by Month", x = "Time", y = "")Chúng ta cũng có thể thấy rằng doanh thu đến từ các Item có phân bố không đều: có một số mặt hàng mang lại doanh chu rất cao cho Rainbow Store:
my_df %>%
group_by(StockCode) %>%
summarise(sales = sum(money)) %>%
ungroup() %>%
arrange(-sales) %>%
mutate(StockCode = factor(StockCode, levels = StockCode)) %>%
mutate(total = sum(sales)) %>%
mutate(money_percent = sales / total) %>%
mutate(cum_money = cumsum(money_percent)) -> moneySales_Item
moneySales_Item %>%
mutate(sales = sales / 1000000000) %>%
ggplot(aes(StockCode, sales)) +
geom_col() +
theme(panel.grid.major.x = element_blank()) +
theme(axis.text.x = element_blank()) +
labs(title = "Figure 5: Money Sales by Product (Billions)", x = "", y = "")Chúng ta có thể list ra danh sách các mặt hàng mà mang lại 80% doanh thu cho Rainbow Store:
moneySales_Item %>%
filter(cum_money <= 0.8) -> top80_sales
top80_items <- top80_sales %>% nrow()
top80_items / nrow(moneySales_Item)## [1] 0.00843729
moneySales_Item %>%
select(-total) %>%
filter(StockCode %in% top80_sales$StockCode) %>%
mutate(ID = 1:top80_items) %>%
kable()| StockCode | sales | money_percent | cum_money | ID |
|---|---|---|---|---|
| 10053361 | 456429446194 | 0.0652207 | 0.0652207 | 1 |
| 10069664 | 205408427811 | 0.0293515 | 0.0945721 | 2 |
| 10053663 | 205385103453 | 0.0293481 | 0.1239203 | 3 |
| 10053664 | 171035994195 | 0.0244399 | 0.1483602 | 4 |
| 10054850 | 169014480527 | 0.0241510 | 0.1725112 | 5 |
| 10054908 | 160322716792 | 0.0229090 | 0.1954202 | 6 |
| 10053716 | 127189715878 | 0.0181745 | 0.2135947 | 7 |
| 10054786 | 121005187343 | 0.0172908 | 0.2308856 | 8 |
| 10071533 | 115900281750 | 0.0165614 | 0.2474469 | 9 |
| 10053662 | 113814602744 | 0.0162633 | 0.2637103 | 10 |
| 10053676 | 103754862514 | 0.0148259 | 0.2785361 | 11 |
| 10053675 | 91333175078 | 0.0130509 | 0.2915870 | 12 |
| 10056225 | 86556505303 | 0.0123683 | 0.3039553 | 13 |
| 10055071 | 75922618487 | 0.0108488 | 0.3148042 | 14 |
| 10225636 | 75731762010 | 0.0108216 | 0.3256257 | 15 |
| 10086797 | 75372024326 | 0.0107701 | 0.3363959 | 16 |
| 10060637 | 70981630306 | 0.0101428 | 0.3465387 | 17 |
| 10053322 | 60778901472 | 0.0086849 | 0.3552236 | 18 |
| 10053749 | 58415519916 | 0.0083472 | 0.3635707 | 19 |
| 10053725 | 54927315183 | 0.0078487 | 0.3714195 | 20 |
| 10214403 | 53473614068 | 0.0076410 | 0.3790605 | 21 |
| 10054606 | 53233512314 | 0.0076067 | 0.3866672 | 22 |
| 10054870 | 53034097530 | 0.0075782 | 0.3942454 | 23 |
| 10053722 | 51666524058 | 0.0073828 | 0.4016282 | 24 |
| 10315134 | 49791909717 | 0.0071149 | 0.4087431 | 25 |
| 10053679 | 48567479158 | 0.0069400 | 0.4156831 | 26 |
| 10053660 | 48167635063 | 0.0068828 | 0.4225659 | 27 |
| 10053750 | 45031083030 | 0.0064346 | 0.4290006 | 28 |
| 10054905 | 44812042983 | 0.0064033 | 0.4354039 | 29 |
| 10053818 | 43440477674 | 0.0062073 | 0.4416113 | 30 |
| 10053440 | 42888239004 | 0.0061284 | 0.4477397 | 31 |
| 10053791 | 42561702119 | 0.0060818 | 0.4538215 | 32 |
| 10054858 | 40212396987 | 0.0057461 | 0.4595675 | 33 |
| 10236531 | 39472051754 | 0.0056403 | 0.4652078 | 34 |
| 10053687 | 38454572733 | 0.0054949 | 0.4707027 | 35 |
| 10054968 | 38366512033 | 0.0054823 | 0.4761850 | 36 |
| 10309060 | 37245336644 | 0.0053221 | 0.4815071 | 37 |
| 10053654 | 35899158023 | 0.0051297 | 0.4866369 | 38 |
| 10071527 | 35310428757 | 0.0050456 | 0.4916825 | 39 |
| 10236565 | 35238100724 | 0.0050353 | 0.4967178 | 40 |
| 10053669 | 34929598436 | 0.0049912 | 0.5017090 | 41 |
| 10053774 | 33872599335 | 0.0048402 | 0.5065492 | 42 |
| 10053645 | 33818836965 | 0.0048325 | 0.5113816 | 43 |
| 10053673 | 33762682646 | 0.0048245 | 0.5162061 | 44 |
| 10054928 | 33504466921 | 0.0047876 | 0.5209937 | 45 |
| 10053666 | 33094988950 | 0.0047290 | 0.5257227 | 46 |
| 10053674 | 33058858137 | 0.0047239 | 0.5304466 | 47 |
| 10053727 | 32799955580 | 0.0046869 | 0.5351335 | 48 |
| 10054848 | 32602625324 | 0.0046587 | 0.5397922 | 49 |
| 10231115 | 32329051607 | 0.0046196 | 0.5444118 | 50 |
| 10054624 | 31325548780 | 0.0044762 | 0.5488880 | 51 |
| 10053672 | 29554609249 | 0.0042232 | 0.5531111 | 52 |
| 10053362 | 29389927798 | 0.0041996 | 0.5573108 | 53 |
| 10071650 | 29387541738 | 0.0041993 | 0.5615100 | 54 |
| 10071517 | 27934205907 | 0.0039916 | 0.5655017 | 55 |
| 10071744 | 27735310899 | 0.0039632 | 0.5694648 | 56 |
| 10071540 | 27638460863 | 0.0039493 | 0.5734142 | 57 |
| 10053661 | 27584568037 | 0.0039416 | 0.5773558 | 58 |
| 10054789 | 25801155583 | 0.0036868 | 0.5810427 | 59 |
| 10054693 | 25763123172 | 0.0036814 | 0.5847240 | 60 |
| 10054881 | 25539209303 | 0.0036494 | 0.5883734 | 61 |
| 10054819 | 24962582033 | 0.0035670 | 0.5919404 | 62 |
| 10053352 | 24910574121 | 0.0035596 | 0.5954999 | 63 |
| 10307816 | 24465327451 | 0.0034959 | 0.5989959 | 64 |
| 10054625 | 23908682903 | 0.0034164 | 0.6024123 | 65 |
| 10055065 | 23381002657 | 0.0033410 | 0.6057532 | 66 |
| 10054793 | 23056925646 | 0.0032947 | 0.6090479 | 67 |
| 10055064 | 22792608200 | 0.0032569 | 0.6123048 | 68 |
| 10054871 | 22726467185 | 0.0032475 | 0.6155523 | 69 |
| 10308124 | 22013371013 | 0.0031456 | 0.6186978 | 70 |
| 10316269 | 21843804960 | 0.0031213 | 0.6218192 | 71 |
| 10054842 | 21741004839 | 0.0031066 | 0.6249258 | 72 |
| 10053740 | 21411330932 | 0.0030595 | 0.6279854 | 73 |
| 10053695 | 21209627842 | 0.0030307 | 0.6310161 | 74 |
| 10054708 | 21156634542 | 0.0030231 | 0.6340392 | 75 |
| 10060672 | 20914890578 | 0.0029886 | 0.6370278 | 76 |
| 10054892 | 20608601274 | 0.0029448 | 0.6399726 | 77 |
| 10237550 | 20512493474 | 0.0029311 | 0.6429037 | 78 |
| 10071516 | 20454613484 | 0.0029228 | 0.6458266 | 79 |
| 10053360 | 20358839329 | 0.0029091 | 0.6487357 | 80 |
| 10053769 | 20093110419 | 0.0028712 | 0.6516069 | 81 |
| 10054816 | 19908528619 | 0.0028448 | 0.6544517 | 82 |
| 10054697 | 19489637197 | 0.0027849 | 0.6572366 | 83 |
| 10054854 | 19413304450 | 0.0027740 | 0.6600106 | 84 |
| 10053820 | 19383359398 | 0.0027698 | 0.6627804 | 85 |
| 10316132 | 18586743031 | 0.0026559 | 0.6654363 | 86 |
| 10053779 | 18536200367 | 0.0026487 | 0.6680850 | 87 |
| 10054806 | 18262788249 | 0.0026096 | 0.6706946 | 88 |
| 10053686 | 18059175147 | 0.0025805 | 0.6732751 | 89 |
| 10054783 | 17473368085 | 0.0024968 | 0.6757720 | 90 |
| 10229621 | 17373627212 | 0.0024826 | 0.6782545 | 91 |
| 10071487 | 17308563037 | 0.0024733 | 0.6807278 | 92 |
| 10053377 | 16794599550 | 0.0023998 | 0.6831277 | 93 |
| 10056226 | 16773813161 | 0.0023969 | 0.6855245 | 94 |
| 10405705 | 16585495873 | 0.0023700 | 0.6878945 | 95 |
| 10404849 | 16450680138 | 0.0023507 | 0.6902452 | 96 |
| 10237432 | 15976029914 | 0.0022829 | 0.6925280 | 97 |
| 10053808 | 15640213722 | 0.0022349 | 0.6947629 | 98 |
| 10053325 | 15577383047 | 0.0022259 | 0.6969888 | 99 |
| 10053324 | 15317538489 | 0.0021888 | 0.6991776 | 100 |
| 10404850 | 15288687737 | 0.0021846 | 0.7013622 | 101 |
| 10071541 | 15277586199 | 0.0021831 | 0.7035453 | 102 |
| 10307829 | 15041879313 | 0.0021494 | 0.7056947 | 103 |
| 10085558 | 14796156915 | 0.0021143 | 0.7078090 | 104 |
| 10053308 | 14764309645 | 0.0021097 | 0.7099187 | 105 |
| 10054846 | 14692924528 | 0.0020995 | 0.7120182 | 106 |
| 10404878 | 14634134375 | 0.0020911 | 0.7141093 | 107 |
| 10054912 | 14619633243 | 0.0020890 | 0.7161984 | 108 |
| 10053942 | 14334870386 | 0.0020484 | 0.7182467 | 109 |
| 10053368 | 14299338336 | 0.0020433 | 0.7202900 | 110 |
| 10403736 | 14057404846 | 0.0020087 | 0.7222987 | 111 |
| 10237429 | 13576721190 | 0.0019400 | 0.7242387 | 112 |
| 10054700 | 13511655588 | 0.0019307 | 0.7261694 | 113 |
| 10054788 | 12865270351 | 0.0018384 | 0.7280078 | 114 |
| 10308125 | 12565635314 | 0.0017955 | 0.7298033 | 115 |
| 10053736 | 12521273631 | 0.0017892 | 0.7315926 | 116 |
| 10054830 | 12403602373 | 0.0017724 | 0.7333649 | 117 |
| 10083059 | 12296104218 | 0.0017570 | 0.7351220 | 118 |
| 10053954 | 12290995037 | 0.0017563 | 0.7368783 | 119 |
| 10053704 | 12026371030 | 0.0017185 | 0.7385968 | 120 |
| 10053374 | 11962966462 | 0.0017094 | 0.7403062 | 121 |
| 10054992 | 11946260885 | 0.0017070 | 0.7420132 | 122 |
| 10053670 | 11735120035 | 0.0016769 | 0.7436901 | 123 |
| 10308204 | 11643806709 | 0.0016638 | 0.7453539 | 124 |
| 10053307 | 11616420019 | 0.0016599 | 0.7470138 | 125 |
| 10053718 | 11559786791 | 0.0016518 | 0.7486656 | 126 |
| 10054729 | 11477426572 | 0.0016400 | 0.7503057 | 127 |
| 10054629 | 11415859242 | 0.0016312 | 0.7519369 | 128 |
| 10053642 | 11155605979 | 0.0015941 | 0.7535310 | 129 |
| 10054773 | 11124836657 | 0.0015897 | 0.7551207 | 130 |
| 10060630 | 11094463720 | 0.0015853 | 0.7567060 | 131 |
| 10316105 | 10945774400 | 0.0015641 | 0.7582701 | 132 |
| 10054807 | 10636599682 | 0.0015199 | 0.7597900 | 133 |
| 10054669 | 10572550361 | 0.0015107 | 0.7613007 | 134 |
| 10053461 | 10413181860 | 0.0014880 | 0.7627887 | 135 |
| 10053409 | 10331265495 | 0.0014763 | 0.7642649 | 136 |
| 10054695 | 10311556876 | 0.0014735 | 0.7657384 | 137 |
| 10053549 | 10307089575 | 0.0014728 | 0.7672112 | 138 |
| 10053351 | 10201644874 | 0.0014577 | 0.7686689 | 139 |
| 10053667 | 9932125807 | 0.0014192 | 0.7700882 | 140 |
| 10054667 | 9799785996 | 0.0014003 | 0.7714885 | 141 |
| 10053724 | 9743546846 | 0.0013923 | 0.7728808 | 142 |
| 10308269 | 9740962357 | 0.0013919 | 0.7742727 | 143 |
| 10231118 | 9688134566 | 0.0013844 | 0.7756571 | 144 |
| 10053950 | 9683930933 | 0.0013838 | 0.7770408 | 145 |
| 10308228 | 9570067804 | 0.0013675 | 0.7784083 | 146 |
| 10053685 | 9331376827 | 0.0013334 | 0.7797417 | 147 |
| 10053476 | 9288517330 | 0.0013273 | 0.7810690 | 148 |
| 10402904 | 9272117776 | 0.0013249 | 0.7823939 | 149 |
| 10054977 | 9201937245 | 0.0013149 | 0.7837088 | 150 |
| 10308235 | 9169815293 | 0.0013103 | 0.7850191 | 151 |
| 10054698 | 9117734907 | 0.0013029 | 0.7863220 | 152 |
| 10054838 | 8931201807 | 0.0012762 | 0.7875982 | 153 |
| 10307820 | 8906175536 | 0.0012726 | 0.7888708 | 154 |
| 10307827 | 8898140436 | 0.0012715 | 0.7901423 | 155 |
| 10054817 | 8834638382 | 0.0012624 | 0.7914047 | 156 |
| 10054835 | 8731081666 | 0.0012476 | 0.7926523 | 157 |
| 10054859 | 8606838398 | 0.0012299 | 0.7938822 | 158 |
| 10053331 | 8569222248 | 0.0012245 | 0.7951067 | 159 |
| 10404061 | 8506058868 | 0.0012155 | 0.7963221 | 160 |
| 10054344 | 8464015802 | 0.0012095 | 0.7975316 | 161 |
| 10071512 | 8454481011 | 0.0012081 | 0.7987397 | 162 |
| 10407045 | 8394225783 | 0.0011995 | 0.7999391 | 163 |
Như vậy 163 Items chủ lực này mang lại 80% doanh thu nên Rainbow Store nên tập trung công tác hậu cần - kho bãi - dự trữ (Logistic) cho nhóm hàng hóa này. Ví dụ, riêng mã hàng 10053361 đã tạo ra 6.52% doanh thu cho cửa hàng.
Người thực hiện dự án này đề xuất sử dụng các Features có tên là R, F và M (có thể gọi là biến đầu vào Inputs) mô tả hành vi tiêu dùng của khách hàng với các định nghĩa như sau:
F và M là những inputs được tính trong một khoảng thời gian khảo sát nhất định (1 năm, một tháng, hoặc 1 quý hoặc là 5 tháng như tình huống của Project này). Riêng R thì phụ thuộc vào mốc thời gian lựa chọn của người làm mô hình và không ảnh hưởng đến kết quả của mô hình. Dưới đây là R codes cho tính toán R, F và M đồng thời tạo ra một cột biến mới có tên BuyNextMonth với mô tả cụ thể như sau: nếu một mã khách hàng, ví dụ, có thực hiện mua hàng trong 2 tháng Feb + Mar và cũng có thực hiện giao dịch trong tháng kế tiếp là Apr thì khách hàng này sẽ được ghi nhận là Yes ở cột biến BuyNextMonth và nếu khách hàng này không có bất kì giao dịch nào ở tháng Apr thì được nghi nhận là No.
#=====================================================
# Extract các Features (RFM Inputs) và Split Data
#=====================================================
# Hàm thực hiện extract ra các inputs RFM:
procesing_RFMdata <- function(month_training, month_lookback) {
# Data sử dụng để huấn luyện mô hình:
train_df <- my_df %>%
filter(time_mon %in% month_training)
# Data dùng để kiểm tra khách hàng sẽ mua ở tháng kế tiếp hay không:
lookback_df <- my_df %>%
filter(time_mon == month_lookback)
# Các khách hàng trong 2 tháng liên tiếp:
customer_2months <- train_df$CustomerID %>% unique()
# Các khách hàng có mặt ở tháng kế tiếp:
customer_next_month <- lookback_df$CustomerID %>% unique()
# Tính M:
train_df %>%
group_by(CustomerID) %>%
summarise(money = sum(money)) %>%
ungroup() -> df_train_m
# Tính F:
train_df %>%
group_by(CustomerID) %>%
count() %>%
ungroup() %>%
rename(freq = n) -> df_train_f
# Tính R:
now_time <- max(train_df$time_ymd)
y <- as.numeric(now_time - train_df$time_ymd)
train_df %>%
mutate(recency = y) %>%
group_by(CustomerID) %>%
summarise(recency = min(recency)) %>%
ungroup() -> df_train_r
# Join RFM:
df_modelling <- df_train_f %>%
full_join(df_train_m, by = "CustomerID") %>%
full_join(df_train_r, by = "CustomerID") %>%
mutate(BuyNextMonth = case_when(CustomerID %in% customer_next_month ~ "Yes", TRUE ~ "No")) %>%
mutate(BuyNextMonth = as.factor(BuyNextMonth))
return(df_modelling )
}Chúng ta sử dụng RFM extract ra từ hai tháng Feb và Mar là Inputs:
# Sử dụng hàm để extract ra RFM:
month_training23 <- c("Feb", "Mar")
procesing_RFMdata(month_training = month_training23, month_lookback = c("Apr")) -> df_modelling
# Xem qua dữ liệu:
df_modelling %>%
head() %>%
kable()| CustomerID | freq | money | recency | BuyNextMonth |
|---|---|---|---|---|
| aGdgaGRm | 21 | 14427449 | 20 | No |
| aGdgbmdq | 9 | 44511900 | 51 | Yes |
| aGdgbWNm | 6 | 99979199 | 46 | Yes |
| aGdgcGNs | 96 | 942962495 | 0 | Yes |
| aGdgcGRp | 59 | 352569255 | 33 | Yes |
| aGdhb2Jn | 2 | 244802756 | 1 | No |
Chúng ta có thể sử dụng nhiều mô hình phân loại để dự báo hành hành vi của khách hàng kể cả cách tiếp cận của thống kê truyền thống là Logistic cho đến các cách tiếp cận hiện đại hơn của Machine Learning. Để lựa chọn một mô hình có chất lượng dự báo tốt nhất mà predicts which customers make at least 1 purchase in a given month thì hướng tiếp cận đề xuất như sau:
Bước này đóng vai trò là thăm dò - tìm kiếm sơ bộ mô hình tốt nhất có thể. Dưới đây là R codes cho việc huấn luyện và so sánh chất lượng dự báo của mô hình dựa trên Sensitivity:
# Scaling 0-1 cho dữ liệu:
df_forML <- df_modelling %>%
select(- CustomerID) %>%
mutate_if(is.numeric, function(x) {(x - min(x)) / (max(x) - min(x))})
# Chia dữ liệu theo tỉ lệ 80 - 20:
library(caret)
set.seed(1)
id <- createDataPartition(df_forML$BuyNextMonth, p = 0.8, list = FALSE)
df_train <- df_forML[id, ]
df_test <- df_forML[-id, ]
# Thiết lập các điều kiện tinh chỉnh và so sánh:
set.seed(1)
number <- 5
repeats <- 3
control <- trainControl(method = "repeatedcv",
number = number ,
repeats = repeats,
classProbs = TRUE,
savePredictions = "final",
index = createResample(df_forML$BuyNextMonth, repeats*number),
summaryFunction = twoClassSummary,
allowParallel = TRUE)
# Sử dụng tính toán song song nhằm tiết kiệm thời gian training:
library(doParallel)
registerDoParallel(cores = detectCores() - 1)
# 15 models được lựa chọn:
my_models <- c("adaboost", "xgbTree", "svmRadial",
"knn", "gbm", "C5.0", "ranger",
"rf", "nnet", "glm", "lda", "treebag",
"bagFDA", "glmboost", "cforest")
# Huấn luyện mô hình:
library(caretEnsemble)
set.seed(1)
system.time(model_list1 <- caretList(BuyNextMonth ~.,
data = df_forML,
trControl = control,
metric = "ROC",
methodList = my_models))## Iter TrainDeviance ValidDeviance StepSize Improve
## 1 1.3482 nan 0.1000 0.0190
## 2 1.3161 nan 0.1000 0.0157
## 3 1.2844 nan 0.1000 0.0159
## 4 1.2559 nan 0.1000 0.0135
## 5 1.2352 nan 0.1000 0.0103
## 6 1.2164 nan 0.1000 0.0092
## 7 1.2014 nan 0.1000 0.0077
## 8 1.1869 nan 0.1000 0.0070
## 9 1.1754 nan 0.1000 0.0054
## 10 1.1640 nan 0.1000 0.0058
## 20 1.1036 nan 0.1000 0.0017
## 40 1.0735 nan 0.1000 0.0002
## 50 1.0704 nan 0.1000 0.0001
##
## note: only 2 unique complexity parameters in default grid. Truncating the grid to 2 .
##
## note: only 2 unique complexity parameters in default grid. Truncating the grid to 2 .
##
## # weights: 26
## initial value 9433.194597
## iter 10 value 7597.875242
## iter 20 value 7203.250534
## iter 30 value 7111.234719
## iter 40 value 7108.289764
## iter 50 value 7106.505261
## iter 60 value 7106.263299
## iter 70 value 7105.117909
## iter 80 value 7103.227087
## iter 90 value 7102.075021
## iter 100 value 7101.667148
## final value 7101.667148
## stopped after 100 iterations
## note: only 2 unique complexity parameters in default grid. Truncating the grid to 2 .
## user system elapsed
## 254.92 7.66 2558.61
# Khai thác các kết quả của việc huấn luyện 15 mô hình:
list_of_results <- lapply(my_models, function(x) {model_list1[[x]]$resample})
# Chuyển hóa về data frame:
total_df <- do.call("bind_rows", list_of_results)
total_df %>% mutate(Model = lapply(my_models, function(x) {rep(x, number*repeats)}) %>% unlist()) -> total_df
# Tính trung bình của Sensitivity, AUC, và Specificity:
total_df %>%
dplyr::select(-Resample) %>%
group_by(Model) %>%
summarise(avg_auc = mean(ROC), avg_sen = mean(Sens), avg_spec = mean(Spec)) %>%
ungroup() -> df_results
# Hàm hình ảnh hóa chất lượng phân loại của các mô hình:
my_bar <- function(metric_name) {
metric_name <- noquote(metric_name)
my_colors <- c("#3E606F")
df_results %>%
dplyr::select(Model, metric_name) -> df
names(df) <- c("Model", "value")
df %>%
arrange(value) %>%
mutate(Model = factor(Model, levels = Model)) %>%
mutate(label = paste0(round(100*value, 1), "%")) -> m
m %>%
ggplot(aes(Model, value)) +
geom_col(fill = my_colors, color = my_colors) +
coord_flip() +
geom_text(data = m, aes(label = label), hjust = 1.1, color = "white", size = 4) +
theme_ft_rc() +
theme(panel.grid = element_blank()) +
theme(axis.text.x = element_blank()) +
theme(axis.text.y = element_text(color = "white", size = 12)) +
scale_y_discrete(expand = c(0.01, 0)) +
labs(x = NULL, y = NULL)
}
# Chất lượng phân loại của các mô hình theo chiều giảm dần của các chỉ số:
gridExtra::grid.arrange(my_bar("avg_auc") + labs(title = "AUC/ROC"),
my_bar("avg_sen") + labs(title = "Sensitivity"),
my_bar("avg_spec") + labs(title = "Specificity"),
nrow = 1, padding = unit(99, "line"))Kết quả trên chỉ ra rằng Neural Network (nnet) là mô hình ML có Sensitivity lớn nhất: trung bình là 81.6%. Tuy nhiên nếu lưu ý thêm tiêu chuẩn diện tích nằm dưới đường cong ROC thì GBM (Gradient Boosting Machines) lại là mô hình toàn diện hơn cả.
Do vậy mô hình kiến nghị được lựa chọn để dự báo một khách hàng “có mua ít nhất một item trong tháng tới hay không?” sẽ là GBM. Dưới đây là R codes huấn luyện GBM trên train data rồi đánh giá lại chất lượng dự báo của mô hình trên test data:
# Thiết lập các điều kiện tinh chỉnh:
set.seed(1)
control_ml <- trainControl(method = "repeatedcv",
number = number ,
repeats = repeats,
classProbs = TRUE,
summaryFunction = twoClassSummary,
allowParallel = TRUE)
# Huấn luyện GBM:
set.seed(29)
gbm_default <- train(BuyNextMonth ~.,
method = "gbm",
data = df_train,
trControl = control_ml,
metric = "ROC")## Iter TrainDeviance ValidDeviance StepSize Improve
## 1 1.3391 nan 0.1000 0.0232
## 2 1.3013 nan 0.1000 0.0189
## 3 1.2705 nan 0.1000 0.0154
## 4 1.2424 nan 0.1000 0.0133
## 5 1.2201 nan 0.1000 0.0111
## 6 1.2010 nan 0.1000 0.0094
## 7 1.1826 nan 0.1000 0.0088
## 8 1.1678 nan 0.1000 0.0075
## 9 1.1545 nan 0.1000 0.0065
## 10 1.1426 nan 0.1000 0.0055
## 20 1.0835 nan 0.1000 0.0012
## 40 1.0583 nan 0.1000 -0.0001
## 50 1.0549 nan 0.1000 -0.0001
# Chất lượng dự báo của GBM:
pred <- predict(gbm_default, df_test)
actual <- df_test$BuyNextMonth
confusionMatrix(pred, actual, positive = "Yes")## Confusion Matrix and Statistics
##
## Reference
## Prediction No Yes
## No 1085 477
## Yes 256 833
##
## Accuracy : 0.7235
## 95% CI : (0.706, 0.7405)
## No Information Rate : 0.5058
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.4458
##
## Mcnemar's Test P-Value : 4.441e-16
##
## Sensitivity : 0.6359
## Specificity : 0.8091
## Pos Pred Value : 0.7649
## Neg Pred Value : 0.6946
## Prevalence : 0.4942
## Detection Rate : 0.3142
## Detection Prevalence : 0.4108
## Balanced Accuracy : 0.7225
##
## 'Positive' Class : Yes
##
Thực tế có 1310 khách mua hàng trong tháng Apr và GBM dự báo đúng sẽ có 833 người (trong tổng số 1310) sẽ mua hàng. Nói cách khác Sensitivity là 63.59% (bằng 833 / (833 + 477)) như ta có thể thấy ở ma trận nhầm lẫn. Chỉ số đánh giá chất lượng khác của mô hình chúng ta quan tâm có thể là Accuracy = 72.23%.
Chúng ta cũng có thể minh họa ROC và tính diện tích nằm dưới đường cong AUC:
# Hàm tính AUC/ROC:
auc_for_test <- function(pd_selected) {
return(pROC::roc(actual, pd_selected))
}
# Hàm hình ảnh hóa AUC/ROC curve:
my_ROC_curve <- function(auc_object) {
sen_spec_df <- data_frame(TPR = auc_object$sensitivities,
FPR = 1 - auc_object$specificities)
sen_spec_df %>%
ggplot(aes(x = FPR, ymin = 0, ymax = TPR))+
geom_polygon(aes(y = TPR), fill = "red", alpha = 0.3)+
geom_path(aes(y = TPR), col = "firebrick", size = 1.2) +
geom_abline(intercept = 0, slope = 1, color = "gray37", size = 1, linetype = "dashed") +
scale_y_continuous(labels = scales::percent) +
scale_x_continuous(labels = scales::percent) +
theme_bw() +
coord_equal() %>%
return()
}
# Xác suất dự báo cho sự kiện "khách hàng sẽ mua hàng trong tháng kế tiếp":
pd_pred <- predict(gbm_default, df_test, type = "prob") %>% pull(Yes)
my_auc <- auc_for_test(pd_pred)
# Sử dụng hàm:
my_auc %>%
my_ROC_curve() +
labs(x = "FPR (1 - Specificity)",
y = "TPR (Sensitivity)",
title = "Figure 3: Model Performance Based on Test Data",
subtitle = paste0("AUC Value for GBM Model: ", my_auc$auc %>% round(3)))Trong hầu hết các ứng dụng thì ROC/AUC trên 65% là mô hình có thể sử dụng được. Trong tình huống của chúng ta thì GBM có ROC/AUC = 77.60% trên test data cũng là một kết quả khá cao.
Dưới đây là một số vấn đề chưa được giải quyết:
Thứ nhất, những kết quả mà chúng ta thu được từ việc sử dụng mô hình GBM trên test data thì đó mới chỉ là GBM mặc định chưa tinh chỉnh tham số. Bằng việc tinh chỉnh tham số tối ưu cho GBM chúng ta có thể đạt kết quả chính xác hơn khi dự báo có hay không một khách hàng sẽ mua hàng trong tháng kế tiếp. Tuy nhiên trong giới hạn thời gian 8h thì việc tinh chỉnh GBM là việc không khả thì vì thời gian training có thể rất lớn và đòi hỏi cấu hình phần cứng máy tính tương đối cao. Giải pháp khác là lựa chọn dịch vụ tính toán đám mây của Amazon. Riêng việc huấn luyện 15 mô hình ở trên cũng đã mất gần 2h.
Thứ hai, Assignment này không đưa ra một mục tiêu cụ thể nào cho việc xây dựng mô hình. Các mô hình ML có thể được tinh chỉnh (và lựa chọn) theo một tiêu chí nào đó như Accuracy, Recall hay ROC/AUC nhưng Assignment này thì không nêu rõ cụ thể. Do vậy, người xây dựng mô hình này tự đặt ra mục tiêu của việc xây dựng mô hình, ví dụ, là phải cover được ít nhất 70% khách hàng sẽ mua hàng trong tháng tới. Hiện tại GBM mặc định mới chỉ cover được 63.59% khách hàng sẽ mua hàng trong tháng tới. Mục tiêu này có thể đạt được bằng một trong hai cách: (1) tinh chỉnh/tìm kiếm tham số tối ưu cho GBM, hơạc (2) thay đổi ngưỡng xác suất khi phân loại vì ma trận nhầm lẫn mặc định sử dụng ngưỡng 0.5 cho phân loại. Vấn đề này đã được trình bày và giải quyết ở mục 3.3.
Việc tinh chỉnh các mô hình ML có thể rất mất thời gian. Chiến lược tinh chỉnh kiểu Grid Search sẽ liệt kê ra tất cả các sự kết hợp có thể có của các tham số rồi tìm kiếm một sự kết hợp cụ thể nào đó của tham số sao cho lượng phân loại của mô hình là cao nhất. Như vậy nếu chúng ta lựa chọn chỉ 5 tham số (con số này vẫn còn ít so với các tham số có thể tinh chỉnh) và mỗi một tham số chọn 10 ứng viên và sử dụng 5 folds cho Cross-Validation thì tổng số các mô hình mà máy tính sẽ phải chạy là 5×10^5 = 500.000 mô hình (nếu lựa chọn refit = False). Để cắt giảm thời gian huấn luyện chúng ta có thể thực hiện tinh chỉnh tham số theo kiểu hên - xui bằng cách chỉ chọn ngẫu nhiên, chẳng hạn, 50000 (tức là 10% của 500.000) sự kết hợp khác nhau của tham số để tinh chỉnh mô hình. Cách thức tinh chỉnh hên xui này gọi là Random Search. Chiến lược tinh chỉnh này là chúng ta có thể cắt giảm đáng kể thời gian tinh chỉnh với các giá phải trả là chúng ta có thể “bỏ sót” tham số tốt nhất của mô hình ML. Tuy vậy trong một số tình huống thì tinh chỉnh theo Random Seach có thể vẫn là không hiệu quả và chúng ta cần một chiến lược tinh chỉnh hiệu quả hơn là Bayesian Optimization có thể được thực hiện trên cả R lẫn Python. Nếu sử dụng Python thì Bayesian Optimization có thể được thực hiện như sau.
Về mặt kĩ thuật, có thể sử dụng thư viện h2o để huấn luyện và tinh chỉnh GBM. Việc sử dụng h2o cho huấn luyện và tinh chỉnh GBM (cũng như các mô hình ML nói chung) có một vài ưu thế: (1) chạy trên cả R và Python với cú pháp tương tự nhau, (2) được viết với lõi là ngôn ngữ Java nên triển khai ứng dụng ở dạng web service là rất tiện lợi, (3) thời gian training mô hình tương đối nhanh.
Tất cả những vấn đề này cần thời gian (cũng như phần cứng máy tính tương đối mạnh) và không thể giải quyết được trong khoảng thời gian là 8h.
Giả sử mục tiêu được hạ xuống là mô hình cover được ít nhất 60% khách hàng sẽ mua hàng trong tháng tới (tức là tháng Apr) thì GBM mặc định của chúng ta thỏa mãn. Nếu vậy chúng ta sẽ gửi promotional e-mails cho 833 khách hàng có danh sách dưới đây (chỉ liệt kê 6 khách hàng đầu):
df_modelling[-id, ] %>%
mutate(BuyNextMonth_Predicted = actual) %>%
filter(BuyNextMonth_Predicted == "Yes") %>%
select(CustomerID, BuyNextMonth_Predicted) %>%
head() %>%
kable()| CustomerID | BuyNextMonth_Predicted |
|---|---|
| aGdobmNl | Yes |
| aGdobmZj | Yes |
| aGhkaWo= | Yes |
| aGhkcWpo | Yes |
| aGhpa2dk | Yes |
| aGNgaGNl | Yes |
Còn nếu chúng ta muốn dự báo những khách hàng nào sẽ mua hàng trong tháng 5 (May) dựa trên dữ liệu giao dịch của hai tháng trước đó là Mar và Apr thì trước hết chúng ta training mô hình GBM (mặc định) trên dữ liệu của hai tháng Mar + Apr:
month_training34 <- c("Mar", "Apr")
procesing_RFMdata(month_training = month_training23, month_lookback = c("May")) -> df_modelling
df_forML <- df_modelling %>%
select(- CustomerID) %>%
mutate_if(is.numeric, function(x) {(x - min(x)) / (max(x) - min(x))})
set.seed(1)
id <- createDataPartition(df_forML$BuyNextMonth, p = 0.8, list = FALSE)
df_train <- df_forML[id, ]
df_test <- df_forML[-id, ]
set.seed(29)
gbm_default <- train(BuyNextMonth ~.,
method = "gbm",
data = df_train,
trControl = control_ml,
metric = "ROC")## Iter TrainDeviance ValidDeviance StepSize Improve
## 1 1.3408 nan 0.1000 0.0201
## 2 1.3068 nan 0.1000 0.0171
## 3 1.2785 nan 0.1000 0.0141
## 4 1.2534 nan 0.1000 0.0118
## 5 1.2326 nan 0.1000 0.0100
## 6 1.2136 nan 0.1000 0.0093
## 7 1.1982 nan 0.1000 0.0076
## 8 1.1842 nan 0.1000 0.0064
## 9 1.1712 nan 0.1000 0.0062
## 10 1.1614 nan 0.1000 0.0044
## 20 1.1075 nan 0.1000 0.0008
## 40 1.0860 nan 0.1000 -0.0001
## 50 1.0824 nan 0.1000 -0.0001
Chất lượng dự báo của mô hình thể hiện qua ma trận nhầm lẫn:
# Chất lượng dự báo của GBM:
pred <- predict(gbm_default, df_test)
actual <- df_test$BuyNextMonth
confusionMatrix(pred, actual, positive = "Yes")## Confusion Matrix and Statistics
##
## Reference
## Prediction No Yes
## No 1167 469
## Yes 235 780
##
## Accuracy : 0.7344
## 95% CI : (0.7172, 0.7512)
## No Information Rate : 0.5289
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.4616
##
## Mcnemar's Test P-Value : < 2.2e-16
##
## Sensitivity : 0.6245
## Specificity : 0.8324
## Pos Pred Value : 0.7685
## Neg Pred Value : 0.7133
## Prevalence : 0.4711
## Detection Rate : 0.2942
## Detection Prevalence : 0.3829
## Balanced Accuracy : 0.7284
##
## 'Positive' Class : Yes
##
GBM lúc này sẽ cover đúng 62.45% khách hàng sẽ mua hàng vào tháng May kế tiếp và dưới đây là danh sách 780 khách hàng đó:
df_modelling[-id, ] %>%
mutate(BuyNextMonth_Predicted = actual) %>%
filter(BuyNextMonth_Predicted == "Yes") %>%
select(CustomerID, BuyNextMonth_Predicted) %>%
head() %>%
kable()| CustomerID | BuyNextMonth_Predicted |
|---|---|
| aGdobmZj | Yes |
| aGdpbmFp | Yes |
| aGhna2Rk | Yes |
| aGhoaWlp | Yes |
| aGlga2k= | Yes |
| aGNhaWVm | Yes |