Введение

Кластеризация может быть особенно полезной отправной точкой для начала пути машинного обучения, особенно когда метки данных еще не созданы. Один из самых простых способов начать — использовать кластеризацию, основанную на покупательском поведении клиентов или отделов. Это можно сделать с любыми наборами данных, которые содержат данные о продажах на основе категорий продуктов. На основе результатов кластеризации клиенты могут быть сгруппированы в значимые кластеры для дальнейшего анализа. Кроме того, выбросы могут быть обнаружены для кластеров с небольшим количеством клиентов внутри.

KMeans и иерархическая кластеризация — два наиболее распространенных и популярных метода, используемых сегодня. В этой статье я хотел бы более подробно рассмотреть сквозной процесс от предварительной обработки данных до постобработки результатов кластеризации и выделить некоторые различия и полезность двух методов, основанных на моем опыте их развертывания в проектах.

Ссылки на код и данные

Стандартная предварительная обработка данных

Набор данных содержит 9994 строки и 21 столбец. Обзор набора данных показан ниже:

Чтобы выполнить первоначальный кластерный анализ, мы будем использовать только категории «Клиент», «Подкатегория» и «Продажи». Сначала мы преобразуем данные, выполнив операцию Pandas pivot_table, чтобы мы могли получить сумму, потраченную каждым клиентом на подкатегории.

df_key_fields = df_raw[["Customer ID", "Sub-Category", "Sales"]].copy()
df_cust_sales = df_key_fields.pivot_table(
    values="Sales", columns="Sub-Category", aggfunc=np.sum, index="Customer ID"
).reset_index()

После операции pivot_table образец вывода показан ниже:

Во многих случаях могут быть некоторые клиенты, которые тратят непропорционально большую сумму по сравнению с большинством других клиентов, что приводит к искажению результатов. Чтобы предотвратить это, мы обычно применяем логарифмическое преобразование к сумме продаж, чтобы стабилизировать дисперсию переменной и сделать связь между переменной и целевой переменной более линейной.

df_cust_sales.iloc[:, 1:] = df_cust_sales.iloc[:, 1:].applymap(lambda x: np.log(x + 1))

Предварительная обработка PCA (анализ основных компонентов) перед KMeans Clustering

Обычно рекомендуется выполнять АПК (анализ основных компонентов) перед кластеризацией KMeans, поскольку АПК может помочь уменьшить размеры и шум и удалить коррелирующие функции, чтобы помочь алгоритму.

Для PCA необходимо определить количество компонентов, и мы обычно выбираем количество компонентов на основе процента объясненной дисперсии, например. 90%.

Для визуализации мы можем запустить pca на основе диапазона n_components, и мы можем визуализировать, как количество изменчивости в наборе данных все больше покрывается большим набором n_components.

pca = PCA()
df_pca = pca.fit_transform(df_for_pca)
exp_var = np.cumsum(pca.explained_variance_ratio_)
plt.bar(range(1, df_pca.shape[1] + 1), pca.explained_variance_ratio_, align="center")
plt.step(
    range(1, df_pca.shape[1] + 1), np.cumsum(pca.explained_variance_ratio_), where="mid"
)
plt.ylabel("Explained variance ratio")
plt.xlabel("Principal Components")
plt.show()

Чтобы напрямую получить количество компонентов на основе порогового значения отношения, мы можем применить маску для маскирования значений, меньших порогового значения, и найти местоположение наименьшего значения с помощью argmin.

exp_var = np.ma.MaskedArray(exp_var, exp_var < 0.9)
n_components = np.argmin(exp_var)

Кластеризация KMeans

Первым методом изучения будет кластеризация KMeans, где количество кластеров должно быть определено заранее. Обычный способ найти оптимальное количество кластеров состоит в том, чтобы перебрать несколько кластеров и найти оптимальное количество кластеров, где искажение уменьшается меньше всего с увеличением K.

Во многих реальных наборах данных график зависимости искажения от количества кластеров K может быть визуально не таким простым. Ниже приведен пример кода для создания графика и результатов на основе набора данных супермаркета.

distortions = []
for i in range(1, 30):
    km = KMeans(n_clusters=i, init="k-means++", max_iter=300, random_state=0)
    km.fit(pca_arr)
    distortions.append(km.inertia_)

print(len(distortions))
plt.plot(range(1, len(distortions) + 1), distortions, marker="o")
plt.xlabel("Number of Clusters")
plt.ylabel("Distortion")
plt.tight_layout()
plt.show()

Из графика видно, что не очень прямолинейная точка рассматривается как точка локтя.

Другой способ найти оптимальное количество кластеров, k для KMeans, — это вычислить средний балл силуэта для различного количества k, который можно получить на основе кода ниже.

Более подробную информацию об оценке силуэта и графиках можно найти на странице примера кода обучения scikit: https://scikit-learn.org/stable/auto_examples/cluster/plot_kmeans_silhouette_analysis.html

from sklearn.metrics import silhouette_score

list_i = []
list_score = []
for i in range(2, n_clusters):
    # Initialize the clusterer with n_clusters value and a random generator
    # seed of 10 for reproducibility.
    km = KMeans(n_clusters=i, init="k-means++", max_iter=500, random_state=0)
    cluster_labels = km.fit_predict(pca_arr)

    # The silhouette_score gives the average value for all the samples.
    # This gives a perspective into the density and separation of the formed
    # clusters
    silhouette_avg = silhouette_score(pca_arr, cluster_labels)
    list_i.append(i)
    list_score.append(silhouette_avg)

Из сгенерированного графика видно, что оптимальное количество кластеров с наивысшей оценкой будет 2 и 4. Во многих случаях такое небольшое количество кластеров может не обеспечивать полезной и значимой группировки, а большие значения кластеров также могут быть не такими простыми. быть выбранным.

Иерархическая кластеризация

В некоторых случаях мы обнаружили, что иерархическая кластеризация более эффективна. Ниже приведены некоторые пакеты иерархической кластеризации в Scikit Learn.

from scipy.cluster.hierarchy import linkage
from scipy.cluster.hierarchy import dendrogram
from sklearn.cluster import AgglomerativeClustering

Иерархическая кластеризация больше похожа на подход «снизу вверх», когда записи группируются вместе в зависимости от расстояния друг от друга. В конце концов, все клиенты будут объединены в единый кластер. Мы можем увидеть детали того, как формируется кластер при каждом слиянии, используя связывание.

row_clusters = linkage(df_for_pca.values, method="ward", metric="euclidean")
df_hc = pd.DataFrame(
    row_clusters,
    columns=["row label 1", "row label 2", "distance", "no. of items in clust."],
    index=[f"Merge {(i + 1)}" for i in range(row_clusters.shape[0])],
)

Оттуда мы можем определить количество слияний, например. 85% данных и найти соответствующие метрики расстояния, которые мы можем использовать для применения кластеризации. Исходя из опыта, более низкий порог расстояния лучше использовать для поиска выбросов, в то время как более высокий порог может помочь получить более крупные кластеры для анализа или дальнейших действий.

# Finding the merge based on 85% of merges
merge_threshold = 0.85
row = int(merge_threshold * df_hc.shape[0])
distance_threshold = df_hc.iloc[row, 2]
print(row, distance_threshold)

Из полученного порога расстояния мы можем визуализировать, как клиенты группируются вместе, используя дендрограмму.

labels = df_cust_sales.iloc[:, 0].values
plt.figure(figsize=(300, 12))
row_dendr = dendrogram(row_clusters, labels=labels, color_threshold=distance_threshold)
plt.ylabel("Euclidean distance")
plt.xlabel("Customer ID")
plt.show()

На дендрограмме, созданной ниже, разные цвета представляют разные кластеры, а метки по оси x — это идентификаторы клиентов из набора данных.

Когда результат выглядит удовлетворительным, кластеры можно создавать с помощью AgglomerativeClustering.

# Use Agglomerative Clustering based on Threshold
from sklearn.cluster import AgglomerativeClustering

ac = AgglomerativeClustering(
    n_clusters=None,
    distance_threshold=distance_threshold,
    affinity="euclidean",
    linkage="ward",
)

hc_cluster = ac.fit_predict(df_for_pca.values)

Постобработка данных и применение информации кластеризации

Когда генерируются сведения о кластере, номер кластера обычно является случайным по своей природе, и может потребоваться много усилий, чтобы дать осмысленное описание для каждого кластера. Некоторая постобработка данных, которая оказалась полезной, включает в себя извлечение сведений на основе атрибутов кластеров (подкатегория в этом примере), сортировку кластеров на основе таких показателей, как общий объем продаж или количество клиентов, и назначение кластера/группы. номер на основе ранга, чтобы обеспечить более значимый номер кластера/группы. Агрегацию для кластера можно легко выполнить с помощью groupby.

df_cluster_cat_count = (
    df_cluster_original_amount.groupby(["Cluster", "Sub-Category"])
    .agg({"Sales": "sum", "Customer ID": "nunique"})
    .reset_index()
)
df_cluster_cat_count.columns = [
    "Cluster",
    "Sub-Category",
    "Sales",
    "Count of Customers in Cluster SubCat",
]

Заключение

По моему опыту, кластеризация может быть очень полезной во многих ситуациях, особенно в качестве метода машинного обучения без учителя, когда не хватает меток данных для обучения с учителем. Для транзакционных данных одним из первых шагов, которые можно предпринять для применения кластеризации, является группировка клиентов по их покупательскому поведению по категориям продуктов.

Иерархическая кластеризация может быть полезным и гибким методом кластеризации клиентов, чтобы найти как выбросы, так и создать группы клиентов для последующих действий. Также может быть стандартизация, основанная на проценте слияния для формирования кластеров.

Идентификатор кластера, назначенный из алгоритмов кластеризации, будет меняться между каждым запуском и не предоставляет полезной информации. Постобработка данных путем агрегирования соответствующих показателей и описания может быть полезна для добавления дополнительной полезной информации для описания кластеров.

Спасибо за прочтение и надеюсь, что информация была хоть как-то полезна!