2024-12-07 09:11:03 +04:00

563 KiB
Raw Blame History

Лабораторная работа 3

1. Определить две бизнес-цели

Во-первых, у нас есть в датасете есть столбец NumWebPurchases — количество покупок через интернет.
Ставим первую бизнес цель: Увеличение продаж через интернет-магазин. А еще у нас имеется столбец Response — отклик на текущую кампанию. Ставим вторую бизнес цель: Анализ отклика на предыдущие кампании для повышения их эффективности.

2. Определить цели технического проекта

Для увеличения интернет-продаж:

Разработать модели сегментации клиентов на основе их характеристик (доход, покупки). Создать прогнозные модели для определения вероятности веб-покупок.

Для оптимизации кампаний:

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

4. Выполнить разбиение каждого набора данных

In [59]:
from sklearn.model_selection import train_test_split
import pandas as pd
dataset = pd.read_csv(".//datasetlab1//marketing_campaign.csv", sep="\t")

# Удаление неинформативных столбцов и выбор целевых данных для бизнес-целей
columns_to_use = [
    "Income", "Kidhome", "Teenhome", "NumWebPurchases", "MntWines", 
    "MntFruits", "MntMeatProducts", "MntFishProducts", "MntSweetProducts",
    "MntGoldProds", "AcceptedCmp1", "AcceptedCmp2", "AcceptedCmp3", 
    "AcceptedCmp4", "AcceptedCmp5", "Response", "Recency"
]

# Очистка данных от пропусков и выбор только необходимых столбцов
filtered_data = dataset[columns_to_use].dropna()

# Разделение данных на признаки (X) и целевую переменную (y) для оптимизации кампаний
X = filtered_data.drop(columns=["Response"])
y = filtered_data["Response"]

# Разбиение на обучающую (60%), контрольную (20%) и тестовую (20%) выборки
X_train, X_temp, y_train, y_temp = train_test_split(X, y, test_size=0.4, random_state=42, stratify=y)
X_val, X_test, y_val, y_test = train_test_split(X_temp, y_temp, test_size=0.5, random_state=42, stratify=y_temp)

# Проверка размера выборок
X_train.shape, X_val.shape, X_test.shape
Out[59]:
((1329, 16), (443, 16), (444, 16))

5. Оценить сбалансированность выборок

За 0 берем не отклик, за 1 - отклик клиента на рекламу.

In [60]:
# Импорт необходимых библиотек
import seaborn as sns
import matplotlib.pyplot as plt

# Функция для визуализации распределения классов
def plot_class_distribution(y, title):
    sns.countplot(x=y_train, color='orange')
    plt.title(title)
    plt.xlabel("Response (Целевой признак)")
    plt.ylabel("Количество записей")
    plt.show()

# Оценка сбалансированности классов в выборках
plot_class_distribution(y_train, "Распределение классов в обучающей выборке")
plot_class_distribution(y_val, "Распределение классов в контрольной выборке")
plot_class_distribution(y_test, "Распределение классов в тестовой выборке")

# Проверка пропорций классов в обучающей выборке
class_distribution_train = y_train.value_counts(normalize=True) * 100
print("Распределение классов в обучающей выборке (в процентах):")
print(class_distribution_train)
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
Распределение классов в обучающей выборке (в процентах):
Response
0    84.951091
1    15.048909
Name: proportion, dtype: float64

Для второй бизнес цели

In [61]:
from sklearn.model_selection import train_test_split

# Целевой признак для второй бизнес-цели
target_col_2 = 'NumWebPurchases'

# Разделение данных на обучающую, контрольную и тестовую выборки
X_train_2, X_temp_2, y_train_2, y_temp_2 = train_test_split(
    X.drop(columns=[target_col_2]),  # Все признаки, кроме целевого
    X[target_col_2],                # Целевой признак
    test_size=0.4,                  # 40% на контрольную и тестовую выборки
    random_state=42
)

X_val_2, X_test_2, y_val_2, y_test_2 = train_test_split(
    X_temp_2,
    y_temp_2,
    test_size=0.5,  # 50% от оставшихся данных для тестовой выборки
    random_state=42
)

# Проверим размеры выборок
print("Размеры выборок для второй цели:")
print(f"Обучающая: {X_train_2.shape}, Контрольная: {X_val_2.shape}, Тестовая: {X_test_2.shape}")
Размеры выборок для второй цели:
Обучающая: (1329, 15), Контрольная: (443, 15), Тестовая: (444, 15)

Оценка:

In [62]:
# Функция для визуализации распределения классов
def plot_class_distribution(y, title):
    sns.countplot(x=y, color='orange')
    plt.title(title)
    plt.xlabel("NumWebPurchases (Целевой признак)")
    plt.ylabel("Количество записей")
    plt.show()

# Оценка сбалансированности классов в выборках
plot_class_distribution(y_train_2, "Распределение классов в обучающей выборке")
plot_class_distribution(y_val_2, "Распределение классов в контрольной выборке")
plot_class_distribution(y_test_2, "Распределение классов в тестовой выборке")

# Проверка пропорций классов в обучающей выборке
class_distribution_train_2 = y_train_2.value_counts(normalize=True) * 100
print("Распределение классов в обучающей выборке (в процентах):")
print(class_distribution_train_2)
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
Распределение классов в обучающей выборке (в процентах):
NumWebPurchases
1     15.876599
2     15.575621
3     14.672686
4     13.167795
5     10.233258
6      8.577878
7      6.471031
8      4.966140
9      3.837472
11     2.558315
0      2.257336
10     1.655380
23     0.075245
27     0.075245
Name: proportion, dtype: float64

6.

Наблюдаем несбалансированность, для Второй бизнес цели выполним Upsampling (увеличение выборки для редких классов). Делаем покупки больше.

In [63]:
from imblearn.over_sampling import SMOTE

# Применение SMOTE к обучающей выборке
smote = SMOTE(random_state=42)
X_train_balanced, y_train_balanced = smote.fit_resample(X_train, y_train)

# Проверим распределение после аугментации
plot_class_distribution(y_train_balanced, "Распределение классов после SMOTE (обучающая выборка)")

# Проверим процентное распределение
balanced_distribution = y_train_balanced.value_counts(normalize=True) * 100
print("Распределение классов после SMOTE (в процентах):")
print(balanced_distribution)
No description has been provided for this image
Распределение классов после SMOTE (в процентах):
Response
0    50.0
1    50.0
Name: proportion, dtype: float64

Для первой бизнес цели:

In [64]:
from imblearn.over_sampling import RandomOverSampler
import pandas as pd

# Применение RandomOverSampler для балансировки классов
ros = RandomOverSampler(random_state=42)
X_train_res, y_train_res = ros.fit_resample(X_train_2, y_train_2)

# Выводим новые размеры выборки
print(f"Размерность выборки после RandomOverSampler:")
print(f"X_train_res: {X_train_res.shape}")
print(f"y_train_res: {y_train_res.shape}")

# Распределение классов в обучающей выборке после балансировки
class_distribution_res = pd.Series(y_train_res).value_counts(normalize=True) * 100
print("\nРаспределение классов после балансировки (в процентах):")
print(class_distribution_res)

# Для визуализации можно использовать график
import seaborn as sns
import matplotlib.pyplot as plt

# Функция для визуализации распределения классов
def plot_class_distribution(y, title):
    sns.countplot(x=y, color="orange")
    plt.title(title)
    plt.xlabel("Response (Целевой признак)")
    plt.ylabel("Количество записей")
    plt.show()

# Построение графика распределения классов
plot_class_distribution(y_train_res, "Распределение классов после балансировки")
Размерность выборки после RandomOverSampler:
X_train_res: (2954, 15)
y_train_res: (2954,)

Распределение классов после балансировки (в процентах):
NumWebPurchases
2     7.142857
5     7.142857
1     7.142857
8     7.142857
9     7.142857
3     7.142857
11    7.142857
7     7.142857
6     7.142857
4     7.142857
0     7.142857
10    7.142857
23    7.142857
27    7.142857
Name: proportion, dtype: float64
No description has been provided for this image

7-8. Делаем конструирование признаков

In [65]:
import pandas as pd
from sklearn.preprocessing import KBinsDiscretizer, MinMaxScaler, StandardScaler
import featuretools as ft
from imblearn.over_sampling import RandomOverSampler


# 1. One-hot encoding для категориальных признаков
X_train_2 = pd.get_dummies(X_train_2, drop_first=True)
X_test_2 = pd.get_dummies(X_test_2, drop_first=True)

# 2. Дискретизация числовых признаков
discretizer = KBinsDiscretizer(n_bins=5, encode='ordinal', strategy='uniform')
X_train_2['Income_binned'] = discretizer.fit_transform(X_train_2[['Income']])
X_test_2['Income_binned'] = discretizer.transform(X_test_2[['Income']])

# 3. Масштабирование признаков
scaler_minmax = MinMaxScaler()
X_train_2[['Income']] = scaler_minmax.fit_transform(X_train_2[['Income']])
X_test_2[['Income']] = scaler_minmax.transform(X_test_2[['Income']])

# Стандартизация признаков
scaler_standard = StandardScaler()
X_train_2[['Income']] = scaler_standard.fit_transform(X_train_2[['Income']])
X_test_2[['Income']] = scaler_standard.transform(X_test_2[['Income']])

# 4. Применение Featuretools для создания признаков
es = ft.EntitySet(id="data")

# Мы добавляем данные в EntitySet с помощью метода add_dataframe
es = es.add_dataframe(
    dataframe_name="customer_data",
    dataframe=X_train_2,
    index="customer_id" 
)

# Применяем Featuretools для создания признаков
# Изменения: теперь указываем `target_dataframe_name` вместо `target_entity`
features, feature_names = ft.dfs(entityset=es, target_dataframe_name="customer_data")

print(features.head())

# 5. Балансировка выборки с помощью RandomOverSampler
ros = RandomOverSampler(random_state=42)
X_train_res, y_train_res = ros.fit_resample(X_train_2, y_train_2)

print(f"Размерность выборки после RandomOverSampler:")
print(f"X_train_res: {X_train_res.shape}")
print(f"y_train_res: {y_train_res.shape}")
               Income  Kidhome  Teenhome  MntWines  MntFruits  \
customer_id                                                     
0           -0.263557        1         1        68          0   
1           -1.102440        1         0        18          3   
2            0.633408        0         1       225        162   
3            1.135917        1         0       739        107   
4            1.299116        0         0       395        183   

             MntMeatProducts  MntFishProducts  MntSweetProducts  MntGoldProds  \
customer_id                                                                     
0                         16                0                 0             8   
1                         19                3                 3             6   
2                        387              106                36            29   
3                        309              140                80            35   
4                        565              166               141            28   

             AcceptedCmp1  AcceptedCmp2  AcceptedCmp3  AcceptedCmp4  \
customer_id                                                           
0                       0             0             0             0   
1                       0             0             0             0   
2                       0             0             0             0   
3                       0             0             0             0   
4                       0             0             0             0   

             AcceptedCmp5  Recency  Income_binned  
customer_id                                        
0                       0        6            1.0  
1                       0       67            0.0  
2                       0       77            1.0  
3                       0        2            2.0  
4                       0       19            2.0  
Размерность выборки после RandomOverSampler:
X_train_res: (2954, 17)
y_train_res: (2954,)
d:\study\3_course\aim\aimenv\Lib\site-packages\featuretools\entityset\entityset.py:1733: UserWarning: index customer_id not found in dataframe, creating new integer column
  warnings.warn(
d:\study\3_course\aim\aimenv\Lib\site-packages\featuretools\synthesis\deep_feature_synthesis.py:169: UserWarning: Only one dataframe in entityset, changing max_depth to 1 since deeper features cannot be created
  warnings.warn(

Прилетает ошибка - UserWarning: Only one dataframe in entityset, changing max_depth to 1 since deeper features cannot be created warnings.warn. Она значит, что EntitySet состоит из одного DataFrame. Т.е. только одна сущность.

Следующая бизнес-цель

In [66]:
# 1. One-hot encoding для категориальных признаков
X_train = pd.get_dummies(X_train, drop_first=True)
X_test = pd.get_dummies(X_test, drop_first=True)

# 2. Дискретизация числовых признаков
discretizer = KBinsDiscretizer(n_bins=5, encode='ordinal', strategy='uniform')
X_train['Income_binned'] = discretizer.fit_transform(X_train[['Income']])
X_test['Income_binned'] = discretizer.transform(X_test[['Income']])

# 3. Масштабирование признаков
scaler_minmax = MinMaxScaler()
X_train[['Income']] = scaler_minmax.fit_transform(X_train[['Income']])
X_test[['Income']] = scaler_minmax.transform(X_test[['Income']])

# Стандартизация признаков
scaler_standard = StandardScaler()
X_train[['Income']] = scaler_standard.fit_transform(X_train[['Income']])
X_test[['Income']] = scaler_standard.transform(X_test[['Income']])

# 4. Применение Featuretools для создания признаков
es = ft.EntitySet(id="data")
es = es.add_dataframe(dataframe_name="customer_data", dataframe=X_train, index="customer_id")

# Применяем deep feature synthesis для создания новых признаков
features, feature_names = ft.dfs(entityset=es, target_dataframe_name="customer_data", max_depth=2)


print(features.head())

# 5. Балансировка выборки с помощью RandomOverSampler
ros = RandomOverSampler(random_state=42)
X_train_res_1, y_train_res_1 = ros.fit_resample(X_train, y_train)

print(f"Размерность выборки после RandomOverSampler для первой бизнес-цели:")
print(f"X_train_res_1: {X_train_res_1.shape}")
print(f"y_train_res_1: {y_train_res_1.shape}")
               Income  Kidhome  Teenhome  NumWebPurchases  MntWines  \
customer_id                                                           
0            0.739837        0         1                6       522   
1           -0.203068        1         1                1        22   
2            0.160233        0         1                7       479   
3            1.049812        0         0                4       594   
4            0.119182        1         2                6       416   

             MntFruits  MntMeatProducts  MntFishProducts  MntSweetProducts  \
customer_id                                                                  
0                    0              522              227               120   
1                    2               10                6                 4   
2                    5               82                7                17   
3                   51              631               72                55   
4                    0               26                0                 0   

             MntGoldProds  AcceptedCmp1  AcceptedCmp2  AcceptedCmp3  \
customer_id                                                           
0                     134             0             0             0   
1                      34             0             0             0   
2                     171             0             0             1   
3                      32             0             0             0   
4                       4             0             0             0   

             AcceptedCmp4  AcceptedCmp5  Recency  Income_binned  
customer_id                                                      
0                       0             0       28            0.0  
1                       0             0       84            0.0  
2                       0             0       30            0.0  
3                       0             0       42            0.0  
4                       1             0       11            0.0  
Размерность выборки после RandomOverSampler для первой бизнес-цели:
X_train_res_1: (2258, 18)
y_train_res_1: (2258,)
d:\study\3_course\aim\aimenv\Lib\site-packages\featuretools\entityset\entityset.py:1733: UserWarning: index customer_id not found in dataframe, creating new integer column
  warnings.warn(
d:\study\3_course\aim\aimenv\Lib\site-packages\featuretools\synthesis\deep_feature_synthesis.py:169: UserWarning: Only one dataframe in entityset, changing max_depth to 1 since deeper features cannot be created
  warnings.warn(

9. Оценить качество каждого набора признаков

Настраиваем модель

In [67]:
X_train = X_train.drop(columns=['customer_id'], errors='ignore')
print("X_train columns:", X_train.columns)
print("X_test columns:", X_test.columns)
X_train columns: Index(['Income', 'Kidhome', 'Teenhome', 'NumWebPurchases', 'MntWines',
       'MntFruits', 'MntMeatProducts', 'MntFishProducts', 'MntSweetProducts',
       'MntGoldProds', 'AcceptedCmp1', 'AcceptedCmp2', 'AcceptedCmp3',
       'AcceptedCmp4', 'AcceptedCmp5', 'Recency', 'Income_binned'],
      dtype='object')
X_test columns: Index(['Income', 'Kidhome', 'Teenhome', 'NumWebPurchases', 'MntWines',
       'MntFruits', 'MntMeatProducts', 'MntFishProducts', 'MntSweetProducts',
       'MntGoldProds', 'AcceptedCmp1', 'AcceptedCmp2', 'AcceptedCmp3',
       'AcceptedCmp4', 'AcceptedCmp5', 'Recency', 'Income_binned'],
      dtype='object')
In [68]:
import time
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.metrics import accuracy_score, roc_auc_score, classification_report
from sklearn.ensemble import RandomForestClassifier
import matplotlib.pyplot as plt
import seaborn as sns

# 1. Оценка предсказательной способности (Accuracy и ROC-AUC для бинарной классификации)
model = RandomForestClassifier(random_state=42)

start_time = time.perf_counter()
model.fit(X_train, y_train)
end_time = time.perf_counter()

train_time = end_time - start_time

y_pred = model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
roc_auc = roc_auc_score(y_test, model.predict_proba(X_test)[:, 1])

print(f"Accuracy: {accuracy}")
print(f"ROC-AUC: {roc_auc}")
print(f"Время обучения модели: {train_time:.4f} секунд")

# 2. Оценка скорости вычисления (время предсказания)
start_time = time.perf_counter()
y_pred = model.predict(X_test)
end_time = time.perf_counter()
predict_time = end_time - start_time

print(f"Время предсказания: {predict_time:.4f} секунд")

# 3. Оценка надежности модели с помощью перекрестной проверки
cv_scores = cross_val_score(model, X, y, cv=5, scoring='accuracy')
mean_cv_score = np.mean(cv_scores)
print(f"Средняя точность по кросс-валидации: {mean_cv_score:.4f}")

# 4. Оценка корреляции признаков с целевой переменной
correlation_matrix = pd.concat([X, y], axis=1).corr()
correlation_with_target = correlation_matrix['Response'].sort_values(ascending=False)
print("Корреляция признаков с целевой переменной:")
print(correlation_with_target)

# Визуализация корреляции
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt='.2f')
plt.title('Корреляционная матрица признаков')
plt.show()

# Дополнительная информация о модели
print(classification_report(y_test, y_pred))
Accuracy: 0.8761261261261262
ROC-AUC: 0.8519339641315966
Время обучения модели: 0.2374 секунд
Время предсказания: 0.0086 секунд
Средняя точность по кросс-валидации: 0.8736
Корреляция признаков с целевой переменной:
Response            1.000000
AcceptedCmp5        0.323374
AcceptedCmp1        0.297345
AcceptedCmp3        0.254005
MntWines            0.246299
MntMeatProducts     0.237746
AcceptedCmp4        0.180205
AcceptedCmp2        0.169294
NumWebPurchases     0.151431
MntGoldProds        0.140332
Income              0.133047
MntFruits           0.122443
MntSweetProducts    0.116170
MntFishProducts     0.108145
Kidhome            -0.077909
Teenhome           -0.153901
Recency            -0.199766
Name: Response, dtype: float64
No description has been provided for this image
              precision    recall  f1-score   support

           0       0.89      0.98      0.93       377
           1       0.70      0.31      0.43        67

    accuracy                           0.88       444
   macro avg       0.79      0.64      0.68       444
weighted avg       0.86      0.88      0.86       444