Unsupervised Learning : K-means Clustering using Python (Case : Online Retail Dataset)
Analisis Cluster atau Clustering merupakan salah satu metode pada analisis multivariat, yang memiliki tujuan untuk mengelompokkan objek-objek berdasarkan karakteristik yang dimilikinya.
Analisis cluster akan mengelompokkan individu atau objek penelitian, sehingga setiap objek yang paling dekat kesamaannya dengan objek lain berada dalam cluster yang sama [1].
K-Means Clustering
K-Means merupakan salah satu algoritma clustering, dimana pada algoritma ini, komputer akan mengelompokkan sendiri data-data yang menjadi masukannya tanpa mengetahui terlebih dulu target kelasnya[2].
Pembelajaran ini termasuk dalam Unsupervised Learning. Input yang diterima berupa data (objek) dan k buah kelompok (cluster) yang diinginkan. Algoritma ini akan mengelompokkan data (objek) ke dalam k buah kelompok tersebut. Pada setiap cluster terdapat titik pusat (centroid) yang merepresentasikan cluster tersebut [2].
Algoritma K-Means Clustering
Berikut ini langkah-langkah dari algoritma untuk melakukan K-Means clustering [3]:
- Pilih K buah titik centroid secara acak.
- Kelompokkan data sehingga terbentuk K buah cluster dengan titik centroid dari setiap cluster merupakan titik centroid yang telah dipilih sebelumnnya.
- Perbaharui nilai titik centroid.
- Ulangi langkah 2 dan 3 sampai nilai dari titik centroid tidak lagi berubah.
Proses pengelompokkan data ke dalam suatu cluster dapat dilakukan dengan cara menghitung jarak terdekat dari suatu data ke sebuah titik centroid. Rumus untuk menghitung jarak tersebut adalah [4] :
dengan:
Sedangkah untuk pembaharuan suatu titik centroid dapat dilakukan dengan rumus berikut [4]:
The Business Goal
Tujuan dari penyelesaian permasalahan ini, yaitu untuk melakukan segmentasi customer berdasarkan RFM (Recency, Frequency dan Monetary), sehingga perusahaan dapat menargetkan segmentasi customer secara lebih efisien.
Berikut ini, merupakan langkah-langkah untuk menyelesaikan permasalahan segmentasi customer pada dataset OnlineRetail menggunakan K-Means Clustering:
Step 1 : Importing the Library
Hal pertama yang harus dilakukan adalah meng-import beberapa Library Python untuk kebutuhan dataframe, visualisasi dan clustering, dengan langkah-langkah sebagai berikut:
# Library untuk dataframe dan visualisasi
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import datetime as dt# Import library untuk Clustering
import sklearn
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
Step 2 : Reading and Understanding the Data
Langkah selanjutnya, yaitu meng-input dataset. Dataset yang akan digunakan pada tulisan ini adalah dataset OnlineRetail. Klik disini untuk mengakses dataset tersebut. Dataset OnlineRetail merupakan sekumpulan data transaksional dari toko-toko online/retail di UK yang terdaftar di suatu perusahaan retail online, dengan rentang waktu periode 1 Desember 2010 sampai dengan tanggal 9 Desember 2011. Berikut adalah langkah untuk meng-input dataset dan melihat detail informasi tentang dataset:
# Load dataset OnlineRetail
df = pd.read_csv('OnlineRetail.csv', sep=",", encoding="ISO-8859-1", header=0)
df.head()
Berdasarkan output diatas, dapat disimpulkan bahwa dataset tersebut memiliki 8 atribut, diantaranya: InvoiceNo, StockCode, Description, Quantity, InvoiceDate, UnitPrice, CustomerID dan Country. Untuk detail info dan statistika deskriptif terkait dataset tersebut dapat diperoleh pada output-output dibawah ini:
df.info()
df.describe()
Step 2 : Data Cleansing
Langkah selanjutnya yaitu akan dilihat komposisi persentase missing value yang terdapat pada dataset, dengan langkah sebagai berikut:
df_null = round(100*(df.isnull().sum())/len(df), 2)
df_null
Berdasarkan output tersebut, dapat disimpulkan bahwa masih terdapat beberapa atribut yang memiliki missing value. Oleh karena itu, akan dilakukan penghapusan terhadap baris-baris yang mengandung missing value, dengan langkah sebagai berikut:
Pada output df.info(), diperoleh informasi bahwa atribut CustomerID memiliki tipe data float64, selanjutnya akan dilakukan pengubahan tipe data pada atribut tersebut menjadi str, dengan langkah sebagai berikut:
df['CustomerID'] = df.CustomerID.astype(str)
Step 3 : Data Preparation
Seperti yang telah disebutkan sebelumnya bahwa, tujuan analisis yang dilakukan pada case ini adalah untuk memperoleh segmentasi dari atribut Customers, yang didasarkan pada 3 faktor berikut:
- R (Recency) : Jumlah hari sejak pembelian terakhir
- F (Frequency) : Jumlah ‘proses transaksi’
- M (Monetary) : Jumlah total transaksi (kontribusi pendapatan)
Maka, langkah pertama yang harus dilakukan adalah membuat atribut baru “Monetary” dengan detail dan perhitungan sebagai berikut:
# Membuat atribut baru : Monetary
df['Monetary'] = df['Quantity']*df['UnitPrice']
rfm_m = df.groupby('CustomerID')['Monetary'].sum()
rfm_m = rfm_m.reset_index()
rfm_m.head()
Langkah selanjutnya, membuat atribut baru “Frequency” dengan detail dan perhitungan sebagai berikut:
# Membuat atribut baru : Frequency
rfm_f = df.groupby('CustomerID')['InvoiceNo'].count()
rfm_f = rfm_f.reset_index()
rfm_f.columns = ['CustomerID', 'Frequency']
rfm_f.head()
Kemudian, akan dilakukan merging (penggabungan) antara dataframe rfm_m dan rmf_f, sebagai berikut:
# Menggabungkan (merging) dua dataframe
rfm = pd.merge(rfm_m, rfm_f, on='CustomerID', how='inner')
rfm.head()
Langkah selanjutnya, yaitu membuat atribut baru “Recency” dengan cara meng-convert tipe data atribut “InvoiceDate” menjadi datetime terlebih dahulu, dengan langkah sebagai berikut:
# Membuat atribut baru : Recency
df['InvoiceDate'] = pd.to_datetime(df['InvoiceDate'],format='%d-%m-%Y %H:%M')
Kemudian, akan ditampilkan tanggal maksimal, untuk mengetahui tanggal terakhir kali melakukan transaksi :
Selanjutnya akan dihitung selisih nilai antara tanggal terakhir melakukan transaksi (max_date) dengan masing-masing tanggal yang terdapat di atribut “InvoiceDate”. Dimisalkan nilai selisih tersebut didefinisikan sebagai atribut baru “Diff”:
# Menghitung selisih antara max_date dengan InvoiceDate
df['Diff'] = max_date - df['InvoiceDate']
df.head()
Selanjutnya, akan dihitung waktu terakhir customer melakukan transaksi guna mendefinisikan atribut Recency atau jumlah hari sejak pembelian terakhir.
# Menghitung the last transaction date untuk atribut Recencyrfm_p = df.groupby('CustomerID')['Diff'].min()
rfm_p = rfm_p.reset_index()
rfm_p.head()
Oleh karena, pada atribut Recency hanya dibutuhkan data terkait jumlah hari saja, maka selanjutnya akan dilakukan ekstraksi, atau hanya akan diambil data keterangan hari saja.
# Extract jumlah hari
rfm_p['Diff'] = rfm_p['Diff'].dt.days
rfm_p.head()
Setelah 3 atribut baru yaitu : Recency (dataframe : rfm_p), Frequency (dataframe : rfm_f) dan Monetary (dataframe : rfm_m) terbentuk, maka langkah selanjutnya yaitu menggabungkan ke-tiga atribut/ kolom tersebut menjadi RFM dataframe, dengan langkah sebagai berikut:
# Menggabungkan dataframe
rfm = pd.merge(rfm, rfm_p, on='CustomerID', how='inner')
rfm.columns = ['CustomerID', 'Amount', 'Frequency', 'Recency']
rfm.head()
Langkah selanjutnya akan dilakukan analisis terhadap outlier menggunakan boxplot, dengan langkah sebagai berikut:
# Outlier Analysis of Monetary, Frequency and Recencyattributes = ['Monetary','Frequency','Recency']
plt.rcParams['figure.figsize'] = [10,8]
sns.boxplot(data = rfm[attributes], orient="v", palette="Set2" ,whis=1.5,saturation=1, width=0.7)
plt.title("Outliers Variable Distribution", fontsize = 14, fontweight = 'bold')
plt.ylabel("Range", fontweight = 'bold')
plt.xlabel("Attributes", fontweight = 'bold')
Boxplot merupakan ringkasan distribusi sampel yang disajikan secara grafis sebagai penggambaran bentuk distribusi data, ukuran tendensi sentral dan ukuran penyebaran (keragaman) data pengamatan. Selain itu boxplot juga bisa menunjukkan ada tidaknya nilai outlier dan nilai ekstrem di data pengamatan.
Nilai Outlier merupakan nilai data yang letaknya lebih dari 1.5 x panjang kotak (IQR), dengan formulasi detail sebagai berikut:
Untuk langkah selanjutnya, akan dilakukan penghapusan terhadap outliers yang ada, dengan langkah sebagai berikut:
# Removing (statistical) outliers for Monetary
Q1 = rfm.Monetary.quantile(0.05)
Q3 = rfm.Monetary.quantile(0.95)
IQR = Q3 - Q1
rfm = rfm[(rfm.Monetary >= Q1 - 1.5*IQR) & (rfm.Monetary <= Q3 + 1.5*IQR)]# Removing (statistical) outliers for Recency
Q1 = rfm.Recency.quantile(0.05)
Q3 = rfm.Recency.quantile(0.95)
IQR = Q3 - Q1
rfm = rfm[(rfm.Recency >= Q1 - 1.5*IQR) & (rfm.Recency <= Q3 + 1.5*IQR)]# Removing (statistical) outliers for Frequency
Q1 = rfm.Frequency.quantile(0.05)
Q3 = rfm.Frequency.quantile(0.95)
IQR = Q3 - Q1
rfm = rfm[(rfm.Frequency >= Q1 - 1.5*IQR) & (rfm.Frequency <= Q3 + 1.5*IQR)]
Tahap selanjutnya yaitu, akan dilakukan rescaling terhadap atribut-atribut yang ada. Rescaling atribut merupakan salah satu tahap yang penting agar masing-masing atribut memiliki skala yang sebanding. Pada case ini, akan digunakan Standardisation Scaling terhadap masing-masing atribut, dengan langkah sebagai berikut:
# Rescaling Atribute
rfm_df = rfm[['Monetary', 'Frequency', 'Recency']]# Instantiate
scaler = StandardScaler()# fit_transform
rfm_df_scaled = scaler.fit_transform(rfm_df)
rfm_df_scaled.shape
rfm_df_scaled = pd.DataFrame(rfm_df_scaled)
rfm_df_scaled.columns = ['Amount', 'Frequency', 'Recency']
rfm_df_scaled.head()
Output diatas merupakan hasil dari Standardisation Scaling yang telah dilakukan.
Step 4 : Model Building
Langkah selanjutnya, yaitu menentukan nilai K. Pada langkah ini, akan dikonfigurasi dan ditentukan nilai inisiasi k (n_clusters) sebesar 4 cluster.
kmeans = KMeans(n_clusters=4, max_iter=50)
kmeans.fit(rfm_df_scaled)
Selanjutnya, akan dicari jumlah optimal dari cluster (k) dengan menggunakan metode Elbow Curve.
Elbow Curve, merupakan salah satu metode yang bisa digunakan untuk menemukan jumlah optimal dari cluster (k), yang langkah-langkah pengerjaan adalah sebagai berikut:
# Elbow-curve/SSD
ssd = []
range_n_clusters = [2, 3, 4, 5, 6, 7, 8]
for num_clusters in range_n_clusters:
kmeans = KMeans(n_clusters=num_clusters, max_iter=50)
kmeans.fit(rfm_df_scaled)
ssd.append(kmeans.inertia_)
# plot the SSDs for each n_clusters
plt.plot(ssd)
Pada elbow curve method, lokasi ‘tikungan’ yang terbentuk di plot, pada umumnya dianggap sebagai indikator jumlah cluster yang tepat [5].
Akan tetapi nilai k ‘optimal’ yang diperoleh dari metode elbow curve, sering kali bersifat “ambigu” atau belum pasti akan menghasilkan jumlah cluster (k) yang optimal.
Oleh karena itu, langkah selanjutnya akan digunakan Silhouette Analysis guna mencari nilai k optimal.
Silhouette Analysis
Secara umum, algoritma dari Silhouette analysis yaitu mengukur seberapa dekat (baik) setiap titik pada sebuah cluster dengan titik-titik data lain di clusternya. Semakin tinggi nilai rata-rata dari silhouette, menunjukkan suatu peng-cluster-an yang baik. Secara matematis, dapat dirumuskan sebagai berikut:
dengan :
p : jarak rata-rata, ke titik-titik di cluster terdekat
q : jarak rata-rata intra cluster ke semua titik di clusternya sendiri
Nilai dari Sillhouette ada diantara -1 sampai dengan 1. Jika nilainya mendekati angka 1, maka titik data akan sangat mirip dengan titik data lainnya di cluster yang sama. Jika mendekati -1 maka titik data tersebut tidak mirip dengan titik data di klusternya.
# Silhouette Analysis
range_n_clusters = [2, 3, 4, 5, 6, 7, 8]
for num_clusters in range_n_clusters:
# Initialise kmeans
kmeans = KMeans(n_clusters=num_clusters, max_iter=50)
kmeans.fit(rfm_df_szcaled)
cluster_labels = kmeans.labels_
# Silhouette Score
silhouette_avg = silhouette_score(rfm_df_scaled, cluster_labels)
print("For n_clusters={0}, the silhouette score is {1}".format(num_clusters, silhouette_avg))
Berdasarkan output tersebut, dapat disimpulkan bahwa untuk n_clusters=2 menghasilkan nilai silhouette yang tinggi.
# Final model with k=2
kmeans = KMeans(n_clusters=2, max_iter=50)
kmeans.fit(rfm_df_scaled)
Langkah selanjutnya yaitu menetapkan label cluster untuk setiap titik data sebagai berikut:
# Assign the label
rfm['Cluster_Id'] = kmeans.labels_
rfm.head()
# Boxplot untuk memvisualisasikan Cluster Id dan Monetary
sns.boxplot(x='Cluster_Id', y='Monetary', data=rfm)
# Boxplot untuk memvisualisasikan Cluster Id vs Frequency
sns.boxplot(x='Cluster_Id', y='Frequency', data=rfm)
# Boxplot untuk memvisualisasikan Cluster Id vs Recency
sns.boxplot(x='Cluster_Id', y='Recency', data=rfm)
Step 5 : Final Analysis
Berdasarkan langkah-langkah pengerjaan yang telah dilakukan, dapat disimpulkan bahwa :
- Customers dengan Cluster Id 0 merupakan customer dengan jumlah total transaksi tinggi dibandingkan dengan customer lain.
- Customers dengan Cluster Id 0 merupakan customer yang paling sering melakukan transaksi dibandingkan dengan customer di cluster lain
Kelebihan K-Means Clustering :
Terdapat beberapa kelebihan K-Means clustering, diantaranya [2]:
1. Mudah untuk diimplementasikan dan dijalankan.
2. Waktu yang dibutuhkan untuk menjalankan pembelajaran ini relatif cepat.
3. Mudah untuk diadaptasi.
4. Umum digunakan.
Kekurangan K-Means Clustering :
Sedangkan beberapa kekurangan dari K-Means clustering, adalah sebagai berikut:
1. Sebelum algoritma dijalankan, k buah titik diinisialisasi secara random sehingga pengelompokkan data yang dihasilkan berbeda-beda.
2. Terdapat kemungkinan terjebak pada masalah yang disebut curse of dimensionality.
3. Jika terdapat banyak sekali titik data, maka perhitungan dan pencarian titik terdekat akan membutuhkan waktu yang lama.
Referensi :
[1] Usman & Sobari. (2013). Aplikasi Teknik Multivariate. Jakarta: Rajawali Pers
[2] id.wikipedia.org/wiki/K-means#cite_ref-:0
[3] P.-N. Tan, M. Steinbach, and V. Kumar, Introduction to Data Mining, (First Edition). Boston, MA, USA: Addison-Wesley Longman Publishing Co., Inc., 2005.
[4] O. Maimon and L. Rokach, Data Mining and Knowledge Discovery Handbo- ok. Secaucus, NJ, USA: Springer-Verlag New York, Inc., 2005.
[5] www.datanovia.com/en/lessons/determining-the-optimal-number-of-clusters-3-must-know-methods/