사과 품종별 군집화

Jun

2020-02-23


출처: 시작하세요! 데이터 분석 with R : R로 배우는 기초 통계와 데이터 분석 기법
Github Code: Jun4871 Github

overview

의사결정 나무를 통한 사과품종 분류에 이어서 품종별 특징을 잡아 이들을 그룹화 해보자.


데이터 파악 및 분류

기존 사과 데이터에서 사과 품종 정보를 제외한 주요 특성을 기반으로 지금의 사과 품종과 비슷하게 구분 지을 수 있을 것이다. 보통 외관으로 구분하는 경우가 많은 것이라고 판단하여 이를 홍색과 적색으로 나누어 보았다.

## Loading required package: ggplot2
## Welcome! Want to learn more? See two factoextra-related books at https://goo.gl/ve3WBa
## Loading required package: lattice
## 'data.frame':    25 obs. of  5 variables:
##  $ model : Factor w/ 5 levels "로얄후지","미시마",..: 3 5 3 2 3 4 1 3 4 1 ...
##  $ weight: int  286 256 251 396 282 342 407 238 295 392 ...
##  $ sugar : num  12.9 13.4 12.1 16.3 15 13.4 13.6 13.5 15.1 15.6 ...
##  $ acid  : num  0.31 0.69 0.32 0.39 0.29 0.31 0.4 0.31 0.29 0.38 ...
##  $ color : Factor w/ 2 levels "적색","홍색": 2 1 2 2 2 2 1 2 2 1 ...
## [1] 15
## [1] 10

홍색 사과 15개의 무게, 당도, 산도를 기반으로 그룹을 더 나눌 수 잇는지 확인하기 위해 각 항목 간 산점도를 확인해보자. 무게와 산도의 관계를 보니 무게가 무거우면서 산도가 높은 위치에 특정 사과들이 몰려있는 것을 확인할 수 있다. 그래서 무게가 350g 이상이고 산도가 0.36 이상인 사과들은 별도 그룹으로 구성하고 “홍색시큼사과”라고 품종 이름을 지었다.

“홍색시큼사과”를 제외한 홍색 사과들의 항목 간 산점도를 보니 산도와 당도를 기준으로 사과들을 구분할 수 있어보인다.

산점도를 중심으로 3개 그룹으로 나눠보았다. 막상 나누고 보니 산도가 아닌 당도 기준으로도 나눌 수 있어보였는데 이렇게 당도를 기분으로 나누어봐도 그럴 듯해 보인다. 특성들을 추가해가며 그룹을 만들어갈수록 생각해야 할 내용은 복잡해지고, 설령 나눈다 하더라도 그룹 간의 기준이 모호해 실제 몇 개의 그룹으로 나누는 것이 적당한지도 알 수 없다.


군집분석

  • 관찰된 객체들의 특징들을 기반으로 유사한 개체들을 자동으로 그룹 짓는 방법은 없을까?
  • 그룹을 짓는다면 적절한 그룹의 수는 어떻게 정해질까?

위의 두 궁금증을 해결하기 위해 군집분석을 해보려고 한다. 군집분석은 관찰된 개체들을 사전 지식이 없는 상태에서 유사성을 기준으로 집단을 분류하는 데 사용되는 분석 기법이다. 이미 분류된 결과를 바탕으로 학습을 통해 분류 기준을 밝혀내고 예측하는 분류분석과는 달리, 군집에 대한 사전정보가 없는 상태에서 새로운 군집을 정의하는 것이다. 따라서 이렇게 도출된 군집들에 대해 맞다 틀리다 할 수도 없고 왜 이렇게 분류됐는지 직관적으로 이해하기 어려울 때도 있다. 하지만 기존의 선입견을 떠나 새롭게 분류된 군집 내 개체들 간의 유사점과 군집 간의 차이점을 살펴봄으로써 새로운 통찰을 얻고 적용할 수 있는 기회를 얻을 수 있다.


군집분석 예제

간단한 예를 통해 x와 y가 가지는 12개 개체를 가지고 계층적 군집분석과 비계층적 군집분석을 진행해보자.


1) scale 함수

단위를 표준화하기 위해 scale 함수를 사용해보자.

##                x         y
##  [1,] -1.1338934  0.977290
##  [2,] -0.9449112  0.914239
##  [3,] -0.7559289  0.977290
##  [4,] -1.1338934 -0.914239
##  [5,] -0.9449112 -1.040341
##  [6,] -0.7559289 -0.914239
##  [7,]  0.7559289  0.977290
##  [8,]  0.9449112  0.914239
##  [9,]  1.1338934  0.977290
## [10,]  0.7559289 -0.914239
## [11,]  0.9449112 -1.040341
## [12,]  1.1338934 -0.914239
## attr(,"scaled:center")
##    x    y 
##   16 2450 
## attr(,"scaled:scale")
##           x           y 
##    5.291503 1586.018457
##        x                 y          
##  Min.   :-1.1339   Min.   :-1.0403  
##  1st Qu.:-0.9449   1st Qu.:-0.9142  
##  Median : 0.0000   Median : 0.0000  
##  Mean   : 0.0000   Mean   : 0.0000  
##  3rd Qu.: 0.9449   3rd Qu.: 0.9773  
##  Max.   : 1.1339   Max.   : 0.9773
## [1] 1
## [1] 1

이번에는 각 개체의 분포를 확인하기 위해 표준화된 데이터의 산점도를 그려보도록 하자. 또한 각 ㅐ체가 어디에 위치하는지 표현하기 위해 포인트 대신에 행으로 점 위치를 표시해 보도록 하겠다. 그래프 상에 점 대신 특정 글자로 위치를 표현하고 싶다면 text 함수를 활용한다.


2) hclust 함수

hclust 함수에서 입력 항목으로 받는 거리행렬이란, 각 개체 간의 거리를 행렬로 표현한 객체이다.

##            1         2         3         4         5         6         7
## 2  0.1992228                                                            
## 3  0.3779645 0.1992228                                                  
## 4  1.8915291 1.8382183 1.9289218                                        
## 5  2.0264622 1.9545800 2.0264622 0.2271915                              
## 6  1.9289218 1.8382183 1.8915291 0.3779645 0.2271915                    
## 7  1.8898224 1.7020084 1.5118579 2.6738195 2.6388808 2.4214864          
## 8  2.0797606 1.8898224 1.7020084 2.7685304 2.7187886 2.4972363 0.1992228
## 9  2.2677868 2.0797606 1.8898224 2.9530898 2.8969404 2.6738195 0.3779645
## 10 2.6738195 2.4972363 2.4214864 1.8898224 1.7055084 1.5118579 1.8915291
## 11 2.8969404 2.7187886 2.6388808 2.0826258 1.8898224 1.7055084 2.0264622
## 12 2.9530898 2.7685304 2.6738195 2.2677868 2.0826258 1.8898224 1.9289218
##            8         9        10        11
## 2                                         
## 3                                         
## 4                                         
## 5                                         
## 6                                         
## 7                                         
## 8                                         
## 9  0.1992228                              
## 10 1.8382183 1.9289218                    
## 11 1.9545800 2.0264622 0.2271915          
## 12 1.8382183 1.8915291 0.3779645 0.2271915
## [1] 0.1992228
## [1] 2.95309

생성된 거리행렬을 통해 각 개체 간의 거리를 확인할 수 있다. 예를 들어, 개체 1과 개체 3의 유클리디안 거리는 0.3779645이며, 가장 가까운 개체는 (2,3), 가장 먼 개체는 (1,12)인 것을 알 수 있다. 이렇게 생성한 거리행렬을 기반으로 hclust 함수를 실행해 군집분석을 해보겠다.

## 
## Call:
## hclust(d = ss_scaled_dist)
## 
## Cluster method   : complete 
## Distance         : euclidean 
## Number of objects: 12


덴드로그램을 통해 군집 수에 따라 개체들이 어떻게 군집화되는지 확인할 수 있다. 예를 들어, 두 개의 군집으로 나눈다면 (1,2,3,4,5,6) 과 (7,8,9,10,11,12)로 나눌 수 있다. 또한 그래프 외에 cutree함수를 통해 군집 수에 따른 개체들의 군집 결과를 조회할 수 있다.

##  [1] 1 1 1 1 1 1 2 2 2 2 2 2
##  [1] 1 1 1 1 1 1 2 2 2 3 3 3
##  [1] 1 1 1 2 2 2 3 3 3 4 4 4
##  [1] 1 1 1 2 2 2 3 3 3 4 4 5


cutree 함수를 실행하면 개체 순서별로 해당 개체가 속하게 될 그룹을 숫자로 표현해 준다. 위의 결과를 보면 3개의 군집으로 나눌 때 1~6번째 개체가 1그룹, 7~9번째 개체가 2그룹, 나머지가 3그룹으로 분류된다는 의미이다. 이번에는 계층적 군집분석의 결과를 산점도 상에 색상으로 표현해 군집 수에 따라 개체들이 어떻게 군집을 형성하는지 살펴보도록 하자.


5개 군집으로 나눈 결과를 보면 상식저으로 봐도 적절히 군집을 나눴다고 볼 수는 없다. 따라서 그 군집이 적절히 나눠졌는지에 대한 최종 판단에는 분석자의 통찰이 반드시 필요하다.


3) pam 함수

이번에는 비계층적 군집분석 기법 중 k-medoids 알고리즘을 기반으로 pam(partitioning Around Medoids) 알고리즘으로 군집 분석을 해보겠다.

##       x    y
## [1,] 11 3900
## [2,] 11  800
## [3,] 21 3900
## [4,] 21  800
##  [1] 1 1 1 2 2 2 3 3 3 4 4 4


계층적 군집분석의 결과와 비계층적 군집분석의 결과가 항상 동일하지는 않지만 본 예제의 경우 단순하고 명확하게 데이터가 몰려 이씩 때문에 앞서 살펴본 hclust 함수와 동일한 군집 결과를 도출했다. 이번에는 pam 알고리즘에 의해 도출된 군집을 산점도로 펴현하고 군집의 중심을 + 기호로 표시해 보겠다.


4) fviz_nbclust 함수

적절한 군집 수를 확인할 수 있는 elbow method 와 실루엣 그래프를 그려보도록 하자. 그래프를 보면 군집 수가 늘어날수록 군집 내 편차들이 급속도로 작아지다가 4개부터는 군집 수가 늘어나더라도 군집 내 편차가 거의 줄어들지 않는 것을 확인할 수 있으며 이를 통해 군집 수는 4개가 적절하다고 판단할 수 있다.


실루엣 그래프를 보면 군집 수가 4개일 때 실루엣 지수값이 가장 높다. 즉 앞서 확인한 Elbow Method 결과와 동일하게 군집 수는 4개가 적절한 것으로 판단할 수 있다.


사과 품종 Clustering

예제에서 사용한 방법을 가지고 사과 품종을 도출해보도록 하자. 사과는 5 종류 씩 5개씩 존재하는 것을 확인할 수 있다.

##       model       weight          sugar            acid         color   
##  로얄후지:5   Min.   :152.0   Min.   :12.10   Min.   :0.2800   적색:10  
##  미시마  :5   1st Qu.:251.0   1st Qu.:13.30   1st Qu.:0.3100   홍색:15  
##  아오리  :5   Median :329.0   Median :13.60   Median :0.3800            
##  홍로    :5   Mean   :317.3   Mean   :14.01   Mean   :0.4168            
##  홍옥    :5   3rd Qu.:391.0   3rd Qu.:14.40   3rd Qu.:0.4100            
##               Max.   :409.0   Max.   :16.80   Max.   :0.7300


이제 군집 분석을 통해 새로운 사과 품종을 도출할 것이기 때문에 모델 항목은 분석 데이터에서 제거한다.

##      weight          sugar            acid         color   
##  Min.   :152.0   Min.   :12.10   Min.   :0.2800   적색:10  
##  1st Qu.:251.0   1st Qu.:13.30   1st Qu.:0.3100   홍색:15  
##  Median :329.0   Median :13.60   Median :0.3800            
##  Mean   :317.3   Mean   :14.01   Mean   :0.4168            
##  3rd Qu.:391.0   3rd Qu.:14.40   3rd Qu.:0.4100            
##  Max.   :409.0   Max.   :16.80   Max.   :0.7300


사과 데이터(apple_DF_without_model)의 범주형 데이터 (color)를 포함해 군집분석하기 위해 kproto함수를 활용하겠다. kproto 함수는 내부적으로 표준화 처리를 하기 때문에 실행하기 전에 별도로 표준화 작업을 할 필요은 없다. 먼저 군집 수를 2개로 설정해 kproto 함수를 실행해 보도록 하자.

## # NAs in variables:
## weight  sugar   acid  color 
##      0      0      0      0 
## 0 observation(s) with NAs.
## 
## Estimated lambda: 4050.061


kproto 함수를 통해 나온 군집분석의 겿과를 보면 다음과 같다.

## List of 10
##  $ cluster     : Named int [1:25] 1 2 2 1 1 1 1 2 1 1 ...
##   ..- attr(*, "names")= chr [1:25] "1" "2" "3" "4" ...
##  $ centers     :'data.frame':    2 obs. of  4 variables:
##   ..$ weight: num [1:2] 360 226
##   ..$ sugar : num [1:2] 14.5 13.1
##   ..$ acid  : num [1:2] 0.355 0.547
##   ..$ color : Factor w/ 2 levels "적색","홍색": 2 1
##  $ lambda      : num 4050
##  $ size        : 'table' int [1:2(1d)] 17 8
##   ..- attr(*, "dimnames")=List of 1
##   .. ..$ clusters: chr [1:2] "1" "2"
##  $ withinss    : num [1:2] 54020 19778
##  $ tot.withinss: num 73798
##  $ dists       : num [1:25, 1:2] 5531 14941 11964 1274 6139 ...
##   ..- attr(*, "dimnames")=List of 2
##   .. ..$ : chr [1:25] "1" "2" "3" "4" ...
##   .. ..$ : NULL
##  $ iter        : num 4
##  $ trace       :List of 2
##   ..$ tot.dists: num [1:4] 475586 136747 80084 73798
##   ..$ moved    : int [1:4] 25 4 3 0
##  $ data        :'data.frame':    25 obs. of  4 variables:
##   ..$ weight: int [1:25] 286 256 251 396 282 342 407 238 295 392 ...
##   ..$ sugar : num [1:25] 12.9 13.4 12.1 16.3 15 13.4 13.6 13.5 15.1 15.6 ...
##   ..$ acid  : num [1:25] 0.31 0.69 0.32 0.39 0.29 0.31 0.4 0.31 0.29 0.38 ...
##   ..$ color : Factor w/ 2 levels "적색","홍색": 2 1 2 2 2 2 1 2 2 1 ...
##  - attr(*, "class")= chr "kproto"
##  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 
##  1  2  2  1  1  1  1  2  1  1  1  1  1  2  1  1  1  2  2  2  2  1  1  1  1
## [1] 73798.21


kproto 함수의 결과를 통해 군집을 지정한 결과(cluster), 군집 중심위치(centers), 군집 내 편차 정보(withiness. tot.withinss) 등 군집분석 과정에서 발생한 다양한 정보를 확인할 수 있다. 이제 for 문을 통해 군집 개수를 2 ~ 10개까지 늘려가면서 군집 내 편차(tot.withinss)를 표현해 보고, elbow point를 확인함으로써 적절한 군집 개수를 도출해 보자.

## # NAs in variables:
## weight  sugar   acid  color 
##      0      0      0      0 
## 0 observation(s) with NAs.
## 
## Estimated lambda: 4050.061 
## 
## # NAs in variables:
## weight  sugar   acid  color 
##      0      0      0      0 
## 0 observation(s) with NAs.
## 
## Estimated lambda: 4050.061 
## 
## # NAs in variables:
## weight  sugar   acid  color 
##      0      0      0      0 
## 0 observation(s) with NAs.
## 
## Estimated lambda: 4050.061 
## 
## # NAs in variables:
## weight  sugar   acid  color 
##      0      0      0      0 
## 0 observation(s) with NAs.
## 
## Estimated lambda: 4050.061 
## 
## # NAs in variables:
## weight  sugar   acid  color 
##      0      0      0      0 
## 0 observation(s) with NAs.
## 
## Estimated lambda: 4050.061 
## 
## # NAs in variables:
## weight  sugar   acid  color 
##      0      0      0      0 
## 0 observation(s) with NAs.
## 
## Estimated lambda: 4050.061 
## 
## # NAs in variables:
## weight  sugar   acid  color 
##      0      0      0      0 
## 0 observation(s) with NAs.
## 
## Estimated lambda: 4050.061 
## 
## # NAs in variables:
## weight  sugar   acid  color 
##      0      0      0      0 
## 0 observation(s) with NAs.
## 
## Estimated lambda: 4050.061 
## 
## # NAs in variables:
## weight  sugar   acid  color 
##      0      0      0      0 
## 0 observation(s) with NAs.
## 
## Estimated lambda: 4050.061 
## 
## # NAs in variables:
## weight  sugar   acid  color 
##      0      0      0      0 
## 0 observation(s) with NAs.
## 
## Estimated lambda: 4050.061


사과 군집 수가 4개 이상부터는 군집 수가 늘더라도 군집 내 편차가 크게 줄지 않는 것으로 미뤄 4개 군집합이 적절하다고 판단된다. 이제 사과를 4개 군집으로 나눠 보도록 하자.

## # NAs in variables:
## weight  sugar   acid  color 
##      0      0      0      0 
## 0 observation(s) with NAs.
## 
## Estimated lambda: 4050.061
##  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 
##  3  1  3  4  3  4  2  3  3  2  3  4  4  1  4  2  4  1  1  1  1  4  2  4  2
## 
## 1 2 3 4 
## 6 5 6 8


table 함수를 통해 4개 군집별 사과 개수를 확인해 본 결과 1그룹 5개, 2그룹 8개, 3그룹 1개, 4그룹 11개로 나눠진 것을 확인할 수 있다. 이제 이 결과를 실제 사과의 품종과 비교해 얼마나 일치하는지 확인해보자.

##  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 
##  3  1  3  4  3  4  2  3  3  2  3  4  4  1  4  2  4  1  1  1  1  4  2  4  2
##  [1] 아오리   홍옥     아오리   미시마   아오리   홍로     로얄후지 아오리  
##  [9] 홍로     로얄후지 아오리   홍로     미시마   홍로     홍로     로얄후지
## [17] 미시마   홍옥     홍옥     홍옥     홍옥     미시마   로얄후지 미시마  
## [25] 로얄후지
## Levels: 로얄후지 미시마 아오리 홍로 홍옥
##    
##     로얄후지 미시마 아오리 홍로 홍옥
##   1        0      0      0    1    5
##   2        5      0      0    0    0
##   3        0      0      5    1    0
##   4        0      5      0    3    0


이제 항목별 군집 간 차이를 확인해보자. 이처럼 군집 간의 차이를 항목별로 확인함으로써 군집 특성을 더 쉽게 확인해 볼 수 있다.