Что исследуем

Вы провели опрос клиентского опыта в вашей компании. В данном случае на каждый вопрос клиенты отвечали по 10 бальной шкале, где 1 - совсем неудовлетворен, а 10 - полностью удовлетворен. Вопросы разбиты на несколько тематических блоков. В начале блок основных вопросов:

  • общая удовлетворенность (CSI - Customer Satisfaction Index);

  • вероятность рекомендации (для расчета NPS - Net Promoter Score);

  • вероятность повторной покупки.

Далее следуют уточняющие блоки вопросов, например, работа менеджера, качество продукции и др.

С какими проблемами сталкиваемся при анализе результатов

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

  • как интерпретировать результаты опроса;

  • как связать общую удовлетворенность нашим продуктом (услугой) с другими, более конкретными аспектами деятельности;

  • какие из аспектов оказывают влияние и в какой мере на общую удовлетворенность.

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

Приступим к анализу

--импортируемые библиотеки
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
from factor_analyzer import FactorAnalyzer
from factor_analyzer.factor_analyzer import calculate_kmo
from factor_analyzer.factor_analyzer import calculate_bartlett_sphericity

1. Исследовательский анализ данных - поиск аномальных респондентов

Загрузим датасет с результатами опроса

df = pd.read_excel('data.xlsx', sheet_name='data')
df.head(10)

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

distribution = df.set_index('client_code').sum(axis=1)
plt.title('Распределение суммарных оценок')
distribution.hist();
На диаграмме размаха (ящика с "усами") очерчены верхняя и нижняя граница ящика, что соответствуют первой (75%) третьей (25%) квантилям. Нам же нужно определить значения, выходящие за так называемые "усы", которые расположены вверху и внизу от границ ящика на расстоянии 1.5 межквартильных размахов.
На диаграмме размаха (ящика с "усами") очерчены верхняя и нижняя граница ящика, что соответствуют первой (75%) третьей (25%) квантилям. Нам же нужно определить значения, выходящие за так называемые "усы", которые расположены вверху и внизу от границ ящика на расстоянии 1.5 межквартильных размахов.

Рассчитаем верхнюю и нижнюю границы:

Q1 = distribution.quantile(0.25)
Q3 = distribution.quantile(0.75)
IQR = Q3 - Q1
print('upper limit: {:.0f}'.format(Q3+1.5*IQR))
print('lower limit: {:.0f}'.format(Q1-1.5*IQR))

upper limit: 348

lower limit: 146

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

outliers = distribution[(distribution > (Q3+1.5*IQR)) 
                        | (distribution < (Q1-1.5*IQR))].reset_index()
outliers = outliers['client_code'].to_list()
print('number of outlier clients:', len(outliers))
print('share of outlier clients: {:.2%}'
      .format(len(outliers)/len(distribution)))

number of outlier clients: 10

share of outlier clients: 4.27%

Аномальными оказались 4%, исключим их их набора данных.

2. Убедимся, что обычные корреляции нам "ни о чем не говорят".

Посчитаем корреляции (Спирмена) между вопросами.

df_corr = df.drop(columns=['client_code']) \
            .corr(method='spearman').unstack().sort_values(ascending=False) \
            .drop_duplicates().reset_index()
df_corr.columns = ['features_1', 'features_2', 'corr']
df_corr_60 = df_corr[(df_corr['corr'] >= 0.6)]
print ('доля вопросов, между которыми корреляция Спирмена 60% и более: {:.0%}'
       .format(len(df_corr_60) / len(df_corr) )) 

доля вопросов, между которыми корреляция Спирмена 60% и более: 5%

Как мы видим, например, у вопроса CSI нет явных корреляций с другими. А доля вопросов, которые между собой коррелируют на 60% и более всего 5%. Поэтому перейдем к факторному анализу.

3. Факторный анализ (ФА)

3.1 Проверка данных на возможность проведения на них факторного анализа

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

df_cc = df.copy()
df_cc = df_cc.set_index('client_code').mask(df_cc == 0).fillna(df_cc.median())

Для того, чтобы определить подходят ли наши данные для факторного анализа, проверим их на критерий Бартлетта и тест Кайзера-Мейера-Олкина (КМО).

Критерий Бартлетта

chi_square_value, p_value = calculate_bartlett_sphericity(df_cc)
chi_square_value, p_value

(4611.652245754526, 0.0)

the p-value 0, тест статистически значим

Тест Кайзера-Мейера-Олкина (КМО)

kmo_all, kmo_model = calculate_kmo(df_cc)
kmo_model

0.8450885308794326

KMO более 0.85 (близко к 1), значит данные подходя для проведения факторного анализа

Наши данные подходят для факторного анализа.

3.2 Определим кол-во факторов

То есть определим количество групп вопросов, результаты которых схожи между собой.

fa = FactorAnalyzer()
fa.fit(df_cc)
ev, v = fa.get_eigenvalues()
ev

array([9.99955313, 3.48353376, 2.65717082, 2.01062146, 1.47540767, 1.21703008, 1.01630207, 0.97019466, 0.91913583, 0.83630588, 0.81516091, 0.72197036, 0.66557448, 0.59507375, 0.57851829, 0.54356097, 0.48482209, 0.42742778, 0.41153206, 0.39001518, 0.34143319, 0.33873975, 0.32238175, 0.28523165, 0.24437559, 0.2380292 , 0.20200081, 0.19508718, 0.14846106, 0.14205693, 0.12562844, 0.11444726, 0.08321594])

Изобразим результат графически.

plt.scatter(range(1,df_cc.shape[1]+1),ev)
plt.plot(range(1,df_cc.shape[1]+1),ev)
plt.title('График каменистой осыпи для определения необходимого \
          кол-ва факторов')
plt.xlabel('Факторы')
plt.ylabel('Собственные значения')
plt.axhline(y=1,c='k');

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

Рассчитаем корреляционную матрицу между вопросами и полученными шестью факторами.

fa = FactorAnalyzer()
fa.set_params(n_factors=6, rotation='varimax')
fa.fit(df_cc)
loadings = fa.loadings_
loadings = np.abs(loadings)

Сохраним результат в DataFrame.

df_fa = pd.DataFrame(loadings).reset_index()
df_fa.columns=['№', 'Factor1', 'Factor2', 'Factor3', 'Factor4', 'Factor5', 'Factor6']
df_fa = (df_fa.merge(questions['question'], how='left', left_index=True, right_index=True)
         .reindex(columns=['№', 'question', 'Factor1', 'Factor2', 'Factor3', 'Factor4', 'Factor5', 'Factor6']))
df_fa

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

df_fa_pivot = (df_fa.pivot_table(index='question')
               .sort_values(by=['№']).drop(columns=['№']))
plt.figure(figsize=(13, 13))
sns.heatmap(df_fa_pivot, annot=True, fmt='.1%', linewidths=1, linecolor='gray');

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

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

4. Интерпретация и анализ результатов ФА

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

  • Для каждого вопроса укажем его средний балл, который получили от респондентов.

  • Далее в каждом факторе выделим корреляции с вопросами, если они выше 0.5.

  • Теперь посчитаем средний балл для каждого фактора как средневзвешенный балл, весами будут выступать корреляции более 0.5. Например, средний балл для Фактора №5:

(9.4 * 0.64 + 9.6 * 0.7) / (0.64 + 0.7) = 9.5

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

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

Приведем несколько примеров как интерпретировать результаты:  

  • Фактор №1 оказывает повышающее влияние на общий балл удовлетворенности, так как его балл 9.3, выше, чем общий (9.1). Посмотрим детальнее из чего он состоит.

  • Этот фактор связан с работой менеджеров и процессом подготовки договоров.

  • Как видно наибольшая корреляция фактора с вопросами, связанными с оперативностью (предоставления информации и согласованию спецификаций). Но если у оперативности предоставления информации менеджером балл высокий (9.4, выше, чем у фактора в целом - 9.3), то у оперативности предоставления спецификации балл занижает общий балл фактора (9.1), при этом имеет самую высокую корреляцию, а значит наибольшее влияние на оценки этой группы вопросов. Значит нам для улучшения общих результатов следует обратить на этот процесс повышенное внимание и внести предложения по его улучшению.

  • Самый низкий балл у группы вопросов в Факторе №1 - оперативность при выставлении КП, он тоже оказывает понижающее действие (9.0 баллов).

  • А вот работа менеджеров, их доступность и оперативность вызывает у клиентов только положительные отзывы, менеджеры молодцы.

Надеюсь предложенный вариант расчета принесет интересные результаты на ваших данных!

Комментарии (0)