Microsegment.ru
  • Главная страница
  • О проекте
  • Портфолио
  • Блог

Прогнозирование оттока клиентов оператора связи «ТелеДом»

Описание проекта

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

Описание услуг

Оператор предоставляет два основных типа услуг:

  • Стационарную телефонную связь. Телефон можно подключить к нескольким линиям одновременно.
  • Интернет. Подключение может быть двух типов: через телефонную линию (DSL, от англ. digital subscriber line — «цифровая абонентская линия») или оптоволоконный кабель (Fiber optic).

Также доступны такие услуги:

  • Интернет-безопасность: антивирус (DeviceProtection) и блокировка небезопасных сайтов (OnlineSecurity);
  • Выделенная линия технической поддержки (TechSupport);
  • Облачное хранилище файлов для резервного копирования данных (OnlineBackup);
  • Стриминговое телевидение (StreamingTV) и каталог фильмов (StreamingMovies).

Клиенты могут платить за услуги каждый месяц или заключить договор на 1–2 года. Возможно оплатить счёт разными способами, а также получить электронный чек.

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

Данные состоят из нескольких файлов, полученных из разных источников:

  • contract_new.csv — информация о договоре;
  • personal_new.csv — персональные данные клиента;
  • internet_new.csv — информация об интернет-услугах;
  • phone_new.csv — информация об услугах телефонии.

Файл contract_new.csv:

  • customerID — идентификатор абонента;
  • BeginDate — дата начала действия договора;
  • EndDate — дата окончания действия договора;
  • Type — тип оплаты: раз в год-два или ежемесячно;
  • PaperlessBilling — электронный расчётный лист;
  • PaymentMethod — тип платежа;
  • MonthlyCharges — расходы за месяц;
  • TotalCharges — общие расходы абонента.

Файл personal_new.csv:

  • customerID — идентификатор пользователя;
  • gender — пол;
  • SeniorCitizen — является ли абонент пенсионером;
  • Partner — есть ли у абонента супруг или супруга;
  • Dependents — есть ли у абонента дети.

Файл internet_new.csv:

  • customerID — идентификатор пользователя;
  • InternetService — тип подключения;
  • OnlineSecurity — блокировка опасных сайтов;
  • OnlineBackup — облачное хранилище файлов для резервного копирования данных;
  • DeviceProtection — антивирус;
  • TechSupport — выделенная линия технической поддержки;
  • StreamingTV — стриминговое телевидение;
  • StreamingMovies — каталог фильмов.

Файл phone_new.csv:

  • customerID — идентификатор пользователя;
  • MultipleLines — подключение телефона к нескольким линиям одновременно.

Во всех файлах столбец customerID содержит код клиента. Информация о договорах актуальна на 1 февраля 2020 года. Данные также находятся в тренажёре, в папке /datasets/.

Содержание

  • 1  Подготовка тетради
    • 1.1  Обновление существующих и установка новых библиотек
    • 1.2  Загрузка библиотек в тетрадь
    • 1.3  Оптимизация отображения контента в тетради
    • 1.4  Глобальные переменные
  • 2  Загрузка данных
  • 3  Исследовательский анализ и предобработка данных
    • 3.1  Полезные функции для предобработки данных
    • 3.2  Анализ и предобработка contract_new (информация о договоре)
    • 3.3  Анализ и предобработка personal_new (персональные данные клиента)
    • 3.4  Анализ и предобработка internet_new (информация об интернет-услугах)
    • 3.5  Анализ и предобработка phone_new (информация об услугах телефонии)
  • 4  Объединение данных
    • 4.1  Предварительное выделение тренировочной выборки
  • 5  Исследовательский анализ данных объединённого датафрейма
    • 5.1  Исследовательский анализ объединённого датафрейма
    • 5.2  Анализ мультиколлинеарности
    • 5.3  Отбор признаков для использования в обучении моделей
  • 6  Подготовка данных
  • 7  Обучение моделей машинного обучения
    • 7.1  Полезные функции
    • 7.2  Обучение модели LogisticRegression
    • 7.3  Обучение модели RandomForestClassifier
    • 7.4  Обучение модели CatBoostClassifier
  • 8  Выбор лучшей модели
    • 8.1  Тестирование лучшей модели
    • 8.2  Анализ протестированной модели
  • 9  Общий вывод и рекомендации заказчику

Подготовка тетради¶

Обновление существующих и установка новых библиотек¶

In [1]:
# Обновление библиотек

# Ядро тетради требует обновление этого модуля
!pip install -U pyodbc

!pip install -U numpy
!pip install -U pandas
!pip install -U scikit-learn
Collecting pyodbc
  Downloading pyodbc-5.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (331 kB)
     |████████████████████████████████| 331 kB 944 kB/s eta 0:00:01
Installing collected packages: pyodbc
Successfully installed pyodbc-5.0.1
Requirement already satisfied: numpy in /opt/conda/lib/python3.9/site-packages (1.21.1)
Collecting numpy
  Downloading numpy-1.26.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (18.2 MB)
     |████████████████████████████████| 18.2 MB 1.0 MB/s eta 0:00:01     |████████████████████████████▏   | 16.0 MB 1.0 MB/s eta 0:00:03
Installing collected packages: numpy
  Attempting uninstall: numpy
    Found existing installation: numpy 1.21.1
    Uninstalling numpy-1.21.1:
      Successfully uninstalled numpy-1.21.1
ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.
scipy 1.9.1 requires numpy<1.25.0,>=1.18.5, but you have numpy 1.26.1 which is incompatible.
numba 0.56.0 requires numpy<1.23,>=1.18, but you have numpy 1.26.1 which is incompatible.
Successfully installed numpy-1.26.1
Requirement already satisfied: pandas in /opt/conda/lib/python3.9/site-packages (1.2.4)
Collecting pandas
  Downloading pandas-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (12.3 MB)
     |████████████████████████████████| 12.3 MB 952 kB/s eta 0:00:01
Requirement already satisfied: pytz>=2020.1 in /opt/conda/lib/python3.9/site-packages (from pandas) (2021.1)
Collecting tzdata>=2022.1
  Downloading tzdata-2023.3-py2.py3-none-any.whl (341 kB)
     |████████████████████████████████| 341 kB 85.6 MB/s eta 0:00:01
Requirement already satisfied: numpy<2,>=1.22.4 in /opt/conda/lib/python3.9/site-packages (from pandas) (1.26.1)
Collecting python-dateutil>=2.8.2
  Downloading python_dateutil-2.8.2-py2.py3-none-any.whl (247 kB)
     |████████████████████████████████| 247 kB 54.9 MB/s eta 0:00:01
Requirement already satisfied: six>=1.5 in /opt/conda/lib/python3.9/site-packages (from python-dateutil>=2.8.2->pandas) (1.16.0)
Installing collected packages: tzdata, python-dateutil, pandas
  Attempting uninstall: python-dateutil
    Found existing installation: python-dateutil 2.8.1
    Uninstalling python-dateutil-2.8.1:
      Successfully uninstalled python-dateutil-2.8.1
  Attempting uninstall: pandas
    Found existing installation: pandas 1.2.4
    Uninstalling pandas-1.2.4:
      Successfully uninstalled pandas-1.2.4
Successfully installed pandas-2.1.3 python-dateutil-2.8.2 tzdata-2023.3
Requirement already satisfied: scikit-learn in /opt/conda/lib/python3.9/site-packages (0.24.1)
Collecting scikit-learn
  Downloading scikit_learn-1.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (10.9 MB)
     |████████████████████████████████| 10.9 MB 946 kB/s eta 0:00:01
Requirement already satisfied: threadpoolctl>=2.0.0 in /opt/conda/lib/python3.9/site-packages (from scikit-learn) (3.1.0)
Collecting joblib>=1.1.1
  Downloading joblib-1.3.2-py3-none-any.whl (302 kB)
     |████████████████████████████████| 302 kB 70.3 MB/s eta 0:00:01
Requirement already satisfied: scipy>=1.5.0 in /opt/conda/lib/python3.9/site-packages (from scikit-learn) (1.9.1)
Requirement already satisfied: numpy<2.0,>=1.17.3 in /opt/conda/lib/python3.9/site-packages (from scikit-learn) (1.26.1)
Collecting numpy<2.0,>=1.17.3
  Downloading numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (17.3 MB)
     |████████████████████████████████| 17.3 MB 77.9 MB/s eta 0:00:01
Installing collected packages: numpy, joblib, scikit-learn
  Attempting uninstall: numpy
    Found existing installation: numpy 1.26.1
    Uninstalling numpy-1.26.1:
      Successfully uninstalled numpy-1.26.1
  Attempting uninstall: joblib
    Found existing installation: joblib 1.1.0
    Uninstalling joblib-1.1.0:
      Successfully uninstalled joblib-1.1.0
  Attempting uninstall: scikit-learn
    Found existing installation: scikit-learn 0.24.1
    Uninstalling scikit-learn-0.24.1:
      Successfully uninstalled scikit-learn-0.24.1
ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.
numba 0.56.0 requires numpy<1.23,>=1.18, but you have numpy 1.24.4 which is incompatible.
Successfully installed joblib-1.3.2 numpy-1.24.4 scikit-learn-1.3.2
In [2]:
# Загрузка новых библиотек

# Анализ мультиколлинеарности
!pip3 install phik
# Pipeline с поддержкой несбалансированных классов
!pip3 install imblearn
# CatBoost - CatBoosting от Яндекс
!pip3 install catboost
# LightGBM
!pip3 install lightgbm
# pytest - требуется для использования statsmodels.stats.tests.test_influence
!pip3 install pytest
Collecting phik
  Downloading phik-0.12.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (679 kB)
     |████████████████████████████████| 679 kB 938 kB/s eta 0:00:01
Requirement already satisfied: matplotlib>=2.2.3 in /opt/conda/lib/python3.9/site-packages (from phik) (3.3.4)
Requirement already satisfied: scipy>=1.5.2 in /opt/conda/lib/python3.9/site-packages (from phik) (1.9.1)
Requirement already satisfied: numpy>=1.18.0 in /opt/conda/lib/python3.9/site-packages (from phik) (1.24.4)
Requirement already satisfied: joblib>=0.14.1 in /opt/conda/lib/python3.9/site-packages (from phik) (1.3.2)
Requirement already satisfied: pandas>=0.25.1 in /opt/conda/lib/python3.9/site-packages (from phik) (2.1.3)
Requirement already satisfied: kiwisolver>=1.0.1 in /opt/conda/lib/python3.9/site-packages (from matplotlib>=2.2.3->phik) (1.4.4)
Requirement already satisfied: cycler>=0.10 in /opt/conda/lib/python3.9/site-packages (from matplotlib>=2.2.3->phik) (0.11.0)
Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.3 in /opt/conda/lib/python3.9/site-packages (from matplotlib>=2.2.3->phik) (2.4.7)
Requirement already satisfied: python-dateutil>=2.1 in /opt/conda/lib/python3.9/site-packages (from matplotlib>=2.2.3->phik) (2.8.2)
Requirement already satisfied: pillow>=6.2.0 in /opt/conda/lib/python3.9/site-packages (from matplotlib>=2.2.3->phik) (8.4.0)
Requirement already satisfied: pytz>=2020.1 in /opt/conda/lib/python3.9/site-packages (from pandas>=0.25.1->phik) (2021.1)
Requirement already satisfied: tzdata>=2022.1 in /opt/conda/lib/python3.9/site-packages (from pandas>=0.25.1->phik) (2023.3)
Requirement already satisfied: six>=1.5 in /opt/conda/lib/python3.9/site-packages (from python-dateutil>=2.1->matplotlib>=2.2.3->phik) (1.16.0)
Installing collected packages: phik
Successfully installed phik-0.12.3
Collecting imblearn
  Downloading imblearn-0.0-py2.py3-none-any.whl (1.9 kB)
Collecting imbalanced-learn
  Downloading imbalanced_learn-0.11.0-py3-none-any.whl (235 kB)
     |████████████████████████████████| 235 kB 922 kB/s eta 0:00:01
Requirement already satisfied: scikit-learn>=1.0.2 in /opt/conda/lib/python3.9/site-packages (from imbalanced-learn->imblearn) (1.3.2)
Requirement already satisfied: numpy>=1.17.3 in /opt/conda/lib/python3.9/site-packages (from imbalanced-learn->imblearn) (1.24.4)
Requirement already satisfied: scipy>=1.5.0 in /opt/conda/lib/python3.9/site-packages (from imbalanced-learn->imblearn) (1.9.1)
Requirement already satisfied: joblib>=1.1.1 in /opt/conda/lib/python3.9/site-packages (from imbalanced-learn->imblearn) (1.3.2)
Requirement already satisfied: threadpoolctl>=2.0.0 in /opt/conda/lib/python3.9/site-packages (from imbalanced-learn->imblearn) (3.1.0)
Installing collected packages: imbalanced-learn, imblearn
Successfully installed imbalanced-learn-0.11.0 imblearn-0.0
Requirement already satisfied: catboost in /opt/conda/lib/python3.9/site-packages (1.0.3)
Requirement already satisfied: six in /opt/conda/lib/python3.9/site-packages (from catboost) (1.16.0)
Requirement already satisfied: numpy>=1.16.0 in /opt/conda/lib/python3.9/site-packages (from catboost) (1.24.4)
Requirement already satisfied: pandas>=0.24.0 in /opt/conda/lib/python3.9/site-packages (from catboost) (2.1.3)
Requirement already satisfied: matplotlib in /opt/conda/lib/python3.9/site-packages (from catboost) (3.3.4)
Requirement already satisfied: scipy in /opt/conda/lib/python3.9/site-packages (from catboost) (1.9.1)
Requirement already satisfied: plotly in /opt/conda/lib/python3.9/site-packages (from catboost) (5.4.0)
Requirement already satisfied: graphviz in /opt/conda/lib/python3.9/site-packages (from catboost) (0.20.1)
Requirement already satisfied: tzdata>=2022.1 in /opt/conda/lib/python3.9/site-packages (from pandas>=0.24.0->catboost) (2023.3)
Requirement already satisfied: pytz>=2020.1 in /opt/conda/lib/python3.9/site-packages (from pandas>=0.24.0->catboost) (2021.1)
Requirement already satisfied: python-dateutil>=2.8.2 in /opt/conda/lib/python3.9/site-packages (from pandas>=0.24.0->catboost) (2.8.2)
Requirement already satisfied: kiwisolver>=1.0.1 in /opt/conda/lib/python3.9/site-packages (from matplotlib->catboost) (1.4.4)
Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.3 in /opt/conda/lib/python3.9/site-packages (from matplotlib->catboost) (2.4.7)
Requirement already satisfied: pillow>=6.2.0 in /opt/conda/lib/python3.9/site-packages (from matplotlib->catboost) (8.4.0)
Requirement already satisfied: cycler>=0.10 in /opt/conda/lib/python3.9/site-packages (from matplotlib->catboost) (0.11.0)
Requirement already satisfied: tenacity>=6.2.0 in /opt/conda/lib/python3.9/site-packages (from plotly->catboost) (8.0.1)
Requirement already satisfied: lightgbm in /opt/conda/lib/python3.9/site-packages (3.3.1)
Requirement already satisfied: wheel in /opt/conda/lib/python3.9/site-packages (from lightgbm) (0.36.2)
Requirement already satisfied: scikit-learn!=0.22.0 in /opt/conda/lib/python3.9/site-packages (from lightgbm) (1.3.2)
Requirement already satisfied: scipy in /opt/conda/lib/python3.9/site-packages (from lightgbm) (1.9.1)
Requirement already satisfied: numpy in /opt/conda/lib/python3.9/site-packages (from lightgbm) (1.24.4)
Requirement already satisfied: threadpoolctl>=2.0.0 in /opt/conda/lib/python3.9/site-packages (from scikit-learn!=0.22.0->lightgbm) (3.1.0)
Requirement already satisfied: joblib>=1.1.1 in /opt/conda/lib/python3.9/site-packages (from scikit-learn!=0.22.0->lightgbm) (1.3.2)
Collecting pytest
  Downloading pytest-7.4.3-py3-none-any.whl (325 kB)
     |████████████████████████████████| 325 kB 1.0 MB/s eta 0:00:01
Collecting iniconfig
  Downloading iniconfig-2.0.0-py3-none-any.whl (5.9 kB)
Collecting tomli>=1.0.0
  Downloading tomli-2.0.1-py3-none-any.whl (12 kB)
Requirement already satisfied: packaging in /opt/conda/lib/python3.9/site-packages (from pytest) (21.3)
Collecting exceptiongroup>=1.0.0rc8
  Downloading exceptiongroup-1.1.3-py3-none-any.whl (14 kB)
Collecting pluggy<2.0,>=0.12
  Downloading pluggy-1.3.0-py3-none-any.whl (18 kB)
Requirement already satisfied: pyparsing!=3.0.5,>=2.0.2 in /opt/conda/lib/python3.9/site-packages (from packaging->pytest) (2.4.7)
Installing collected packages: tomli, pluggy, iniconfig, exceptiongroup, pytest
Successfully installed exceptiongroup-1.1.3 iniconfig-2.0.0 pluggy-1.3.0 pytest-7.4.3 tomli-2.0.1

Загрузка библиотек в тетрадь¶

In [3]:
# Базовые библиотеки
import numpy as np
import pandas as pd

# Вспомогательные библиотеки
import os # Загрузка датафрейма
import time # Оценка времени выполнения кода
import re # Изменение названий столбцов

# Работа с датафреймом
from sklearn.model_selection import(
    #cross_val_score, 
    train_test_split # Автоматическое разделение датафреймов на трейн и тест
)

# Предобработка данных
from sklearn.preprocessing import(
    MaxAbsScaler, # стандартизация по максимальному абсолютном значению в пределах 0-1
    PowerTransformer, # Приведение к нормальному распределению
    OneHotEncoder, # OHE кодирование для "линейных" моделей с созданием отдельных столбцов для каждого категориального значения, drop='first' (удаление первого столбца против dummy-ловушки), sparse=False (?)
    OrdinalEncoder # порядковое кодирование для "деревянных" моделей
) 

# Автоматизация раздельного декодирования признаков
from sklearn.compose import(
    make_column_transformer, 
    ColumnTransformer
)

# Pipeline (пайплайн)
from imblearn.pipeline import Pipeline # Более предпочтительнее для TfidfVectorizer
from imblearn.over_sampling import SMOTE # Балансировка классов в imblearn Pipeline
# Функция для поддержки экспериментальной функции HavingGridSearchSV
from sklearn.experimental import enable_halving_search_cv
# Ускоренная автоматизация поиска лучших моделей и их параметров
from sklearn.model_selection import HalvingGridSearchCV

# Модели
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier # Случайный лес. Классификация
from sklearn.naive_bayes import ComplementNB # Улучшенный классификатор для не сбалансированных классов
from catboost import CatBoostClassifier # CatBoost (Яндекс). Классификация# LightGBM
from lightgbm import LGBMClassifier # LightGBM. Классификация

# Метрики
from sklearn.metrics import(
    roc_curve, # Кривая "ROC"
    auc, # Параметр "AUC" 
    roc_auc_score, # Площадь под кривой рабочих характеристик приемника "ROC AUC"
    accuracy_score, # Accuracy 
    confusion_matrix, # Матрица "путаницы" для матрицы ошибок
    ConfusionMatrixDisplay # Матрица ошибок
) 

# Анализ мультиколлениарности
import phik

# Визуализация графиков
import seaborn as sns
import matplotlib
%matplotlib inline
from matplotlib import pyplot as plt
from matplotlib import rcParams, rcParamsDefault
from pandas.plotting import scatter_matrix

Оптимизация отображения контента в тетради¶

In [4]:
# Отображение всех столбцов таблицы
pd.set_option('display.max_columns', None)
# Обязательно для нормального отображения графиков plt
rcParams['figure.figsize'] = 10, 6
%config InlineBackend.figure_format = 'svg'
# Дополнительно и не обязательно для декорирования графиков
factor = .8
default_dpi = rcParamsDefault['figure.dpi']
rcParams['figure.dpi'] = default_dpi * factor

Глобальные переменные¶

In [5]:
# Глобальная константа 
# для фиксации случайных значений
STATE = 30112023

Загрузка данных¶

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

  • datasets/…_new.csv;
  • /datasets/…_new.csv;
  • https://code.s3.yandex.net/datasets/…_new.csv,

где ... первая часть названия файла данных.

In [6]:
# Функция загрузки данных из файлов в датафреймы
def read_files(file_name):
    
    data = pd.DataFrame()
    
    path_1 = 'datasets/' + file_name + '.csv'
    path_2 = '/datasets/' + file_name + '.csv'
    path_3 = 'https://code.s3.yandex.net/datasets/' + file_name + '.csv'
    
    if os.path.exists(path_1):
        data = pd.read_csv(path_1)
    elif os.path.exists(path_2):
        data = pd.read_csv(path_2)
    elif os.path.exists(path_3):
        data = pd.read_csv(path_3)
    
    return data
In [7]:
# Загрузка файла "contract_new.csv" — информация о договоре
contract_new = read_files('contract_new')
#contract_new = pd.read_csv('https://code.s3.yandex.net/datasets/contract_new.csv')

# Анализ данных из файла
contract_new.head()
Out[7]:
customerID BeginDate EndDate Type PaperlessBilling PaymentMethod MonthlyCharges TotalCharges
0 7590-VHVEG 2020-01-01 No Month-to-month Yes Electronic check 29.85 31.04
1 5575-GNVDE 2017-04-01 No One year No Mailed check 56.95 2071.84
2 3668-QPYBK 2019-10-01 No Month-to-month Yes Mailed check 53.85 226.17
3 7795-CFOCW 2016-05-01 No One year No Bank transfer (automatic) 42.30 1960.6
4 9237-HQITU 2019-09-01 No Month-to-month Yes Electronic check 70.70 353.5
In [8]:
# Загрузка файла "personal_new.csv" — персональные данные клиента
personal_new = read_files('personal_new')

# Анализ данных из файла
personal_new.head()
Out[8]:
customerID gender SeniorCitizen Partner Dependents
0 7590-VHVEG Female 0 Yes No
1 5575-GNVDE Male 0 No No
2 3668-QPYBK Male 0 No No
3 7795-CFOCW Male 0 No No
4 9237-HQITU Female 0 No No
In [9]:
# Загрузка файла "internet_new.csv" — информация об интернет-услугах
internet_new = read_files('internet_new')

# Анализ данных из файла
internet_new.head()
Out[9]:
customerID InternetService OnlineSecurity OnlineBackup DeviceProtection TechSupport StreamingTV StreamingMovies
0 7590-VHVEG DSL No Yes No No No No
1 5575-GNVDE DSL Yes No Yes No No No
2 3668-QPYBK DSL Yes Yes No No No No
3 7795-CFOCW DSL Yes No Yes Yes No No
4 9237-HQITU Fiber optic No No No No No No
In [10]:
# Загрузка файла "phone_new.csv" — информация об услугах телефонии
phone_new = read_files('phone_new')

# Анализ данных из файла
phone_new.head()
Out[10]:
customerID MultipleLines
0 5575-GNVDE No
1 3668-QPYBK No
2 9237-HQITU No
3 9305-CDSKC Yes
4 1452-KIOVK Yes

Исследовательский анализ и предобработка данных¶

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

Анализ и предобработка contract_new (информация о договоре)¶

In [11]:
# Анализ датафрейма "contract_new"
contract_new.info()
contract_new.head()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 7043 entries, 0 to 7042
Data columns (total 8 columns):
 #   Column            Non-Null Count  Dtype  
---  ------            --------------  -----  
 0   customerID        7043 non-null   object 
 1   BeginDate         7043 non-null   object 
 2   EndDate           7043 non-null   object 
 3   Type              7043 non-null   object 
 4   PaperlessBilling  7043 non-null   object 
 5   PaymentMethod     7043 non-null   object 
 6   MonthlyCharges    7043 non-null   float64
 7   TotalCharges      7043 non-null   object 
dtypes: float64(1), object(7)
memory usage: 440.3+ KB
Out[11]:
customerID BeginDate EndDate Type PaperlessBilling PaymentMethod MonthlyCharges TotalCharges
0 7590-VHVEG 2020-01-01 No Month-to-month Yes Electronic check 29.85 31.04
1 5575-GNVDE 2017-04-01 No One year No Mailed check 56.95 2071.84
2 3668-QPYBK 2019-10-01 No Month-to-month Yes Mailed check 53.85 226.17
3 7795-CFOCW 2016-05-01 No One year No Bank transfer (automatic) 42.30 1960.6
4 9237-HQITU 2019-09-01 No Month-to-month Yes Electronic check 70.70 353.5
In [12]:
# Анализ уникальных значений
for i in contract_new.columns:
    print(f'Признак "{i}" имеет {len(contract_new[i].unique())} уникальных значений:')
    print(contract_new[i].unique(), '\n')
Признак "customerID" имеет 7043 уникальных значений:
['7590-VHVEG' '5575-GNVDE' '3668-QPYBK' ... '4801-JZAZL' '8361-LTMKD'
 '3186-AJIEK'] 

Признак "BeginDate" имеет 77 уникальных значений:
['2020-01-01' '2017-04-01' '2019-10-01' '2016-05-01' '2019-09-01'
 '2019-03-01' '2018-04-01' '2019-04-01' '2017-07-01' '2014-12-01'
 '2019-01-01' '2018-10-01' '2015-04-01' '2015-09-01' '2018-01-01'
 '2014-05-01' '2015-10-01' '2014-03-01' '2018-05-01' '2019-11-01'
 '2019-02-01' '2016-01-01' '2017-08-01' '2015-12-01' '2014-02-01'
 '2018-06-01' '2019-12-01' '2017-11-01' '2019-06-01' '2016-04-01'
 '2017-02-01' '2018-12-01' '2014-04-01' '2018-09-01' '2014-11-01'
 '2016-07-01' '2015-02-01' '2018-07-01' '2014-08-01' '2016-03-01'
 '2018-08-01' '2014-10-01' '2015-06-01' '2016-08-01' '2019-05-01'
 '2017-03-01' '2016-02-01' '2017-09-01' '2014-09-01' '2017-12-01'
 '2016-12-01' '2017-06-01' '2015-05-01' '2016-10-01' '2016-09-01'
 '2019-08-01' '2019-07-01' '2017-05-01' '2017-10-01' '2014-07-01'
 '2018-03-01' '2015-01-01' '2018-11-01' '2015-03-01' '2018-02-01'
 '2016-06-01' '2015-08-01' '2015-11-01' '2014-06-01' '2017-01-01'
 '2015-07-01' '2020-02-01' '2016-11-01' '2013-11-01' '2014-01-01'
 '2013-10-01' '2013-12-01'] 

Признак "EndDate" имеет 67 уникальных значений:
['No' '2017-05-01' '2016-03-01' '2018-09-01' '2018-11-01' '2018-12-01'
 '2019-08-01' '2018-07-01' '2017-09-01' '2015-09-01' '2016-07-01'
 '2016-06-01' '2018-03-01' '2019-02-01' '2018-06-01' '2019-06-01'
 '2020-01-01' '2019-11-01' '2016-09-01' '2015-06-01' '2016-12-01'
 '2019-05-01' '2019-04-01' '2017-06-01' '2017-08-01' '2018-04-01'
 '2018-08-01' '2018-02-01' '2019-07-01' '2015-12-01' '2014-06-01'
 '2018-10-01' '2019-01-01' '2017-07-01' '2017-12-01' '2018-05-01'
 '2015-11-01' '2019-10-01' '2019-03-01' '2016-02-01' '2016-10-01'
 '2018-01-01' '2017-11-01' '2015-10-01' '2019-12-01' '2015-07-01'
 '2017-04-01' '2015-02-01' '2017-03-01' '2016-05-01' '2016-11-01'
 '2015-08-01' '2019-09-01' '2017-10-01' '2017-02-01' '2016-08-01'
 '2016-04-01' '2015-05-01' '2014-09-01' '2014-10-01' '2017-01-01'
 '2015-03-01' '2015-01-01' '2016-01-01' '2015-04-01' '2014-12-01'
 '2014-11-01'] 

Признак "Type" имеет 3 уникальных значений:
['Month-to-month' 'One year' 'Two year'] 

Признак "PaperlessBilling" имеет 2 уникальных значений:
['Yes' 'No'] 

Признак "PaymentMethod" имеет 4 уникальных значений:
['Electronic check' 'Mailed check' 'Bank transfer (automatic)'
 'Credit card (automatic)'] 

Признак "MonthlyCharges" имеет 1585 уникальных значений:
[29.85 56.95 53.85 ... 63.1  44.2  78.7 ] 

Признак "TotalCharges" имеет 6658 уникальных значений:
['31.04' '2071.84' '226.17' ... '325.6' '520.8' '7251.82'] 

In [13]:
# Проверка на наличие не числовых значений в "BeginDate"
contract_new.loc[
    contract_new['BeginDate'].str.contains(' |,|[a-z]|[A-Z]|[а-я]|[А-Я]'), 
    'BeginDate'
].unique()
Out[13]:
array([], dtype=object)
In [14]:
# Проверка на наличие не числовых значений в "EndDate"
contract_new.loc[
    contract_new['EndDate'].str.contains(' |,|[a-z]|[A-Z]|[а-я]|[А-Я]'), 
    'EndDate'
].unique()
Out[14]:
array(['No'], dtype=object)
In [15]:
# Проверка на наличие не числовых значений в "TotalCharges"
contract_new.loc[
    contract_new['TotalCharges'].str.contains(' |,|[a-z]|[A-Z]|[а-я]|[А-Я]'), 
    'TotalCharges'
].unique()
Out[15]:
array([' '], dtype=object)
In [16]:
# Проверка "BeginDate", "EndDate" и "TotalCharges" на совпадение не числовых значений
contract_new.loc[contract_new['EndDate'] == 'No', ['BeginDate', 'EndDate', 'TotalCharges']]
Out[16]:
BeginDate EndDate TotalCharges
0 2020-01-01 No 31.04
1 2017-04-01 No 2071.84
2 2019-10-01 No 226.17
3 2016-05-01 No 1960.6
4 2019-09-01 No 353.5
… … … …
7038 2018-02-01 No 2035.2
7039 2014-02-01 No 7430.4
7040 2019-03-01 No 325.6
7041 2019-07-01 No 520.8
7042 2014-08-01 No 7251.82

5942 rows × 3 columns

In [17]:
# Проверка "BeginDate", "EndDate" и "TotalCharges" на совпадение не числовых значений
contract_new.loc[contract_new['TotalCharges'] == ' ', ['BeginDate', 'EndDate', 'TotalCharges']]
Out[17]:
BeginDate EndDate TotalCharges
488 2020-02-01 No
753 2020-02-01 No
936 2020-02-01 No
1082 2020-02-01 No
1340 2020-02-01 No
3331 2020-02-01 No
3826 2020-02-01 No
4380 2020-02-01 No
5218 2020-02-01 No
6670 2020-02-01 No
6754 2020-02-01 No

Предварительные выводы

  1. Датафрейм contract_new содержит 8 признаков и 7043 объекта.
  2. Датафрейм не содержит пропусков.
  3. Признак customerID содерижт идентификаторы клиентов.
  4. Признаки BeginDate и EndDate содержат информацию о дате и месяце начала и окончания действия договора соответственно. Если EndDate содержит No, значит договор был активен 01.02.2020. Оба признака требуется удалить из датафрейма, т.к. они «привязаны» к определенным временным промежуткам и приведут к ошибочным предсказаниям при использовании более поздних дат. Перед их удалением на их основе требуется создать признаки active_contract и duration_contract. Для корректного расчета продолжительности контракта, после создания поризнака active_contract и до создания duration_contract следует значения No признака EndDate заменить на числовое значение 2020-02-01 (актуальная дата информации, согласно данным от руководителя проекта).
    1. Признак active_contract будет содержать 0, если EndDate содержит No, и 1, если EndDate содержит дату. Данный признак будет основой целевого признака в объедененном датафрейме при обучении моделей.
    2. Признак duration_contract будет содержать информацию о продолжительности действия договора.
  5. Признаки Type, PaperlessBilling и PaymentMethod категориальные. Их требуется трансформировать методом OneHotEncoder для «линейных» моделей и OrdinalEncoder для «деревянных» моделей в Pipeline перед обучением модели.
  6. Признаки MonthlyCharges и TotalCharges содержат числовые значения. При этом, признак TotalCharges имеет тип object и требует преобразования в тип float64. Пробелы в признаке TotalCharges требуется заменить на числовое значение, например, на 0, т.к. предполагаем, что отсутствие значения в данном признаке говорит об отсутствии оплат.
  7. Названия признаков желательно привести к «змеиному стилю».
In [18]:
# Создание признака "active_contract"
contract_new['active_contract'] = 1
contract_new.loc[contract_new['EndDate'] == 'No', 'active_contract'] = 0
In [19]:
# Оптимизация типа данных признака "BeginDate"
contract_new['BeginDate'] = pd.to_datetime(
    contract_new['BeginDate'], 
    format='%Y-%m-%d'
)

# Оптимизация типа данных признака "EndDate"
contract_new.loc[contract_new['EndDate'] == 'No', 'EndDate'] = '2020-02-01'
contract_new['EndDate'] = pd.to_datetime(
    contract_new['EndDate'], 
    format='%Y-%m-%d'
)

# Создание признака "duration_contract"
contract_new['duration_contract'] = (contract_new['EndDate'] - contract_new['BeginDate']).dt.components.days

# Удаление признкаов "BeginDate" и "EndDate"
contract_new = contract_new.drop(['BeginDate', 'EndDate'], axis=1)
In [20]:
# Обработка признака "total_charges"
contract_new.loc[contract_new['TotalCharges'] == ' ', 'TotalCharges'] = 0
contract_new['TotalCharges'] = contract_new['TotalCharges'].astype('float64')
In [21]:
# Приведение названий признаков датафрейма к "змеиному стилю"
contract_new.columns = [re.sub(r'(?<!^)(?=[A-Z])', '_', i).lower() for i in contract_new.columns] 
contract_new = contract_new.rename(columns={'customer_i_d':'customer_id'})

# Проверка полученных названий
contract_new.columns
Out[21]:
Index(['customer_id', 'type', 'paperless_billing', 'payment_method',
       'monthly_charges', 'total_charges', 'active_contract',
       'duration_contract'],
      dtype='object')
In [22]:
# Проверка датафрейма после после предобработки данных
print(contract_new.info())
contract_new.head()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 7043 entries, 0 to 7042
Data columns (total 8 columns):
 #   Column             Non-Null Count  Dtype  
---  ------             --------------  -----  
 0   customer_id        7043 non-null   object 
 1   type               7043 non-null   object 
 2   paperless_billing  7043 non-null   object 
 3   payment_method     7043 non-null   object 
 4   monthly_charges    7043 non-null   float64
 5   total_charges      7043 non-null   float64
 6   active_contract    7043 non-null   int64  
 7   duration_contract  7043 non-null   int64  
dtypes: float64(2), int64(2), object(4)
memory usage: 440.3+ KB
None
Out[22]:
customer_id type paperless_billing payment_method monthly_charges total_charges active_contract duration_contract
0 7590-VHVEG Month-to-month Yes Electronic check 29.85 31.04 0 31
1 5575-GNVDE One year No Mailed check 56.95 2071.84 0 1036
2 3668-QPYBK Month-to-month Yes Mailed check 53.85 226.17 0 123
3 7795-CFOCW One year No Bank transfer (automatic) 42.30 1960.60 0 1371
4 9237-HQITU Month-to-month Yes Electronic check 70.70 353.50 0 153

Анализ и предобработка personal_new (персональные данные клиента)¶

In [23]:
# Анализ датафрейма "personal_new"
personal_new.info()
personal_new.head()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 7043 entries, 0 to 7042
Data columns (total 5 columns):
 #   Column         Non-Null Count  Dtype 
---  ------         --------------  ----- 
 0   customerID     7043 non-null   object
 1   gender         7043 non-null   object
 2   SeniorCitizen  7043 non-null   int64 
 3   Partner        7043 non-null   object
 4   Dependents     7043 non-null   object
dtypes: int64(1), object(4)
memory usage: 275.2+ KB
Out[23]:
customerID gender SeniorCitizen Partner Dependents
0 7590-VHVEG Female 0 Yes No
1 5575-GNVDE Male 0 No No
2 3668-QPYBK Male 0 No No
3 7795-CFOCW Male 0 No No
4 9237-HQITU Female 0 No No
In [24]:
# Анализ уникальных значений
for i in personal_new.columns:
    print(f'Признак "{i}" имеет {len(personal_new[i].unique())} уникальных значений:')
    print(personal_new[i].unique(), '\n')
Признак "customerID" имеет 7043 уникальных значений:
['7590-VHVEG' '5575-GNVDE' '3668-QPYBK' ... '4801-JZAZL' '8361-LTMKD'
 '3186-AJIEK'] 

Признак "gender" имеет 2 уникальных значений:
['Female' 'Male'] 

Признак "SeniorCitizen" имеет 2 уникальных значений:
[0 1] 

Признак "Partner" имеет 2 уникальных значений:
['Yes' 'No'] 

Признак "Dependents" имеет 2 уникальных значений:
['No' 'Yes'] 

Предварительные выводы

  1. Датафрейм personal_new содержит 5 признаков и 7043 объекта.
  2. Датафрейм не содержит пропусков.
  3. Признак customerID содерижт идентификаторы клиентов.
  4. Признаки gender, SeniorCitizen, Partner и Dependents бинарные категориальные. Их требуется трансформировать методом OneHotEncoder (для «линейных» моделей) и OrdinalEncoder (для «деревянных» моделей) в Pipeline перед обучением модели. в Pipeline перед обучением модели.
  5. Признак SeniorCitizen отличается от других категориальных бинарных признаков наличием числовых значений. Для его унификации с другими категориальными значениями требуется его значения 0 и 1 привести к No и Yes соответственно, а тип данных SeniorCitizen привести к типу object.
  6. Названия признаков желательно привести к «змеиному стилю».
In [25]:
# Трансформация категориальных признаков, кроме "SeniorCitizen"
personal_new.loc[personal_new['SeniorCitizen'] == 0, 'SeniorCitizen'] = 'No' 
personal_new.loc[personal_new['SeniorCitizen'] == 1, 'SeniorCitizen'] = 'Yes' 
/tmp/ipykernel_63/2905468989.py:2: FutureWarning: Setting an item of incompatible dtype is deprecated and will raise in a future error of pandas. Value 'No' has dtype incompatible with int64, please explicitly cast to a compatible dtype first.
  personal_new.loc[personal_new['SeniorCitizen'] == 0, 'SeniorCitizen'] = 'No'
In [26]:
# Приведение названий признаков датафрейма к "змеиному стилю"
personal_new.columns = [re.sub(r'(?<!^)(?=[A-Z])', '_', i).lower() for i in personal_new.columns] 
personal_new = personal_new.rename(columns={'customer_i_d':'customer_id'})

# Проверка полученных названий
personal_new.columns
Out[26]:
Index(['customer_id', 'gender', 'senior_citizen', 'partner', 'dependents'], dtype='object')
In [27]:
# Анализ датафрейма после предобработки данных
print(personal_new.info())
personal_new.head()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 7043 entries, 0 to 7042
Data columns (total 5 columns):
 #   Column          Non-Null Count  Dtype 
---  ------          --------------  ----- 
 0   customer_id     7043 non-null   object
 1   gender          7043 non-null   object
 2   senior_citizen  7043 non-null   object
 3   partner         7043 non-null   object
 4   dependents      7043 non-null   object
dtypes: object(5)
memory usage: 275.2+ KB
None
Out[27]:
customer_id gender senior_citizen partner dependents
0 7590-VHVEG Female No Yes No
1 5575-GNVDE Male No No No
2 3668-QPYBK Male No No No
3 7795-CFOCW Male No No No
4 9237-HQITU Female No No No

Анализ и предобработка internet_new (информация об интернет-услугах)¶

In [28]:
# Анализ датафрейма "internet_new"
print(internet_new.info())
internet_new.head()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5517 entries, 0 to 5516
Data columns (total 8 columns):
 #   Column            Non-Null Count  Dtype 
---  ------            --------------  ----- 
 0   customerID        5517 non-null   object
 1   InternetService   5517 non-null   object
 2   OnlineSecurity    5517 non-null   object
 3   OnlineBackup      5517 non-null   object
 4   DeviceProtection  5517 non-null   object
 5   TechSupport       5517 non-null   object
 6   StreamingTV       5517 non-null   object
 7   StreamingMovies   5517 non-null   object
dtypes: object(8)
memory usage: 344.9+ KB
None
Out[28]:
customerID InternetService OnlineSecurity OnlineBackup DeviceProtection TechSupport StreamingTV StreamingMovies
0 7590-VHVEG DSL No Yes No No No No
1 5575-GNVDE DSL Yes No Yes No No No
2 3668-QPYBK DSL Yes Yes No No No No
3 7795-CFOCW DSL Yes No Yes Yes No No
4 9237-HQITU Fiber optic No No No No No No
In [29]:
# Анализ уникальных значений
for i in internet_new.columns:
    print(f'Признак "{i}" имеет {len(internet_new[i].unique())} уникальных значений:')
    print(internet_new[i].unique(), '\n')
Признак "customerID" имеет 5517 уникальных значений:
['7590-VHVEG' '5575-GNVDE' '3668-QPYBK' ... '4801-JZAZL' '8361-LTMKD'
 '3186-AJIEK'] 

Признак "InternetService" имеет 2 уникальных значений:
['DSL' 'Fiber optic'] 

Признак "OnlineSecurity" имеет 2 уникальных значений:
['No' 'Yes'] 

Признак "OnlineBackup" имеет 2 уникальных значений:
['Yes' 'No'] 

Признак "DeviceProtection" имеет 2 уникальных значений:
['No' 'Yes'] 

Признак "TechSupport" имеет 2 уникальных значений:
['No' 'Yes'] 

Признак "StreamingTV" имеет 2 уникальных значений:
['No' 'Yes'] 

Признак "StreamingMovies" имеет 2 уникальных значений:
['No' 'Yes'] 

Предварительные выводы

  1. Датафрейм internet_new содержит 8 признаков и 5517 объектов.
  2. Датафрейм не содержит пропусков.
  3. Признак customerID содерижт идентификаторы клиентов.
  4. Признаки InternetService, OnlineSecurity, OnlineBackup, DeviceProtection, TechSupport, StreamingTV и StreamingMovies бинарные категориальные. Их требуется кодировать с методом OneHotEncoder (для «линейных» моделей) и OrdinalEncoder (для «деревянных» моделей) в Pipeline перед обучением модели.
  5. Названия признаков желательно привести к «змеиному стилю».
In [30]:
# Приведение названий признаков датафрейма к "змеиному стилю"
internet_new.columns = [re.sub(r'(?<!^)(?=[A-Z])', '_', i).lower() for i in internet_new.columns] 
internet_new = internet_new.rename(columns={
    'customer_i_d':'customer_id', 
    'streaming_t_v':'streaming_tv'
})

# Проверка полученных названий
internet_new.columns
Out[30]:
Index(['customer_id', 'internet_service', 'online_security', 'online_backup',
       'device_protection', 'tech_support', 'streaming_tv',
       'streaming_movies'],
      dtype='object')
In [31]:
# Анализ датафрейма после предобработки данных
print(internet_new.info())
internet_new.head()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5517 entries, 0 to 5516
Data columns (total 8 columns):
 #   Column             Non-Null Count  Dtype 
---  ------             --------------  ----- 
 0   customer_id        5517 non-null   object
 1   internet_service   5517 non-null   object
 2   online_security    5517 non-null   object
 3   online_backup      5517 non-null   object
 4   device_protection  5517 non-null   object
 5   tech_support       5517 non-null   object
 6   streaming_tv       5517 non-null   object
 7   streaming_movies   5517 non-null   object
dtypes: object(8)
memory usage: 344.9+ KB
None
Out[31]:
customer_id internet_service online_security online_backup device_protection tech_support streaming_tv streaming_movies
0 7590-VHVEG DSL No Yes No No No No
1 5575-GNVDE DSL Yes No Yes No No No
2 3668-QPYBK DSL Yes Yes No No No No
3 7795-CFOCW DSL Yes No Yes Yes No No
4 9237-HQITU Fiber optic No No No No No No

Анализ и предобработка phone_new (информация об услугах телефонии)¶

In [32]:
# Анализ датафрейма "phone_new"
print(phone_new.info())
phone_new.head()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 6361 entries, 0 to 6360
Data columns (total 2 columns):
 #   Column         Non-Null Count  Dtype 
---  ------         --------------  ----- 
 0   customerID     6361 non-null   object
 1   MultipleLines  6361 non-null   object
dtypes: object(2)
memory usage: 99.5+ KB
None
Out[32]:
customerID MultipleLines
0 5575-GNVDE No
1 3668-QPYBK No
2 9237-HQITU No
3 9305-CDSKC Yes
4 1452-KIOVK Yes

Предварительные выводы

  1. Датафрейм phone_new содержит 2 признака и 6361 объект.
  2. Датафрейм не содержит пропусков.
  3. Признак customerID содерижт идентификаторы клиентов.
  4. Признак MultipleLines бинарный категориальный. Его требуется трансформировать методом OneHotEncoder (для «линейных» моделей) и OrdinalEncoder (для «деревянных» моделей) в Pipeline перед трансформировать моделей.
  5. Названия признаков желательно привести к «змеиному стилю».
In [33]:
# Приведение названий признаков датафрейма к "змеиному стилю"
phone_new.columns = [re.sub(r'(?<!^)(?=[A-Z])', '_', i).lower() for i in phone_new.columns] 
phone_new = phone_new.rename(columns={'customer_i_d':'customer_id'})

# Проверка полученных названий
phone_new.columns
Out[33]:
Index(['customer_id', 'multiple_lines'], dtype='object')
In [34]:
# Проверка датафрейма после изменения
print(phone_new.info())
phone_new.head()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 6361 entries, 0 to 6360
Data columns (total 2 columns):
 #   Column          Non-Null Count  Dtype 
---  ------          --------------  ----- 
 0   customer_id     6361 non-null   object
 1   multiple_lines  6361 non-null   object
dtypes: object(2)
memory usage: 99.5+ KB
None
Out[34]:
customer_id multiple_lines
0 5575-GNVDE No
1 3668-QPYBK No
2 9237-HQITU No
3 9305-CDSKC Yes
4 1452-KIOVK Yes

Объединение данных¶

Объединение выбранных признаков в один датафрейм по ключу. Ключом является признак customer_id (идентификатор клиента) во всех датафреймах.

In [35]:
print('Размерность датафрейма "contract_new":', contract_new.shape)
print('Размерность датафрейма "personal_new":', personal_new.shape)
print('Размерность датафрейма "internet_new":', internet_new.shape)
print('Размерность датафрейма "phone_new"   :', phone_new.shape)
Размерность датафрейма "contract_new": (7043, 8)
Размерность датафрейма "personal_new": (7043, 5)
Размерность датафрейма "internet_new": (5517, 8)
Размерность датафрейма "phone_new"   : (6361, 2)
In [36]:
# Пробное объединение исходных датафреймов 
# для проверки уникальности идентификаторов 
# (если количество объектов после объединения 
# будет одинаковым с исходным, значит идентификаторы совпадают)
personal_new.join(contract_new.set_index('customer_id'), on='customer_id', how='outer').shape
Out[36]:
(7043, 12)

Датафреймы contract_new и personal_new содержат ключевую информацию проекта о договорах и клиентах соответственно. Учитывая тот факт, что оба эти датафрейма до и после объединения имеют одинаковое количество объектов, значит идентификаторы клиентов в них полностью совпадают. Эти датафреймы можно объединять в общий датафрейм total_new через join. Остальные датафреймы имеют меньшее количество объектов. Следовательно, их следует объединять с общим датафреймом через левый join.

Также следует предположить, что отсутствие значений в меньших датафреймах с информацией о приобретенных услугах — это либо не приобретенные услуги, либо отсутствие информации о приобретении. Значит, NaN можно интерпретировать как No в рамках принятой здесь типология, но лучше NoValue, то есть отсутствие значения.

In [37]:
# Последовательное объединение датафреймов в "total_new"
total_new = personal_new.join(contract_new.set_index('customer_id'), on='customer_id')
total_new = total_new.join(internet_new.set_index('customer_id'), on='customer_id', how='left')
total_new = total_new.join(phone_new.set_index('customer_id'), on='customer_id', how='left')
In [38]:
# "NaN" - это не приобретенные значения
total_new = total_new.fillna('NoValue')

Предварительное выделение тренировочной выборки¶

Для наиболее эффективного обучения модели дальнейший анализ и трансформация объединенного датафрейма будет производиться только на треннировочной выборке полученной методом train_test_split. При разделении выборок будут использоваться параметры random_state=30112023 и test_size=.25, заданные требованиями к данному проекту. Для анализа объединенных данных треннировочные выборки с «фичами» и целевой переменной будут объеденены в общий датафрейм. Это требуется для анализа зависимостей переменных. Также это позволит сохранить разделенные выборки для использования в моделях.

In [39]:
# Разделение датафрейма 
# на целевые и не целевые выборки
#features = data['text']
features = total_new.drop('active_contract', axis=1)
target = total_new['active_contract']

# Разделение выборок 
# на обучающие и тестовые
features_train, features_test, target_train, target_test = train_test_split(
    features, 
    target, 
    test_size=.25, 
    stratify=target, 
    random_state=STATE
)

# Объединение обучающих выборок
total_new_train = features_train.join(target_train)
In [40]:
# Проверка баланса классов
target_train_0 = target_train.loc[target_train == 0].count(),
target_train_1 = target_train.loc[target_train == 1].count(),
target_test_0 = target_test.loc[target_test == 0].count()
target_test_1 = target_test.loc[target_test == 1].count()

target_train_0 = target_train_0[0]
target_train_1 = target_train_1[0]

display(pd.DataFrame({
    'Тип выборки':['Тренировочная', 'Тестовая'], 
    'Класс 0':[
        target_train_0, 
        target_test_0
    ], 
    'Класс 1':[
        target_train_1, 
        target_test_1
    ]
})) 


if target_train_0 == target_train_1:
    print('Классы в тренировочной выборке сбалансированы.') 
else:
    print('Классы в тренировочной выборке не сбалансированы.') 
    print(f'Класс 0: {(target_train_0/(target_train_0+target_train_1)*100).round(2)}%') 
    print(f'Класс 1: {(target_train_1/(target_train_0+target_train_1)*100).round(2)}%') 
print() 
if  target_test_0 == target_test_1:
    print('Классы в тестовой выборке сбалансированы.') 
else:
    print('Классы в тестовой выборке не сбалансированы.') 
    print(f'Класс 0: {(target_test_0/(target_test_0+target_test_1)*100).round(2)}%') 
    print(f'Класс 1: {(target_test_1/(target_test_0+target_test_1)*100).round(2)}%') 
Тип выборки Класс 0 Класс 1
0 Тренировочная 4456 826
1 Тестовая 1486 275
Классы в тренировочной выборке не сбалансированы.
Класс 0: 84.36%
Класс 1: 15.64%

Классы в тестовой выборке не сбалансированы.
Класс 0: 84.38%
Класс 1: 15.62%
In [41]:
# Анализ выборки
total_new_train.head()
Out[41]:
customer_id gender senior_citizen partner dependents type paperless_billing payment_method monthly_charges total_charges duration_contract internet_service online_security online_backup device_protection tech_support streaming_tv streaming_movies multiple_lines active_contract
3798 2800-QQUSO Male No No No Month-to-month Yes Electronic check 100.30 4212.60 1279 Fiber optic No No No Yes Yes Yes Yes 0
3516 7587-RZNME Male No No No Month-to-month Yes Electronic check 43.30 259.80 184 DSL No No No No No No No 0
5031 8118-TJAFG Male No Yes Yes Month-to-month Yes Electronic check 101.50 977.45 276 Fiber optic No Yes No Yes Yes Yes No 0
3922 6173-GOLSU Male Yes Yes No Month-to-month Yes Credit card (automatic) 94.65 6341.55 2041 Fiber optic No No No No Yes Yes Yes 0
3275 1245-HARPS Female No Yes No Two year No Mailed check 20.40 204.00 304 NoValue NoValue NoValue NoValue NoValue NoValue NoValue No 1
In [42]:
# Анализ данных в обучающей выборке
total_new_train.describe()
Out[42]:
monthly_charges total_charges duration_contract active_contract
count 5282.000000 5282.000000 5282.000000 5282.00000
mean 64.914691 2118.084824 895.525559 0.15638
std 30.054313 2119.483435 684.487255 0.36325
min 18.250000 0.000000 0.000000 0.00000
25% 35.750000 435.075000 276.000000 0.00000
50% 70.500000 1349.755000 761.000000 0.00000
75% 89.800000 3247.812500 1432.000000 0.00000
max 118.750000 9221.380000 2314.000000 1.00000

Исследовательский анализ данных объединённого датафрейма¶

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

Исследовательский анализ объединённого датафрейма¶

In [43]:
# Анализ объединенного датафрейма
print(total_new_train.info())
total_new_train.head()
<class 'pandas.core.frame.DataFrame'>
Index: 5282 entries, 3798 to 670
Data columns (total 20 columns):
 #   Column             Non-Null Count  Dtype  
---  ------             --------------  -----  
 0   customer_id        5282 non-null   object 
 1   gender             5282 non-null   object 
 2   senior_citizen     5282 non-null   object 
 3   partner            5282 non-null   object 
 4   dependents         5282 non-null   object 
 5   type               5282 non-null   object 
 6   paperless_billing  5282 non-null   object 
 7   payment_method     5282 non-null   object 
 8   monthly_charges    5282 non-null   float64
 9   total_charges      5282 non-null   float64
 10  duration_contract  5282 non-null   int64  
 11  internet_service   5282 non-null   object 
 12  online_security    5282 non-null   object 
 13  online_backup      5282 non-null   object 
 14  device_protection  5282 non-null   object 
 15  tech_support       5282 non-null   object 
 16  streaming_tv       5282 non-null   object 
 17  streaming_movies   5282 non-null   object 
 18  multiple_lines     5282 non-null   object 
 19  active_contract    5282 non-null   int64  
dtypes: float64(2), int64(2), object(16)
memory usage: 995.6+ KB
None
Out[43]:
customer_id gender senior_citizen partner dependents type paperless_billing payment_method monthly_charges total_charges duration_contract internet_service online_security online_backup device_protection tech_support streaming_tv streaming_movies multiple_lines active_contract
3798 2800-QQUSO Male No No No Month-to-month Yes Electronic check 100.30 4212.60 1279 Fiber optic No No No Yes Yes Yes Yes 0
3516 7587-RZNME Male No No No Month-to-month Yes Electronic check 43.30 259.80 184 DSL No No No No No No No 0
5031 8118-TJAFG Male No Yes Yes Month-to-month Yes Electronic check 101.50 977.45 276 Fiber optic No Yes No Yes Yes Yes No 0
3922 6173-GOLSU Male Yes Yes No Month-to-month Yes Credit card (automatic) 94.65 6341.55 2041 Fiber optic No No No No Yes Yes Yes 0
3275 1245-HARPS Female No Yes No Two year No Mailed check 20.40 204.00 304 NoValue NoValue NoValue NoValue NoValue NoValue NoValue No 1
In [44]:
# Справочник признаков объединенного датафрейма
total_new_features = {
    'customer_id':'идентификатор абонента',
    'gender':'пол',
    'senior_citizen':'является ли абонент пенсионером',
    'partner':'есть ли у абонента супруг или супруга',
    'dependents':'есть ли у абонента дети',

    'type':'тип оплаты',
    'paperless_billing':'электронный расчётный лист',
    'payment_method':'тип платежа',
    'monthly_charges':'расходы за месяц',
    'total_charges':'общие расходы абонента',
    'active_contract':'активный договор (целевой признак)', 
    'duration_contract':'продолжительность договора в днях',

    'internet_service':'тип подключения',
    'online_security':'блокировка опасных сайтов',
    'online_backup':'облачное хранилище файлов для резервного копирования данных',
    'device_protection':'антивирус',
    'tech_support':'выделенная линия технической поддержки',
    'streaming_tv':'стриминговое телевидение',
    'streaming_movies':'каталог фильмов',

    'multiple_lines':'подключение телефона к нескольким линиям одновременно'
}

# Проверка справочника признаков
total_new_features
Out[44]:
{'customer_id': 'идентификатор абонента',
 'gender': 'пол',
 'senior_citizen': 'является ли абонент пенсионером',
 'partner': 'есть ли у абонента супруг или супруга',
 'dependents': 'есть ли у абонента дети',
 'type': 'тип оплаты',
 'paperless_billing': 'электронный расчётный лист',
 'payment_method': 'тип платежа',
 'monthly_charges': 'расходы за месяц',
 'total_charges': 'общие расходы абонента',
 'active_contract': 'активный договор (целевой признак)',
 'duration_contract': 'продолжительность договора в днях',
 'internet_service': 'тип подключения',
 'online_security': 'блокировка опасных сайтов',
 'online_backup': 'облачное хранилище файлов для резервного копирования данных',
 'device_protection': 'антивирус',
 'tech_support': 'выделенная линия технической поддержки',
 'streaming_tv': 'стриминговое телевидение',
 'streaming_movies': 'каталог фильмов',
 'multiple_lines': 'подключение телефона к нескольким линиям одновременно'}
In [45]:
# Анализ данных объединенного датафрейма
for i in total_new_train.columns:
    if i not in ['customer_id']:
        for j in total_new_train:
            if i == j:
                print(f'Распределение значений признака "{i}" ({total_new_features[j]}):')
                
                t0 = total_new_train.loc[total_new_train['active_contract'] == 0, i] 
                t1 = total_new_train.loc[total_new_train['active_contract'] == 1, i] 
                
                t0 = t0.rename('Класс 0') 
                t1 = t1.rename('Класс 1') 
                
                display(pd.DataFrame([t0.describe(), t1.describe()])) 
                
                t0.hist(legend=True, alpha=.8)
                t1.hist(legend=True, alpha=.8) 
                plt.title(f'Распределение значений признака "{i}"')
                plt.xlabel(f'Значения признака "{i}"')
                plt.ylabel('Количество значений')
                plt.show() 
Распределение значений признака "gender" (пол):
count unique top freq
Класс 0 4456 2 Male 2237
Класс 1 826 2 Male 438
2023-11-11T16:15:43.566452 image/svg+xml Matplotlib v3.3.4, https://matplotlib.org/
Распределение значений признака "senior_citizen" (является ли абонент пенсионером):
count unique top freq
Класс 0 4456 2 No 3756
Класс 1 826 2 No 661
2023-11-11T16:15:43.720229 image/svg+xml Matplotlib v3.3.4, https://matplotlib.org/
Распределение значений признака "partner" (есть ли у абонента супруг или супруга):
count unique top freq
Класс 0 4456 2 No 2433
Класс 1 826 2 Yes 537
2023-11-11T16:15:43.888140 image/svg+xml Matplotlib v3.3.4, https://matplotlib.org/
Распределение значений признака "dependents" (есть ли у абонента дети):
count unique top freq
Класс 0 4456 2 No 3146
Класс 1 826 2 No 551
2023-11-11T16:15:44.042231 image/svg+xml Matplotlib v3.3.4, https://matplotlib.org/
Распределение значений признака "type" (тип оплаты):
count unique top freq
Класс 0 4456 3 Month-to-month 2610
Класс 1 826 3 Month-to-month 314
2023-11-11T16:15:44.199503 image/svg+xml Matplotlib v3.3.4, https://matplotlib.org/
Распределение значений признака "paperless_billing" (электронный расчётный лист):
count unique top freq
Класс 0 4456 2 Yes 2591
Класс 1 826 2 Yes 552
2023-11-11T16:15:44.355056 image/svg+xml Matplotlib v3.3.4, https://matplotlib.org/
Распределение значений признака "payment_method" (тип платежа):
count unique top freq
Класс 0 4456 4 Electronic check 1533
Класс 1 826 4 Electronic check 268
2023-11-11T16:15:44.511595 image/svg+xml Matplotlib v3.3.4, https://matplotlib.org/
Распределение значений признака "monthly_charges" (расходы за месяц):
count mean std min 25% 50% 75% max
Класс 0 4456.0 62.982080 29.831779 18.25 29.9000 69.55 87.2125 118.60
Класс 1 826.0 75.340496 29.108727 18.40 55.8875 84.10 99.4875 118.75
2023-11-11T16:15:44.779783 image/svg+xml Matplotlib v3.3.4, https://matplotlib.org/
Распределение значений признака "total_charges" (общие расходы абонента):
count mean std min 25% 50% 75% max
Класс 0 4456.0 2080.726842 2205.653256 0.00 375.3625 1202.0 3193.425 9221.38
Класс 1 826.0 2319.618923 1561.054801 77.84 1010.2875 2062.7 3359.675 7594.92
2023-11-11T16:15:44.967736 image/svg+xml Matplotlib v3.3.4, https://matplotlib.org/
Распределение значений признака "duration_contract" (продолжительность договора в днях):
count mean std min 25% 50% 75% max
Класс 0 4456.0 894.219031 719.270635 0.0 245.00 702.0 1523.0 2314.0
Класс 1 826.0 902.573850 453.170555 28.0 548.25 884.0 1247.0 2068.0
2023-11-11T16:15:45.142587 image/svg+xml Matplotlib v3.3.4, https://matplotlib.org/
Распределение значений признака "internet_service" (тип подключения):
count unique top freq
Класс 0 4456 3 Fiber optic 1891
Класс 1 826 3 Fiber optic 444
2023-11-11T16:15:45.325185 image/svg+xml Matplotlib v3.3.4, https://matplotlib.org/
Распределение значений признака "online_security" (блокировка опасных сайтов):
count unique top freq
Класс 0 4456 3 No 2233
Класс 1 826 3 No 396
2023-11-11T16:15:45.481911 image/svg+xml Matplotlib v3.3.4, https://matplotlib.org/
Распределение значений признака "online_backup" (облачное хранилище файлов для резервного копирования данных):
count unique top freq
Класс 0 4456 3 No 2018
Класс 1 826 3 Yes 420
2023-11-11T16:15:45.647505 image/svg+xml Matplotlib v3.3.4, https://matplotlib.org/
Распределение значений признака "device_protection" (антивирус):
count unique top freq
Класс 0 4456 3 No 2021
Класс 1 826 3 Yes 412
2023-11-11T16:15:45.812545 image/svg+xml Matplotlib v3.3.4, https://matplotlib.org/
Распределение значений признака "tech_support" (выделенная линия технической поддержки):
count unique top freq
Класс 0 4456 3 No 2198
Класс 1 826 3 No 412
2023-11-11T16:15:45.976800 image/svg+xml Matplotlib v3.3.4, https://matplotlib.org/
Распределение значений признака "streaming_tv" (стриминговое телевидение):
count unique top freq
Класс 0 4456 3 No 1823
Класс 1 826 3 Yes 429
2023-11-11T16:15:46.134335 image/svg+xml Matplotlib v3.3.4, https://matplotlib.org/
Распределение значений признака "streaming_movies" (каталог фильмов):
count unique top freq
Класс 0 4456 3 No 1812
Класс 1 826 3 Yes 456
2023-11-11T16:15:46.304035 image/svg+xml Matplotlib v3.3.4, https://matplotlib.org/
Распределение значений признака "multiple_lines" (подключение телефона к нескольким линиям одновременно):
count unique top freq
Класс 0 4456 3 No 2281
Класс 1 826 3 Yes 509
2023-11-11T16:15:46.459971 image/svg+xml Matplotlib v3.3.4, https://matplotlib.org/
Распределение значений признака "active_contract" (активный договор (целевой признак)):
count mean std min 25% 50% 75% max
Класс 0 4456.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
Класс 1 826.0 1.0 0.0 1.0 1.0 1.0 1.0 1.0
2023-11-11T16:15:46.753854 image/svg+xml Matplotlib v3.3.4, https://matplotlib.org/
In [46]:
# Числовые признаки финального датафрейма,
# требующие нормализации
total_new_numeric = ['monthly_charges', 'total_charges', 'duration_contract']

print('Количество числовых признаков, требующих нормализации:', len(total_new_numeric))
total_new_train[total_new_numeric].head()
Количество числовых признаков, требующих нормализации: 3
Out[46]:
monthly_charges total_charges duration_contract
3798 100.30 4212.60 1279
3516 43.30 259.80 184
5031 101.50 977.45 276
3922 94.65 6341.55 2041
3275 20.40 204.00 304
In [47]:
# Категоиальные признаки финального датафрейма,
# требующие кодирования методом `OneHotEncoder` (для "линейных" моделей) 
# и `OrdinalEncoder` (для "деревянных" моделей).
# Это все признаки, кроме тех, названия которых входят 
# в переменную "total_new_numeric", 
# и признака, содержащего идентификаторы
total_new_categories = []

for i in total_new_train.columns:
    if (i not in total_new_numeric) & (i not in ['customer_id', 'active_contract']):
        total_new_categories.append(i)

print('Количество категориальных признаков, требующих кодирования методом "OneHotEncoder" (для "линейных" моделей) и "OrdinalEncoder" (для "деревянных" моделей):', len(total_new_categories))
total_new_train[total_new_categories].head()
Количество категориальных признаков, требующих кодирования методом "OneHotEncoder" (для "линейных" моделей) и "OrdinalEncoder" (для "деревянных" моделей): 15
Out[47]:
gender senior_citizen partner dependents type paperless_billing payment_method internet_service online_security online_backup device_protection tech_support streaming_tv streaming_movies multiple_lines
3798 Male No No No Month-to-month Yes Electronic check Fiber optic No No No Yes Yes Yes Yes
3516 Male No No No Month-to-month Yes Electronic check DSL No No No No No No No
5031 Male No Yes Yes Month-to-month Yes Electronic check Fiber optic No Yes No Yes Yes Yes No
3922 Male Yes Yes No Month-to-month Yes Credit card (automatic) Fiber optic No No No No Yes Yes Yes
3275 Female No Yes No Two year No Mailed check NoValue NoValue NoValue NoValue NoValue NoValue NoValue No

Предварительные выводы

  1. Тренировочная выборка total_new_train, полученная из объединенного датафрейма total_new содержит 20 признаков и 7043 объекта.
  2. В объединенном датафрейме имеются 3 категориальных и 27 числовых признака. Часть из них не сбалансированы. Категориальные имеют дисбаланс классов, а числовые не не имеют нормального распределения или имеют существенное количество выбросов.
  3. Признак active_contract является целевым для обучения. Он бинарный категориальный и не сбалансирован с перевесом в пользу значения 1 (контракт активен). Данный признак не содержит пропусков. Следовательно, по всем объектам выборки есть целевые значения.
  4. Многие категориальные признаки содержат пропуски. Эти пропуски не требуется обрабатывать, т.к. не известны причины их появления.
  5. Все категориальные признаки, в том числе те, что ранее содержали только 0 и 1, но кроме целевого, требуется кодировать методом OneHotEncoder (для «линейных» моделей) и OrdinalEncoder (для «деревянных» моделей).
  6. Все числовые признаки, названия которых входят в переменную total_new_numeric (monthly_charges, total_charges, duration_contract), требуется нормализовать.
  7. Все признаки требуется проверить на наличие корреляции. Сильно выраженные мультиколленеарные признаки и признаки со статистически не значемой корреляцией требуется исключить из объединенного датафрейма при обучении моделей.

Анализ мультиколлинеарности¶

In [48]:
# Анализ мультиколлинеарности 
# с помощью методов библиотеки Phik
multicollinearity = total_new_train.drop('customer_id', axis=1).phik_matrix(interval_cols=total_new_numeric)
display(multicollinearity)

# Подсветка результатов с помощью цветовой карты
#multicollinearity.style.background_gradient(cmap='YlOrRd')
sns.heatmap(multicollinearity.copy().round(1), annot=True);
gender senior_citizen partner dependents type paperless_billing payment_method monthly_charges total_charges duration_contract internet_service online_security online_backup device_protection tech_support streaming_tv streaming_movies multiple_lines active_contract
gender 1.000000 0.000000 0.006566 0.000000 0.000000 0.011572 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.022799
senior_citizen 0.000000 1.000000 0.017424 0.331492 0.087711 0.238674 0.291426 0.301566 0.135638 0.056006 0.161784 0.125800 0.109632 0.110695 0.137321 0.110441 0.112665 0.088044 0.060924
partner 0.006566 0.017424 1.000000 0.644202 0.177126 0.000000 0.238667 0.197576 0.380985 0.448887 0.000000 0.087660 0.087738 0.108014 0.072649 0.081819 0.073701 0.079918 0.220231
dependents 0.000000 0.331492 0.644202 1.000000 0.145894 0.170514 0.226053 0.189303 0.089412 0.198359 0.109556 0.115012 0.095640 0.095338 0.111950 0.084742 0.081822 0.022756 0.042390
type 0.000000 0.087711 0.177126 0.145894 1.000000 0.105117 0.278014 0.391819 0.467148 0.630725 0.506019 0.637745 0.583680 0.640261 0.669856 0.551836 0.552991 0.255877 0.090360
paperless_billing 0.011572 0.238674 0.000000 0.170514 0.105117 1.000000 0.359655 0.468074 0.203461 0.000000 0.229926 0.210475 0.197299 0.197612 0.202854 0.207174 0.203204 0.101023 0.097563
payment_method 0.000000 0.291426 0.238667 0.226053 0.278014 0.359655 1.000000 0.404730 0.337116 0.357240 0.325465 0.318070 0.296217 0.304161 0.321200 0.284987 0.286679 0.177943 0.200485
monthly_charges 0.000000 0.301566 0.197576 0.189303 0.391819 0.468074 0.404730 1.000000 0.710069 0.380074 0.918715 0.813962 0.820570 0.826953 0.818585 0.865701 0.862921 0.714656 0.221155
total_charges 0.000000 0.135638 0.380985 0.089412 0.467148 0.203461 0.337116 0.710069 1.000000 0.850265 0.488722 0.517825 0.552905 0.557239 0.523894 0.554019 0.550379 0.469162 0.299329
duration_contract 0.000000 0.056006 0.448887 0.198359 0.630725 0.000000 0.357240 0.380074 0.850265 1.000000 0.058646 0.346647 0.373740 0.379760 0.347049 0.308803 0.297715 0.350429 0.378422
internet_service 0.000000 0.161784 0.000000 0.109556 0.506019 0.229926 0.325465 0.918715 0.488722 0.058646 1.000000 0.949841 0.942828 0.942856 0.949908 0.946307 0.945961 0.738624 0.054731
online_security 0.000000 0.125800 0.087660 0.115012 0.637745 0.210475 0.318070 0.813962 0.517825 0.346647 0.949841 1.000000 0.947628 0.947048 0.952966 0.943023 0.943180 0.540095 0.059206
online_backup 0.000000 0.109632 0.087738 0.095640 0.583680 0.197299 0.296217 0.820570 0.552905 0.373740 0.942828 0.947628 1.000000 0.947803 0.947288 0.945168 0.945244 0.566072 0.088436
device_protection 0.000000 0.110695 0.108014 0.095338 0.640261 0.197612 0.304161 0.826953 0.557239 0.379760 0.942856 0.947048 0.947803 1.000000 0.950434 0.953105 0.952855 0.563521 0.084696
tech_support 0.000000 0.137321 0.072649 0.111950 0.669856 0.202854 0.321200 0.818585 0.523894 0.347049 0.949908 0.952966 0.947288 0.950434 1.000000 0.946266 0.946221 0.541000 0.050852
streaming_tv 0.000000 0.110441 0.081819 0.084742 0.551836 0.207174 0.284987 0.865701 0.554019 0.308803 0.946307 0.943023 0.945168 0.953105 0.946266 1.000000 0.965023 0.583205 0.072660
streaming_movies 0.000000 0.112665 0.073701 0.081822 0.552991 0.203204 0.286679 0.862921 0.550379 0.297715 0.945961 0.943180 0.945244 0.952855 0.946221 0.965023 1.000000 0.585280 0.084716
multiple_lines 0.000000 0.088044 0.079918 0.022756 0.255877 0.101023 0.177943 0.714656 0.469162 0.350429 0.738624 0.540095 0.566072 0.563521 0.541000 0.583205 0.585280 1.000000 0.102937
active_contract 0.022799 0.060924 0.220231 0.042390 0.090360 0.097563 0.200485 0.221155 0.299329 0.378422 0.054731 0.059206 0.088436 0.084696 0.050852 0.072660 0.084716 0.102937 1.000000
2023-11-11T16:15:49.113882 image/svg+xml Matplotlib v3.3.4, https://matplotlib.org/

Отбор признаков для использования в обучении моделей¶

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

Предлагается установить минимальный порог коррелируемости с целевым признаком в 0.3. Также предлагается установить максимальный порог мультиколлинеарности в 0.75.

In [49]:
# Создание списка не мультиколлинеарных признаков
# со статистически значимой корреляцией с целевым признаком
def non_multicollinear_features(m, target_feature, v_min, v_max):
    
    columns = [] 
    append = True
    
    for c in m.columns:
        if ((m[c][target_feature] > v_min) & (c != target_feature)): 
            for i in m.index:
                if ((c != i) & (c != target_feature) & (c not in columns) & (i not in columns)):
                    if m[c][i] > v_max:
                        if m[c][target_feature] < m[i][target_feature]:
                            append = False
                            break
            if append == True: columns.append(c) 
            append = True 
    
    return columns

columns = non_multicollinear_features(multicollinearity, 'active_contract', .03, .75)

print('Не мультиколлинеарные признаки, обладающие статистически значимой парной корреляцией с целевым признаком:')
columns
Не мультиколлинеарные признаки, обладающие статистически значимой парной корреляцией с целевым признаком:
Out[49]:
['senior_citizen',
 'partner',
 'dependents',
 'type',
 'paperless_billing',
 'payment_method',
 'monthly_charges',
 'duration_contract',
 'online_backup',
 'streaming_movies',
 'multiple_lines']
In [50]:
# Разделение отобранных параметров на категориальные и числовые
features_categories = []
for i in columns:
    for j in total_new_categories:
        if i == j: features_categories.append(i)

features_numeric = []
for i in columns:
    for j in total_new_numeric:
        if i == j: features_numeric.append(i)

print('Категориальные признаки:', features_categories)
print()
print('Числовые признаки      :', features_numeric)
Категориальные признаки: ['senior_citizen', 'partner', 'dependents', 'type', 'paperless_billing', 'payment_method', 'online_backup', 'streaming_movies', 'multiple_lines']

Числовые признаки      : ['monthly_charges', 'duration_contract']

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

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

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

Названия этих признаков собраны в переменную columns. Учитывая выше сказанное требуется использовать ранее созданные выборки, но в выборках с «фичами» оставить только признаки, перечисленные в переменной columns.

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

In [51]:
# Очистка выборок с фичами 
# от статистически не значимых признаков
features_train = features_train[columns]
features_test = features_test[columns]

# Очистка памяти 
# от неиспользуемых переменных
#del contract_new, personal_new, internet_new, phone_new
#del total_new, features, target
#del total_new_train
In [52]:
# Проверка выборки "features_train"
print(features_train.info())
features_train.head()
<class 'pandas.core.frame.DataFrame'>
Index: 5282 entries, 3798 to 670
Data columns (total 11 columns):
 #   Column             Non-Null Count  Dtype  
---  ------             --------------  -----  
 0   senior_citizen     5282 non-null   object 
 1   partner            5282 non-null   object 
 2   dependents         5282 non-null   object 
 3   type               5282 non-null   object 
 4   paperless_billing  5282 non-null   object 
 5   payment_method     5282 non-null   object 
 6   monthly_charges    5282 non-null   float64
 7   duration_contract  5282 non-null   int64  
 8   online_backup      5282 non-null   object 
 9   streaming_movies   5282 non-null   object 
 10  multiple_lines     5282 non-null   object 
dtypes: float64(1), int64(1), object(9)
memory usage: 624.2+ KB
None
Out[52]:
senior_citizen partner dependents type paperless_billing payment_method monthly_charges duration_contract online_backup streaming_movies multiple_lines
3798 No No No Month-to-month Yes Electronic check 100.30 1279 No Yes Yes
3516 No No No Month-to-month Yes Electronic check 43.30 184 No No No
5031 No Yes Yes Month-to-month Yes Electronic check 101.50 276 Yes Yes No
3922 Yes Yes No Month-to-month Yes Credit card (automatic) 94.65 2041 No Yes Yes
3275 No Yes No Two year No Mailed check 20.40 304 NoValue NoValue No
In [53]:
# Проверка выборки "target_train"
print(pd.DataFrame(target_train).info())
target_train.head()
<class 'pandas.core.frame.DataFrame'>
Index: 5282 entries, 3798 to 670
Data columns (total 1 columns):
 #   Column           Non-Null Count  Dtype
---  ------           --------------  -----
 0   active_contract  5282 non-null   int64
dtypes: int64(1)
memory usage: 211.6 KB
None
Out[53]:
3798    0
3516    0
5031    0
3922    0
3275    1
Name: active_contract, dtype: int64
In [54]:
# Проверка выборки "features_test"
print(features_test.info())
features_test.head()
<class 'pandas.core.frame.DataFrame'>
Index: 1761 entries, 5707 to 538
Data columns (total 11 columns):
 #   Column             Non-Null Count  Dtype  
---  ------             --------------  -----  
 0   senior_citizen     1761 non-null   object 
 1   partner            1761 non-null   object 
 2   dependents         1761 non-null   object 
 3   type               1761 non-null   object 
 4   paperless_billing  1761 non-null   object 
 5   payment_method     1761 non-null   object 
 6   monthly_charges    1761 non-null   float64
 7   duration_contract  1761 non-null   int64  
 8   online_backup      1761 non-null   object 
 9   streaming_movies   1761 non-null   object 
 10  multiple_lines     1761 non-null   object 
dtypes: float64(1), int64(1), object(9)
memory usage: 165.1+ KB
None
Out[54]:
senior_citizen partner dependents type paperless_billing payment_method monthly_charges duration_contract online_backup streaming_movies multiple_lines
5707 No No No Month-to-month No Electronic check 45.55 62 No No No
2139 No No No Month-to-month Yes Electronic check 66.85 215 No Yes No
1083 No No No Month-to-month Yes Electronic check 80.95 31 No Yes No
4035 No Yes Yes Month-to-month Yes Bank transfer (automatic) 93.25 1584 Yes Yes Yes
4553 No Yes No Two year Yes Bank transfer (automatic) 115.80 2191 Yes Yes Yes
In [55]:
# Проверка выборки "target_test"
print(pd.DataFrame(target_test).info())
target_test.head()
<class 'pandas.core.frame.DataFrame'>
Index: 1761 entries, 5707 to 538
Data columns (total 1 columns):
 #   Column           Non-Null Count  Dtype
---  ------           --------------  -----
 0   active_contract  1761 non-null   int64
dtypes: int64(1)
memory usage: 27.5 KB
None
Out[55]:
5707    0
2139    0
1083    1
4035    0
4553    0
Name: active_contract, dtype: int64

Обучение моделей машинного обучения¶

Обучение как минимум двух моделей. Подбор хотя бы для одной из них как минимум двух гиперпараметров.

Полезные функции¶

In [56]:
# Model + Params + Pipeline + HalvingGridSearchCV
def model_pipeline_gridsearch(
    features_train, 
    target_train, 
    pipeline, 
    params, 
    data_grids, 
    data_times 
):
    
    # Начало отслеживания времени
    start_time = time.time() 
    
    # HalvingGridSearchCV
    # (о подборе оптимальных параметров:
    # https://scikit-learn.ru/3-2-tuning-the-hyper-parameters-of-an-estimator/)
    #grid = HalvingRandomSearchCV(
    grid = HalvingGridSearchCV(
        pipeline, 
        params, 
        #resorce=
        cv=4, # параметр KFold для кроссвалидации (обучющая и валидационная выборки 75:25)
        #n_jobs=-1, # количество параллельно выполняемых заданий (-1 - задействованы все процессоры)
        scoring='roc_auc', # функция оценки (ошибки)
        error_score='raise', 
        random_state=STATE
    ) 
    
    # Обучение
    grid.fit(features_train, target_train)
    
    # Подсчет времени выполнения скрипта
    finish_time = time.time()
    funtion_time = finish_time - start_time
    
    data_grids.append(grid)
    data_times.append(funtion_time) 
    
    return data_grids, data_times
In [57]:
# Вывод на печать результатов модели
def print_model_result(grids, data_times, model_name, process_print=False):
    print()
    print('Модель         :', model_name)
    print('Метрика ROC AUC:', grids[-1].best_score_)
    print(f'Время          : {data_times[-1]} секунд')
    print('Параметры      :', grids[-1].best_estimator_[-1].get_params())
    print()
    if process_print == True:
        print('Результаты выбора параметров модели:')
        display(pd.DataFrame(grids[-1].cv_results_)[[
            'params', 
            'mean_train_score', 
            'mean_test_score'
        ]])
    print('-' * 30)
In [58]:
def print_model_graphicresult(grids):
    pd.DataFrame(grids[-1].cv_results_)[[
        'mean_test_score', 
        'mean_train_score'
    ]].plot();
In [59]:
# Переменная, содержащая баланс 
# классов целевой переменной
class_weights = {
    1:target_train.loc[target_train == 1].count() / target_train.count(), 
    0:target_train.loc[target_train == 0].count() / target_train.count()
}

# Проверка переменной
class_weights
Out[59]:
{1: 0.1563801590306702, 0: 0.8436198409693298}
In [60]:
# Функция уточнения значений гиперпараметров моделей 
# путем выделения центров левого и правого
# диапазнов значений интервальных гиперпараметров
def two_range(a, b, int_type=False):
    
    center = (b - a) / 2
    left = a + (center / 2)
    right = b - (center / 2)
    center = a + center
    
    # Приведение значений к целому 
    # (в некоторых случаях не допустимы 
    # значения с плавающей точкой)
    if int_type == True:
        left = int(left)
        center = int(center)
        right = int(right)
    
    return [left, center, right]
In [61]:
# Поиск лучших моделей и их параметров
data_grids = []
data_times = []

Обучение модели LogisticRegression¶

In [62]:
# LogisticRegression (solver, C)

# Модель
model = LogisticRegression(
    random_state=STATE, 
    class_weight='balanced'
) 

# Преобразование признаков
column_trans = ColumnTransformer([
    ('ohe', OneHotEncoder(drop='first', handle_unknown='ignore'), features_categories), # кодирование категорий через OneHotEncoder (для линейной модели)
    #('std', PowerTransformer(method='box-cox'), features_numeric), # Приведение к нормальному распределению
    ('std', PowerTransformer(), features_numeric), # Приведение к нормальному распределению
], remainder='drop', verbose_feature_names_out=False)

# Pipeline
pipeline = Pipeline([
    ('transform', column_trans), # трансформация выборки
    ('clf', model) # модель классификации 
])

# Диапазон значений искомого параметра
n_С = [.111, .5, .999]

# Обход диапазона значений
while n_С[1] - n_С[0] > .01:
    n_С = two_range(n_С[0], n_С[-1], False)

    # Параметры модели
    params = [{
        'clf__solver': ('liblinear', 'lbfgs', 'newton-cg'),
        'clf__C': n_С
    }]

    # Обучение модели
    data_grids, data_times = model_pipeline_gridsearch(
        features_train, 
        target_train, 
        pipeline, 
        params, 
        data_grids, 
        data_times 
    )

    # Вывод результата обучения модели
    print_model_result(data_grids, data_times, 'LogisticRegression', process_print=True)

    # Лучшее значение искомого параметра
    this_best_params = data_grids[-1].best_estimator_[-1].get_params()['C']

    # Корректировка диапазона значений искомого параметра
    if len(n_С) > 1:
        if n_С[0] == this_best_params:
            n_С = [n_С[0], (n_С[0] + (n_С[1] - n_С[0])/2), n_С[1]]
        if n_С[2] == this_best_params:
            n_С = [n_С[1], (n_С[1] + (n_С[2] - n_С[1])/2), n_С[2]]

# График финальной версии модели этой ячейки
print_model_graphicresult(data_grids)
Модель         : LogisticRegression
Метрика ROC AUC: 0.7267741438350803
Время          : 8.754640102386475 секунд
Параметры      : {'C': 0.777, 'class_weight': 'balanced', 'dual': False, 'fit_intercept': True, 'intercept_scaling': 1, 'l1_ratio': None, 'max_iter': 100, 'multi_class': 'auto', 'n_jobs': None, 'penalty': 'l2', 'random_state': 30112023, 'solver': 'lbfgs', 'tol': 0.0001, 'verbose': 0, 'warm_start': False}

Результаты выбора параметров модели:
params mean_train_score mean_test_score
0 {‘clf__C’: 0.333, ‘clf__solver’: ‘liblinear’} 0.750635 0.672675
1 {‘clf__C’: 0.333, ‘clf__solver’: ‘lbfgs’} 0.753359 0.677325
2 {‘clf__C’: 0.333, ‘clf__solver’: ‘newton-cg’} 0.753323 0.677226
3 {‘clf__C’: 0.555, ‘clf__solver’: ‘liblinear’} 0.753197 0.678034
4 {‘clf__C’: 0.555, ‘clf__solver’: ‘lbfgs’} 0.755023 0.679600
5 {‘clf__C’: 0.555, ‘clf__solver’: ‘newton-cg’} 0.755023 0.679600
6 {‘clf__C’: 0.777, ‘clf__solver’: ‘liblinear’} 0.754340 0.679522
7 {‘clf__C’: 0.777, ‘clf__solver’: ‘lbfgs’} 0.755755 0.679621
8 {‘clf__C’: 0.777, ‘clf__solver’: ‘newton-cg’} 0.755719 0.679533
9 {‘clf__C’: 0.555, ‘clf__solver’: ‘lbfgs’} 0.739170 0.708645
10 {‘clf__C’: 0.555, ‘clf__solver’: ‘newton-cg’} 0.739160 0.708667
11 {‘clf__C’: 0.777, ‘clf__solver’: ‘lbfgs’} 0.739191 0.708770
12 {‘clf__C’: 0.777, ‘clf__solver’: ‘lbfgs’} 0.731876 0.726774
------------------------------

Модель         : LogisticRegression
Метрика ROC AUC: 0.7268027416455016
Время          : 11.394300699234009 секунд
Параметры      : {'C': 0.666, 'class_weight': 'balanced', 'dual': False, 'fit_intercept': True, 'intercept_scaling': 1, 'l1_ratio': None, 'max_iter': 100, 'multi_class': 'auto', 'n_jobs': None, 'penalty': 'l2', 'random_state': 30112023, 'solver': 'newton-cg', 'tol': 0.0001, 'verbose': 0, 'warm_start': False}

Результаты выбора параметров модели:
params mean_train_score mean_test_score
0 {‘clf__C’: 0.6105, ‘clf__solver’: ‘liblinear’} 0.753543 0.678651
1 {‘clf__C’: 0.6105, ‘clf__solver’: ‘lbfgs’} 0.754966 0.679848
2 {‘clf__C’: 0.6105, ‘clf__solver’: ‘newton-cg’} 0.754992 0.679848
3 {‘clf__C’: 0.666, ‘clf__solver’: ‘liblinear’} 0.753853 0.679224
4 {‘clf__C’: 0.666, ‘clf__solver’: ‘lbfgs’} 0.755273 0.679890
5 {‘clf__C’: 0.666, ‘clf__solver’: ‘newton-cg’} 0.755264 0.679890
6 {‘clf__C’: 0.7215, ‘clf__solver’: ‘liblinear’} 0.754075 0.678943
7 {‘clf__C’: 0.7215, ‘clf__solver’: ‘lbfgs’} 0.755541 0.679841
8 {‘clf__C’: 0.7215, ‘clf__solver’: ‘newton-cg’} 0.755549 0.679841
9 {‘clf__C’: 0.6105, ‘clf__solver’: ‘newton-cg’} 0.739136 0.708586
10 {‘clf__C’: 0.666, ‘clf__solver’: ‘lbfgs’} 0.739211 0.708719
11 {‘clf__C’: 0.666, ‘clf__solver’: ‘newton-cg’} 0.739202 0.708729
12 {‘clf__C’: 0.666, ‘clf__solver’: ‘newton-cg’} 0.731871 0.726803
------------------------------

Модель         : LogisticRegression
Метрика ROC AUC: 0.7267885583928695
Время          : 9.398901462554932 секунд
Параметры      : {'C': 0.6937500000000001, 'class_weight': 'balanced', 'dual': False, 'fit_intercept': True, 'intercept_scaling': 1, 'l1_ratio': None, 'max_iter': 100, 'multi_class': 'auto', 'n_jobs': None, 'penalty': 'l2', 'random_state': 30112023, 'solver': 'lbfgs', 'tol': 0.0001, 'verbose': 0, 'warm_start': False}

Результаты выбора параметров модели:
params mean_train_score mean_test_score
0 {‘clf__C’: 0.63825, ‘clf__solver’: ‘liblinear’} 0.753789 0.678706
1 {‘clf__C’: 0.63825, ‘clf__solver’: ‘lbfgs’} 0.755094 0.679836
2 {‘clf__C’: 0.63825, ‘clf__solver’: ‘newton-cg’} 0.755085 0.679836
3 {‘clf__C’: 0.666, ‘clf__solver’: ‘liblinear’} 0.753853 0.679224
4 {‘clf__C’: 0.666, ‘clf__solver’: ‘lbfgs’} 0.755273 0.679890
5 {‘clf__C’: 0.666, ‘clf__solver’: ‘newton-cg’} 0.755264 0.679890
6 {‘clf__C’: 0.6937500000000001, ‘clf__solver’: … 0.753941 0.679209
7 {‘clf__C’: 0.6937500000000001, ‘clf__solver’: … 0.755450 0.679891
8 {‘clf__C’: 0.6937500000000001, ‘clf__solver’: … 0.755459 0.679977
9 {‘clf__C’: 0.666, ‘clf__solver’: ‘newton-cg’} 0.739202 0.708729
10 {‘clf__C’: 0.6937500000000001, ‘clf__solver’: … 0.739220 0.708742
11 {‘clf__C’: 0.6937500000000001, ‘clf__solver’: … 0.739218 0.708732
12 {‘clf__C’: 0.6937500000000001, ‘clf__solver’: … 0.731871 0.726789
------------------------------

Модель         : LogisticRegression
Метрика ROC AUC: 0.7267939804399587
Время          : 10.204588413238525 секунд
Параметры      : {'C': 0.6868125, 'class_weight': 'balanced', 'dual': False, 'fit_intercept': True, 'intercept_scaling': 1, 'l1_ratio': None, 'max_iter': 100, 'multi_class': 'auto', 'n_jobs': None, 'penalty': 'l2', 'random_state': 30112023, 'solver': 'newton-cg', 'tol': 0.0001, 'verbose': 0, 'warm_start': False}

Результаты выбора параметров модели:
params mean_train_score mean_test_score
0 {‘clf__C’: 0.6729375000000001, ‘clf__solver’: … 0.753854 0.679129
1 {‘clf__C’: 0.6729375000000001, ‘clf__solver’: … 0.755335 0.679802
2 {‘clf__C’: 0.6729375000000001, ‘clf__solver’: … 0.755371 0.679802
3 {‘clf__C’: 0.679875, ‘clf__solver’: ‘liblinear’} 0.753879 0.679119
4 {‘clf__C’: 0.679875, ‘clf__solver’: ‘lbfgs’} 0.755407 0.679887
5 {‘clf__C’: 0.679875, ‘clf__solver’: ‘newton-cg’} 0.755398 0.679887
6 {‘clf__C’: 0.6868125, ‘clf__solver’: ‘liblinear’} 0.753914 0.679109
7 {‘clf__C’: 0.6868125, ‘clf__solver’: ‘lbfgs’} 0.755371 0.679891
8 {‘clf__C’: 0.6868125, ‘clf__solver’: ‘newton-cg’} 0.755388 0.679891
9 {‘clf__C’: 0.679875, ‘clf__solver’: ‘newton-cg’} 0.739210 0.708710
10 {‘clf__C’: 0.6868125, ‘clf__solver’: ‘lbfgs’} 0.739221 0.708742
11 {‘clf__C’: 0.6868125, ‘clf__solver’: ‘newton-cg’} 0.739213 0.708742
12 {‘clf__C’: 0.6868125, ‘clf__solver’: ‘newton-cg’} 0.731870 0.726794
------------------------------
2023-11-11T16:16:30.119768 image/svg+xml Matplotlib v3.3.4, https://matplotlib.org/

Обучение модели RandomForestClassifier¶

In [63]:
# RandomForestRegressor (max_features, max_depth)

# Модель
model = RandomForestClassifier(
    random_state=STATE
) 

# Преобразование признаков
column_trans = ColumnTransformer([
    ('ohe', OrdinalEncoder(), features_categories), # кодирование категорий через OrdinalEncoder (для деревьев и бустингов)
    ('std', MaxAbsScaler(), features_numeric), # Стандартизация числовых данных от 0 до 1
], remainder='drop', verbose_feature_names_out=False)

# Pipeline
pipeline = Pipeline([
    ('transform', column_trans), # трансформация выборки
    ('clf', model) # модель классификации 
])

# Диапазон значений искомого параметра
# max_depth
n_max_depth = [1, 100, 200]
# n_estimators
n_n_estimators = [1, 100, 200]

# Обход диапазона значений
while (
    (n_max_depth[1] - n_max_depth[0] > 10) 
    | (n_n_estimators[1] - n_n_estimators[0] > 10)
):
    # max_depth
    n_max_depth = two_range(n_max_depth[0], n_max_depth[-1], True)
    # n_estimators
    n_n_estimators = two_range(n_n_estimators[0], n_n_estimators[-1], True)
    
    # Параметры модели
    params = [{
        'clf__max_features':('sqrt', 'log2', None, 1.), 
        'clf__max_depth': n_max_depth, 
        'clf__n_estimators': n_n_estimators
    }]
    
    # Обучение модели
    data_grids, data_times = model_pipeline_gridsearch(
        features_train, 
        target_train, 
        pipeline, 
        params, 
        data_grids, 
        data_times 
    )
    
    # Вывод результата обучения модели
    print_model_result(data_grids, data_times, 'RandomForestRegressor', process_print=True)
    
    # Корректировка диапазона значений искомого параметра
    # Лучшее значение искомого параметра
    this_best_params = data_grids[-1].best_estimator_[-1].get_params()['max_depth']
    # max_depth
    if len(n_max_depth) > 1:
        if n_max_depth[0] == this_best_params:
            n_max_depth = [n_max_depth[0], int(n_max_depth[0] + (n_max_depth[1] - n_max_depth[0])/2), n_max_depth[1]]
        if n_max_depth[2] == this_best_params:
            n_max_depth = [n_max_depth[1], int(n_max_depth[1] + (n_max_depth[2] - n_max_depth[1])/2), n_max_depth[2]]
        else:
            n_max_depth = n_max_depth
    else:
        n_max_depth = n_max_depth
    # Лучшее значение искомого параметра
    this_best_params = data_grids[-1].best_estimator_[-1].get_params()['n_estimators']
    # n_estimators
    if len(n_n_estimators) > 1:
        if n_n_estimators[0] == this_best_params:
            n_n_estimators = [n_n_estimators[0], int(n_n_estimators[0] + (n_n_estimators[1] - n_n_estimators[0])/2), n_n_estimators[1]]
        if n_n_estimators[2] == this_best_params:
            n_n_estimators = [n_n_estimators[1], int(n_n_estimators[1] + (n_n_estimators[2] - n_n_estimators[1])/2), n_n_estimators[2]]
    
# График финальной версии модели этой ячейки
print_model_graphicresult(data_grids)
Модель         : RandomForestRegressor
Метрика ROC AUC: 0.8102019536468412
Время          : 38.58482789993286 секунд
Параметры      : {'bootstrap': True, 'ccp_alpha': 0.0, 'class_weight': None, 'criterion': 'gini', 'max_depth': 50, 'max_features': 'sqrt', 'max_leaf_nodes': None, 'max_samples': None, 'min_impurity_decrease': 0.0, 'min_samples_leaf': 1, 'min_samples_split': 2, 'min_weight_fraction_leaf': 0.0, 'n_estimators': 100, 'n_jobs': None, 'oob_score': False, 'random_state': 30112023, 'verbose': 0, 'warm_start': False}

Результаты выбора параметров модели:
params mean_train_score mean_test_score
0 {‘clf__max_depth’: 50, ‘clf__max_features’: ‘s… 1.0 0.746230
1 {‘clf__max_depth’: 50, ‘clf__max_features’: ‘s… 1.0 0.736584
2 {‘clf__max_depth’: 50, ‘clf__max_features’: ‘s… 1.0 0.736183
3 {‘clf__max_depth’: 50, ‘clf__max_features’: ‘l… 1.0 0.746230
4 {‘clf__max_depth’: 50, ‘clf__max_features’: ‘l… 1.0 0.736584
5 {‘clf__max_depth’: 50, ‘clf__max_features’: ‘l… 1.0 0.736183
6 {‘clf__max_depth’: 50, ‘clf__max_features’: No… 1.0 0.723803
7 {‘clf__max_depth’: 50, ‘clf__max_features’: No… 1.0 0.735105
8 {‘clf__max_depth’: 50, ‘clf__max_features’: No… 1.0 0.725254
9 {‘clf__max_depth’: 50, ‘clf__max_features’: 1…. 1.0 0.723803
10 {‘clf__max_depth’: 50, ‘clf__max_features’: 1…. 1.0 0.735105
11 {‘clf__max_depth’: 50, ‘clf__max_features’: 1…. 1.0 0.725254
12 {‘clf__max_depth’: 100, ‘clf__max_features’: ‘… 1.0 0.746230
13 {‘clf__max_depth’: 100, ‘clf__max_features’: ‘… 1.0 0.736584
14 {‘clf__max_depth’: 100, ‘clf__max_features’: ‘… 1.0 0.736183
15 {‘clf__max_depth’: 100, ‘clf__max_features’: ‘… 1.0 0.746230
16 {‘clf__max_depth’: 100, ‘clf__max_features’: ‘… 1.0 0.736584
17 {‘clf__max_depth’: 100, ‘clf__max_features’: ‘… 1.0 0.736183
18 {‘clf__max_depth’: 100, ‘clf__max_features’: N… 1.0 0.723803
19 {‘clf__max_depth’: 100, ‘clf__max_features’: N… 1.0 0.735105
20 {‘clf__max_depth’: 100, ‘clf__max_features’: N… 1.0 0.725254
21 {‘clf__max_depth’: 100, ‘clf__max_features’: 1… 1.0 0.723803
22 {‘clf__max_depth’: 100, ‘clf__max_features’: 1… 1.0 0.735105
23 {‘clf__max_depth’: 100, ‘clf__max_features’: 1… 1.0 0.725254
24 {‘clf__max_depth’: 150, ‘clf__max_features’: ‘… 1.0 0.746230
25 {‘clf__max_depth’: 150, ‘clf__max_features’: ‘… 1.0 0.736584
26 {‘clf__max_depth’: 150, ‘clf__max_features’: ‘… 1.0 0.736183
27 {‘clf__max_depth’: 150, ‘clf__max_features’: ‘… 1.0 0.746230
28 {‘clf__max_depth’: 150, ‘clf__max_features’: ‘… 1.0 0.736584
29 {‘clf__max_depth’: 150, ‘clf__max_features’: ‘… 1.0 0.736183
30 {‘clf__max_depth’: 150, ‘clf__max_features’: N… 1.0 0.723803
31 {‘clf__max_depth’: 150, ‘clf__max_features’: N… 1.0 0.735105
32 {‘clf__max_depth’: 150, ‘clf__max_features’: N… 1.0 0.725254
33 {‘clf__max_depth’: 150, ‘clf__max_features’: 1… 1.0 0.723803
34 {‘clf__max_depth’: 150, ‘clf__max_features’: 1… 1.0 0.735105
35 {‘clf__max_depth’: 150, ‘clf__max_features’: 1… 1.0 0.725254
36 {‘clf__max_depth’: 100, ‘clf__max_features’: ‘… 1.0 0.743447
37 {‘clf__max_depth’: 150, ‘clf__max_features’: ‘… 1.0 0.743447
38 {‘clf__max_depth’: 150, ‘clf__max_features’: ‘… 1.0 0.743447
39 {‘clf__max_depth’: 50, ‘clf__max_features’: ‘l… 1.0 0.743447
40 {‘clf__max_depth’: 50, ‘clf__max_features’: ‘s… 1.0 0.743447
41 {‘clf__max_depth’: 100, ‘clf__max_features’: ‘… 1.0 0.743447
42 {‘clf__max_depth’: 100, ‘clf__max_features’: ‘… 1.0 0.728754
43 {‘clf__max_depth’: 150, ‘clf__max_features’: ‘… 1.0 0.728754
44 {‘clf__max_depth’: 100, ‘clf__max_features’: ‘… 1.0 0.728754
45 {‘clf__max_depth’: 150, ‘clf__max_features’: ‘… 1.0 0.728754
46 {‘clf__max_depth’: 50, ‘clf__max_features’: ‘l… 1.0 0.728754
47 {‘clf__max_depth’: 50, ‘clf__max_features’: ‘s… 1.0 0.728754
48 {‘clf__max_depth’: 150, ‘clf__max_features’: ‘… 1.0 0.771462
49 {‘clf__max_depth’: 50, ‘clf__max_features’: ‘l… 1.0 0.771462
50 {‘clf__max_depth’: 50, ‘clf__max_features’: ‘s… 1.0 0.771462
51 {‘clf__max_depth’: 100, ‘clf__max_features’: ‘… 1.0 0.771462
52 {‘clf__max_depth’: 50, ‘clf__max_features’: ‘s… 1.0 0.810202
53 {‘clf__max_depth’: 100, ‘clf__max_features’: ‘… 1.0 0.810202
------------------------------

Модель         : RandomForestRegressor
Метрика ROC AUC: 0.8112027888493061
Время          : 43.28140997886658 секунд
Параметры      : {'bootstrap': True, 'ccp_alpha': 0.0, 'class_weight': None, 'criterion': 'gini', 'max_depth': 62, 'max_features': 'sqrt', 'max_leaf_nodes': None, 'max_samples': None, 'min_impurity_decrease': 0.0, 'min_samples_leaf': 1, 'min_samples_split': 2, 'min_weight_fraction_leaf': 0.0, 'n_estimators': 125, 'n_jobs': None, 'oob_score': False, 'random_state': 30112023, 'verbose': 0, 'warm_start': False}

Результаты выбора параметров модели:
params mean_train_score mean_test_score
0 {‘clf__max_depth’: 62, ‘clf__max_features’: ‘s… 1.0 0.732380
1 {‘clf__max_depth’: 62, ‘clf__max_features’: ‘s… 1.0 0.736584
2 {‘clf__max_depth’: 62, ‘clf__max_features’: ‘s… 1.0 0.737170
3 {‘clf__max_depth’: 62, ‘clf__max_features’: ‘l… 1.0 0.732380
4 {‘clf__max_depth’: 62, ‘clf__max_features’: ‘l… 1.0 0.736584
5 {‘clf__max_depth’: 62, ‘clf__max_features’: ‘l… 1.0 0.737170
6 {‘clf__max_depth’: 62, ‘clf__max_features’: No… 1.0 0.737403
7 {‘clf__max_depth’: 62, ‘clf__max_features’: No… 1.0 0.735105
8 {‘clf__max_depth’: 62, ‘clf__max_features’: No… 1.0 0.735968
9 {‘clf__max_depth’: 62, ‘clf__max_features’: 1…. 1.0 0.737403
10 {‘clf__max_depth’: 62, ‘clf__max_features’: 1…. 1.0 0.735105
11 {‘clf__max_depth’: 62, ‘clf__max_features’: 1…. 1.0 0.735968
12 {‘clf__max_depth’: 75, ‘clf__max_features’: ‘s… 1.0 0.732380
13 {‘clf__max_depth’: 75, ‘clf__max_features’: ‘s… 1.0 0.736584
14 {‘clf__max_depth’: 75, ‘clf__max_features’: ‘s… 1.0 0.737170
15 {‘clf__max_depth’: 75, ‘clf__max_features’: ‘l… 1.0 0.732380
16 {‘clf__max_depth’: 75, ‘clf__max_features’: ‘l… 1.0 0.736584
17 {‘clf__max_depth’: 75, ‘clf__max_features’: ‘l… 1.0 0.737170
18 {‘clf__max_depth’: 75, ‘clf__max_features’: No… 1.0 0.737403
19 {‘clf__max_depth’: 75, ‘clf__max_features’: No… 1.0 0.735105
20 {‘clf__max_depth’: 75, ‘clf__max_features’: No… 1.0 0.735968
21 {‘clf__max_depth’: 75, ‘clf__max_features’: 1…. 1.0 0.737403
22 {‘clf__max_depth’: 75, ‘clf__max_features’: 1…. 1.0 0.735105
23 {‘clf__max_depth’: 75, ‘clf__max_features’: 1…. 1.0 0.735968
24 {‘clf__max_depth’: 87, ‘clf__max_features’: ‘s… 1.0 0.732380
25 {‘clf__max_depth’: 87, ‘clf__max_features’: ‘s… 1.0 0.736584
26 {‘clf__max_depth’: 87, ‘clf__max_features’: ‘s… 1.0 0.737170
27 {‘clf__max_depth’: 87, ‘clf__max_features’: ‘l… 1.0 0.732380
28 {‘clf__max_depth’: 87, ‘clf__max_features’: ‘l… 1.0 0.736584
29 {‘clf__max_depth’: 87, ‘clf__max_features’: ‘l… 1.0 0.737170
30 {‘clf__max_depth’: 87, ‘clf__max_features’: No… 1.0 0.737403
31 {‘clf__max_depth’: 87, ‘clf__max_features’: No… 1.0 0.735105
32 {‘clf__max_depth’: 87, ‘clf__max_features’: No… 1.0 0.735968
33 {‘clf__max_depth’: 87, ‘clf__max_features’: 1…. 1.0 0.737403
34 {‘clf__max_depth’: 87, ‘clf__max_features’: 1…. 1.0 0.735105
35 {‘clf__max_depth’: 87, ‘clf__max_features’: 1…. 1.0 0.735968
36 {‘clf__max_depth’: 62, ‘clf__max_features’: ‘l… 1.0 0.744512
37 {‘clf__max_depth’: 75, ‘clf__max_features’: ‘s… 1.0 0.744512
38 {‘clf__max_depth’: 87, ‘clf__max_features’: ‘s… 1.0 0.744512
39 {‘clf__max_depth’: 87, ‘clf__max_features’: ‘l… 1.0 0.744512
40 {‘clf__max_depth’: 62, ‘clf__max_features’: ‘s… 1.0 0.744512
41 {‘clf__max_depth’: 75, ‘clf__max_features’: ‘l… 1.0 0.744512
42 {‘clf__max_depth’: 62, ‘clf__max_features’: No… 1.0 0.742024
43 {‘clf__max_depth’: 62, ‘clf__max_features’: 1…. 1.0 0.742024
44 {‘clf__max_depth’: 75, ‘clf__max_features’: No… 1.0 0.742024
45 {‘clf__max_depth’: 87, ‘clf__max_features’: No… 1.0 0.742024
46 {‘clf__max_depth’: 87, ‘clf__max_features’: 1…. 1.0 0.742024
47 {‘clf__max_depth’: 75, ‘clf__max_features’: 1…. 1.0 0.742024
48 {‘clf__max_depth’: 87, ‘clf__max_features’: ‘s… 1.0 0.771273
49 {‘clf__max_depth’: 87, ‘clf__max_features’: ‘l… 1.0 0.771273
50 {‘clf__max_depth’: 62, ‘clf__max_features’: ‘s… 1.0 0.771273
51 {‘clf__max_depth’: 75, ‘clf__max_features’: ‘l… 1.0 0.771273
52 {‘clf__max_depth’: 62, ‘clf__max_features’: ‘s… 1.0 0.811203
53 {‘clf__max_depth’: 75, ‘clf__max_features’: ‘l… 1.0 0.811203
------------------------------

Модель         : RandomForestRegressor
Метрика ROC AUC: 0.8110573515684645
Время          : 46.0589439868927 секунд
Параметры      : {'bootstrap': True, 'ccp_alpha': 0.0, 'class_weight': None, 'criterion': 'gini', 'max_depth': 65, 'max_features': 'sqrt', 'max_leaf_nodes': None, 'max_samples': None, 'min_impurity_decrease': 0.0, 'min_samples_leaf': 1, 'min_samples_split': 2, 'min_weight_fraction_leaf': 0.0, 'n_estimators': 118, 'n_jobs': None, 'oob_score': False, 'random_state': 30112023, 'verbose': 0, 'warm_start': False}

Результаты выбора параметров модели:
params mean_train_score mean_test_score
0 {‘clf__max_depth’: 65, ‘clf__max_features’: ‘s… 1.0 0.737445
1 {‘clf__max_depth’: 65, ‘clf__max_features’: ‘s… 1.0 0.737653
2 {‘clf__max_depth’: 65, ‘clf__max_features’: ‘s… 1.0 0.742193
3 {‘clf__max_depth’: 65, ‘clf__max_features’: ‘l… 1.0 0.737445
4 {‘clf__max_depth’: 65, ‘clf__max_features’: ‘l… 1.0 0.737653
5 {‘clf__max_depth’: 65, ‘clf__max_features’: ‘l… 1.0 0.742193
6 {‘clf__max_depth’: 65, ‘clf__max_features’: No… 1.0 0.733404
7 {‘clf__max_depth’: 65, ‘clf__max_features’: No… 1.0 0.733003
8 {‘clf__max_depth’: 65, ‘clf__max_features’: No… 1.0 0.736687
9 {‘clf__max_depth’: 65, ‘clf__max_features’: 1…. 1.0 0.733404
10 {‘clf__max_depth’: 65, ‘clf__max_features’: 1…. 1.0 0.733003
11 {‘clf__max_depth’: 65, ‘clf__max_features’: 1…. 1.0 0.736687
12 {‘clf__max_depth’: 68, ‘clf__max_features’: ‘s… 1.0 0.737445
13 {‘clf__max_depth’: 68, ‘clf__max_features’: ‘s… 1.0 0.737653
14 {‘clf__max_depth’: 68, ‘clf__max_features’: ‘s… 1.0 0.742193
15 {‘clf__max_depth’: 68, ‘clf__max_features’: ‘l… 1.0 0.737445
16 {‘clf__max_depth’: 68, ‘clf__max_features’: ‘l… 1.0 0.737653
17 {‘clf__max_depth’: 68, ‘clf__max_features’: ‘l… 1.0 0.742193
18 {‘clf__max_depth’: 68, ‘clf__max_features’: No… 1.0 0.733404
19 {‘clf__max_depth’: 68, ‘clf__max_features’: No… 1.0 0.733003
20 {‘clf__max_depth’: 68, ‘clf__max_features’: No… 1.0 0.736687
21 {‘clf__max_depth’: 68, ‘clf__max_features’: 1…. 1.0 0.733404
22 {‘clf__max_depth’: 68, ‘clf__max_features’: 1…. 1.0 0.733003
23 {‘clf__max_depth’: 68, ‘clf__max_features’: 1…. 1.0 0.736687
24 {‘clf__max_depth’: 71, ‘clf__max_features’: ‘s… 1.0 0.737445
25 {‘clf__max_depth’: 71, ‘clf__max_features’: ‘s… 1.0 0.737653
26 {‘clf__max_depth’: 71, ‘clf__max_features’: ‘s… 1.0 0.742193
27 {‘clf__max_depth’: 71, ‘clf__max_features’: ‘l… 1.0 0.737445
28 {‘clf__max_depth’: 71, ‘clf__max_features’: ‘l… 1.0 0.737653
29 {‘clf__max_depth’: 71, ‘clf__max_features’: ‘l… 1.0 0.742193
30 {‘clf__max_depth’: 71, ‘clf__max_features’: No… 1.0 0.733404
31 {‘clf__max_depth’: 71, ‘clf__max_features’: No… 1.0 0.733003
32 {‘clf__max_depth’: 71, ‘clf__max_features’: No… 1.0 0.736687
33 {‘clf__max_depth’: 71, ‘clf__max_features’: 1…. 1.0 0.733404
34 {‘clf__max_depth’: 71, ‘clf__max_features’: 1…. 1.0 0.733003
35 {‘clf__max_depth’: 71, ‘clf__max_features’: 1…. 1.0 0.736687
36 {‘clf__max_depth’: 68, ‘clf__max_features’: ‘s… 1.0 0.746659
37 {‘clf__max_depth’: 71, ‘clf__max_features’: ‘s… 1.0 0.746659
38 {‘clf__max_depth’: 71, ‘clf__max_features’: ‘l… 1.0 0.746659
39 {‘clf__max_depth’: 65, ‘clf__max_features’: ‘l… 1.0 0.746659
40 {‘clf__max_depth’: 65, ‘clf__max_features’: ‘s… 1.0 0.746659
41 {‘clf__max_depth’: 68, ‘clf__max_features’: ‘l… 1.0 0.746659
42 {‘clf__max_depth’: 68, ‘clf__max_features’: ‘s… 1.0 0.746778
43 {‘clf__max_depth’: 71, ‘clf__max_features’: ‘s… 1.0 0.746778
44 {‘clf__max_depth’: 65, ‘clf__max_features’: ‘l… 1.0 0.746778
45 {‘clf__max_depth’: 71, ‘clf__max_features’: ‘l… 1.0 0.746778
46 {‘clf__max_depth’: 65, ‘clf__max_features’: ‘s… 1.0 0.746778
47 {‘clf__max_depth’: 68, ‘clf__max_features’: ‘l… 1.0 0.746778
48 {‘clf__max_depth’: 65, ‘clf__max_features’: ‘l… 1.0 0.770919
49 {‘clf__max_depth’: 71, ‘clf__max_features’: ‘l… 1.0 0.770919
50 {‘clf__max_depth’: 65, ‘clf__max_features’: ‘s… 1.0 0.770919
51 {‘clf__max_depth’: 68, ‘clf__max_features’: ‘l… 1.0 0.770919
52 {‘clf__max_depth’: 65, ‘clf__max_features’: ‘s… 1.0 0.811057
53 {‘clf__max_depth’: 68, ‘clf__max_features’: ‘l… 1.0 0.811057
------------------------------
2023-11-11T16:18:38.258782 image/svg+xml Matplotlib v3.3.4, https://matplotlib.org/

Обучение модели CatBoostClassifier¶

In [64]:
# CatBoostClassifier (depth)

# Модель
model = CatBoostClassifier(
    #class_weights=class_weights, 
    logging_level='Silent', 
    random_state=STATE
) 

# Преобразование признаков
column_trans = ColumnTransformer([
    ('ohe', OrdinalEncoder(), features_categories), # кодирование категорий через OrdinalEncoder (для деревьев и бустингов)
    ('std', MaxAbsScaler(), features_numeric), # Стандартизация числовых данных от 0 до 1
], remainder='drop', verbose_feature_names_out=False)

# Pipeline
pipeline = Pipeline([
    ('transform', column_trans), # трансформация выборки
    ('clf', model) # модель классификации 
])

# Диапазон значений искомого параметра
# depth
n_depth = [1, 10, 15]
# n_estimators
n_n_estimators = [500, 1000, 1500] 
# learning_rate
n_learning_rate = [.001, .5, .999]

# Обход диапазона значений
while (
    (n_depth[1] - n_depth[0] > 1) 
    & (n_n_estimators[1] - n_n_estimators[0] > 10) 
    & (n_learning_rate[1] - n_learning_rate[0] > .01)
):
    
    # depth
    n_depth = two_range(n_depth[0], n_depth[-1], True)
    # n_estimators
    n_n_estimators = two_range(n_n_estimators[0], n_n_estimators[-1], True)
    # learning_rate
    n_learning_rate = two_range(n_learning_rate[0], n_learning_rate[-1], False)

    # Параметры модели
    params = [{
        'clf__depth': n_depth, 
        'clf__n_estimators': n_n_estimators, 
        'clf__learning_rate': n_learning_rate
    }]

    # Обучение модели
    data_grids, data_times = model_pipeline_gridsearch(
        features_train, 
        target_train, 
        pipeline, 
        params, 
        data_grids, 
        data_times 
    )

    # Вывод результата обучения модели
    print_model_result(data_grids, data_times, 'CatBoostClassifier', process_print=True)

    # Корректировка диапазона значений искомого параметра
    # Лучшее значение искомого параметра
    this_best_params = data_grids[-1].best_estimator_[-1].get_params()['depth']
    # depth
    if len(n_depth) > 1:
        if n_depth[0] == this_best_params:
            n_depth = [n_depth[0], int(n_depth[0] + (n_depth[1] - n_depth[0])/2), n_depth[1]]
        if n_depth[2] == this_best_params:
            n_depth = [n_depth[1], int(n_depth[1] + (n_depth[2] - n_depth[1])/2), n_depth[2]]
        else:
            n_depth = n_depth
    else:
        n_depth = n_depth
    # Лучшее значение искомого параметра
    this_best_params = data_grids[-1].best_estimator_[-1].get_params()['n_estimators']
    # n_estimators
    if len(n_n_estimators) > 1:
        if n_n_estimators[0] == this_best_params:
            n_n_estimators = [n_n_estimators[0], int(n_n_estimators[0] + (n_n_estimators[1] - n_n_estimators[0])/2), n_n_estimators[1]]
        if n_n_estimators[2] == this_best_params:
            n_n_estimators = [n_n_estimators[1], int(n_n_estimators[1] + (n_n_estimators[2] - n_n_estimators[1])/2), n_n_estimators[2]]
        else:
            n_n_estimators = n_n_estimators
    else:
        n_n_estimators = n_n_estimators
    # Лучшее значение искомого параметра
    this_best_params = data_grids[-1].best_estimator_[-1].get_params()['learning_rate']
    # learning_rate
    if len(n_learning_rate) > 1:
        if n_learning_rate[0] == this_best_params:
            n_learning_rate = [n_learning_rate[0], int(n_learning_rate[0] + (n_learning_rate[1] - n_learning_rate[0])/2), n_learning_rate[1]]
        if n_n_estimators[2] == this_best_params:
            n_learning_rate = [n_learning_rate[1], int(n_learning_rate[1] + (n_learning_rate[2] - n_learning_rate[1])/2), n_learning_rate[2]]
    

# График финальной версии модели этой ячейки
print_model_graphicresult(data_grids)
Модель         : CatBoostClassifier
Метрика ROC AUC: 0.9186625677422635
Время          : 413.962012052536 секунд
Параметры      : {'logging_level': 'Silent', 'random_state': 30112023, 'depth': 4, 'learning_rate': 0.2505, 'n_estimators': 1000}

Результаты выбора параметров модели:
params mean_train_score mean_test_score
0 {‘clf__depth’: 4, ‘clf__learning_rate’: 0.2505… 1.000000 0.740765
1 {‘clf__depth’: 4, ‘clf__learning_rate’: 0.2505… 1.000000 0.741636
2 {‘clf__depth’: 4, ‘clf__learning_rate’: 0.2505… 1.000000 0.741795
3 {‘clf__depth’: 4, ‘clf__learning_rate’: 0.5, ‘… 1.000000 0.711752
4 {‘clf__depth’: 4, ‘clf__learning_rate’: 0.5, ‘… 1.000000 0.710653
5 {‘clf__depth’: 4, ‘clf__learning_rate’: 0.5, ‘… 1.000000 0.711365
6 {‘clf__depth’: 4, ‘clf__learning_rate’: 0.7495… 1.000000 0.753797
7 {‘clf__depth’: 4, ‘clf__learning_rate’: 0.7495… 1.000000 0.753956
8 {‘clf__depth’: 4, ‘clf__learning_rate’: 0.7495… 1.000000 0.753956
9 {‘clf__depth’: 8, ‘clf__learning_rate’: 0.2505… 1.000000 0.722648
10 {‘clf__depth’: 8, ‘clf__learning_rate’: 0.2505… 1.000000 0.721382
11 {‘clf__depth’: 8, ‘clf__learning_rate’: 0.2505… 1.000000 0.721382
12 {‘clf__depth’: 8, ‘clf__learning_rate’: 0.5, ‘… 1.000000 0.754924
13 {‘clf__depth’: 8, ‘clf__learning_rate’: 0.5, ‘… 1.000000 0.754924
14 {‘clf__depth’: 8, ‘clf__learning_rate’: 0.5, ‘… 1.000000 0.753182
15 {‘clf__depth’: 8, ‘clf__learning_rate’: 0.7495… 1.000000 0.722444
16 {‘clf__depth’: 8, ‘clf__learning_rate’: 0.7495… 1.000000 0.723315
17 {‘clf__depth’: 8, ‘clf__learning_rate’: 0.7495… 1.000000 0.722444
18 {‘clf__depth’: 11, ‘clf__learning_rate’: 0.250… 1.000000 0.670383
19 {‘clf__depth’: 11, ‘clf__learning_rate’: 0.250… 1.000000 0.668795
20 {‘clf__depth’: 11, ‘clf__learning_rate’: 0.250… 1.000000 0.667375
21 {‘clf__depth’: 11, ‘clf__learning_rate’: 0.5, … 1.000000 0.719171
22 {‘clf__depth’: 11, ‘clf__learning_rate’: 0.5, … 1.000000 0.719883
23 {‘clf__depth’: 11, ‘clf__learning_rate’: 0.5, … 1.000000 0.719883
24 {‘clf__depth’: 11, ‘clf__learning_rate’: 0.749… 1.000000 0.703656
25 {‘clf__depth’: 11, ‘clf__learning_rate’: 0.749… 1.000000 0.701914
26 {‘clf__depth’: 11, ‘clf__learning_rate’: 0.749… 1.000000 0.703339
27 {‘clf__depth’: 4, ‘clf__learning_rate’: 0.2505… 1.000000 0.763349
28 {‘clf__depth’: 4, ‘clf__learning_rate’: 0.2505… 1.000000 0.760586
29 {‘clf__depth’: 4, ‘clf__learning_rate’: 0.2505… 1.000000 0.760135
30 {‘clf__depth’: 8, ‘clf__learning_rate’: 0.5, ‘… 1.000000 0.738038
31 {‘clf__depth’: 4, ‘clf__learning_rate’: 0.7495… 1.000000 0.740314
32 {‘clf__depth’: 4, ‘clf__learning_rate’: 0.7495… 1.000000 0.740359
33 {‘clf__depth’: 4, ‘clf__learning_rate’: 0.7495… 1.000000 0.739483
34 {‘clf__depth’: 8, ‘clf__learning_rate’: 0.5, ‘… 1.000000 0.738732
35 {‘clf__depth’: 8, ‘clf__learning_rate’: 0.5, ‘… 1.000000 0.738223
36 {‘clf__depth’: 4, ‘clf__learning_rate’: 0.2505… 1.000000 0.835423
37 {‘clf__depth’: 4, ‘clf__learning_rate’: 0.2505… 1.000000 0.837120
38 {‘clf__depth’: 4, ‘clf__learning_rate’: 0.2505… 1.000000 0.836155
39 {‘clf__depth’: 4, ‘clf__learning_rate’: 0.2505… 0.999982 0.918663
------------------------------
2023-11-11T16:25:32.372145 image/svg+xml Matplotlib v3.3.4, https://matplotlib.org/

Выбор лучшей модели¶

Выбор лучшей модели и проверка её качества на тестовой выборке.

In [65]:
# Лучшая модель из расчета ROC AUC
data_grids_best = data_grids[0]
data_times_best = data_times[0]
n = 0
for i in range(0, len(data_grids)):
    if data_grids[i].best_score_ > data_grids_best.best_score_: 
    #if (data_grids[i].best_score_ > data_grids_best.best_score_) & (data_times[i] < data_times_best): 
        data_grids_best = data_grids[i]
        data_times_best = data_times[i]

print('Лучшее время             :', data_times_best)
print('Лучший показатель ROC AUC:', data_grids_best.best_score_)
print('Лучшая модель            :') 
print(data_grids_best) 
print('Лучшие параметры модели  :') 
data_grids_best.get_params()
Лучшее время             : 413.962012052536
Лучший показатель ROC AUC: 0.9186625677422635
Лучшая модель            :
HalvingGridSearchCV(cv=4, error_score='raise',
                    estimator=Pipeline(steps=[('transform',
                                               ColumnTransformer(transformers=[('ohe',
                                                                                OrdinalEncoder(),
                                                                                ['senior_citizen',
                                                                                 'partner',
                                                                                 'dependents',
                                                                                 'type',
                                                                                 'paperless_billing',
                                                                                 'payment_method',
                                                                                 'online_backup',
                                                                                 'streaming_movies',
                                                                                 'multiple_lines']),
                                                                               ('std',
                                                                                MaxAbsScaler(),
                                                                                ['monthly_charges',
                                                                                 'duration_contract'])],
                                                                 verbose_feature_names_out=False)),
                                              ('clf',
                                               <catboost.core.CatBoostClassifier object at 0x7feed7f865b0>)]),
                    param_grid=[{'clf__depth': [4, 8, 11],
                                 'clf__learning_rate': [0.2505, 0.5, 0.7495],
                                 'clf__n_estimators': [750, 1000, 1250]}],
                    random_state=30112023, scoring='roc_auc')
Лучшие параметры модели  :
Out[65]:
{'aggressive_elimination': False,
 'cv': 4,
 'error_score': 'raise',
 'estimator__memory': None,
 'estimator__steps': [('transform',
   ColumnTransformer(transformers=[('ohe', OrdinalEncoder(),
                                    ['senior_citizen', 'partner', 'dependents',
                                     'type', 'paperless_billing', 'payment_method',
                                     'online_backup', 'streaming_movies',
                                     'multiple_lines']),
                                   ('std', MaxAbsScaler(),
                                    ['monthly_charges', 'duration_contract'])],
                     verbose_feature_names_out=False)),
  ('clf', <catboost.core.CatBoostClassifier at 0x7feed7f865b0>)],
 'estimator__verbose': False,
 'estimator__transform': ColumnTransformer(transformers=[('ohe', OrdinalEncoder(),
                                  ['senior_citizen', 'partner', 'dependents',
                                   'type', 'paperless_billing', 'payment_method',
                                   'online_backup', 'streaming_movies',
                                   'multiple_lines']),
                                 ('std', MaxAbsScaler(),
                                  ['monthly_charges', 'duration_contract'])],
                   verbose_feature_names_out=False),
 'estimator__clf': <catboost.core.CatBoostClassifier at 0x7feed7f865b0>,
 'estimator__transform__n_jobs': None,
 'estimator__transform__remainder': 'drop',
 'estimator__transform__sparse_threshold': 0.3,
 'estimator__transform__transformer_weights': None,
 'estimator__transform__transformers': [('ohe',
   OrdinalEncoder(),
   ['senior_citizen',
    'partner',
    'dependents',
    'type',
    'paperless_billing',
    'payment_method',
    'online_backup',
    'streaming_movies',
    'multiple_lines']),
  ('std', MaxAbsScaler(), ['monthly_charges', 'duration_contract'])],
 'estimator__transform__verbose': False,
 'estimator__transform__verbose_feature_names_out': False,
 'estimator__transform__ohe': OrdinalEncoder(),
 'estimator__transform__std': MaxAbsScaler(),
 'estimator__transform__ohe__categories': 'auto',
 'estimator__transform__ohe__dtype': numpy.float64,
 'estimator__transform__ohe__encoded_missing_value': nan,
 'estimator__transform__ohe__handle_unknown': 'error',
 'estimator__transform__ohe__max_categories': None,
 'estimator__transform__ohe__min_frequency': None,
 'estimator__transform__ohe__unknown_value': None,
 'estimator__transform__std__copy': True,
 'estimator__clf__logging_level': 'Silent',
 'estimator__clf__random_state': 30112023,
 'estimator': Pipeline(steps=[('transform',
                  ColumnTransformer(transformers=[('ohe', OrdinalEncoder(),
                                                   ['senior_citizen', 'partner',
                                                    'dependents', 'type',
                                                    'paperless_billing',
                                                    'payment_method',
                                                    'online_backup',
                                                    'streaming_movies',
                                                    'multiple_lines']),
                                                  ('std', MaxAbsScaler(),
                                                   ['monthly_charges',
                                                    'duration_contract'])],
                                    verbose_feature_names_out=False)),
                 ('clf',
                  <catboost.core.CatBoostClassifier object at 0x7feed7f865b0>)]),
 'factor': 3,
 'max_resources': 'auto',
 'min_resources': 'exhaust',
 'n_jobs': None,
 'param_grid': [{'clf__depth': [4, 8, 11],
   'clf__n_estimators': [750, 1000, 1250],
   'clf__learning_rate': [0.2505, 0.5, 0.7495]}],
 'random_state': 30112023,
 'refit': True,
 'resource': 'n_samples',
 'return_train_score': True,
 'scoring': 'roc_auc',
 'verbose': 0}

Выводы из выбора обученной модели

В резуьтате выбора модели на основе метрики ROC AUC лучшие результат у модели CatBoostClassifier с гиперпараметрами {'logging_level': 'Silent', 'random_state': 30112023, 'depth': 4, 'learning_rate': 0.2505, 'n_estimators': 1000} показатель ROC AUC равен 0.9186625677422635. Время обучения более 24 секунд.

Тестирование лучшей модели¶

In [66]:
start_time = time.time()

# Предсказание лучшей модели
predict = data_grids_best.predict(features_test)
predict_proba = data_grids_best.predict_proba(features_test)

finish_time = time.time()
funtion_time = finish_time - start_time

Анализ протестированной модели¶

In [67]:
# Расчет "ROC AUC" и времени выполнения предсказания
roc_auc = roc_auc_score(target_test, predict_proba[:, 1])
print('Показатель ROC AUC:', roc_auc)
print(f'Время предсказания: {funtion_time} секунд')
Показатель ROC AUC: 0.9261042456870182
Время предсказания: 0.026419639587402344 секунд

Цель данного проекта достигнута. В результате тестирования выбранной модели машинного обучения с подобранными гиперпараметрами получена метрика ROC AUC выше требуемого уровня данного параметра в 0.85 более чем на 0.05.

In [68]:
# ROC кривая
fpr, tpr, treshold = roc_curve(target_test, predict_proba[:, 1])
roc_auc = auc(fpr, tpr)

# Построение графика
plt.plot(
    fpr, 
    tpr, color='darkorange', 
    label='ROC кривая (площадь под кривой = %0.4f)' % roc_auc
)
plt.plot([0, 1], [0, 1], color='navy', linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC кривая')
plt.legend(loc="lower right")
plt.show()
2023-11-11T16:25:32.599888 image/svg+xml Matplotlib v3.3.4, https://matplotlib.org/

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

In [70]:
# Метрика "Accuracy" 
print('Показатель Accuracy:', accuracy_score(target_test, predict)) 
Показатель Accuracy: 0.9381033503691084

Почти в 94% случаев выбранная модель правильно предсказывает класс объекта. Это хороший результат, но здесь важно учесть дисбаланс классов, который может привести к лучшей предстказуемости большего класса. В случае данного проекта это класс 0 (договор не расторгнут). Точный расклад по количеству предсказанных объектов разного класса видно на матрице ошибок.

In [71]:
# Матрица ошибок
cm = confusion_matrix(target_test, predict)
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=["Negative", "Positive"])
disp.plot()
plt.show()
2023-11-11T16:26:58.844655 image/svg+xml Matplotlib v3.3.4, https://matplotlib.org/

Анализ матрицы ошибок показывает, что протестированная модель правильно предсказывает большую часть класса 0 (на графике горизонталь «Negative», договор не расторгнут). Ошибки в этом случае составляют около 2%. Значительно хуже обстоят дела с предсказанием класса 1 (на графике горизонталь «Positive», договор расторгнут). Здесь правильно предсказано только 2/3 всех значений. Вывод. Модель эффективно предскажет пользователя, который не собирается расторгать договор, но только в 2 из 3 случаях правильно предскажет будет ли пользователь расторгать договор.

In [72]:
# Анализ важности признаков
pd.DataFrame(
    data_grids_best.best_estimator_[-1].feature_importances_, 
    index=data_grids_best.best_estimator_[:-1].get_feature_names_out(),
    columns=['Важность признака']
).sort_values(by='Важность признака', ascending=False)
Out[72]:
Важность признака
duration_contract 53.036833
monthly_charges 19.611513
type 6.667458
payment_method 4.441345
multiple_lines 3.692476
partner 3.311501
online_backup 2.543628
dependents 1.987133
streaming_movies 1.930383
paperless_billing 1.561446
senior_citizen 1.216284

Анализ важности признаков выявил бесспорного лидера — duration_contract (продолжительность договора в днях). Следом за ним с большим отрывом располагается признак monthly_charges. Не смотря на разрыв с лидером по важности, этот признак тоже кратно превосходит третью позицию в этом рейтинге — type. Изменение этих признаков приведет к значительному изменению предсказаний. Замыкает этот рейтинг признак senior_citizen. Изменение этого признака в наименьшей степени повлияет на предсказание.

Общий вывод и рекомендации заказчику¶

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

Общий вывод

Цель данного проекта достигнута. Для предсказания оттока клиентов компании «ТелеДом» в продакшене рекоммендуется использовать модель машинного обучения CatBoostClassifier с гиперпараметрами {'logging_level': 'Silent', 'random_state': 30112023, 'depth': 4, 'learning_rate': 0.2505, 'n_estimators': 1000}. При тестировании данная модель имеет значение показателя ROC AUC равное 0.9261042456870182, что выше требуемого уровня в 0.85.

Рекоммендации заказчику

Модель CatBoostClassifier с подобранными гиперпараметрами дает значение показателя ROC AUC выше требуемого. При возникновении потребности доведения этого показателя выше полученного здесь значения, предлагается рассмотреть возможность в дальнешем использовать модели нейронных сетей. Например, Keras (TensorFlow), которая на тех же данных может показать еще более высокий результат.

Также, возможно, следует изменить подход к требованиям данного проекта. Так, после расчета «технической» метрики, коей является ROC AUC, можно расчитать бизнес-метрику. Например, финансовые потери от ухода клиента. Тогда, предсказанные только 2 из 3 ушедших клиентов могут оказаться существенной потерей и потребуется дообучить модель так, чтобы она чаще правильно предсказывала уход клиентов.

Прошу обратить внимание на исходные данные для данного проекта. Типы данных не оптимальны. Возможно, требуется оптимизация системы хранения данных. Так, например, параметр TotalCharges из файла contract_new.csv имеет тип object при том, что он содержит числовые значения с плавающей точкой. Еще более существенным является хранение категориальных бинарных признаков, таких как PaperlessBilling из файла contract_new.csv, с типом object содержащих текстовые значения. Изменение типа подобных переменных на bool или даже int8 с бинарными значениями сделает таблицы легче без потери информативности. Также, прошу обратить внимание на пропуски в данных о клиентах. Это не очевидно при использовании разрозненных таблиц, но при их объединении проявляются объекты, содержащие пропуски по некоторым параметрам. Т.е., отсутствует информация по некоторым продуктам, которые используют клиенты.

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

  1. Целевой признак active_contract. Он указывает на наличие или отсутствие действующего договора с клиентом. Возможно, в следствие его значимости, требуется хранить в используемой системе хранения в бинарном виде.
  2. Категориальные признаки: multiple_lines, paperless_billing, payment_method, tech_support, internet_service.
  3. Числовые признаки: total_charges, duration_contract.

Выводы о проделанной работе

В проекте использованы наиболее распространенные типы моделей машинного обучения LogisticRegression, RandomForestRegressor и CatBoostClassifier. Однако, минимальный порог по метрике ROC AUC преодолела только модель CatBoostClassifier. В перспективе ее промышленной эксплуатации на схожих данных ей будет помогать задействованные в проекте кросс-валидация и оптимизация показателей с использованием Pipeline и HalvingGridSearchCV.

Для достижения поставленной цели проекта было выполнено:

  1. Подготовлена тетрадь Jupyter Notebook. Обновлены существующие и установлены новые библиотеки. В тетрадь загружены требуемые в проекте библиотеки. Оптимизировано отображение контента в тетради. Добавлены глобальные переменные.
  2. Загружены и проверены данные из файлов.
  3. Проведены исследовательский анализ и предобработка данных исходных датафреймов contract_new (информация о договоре), personal_new (персональные данные клиента), internet_new (информация об интернет-услугах) и phone_new (информация об услугах телефонии).
  4. Данные исходных датафреймов объеденены в единый датафрейм. Из него предварительно выделены тренировочные выборки для анализа.
  5. Проведены исследовательский анализ и предобработка данных предварительно выделенной треннировочной выборки из объединённого датафрейма. Были проведены анализ мультиколлинеарности и парной корреляции. Отобраны признаки для использования в обучении моделей.
  6. Подготовлены данные к использованию в обучении моделей.
  7. Обучены модели машинного обучения LogisticRegression, RandomForestRegressor и CatBoostClassifier.
  8. На основе метрики ROC AUC выбрана и протестирована лучшая модель.
  9. Описан общий вывод проекта и даны рекомендации заказчику.

Политика конфиденциальности

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




Администрация и владельцы данного информационного ресурса не несут ответственности за возможные последствия, связанные с использованием информации, размещенной на нем.


Все права защищены. При копировании материалов сайта обязательно указывать ссылку на © Microsegment.ru (2020-2025)