캐글데이터를 이용해 클러스터링 기법을 실습해보자.
[Mall Customers 데이터]
200명의 쇼핑몰 고객에 대한 정보 데이터
• 아래의 변수를 포함
• 고객 ID
• 성별
• 나이
• 연간 소득
• 쇼핑 점수
• 쇼핑 행동과 지출 성향을 기반으로 쇼핑몰에서 점수 부여
풀어야 하는 문제
• 주어진 고객 데이터를 바탕으로 고객을 세분화(Customer Segmentation) 군집화
머신 러닝 모델의 입, 출력 정의
• 입력 : 고객 정보 데이터
• 출력 : 클러스터링 결과

군집화를 통해 어떤 문제를 정의하는지가 중요.
하지만 이번 실습은 문제정의 보다는 군집화에 집중!
import pandas as pd
# 데이터 경로 지정 및 읽어오기
data_path = '/content/Mall_Customers.csv'
customers = pd.read_csv(data_path)
# 데이터 꼴 확인
customers.head()
# 기본 정보
print('#'*20, '기본 정보', '#'*20)
customers.info() # info() 안에서 자동으로 print를 진행
# 기초 통계량
summary_statistics = customers.describe(include='all')
print('#'*20, '기초 통계량', '#'*20)
print(summary_statistics)
# 성별 데이터 분포 확인
category_columns = ['Genre']
category_data = customers[category_columns]
import matplotlib.pyplot as plt
plt.figure(figsize=(4, 4))
np.random.seed(seed)
col = (np.random.random(), np.random.random(), np.random.random())
customers['Genre'].value_counts().plot(kind='bar', color=col)
plt.title('Female VS Male')
plt.tight_layout()
plt.show()

쇼핑 데이터이므로 약~간의 치우침이 있지만 큰 차이로 보이지는 않음
즉, 성별 분포는 비슷한 정도의 수준
# 데이터 가져와서
numeric_columns = ['Age', 'Annual Income (k$)', 'Spending Score (1-100)']
numeric_data = customers[numeric_columns]
# 분포 시각화
plt.figure(figsize=(18, 4))
np.random.seed(seed)
for idx, numeric in enumerate(numeric_columns) :
col = (np.random.random(), np.random.random(), np.random.random())
plt.subplot(1, 3, idx+1)
plt.hist(numeric_data[numeric], bins=50, color=col, edgecolor='black')
plt.title(numeric)
plt.tight_layout()
plt.tight_layout()
plt.show()

이제 실제로 아웃라이어를 확인해보자
# 아웃라이어 확인
plt.figure(figsize=(12, 4))
np.random.seed(seed)
for idx, numeric in enumerate(numeric_columns) :
plt.subplot(1, 3, idx+1)
plt.boxplot(numeric_data[numeric].dropna(), labels=[numeric])
plt.tight_layout()
plt.show()

연간 소득 부분에서 하나의 데이터가 아웃라이어로 나왔지만 이는 무시해도 될 수준!
나이, 소득, 쇼핑 점수 각 컬럼들의 상관계수를 보
# 상관관계 메트릭스
correlation_matrix = numeric_data.corr()
# 상관관계 메트릭스 시각화
plt.figure(figsize=(4, 4))
plt.matshow(correlation_matrix, fignum=1)
plt.colorbar()
plt.xticks(range(len(correlation_matrix.columns)), correlation_matrix.columns, rotation=90)
plt.yticks(range(len(correlation_matrix.columns)), correlation_matrix.columns)
for (i, j), val in np.ndenumerate(correlation_matrix):
plt.text(j, i, '{:0.2f}'.format(val), ha='center', va='center', color='black')
plt.show()

결측치는 없음.
성별을 1, 0으로 변경
# 성별을 이진 변수로 변환
encoding_map = {'Female': 1, 'Male': 0}
categori_data_encode = pd.DataFrame(customers['Genre'].replace(encoding_map))
categori_data_encode.columns = ['Gender']
categori_data_encode
수치형 데이터의 크기를 동일한 형태로 스케일링
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
# 수치형 데이터 스케일링
numeric_data = customers[numeric_columns]
numeric_data_scaled = scaler.fit_transform(numeric_data)
numeric_data_scaled = pd.DataFrame(numeric_data_scaled)
numeric_data_scaled.columns = numeric_columns
numeric_data_scaled

이후 해당 데이터를 원래 데이터에 합쳐준다.
customers_combined = pd.concat([numeric_data_scaled,
categori_data_encode],
axis=1)
customers_combined

이제 실제로 군집화를 진행해 보자.
K클러스터 사용 전 일단 최적의 K값을 찾아야 된다.
이를 엘보우 메소드 SSE, 실루엣 방식으로 찾을 것이다.
K값을 통해 나오는 값을 시각화해보았다.
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
inertia, silhouette = [], []
K_range = range(2, 11)
for k in K_range:
kmeans = KMeans(n_clusters=k, init='k-means++', n_init=10, random_state=0)
kmeans.fit(customers_combined)
inertia.append(kmeans.inertia_)
y_kmeans_silhouette = kmeans.predict(customers_combined)
silhouette.append(silhouette_score(customers_combined, y_kmeans_silhouette))
plt.figure(figsize=(8, 10))
plt.subplot(2, 1, 1)
plt.plot(K_range, inertia, label='SSE', marker='o')
plt.xlabel('Number of clusters')
plt.ylabel('Inertia')
plt.title('Elbow Method - SSE')
plt.legend()
plt.subplot(2, 1, 2)
plt.plot(K_range, silhouette, label='Silhouette', marker='o')
plt.xlabel('Number of clusters')
plt.ylabel('Silhouette')
plt.title('Elbow Method - Silhouette')
plt.legend()
plt.show()

최적 K=6으로 선택!
실루엣 계수의 변동이 있는 것은 매우 일반적인 현상
• 데이터 구조의 복잡성
• 잡음과 이상치
• 균일하지 않은 밀도 & 데이터 간 거리
• 실루엣 계수 간 차이가 크지 않다면 작은 K를 기준으로 하는게 좋은 선택
이제 K=6으로 학습을 진행해 보자
kmeans = KMeans(n_clusters=6,
init='k-means++',
n_init=10,
random_state=0)
kmeans.fit(customers_combined)
y_pred = kmeans.predict(customers_combined)
silhouette_avg = silhouette_score(customers_combined, y_pred)
print("SSE Value : {:.2f}".format(kmeans.inertia_))
print("Silhouette Score: {:.2f}".format(silhouette_avg))

실루엣은 양수임으로 나쁘지 않은 결과이다.
하지만 SSE의 경우 181이 나왔는데 이게 좋은지 나쁜지 모르겠다.
즉! 군집화가 잘되었는지 확인하기 위해서는 수치가 아닌 실제로 그려서 판단하는 것이 좋다.
여기서 2차원의 각 컬럼에는 어떤 계산 결과가 들어간다.
어떤 계산 결과란.. 실제 데이터 내부에서 자동으로 나오는 것으로 알 수 없다.
# 우리가 사용한 4차원 데이터를 시각화하기 위해 2차원 데이터로 변환 (t-SNE 알고리즘 활용)
from sklearn.manifold import TSNE
tsne = TSNE(n_components=2, random_state=seed)
customers_tsne = tsne.fit_transform(customers_combined)
2차원에서 나온 두개의 데이터 컬럼명을 ['TSNE1', 'TSNE2']로 줘서 데이터 프레임 생성.
# 2차원으로 변화된 데이터에 feature 이름을 넣어주고
# K-means가 예측한 각 데이터의 클러스터링 인덱스를 제공
customers_tsne_df = pd.DataFrame(data=customers_tsne, columns=['TSNE1', 'TSNE2'])
customers_tsne_df['Cluster'] = y_pred
customers_tsne_df

이후 이를 시각화 한다면?
# 시각화
np.random.seed(seed)
plt.figure(figsize=(10, 8))
for idx in range(kmeans.n_clusters):
_color = (np.random.random(), np.random.random(), np.random.random())
cluster_data = customers_tsne_df[customers_tsne_df['Cluster'] == idx]
plt.scatter(cluster_data['TSNE1'],
cluster_data['TSNE2'],
color=_color,
label=f'Cluster {idx+1}',
marker='o')
plt.title('Customer Segments - t-SNE 2D')
plt.legend()
plt.show()

K=6으로 잘 나눠진 모습을 볼 수 있다.
좀 튀어나간 부분은 고차원 부분에서는 군집화되었다고 볼 수 있다.
와~ 군집화 끝! 에서 끝나면 안된다.
이 군집화를 통해 새로운 인사이트를 찾아내야 된다!!!!!!!!
아래 데이터 프레임을 기준으로 어떤 인사이트를 도출해 낼 수 있을지 분석해보자

군집화레벨 데이터가 있는 클러스터컬럼을 추가해준다.
# 원래 데이터에서 재건하기
customers_combined['Cluster'] = kmeans.labels_
# 수치형 데이터 스케일링 작업을 역으로 수행하기
original_numeric_data = pd.DataFrame(scaler.inverse_transform(customers_combined[numeric_columns]))
original_numeric_data.columns = numeric_columns
# 범주형 데이터 역 인코딩 (필요시)
# 수치형으로 되어있던 데이터를 다시 범주형으로 바꾸면 특성 파악이 눈에 안들어올 수 있음!
reverse_encoding_map = {v: k for k, v in encoding_map.items()}
original_category_data = pd.DataFrame(customers_combined['Gender'].replace(reverse_encoding_map))
original_category_data.columns = category_columns
# pandas로 원래 데이터 다시 만들기
labeled_origin_date = pd.concat([customers_combined['Cluster'],
original_numeric_data,
customers_combined['Gender']],
# original_category_data], # 범주형 데이터 필요시 사용
axis=1)
labeled_origin_date

# 각 군집에 대한 기술통계 계산
cluster_description = labeled_origin_date.groupby('Cluster').describe().transpose()
# 군집별 평균값
cluster_means = labeled_origin_date.groupby('Cluster').mean().transpose()

각 클러스터별로 나이, 수익 등등 을 확인해보면서 어떤 기준으로 군집화가 되었는지 확인해볼 수 있다.
일반적으로는 만들어진 클러스터가 어떤 의미가 있는지 도메인 지식을 이용해 추측해야 함
원래 데이터의 형태로 전처리의 역과정을 거쳐 데이터의 재건한 뒤
각 클러스터에 포함된 데이터의 의미를 확인해야 함
• 이때 정해진 방법이 있는 건 아니다.
• 간단하게 기본 정보 혹은 기술 통계를 보거나
• 상관관계 분석, 다른 머신 러닝 모델 생성 등의 과정이 필요하다.