Clustering - K means, SOM(Self-Organaizing Map)

이론설명 자료 링크
다변량 자료를 각 특성의 유사성에 따라 여러 그룹(군집 또는 집락)으로 나누는 통계적 기법중의 하나
군집의 개수, 내용, 구조가 파악되지 않은 상태에서 특성을 파악하며, 군집들 간의 관계를 분석 (탐색적 분석)
고객의 세분화 또는 군집 별로 추가적인 분석을 수행하기 위해 활용

유사성, 거리
- 군집으로 묶기 위해서는 개체간에 유사한 특성을 가지고 있어야 함
- 이 유사한 특성의 정도를 나타내는 척도로 개체간의 거리를 사용하고, 거리가 상대적으로 가까운 개체들을 동일 군집으로 묶음

1. 계층적 군집분석(Hierarchical Clustering)
- 정확히 하나의 레코드로 구성된 군집들로 시작
- 최종적으로 모든 레코드들로 구성된 하나의 군집만이 남을 때까지 가장 가까운 두 군집들을 점진적으로 병합

2. 비계층적 군집분석 : K-평균 군집분석(K-Means algorithm)
- 단계0 : 사전에 군집의 수 K를 지정한다.
- 단계1 : 각 군집에 1개의 군집중심을 임의로 정한다.(보통 서로 상당히 떨어진 개체를 선택함)
- 단계2 : 모든 개체를 각각 가장 가까운 군집중심에 배속시킨다.
- 단계3 : 각 군집의 중심을 산출한다.
- 단계4 : 단계2와 단계3을 변화가 거의 없을 때까지(보통 10회 이하) 반복한다.

3. Kohonen SOM 군집분석

SOM(Self-Organizing Map)은 "자기조직화 지도"라는 하며, 관측 개체들을 스스로 조직화하여 지도의 형태로 뿌려주는 신경망 기법이다.
SOM 개념도는 2개의 층으로 이루어져 있으며, 첫 번째 층이 입력층(input layer), 두 번째 층은 출력층(output layer)으로 이루어진 2차원 격자(grid)로 되어 있다.
library(cluster)
library(NbClust)
library(kohonen)
library(ggplot2)
library(gridExtra)
library(scales)
# Read Data
cdata <- read.delim("data/Cluster.txt", stringsAsFactors=FALSE)
head(cdata)
##   ID  MONEY VISIT CROSS API
## 1  1 367900    15     3  14
## 2  2  64000     3     1 109
## 3  3 467400    10     8  12
## 4  4  61000     3     1  70
## 5  5 128000     4     2  45
## 6  6 353620     6     5  22
##### K-means Clustering with R

# 군집수를 4로 하는 k-means clustering
km <- kmeans(subset(cdata, select=-c(ID)), centers=4)
str(km)
## List of 9
##  $ cluster     : Named int [1:1000] 3 3 3 3 3 3 1 3 3 3 ...
##   ..- attr(*, "names")= chr [1:1000] "1" "2" "3" "4" ...
##  $ centers     : num [1:4, 1:4] 1.28e+06 3.42e+06 2.55e+05 1.13e+07 3.26e+01 ...
##   ..- attr(*, "dimnames")=List of 2
##   .. ..$ : chr [1:4] "1" "2" "3" "4"
##   .. ..$ : chr [1:4] "MONEY" "VISIT" "CROSS" "API"
##  $ totss       : num 1.65e+15
##  $ withinss    : num [1:4] 2.80e+13 3.39e+13 2.77e+13 1.42e+14
##  $ tot.withinss: num 2.32e+14
##  $ betweenss   : num 1.42e+15
##  $ size        : int [1:4] 181 41 770 8
##  $ iter        : int 3
##  $ ifault      : int 0
##  - attr(*, "class")= chr "kmeans"
km
## K-means clustering with 4 clusters of sizes 181, 41, 770, 8
## 
## Cluster means:
##        MONEY      VISIT     CROSS       API
## 1  1284818.8  32.607735 10.966851  7.232044
## 2  3421840.0  62.146341 15.390244  3.024390
## 3   255051.4   8.124675  3.654545 34.877922
## 4 11323243.8 101.125000 20.750000  0.750000
## 
## Clustering vector:
##    1    2    3    4    5    6    7    8    9   10   11   12   13   14   15 
##    3    3    3    3    3    3    1    3    3    3    1    3    1    3    1 
##   16   17   18   19   20   21   22   23   24   25   26   27   28   29   30 
##    3    3    3    3    1    3    3    1    1    3    3    1    3    1    1 
##   31   32   33   34   35   36   37   38   39   40   41   42   43   44   45 
##    3    3    4    2    1    3    3    3    3    3    3    3    3    3    3 
##   46   47   48   49   50   51   52   53   54   55   56   57   58   59   60 
##    3    3    3    1    3    3    3    1    1    1    3    3    3    3    3
    .........
##  931  932  933  934  935  936  937  938  939  940  941  942  943  944  945
##    3    3    3    3    1    3    3    3    3    3    3    3    3    3    3 
##  946  947  948  949  950  951  952  953  954  955  956  957  958  959  960 
##    3    3    2    3    1    1    3    3    3    1    3    3    3    2    1 
##  961  962  963  964  965  966  967  968  969  970  971  972  973  974  975 
##    3    3    3    3    3    3    3    3    3    1    3    3    3    1    3 
##  976  977  978  979  980  981  982  983  984  985  986  987  988  989  990 
##    3    3    3    3    2    3    3    3    3    3    3    2    3    3    3 
##  991  992  993  994  995  996  997  998  999 1000 
##    3    3    3    3    3    3    3    3    3    3 
## 
## Within cluster sum of squares by cluster:
## [1] 2.803588e+13 3.391002e+13 2.770304e+13 1.424076e+14
##  (between_SS / total_SS =  85.9 %)
## 
## Available components:
## 
## [1] "cluster"      "centers"      "totss"        "withinss"    
## [5] "tot.withinss" "betweenss"    "size"         "iter"        
## [9] "ifault"
# API 변수를 사용하지 않고, 군집수를 3개로 설정.
km3 <- kmeans(subset(cdata, select=-c(ID, API)), centers=3)
km3
## K-means clustering with 3 clusters of sizes 887, 105, 8
## 
## Cluster means:
##        MONEY     VISIT     CROSS
## 1   357964.3  10.88388  4.507328
## 2  2397360.6  48.11429 13.638095
## 3 11323243.8 101.12500 20.750000
## 
## Clustering vector:
##    1    2    3    4    5    6    7    8    9   10   11   12   13   14   15 
##    1    1    1    1    1    1    2    1    1    1    1    1    1    1    1 
##   16   17   18   19   20   21   22   23   24   25   26   27   28   29   30 
##    1    1    1    1    2    1    1    1    2    1    1    1    1    2    2 
##   31   32   33   34   35   36   37   38   39   40   41   42   43   44   45 
##    1    1    3    2    1    1    1    1    1    1    1    1    1    1    1 
##   46   47   48   49   50   51   52   53   54   55   56   57   58   59   60 
##    1    1    1    1    1    1    1    1    2    1    1    1    1    1    1
    .........
##  931  932  933  934  935  936  937  938  939  940  941  942  943  944  945
##    1    1    1    1    1    1    1    1    1    1    1    1    1    1    1 
##  946  947  948  949  950  951  952  953  954  955  956  957  958  959  960 
##    1    1    2    1    1    2    1    1    1    1    1    1    1    2    1 
##  961  962  963  964  965  966  967  968  969  970  971  972  973  974  975 
##    1    1    1    1    1    1    1    1    1    2    1    1    1    1    1 
##  976  977  978  979  980  981  982  983  984  985  986  987  988  989  990 
##    1    1    1    1    2    1    1    1    1    1    1    2    1    1    1 
##  991  992  993  994  995  996  997  998  999 1000 
##    1    1    1    1    1    1    1    1    1    1 
## 
## Within cluster sum of squares by cluster:
## [1] 9.262028e+13 1.088448e+14 1.424076e+14
##  (between_SS / total_SS =  79.2 %)
## 
## Available components:
## 
## [1] "cluster"      "centers"      "totss"        "withinss"    
## [5] "tot.withinss" "betweenss"    "size"         "iter"        
## [9] "ifault"
# 군집의 반경과 군집간 관계
clusplot(subset(cdata, select=-c(ID)), km$cluster, main="cluster 4 : All variables")

clusplot(subset(cdata, select=-c(ID, API)), km3$cluster, main="cluster 3 : remove API")

# 군집의 분포
cdata$cluster <- as.factor(km$cluster)
qplot(MONEY, VISIT, colour=cluster, data=cdata)

plot(subset(cdata, select=-c(ID,cluster)), col=km$cluster)

cdata$cluster3 <- as.factor(km3$cluster)
qplot(MONEY, VISIT, colour=cluster3, data=cdata)

plot(subset(cdata, select=-c(ID,cluster,cluster3)), col=km3$cluster)

# 군집별 군집화변수의 밀도 : 방법1
p1 <- qplot(MONEY, fill=cluster, alpha=.5, data=cdata, geom="density") + scale_alpha(guide="none")
p2 <- qplot(VISIT, fill=cluster, alpha=.5, data=cdata, geom="density") + theme(legend.position="none")
p3 <- qplot(CROSS, fill=cluster, alpha=.5, data=cdata, geom="density") + theme(legend.position="none")
p4 <- qplot(API, fill=cluster, alpha=.5, data=cdata, geom="density") + theme(legend.position="none")
grid.arrange(p1, p2, p3, p4, ncol=2, nrow=2)

# 군집별 군집화변수의 밀도 : 방법2
p1 <- ggplot(cdata, aes(MONEY)) + geom_density(fill='deeppink3', adjust=1) + facet_grid(. ~ cluster) + scale_x_continuous(breaks=NULL) + scale_y_continuous("", breaks=NULL)
p2 <- ggplot(cdata, aes(VISIT)) + geom_density(fill='deeppink3', adjust=1) + facet_grid(. ~ cluster) + scale_x_continuous(breaks=NULL) + scale_y_continuous("", breaks=NULL) + theme(strip.text.x=element_blank())
p3 <- ggplot(cdata, aes(CROSS)) + geom_density(fill='deeppink3', adjust=1) + facet_grid(. ~ cluster) + scale_x_continuous(breaks=NULL) + scale_y_continuous("", breaks=NULL) + theme(strip.text.x=element_blank())
p4 <- ggplot(cdata, aes(API)) + geom_density(fill='deeppink3', adjust=1) + facet_grid(. ~ cluster) + scale_x_continuous(breaks=NULL) + scale_y_continuous("", breaks=NULL) + theme(strip.text.x=element_blank())
grid.arrange(p1, p2, p3, p4, ncol=1, nrow=4)

# 군집의 크기
x <- ggplot(cdata, aes(x=factor(1), fill=cluster))
x + geom_bar(width=1) + coord_polar(theta="y")

# 최적의 군집 수 찾기 : 방법1
sd <- cdata[sample(1:nrow(cdata),100),-1]
d <- dist(sd, method = "euclidean")
fit <- hclust(d, method="complete")
plot(fit)
rect.hclust(fit, k=4, border = "red")

# 최적의 군집 수 찾기 : 방법2
wss <- 0; set.seed(1)
for(i in 1:15) wss[i] <- kmeans(subset(cdata, select=-c(ID,cluster,cluster3)), centers=i)$tot.withinss
plot(1:15, wss, type="b", xlab="# of clusters", ylab="Within group sum of squares")

# 최적의 군집 수 찾기: 방법3. 많은 시간 소요됨.
# nc = NbClust(subset(cdata, select=-c(ID,cluster,cluster3)), min.nc=2, max.nc=15, method='kmeans')
# barplot(table(nc$Best.nc[1,]), xlab="# of clusters", ylab="# of criteria", main="Number of clusters chosen by 26 criteria")


##########################################
SOM Clustering with R
##########################################
cdata <- read.delim("data/Cluster.txt", stringsAsFactors=FALSE)

# 데이터 정규화
cdata.n <- scale(subset(cdata, select=-c(ID)))

# SOM clustering 을 3 x 3 으로 설정.
set.seed(1)
som_model <- som(data = cdata.n, grid = somgrid(3, 3, "rectangular"))
str(som_model)    # codes : 각 노드의 weight
## List of 10
##  $ data        : num [1:1000, 1:4] -0.227 -0.463 -0.15 -0.466 -0.414 ...
##   ..- attr(*, "dimnames")=List of 2
##   .. ..$ : chr [1:1000] "1" "2" "3" "4" ...
##   .. ..$ : chr [1:4] "MONEY" "VISIT" "CROSS" "API"
##   ..- attr(*, "scaled:center")= Named num [1:4] 659823.1 15.5 5.6 28.3
##   .. ..- attr(*, "names")= chr [1:4] "MONEY" "VISIT" "CROSS" "API"
##   ..- attr(*, "scaled:scale")= Named num [1:4] 1.29e+06 1.92e+01 4.67 3.22e+01
##   .. ..- attr(*, "names")= chr [1:4] "MONEY" "VISIT" "CROSS" "API"
##  $ grid        :List of 5
##   ..$ pts   : int [1:9, 1:2] 1 2 3 1 2 3 1 2 3 1 ...
##   .. ..- attr(*, "dimnames")=List of 2
##   .. .. ..$ : NULL
##   .. .. ..$ : chr [1:2] "x" "y"
##   ..$ xdim  : num 3
##   ..$ ydim  : num 3
##   ..$ topo  : chr "rectangular"
##   ..$ n.hood: chr "square"
##   ..- attr(*, "class")= chr "somgrid"
##  $ codes       : num [1:9, 1:4] 8.546 2.183 0.775 0.269 -0.111 ...
##   ..- attr(*, "dimnames")=List of 2
##   .. ..$ : NULL
##   .. ..$ : chr [1:4] "MONEY" "VISIT" "CROSS" "API"
##  $ changes     : num [1:100, 1] 0.0202 0.021 0.0212 0.0224 0.0221 ...
##  $ alpha       : num [1:2] 0.05 0.01
##  $ radius      : num [1:2] 2 -2
##  $ toroidal    : logi FALSE
##  $ unit.classif: int [1:1000] 9 8 5 8 6 5 4 5 9 4 ...
##  $ distances   : num [1:1000] 0.309 0.752 0.273 0.146 0.055 ...
##  $ method      : chr "som"
##  - attr(*, "class")= chr "kohonen"
# SOM 시각화
plot(som_model, main = "feature distribution")

plot(som_model, type="counts", main = "cluster size")

plot(som_model, type="quality", main = "mapping quality")  # 할당된 element 들의 유사도

# 군집별 변수의 영향도
coolBlueHotRed <- function(n, alpha = 1) {
  rainbow(n, end=4/6, alpha=alpha)[n:1]
}

for (i in 1:ncol(som_model$data))
  plot(som_model, type="property", property=som_model$codes[,i], main=dimnames(som_model$data)[[2]][i], palette.name=coolBlueHotRed)

# ggplot2 패키지를 이용하여 SPSS Modeler와 유사한 Grid 시각화
cdata$clusterX <- som_model$grid$pts[som_model$unit.classif,"x"]
cdata$clusterY <- som_model$grid$pts[som_model$unit.classif,"y"]
p <- ggplot(cdata, aes(clusterX, clusterY))
p + geom_jitter(position = position_jitter(width=.2, height=.2))



# K-Means(k=6)을 사용하여 SOM neuron(10x10)들 간의 유사성을 시각화

참조 슬라이드 링크
library(kohonen)
library(RColorBrewer)
library(fields)
Hexagon <- function (x, y, unitcell = 1, col = col) {
    polygon(c(x, x, x + unitcell/2, x + unitcell, x + unitcell, 
              x + unitcell/2), c(y + unitcell * 0.125, 
                                 y + unitcell * 0.875, 
                                 y + unitcell * 1.125, 
                                 y + unitcell * 0.875, 
                                 y + unitcell * 0.125, 
                                 y - unitcell * 0.125), 
            col = col, border=NA)
}

cdata <- read.delim("data/Cluster.txt", stringsAsFactors=FALSE)
cdata.n <- scale(subset(cdata, select=-c(ID)))

som_model2 <- som(data = cdata.n, grid = somgrid(10, 10, "rectangular"))
k = 6
somClusters <- kmeans(som_model2$codes, centers = k)
somClusters
## K-means clustering with 6 clusters of sizes 2, 46, 26, 3, 7, 16
## 
## Cluster means:
##        MONEY      VISIT      CROSS        API
## 1  8.2237320  4.8942046  3.6120212 -0.8606384
## 2 -0.2699493 -0.3223770 -0.3357094 -0.2496793
## 3  0.3566740  0.5408914  0.9180064 -0.6252556
## 4 -0.4596952 -0.6586599 -0.9624127  4.2828612
## 5  2.0625665  2.6264913  2.0452184 -0.7848548
## 6 -0.4199132 -0.5746073 -0.7785007  1.1355674
## 
## Clustering vector:
##   [1] 1 5 5 3 3 3 2 2 2 2 1 5 5 3 3 2 2 2 2 2 5 5 3 3 3 2 2 2 2 2 5 3 3 3 2
##  [36] 2 2 2 2 2 3 3 3 3 2 2 2 2 2 6 3 3 3 2 2 2 2 2 6 6 3 3 3 2 2 2 6 6 6 6
##  [71] 3 3 3 2 2 2 6 6 6 6 3 2 2 2 2 2 6 6 6 4 3 2 2 2 2 2 6 6 4 4
## 
## Within cluster sum of squares by cluster:
## [1] 25.401693 15.783441 19.868128  3.951426 16.116291  5.408053
##  (between_SS / total_SS =  84.5 %)
## 
## Available components:
## 
## [1] "cluster"      "centers"      "totss"        "withinss"    
## [5] "tot.withinss" "betweenss"    "size"         "iter"        
## [9] "ifault"
# plotting 1
plot(som_model2, main = "feature distribution")

# plotting 2
plot(0, 0, type = "n", axes = FALSE, xlim = c(0, som_model2$grid$xdim), ylim = c(0, som_model2$grid$ydim), xlab = "", ylab = "", asp = 1)
ColRamp <- rev(designer.colors(n=k, col=brewer.pal(k,"Set1")))
ColorCode <- rep("#FFFFFF", length(somClusters$cluster))

for (i in 1:length(somClusters$cluster))
    ColorCode[i] <- ColRamp[somClusters$cluster[i]]

offset <- 0.5
for (row in 1:som_model2$grid$ydim) {
    for (column in 0:(som_model2$grid$xdim-1))
        Hexagon(column + offset, row - 1, col = ColorCode[row + som_model2$grid$ydim * column])
    offset <- ifelse(offset, 0, 0.5)
}