## 데이터의 기초 통계량 계산 및 시각화
import time
from scipy import stats
# 데이터 구조 정의
# 사용자 ID를 키로 갖고 상품 코드의 셋을 값으로 갖는 딕셔너리와
# 상품 코드를 키로 갖고 사용자 ID의 셋을 값으로 갖는 딕셔너리
user_product_dic = {}
product_user_dic = {}
# 상품 코드를 키로 갖고 상품명을 값으로 갖는 딕셔너리
# 군집화의 내용을 확인하는 단계에서 상품명을 사용합니다.
product_id_name_dic = {}
# 파일을 읽어 위에서 정의한 데이터 구조를 채웁니다.
# 파일은 여기에서...
# https://archive.ics.uci.edu/ml/machine-learning-databases/00352/
for line in open('D:/99_Study/FirstML\source/10_UserGroup/online_retail_utf.txt'):
# 데이터를 한 행씩 읽어 필요한 항목을 저장합니다.
line_items = line.strip().split('\t')
user_code = line_items[6]
product_id = line_items[1]
product_name = line_items[2]
# 사용자 ID가 없을 경우 무시합니다.
if len(user_code) == 0:
continue
# 영국에서 구매한 사용자만 고려하므로 국가가 united kingdom이 아닌 경우엔 무시합니다.
country = line_items[7]
if country != 'United Kingdom':
continue
# 연도를 읽을 때 에러 처리. 파일 헤더를 무시합니다.
try:
# invoice_year = time.strptime(line_items[4], '%m/%d/%y %H:%M').tm_year
invoice_year = time.strptime(line_items[4], '%Y-%m-%d %H:%M').tm_year
except ValueError:
continue
# 2011년에 일어난 구매가 아닌 것은 무시합니다.
if invoice_year != 2011:
continue
# 읽은 정보로 데이터 구조를 채웁니다.
# 상품 가짓수를 고려하므로 상품 코드를 셋으로 가지도록 하겠습니다.
user_product_dic.setdefault(user_code, set())
user_product_dic[user_code].add(product_id)
product_user_dic.setdefault(product_id, set())
product_user_dic[product_id].add(user_code)
product_id_name_dic[product_id] = product_name
# 데이터 구조를 다 채웠으므로 각 사용자가 구매한 상품 가짓수로 리스트를 만듭니다.
product_per_user_li = [len(x) for x in user_product_dic.values()]
# 이 장에서 사용할 최종 사용자 수와 상품 가짓수를 출력합니다.
print('# of users:', len(user_product_dic))
print('# of products:', len(product_user_dic))
# 각 사용자가 구매한 상품 가짓수로 기초 통계량을 출력합니다.
print(stats.describe(product_per_user_li))
## 사용자가 구매한 상품 가짓수
from collections import Counter
import matplotlib.pyplot as plt
# 한글 폰트 사용을 위해서 세팅
from matplotlib import font_manager, rc
font_path = "C:/Windows/Fonts/NGULIM.TTF"
font = font_manager.FontProperties(fname=font_path).get_name()
rc('font', family=font)
# 사용자가 구매한 고유 상품 가짓수를 플롯해봅니다.
plot_data_all = Counter(product_per_user_li)
plot_data_x = list(plot_data_all.keys())
plot_data_y = list(plot_data_all.values())
plt.xlabel('고유 상품 가짓수')
plt.ylabel('사용자 수')
plt.scatter(plot_data_x, plot_data_y, marker='+')
plt.show()
## 예외적인 구매 패턴을 보이는 사용자 제거하기
# 구매한 상품의 가짓수가 1인 사용자의 사용자 ID를 찾습니다.
min_product_user_li = [k for k,v in user_product_dic.items() if len(v)==1]
# 마찬가지로 구매한 상품의 가짓수가 600개 이상인 사용자의 사용자 ID를 찾습니다.
max_product_user_li = [k for k,v in user_product_dic.items() if len(v)>=600]
print('# of users purchased one product : %d' % (len(min_product_user_li)))
print('# of users purchased more than 600 product : %d' % (len(max_product_user_li)))
# 찾아낸 사용자를 군집화에 사용할 user_product_dic에서 제외합니다.
user_product_dic = {k:v for k,v in user_product_dic.items() if len(v)>1 and len(v)<=600}
print('# of left user : %d' % (len(user_product_dic)))
# 구매한 상품별 갯수를 딕셔너리로 저장
id_product_dic = {}
for product_set_li in user_product_dic.values():
for x in product_set_li:
if x in id_product_dic:
product_id = id_product_dic[x]
else:
id_product_dic.setdefault(x, len(id_product_dic))
print("# of left items : %d" % (len(id_product_dic)))
## 원-핫 인코딩을 이용한 피처 생성
# 사용자 ID 참조를 위한 딕셔너리
id_user_dic = {}
# 군집화의 입력으로 사용할 리스트
user_product_vec_li = []
# 군집화에서 사용할 총 고유 상품 가짓수. 즉, 원-핫 인코딩으로 변환할 피처의 가짓수
all_product_count = len(id_product_dic)
for user_code, product_per_user_set in user_product_dic.items():
# 고유 상품 가짓수를 길이로 하는 리스트 생성 (초기값=0)
user_product_vec = [0] * all_product_count
# id_user_dic의 길이를 이용하여 사용자 ID를 0부터 시작하는 user_id로 바꿉니다.
id_user_dic[len(id_user_dic)] = user_code
# 사용자가 구매한 상품 코드를 키로 하여 user_product_vec에서의
# 해당 상품 코드의 상품 ID를 찾습니다. 그리고 값을 1로 셋팅합니다.
for product_id in product_per_user_set:
user_product_vec[id_product_dic[product_id]] = 1
# 한 사용자의 처리가 끝났으므로 이 사용자의 user_product_vec을 배열에 추가합니다.
# 이때 배열의 인덱스는 새로 정의한 user_id가 됩니다.
user_product_vec_li.append(user_product_vec)
# print(id_user_dic[0])
# print(user_product_dic[id_user_dic[0]])
# print(user_product_vec_li[0])
# print(len(user_product_vec_li[0]))
### K-Means 군집화
## 사이킷런의 predict 함수를 이용하여 사용자가 속할 클러스터 예측
from sklearn.cluster import KMeans
import random
# 학습용과 평가용 데이터로 나누기 위해 사용자-상품 벡터를 사용합니다.
random.shuffle(user_product_vec_li)
# 학습용 데이터에 사용자 2500명을, 평가용 데이터에 나머지 사용자를 넣습니다.
# 학습용 데이터에 있는 사용자 정보만을 가지고 클러스터를 만든 후
# 평가용 데이터의 사용자가 어느 클러스터에 속하는지 알아봅니다.
train_data = user_product_vec_li[:2500]
test_data = user_product_vec_li[2500:]
print("# of train data:%d, # of test data:%d" % (len(train_data),len(test_data)))
# 학습 데이터를 군집화하여 4개의 클러스터를 생성한 후, 그 결과를 km_predict에 저장합니다.
km_predict = KMeans(n_clusters=4, init='k-means++', n_init=10, max_iter=20).fit(train_data)
# km_predict의 predict 함수를 이용하여 평가 데이터가 전 단계에서 만든 4개의 클러스터 중 어느 곳에
# 속하는지 살펴봅니다.
km_predict_result = km_predict.predict(test_data)
print(km_predict_result)
## 사이킷런을 이용하여 실루엣 계수 구하기
from sklearn.metrics import silhouette_score
import numpy as np
test_data = np.array(user_product_vec_li)
for k in range(2, 9):
km = KMeans(n_clusters=k).fit(test_data)
print("score for %d clusters:%.3f" % (k, silhouette_score(test_data, km.labels_)))
## 사이킷런을 이용하여 클러스터 수 K에 따라 달라지는 급내제곱합 구하기 (엘보 방법)
# 클러스터 수를 키로 하고 inertia를 값으로 하는 딕셔너리입니다.
ssw_dic = {}
# 클러스터 수 K를 1부터 8까지 바꾸어가며 급내제곱합의 평균값을 계산하고,
# K를 키로 지정하여 딕셔너리에 넣습니다.
for k in range(1, 8):
km = KMeans(n_clusters=k).fit(test_data)
ssw_dic[k] = km.inertia_
# 클러스터 수 K를 x축으로, inertia를 y축으로 하여 플롯을 그립니다.
plot_data_x = list(ssw_dic.keys())
plot_data_y = list(ssw_dic.values())
plt.xlabel("# of clusters")
plt.ylabel("withis ss")
plt.plot(plot_data_x, plot_data_y, linestyle="-", marker='o')
plt.show()
## 클러스터에 속한 사용자가 구매한 상품명에 나타나는 키워드의 빈도 구하기
def analyze_clusters_keywords(labels, product_id_name_dic, user_product_dic, id_user_dic):
# 각 클러스터의 ID와 클러스터에 들어있는 사용자 수를 출력합니다.
print(Counter(labels))
cluster_item = {}
for i in range(len(labels)):
cluster_item.setdefault(labels[i], [])
# 각 사용자의 임시 ID i에 대해 사용자 코드를 찾은 후
# 그 사용자 코드와 연결된 구매상품의 ID를 참조한 후
# 그 ID를 이용해 상품명을 찾아
# 딕셔너리에 클러스터 ID를 키로, 상품명을 값으로 추가합니다.
for x in user_product_dic[id_user_dic[i]]:
cluster_item[labels[i]].extend([product_id_name_dic[x]])
for cluster_id, product_name in cluster_item.items():
# 각 클러스터 안의 상품명을 join 명령으로 합쳐 하나의 문자열로 만든 뒤
# OF를 공백으로 replace하고
# 스페이스 혹은 탭으로 split하여 키워드로 분해한 뒤
# 연속되는 두 키워드를 합쳐서 하나의 키워드로 만듭니다.
bigram = []
product_name_keyword = (' ').join(product_name).replace(' OF ', ' ').split()
for i in range(0, len(product_name_keyword) - 1):
bigram.append(' '.join(product_name_keyword[i:i+2]))
# 클러스터의 ID와 그 ID를 가지는 클러스터에 속한 사용자들이
# 구매한 상품의 상품명 안에서 가장 자주 나타나는 단어 20개를 빈도순으로 출력합니다.
print('cluster_id : ', cluster_id)
# print(Counter(product_name_keyword).most_common(20))
print(Counter(bigram).most_common(20))
km = KMeans(n_clusters=2, n_init=10, max_iter=20)
km.fit(test_data)
analyze_clusters_keywords(km.labels_, product_id_name_dic, user_product_dic, id_user_dic)
## 각 클러스터의 사용자가 구입한 고유 상품 가짓수의 기초 통계량 구하기
def analyze_clusters_product_count(labels, user_product_dic, id_user_dic):
product_len_dic = {}
for i in range(0, len(labels)):
product_len_dic.setdefault(labels[i], [])
# 클러스터의 ID를 키로 하는 딕셔너리에
# 그 클러스터에 속한 사용자가 구매한 고유 상품의 가짓수를 저장합니다.
product_len_dic[labels[i]].append(len(user_product_dic[id_user_dic[i]]))
for k, v in product_len_dic.items():
print('cluster : ', k)
print(stats.describe(v))
analyze_clusters_product_count(km.labels_, user_product_dic, id_user_dic)
### 계층적 군집화
## scipy를 이용한 집괴적 군집화
from scipy.cluster.hierarchy import linkage
from scipy.cluster.hierarchy import dendrogram
# scipy의 집괴적 군집화 함수
# 이번에는 두 클러스터에 속한 모든 샘플 간의 거리 평균을
# 클러스터를 집괴하는 기준으로 합니다.
# 거리 함수로는 유클리디안 함수를 씁니다.
row_clusters = linkage(test_data, method='complete', metric='euclidean')
# 사용자 ID를 사용자 코드로 반환합니다.
tmp_label=[]
for i in range(len(id_user_dic)):
tmp_label.append(id_user_dic[i])
# 플롯을 그립니다.
row_denr = dendrogram(row_clusters, labels=tmp_label)
# =============================================================================
# File "C:\Users\pc1\anaconda3\lib\site-packages\scipy\cluster\hierarchy.py", line 3290, in dendrogram
# raise ValueError("Dimensions of Z and labels must be consistent.")
#
# ValueError: Dimensions of Z and labels must be consistent.
#
# =====> 버그인 듯 싶어 hierarchy.py 직접 수정.... (주석 처리)
# =============================================================================
plt.tight_layout()
plt.ylabel('euclid')
plt.show()
# ---> 플롯이 따로 그려지는데... ㅡㅡ
## 샘플 데이터를 이용한 집괴적 군집화와 계통 트리 만들기 for 계통트리 설명
small_test_data = np.array(random.sample(user_product_vec_li, 10))
small_row_clusters = linkage(small_test_data, method="complete", metric="euclidean")
plt.figure(figsize=(25,10))
row_denr = dendrogram(small_row_clusters, labels=list(range(len(small_test_data))), leaf_font_size=20.)
plt.ylabel('euclid')
plt.show()
## 사이킷런을 이용한 집괴적 군집화
from sklearn.cluster import AgglomerativeClustering
ward = AgglomerativeClustering(n_clusters=2, affinity='euclidean', linkage='ward')
ward.fit(test_data)
## 집괴적 군집화로 생성된 클러스터 내부의 상품명 키워드 살펴보기
analyze_clusters_keywords(ward.labels_, product_id_name_dic, user_product_dic, id_user_dic)
출처 : 처음 배우는 머신러닝 : 기초부터 모델링, 실전 예제, 문제 해결까지
'데이터분석 > Python' 카테고리의 다른 글
머신러닝 Example by Python - 이미지 데이터를 이용한 K-평균 군집화 (이미지 인식 시스템) (0) | 2022.01.14 |
---|---|
머신러닝 Example by Python - 고유명사 태깅 시스템 만들기 (문서 분류) (0) | 2022.01.14 |
머신러닝 Example by Python - 품사 분석 시스템 만들기 (문서 분류) (0) | 2022.01.14 |
머신러닝 Example by Python - 토픽 모델 시스템 만들기 (문서 분류) (0) | 2022.01.14 |
머신러닝 Example by Python - 스팸 문자 필터 만들기 (문서 분류) (0) | 2022.01.14 |