Phần phân tích này là phần thứ hai trong phân tích phân khúc khách hàng. Bài phân tích đầu tiên tập trung vào việc phân cụm K-Means trong R để phân khúc khách hàng thành các nhóm riêng biệt dựa trên thói quen mua hàng. Bài phân tích này có một cách tiếp cận khác, sử dụng Phân tích thành phần chính (PCA) trong R làm công cụ để xem các nhóm khách hàng. Bởi vì PCA tấn công vấn đề từ một góc độ khác với K-Mean, nên chúng ta có thể có được những hiểu biết sâu sắc khác nhau. Chúng ta sẽ so sánh cả kết quả K-Mean với hình ảnh trực quan của PCA. Hãy xem điều gì sẽ xảy ra khi chúng ta áp dụng PCA.
PCA là thuật toán giảm kích thước: PCA lấy dữ liệu của bạn và phân tách dữ liệu đó bằng cách sử dụng các phép biến đổi thành các thành phần chính (PC). Mỗi PC được chọn theo hướng trực giao để tối đa hóa phương sai tuyến tính của dữ liệu. Về bản chất, PCA không gì khác hơn là một thuật toán lấy dữ liệu số theo tọa độ x, y, z và thay đổi tọa độ thành x’, y’ và z’ để tối đa hóa phương sai tuyến tính. Một bài viết hay về nền tảng của PCA là Phân tích thành phần giá trị: Giải thích bằng trực quan. Như tiêu đề gợi ý, bài viết có hình ảnh tuyệt vời để giải thích thuật toán.
Điều này giúp ích như thế nào trong việc phân khúc khách hàng/phát hiện cụm tương đồng? Không giống như K-Mean, PCA không phải là giải pháp trực tiếp. Những gì PCA giúp là trực quan hóa bản chất của một tập dữ liệu. Vì PCA chọn PC dựa trên phương sai tuyến tính tối đa nên chúng ta có thể sử dụng một số PC đầu tiên để mô tả phần lớn tập dữ liệu mà không cần so sánh và đối chiếu từng tính năng. Bằng cách sử dụng PC1 và PC2, chúng ta có thể hiển thị dưới dạng 2D và kiểm tra các cụm. Chúng ta cũng có thể kết hợp các kết quả với các nhóm K-means để xem K-means được phát hiện so với các cụm trong hình ảnh trực quan PCA.
Trước khi chuyển sang PCA, bạn nên xem lại phần chúng ta đã dừng lại trong bài viết phân khúc khách hàng trước đó.
Trong bài phân tích đầu tiên, chúng ta đã sử dụng phân cụm K-means để
phân tích tập dữ liệu bikes data set, một tập hợp các tệp
excel chứa dữ liệu cho các cửa hàng xe đạp (khách hàng), xe đạp (sản
phẩm) và đơn đặt hàng cho nhà sản xuất xe đạp
Cannondale. Các cửa hàng xe đạp và đơn đặt hàng là hư
cấu/mô phỏng (xem bài viết
orderSimulatoR
để tham khảo), nhưng xe đạp (sản phẩm) là mô hình thực tế từ trang web
của Cannondale.
Một giả thuyết được hình thành rằng các cửa hàng xe đạp mua xe đạp
dựa trên các đặc điểm của xe đạp như đơn giá (cao cấp so với giá cả phải
chăng), loại chính (Núi so với Đường bộ), khung (nhôm so với carbon),
v.v. Các đơn đặt hàng được kết hợp với khách hàng và thông tin sản phẩm
và được nhóm lại để tạo thành ma trận bán hàng theo mẫu mã và khách
hàng. Hàm kmeans() được chạy trên một loạt các cụm tiềm
năng, k và hàm Silhouette() từ gói cluster
được sử dụng để xác định số lượng cụm tối ưu.
Thay vì xem lại bài phân tích trước, bạn có thể sử dụng các phần bên dưới để chuẩn bị sẵn sàng mọi thứ cho PCA.
Chúng ta có thể truy cập dữ liệu ở đây nếu bạn muốn theo dõi. Chúng ta sẽ cần tải xuống các tệp sau:
order.xlsx: Chứa các đơn đặt hàng hư cấu cho
Cannondale. customer.id trong tệp
order.xlsx liên quan đến bike shop.id
trong tệp bikeshops.xlsx và product.id
trong tệp order.xlsx liên quan đến bike.id
trong tệp bikeshops.xlsx.
xe đạp.xlsx: Chứa thông tin về sản phẩm (ví dụ:
mẫu xe đạp, danh mục chính, danh mục phụ, đơn giá, v.v.).
bike.id là khóa chính.
bikeshops.xlsx: Chứa thông tin về khách hàng (ví
dụ: tên và địa điểm của khách hàng). bikeshop.id là khóa
chính.
Tập lệnh tải và định cấu hình dữ liệu vào ma trận xu hướng khách hàng được hiển thị bên dưới.
Kịch bản này sẽ đọc dữ liệu. Đảm bảo bạn có các tệp excel trong thư mục có tên “data” trong thư mục làm việc hiện tại của bạn trước khi chạy tập lệnh bên dưới.
# Read Cannondale orders data --------------------------------------------------
library(xlsx) # Used to read bikes data set
customers <- read.xlsx2("./data/bikeshops.xlsx", sheetIndex = 1,
colClasses = c("numeric", "character", "character",
"character", "character", "numeric"))
products <- read.xlsx2("./data/bikes.xlsx", sheetIndex = 1,
colClasses = c("numeric", "character", "character",
"character", "character", "numeric"))
orders <- read.xlsx2("./data/orders.xlsx", sheetIndex = 1, colIndex = 2:7,
colClasses = c("numeric", "numeric", "Date", "numeric",
"numeric", "numeric"))
Tập lệnh bên dưới kết hợp các khung dữ liệu orders,
customers và products vào
order.extends, đây là khung dữ liệu mô phỏng đầu ra mà
chúng ta sẽ nhận được từ truy vấn SQL của cơ sở dữ liệu đơn đặt hàng/hệ
thống ERP. Sau đó, dữ liệu được xử lý để tạo thành
customerTrends, có cấu trúc dữ liệu sao cho các hàng chứa
sản phẩm và các cột chứa số lượng mua hàng (dưới dạng phần trăm trên
tổng số) theo khách hàng. Đầu ra, customerTrends, được sử
dụng để phân cụm k-means.
# Combine orders, customers, and products data frames --------------------------
library(dplyr)
orders.extended <- merge(orders, customers, by.x = "customer.id", by.y="bikeshop.id")
orders.extended <- merge(orders.extended, products, by.x = "product.id", by.y = "bike.id")
orders.extended <- orders.extended %>%
mutate(price.extended = price * quantity) %>%
select(order.date, order.id, order.line, bikeshop.name, model,
quantity, price, price.extended, category1, category2, frame) %>%
arrange(order.id, order.line)
# Group by model & model features, summarize by quantity purchased -------------
library(tidyr) # For spread function
customerTrends <- orders.extended %>%
group_by(bikeshop.name, model, category1, category2, frame, price) %>%
summarise(total.qty = sum(quantity)) %>%
spread(bikeshop.name, total.qty)
customerTrends[is.na(customerTrends)] <- 0 # Remove NA's
# Convert price to binary high/low category ------------------------------------
library(Hmisc) # Needed for cut2 function
customerTrends$price <- cut2(customerTrends$price, g=2)
# Convert customer purchase quantity to percentage of total quantity -----------
customerTrends.mat <- as.matrix(customerTrends[,-(1:5)]) # Drop first five columns
customerTrends.mat <- prop.table(customerTrends.mat, margin = 2) # column-wise pct
customerTrends <- bind_cols(customerTrends[,1:5], as.data.frame(customerTrends.mat))
Dữ liệu customerTrends có mẫu như sau:
# Preview the data
orders.extended %>%
DT::datatable(
extensions = 'FixedColumns',
options = list(
dom = 't',
scrollX = TRUE,
fixedColumns = TRUE,
scrollY = 420,
scroller = TRUE
)
)
Chúng ta đã sử dụng hàm kmeans() để thực hiện phân cụm
k-means. Bài phân tích K-Means đi sâu vào chi
tiết về cách chọn đúng số cụm mà ta đã bỏ qua để cho ngắn gọn. Chúng ta
sẽ sử dụng các nhóm cho cụm k=4 và k=5
với phân tích PCA. Năm cụm là giải pháp tốt nhất về mặt lý thuyết và bốn
cụm là giải pháp tốt nhất khi kiểm tra dữ liệu.
# K-Means Clustering (used later) ----------------------------------------------
set.seed(11) # For reproducibility
km4.out <- kmeans(t(customerTrends[,-(1:5)]), centers = 4, nstart = 50)
set.seed(11) # For reproducibility
km5.out <- kmeans(t(customerTrends[,-(1:5)]), centers = 5, nstart = 50)
Bây giờ, quay lại trọng tâm chính của chúng ta: PCA.
Việc áp dụng PCA rất đơn giản sau khi dữ liệu được định dạng. Chúng ta
sử dụng hàm prcomp() có sẵn trong cơ sở R.
Chúng ta thực sự khuyên bạn nên chia tỷ lệ và căn giữa dữ liệu (vì một
số lý do, đây không phải là mặc định). Đọc thêm về PCA trong
R có thể được tìm thấy
ở
đây.
# PCA using prcomp() -----------------------------------------------------------
pca <- prcomp(t(customerTrends[,-(1:5)]), scale. = T, center = T) # Perform PCA
Sau khi PCA được thực hiện, chúng ta nên xem xét phương sai được giải
thích. Như đã nêu trước đây, mục tiêu của PCA là giảm kích thước của dữ
liệu. PCA thực hiện điều này bằng cách chuyển đổi dữ liệu thành các kích
thước trực giao với biến thể. Sự khác biệt được giải thích càng lớn thì
PC càng tóm tắt được nhiều thông tin hơn. Hàm prcomp() trả
về phương sai này bằng PC. Chúng ta có thể sử dụng
summary(pca) để giải thích phương sai. Chúng ta hãy nhìn
trực quan chín PC đầu tiên.
summary(pca)
## Importance of components:
## PC1 PC2 PC3 PC4 PC5 PC6 PC7
## Standard deviation 4.9852 4.2206 2.2308 2.18304 2.1169 2.03586 1.86265
## Proportion of Variance 0.2562 0.1836 0.0513 0.04913 0.0462 0.04273 0.03577
## Cumulative Proportion 0.2562 0.4399 0.4911 0.54028 0.5865 0.62921 0.66498
## PC8 PC9 PC10 PC11 PC12 PC13 PC14
## Standard deviation 1.80739 1.76329 1.70165 1.61928 1.55759 1.50889 1.46505
## Proportion of Variance 0.03368 0.03205 0.02985 0.02703 0.02501 0.02347 0.02213
## Cumulative Proportion 0.69865 0.73071 0.76056 0.78759 0.81260 0.83607 0.85820
## PC15 PC16 PC17 PC18 PC19 PC20 PC21
## Standard deviation 1.30348 1.26612 1.22705 1.18511 1.13653 1.04480 0.98599
## Proportion of Variance 0.01752 0.01653 0.01552 0.01448 0.01332 0.01125 0.01002
## Cumulative Proportion 0.87572 0.89224 0.90776 0.92224 0.93556 0.94681 0.95684
## PC22 PC23 PC24 PC25 PC26 PC27 PC28
## Standard deviation 0.87912 0.84801 0.80364 0.74209 0.69318 0.6607 0.5990
## Proportion of Variance 0.00797 0.00741 0.00666 0.00568 0.00495 0.0045 0.0037
## Cumulative Proportion 0.96480 0.97222 0.97888 0.98455 0.98951 0.9940 0.9977
## PC29 PC30
## Standard deviation 0.47169 2.214e-15
## Proportion of Variance 0.00229 0.000e+00
## Cumulative Proportion 1.00000 1.000e+00
PC1 và PC2 kết hợp giải thích 44% phương sai của dữ liệu và có sự chênh lệch lớn giữa PC2 và PC3. Điều này có nghĩa là việc vẽ đồ thị của PC 1 và 2 sẽ giúp chúng ta hiểu khá rõ về dữ liệu và việc thêm nhiều PC hơn PC2 sẽ mang lại sự cải thiện tối thiểu. Lưu ý rằng không phải lúc nào cũng có sự sụt giảm đáng kể sau PC2 và nếu có nhiều sự khác biệt về PC được giải thích, chúng ta cũng cần phải đánh giá chúng.
Chúng ta sẽ vẽ đồ thị PC1 và PC2, đồng thời ban đầu chúng ta sẽ tô
màu các cụm bằng cách sử dụng các nhóm k-mean đã tạo trước đó. Để làm
điều này, trước tiên chúng ta cần đưa dữ liệu pca vào khung
dữ liệu kết hợp khách hàng với PC. May mắn thay, gói
ggfortify có chức năng gọi là fortify() để
thực hiện việc đó. Sau khi dữ liệu của chúng ta được củng cố, chúng ta
sẽ tạo hai khung dữ liệu với các nhóm k-mean được thêm vào cuối để chúng
ta có thể nhóm chúng. Chúng ta sẽ sử dụng nhóm k-means để tô màu kết quả
PCA để có thể so sánh với k-means và hiểu rõ hơn.
# Manipulate data for PCA Analyis ----------------------------------------------
library(ggfortify) # For fortify()
pca.fortify <- fortify(pca) # fortify() gets pca into usable format
# Add group (short for color) column using k=4 and k=5 groups
pca4.dat <- cbind(pca.fortify, group=km4.out$cluster)
pca5.dat <- cbind(pca.fortify, group=km5.out$cluster)
Tập lệnh bên dưới cung cấp các tập lệnh ggplot mẫu để
tạo sơ đồ chung cho PC1 và PC2. Tập lệnh sử dụng thư viện vẽ đồ thị
tương tác yêu thích của chúng ta, plotly, để biến kết quả
ggplot thành các đồ thị tương tác. Các tập lệnh được sử
dụng cho phần tiếp theo cũng tuân theo quy trình chung tương tự, vì vậy
chúng ta sẽ không hiển thị mã cho ngắn gọn.
# Plotting PC1 and PC2 using ggplot and plotly ---------------------------------
library(ggplot2)
library(plotly)
# Script for plotting k=4
gg2 <- ggplot(pca4.dat) +
geom_point(aes(x=PC1, y=PC2, col=factor(group), text=rownames(pca4.dat)), size=2) +
labs(title = "Visualizing K-Means Clusters Against First Two Principal Components") +
scale_color_brewer(name="", palette = "Set1")
# Use plotly for inteactivity
plotly2 <- ggplotly(gg2, tooltip = c("text", "x", "y")) %>%
layout(legend = list(x=.9, y=.99))
Phân đoạn PCA cho thấy một số kết quả thú vị. Có vẻ như có năm cụm thay vì bốn. Kết quả k=4 k-mean sẽ không xác định được điều này vì chúng tôi đã chọn bốn cụm. Có thể bạn nghĩ rằng phân tích hình bóng từ bài viết trước chỉ ra rằng chúng ta nên chọn k=5, vì vậy đây là chỗ chúng ta đã sai. Hãy xem điều gì xảy ra với năm cụm k-means.
Có vẻ như Nhóm 2 đã được phân loại sai. Từ bài phân tích của K-means, điều này thực sự có ý nghĩa: Chúng ta đã kiểm tra các cụm và Nhóm 2 và Nhóm 4 rất giống nhau về sở thích đối với xe đạp ở mức giá cấp thấp. Chúng ta quyết định kết hợp các cụm này bằng cách chuyển sang cụm k=4. Tiếp theo, chúng ta sẽ xem xét việc phân cụm dựa trên việc kiểm tra sơ đồ PCA.
Từ hình ảnh PCA, chúng ta có thể thấy có hai cửa hàng xe đạp không thuộc Nhóm 5. Dựa trên kiểm tra trực quan, chúng ta có thể sửa đổi các nhóm (cột 128) bằng cách chuyển Nhóm 1 (San Antonio Bike Shop & Philadelphia Bike Shop) với các cửa hàng thuộc Nhóm 5 được phân loại không chính xác (Denver Bike Shop & Kansas City 29ers).
# Switch Group 2 Bike Shops with misclassified Bike Shops in Group 4 -----------
pca.final.dat <- pca5.dat
pca.final.dat[rownames(pca.final.dat) %in%
c("San Antonio Bike Shop", "Philadelphia Bike Shop"), 128] <- 5
pca.final.dat[rownames(pca.final.dat) %in%
c("Denver Bike Shop", "Kansas City 29ers"), 128] <- 1
Và kết qua được vẽ lại như sau:
Mọi thứ có vẻ ổn, nhưng chúng ta cần kiểm tra các tùy chọn của Nhóm 1 mới được tạo để đảm bảo rằng họ thực sự là một phân khúc khách hàng độc lập.
Tập lệnh bên dưới sửa đổi khung dữ liệu CustomerTrends
để chỉ chọn các cột khách hàng nằm trong nhóm mà chúng ta muốn kiểm tra.
Sau đó, tập lệnh lấy trung bình phần trăm số lượng đã mua của khách hàng
so với tổng số lượng đã mua (các giá trị trong cột khách hàng). Khung dữ
liệu được sắp xếp theo mức độ được mua thường xuyên nhất để chúng ta có
thể thấy xu hướng trung tâm của nhóm. Đối với Nhóm 1, xu hướng trung tâm
là ưa chuộng những chiếc xe đạp leo núi cấp thấp.
# Inspect Group 2 Preferences --------------------------------------------------
# Select only groups in group num and perform row-wise average of bike prefs
library(dplyr)
group.num <- 1 # Set group number
group.names <- rownames(pca.final.dat[pca.final.dat$group == group.num, ])
groupTrends <- customerTrends %>%
select(model:price, match(group.names, names(.))) # Use match() to select column names
group.avg <- apply(groupTrends[6:ncol(groupTrends)], 1, mean) # Take average of values
groupTrends <- bind_cols(groupTrends, as_data_frame(group.avg)) %>%
arrange(-group.avg)
knitr::kable(head(groupTrends, 10)) # Top ten products by group avg. pct. purchased
| model | category1 | category2 | frame | price | Denver Bike Shop | Kansas City 29ers | value |
|---|---|---|---|---|---|---|---|
| Catalyst 2 | Mountain | Sport | Aluminum | [ 415, 3500) | 0.0256410 | 0.0187266 | 0.0221838 |
| Trail 5 | Mountain | Sport | Aluminum | [ 415, 3500) | 0.0204259 | 0.0216076 | 0.0210168 |
| F-Si Carbon 4 | Mountain | Cross Country Race | Carbon | [ 415, 3500) | 0.0165146 | 0.0247767 | 0.0206456 |
| Scalpel 29 4 | Mountain | Cross Country Race | Aluminum | [ 415, 3500) | 0.0186875 | 0.0210314 | 0.0198595 |
| Catalyst 4 | Mountain | Sport | Aluminum | [ 415, 3500) | 0.0199913 | 0.0184385 | 0.0192149 |
| F-Si 1 | Mountain | Cross Country Race | Aluminum | [ 415, 3500) | 0.0204259 | 0.0175742 | 0.0190000 |
| Trail 4 | Mountain | Sport | Aluminum | [ 415, 3500) | 0.0152108 | 0.0218957 | 0.0185532 |
| Trail 1 | Mountain | Sport | Aluminum | [ 415, 3500) | 0.0204259 | 0.0164218 | 0.0184238 |
| Trail 2 | Mountain | Sport | Aluminum | [ 415, 3500) | 0.0208605 | 0.0158456 | 0.0183530 |
| Beast of the East 1 | Mountain | Trail | Aluminum | [ 415, 3500) | 0.0182529 | 0.0181504 | 0.0182017 |
Hãy so sánh với Nhóm 5. Chạy lại tập lệnh trước đó thay đổi group.num từ 1 thành 5. Chúng ta có thể thấy rằng sở thích của Nhóm 5 giống với Nhóm 1 ở chỗ cả hai nhóm đều thích xe đạp cấp thấp/giá cả phải chăng. Tuy nhiên, các giao dịch mua hàng đầu của Nhóm 5 bao gồm sự kết hợp giữa “Mountain and Road”, trong khi các giao dịch mua hàng đầu của Nhóm 1 chỉ có “Mountain”. Dường như có sự khác biệt!
library(dplyr)
group.num <- 5 # Set group number
group.names <- rownames(pca.final.dat[pca.final.dat$group == group.num, ])
groupTrends <- customerTrends %>%
select(model:price, match(group.names, names(.))) # Use match() to select column names
group.avg <- apply(groupTrends[6:ncol(groupTrends)], 1, mean) # Take average of values
groupTrends <- bind_cols(groupTrends, as_data_frame(group.avg)) %>%
arrange(-group.avg)
#knitr::kable(head(groupTrends, 10)) # Top ten products by group avg. pct. purchased
groupTrends %>%
DT::datatable(
extensions = 'FixedColumns',
options = list(
dom = 't',
scrollX = TRUE,
fixedColumns = TRUE,
scrollY = 420,
scroller = TRUE
)
)
PCA có thể là một công cụ kiểm tra chéo có giá trị đối với K-means để phân khúc khách hàng. Mặc dù k-means giúp chúng ta tiếp cận các phân khúc khách hàng thực sự, nhưng việc đánh giá trực quan các nhóm bằng PCA đã giúp xác định một phân khúc khách hàng khác, một phân khúc mà giải pháp k=5 k-means không phát hiện được.
Bài phân tích này mở rộng phương pháp phân khúc khách hàng của chúng
ta bằng cách sử dụng PCA để kiểm tra trực quan các cụm. Chúng ta đã thao
tác dữ liệu đơn đặt hàng của mình để có được định dạng liên quan đến sản
phẩm với việc mua hàng của khách hàng. Chúng ta đã sử dụng hàm
prcomp() để thực hiện PCA trên khung dữ liệu được định dạng
của mình. Chúng ta đã củng cố đầu ra PCA bằng cách sử dụng hàm
fortify() từ gói ggfortify, cho phép chúng ta
vẽ biểu đồ PC theo khách hàng. Chúng ta đã thêm các nhóm cụm k-means vào
khung dữ liệu được củng cố để kiểm tra trực quan các cụm k-mean. Chúng
ta thấy rằng sự khác biệt có thể phát sinh do k-means xác định các cụm
theo chương trình trong khi PCA cho phép chúng ta kiểm tra các cụm một
cách trực quan. Kết quả cuối cùng là sự cải thiện về phân khúc khách
hàng bằng cách giải quyết vấn đề phát hiện cộng đồng từ hai góc độ khác
nhau và kết hợp các kết quả!
1.Pricipal Component Analysis: Explained Visually: This article is an excellent place to start for those that are new to PCA or those that would like to understand the details.
2.Computing and Visualizing PCA in R: This is a great article that takes PCA to the next level in R with biplots, predictions, and the caret package.