Отток — это когда клиенты перестают пользоваться услугами компании. Таким образом, прогнозирование оттока выявляет клиентов, которые могут расторгнуть свои контракты. Если компания может предсказать это, она может предложить скидки на эти услуги, чтобы сохранить клиентов. Чтобы предсказать отток, я буду использовать машинное обучение. Для создания модели машинного обучения я буду использовать данные о клиентах, которые ушли из Kaggle. На основе этого я создам модель для выявления существующих клиентов, которые вот-вот уйдут.

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

Импорт данных

#DATA PROFILING
#Importing Data
import pandas as pd
pd.set_option('display.max_columns',None)
df = pd.read_csv('Telco Customer Churn.csv')

Первый шаг — инициализировать библиотеку, импортировать набор данных в Python с помощью Pandas, и назначить его как df. Данные, загруженные из Kaggle, будут сохранены как Telco Customer Churn.csv.

Отображение длины данных

#Showing The Length of The Data
print("\nThe Length of The Data: ", len(df))

Второй шаг — показать, сколько данных содержится в наборе данных, используя len(). В результате размер этих данных составляет 7043.

Отображение формы данных

#Showing The Shape of The Data
print("\nThe Shape of The Data: ", df.shape)

Третий шаг — отобразить форму данных с помощью .shape. В результате эти данные содержат 7043 строки и 21 столбец.

Отображение информации о данных

#Showing The Information of The Data
print("\nThe Information of The Data: ")
print(df.info())

Четвертый шаг — получение информации из данных с помощью функции .info().

Отображение статистических расчетов

#Showing The Statistical Calculations
print("\nThe Statistical Calculations: ")
print(df.describe().T)

Пятый шаг — отобразить статистический анализ данных с помощью .describe().

Отображение уникальных данных

#Showing The Unique Data
print("\nThe Unique Data: ")
print(df.nunique())

Шестой шаг профилирования данных — отображение уникальных данных из каждого столбца с помощью функции .nunique().

Изменение имени столбца

#Changing The Column's Name
df.rename(columns={'customerID': 'CustomerID',
                   'gender': 'Gender',
                   'tenure': 'Tenure'}, inplace=True)

В поисках корреляции

#Looking For A Correlation
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
plt.figure(figsize=(17, 15))
corr_mask = np.triu(df.corr())
h_map = sns.heatmap(df.corr(), mask=corr_mask, annot=True, cmap='Blues')
plt.yticks(rotation=360)
plt.show()

Последним этапом профилирования данных является поиск корреляции каждых данных путем их визуализации. В этой части я буду использовать Seaborn, Matplotlib и Numpy.

Очистка данных

Поиск пропущенного значения в каждом столбце

#DATA CLEANSING
#Looking For The Missing Value in Each Column
print("\nLooking For The Missing Value in Each Column: ")
print(df.isnull().sum())

Я использую isnull() и sum(), чтобы узнать, сколько пропущенного значения. Оказывается, в этом наборе данных нет пропущенного значения.

Удаление ненужного столбца

#Removing Unnecessary Column
df = df.drop(['CustomerID'], axis=1)
print("\nThe Shape of The Data: ", df.shape)

Проверка столбца «Пол»

#Checking The Gender Column
print("\nChecking The Gender Column")
sns.countplot(x=df['Gender'], palette='flare')
plt.show()

В этом столбце указано, является ли клиент мужчиной или женщиной.

Проверка столбца SeniorCetizen

#Checking The SeniorCitizen Column
print("\nChecking The SeniorCitizen Column")
sns.countplot(x=df['SeniorCitizen'], palette='flare')
plt.show()

В этом столбце указано, является ли клиент пожилым гражданином или нет (1 = Да, 0 = Нет).

mask_sc = {
    0   :'No',
    1   :'Yes'}
df['SeniorCitizen'] = df['SeniorCitizen'].map(mask_sc)
sns.countplot(x=df['SeniorCitizen'], palette='flare')
plt.show()

Проверка столбца «Партнер»

#Checking The Partner Column
print("\nChecking The Partner Column")
sns.countplot(x=df['Partner'], palette='flare')
plt.show()

В этом столбце указано, есть ли у клиента партнер или нет (Да, Нет).

Проверка столбца зависимых

#Checking The Dependents Column
print("\nChecking The Dependents Column")
sns.countplot(x=df['Dependents'], palette='flare')
plt.show()

В этом столбце указано, есть ли у клиента иждивенцы или нет (Да, Нет).

Проверка столбца срока владения

#Checking The Tenure Column
print("\nChecking The Tenure Column")
sns.countplot(x=df['Tenure'], palette='flare')
plt.show()

В этом столбце указано количество месяцев, в течение которых клиент оставался в компании.

Проверка столбца PhoneService

#Checking The PhoneService Column
print("\nChecking The PhoneService Column")
sns.countplot(x=df['PhoneService'], palette='flare')
plt.show()

В этом столбце указано, есть ли у клиента телефонная связь или нет (Да, Нет).

Проверка столбца MultipleLines

#Checking The MultipleLines Column
print("\nChecking The MultipleLines Column")
sns.countplot(x=df['MultipleLines'], palette='flare')
plt.show()

В этом столбце указано, есть ли у клиента несколько линий или нет (Да, Нет, Нет телефонной службы).

Проверка столбца InternetService

#Checking The InternetServiceColumn
print("\nChecking The InternetServiceColumn")
sns.countplot(x=df['InternetService'], palette='flare')
plt.show()

В этом столбце указан интернет-провайдер клиента (DSL, оптоволокно, нет).

Проверка столбца OnlineSecurity

#Checking The OnlineSecurity Column
print("\nChecking The OnlineSecurity Column")
sns.countplot(x=df['OnlineSecurity'], palette='flare')
plt.show()

В этом столбце указано, есть ли у клиента онлайн-безопасность или нет (Да, Нет, Нет интернет-сервиса).

Проверка столбца OnlineBackup

#Checking The OnlineBackup Column
print("\nChecking The OnlineBackup Column")
sns.countplot(x=df['OnlineBackup'], palette='flare')
plt.show()

В этом столбце указано, есть ли у клиента онлайн-резервное копирование или нет (Да, Нет, Нет интернет-сервиса).

Проверка столбца DeviceProtection

#Checking The DeviceProtection Column
print("\nChecking The DeviceProtection Column")
sns.countplot(x=df['DeviceProtection'], palette='flare')
plt.show()

В этом столбце указано, есть ли у клиента защита устройства или нет (Да, Нет, Нет интернет-сервиса).

Проверка колонки техподдержки

#Checking The TechSupport Column
print("\nChecking The TechSupport Column")
sns.countplot(x=df['TechSupport'], palette='flare')
plt.show()

В этом столбце указано, есть ли у клиента техподдержка или нет (Да, Нет, Нет интернет-сервиса).

Проверка столбца StreamingTV

#Checking The StreamingTV Column
print("\nChecking The StreamingTV Column")
sns.countplot(x=df['StreamingTV'], palette='flare')
plt.show()

В этом столбце указано, есть ли у клиента потоковое телевидение или нет (Да, Нет, Нет интернет-сервиса).

Проверка столбца StreamingMovies

#Checking The StreamingMovies Column
print("\nChecking The StreamingMovies Column")
sns.countplot(x=df['StreamingMovies'], palette='flare')
plt.show()

В этом столбце указано, есть ли у клиента потоковое воспроизведение фильмов или нет (Да, Нет, Нет интернет-сервиса).

Проверка столбца контракта

#Checking The Contract Column
print("\nChecking The Contract Column")
sns.countplot(x=df['Contract'], palette='flare')
plt.show()

Этот столбец содержит срок контракта клиента (Месяц за месяцем, Один год, Два года).

Проверка столбца PaperlessBilling

#Checking The PaperlessBilling Column
print("\nChecking The PaperlessBilling Column")
sns.countplot(x=df['PaperlessBilling'], palette='flare')
plt.show()

В этом столбце указано, есть ли у клиента безбумажный биллинг или нет (Да, Нет).

Проверка столбца PaymentMethod

#Checking The PaymentMethod Column
print("\nChecking The PaymentMethod Column")
sns.countplot(x=df['PaymentMethod'], palette='flare')
plt.show()

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

Проверка столбца TotalCharges

#Checking The TotalCharges Column
print("\nChecking The TotalCharges Column")
df["TotalCharges"] = pd.to_numeric(df["TotalCharges"],errors='coerce')

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

print("\nLooking For The Missing Value in Each Column After Changing The TotalCharges Column: ")
print(df.isnull().sum())

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

df = df.loc[(df['TotalCharges'].notnull())]
print("\nLooking For The Missing Value in Each Column After Removing Missing Values: ")
print(df.isnull().sum())

Оказывается, в столбце TotalCharges отсутствует 11 значений, как показано на Рис. 23. Я удалю это отсутствующее значение из набора данных на следующем шаге.

Проверка столбца оттока

#Checking The Churn Column
print("\nChecking Label Distribution Using .groupby() And .size() Functions")
mask_churn = {
    'No'    : 0,
    'Yes'   : 1}
df['Churn'] = df['Churn'].map(mask_churn)
print(df.groupby('Churn').size())
sns.countplot(x=df['Churn'], palette='flare')
plt.show()

В этом столбце указано, ушел ли клиент или нет (да или нет). Количество клиентов, которые ушли (1), – 1 869, а количество клиентов, которые не ушли (0), – 5 163.

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

Удаление повторяющихся данных

#Removing Duplicated Data
print("\nRemoving Duplicated Data")
df.drop_duplicates(inplace=True)
print('The Shape of The Data After Removing The Duplicated Data: ', df.shape)

В этом разделе я собираюсь удалить повторяющиеся данные. После удаления повторяющихся данных эти данные имеют 7010 строк и 20 столбцов.

Метка Conversin в наборе данных

#Label Conversion on The Dataset
print("\nLabel Conversion on The Dataset")
Gender = pd.get_dummies(df['Gender'], drop_first=True)
SeniorCitizen = pd.get_dummies(df['SeniorCitizen'], drop_first=True)
Partner = pd.get_dummies(df['Partner'], drop_first=True)
Dependents = pd.get_dummies(df['Dependents'], drop_first=True)
PhoneService = pd.get_dummies(df['PhoneService'], drop_first=True)
MultipleLines = pd.get_dummies(df['MultipleLines'], drop_first=True)
InternetService = pd.get_dummies(df['InternetService'], drop_first=True)
OnlineSecurity = pd.get_dummies(df['OnlineSecurity'], drop_first=True)
OnlineBackup = pd.get_dummies(df['OnlineBackup'], drop_first=True)
DeviceProtection = pd.get_dummies(df['DeviceProtection'], drop_first=True)
TechSupport = pd.get_dummies(df['TechSupport'], drop_first=True)
StreamingTV = pd.get_dummies(df['StreamingTV'], drop_first=True)
StreamingMovies = pd.get_dummies(df['StreamingMovies'], drop_first=True)
Contract = pd.get_dummies(df['Contract'], drop_first=True)
PaperlessBilling = pd.get_dummies(df['PaperlessBilling'], drop_first=True)
PaymentMethod = pd.get_dummies(df['PaymentMethod'], drop_first=True)

df = df.drop(['Gender',
              'SeniorCitizen',
              'Partner',
              'Dependents',
              'PhoneService',
              'MultipleLines',
              'InternetService',
              'OnlineSecurity',
              'OnlineBackup',
              'DeviceProtection',
              'TechSupport',
              'StreamingTV',
              'StreamingMovies',
              'Contract',
              'PaperlessBilling',
              'PaymentMethod'], axis=1)

df = pd.concat([Gender,
                SeniorCitizen,
                Partner,
                Dependents,
                PhoneService,
                MultipleLines,
                InternetService,
                OnlineSecurity,
                OnlineBackup,
                DeviceProtection,
                TechSupport,
                StreamingTV,
                StreamingMovies,
                Contract,
                PaperlessBilling,
                PaymentMethod,
                df], axis=1)

Разделение функций и меток

#Separating Features and Labels
X = df.drop('Churn', axis=1)
y = df['Churn']
print("\nX Dataset: ", X.shape)
print("y Dataset: ", y.shape)

В этой части я создам две переменные: X только с функциями (7010 строк и 30 столбцов) и y только с меткой (7010 строк и 1 столбец).

Обработка несбалансированного набора данных

#Handling The Imbalanced Dataset
#Over-Sampling
from imblearn.over_sampling import RandomOverSampler
upsampling = RandomOverSampler(random_state=42)
X_sampling, y_sampling = upsampling.fit_resample(X, y)
print("\nX Dataset After Resampling: ", X_sampling.shape)
print("y Dataset After Resampling: ", y_sampling.shape)

После обработки дисбалансов этот набор данных содержит 10 306 строк и 30 столбцов. Ранее в этом наборе данных было 7010 строк и 30 столбцов. Увеличение числа связано с тем, что я делал избыточную выборку.

Подготовка обучения, тестирования и проверки набора данных

#Preparing Training, Testing, And Validating Dataset
from sklearn.model_selection import train_test_split
X_train_full, X_test, y_train_full, y_test = train_test_split(X_sampling, y_sampling, test_size=0.2, random_state=42)
X_train, X_val, y_train, y_val = train_test_split(X_train_full, y_train_full, test_size=0.2, random_state=42)

Создайте модель машинного обучения

Следующим шагом является построение модели машинного обучения. Я буду сравнивать между LogisticRegression, DecisionTreeClassifier, RandomForestClassifier и KNeighborsClassifier.

Логистическая регрессия

#Build a Machine Learning Model Logistic Regression
print("\nBuild a Machine Learning Model Logistic Regression")
from sklearn.model_selection import GridSearchCV
from sklearn.linear_model import LogisticRegression
parameter = {
    'solver'    : ['newton-cg', 'lbfgs', 'liblinear', 'sag', 'saga']
}
clf = GridSearchCV(LogisticRegression(random_state=42),
                   param_grid=parameter,
                   cv=5,
                   return_train_score=False)
clf.fit(X_train, y_train)
print(clf.best_estimator_)
model_logreg = clf.best_estimator_
model_logreg = model_logreg.fit(X_train, y_train)
y_pred_logreg = model_logreg.predict(X_test)

#Evaluating Model Performance
from sklearn.metrics import confusion_matrix
from sklearn.metrics import precision_score, recall_score, f1_score, accuracy_score
cm = confusion_matrix(y_test, y_pred_logreg)
fig, ax = plt.subplots(figsize=(5, 5))
ax.matshow(cm, cmap=plt.cm.Oranges, alpha=0.3)
for i in range(cm.shape[0]):
    for j in range(cm.shape[1]):
        ax.text(x=j, y=i, s=cm[i, j], va='center', ha='center', size='xx-large')
plt.xlabel('Predictions', fontsize=18)
plt.ylabel('Actuals', fontsize=18)
plt.title('Confusion Matrix Logistic Regression', fontsize=18)
plt.show()
print('Precision: %.3f' % precision_score(y_test, y_pred_logreg))
print('Recall: %.3f' % recall_score(y_test, y_pred_logreg))
print('Accuracy: %.3f' % accuracy_score(y_test, y_pred_logreg))
print('F1 Score: %.3f' % f1_score(y_test, y_pred_logreg))

Классификатор дерева решений

#Build a Machine Learning Model Decision Tree Classifier
print("\nBuild a Machine Learning Model Decision Tree Classifier")
from sklearn.model_selection import GridSearchCV
from sklearn.tree import DecisionTreeClassifier
parameter = {
    'criterion'    : ['gini', 'entropy']
}
clf = GridSearchCV(DecisionTreeClassifier(random_state=42),
                   param_grid=parameter,
                   cv=5,
                   return_train_score=False)
clf.fit(X_train, y_train)
print(clf.best_estimator_)
model_dtc = clf.best_estimator_
model_dtc = model_dtc.fit(X_train, y_train)
y_pred_dtc = model_dtc.predict(X_test)

#Evaluating Model Performance
from sklearn.metrics import confusion_matrix
from sklearn.metrics import precision_score, recall_score, f1_score, accuracy_score
cm = confusion_matrix(y_test, y_pred_dtc)
fig, ax = plt.subplots(figsize=(5, 5))
ax.matshow(cm, cmap=plt.cm.Oranges, alpha=0.3)
for i in range(cm.shape[0]):
    for j in range(cm.shape[1]):
        ax.text(x=j, y=i, s=cm[i, j], va='center', ha='center', size='xx-large')
plt.xlabel('Predictions', fontsize=18)
plt.ylabel('Actuals', fontsize=18)
plt.title('Confusion Matrix Decision Tree Classifier', fontsize=18)
plt.show()
print('Precision: %.3f' % precision_score(y_test, y_pred_dtc))
print('Recall: %.3f' % recall_score(y_test, y_pred_dtc))
print('Accuracy: %.3f' % accuracy_score(y_test, y_pred_dtc))
print('F1 Score: %.3f' % f1_score(y_test, y_pred_dtc))

Случайный лесной классификатор

#Build a Machine Learning Model Random Forest Classifier
print("\nBuild a Machine Learning Model Random Forest Classifier")
from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import RandomForestClassifier
parameter = {
    'criterion'    : ['gini', 'entropy']
}
clf = GridSearchCV(RandomForestClassifier(random_state=42),
                   param_grid=parameter,
                   cv=5,
                   return_train_score=False)
clf.fit(X_train, y_train)
print(clf.best_estimator_)
model_rfc = clf.best_estimator_
model_rfc = model_rfc.fit(X_train, y_train)
y_pred_rfc = model_rfc.predict(X_test)

#Evaluating Model Performance
from sklearn.metrics import confusion_matrix
from sklearn.metrics import precision_score, recall_score, f1_score, accuracy_score
cm = confusion_matrix(y_test, y_pred_rfc)
fig, ax = plt.subplots(figsize=(5, 5))
ax.matshow(cm, cmap=plt.cm.Oranges, alpha=0.3)
for i in range(cm.shape[0]):
    for j in range(cm.shape[1]):
        ax.text(x=j, y=i, s=cm[i, j], va='center', ha='center', size='xx-large')
plt.xlabel('Predictions', fontsize=18)
plt.ylabel('Actuals', fontsize=18)
plt.title('Confusion Matrix Random Forest Classifier', fontsize=18)
plt.show()
print('Precision: %.3f' % precision_score(y_test, y_pred_rfc))
print('Recall: %.3f' % recall_score(y_test, y_pred_rfc))
print('Accuracy: %.3f' % accuracy_score(y_test, y_pred_rfc))
print('F1 Score: %.3f' % f1_score(y_test, y_pred_rfc))

Классификатор K-соседей

#Build a Machine Learning Model K-Neighbors Classifier
print("\nBuild a Machine Learning Model K-Neighbors Classifier")
from sklearn.model_selection import GridSearchCV
from sklearn.neighbors import KNeighborsClassifier
parameter = {
    'algorithm'    : ['ball_tree', 'kd_tree', 'brute']
}
clf = GridSearchCV(KNeighborsClassifier(),
                   param_grid=parameter,
                   cv=5,
                   return_train_score=False)
clf.fit(X_train, y_train)
print(clf.best_estimator_)
model_knc = clf.best_estimator_
model_knc = model_knc.fit(X_train, y_train)
y_pred_knc = model_knc.predict(X_test)

#Evaluating Model Performance
from sklearn.metrics import confusion_matrix
from sklearn.metrics import precision_score, recall_score, f1_score, accuracy_score
cm = confusion_matrix(y_test, y_pred_knc)
fig, ax = plt.subplots(figsize=(5, 5))
ax.matshow(cm, cmap=plt.cm.Oranges, alpha=0.3)
for i in range(cm.shape[0]):
    for j in range(cm.shape[1]):
        ax.text(x=j, y=i, s=cm[i, j], va='center', ha='center', size='xx-large')
plt.xlabel('Predictions', fontsize=18)
plt.ylabel('Actuals', fontsize=18)
plt.title('Confusion Matrix K-Neighbors Classifier', fontsize=18)
plt.show()
print('Precision: %.3f' % precision_score(y_test, y_pred_knc))
print('Recall: %.3f' % recall_score(y_test, y_pred_knc))
print('Accuracy: %.3f' % accuracy_score(y_test, y_pred_knc))
print('F1 Score: %.3f' % f1_score(y_test, y_pred_knc))

Оценка модели машинного обучения

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

Логистическая регрессия

Результатом использования логистической регрессии является оценка отзыва — 0,793, а оценка F1 — 0,769.

Классификатор дерева решений

Результатом использования классификатора дерева решений является показатель отзыва, равный 0,913, и показатель F1, равный 0,864.

Случайный лесной классификатор

Результат использования классификатора случайного леса: показатель отзыва равен 0,917, а показатель F1 – 0,875.

Классификатор K-соседей

Результат использования классификатора K-соседей: показатель отзыва равен 0,792, а показатель F1 – 0,757.

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

Проверка модели машинного обучения

#Validating The Machine Learning Model
for i in range(10):
    real = y_val.iloc[i]
    pred = model_rfc.predict(X_val.iloc[i].to_frame().T)[0]
    print(f'Real Value      ----->>>>> {real}\n'
          f'Predicted Value ----->>>>> {pred}')
    print()
pred = model_rfc.predict(X_val)
print('Precision: %.3f' % precision_score(y_val, pred))
print('Recall: %.3f' % recall_score(y_val, pred))
print('Accuracy: %.3f' % accuracy_score(y_val, pred))
print('F1 Score: %.3f' % f1_score(y_val, pred))

Заключение

Модель машинного обучения с использованием классификатора случайного леса дает лучший результат Оценка отзыва при тестировании = 0,917, Оценка при проверке F1 = 0,875, Оценка при проверке достоверности = 0,918 и Оценка при проверке F1 = 0,867

Это от меня; Вы можете связаться со мной, если у вас есть критика, совет или вопрос об этом проекте. Спасибо, я очень ценю, что вы нашли время, чтобы прочитать это.