Сегментация клиентской базы телекоммуникационной компании


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

Описание данных

Каждый клиент описывается следующим набором признаков:

Возраст, Среднемесячный расход, Средняя продолжительность разговоров, Звонков днем за месяц, Звонков вечером за месяц, Звонков ночью за месяц, Звонки в другие города, Звонки в другие страны, Доля звонков на стационарные телефоны, Количество SMS за месяц, Дата подключения тарифа.

План по выполнению проекта:

Шаг 1. Загрузка данных;
Шаг 2. Первичная обработка данных (при необходимости):
  • скорректировать заголовки;
  • скорректировать типы признаков;
  • проверить наличие дублирующихся записей;
  • проверить наличие аномальных значений;
  • восстановить пропущенные значения;
Шаг 3. Добавление новых переменных:
по значениям признака Возраст введите новую переменную Возрастная категория, принимающую значения "студент", "аспирант", "бизнесмен" и "знаток" по следующую правилу:
Возрастная категория = студент, если Возраст ∈[19,24];
аспирант, если Возраст ∈[25,33];
бизнесмен, если Возраст ∈[34,56];
знаток, если Возраст ∈[57,70].
По значениям признака Дата подключения тарифа создайте признаки: Год подключения, Месяц подключения, Дата подключения;
Шаг 3. Провести исследовательский анализ данных:
  • В разрезе значений признаков Год подключения, Месяц подключения, Дата подключения исследуйте: динамику подключения к тарифам (количество клиентов). Постройте графики. В какой год, месяц и день подключались меньше/больше всего клиентов? Какие выводы можно сделать?
  • В разрезе значений признака Возрастная категория исследуйте распределение признаков Среднемесячный расход, Средняя продолжительность разговоров, Звонков днем за месяц, Звонков вечером за месяц, Звонков ночью за месяц, Звонки в другие города, Доля звонков на стационарные телефоны, Количество SMS за месяц.
  • Для каждого из признаков рассчитайте выборочное среднее, медиану и моду.
Строим графики. Какие выводы можно сделать о предпочтениях клиентов разных возрастных категорий в отношении используемых услуг (звонков и SMS; времени суток);
Клиенты каких возрастных категорий (ТОП-2):
  • больше всего в среднем в месяц тратят на оплату услуг связи;
  • больше всего тратят времени на общение в месяц днем, вечером и ночью;
  • больше всего по количеству звонков в месяц днем, вечером и ночью. Совпадают ли результаты с предыдущем пунктом;
С помощью диаграмм рассеивания исследуем зависимости между признаками Среднемесячный расход, Средняя продолжительность разговоров, Звонков днем за месяц, Звонков вечером за месяц, Звонков ночью за месяц, Звонки в другие города, Доля звонков на стационарные телефоны, Количество SMS за месяц. Сделаем выводы
Шаг 4. Провести кластерный анализ, дать оценку качества построенной кластеризации.

Шаг 5. Сформулировать и проверить следующие гипотезы
  • клиенты чаще звонят днем или вечером по количеству звонков
  • клиенты больше звонили в 2019 году по сравнению с 2021 годом по количеству звонков.
Подключение библиотек:
import pandas as pd
import numpy as np
import datetime as dt
import matplotlib.pyplot as plt
import seaborn as sns

Шаг 1. Загрузка данных

df = pd.read_csv('/content/dataset_telecom.csv')
df.head(5)

Информация о датафрейме

df.info()

--- ------ -------------- -----
0 Возраст 4492 non-null int64
1 Среднемесячный расход 4468 non-null float64
2 Средняя продолжительность разговоров 4475 non-null float64
3 Звонков днем за месяц 4472 non-null float64
4 Звонков вечером за месяц 4489 non-null float64
5 Звонков ночью за месяц 4492 non-null object
6 Звонки в другие города 4492 non-null object
7 Звонки в другие страны 4492 non-null int64
8 Доля звонков на стационарные телефоны 4492 non-null object
9 Количество SMS за месяц 4492 non-null object
10 Дата подключения тарифа 4492 non-null object
dtypes: float64(4), int64(2), object(5)
memory usage: 386.2+ KB

Шаг 2. Первичная обработка данных:

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

корректировка заголовков

корректировка типов признаков

df['night_calls_monthly']=pd.to_numeric(df['night_calls_monthly'],errors='coerce')
df['sms_monthly']=pd.to_numeric(df['sms_monthly'],errors='coerce')
df.other_country_calls = df.other_country_calls.astype(float)
df['other_city_calls']=pd.to_numeric(df['other_city_calls'],errors='coerce')
df['landlines_calls']=pd.to_numeric(df['landlines_calls'],errors='coerce')
df.tariff_activation_date = df.tariff_activation_date.astype(np.datetime64)

проверка на наличие дублирующих записей

df.duplicated().sum()

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

График плотности распределения признаков после обработки аномальных значений

Выводим процент обработанных аномалий и формируем новый датасет

(1 - df[no_anomals_avr_monthly_exp & no_anomals_day_calls_monthly\
& no_anomals_other_city_calls]. shape[0]/df.shape[0])*100
10.819234194122885
----------------------------

По значениям признака Возраст вводим новую переменную "Возрастная категория" студент, если Возраст ∈[19,24];аспирант,если Возраст ∈[25,33];бизнесмен,если Возраст ∈[34,56];знаток,если Возраст ∈[57,70].

bins = [0,24,33,56,70]
labels=['student','aspirant','businessman','znatok']
df_clear['age_category'] = pd.cut(df['age'],bins=bins, labels=labels)
df_clear.head(5)

По значениям признака "Дата подключения" тарифа выделяем признаки: Год подключения, Месяц подключения, Дата подключения


df_clear['year_activation'] = df_clear['tariff_activation_date'].dt.year
df_clear['month_activation'] = df_clear['tariff_activation_date'].dt.month
df_clear['date_activation'] = df_clear['tariff_activation_date'].dt.date
df_clear['date_activation'] = df_clear['tariff_activation_date'].dt.day_name()

df_clear.head(5)

Шаг 3. Проводим исследовательский анализ данных:

в разрезе значений признаков Год подключения, Месяц подключения, Дата подключения исследуем:
динамику подключения к тарифам (количество клиентов). Строим графики. В какой год, месяц и день подключались меньше/больше всего клиентов? Делаем выводы.
ВЫВОД: подключение в среднем приблизительно одинаковое по всем годам, кроме 2017г.,где подключалось больше всего клиентов, в 2015 подключалось меньше всего клиентов.
По графику видим, что больше всего клиентов подключалось в августе, меньше в марте. Возможно месяц максимального подключения клиентов связан с проведением акций в конце летнего периода.
В понедельник подключалось больше всего клиентов, в субботу подключалось меньше всего клиентов. Активное подключение с понедельника возможно связано с планами людей - начинать дела с новой рабочей недели.

Посмотрим медиану, моду по всем признакам в разрезе Возрастной категории

В разрезе значений признака Возрастная категория исследуем распределение признаков Среднемесячный расход, Средняя продолжительность разговоров, Звонков днем за месяц, Звонков вечером за месяц, Звонков ночью за месяц, Звонки в другие города, Доля звонков на стационарные телефоны, Количество SMS за месяц. Построим графики. Сделаем выводы о предпочтениях клиентов разных возрастных категорий в отношении используемых услуг (звонков и SMS; времени суток)

Вывод:
среди всех групп, по звонкам вечером, днем и ночью, на стационарные телефоны и в др. страны возрастная группа Аспирантов (25-33) совершает больше всего звонков. Студенты чаще всех отправляют смс в месяц, но меньше всего звонков у них на стационарные телефоны и в др. города и страны, где лидируют Аспиранты и Бизнесмены. Ночью и вечером Студенты, после Аспирантов, совершают больше звонков. Вторая лидирующая группа это Бизнесмены, кроме звонков вечером и ночью. Группа Знатоков меньше всего отправляют смс, предположительно ввиду отсутствия навыков и делают меньше звонков ночью.

Клиенты возрастных категорий (ТОП-2):

  • больше всего в среднем в месяц тратят на оплату услуг связи;
  • больше всего тратят времени на общение в месяц днем, вечером и ночью;
  • больше всего по количеству звонков в месяц днем, вечером и ночью.
C помощью диаграмм рассеивания исследуем зависимости между признаками Среднемесячный расход, Средняя продолжительность разговоров, Звонков днем за месяц, Звонков вечером за месяц, Звонков ночью за месяц, Звонки в другие города, Доля звонков на стационарные телефоны, количество SMS за месяц. Какие выводы?
Вывод:
Построив диаграмму рассеивания по признакам Среднемесячный расход, Средняя продолжительность разговоров, Звонков днем за месяц, мы наблюдаем некую зависимость между признаками.
Если провести воображаемую прямую по диагонали, то можно заметить, что распределение сосредоточено вдоль этой прямой. Между продолжительностью разговора и тратами в месяц есть некая положительная корреляционная зависимость, также как и между звонками днем и ежемесячными расходами- имеется некая зависимость. Можно предположить, что при возрастании одного признака в среднем увеличивается другой, т.е чем больше продолжительность разговора тем больше расходы в месяц и наоборот. Чем больше звонков днем- растут и ежемесячные расходы.
Вывод:
Корреляционная матрица показывает некую выраженную зависимость по коэффициенту = 0.881 между продолжительностью разговоров и среднемесячными расходами.

Заключение:

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

  • в процессе разделения нами клиентов на 4-е возрастные группы Student[19,24], Aspirant [25,33], Businessman[34,56], Znatok[57,70], а также группировки совершенных ими звонков по году, месяцу, дню, мы заметили, что в разрезе признака Год подключения по количеству клиентов, подключение в период с 2015-2021г в среднем приблизительно одинаковое, кроме 2017г., где подключалось больше всего клиентов,а в 2015 подключалось меньше всего клиентов.
  • в разрезе признака Месяц подключения, больше всего клиентов подключалось в августе, меньше - в марте. Возможно месяц максимального подключения клиентов связан с проведением акций в конце летнего периода.
  • в разрезе признака Дата(день) подключения, в понедельник подключалось больше всего клиентов, в субботу подключалось меньше всего клиентов. Активное подключение с понедельника возможно связано с планами людей - начинать дела с новой рабочей недели.
  • в разрезе признака Возрастная категория, на основе расчета среднего показателя возраста клиента, и пользующегося услугами чаще других, можно выделить некий портрет клиента: Студент - 20 лет; Аспирант - 29, 31 лет; Бизнесмен - 45 лет; Знаток - 60, 58 лет.
  • сгруппировав по Возрастной категории кол-во звонков в разное время, города, страны, мы увидели что, по количеству звонков вечером, днем и ночью, на стационарные телефоны и в др. страны, Аспиранты (25-33) совершают звонки чаще остальных. Студенты больше всех отправляют смс в месяц, но меньше всего звонков совершают на стационарные телефоны и в др. города и страны, где Аспиранты и Бизнесмены лидируют. Ночью и вечером Студенты после Аспирантов совершают больше звонков. Вторая лидирующая группа это Бизнесмены, кроме количества совершенных ими звонков вечером и ночью. Группа знатоков меньше всего отправляют смс,предположительно ввиду отсутствия навыков и делают меньше звонков ночью.
  • по расходам, кол-ву звонков и времени, в разрезе признака Возрастная категория, исследовав средний показатель, мы увидели, что больше всего в среднем в месяц тратят на оплату услуг связи - Аспирант(25-33) и Бизнесмен(34-56), а так же совершают больше звонков и тратят больше времени общения днем, вечером и ночью. Меньше всего времени на общение в месяц, днем и вечером тратят клиенты возрастной категории - Знаток (57-70), по расходам на услуги и продолжительности разговоров -Студент(19-24).
  • построив и исследовав диаграмму рассеивания, отметим некую корреляцию между признаками Средний расход в месяц, продолжительность разговоров и средним количеством дневных звонков в месяц. Можно предположить, что при возрастастании одного признака в среднем увеличивается другой, т.е чем больше продолжительность разговора тем больше расходы в месяц и наоборот.Чем больше звонков днем- растут и ежемесячные расходы.
Из предложений - например для возрастных групп Студент и Знаток, которые отстают по расходам на услуги и времени на общения - провести различные спецакции, установить спецтарифы с целью вовлечения и удержания аудитории.

Шаг 4. Проведем кластерный анализ

Загрузка библиотек и методов
from scipy.cluster.hierarchy import linkage, dendrogram, fcluster
from sklearn.preprocessing import StandardScaler, MinMaxScaler
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

Отберем и стандартизируем признаки


X = df_clear[['day_calls_monthly', 'avr_monthly_exp']]
sc = MinMaxScaler()
X_scaler = sc.fit_transform(X)

Считаем матрицу попарных расстояний для признаков


z = linkage(X_scaler, method='average', metric='euclidean')
В первом и втором столбцах массива указаны индексы кластеров (в том числе синглетонов), объединяющих на текущей итерации. В третьем столбце находится расстояние между кластерами. В четвертом столбце- количество элементов в новом кластере (рис.ниже)

Построим дендрограмму из 7 последних кластеров.

Дадим оценку качества построенной кластеризации с помощью коэффициента кофенетической корреляции


from scipy.cluster.hierarchy import cophenet
from scipy.spatial.distance import pdist

c, coph_dists = cophenet(z, pdist(X_scaler))
c = 0.7595560173335718

Метрика Евклида показывает лучший коэффициент корреляции расстояния по центроиду

Посмотрим на структуру с помощью метода локтя

Bывод: по графику мы можем видеть, что для разбиения на кластеры подойдут значения 2,3, 5, 6, 10,12. Чем меньше расстояние между линиями тем лучше качество кластеризации, оптимально по графику 2 и 6

Посмотрим метод f-cluster

Выделим три макс. кластера по двум признакам и составим портрет клиентов по кластерам
df_clear['number_cluster'] = fcluster(z, t=3, criterion='maxclust')
df_clear.groupby('number_cluster')[['day_calls_monthly','avr_monthly_exp']].mean()
Вывод: согласно таблице, в первом кластере, в среднем, клиенты совершают ежедневных звонков за месяц 46, и тратят в среднем 267, во втором кластере -136, и траты в месяц составляют 519 и т.д.

Шаг 5. Сформулируем и проверим следующие гипотезы


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

____________________________

подключение методов для проверки гипотез о виде закона распределения,

from scipy.stats import shapiro
from scipy.stats import kstest
from scipy.stats import ks_2samp
from scipy.stats import anderson
from scipy.stats import jarque_bera

______________________

Сформулируем и проверим гипотезу о нормальности распределения признаков с помощью теста Шапиро

H_0 Признак 'evening_calls_monthly' имеет нормальное распределение

H_1 Признак 'evening_calls_monthly' имеет распределение, отличное от нормального

ShapiroResult(statistic=0.9721884727478027, pvalue=1.0613379830048937e-28)

pvalue(1.0613379830048937e-28) < alfa(0,05), гипотезу о нормальности распределения отвергаем! Принимаем гипотезу H1

___________________________________

Сформулируем и проверим гипотезу о том, имеют ли признаки одинаковое либо разные распределения с помощью теста Колмогорова-Смирнова

H_0 Признаки 'evening_calls_monthly', day_calls_monthly' имеют одинаковое распределение

H_1 Признаки 'evening_calls_monthly',day_calls_monthly' имеют разные распределения распределение

KstestResult(statistic=0.9792823370529647, pvalue=0.0, statistic_location=3.0, statistic_sign=-1)

KstestResult(statistic=0.9795049550404982, pvalue=0.0, statistic_location=3.0, statistic_sign=-1)

pvalue(0,0) < alfa(0,05), гипотезу об одинаковом распределении признаков отвергаем! Принимаем гипотезу H1

Рис 1. показывает, что признаки 'evening_calls_monthly' (ежемесячные звонки вечером-blue)

и 'day_calls_monthly' (ежемесячные звонки днем-red) имеют разные распределения и отличные от нормального

____________________________________




Рис.1

Сформулируем и проверим гипотезы с помощью критерия Манна-Уитни

H_0 Разница между количеством звонков совершаемых клиентами днем и вечером, статистически не значима

H_1 Количество звонков совершаемых клиентами днем меньше количества звонков совершаемых клиентами вечером

MannwhitneyuResult(statistic=7799836.5, pvalue=1.007006308442071e-77)

pvalue(1.007006308442071e-77) < alfa(0,05) - гипотезу Н_0 отвергаем, принимаем Н_1

_____________________________________

клиенты больше звонили в 2019 году по сравнению с 2021 годом по количеству звонков

H_0 Признак 'tariff_activation_date = 2019' по отношению к клиентам имеет нормальное распределение

H_1 Признак 'tariff_activation_date = 2019' по отношению к клиентам имеет распределение, отличное от нормального

ShapiroResult(statistic=0.8143370747566223, pvalue=0.14920618818837372)

pvalue(0,14) > alfa(0,05), гипотезу H_0 о нормальности распределения принимаем

__________________________________________

Сформулируем и проверим гипотезу о том, подчиняются ли признаки закону о нормальном распределения с помощью теста Колмогорова-Смирнова

H_0 Обе выборки (df.tariff_activation_date == 2019/df.tariff_activation_date == 2021) подчиняются закону о нормальном распределении

H_1 Обе выборки (df.tariff_activation_date == 2019/df.tariff_activation_date == 2021) подчиняются закону, отличному от закона о нормальном распределении

KstestResult(statistic=0.3333333333333333, pvalue=1.0, statistic_location=42137.0, statistic_sign=1)

pvalue(1.0)>alfa(0,05) - принимаем гипотезу H_0

_____________________________________________

H_0 Разница между количеством звонков совершаемых клиентами в 2019 и 2021 годах, статистически не значима.

H_1 Количество звонков совершаемых клиентами в 2019 меньше количества звонков совершаемых клиентами в 2021.

MannwhitneyuResult(statistic=4.0, pvalue=0.5)

pvalue(0,5)>alfa(0,05) - принимаем гипотезу Н_0


Заключение:

Критерий Манна-Уитни показал, что разница между количеством звонков совершаемых клиентами в 2019 и 2021 годах, статистически не значима, т.е. клиенты одинаково совершают звонки как в 2019, так и в 2021. А количество звонков, совершаемых клиентами днем, меньше количества звонков, совершаемых клиентами вечером.

СЕГМЕНТАЦИЯ ЗАЕМЩИКОВ БАНКА ПО КРЕДИТНОЙ ИСТОРИИ


Аналитическая задача — провести анализ данных с целью выделения портретов заемщиков по каждой группе целевого признака.
План исследования
Шаг 1. Загрузка данных;
Шаг 2. Первичная обработка данных (при необходимости):
  • скорректировать заголовки;
  • скорректировать типы признаков;
  • проверить наличие дублирующихся записей;
  • проверить наличие аномальных значений;
  • восстановить пропущенные значения;
Шаг 3. Добавление новых признаков:
  • для каждого клиента рассчитаем его возраст на настоящий момент времени (на 2023 год);
Шаг 4. Исследовательский анализ данных
в разрезе значений целевого признака (Дисциплина клиентов без просрочки по кредиту) исследуем распределения числовых и категориальных признаков;
  • в разрезе значений целевого признака составим портреты клиентов платежной системы.
Подключение библиотек:
import pandas as pd
import numpy as np
import datetime as dt
import matplotlib.pyplot as plt
import seaborn as sns

Шаг 1. Загрузка данных

df = pd.read_csv('/content/dataset_segment_bank.csv')
df.head(2)
df.info() - смотрим типы признаков и пропущенные значения
class 'pandas.core.frame.DataFrame'>
RangeIndex: 50224 entries, 0 to 50223
Data columns (total 14 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Идентификатор 50224 non-null int64
1 Дата рождения 50224 non-null object
2 Дисциплина клиентов без просрочки по кредиту 50223 non-null object
3 Количество переводов 50158 non-null float64
4 Тип переводов 50191 non-null float64
5 География переводов 50191 non-null float64
6 География телефона 48324 non-null float64
7 Сумма перевода 50158 non-null float64
8 Максимальная сумма перевода 50158 non-null float64
9 Средняя сумма перевода 50158 non-null float64
10 Полная сумма перевода 50158 non-null float64
11 Канал, через который пришел клиент 50193 non-null object
12 Оператор связи 49579 non-null object
13 Пол 50215 non-null object
dtypes: float64(8), int64(1), object(5)
memory usage: 5.4+ MB

Смотрим описательные статистики, выявляем аномальные значения

Выделим категориальные и интервальные признаки в датафрейме


vars_cont = ['количество переводов', 'сумма перевода',
'максимальная сумма перевода']

vars_cat = ['дисциплина клиента', 'география переводов', 'география телефона',
'канал клиента', 'оператор связи', 'пол', 'тип переводов']

Шаг 2. Первичная обработка данных

Этапы:
  • скорректируем заголовки - df.columns - df.columns.str.lower()
  • скорректируем типы признаков - df = df.astype({'дата рождения': 'datetime64'})
  • проверим наличие дублирующихся записей, удалить - df.drop_duplicates(keep='first', inplace = True), и удалим дублирующий столбец df.drop(columns = ['полная сумма перевода']
  • проверим наличие аномальных значений - выделим 3 признака
  • восстановим пропущенные значения;

Поиск и обработка аномальных аномальных значений


Делим признаки на непрерывные и категориальные:

vars_cont = ['количество переводов', 'сумма перевода',
'максимальная сумма перевода']
vars_cat = ['дисциплина клиента', 'география переводов', 'география телефона',
'канал клиента', 'оператор связи', 'пол', 'тип переводов']

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


---- начало признака дисциплина клиента ----
GOOD 26852
MIDDLE 13577
BAD 9219
Name: дисциплина клиента, dtype: int64
---- конец признака дисциплина клиента ----
---- начало признака география переводов ----
77.0 19144 78.0 4140 61.0 2338 23.0 2113 66.0 1502 ... 75.0 2 498001.0 2 6.0 1 20.0 1 19.0 1
Name: география переводов, Length: 74, dtype: int64
---- конец признака география переводов ----
---- начало признака география телефона ----
77.0 13610, 78.0 5468, 23.0 2457, 61.0 2266, 66.0 1638,..0.0 2, 25.0 1, 38.0 1, 75.0 1, 14.0 1
Name: география телефона, Length: 64, dtype: int64
---- конец признака география телефона ----
---- начало признака канал клиента ----
Офис 24876
Партнер 20560
Стойка 4182
Name: канал клиента, dtype: int64
---- конец признака канал клиента ----
---- начало признака оператор связи ----
МегаФон 14221 Вымпел-Коммуникации 13463 Мобильные ТелеСистемы 12251 МобильныеТелеСистемы 1805 Санкт-Петербург Телеком 1143 Ростовская Сотовая Связь 1120 Вотек Мобайл 922 ЕКАТЕРИНБУРГ-2000 601 Ростелеком 595 Челябинская Сотовая Связь 534 Нижегородская сотовая связь 455 Кемеровская Мобильная Связь 361 Смоленская Сотовая Связь 246 Сибирская Сотовая Связь 187 Персональные Системы Связи в Регионе 167 Волгоград-GSM 142 Телеком Евразия 111 Липецк Мобайл 108 Сотовая Связь Удмуртии 105 Новгородские телекоммуникации 91 Средневолжская межрегиональная ассоциация радиотел 74 Телесет Лтд. 56 Мурманская Мобильная Сеть 46 Белгородская Сотовая Связь 43 Московская сотовая связь 35 Дельта Телеком 25 Пенза-GSM 25 Ниж-ская сот.связь 21 Оренбург-GSM 14 Астрахань GSM 7 Ярославль-GSM 5 Курская Сотовая Связь 4 Енисейтелеком 4 Астарта 3 Кодотел 2 Парма Мобайл 2 Сот.св.Башкортостана 2
Байкалвестком 2 Адыгейская сотовая связь 1 Финансовая компания ИМПЕРАТИВ 1 Новая телефонная компания 1 Беспровод. инф. технологи 1 Архангельские Мобильные Сети 1 СИБИНТЕРТЕЛЕКОМ 1 Скай Линк 1
Name: оператор связи, dtype: int64
---- конец признака оператор связи ----
---- начало признака пол ----
Ж 25212
М 24428
Name: пол, dtype: int64
---- конец признака пол ----
---- начало признака тип переводов ----
2.0 18673 5.0 13778 69.0 6527 6.0 2542 8.0 1991 1.0 1633 10.0 1549 0.0 739 12.0 468 26.0 289 32.0 253 16.0 248 37.0 220 11.0 194 45.0 157 7.0 139 44.0 62 58.0 45 3.0 30 21.0 14 28.0 14 33.0 7 29.0 6 4.0 5 23.0 5 19.0 5 17.0 3 55.0 3 54.0 3 34.0 2 53.0 2 59.0 2 61.0 2 43.0 1 67.0 1 30.0 1 -1.0 1 9.0 1 49.0 1
Name: тип переводов, dtype: int64
---- конец признака тип переводов ----

Проведем исследование отдельно категориальных признаков: `география переводов`, география телефона`, оператор связи, тип переводов`.


Для оператора связи переименуем и выделим основные операторы с наибольшими значениями, остальные с небольшими значениями отнесем к другой общей группе:
dict_replace = {'оператор связи': {'Мобильные ТелеСистемы': 'МТС',
'МобильныеТелеСистемы': 'МТС', 'Ниж-ская сот.связь': 'Нижегородская сотовая связь'}}
df.replace(dict_replace, inplace = True)
set_to_replace = set(df['оператор связи'].unique()) - set(['МегаФон', 'МТС', 'Вымпел-Коммуникации'])
dict_to_replace = dict.fromkeys(list(set_to_replace), 'Региональная телекомкомпания')
df['оператор связи'].value_counts()
Получаем: МТС 9527 МегаФон 9143 Вымпел-Коммуникации 8916 Региональная телекомкомпания 5640
Для типа перевода переименуем и выделим отдельную группу с наименьшими значениями
set_to_replace = set(df['тип переводов'].unique()) - set([2,5,69,6,8,1,10])
dict_to_replace = dict.fromkeys(list(set_to_replace), 'Другие переводы')
df['тип переводов'].value_counts()
Получаем 2.0 - 13316, 5.0 - 7714, 69.0 - 6495, 1.0 - 1587, Другие цели переводов - 1482
Для географии телефона и географии перевода создадим столбец и сформируем две выборки
df_no_anomals['откуда_перевод'] = np.where(df_no_anomals['география переводов'] == df_no_anomals['география телефона'], 'из родного региона', 'из другого региона')
Получаем: из родного региона 26038, из другого региона 7188

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

Напишем фильтр на аномальные значения по интервальным признакам и сформируем новый датафрейм


filter_no_anomals = ((df['количество переводов'].between(min(df['количество переводов']), 114))
& ((df['сумма перевода'].between(min(df['сумма перевода']),
np.percentile(df['сумма перевода'].dropna(), 99))))
& (df['максимальная сумма перевода'].between(min(df['максимальная сумма перевода']),
np.percentile(df['максимальная сумма перевода'].dropna(), 99)))
)
df_no_anomals = df.loc[filter_no_anomals]

Исследуем пропущенные значения

df_no_anomals.isna().sum()
идентификатор 0 дата рождения 0 дисциплина клиента 1 количество переводов 0
тип переводов 0 география переводов 0 география телефона 1797 сумма перевода 0
максимальная сумма перевода 0 средняя сумма перевода 0 канал клиента 24
оператор связи 0 пол 7 откуда_перевод 0

Заменим пропуски на значения
values = {"география телефона": "не указана", "оператор связи": "не указан"}
df_no_anomals.fillna(value=values, inplace = True)

Шаг 3. Добавление новых признаков:

для каждого клиента рассчитаем его возраст на настоящий момент времени (на 2023 год);
df_no_anomals['возраст'] = np.floor((dt.datetime.now() - df_no_anomals['дата рождения']) / np.timedelta64(1, "Y"))
df_no_anomals.loc[:, 'возраст'] = np.floor((dt.datetime.now() - df_no_anomals['дата рождения']) / np.timedelta64(1, "Y"))

Шаг 4. Исследовательский анализ данных

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

Целевой признак
df_no_anomals['дисциплина клиента'].unique()
array(['BAD', 'GOOD', 'MIDDLE'], dtype=object)
Числовой признак ['количество переводов', 'сумма перевода', 'максимальная сумма перевода', 'возраст']

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

Рассчитаем доли по признакам и нормируем показатели

columns = ['канал клиента', 'оператор связи', 'пол', 'тип переводов', 'откуда_перевод']

for col in columns:
df_pt_canal = pd.pivot_table(data = df_no_anomals,
index = 'дисциплина клиента',
columns = col,
values = 'идентификатор',
aggfunc = 'count')
df_pt_canal
(np.round(df_pt_canal / df_pt_canal.sum() * 100,2))

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

from sklearn.preprocessing import LabelEncoder
for col in columns:
le = LabelEncoder()
df_copy[col] = le.fit_transform(df_copy[col])

Отбираем признаки [ количество переводов, сумма перевода, откуда_перевод, оператор связи, канал] согласно коэффициентам корреляции для составления портрета клиента и проверяем гипотезы

Проверим гипотезы в отношении непрерывных признаков

[ количество переводов, сумма перевода, возраст]

Формируем выборки

X = df_no_anomals[df_no_anomals['дисциплина клиента'] == 'BAD']['количество переводов']

Y = df_no_anomals[df_no_anomals['дисциплина клиента'] == 'GOOD']['количество переводов']

проверяем нормальность распределения при помощи теста Колмогорова-Смирнова

H_0 Признак 'количество переводов' имеет нормальное распределение

H_1 Признак 'количество переводов' имеет распределение, отличное от нормального

KstestResult (statistic=0.0766741564329253, pvalue=7.390241215370946e-47, statistic_location=45.0, statistic_sign=1)

т.к. 0 = p_value < alpha = 0.05, то гипотезу о нормальном законе распределения X отвергаем, принимаем гипотезу H_1

-------------------

проверяем гипотезу в отношении параметров -- медиана, при помощи критерия Манна-Уитни и теста Краскела-Уоллиса

H_0: количество переводов клиентов с дисциплиной <<BAD>> не отличается

от количества переводов клиентов с дисциплиной <<GOOD>>

H_1: количество переводов клиентов с дисциплиной <<BAD>> меньше

количества переводов с клиентов дисциплиной <<GOOD>>

MannwhitneyuResult(statistic=93928805.0, pvalue=6.09947592155125e-192)

т.к. 2.947261207847815e-205 = p_value < alpha = 0.05, то гипотезу о равенстве отвергаем

KruskalResult(statistic=871.9653915351048, pvalue=1.219873554501589e-191), результат аналогичен, гипотезу о равенстве отвергаем, принимаем H_1 (количество переводов клиентов с дисциплиной <<BAD>> отличается от

количества переводов с клиентов дисциплиной <<GOOD>>

--------------------

X = df_no_anomals[df_no_anomals['дисциплина клиента'] == 'BAD']['сумма перевода"]

Y = df_no_anomals[df_no_anomals['дисциплина клиента'] == 'MIDDLE']['сумма перевода']

H_0: сумма перевода клиентов с дисциплиной <<BAD>> не отличается

от суммы перевода клиентов с дисциплиной <<MIDDLE>>

H_1: сумма перевода клиентов с дисциплиной <<BAD>> меньше

суммы перевода клиентов с дисциплиной <<MIDDLE>>

KstestResult(statistic=0.1742165978834355, pvalue=2.0856105096193907e-241, statistic_location=30000.0, statistic_sign=-1)

т.к. 2.0856105096193907e-241= p_value < alpha = 0.05, то гипотезу о нормальном законе распределения X отвергаем, принимаем гипотезу H_1

MannwhitneyuResult(statistic=50352526.0, pvalue=9.005618664243352e-89)

т.к. 9.005618664243352e-89 = p_value < alpha = 0.05, то гипотезу о равенстве отвергаем

KruskalResult(statistic=397.63607891297033, pvalue=1.8010854742980258e-88)

т.к. 1.8010854742980258e-88 = p_value < alpha = 0.05, то гипотезу о равенстве отвергаем

----------------------------

X = df_no_anomals[df_no_anomals['дисциплина клиента'] == 'BAD']['возраст]

Y = df_no_anomals[df_no_anomals['дисциплина клиента'] == 'GOOD']['возраст']

H_0: возраст клиентов с дисциплиной <<BAD>> не отличается

от возраста клиентов с дисциплиной <<GOOD>>

H_1: возраст клиентов с дисциплиной <<BAD>> меньше

возраста клиентов с дисциплиной <<GOOD>>

KstestResult(statistic=0.07667823062627172, pvalue=7.306656320761953e-47, statistic_location=45.0, statistic_sign=1)

т.к. 7.306656320761953e-47= p_value < alpha = 0.05, то гипотезу о нормальном законе распределения X отвергаем, принимаем гипотезу H_1

MannwhitneyuResult(statistic=93928584.5, pvalue=6.051812863407449e-192)

т.к. 6.051812863407449e-192 = p_value < alpha = 0.05, то гипотезу о равенстве отвергаем

KruskalResult(statistic=871.9810635613217, pvalue=1.210341111705094e-191)

т.к. 1.210341111705094e-191 = p_value < alpha = 0.05, то гипотезу о равенстве отвергаем

Гипотезы в отношении категориальных признаков

[ канал клиента, откуда_перевод, оператор связи ]

[ канал клиента]

Для биномиальных распределений применяем z-критерий и рассчитываем по формуле

z_value = (m1/n1 - m2/n2) / math.sqrt(((m1+m2)/(n1+n2))*(1-((m1+m2)/(n1+n2)))*(1/n1+1/n2))

test_z_criterion(n1, n2, m1, m2)

Результаты проверки гипотезы H_0 по z-критерию:

Уровень значимости alpha=0.05

P-value: 0.00

Отвергаем нулевую гипотезу: разница в долях статистически значима.

[откуда_перевод]

Результаты проверки гипотезы H_0 по z-критерию:

Уровень значимости alpha=0.05

P-value: 0.00

Отвергаем нулевую гипотезу: разница в долях статистически значима.

[оператор связи]

Результаты проверки гипотезы H_0 по z-критерию:

Уровень значимости alpha=0.05

P-value: 0.00

Отвергаем нулевую гипотезу: разница в долях статистически значима.

Выводы:

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

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

Если клиент делает меньше 137000 переводов, то с высокой долей вероятности, по оценке банка, он Плохой заемщик, от 183000 переводов- Средний заемщик.

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

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

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

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


АНАЛИЗ КРЕДИТНЫХ ПРЕДЛОЖЕНИЙ


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

Шаг 1. Загрузка данных;
Шаг 2. Первичная обработка данных (при необходимости):
  • скорректировать заголовки;
  • скорректировать типы признаков;
  • проверить наличие дублирующихся записей;
  • проверить наличие аномальных значений;
  • восстановить пропущенные значения;
Шаг 3. Провести исследовательский анализ данных:
  • исследовать распределения признаков;
  • исследовать возможные зависимости между признаками;
Шаг 4. Отбор признаков и портреты клиентов:
  • исследовать признаки в разрезе групп;
  • выявить наличие значимых различий в значениям признаков в разрезе групп;
  • сформулировать и проверить гипотезы о равенстве значений показателей по группам
Шаг 5. Построить классификационные модели
  • Построить несколько моделей классификации и сравнить их.
  • построить классификационные модели для прогнозирования значений целевой переменной.

Шаг 1. Загрузка данных


Подключение библиотек:
import pandas as pd
import numpy as np
import datetime as dt
import matplotlib.pyplot as plt

import seaborn as sns

df = pd.read_csv('/content/vkr_dataset_open_credit_card.csv')

df.head(2)

Шаг 2. Первичная обработка данных

Корректировка заголовков

корректировка не требуется.

Корректировка типов признаков

df.info()

RangeIndex: 10464 entries, 0 to 10463
Data columns (total 18 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 client_id 10464 non-null int64 1 gender 10464 non-null int64 2 age 10462 non-null object 3 marital_status 10459 non-null object 4 job_position 10464 non-null int64
5 credit_sum 10463 non-null float64 6 credit_month 10464 non-null int64 7 score_shk 10461 non-null float64 8 education 10461 non-null object 9 tariff_id 10464 non-null int64
10 living_region 10455 non-null object 11 okrug 10455 non-null object
12 avregzarplata 10455 non-null object 13 monthly_income 10463 non-null object
14 credit_count 9874 non-null object 15 overdue_credit_count 9873 non-null object
16 open_account_flg 10463 non-null float64 17 date_bank 10463 non-null object

dtypes: float64(3), int64(5), object(10)

df = df.astype({'date_bank': 'datetime64', 'age': 'float64', 'marital_status':'float64',' education':'float64', 'living_region':'float64', 'okrug':'float64', 'avregzarplata':'float64', 'monthly_income':'float64','credit_count':'float64', 'overdue_credit_count':'float64'})

поиск дублирующих записей

df.duplicated().sum() 0

Проверка на аномальные значения

Выделим признаки непрерывные и категориальные

vars_cont = ['age','credit_sum','score_shk','avregzarplata','monthly_income']

vars_cat = ['gender','marital_status', 'job_position','credit_month'\

,'education','tariff_id','living_region','okrug','credit_count'\

,'overdue_credit_count','open_account_flg']

Исследуем категориальные признаки

просмотр уникальных значений признаков и их распределение


---- начало признака gender ----
1 88697
2 82049
Name: gender, dtype: int64
---- конец признака gender ----
---- начало признака marital_status ----
3.0 93952 4.0 52148 2.0 16970 1.0 4196 5.0 3475
Name: marital_status, dtype: int64
---- конец признака marital_status ----
---- начало признака job_position ----
14 134680 15 17674 2 5591 10 4107 4 3750 1 2791 17 656 8 537 16 352 6 241 3 126 18 110 11 65 13 40 12 12 5 8 7 5 9 1
Name: job_position, dtype: int64
---- конец признака job_position ----
---- начало признака credit_month ----
10 95724 12 45341 6 10203 24 6080 18 2962 14 2578 4 1400 3 1350 8 1015 13 836 9 627 36 500 11 482 5 400 16 394 7 387 15 230 20 114 17 38 19 17 22 11 23 11 30 11 28 8 26 8 21 7 32 4 25 4 27 2 31 1 29 1
Name: credit_month, dtype: int64
---- конец признака credit_month ----
---- начало признака education ----
4.0 87537 2.0 72593 5.0 9941 3.0 565 1.0 107
Name: education, dtype: int64
---- конец признака education ----
---- начало признака tariff_id ----
2 69355 28 39117 19 15537 20 10970 25 7497 30 5538 22 3930 18 3339 3 3232 1 2245 23 2228 6 2102 29 1007 8 766 4 717 9 579 7 540 32 414 10 376 11 370 31 317 12 303 21 132 13 56 5 36 14 11 16 10 26 7 15 6 24 5 27 2 33 1 17 1
Name: tariff_id, dtype: int64
---- конец признака tariff_id ----
---- начало признака living_region ----
31.0 12844
30.0 9261
73.0 8735
23.0 8355
63.0 8303
...
45.0 54
81.0 32
78.0 31
49.0 19
79.0 17
Name: living_region, Length: 81, dtype: int64
---- конец признака living_region ----
---- начало признака okrug ----
7.0 39049 2.0 36092 6.0 22552 3.0 22303 5.0 18602 8.0 18554 1.0 7074 4.0 6328
Name: okrug, dtype: int64
---- конец признака okrug ----
---- начало признака credit_count ----
1.0 45279 2.0 36998 0.0 25806 3.0 23918 4.0 14113 5.0 7543 6.0 3915 7.0 1962 8.0 1007 9.0 476 10.0 220 11.0 129 12.0 74 13.0 33 14.0 15 15.0 13 17.0 5 16.0 4 19.0 3 18.0 2 21.0 1
Name: credit_count, dtype: int64
---- конец признака credit_count ----
---- начало признака overdue_credit_count ----
0.0 154135 1.0 7326 2.0 50 3.0 5
Name: overdue_credit_count, dtype: int64
---- конец признака overdue_credit_count ----
---- начало признака open_account_flg ----
0 140690 1 30056
Name: open_account_flg, dtype: int64

---- конец признака open_account_flg ----

Сократим признак job_position до 6 значений "14, 15, 2, 10, 4, 1"

Сократим признак education до 4 значений "2.0. 3.0 4.0 5.0"

Сократим признак credit_count до 6 значений "0.0 1.0 2.0. 3.0 4.0 5.0"

Сократим признак okrug до 7 значений "1.0 2.0 3.0. 4.0 5.0 6.0 7.0"

Признак overdue_credit_count сократим до значений 1 и 0

Исследуем непрерывные признаки

У признаков age, credit_sum, monthly_income отсечем аномалии по границам усов,

признак avregzarplata - отсечем после 99 квантили

Потеря данных составила 14,22%

Шаг 3. Исследовательский анализ данных:

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

Исследуем возможные зависимости между признаками c помощью тепловой карты

По коэффициентам корреляции с целевым признаком отбираем независимые признаки [gender, job_position, credit_sum, score_shk, tariff_id, education, okrug] для составления портрета клиента

Шаг 4. Отбор признаков и портреты клиентов:

Сформулируем и проверим гипотезы в отношении непрерывных признаков

vars_cont = ['credit_sum', 'score_shk']

Формируем выборки

X = df_no_anomals[df_no_anomals['open_account_flg'] == 0]['credit_sum']

Y = df_no_anomals[df_no_anomals['open_account_flg'] == 1]['credit_sum']

проверяем нормальность распределения при помощи теста Колмогорова-Смирнова

H_0 Признак 'credit_sum' имеет нормальное распределение

H_1 Признак 'credit_sum'' имеет распределение, отличное от нормального

KstestResult(statistic=0.09616544557312695, pvalue=0.0, statistic_location=20008.0, statistic_sign=1)

т.к. 0.0 = p_value < alpha = 0.05, то гипотезу о нормальном законе распределения X отвергаем, принимаем гипотезу H_1

___________________________________

Проверяем гипотезу в отношении параметров -- медиана, при помощи критерия Манна-Уитни и теста Краскела-Уоллиса

H_0: сумма кредита клиентов с неоткрытым кредитным счетом в банке не отличается

от суммы кредита клиентов с открытым кредитным счетом

H_1: сумма кредита клиентов с неоткрытым кредитным счетом в банке больше

суммы кредита клиентов с открытым кредитным счетом

MannwhitneyuResult(statistic=1477416854.5, pvalue=1.3967706945421116e-124)

KruskalResult(statistic=562.1995058558035, pvalue=2.793535433495171e-124)

т.к. 1.3967706945421116e-124= p_value < alpha = 0.05, то гипотезу о равенстве параметров H_0 отвергаем, принимаем гипотезу H_1

_____________________________________________

-------[ 'score_shk']-------

проверяем нормальность распределения при помощи теста Колмогорова-Смирнова

H_0 Признак 'score_shk' имеет нормальное распределение

H_1 Признак 'score_shk' имеет распределение, отличное от нормального

KstestResult(statistic=0.026520177299070802, pvalue=1.0552911752083077e-70, statistic_location=0.441129, statistic_sign=1)

т.к. 1.0552911752083077e-70 = p_value < alpha = 0.05, то гипотезу о нормальном законе распределения X отвергаем, принимаем гипотезу H_1

___________________________

проверяем гипотезу в отношении параметров -- медиана, при помощи критерия Манна-Уитни и теста Краскела-Уоллиса

проверяем гипотезу в отношении параметров -- медиана

H_0: внутренняя скоринговая оценка банка клиентов с неоткрытым кредитным счетом в банке не отличается

от внутренней скоринговой оценки банка клиентов с открытым кредитным счетом

H_1: внутренняя скоринговая оценка банка клиентов с неоткрытым кредитным счетом в банке меньше

внутренней скоринговой оценки банка клиентов с открытым кредитным счетом

MannwhitneyuResult(statistic=53930.5, pvalue=0.4571374765169218)

KruskalResult(statistic=0.011591325450781226, pvalue=0.9142630062808981)

т.к. 0.4571374765169218= p_value > alpha = 0.05, то гипотезу о равенстве параметров H_0 принимаем, гипотезу H_1 отвергаем

________________________________________________________________________________

Гипотезы в отношении категориальных признаков

[gender, job_position, tariff_id, education, credit_count, okrug]

[ gender]

Для биномиальных распределений применяем z-критерий и рассчитываем по формуле

z_value = (m1/n1 - m2/n2) / math.sqrt(((m1+m2)/(n1+n2))*(1-((m1+m2)/(n1+n2)))*(1/n1+1/n2))

test_z_criterion(n1, n2, m1, m2)

Результаты проверки гипотезы H_0 по z-критерию:

Уровень значимости alpha=0.05

P-value: 0.00

Отвергаем нулевую гипотезу: разница в долях статистически значима.

[ okrug]

Сравним 1.0 & 3.0 и 1.0 & 7.0, среди не открывших карту

Результаты проверки гипотезы H_0 по z-критерию:

Уровень значимости alpha=0.05

P-value: 0.00

Отвергаем нулевую гипотезу: разница в долях статистически значима.

[job_position]

Сравним 1.0 и 'квал_позиции', среди не открывших карту

Сравним 1.0 и 'квал_позиции', среди открывших карту

Результаты проверки гипотезы H_0 по z-критерию:

Уровень значимости alpha=0.05

P-value: 0.00

Отвергаем нулевую гипотезу: разница в долях статистически значима.

[credit_count]

Сравним 0.0 и 0.5, среди не открывших карту, и, среди открывших карту

Результаты проверки гипотезы H_0 по z-критерию:

Уровень значимости alpha=0.05

P-value: 0.00

Отвергаем нулевую гипотезу: разница в долях статистически значима


Портреты клиентов

На основе проведенных исследований, можно сделать вывод, что с высокой долей вероятности, клиент, открывший карту, будет иметь сл. характеристики:

age (возраст) - до 35

gender (пол) - 2

job_position (работа) - 4

education (образование) -4

okrug (округ) - 1

score_shk (внутренняя скоринговая оценка) - 0,4572

Клиент, склонный не открывать карту, с высокой долей вероятности, будет иметь следующие характеристики:

age(возраст) - от 35

gender (пол)r - 1

job_position (работа) - 10

education (образование) - 2

okrug (округ) - 3

score_shk (внутренняя скоринговая оценка) - 0.467237

Шаг 5. Построим классификационные модели

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

Загрузка библиотек, классификаторов и метрик


from sklearn.preprocessing import StandardScaler, MinMaxScaler
from sklearn.model_selection import train_test_split, GridSearchCV

классификаторы
from sklearn.tree import DecisionTreeClassifier, plot_tree # дерево решений
from sklearn.ensemble import RandomForestClassifier #случайный лес
from sklearn.linear_model import LogisticRegression # логистическая регрессия
from sklearn.svm import SVC # метод опорных векторов
from sklearn.linear_model import SGDClassifier # стохастический градиентный спуск

метрики
from sklearn.metrics import confusion_matrix
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
from sklearn.metrics import classification_report

Сформируем признаковое пространство


select_columns = ['credit_sum','score_shk', 'gender', 'job_position' ,'education','tariff_id', 'okrug','credit_count']
y = df_no_anomals.open_account_flg
x = df_no_anomals[select_columns]

Балансируем показатели с помощью SMOTE


from imblearn.over_sampling import SMOTE
smote = SMOTE(random_state = 42)
x_res, y_res = smote.fit_resample(x,y)

Разделим признаки на обучающее и тестирующее множества


x_train, x_test, y_train, y_test = train_test_split(x,y,
test_size=0.3,
random_state=0)

С помощью StandartScaler преобразуем независимые переменные


st_scaler = StandardScaler()
st_scaler.fit(x_train)
x_train_st = st_scaler.transform(x_train)
x_test_st = st_scaler.transform(x_test)

При помощи Логрегрессии построим прогнозную модель

lr = LogisticRegression(random_state=0)
lr.fit(x_train_st, y_train)
y_pred = lr.predict(x_test_st)
print(classification_report(y_test, y_pred))

При помощи Случайного леса построим прогнозную модель

lrnd = RandomForestClassifier(random_state=0)
rnd.fit(x_train_st, y_train)
rnd.feature_importances_
rnd.score(x_test_st, y_test)
y_pred = rnd.predict(x_test_st)
print(classification_report(y_test, y_pred))

Bыводы: Обе прогнозные модели показывают значения в пользу класса 0 (клиента, склонного не открывать карту). Модель с RandomForestClassifier показывает лучше результаты, чем модель с LogisticRegression, где по некоторым показателям ( Полнота, F-мера) значения выше.

A/B-тест для мобильного приложения


Имеется мобильное приложение для магазина по продаже продуктов питания. Выдвинута гипотеза о том, что смена шрифтов улучшит качество обслуживания по количеству клиентов, совершающих каждое событие. Для проверки этой гипотезы принято решение провести A/B-тест. Пользователей разбили на 2 группы: 247 -- контрольная группа со старыми шрифтами, 248 -- экспериментальная с новыми шрифтами.

Инструкция по выполнению проекта

Шаг 1. Загрузка данных
  • Знакомство с данными;
  • Корректность a/b-теста.
Шаг 2. Подготовка данных
  • Корректировка заголовков;
  • Типы данных;
  • Аномалии, пропуски.
Шаг 3. EDA
  • Cколько всего событий?
  • Сколько всего пользователей в логе?
  • Сколько в среднем событий приходится на пользователя?
  • Период теста: максимальная и минимальная даты; гистограмма по дате и количеству событий.
Шаг 4. Анализ воронки событий
  • Распределение событий: какие события и в каком количестве.
  • Сколько пользователей совершали каждое из этих событий?
  • Постройте воронку событий: какая доля пользователей проходит на следующий шаг воронки. На каком шаге теряете больше всего пользователей? Какая доля пользователей доходит от первого события до оплаты?
Шаг 5. Анализ результатов эксперимента
  • Сколько пользователей в каждой группе?
  • Посчитайте долю пользователей, совершивших каждое из событий.
  • Проверьте гипотезу о наличие значимых отличий по результатам теста.
Подключение библиотек:
import pandas as pd
import numpy as np
import datetime as dt
import matplotlib.pyplot as plt
import seaborn as sns

Шаг 1. Загрузка данных
df = pd.read_csv('/content/17.11.23+ab_test_home.csv')
df
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 84249 entries, 0 to 84248
Data columns (total 4 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 EventName 84249 non-null object
1 DeviceIDHash 84249 non-null int64
2 EventTimestamp 84248 non-null object
3 ExpId 84248 non-null float64
dtypes: float64(1), int64(1), object(2)
memory usage: 2.6+ MB

Шаг 2. Подготовка данных

Корректируем заголовки и типы признаков

df.columns = ['event_name','user_id','event_time','group_id']
df.event_time = df.event_time.astype(np.datetime64)

Исследуем пропуски и дубликаты


df.isna().sum()
event_name 0 user_id 0 event_time 1 group_id 1
df.dropna(inplace=True)

df.drop_duplicates(keep='first', inplace=True)

Проведем исследование, посмотрим корректность а/b теста

df.event_name.unique()
df.group_id.unique()
print(df.event_name.value_counts())
print(df.group_id.value_counts())

df.groupby('group_id').user_id.nunique()
247.0 1210 248.0 1181

Проверим, что одни и те же пользователи не состоят в разных группах, при помощи пересечения множеств
len(set(df[df.group_id == 247].user_id).intersection(set(df[df.group_id == 248].user_id))) =0

Шаг 3. EDA Исследовательский анализ


Cколько всего событий?
df.shape[0] - 84071
df.event_name.nunique(), уникальрных - 5 (Payment, ScreenSuccessful, CartScreenAppear, MainScreenAppear, Tutorial, OffersScreenAppear)
Сколько всего пользователей в логе?
df.user_id.nunique() - Всего пользователей в логе 4303
Сколько в среднем событий приходится на пользователя?
df.shape[0] / df.user_id.nunique() - 19.54
Период теста: максимальная и минимальная даты; гистограмма по дате и количеству событий.
df.event_time.agg(['min', 'max'])
min 2019-07-25 11:28:47 max 2019-08-04 12:36:33

Построим гистограмму по дате и количеству событий


выделим день/дату совершения события
df['event_day'] = df['event_time'].dt.date
df.groupby('event_day').user_id.count().reset_index()

Оставим для исследования период с наибольшим кол-вом событий в день


df['event_time_str'] = df['event_time'].dt.strftime('%Y-%m-%d')
df_ab = df.loc[df['event_time_str'] >= '2019-08-01']

Шаг 4. Анализ воронки событий


Распределение событий: какие события и в каком количестве

Сколько пользователей совершали каждое из этих событий

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

Шаг 5. Анализ результатов эксперимента

.

Сколько пользователей в каждой группе


print(df_ab[df_ab['group_id'] == 247].user_id.nunique(), df_ab[df_ab['group_id'] == 248].user_id.nunique()) = 2111 и 2108

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

Сформулируем и проверим гипотезу о наличие значимых отличий по результатам теста


H0: Статистически значимая разница между долями пользователей из групп 247 и 248, совершивших одно и то же событие, отсутствует

H1: Имеется статистически значимая разница между долями пользователей из групп 247 и 248, совершивших одно и тоже событие

--------------------------------------------------------

z_value = (m1/n1 - m2/n2) / math.sqrt(((m1+m2)/(n1+n2))*(1-((m1+m2)/(n1+n2)))*(1/n1+1/n2))
distr = st.norm(0, 1)
p_value = (1 - distr.cdf(abs(z_value)))
_______________________________________________________________________
Результаты проверки гипотезы H_0 по z-критерию:
Уровень значимости alpha=0.05
P-value: 0.69
Нулевая гипотеза принимается: разница в долях по
событию <<MainScreenAppear>> статистически незначима.
------------------------------------------------------------
Результаты проверки гипотезы H_0 по z-критерию:
Уровень значимости alpha=0.05
P-value: 0.96
Нулевая гипотеза принимается: разница в долях по
событию <<OffersScreenAppear>> статистически незначима.
------------------------------------------------------------
Результаты проверки гипотезы H_0 по z-критерию:
Уровень значимости alpha=0.05
P-value: 0.59
Нулевая гипотеза принимается: разница в долях по
событию <<CartScreenAppear>> статистически незначима.
------------------------------------------------------------
Результаты проверки гипотезы H_0 по z-критерию:
Уровень значимости alpha=0.05
P-value: 0.29
Нулевая гипотеза принимается: разница в долях по
событию <<PaymentScreenSuccessful>> статистически незначима.
------------------------------------------------------------
Результаты проверки гипотезы H_0 по z-критерию:
Уровень значимости alpha=0.05
P-value: 0.44
Нулевая гипотеза принимается: разница в долях по
событию <<Tutorial>> статистически незначима.
Вывод: Выдвинутая гипотеза о том, что смена шрифтов улучшит качество обслуживания по количеству клиентов, совершающих каждое событие, не подтверждается, так как отсутствует разница в долях пользователей из обеих групп, совершивших одно и то же событие.

This site was made on Tilda — a website builder that helps to create a website without any code
Create a website