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

Прежде всего, что такое модель гауссовой смеси (GMM) и почему мы должны использовать GMM вместо K-среднего?

Модели гауссовой смеси (GMM) - это вероятностная модель кластеризации для представления нормально распределенных субпопуляций в общей популяции. GMM дает нам больше гибкости, чем K-среднее. В K-mean мы назначаем каждую точку данных конкретному кластеру (жесткое назначение). В следующей итерации мы можем пересмотреть его на основе измерения расстояния между точкой данных и центроидом кластера и переназначить центроид. В GMM используется мягкое присвоение (вероятность принадлежности к каждому кластеру), поэтому модель дает больше гибкости)

Шаг GMM

  1. Угадай какие-то кластерные центры
  2. Повторяйте до тех пор, пока не сойдутся
  • E-Step: назначьте точки ближайшему центру кластера
  • M-Step: установите для центров кластеров среднее значение

Плюсы и минусы GMM:

Преимущество:

  1. Не смещает размеры кластеров, чтобы они имели определенные структуры, и не предполагает, что кластеры имеют какую-либо геометрию, как это делается с помощью K-средних.
  2. GMM намного более гибок с точки зрения кластерной ковариации.
  3. GMM всегда подойдет лучше. GMM учитывает дисперсию при вычислении измерения.

Недостаток:

  1. Он использует все компоненты, к которым у него есть доступ, поэтому инициализация кластеров будет затруднена при высокой размерности данных.
  2. Трудно интерпретировать.

Реальное приложение:

Набор данных: включает 35 функций, каждая функция представляет поведение каждого клиента (можно скачать по ссылке git здесь)

Подготовка данных

import numpy as np
import pandas as pd
from matplotlib import colors
from tqdm import tqdm as tqdm
%matplotlib inline
import plotly as pl
import plotly.graph_objs as go
import plotly.express as px
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn import metrics
# gmm
from sklearn.mixture import GaussianMixture
#load data
data = pd.read_csv('data_segmentation.csv')
categerical_col = data.select_dtypes(include=['object']).columns
numberical_col = data._get_numeric_data().columns
data[numberical_col] = data[numberical_col].astype(float)
# Clean data
# misssing percentage 
def missing_percentage(data):
    """This function takes a DataFrame(df) as input and returns two columns, total missing values and total missing values percentage"""
    total = data.isnull().sum().sort_values(ascending = False)
    percent = round(data.isnull().sum().sort_values(ascending = False)/len(data)*100,2)
    return pd.concat([total, percent], axis=1, keys=['Total','Percent'])
missing_percentage(data)
data[categerical_col] = data[categerical_col].fillna("No infor")
data[numberical_col] = data[numberical_col].fillna(0)
# Dealing with outlier
std = np.std(data[numberical_col])
mean = np.mean(data[numberical_col])
#check outlier
def cnt_outlier(data,sd, inc_cols=[]):
    num_cols = data.select_dtypes(include=[np.number]).columns
    num_cols = [e for e in num_cols if e in inc_cols]
    outlier = (data[numberical_col]-mean).abs() > sd*std
    return outlier.sum()
# Remove Outlier
from scipy import stats
z = np.abs(stats.zscore(data[numberical_col]))
threshold = 4
data_work = data[(z <= 4).all(axis=1)]
data_work.head()

Факторный анализ:

Почему факторный анализ?

Факторный анализ связан с моделями смеси.

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

Факторный анализ - это метод уменьшения размера: он уменьшает размер исходного пространства.

Факторный анализ - это метод исследовательского анализа данных: откройте для себя небольшой набор компонентов, лежащих в основе многомерного набора данных (лагерь данных)

Тест на адекватность Перед тем, как выполнять факторный анализ, вам необходимо оценить «факторизуемость» нашего набора данных. Факторированность означает, можем ли мы найти факторы в наборе данных ?. Есть два метода проверки факторизации или адекватности выборки:

1. Bartlett’s Test
2. Kaiser-Meyer-Olkin Test (KMO)

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

from factor_analyzer import FactorAnalyzer
from factor_analyzer.factor_analyzer import calculate_bartlett_sphericity
chi_square_value,p_value=calculate_bartlett_sphericity(data_work[numberical_col])
chi_square_value, p_value
(166361.1684484173, 0.0)

В этом тесте Бартлетта p-значение равно 0. Тест был статистически значимым, что указывало на то, что наблюдаемая корреляционная матрица не является тождественной матрицей.

Тест Кайзера-Мейера-Олкина (КМО) измеряет пригодность данных для факторного анализа. Он определяет адекватность для каждой наблюдаемой переменной и для всей модели. KMO оценивает долю дисперсии среди всех наблюдаемых переменных. Идентификатор более низкой доли больше подходит для факторного анализа. Значения KMO находятся в диапазоне от 0 до 1. Значение KMO менее 0,6 считается недостаточным. (Источник: Data camp)

from factor_analyzer.factor_analyzer import calculate_kmo
kmo_all,kmo_model=calculate_kmo(a)
kmo_model
0.8211935165487708

KMO составляет 0,82, что указывает на то, что мы можем продолжить плановый факторный анализ.

Выбор количества факторов:

Для выбора количества факторов можно использовать критерий Кайзера и график осыпи. Оба основаны на собственных значениях.

fa = FactorAnalyzer()
fa.fit(a)
eigen_values, vectors = fa.get_eigenvalues()
x = np.arange(1,a.shape[1]+1)
import plotly.graph_objects as go
import numpy as np
# x = np.arange(10)
fig = go.Figure(data=go.Scatter(x=x, y=eigen_values))
fig.update_layout(title='Eigen value by number of factors',
                   xaxis_title='Factors',
                   yaxis_title='eigen_values')
fig.show()

Варианты фактора

_, _, cumvar = fa.get_factor_variance()
fig = go.Figure(data=go.Scatter(x=x, y=cumvar))
fig.update_layout(title='Eigen value by number of factors',
                   xaxis_title='Numbe of Factors',
                   yaxis_title='Variance%')
fig.show()

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

fa.set_params(n_factors=10, rotation='varimax')
fa.fit(a)
loadings = fa.loadings_
factor_loading = pd.DataFrame(loadings)
factor_loading.head()

# Get variance of each factors
fa.get_factor_variance()
factor_variance = pd.DataFrame(fa.get_factor_variance())
factor_variance

fa_finnal = FactorAnalyzer(n_factors=10, rotation="varimax")
fa_finnal.fit(a)

Создайте новый фрейм данных только с 10 факторами, которые мы нашли ранее.

col_name = []
for i in range(10): 
    col_name.append('Factor '+str(i+1))
df_factors = fa_finnal.transform(a)
df_factors = pd.DataFrame(df_fac, columns = col_name)

Использование GMM для сегментации:

Выбор количества кластеров

def gmm_bic(a, lower=2, upper=10):
    bic = []
    for i in tqdm(range(lower,upper)):
        gm = GaussianMixture(n_components=i, covariance_type="full",max_iter=300, random_state=42)
        gm.fit(a)
        b = gm.bic(a)
        bic.append(b)
        print('Convergence? {} at iteration {}'.format(gm.converged_, gm.n_iter_))
    fig = go.Figure(data=go.Scatter(x=np.arange(lower,upper), y=bic))
    fig.update_layout(title='BIC',
                   xaxis_title='Numbe of Clusters',
                   yaxis_title='BIC')
    fig.show()

Запустите GMM на фрейме данных факторов

gmm_bic(df_fac)

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

Запустите GMM на факторе Dataframe

gmm = GaussianMixture(n_components=4, max_iter=300, random_state=42)
gmm.fit(df_fac)
a['Segment'] = gmm.predict(df_fac)

Вывод

#display
pd.options.display.float_format = '{:,.2f}'.format
output = a[features].groupby('Segment').mean().T
output.to_csv('result.csv')
#show gradient
def background_gradient(s, m, M, cmap='PuBu', low=0, high=0):
    rng = M - m
    norm = colors.Normalize(m - (rng * low),
                            M + (rng * high))
    normed = norm(s.values)
    c = [colors.rgb2hex(x) for x in plt.cm.get_cmap(cmap)(normed)]
    return ['background-color: %s' % color for color in c]
# display output with gradient
output.style.apply(background_gradient,
#                highlight_max(output),
               cmap='PuBu',
               m=output.min().min(),
               M=output.max().max(),
               low=0,
               high=0.2).format('{:,.2f}')

a.groupby('Segment').size()
Segment
0    1085
1    1088
2     767
3     670
dtype: int64

Профилирование вывода

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

Group 1 (1,085): Promotion hunter
Group 2 (670): Neglector
Group 3 (1,088): Hopeful fan
Group 4 (767): The royalty

Использованная литература: