Как реализовать иерархическую кластеризацию для кампаний прямого маркетинга - с помощью кода на Python

Автор: Дмитрий Иванов [Команда P9X]

~8 минут чтения

Мотивация

Представьте, что вы специалист по данным в ведущем финансовом учреждении, и ваша задача — помочь вашей команде разделить существующих клиентов на отдельные профили: low, average, medium и platinum для одобрения кредита.

Но вот в чём загвоздка:

У этих клиентов нет таких исторических меток, так как же вы будете создавать эти категории?

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

Существует множество методов кластеризации, но в этом руководстве основное внимание будет уделено подходу иерархической кластеризации.

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

Что такое иерархическая кластеризация?

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

Алгоритм иерархической кластеризации опирается на меры расстояния для формирования кластеров и обычно включает следующие основные шаги:

  • Вычисление матрицы расстояний, содержащей расстояние между каждой парой точек данных, с использованием определённой метрики расстояния, такой как евклидово расстояние, манхэттенское расстояние или косинусное сходство.
  • Объединение двух кластеров, которые находятся ближе всего по расстоянию.
  • Обновление матрицы расстояний с учётом новых кластеров.
  • Повторение шагов 1, 2 и 3 до тех пор, пока все кластеры не будут объединены в один.

Некоторые графические иллюстрации иерархической кластеризации

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

#1. Агломеративная кластеризация

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

Давайте рассмотрим иллюстрацию ниже, где:

  • Мы начинаем с того, что каждое животное рассматривается как уникальный кластер.
  • Затем, на основе списка животных, три разных кластера формируются в соответствии с их сходствами: орлы и павлины классифицируются как Птицы, львы и медведи — как Млекопитающие, скорпионы и пауки — как 3+ ноги.
  • Мы продолжаем процесс слияния для создания кластера Позвоночные путём объединения двух наиболее похожих кластеров: Птицы и Млекопитающие.
  • Наконец, оставшиеся два кластера, Позвоночные и 3+ ноги, объединяются для создания единого кластера Животные.

Иллюстрация агломеративной кластеризации (Изображение автора)

#2. Дивизивная кластеризация

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

Наблюдая за графическим представлением дивизивного подхода:

  • Мы замечаем, что весь набор данных Животные рассматривается как единый блок.
  • Затем этот блок разделяется на два разных кластера: Позвоночные и 3+ ноги.
  • Процесс разделения итеративно применяется к ранее созданным кластерам до тех пор, пока каждое животное не будет выделено как отдельный уникальный кластер.

Иллюстрация дивизивной кластеризации (Изображение автора)

Выбор подходящей меры расстояния

Выбор подходящей меры расстояния является важным шагом в кластеризации, и она зависит от конкретной проблемы.

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

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

Как измерить кластеры перед их объединением

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

Существуют три основных стандартных способа измерения ближайшей пары кластеров перед их объединением: (1) одиночная связь, (2) полная связь и (3) средняя связь. Давайте рассмотрим каждый из них более подробно.

#1. Одиночная связь

В кластеризации одиночной связи расстояние между двумя заданными кластерами C1 и C2 соответствует минимальным расстояниям между всеми парами элементов в двух кластерах.

Формула расстояния для одиночной связи (Изображение автора)

Из всех пар элементов из двух кластеров b и k имеют минимальное расстояние.

Иллюстрация одиночной связи (Изображение автора)

#2. Полная связь

Для кластеризации полной связи расстояние между двумя заданными кластерами C1 и C2 является максимальным расстоянием между всеми парами элементов в двух кластерах.

Формула расстояния для одиночной связи (Изображение автора)

Из всех пар элементов из двух кластеров максимальное расстояние имеют те, которые выделены зелёным (f и m).

Иллюстрация полной связи (Изображение автора)

#3. Средняя связь

В кластеризации средней связи расстояние между двумя заданными кластерами C1 и C2 вычисляется с использованием среднего значения всех расстояний между каждой парой элементов в двух кластерах.

Формула расстояния для средней связи (Изображение автора) Иллюстрация средней связи (Изображение автора)

Из приведённой выше формулы среднее расстояние можно вычислить следующим образом:

Вычисление расстояния для средней связи (Изображение автора)

Реализация иерархической кластеризации в Python

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

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

Настройка среды

Python необходим и должен быть установлен вместе со следующими библиотеками:

  • Pandas для загрузки фрейма данных.
  • Scikit-learn для нормализации данных.
  • Seaborn и Matplotlib для визуализации данных.
  • Scipy для применения кластеризации.

Все эти библиотеки устанавливаются с помощью команды pip следующим образом из вашего ноутбука:

%%bash
pip install scikit-learn
pip install pandas
pip install matplotlib seaborn
pip install scipy

Вместо индивидуальной установки каждой библиотеки с помощью !pip [library] мы используем оператор %%bash вместо этого, чтобы ячейка ноутбука рассматривалась как команда оболочки, которая игнорирует ! и тем самым упрощает установку.

Понимание данных

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

Этот набор данных взят из UCI и лицензирован в соответствии с Creative Commons Attribution 4.0 International (CC BY 4.0).

Из-за неконтролируемого характера этого руководства мы избавляемся от целевого столбца y, в котором указано, подписался ли клиент на депозит или нет.

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

import pandas as pd

URL = "https://raw.githubusercontent.com/keitazoumana/Medium-Articles-Notebooks/main/data/bank.csv"
bank_data = pd.read_csv(URL, sep=";")
bank_data.head()

Первые пять строк загруженных данных (Изображение автора)

Однако, если мы используем функцию info, мы можем получить более детальную информацию о наборе данных, такую как:

  • Общее количество записей (4521) и столбцов (17).
  • Имя каждого столбца и его тип. Мы можем заметить, что есть два основных типа столбцов: int64 и object.
  • Общее количество пропущенных значений в каждом столбце.
bank_data.info()

Вывод:

Информация о данных (Изображение автора)

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

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

  • Заполнение пропущенных значений соответствующей информацией.
  • Нормализация значений столбцов.
  • Наконец, удаление нерелевантных столбцов.

#1. Работа с пропущенными значениями

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

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

percent_missing = round(100 * (loan_data.isnull().sum()) / len(loan_data), 2)
percent_missing

Вывод:

Процент пропущенных значений в данных (Изображение автора)

#2. Удаление нерелевантных столбцов

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

Для простоты в анализе используются только столбцы int64 (числовые). С помощью функции select_dtypes мы выбираем желаемый тип столбца для сохранения.

import numpy as np

cleaned_data = bank_data.select_dtypes(include=[np.int64])
cleaned_data.info()

Вывод:

Новые данные без ненужных столбцов (Изображение автора)

#3. Анализ выбросов

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

Простой способ определить эти выбросы — проанализировать распределение данных с помощью boxplot, как показано ниже, в функции помощника show_boxplot, которая использует встроенную функцию boxplot в Seaborn.

import matplotlib.pyplot as plt
import seaborn as sns

def show_boxplot(df):
  plt.rcParams['figure.figsize'] = [14, 6]
  sns.boxplot(data=df, orient="v")
  plt.title("Распределение выбросов", fontsize=16)
  plt.ylabel("Диапазон", fontweight='bold')
  plt.xlabel("Атрибуты", fontweight='bold')

show_boxplot(cleaned_data)

Вывод:

Boxplot всех переменных в данных (Изображение автора)

Атрибут balance, представляющий средний годовой баланс клиентов, является единственным, имеющим точки данных, далеко отстоящими от остальных.

Используя подход межквартильного размаха, мы можем удалить все такие точки, которые лежат за пределами диапазона, определённого квартилями +/-1,5*IQR, где IQR — это межквартильный размах.

Общая логика реализована в функции помощника remove_outliers.

def remove_outliers(data):

  df = data.copy()

  for col in list(df.columns):
    Q1 = df[str(col)].quantile(0.05)
    Q3 = df[str(col)].quantile(0.95)
    IQR = Q3 - Q1
    lower_bound = Q1 - 1.5 * IQR
    upper_bound = Q3 + 1.5 * IQR

    df = df[(df[str(col)] >= lower_bound) & (df[str(col)] <= upper_bound)]

  return df

Затем мы можем применить функцию к набору данных и сравнить новый boxplot с тем, который был до удаления выбросов.

without_outliers = remove_outliers(cleaned_data)

show_boxplot(without_outliers)

Вывод:

Больше нет точек данных, лежащих за пределами межквартильного размаха (Изображение автора)

without_outliers.shape

Мы получили набор данных из 4393 строк и 7 столбцов, что означает, что оставшиеся 127 наблюдений, удалённых из данных, были выбросами.

#4. Изменение масштаба данных

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

Функция fit_transform из класса StandardScaler преобразует исходные данные так, чтобы каждый столбец имел среднее значение, равное нулю, и стандартное отклонение, равное единице.

from sklearn.preprocessing import StandardScaler

data_scaler = StandardScaler()

scaled_data = data_scaler.fit_transform(without_outliers)
scaled_data.shape

Форма данных остаётся неизменной (4393 строки и 7 столбцов), поскольку нормализация не влияет на форму данных.

Применение алгоритма иерархической кластеризации

Мы все готовы углубиться в реализацию алгоритма кластеризации!

На этом этапе мы можем решить, какой подход к связи использовать для кластеризации атрибута method функции linkage().

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

from scipy.cluster.hierarchy import linkage, dendrogram

complete_clustering = linkage(scaled_data, method="complete", metric="euclidean")
average_clustering = linkage(scaled_data, method="average", metric="euclidean")
single_clustering = linkage(scaled_data, method="single", metric="euclidean")

После вычисления всех трёх кластеризаций соответствующие дендрограммы визуализируются с помощью функции dendogram из модуля scipy.cluster и функции pyplot из matplotlib.

Каждая дендрограмма организована следующим образом:

  • x-ось представляет кластеры в данных.
  • y-ось соответствует расстоянию между этими выборками. Чем выше линия, тем более несхожи эти кластеры.
  • Соответствующее количество кластеров получается путём проведения горизонтальной линии через самую высокую вертикальную линию.
  • Количество пересечений с горизонтальной линией соответствует количеству кластеров.
dendrogram(complete_clustering)
plt.show()

Вывод:

Дендрограмма подхода полной кластеризации (Изображение автора)

dendrogram(average_clustering)
plt.show()

Вывод:

Дендрограмма подхода средней кластеризации (Изображение автора)

При запуске одиночной кластеризации мы можем столкнуться с проблемой ограничения рекурсии. Это решается с помощью функции setrecursionlimit с достаточно большим значением:

import sys
sys.setrecursionlimit(1000000)

Теперь мы отображаем дендрограмму:

dendrogram(single_clustering)
plt.show()

Вывод:

Дендрограмма подхода одиночной кластеризации (Изображение автора)

Определение оптимального количества кластеров в дендрограммах

Оптимальное количество кластеров можно получить, определив самую высокую вертикальную линию, которая не пересекается с другими кластерами (горизонтальная линия). Такая линия найдена ниже с помощью красного круга и зелёной галочки.

  • Для полной связи: нет значительного количества сгенерированных кластеров.

Полная связь: оптимальное количество кластеров с наивысшим расстоянием без пересечения (Изображение автора)

  • Для средней связи: разница между двумя горизонтальными оранжевыми линиями чуть больше единицы. Мы можем рассмотреть два кластера вместо одного.

Средняя связь: оптимальное количество кластеров с наивысшим расстоянием без пересечения (Изображение автора)

  • Для одиночной связи: чёткое количество кластеров определить невозможно.

Одиночная связь: оптимальное количество кластеров с наивысшим расстоянием без пересечения (Изображение автора)

На основе проведённого выше анализа средняя связь, по-видимому, обеспечивает оптимальное количество кластеров по сравнению с одиночной и полной связями, которые не дают чёткого понимания количества кластеров.

Теперь, когда мы нашли оптимальное количество кластеров, давайте интерпретируем эти кластеры в контексте среднего годового баланса клиентов с помощью функции cut_tree.

cluster_labels = cut_tree(average_clustering, n_clusters=2).reshape(-1, )
without_outliers["Cluster"] = cluster_labels

sns.boxplot(x='Cluster', y='balance', data=without_outliers)

Boxplot двух типов заёмщиков (Изображение автора)

Из вышеприведённого boxplot мы можем наблюдать, что:

  • Клиенты из кластера 0 обладают самым высоким средним годовым балансом.
  • Заёмщики из кластера 1 имеют сравнительно более низкий средний годовой баланс.

Заключение

Поздравляю!!!🎉

Надеюсь, эта статья предоставила вам достаточно инструментов, чтобы вывести ваши знания на новый уровень. Код доступен на моём GitHub.

Также, если вам нравятся мои статьи и вы хотите поддержать моё творчество, подумайте о том, чтобы стать участником Medium. Это всего 5 долларов в месяц, и вы получите неограниченный доступ к тысячам руководств по Python и статьям о науке о данных.

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

Присоединяйтесь к Medium с моей реферальной ссылкой — Зумана Кейта

Не стесняйтесь подписываться на меня на YouTube или писать мне в LinkedIn. Я также открыт для индивидуальных обсуждений, если вам нужна дополнительная информация.