Practice: Cluster Analysis using R

Đề xuất bởi J.MacQueen:

Some Methods for Classification and analysis of multivariate observations - J. MacQueen (1967).

Link: https://www.cs.cmu.edu/~bhiksha/courses/mlsp.fall2010/class14/macqueen.pdf

Ý tưởng chính của Cluster Analysis: Chia dữ liệu vào các cluster và các item tương tự được nhóm vào cùng một cluster, mục đích khám phá hơn là tiên lượng/ tiên đoán (discovery rather than prediction)

Lợi ích của phân tích cụm rất nhiều, ví dụ như: Phân nhóm khách hàng vào các nhóm (quan tâm vấn đề lịch sử mua hàng số tiền, số lần, giới tính, độ tuổi, trung bình giữa các hóa đơn, …), phân loại các mẫu thời tiết khác nhau cho một vùng, nhóm các bài viết/ tin tức vào các chủ đề, khám phá các điểm nóng tội phạm, …

Trong bài viết này mình chủ yếu thực hành các bước phân tích cụm sử dụng thuật toán K-Means và các phương pháp để tìm K ‘nhóm’ tối ưu, tuy nói K tối ưu nhưng trong thực tế việc tìm K rất khó và luôn mang ý nghĩa chủ quan, vì vậy việc tìm K luôn được cân nhắc bằng cách chọn thủ công theo ý quyết định của người muốn phân bao nhiêu nhóm.

Data lấy từ Kaggle có tên Real estate price prediction. Link data: https://www.kaggle.com/quantbruce/real-estate-price-prediction?select=Real+estate.csv

Để tiến hành thực hiện đầu tiên cần Tải các thư viện cần thiết để tiến hành phân tích cụm

library(tidyverse)
library(dplyr)
library(factoextra)
library(NbClust)
library(cluster) 
library(skimr)
library(table1)
library(explore)
library(ggplot2)
library(psych)
library(broom)
library(DataExplorer)

Explorer Data

df <- read.csv("C:/Users/KH/Google Drive/Data Sample/Real estate.csv")
skim(df)
Name df
Number of rows 414
Number of columns 8
_______________________
Column type frequency:
numeric 8
________________________
Group variables None

Data summary

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
No 0 1 207.50 119.66 1.00 104.25 207.50 310.75 414.00 ▇▇▇▇▇
X1.transaction.date 0 1 2013.15 0.28 2012.67 2012.92 2013.17 2013.42 2013.58 ▆▅▅▃▇
X2.house.age 0 1 17.71 11.39 0.00 9.03 16.10 28.15 43.80 ▆▇▃▅▂
X3.distance.to.the.nearest.MRT.station 0 1 1083.89 1262.11 23.38 289.32 492.23 1454.28 6488.02 ▇▂▁▁▁
X4.number.of.convenience.stores 0 1 4.09 2.95 0.00 1.00 4.00 6.00 10.00 ▇▅▆▃▂
X5.latitude 0 1 24.97 0.01 24.93 24.96 24.97 24.98 25.01 ▁▅▇▂▁
X6.longitude 0 1 121.53 0.02 121.47 121.53 121.54 121.54 121.57 ▁▁▂▇▁
Y.house.price.of.unit.area 0 1 37.98 13.61 7.60 27.70 38.45 46.60 117.50 ▅▇▂▁▁

Để làm gọn tên các biến số mình dùng hàm rename trong package ‘dplyr’, quy ước chung: No: Loại bỏ, X1: Transaction Date, X2: House Age, X3: Distance to the nearest MRT Station, X4: Number of Convenience Stores, X5: Latitude, X6: Longitude, Y: House Price of Unit Area

df <- df[-c(1, 9)] 
df <- rename(df,
             X1 = X1.transaction.date,
             X2 = X2.house.age, 
             X3 = X3.distance.to.the.nearest.MRT.station, 
             X4 = X4.number.of.convenience.stores,
             X5 = X5.latitude,
             X6 = X6.longitude,
             Y = Y.house.price.of.unit.area)

head(df)
##         X1   X2         X3 X4       X5       X6    Y
## 1 2012.917 32.0   84.87882 10 24.98298 121.5402 37.9
## 2 2012.917 19.5  306.59470  9 24.98034 121.5395 42.2
## 3 2013.583 13.3  561.98450  5 24.98746 121.5439 47.3
## 4 2013.500 13.3  561.98450  5 24.98746 121.5439 54.8
## 5 2012.833  5.0  390.56840  5 24.97937 121.5425 43.1
## 6 2012.667  7.1 2175.03000  3 24.96305 121.5125 32.1
summary(df)
##        X1             X2               X3                X4        
##  Min.   :2013   Min.   : 0.000   Min.   :  23.38   Min.   : 0.000  
##  1st Qu.:2013   1st Qu.: 9.025   1st Qu.: 289.32   1st Qu.: 1.000  
##  Median :2013   Median :16.100   Median : 492.23   Median : 4.000  
##  Mean   :2013   Mean   :17.713   Mean   :1083.89   Mean   : 4.094  
##  3rd Qu.:2013   3rd Qu.:28.150   3rd Qu.:1454.28   3rd Qu.: 6.000  
##  Max.   :2014   Max.   :43.800   Max.   :6488.02   Max.   :10.000  
##        X5              X6              Y         
##  Min.   :24.93   Min.   :121.5   Min.   :  7.60  
##  1st Qu.:24.96   1st Qu.:121.5   1st Qu.: 27.70  
##  Median :24.97   Median :121.5   Median : 38.45  
##  Mean   :24.97   Mean   :121.5   Mean   : 37.98  
##  3rd Qu.:24.98   3rd Qu.:121.5   3rd Qu.: 46.60  
##  Max.   :25.01   Max.   :121.6   Max.   :117.50
geometric.mean(df$Y) #Khảo sát bằng trung bình nhân 
## [1] 35.3994

Khảo sát phân bố dữ liệu của đơn giá bán

df %>% explore(Y, 
               color="#333333", 
               title = "Distribution of House price of unit area", 
               auto_scale = TRUE)

Khảo sát toàn bộ phân bố dữ liệu của các biến qua histogram

plot_histogram(df)

Khảo sát giá bán trung bình của BĐS theo phân nhóm các tiện ích, ở đây trung bình về đơn giá bán có xu hướng tăng theo nhóm tiện ích, càng nhiều tiện ích giá trị cộng hưởng cho giá bán càng tăng cao.

group_mean_X4 <- aggregate(df$Y, 
                          list(df$X4), 
                          mean)

colnames(group_mean_X4) <- c("Number of convenience stores", "Mean of Y")

group_mean_X4 %>% 
  ggplot(aes(x = `Number of convenience stores`, y = `Mean of Y`)) + 
  geom_line(color="#333333") + 
  ggtitle("Number of convenience stores via Mean of Y") + 
  theme_minimal()

Biểu đồ dưới đây cho thấy mối quan hệ giữa đơn giá giao dịch và khoảng cách các BĐS giao dịch này xa/ gần MRT station. Khoảng cách càng xa MRT station giá càng có xu hướng thấp hơn.

df %>% ggplot(aes(X3, Y)) + 
  geom_point(color="#333333") + 
  ggtitle("House price of unit area via distance to the nearest MRT station ") + 
  theme_minimal()

Khảo sát tương quan giữa các biến

plot_correlation(df)

Visualization of distance matrix

Đầu tiên,tính chỉ số Hopkins xem xét về khả năng phân nhóm của dữ liệu, cũng như vẽ ma trận khoảng cách (matrix distance), chỉ số hopskin có giá trị trong khoảng [-1,1], chỉ số càng hướng về 1 biểu thị khả năng tập dữ liệu có khả năng phân nhóm.

set.seed(123)

df_ct <- get_clust_tendency(df, 
                            n=50, 
                            graph=T)
                        
df_ct$hopkins
## [1] 0.9372092
print(df_ct$plot)

Choosing K

Chọn K sử dụng Elbow Method và đối chiếu với Gap Statistic Method:

#Elbow method

fviz_nbclust(df, 
             kmeans, # Sử dụng thuật toán K-Means
             method='wss') + 
  labs(subtitle = "Elbow method")

#Gap Statistic method

fviz_nbclust(df, 
             kmeans, 
             nstart = 35, 
             method = 'gap_stat', 
             nboot = 50) + 
  labs(subtitle = "Gap statistic method")

Implement k-means clustering

Ta thử tiến hành phân tích cluster qua K-Means Alg với center = 3, tức là chọn k = 3 cụm.

df_clust <- kmeans(df, 
                   centers = 3, 
                   nstart = 35)
summary(df_clust)
##              Length Class  Mode   
## cluster      414    -none- numeric
## centers       21    -none- numeric
## totss          1    -none- numeric
## withinss       3    -none- numeric
## tot.withinss   1    -none- numeric
## betweenss      1    -none- numeric
## size           3    -none- numeric
## iter           1    -none- numeric
## ifault         1    -none- numeric
fviz_cluster(df_clust, 
             df, 
             palette = c("#2E9FDF", "#00AFBB", "#E7B800"), 
             geom = "point",
             ellipse.type = "convex",
             ggtheme = theme_bw())

tidy(df_clust) #Trích xuất nội dung các tham số trong df_clust thành dataframe, hàm tidy trong package broom 
## # A tibble: 3 x 10
##      X1    X2    X3    X4    X5    X6     Y  size  withinss cluster
##   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <int>     <dbl> <fct>  
## 1 2013.  19.5 4296. 0.220  24.9  122.  20.2    41 24403788. 1      
## 2 2013.  18.3  386. 5.32   25.0  122.  44.3   280 14322405. 2      
## 3 2013.  15.2 1768. 2.10   25.0  122.  26.7    93 16392660. 3

Khả năng của hàm augment() trong package broom cho phép ta chồng 1 dataframe lên dataframe gốc, từ đó ta có thể tạo các vụ khác 1 cách dễ dàng trên dataframe đã có

augment(df_clust, df) %>%  
  ggplot(aes(X3, 
             Y, 
             color =.cluster)) + 
  geom_point(alpha = 2) + 
  ggtitle("Clustering: House price of unit area via distance to the nearest MRT station") + 
  theme_minimal()

library(plotly)
## 
## Attaching package: 'plotly'

## The following object is masked from 'package:ggplot2':
## 
##     last_plot

## The following object is masked from 'package:stats':
## 
##     filter

## The following object is masked from 'package:graphics':
## 
##     layout
plot <- augment(df_clust, df) %>%  
  ggplot(aes(X3, 
             Y, 
             color =.cluster)) + 
  geom_point(alpha = 3) + 
  ggtitle("Clustering: House price of unit area via distance to the nearest MRT station") 

ggplotly(plot)

Validation

Ước tính chỉ số Silhouette S_i có giá trị từ - 1 đến 1

sil = silhouette(df_clust$cluster, dist(df))

fviz_silhouette(sil)
##   cluster size ave.sil.width
## 1       1   41          0.68
## 2       2  280          0.80
## 3       3   93          0.61