AIM-PIbd-31-Izotov-A-P/lab4/lab4.ipynb
2024-11-10 20:46:11 +04:00

385 KiB
Raw Permalink Blame History

Лабораторная работа №4

Определение бизнес-целей для решения задач регрессии и классификации

Вариант задания: Набор данных о ценах на акции Starbucks.

Бизнес-цели:

  1. Регрессия: Предсказание цены закрытия акции (Close) на основе исторических данных.

  2. Классификация: Определение направления изменения цены (повышение или понижение) на следующий день, что можно выразить в бинарной метке (например, 1 — цена повысилась, 0 — снизилась). Метка будет рассчитываться как разница между Close сегодняшнего и завтрашнего дня.

Столбцы датасета и их пояснение:

Date - Дата, на которую относятся данные. Эта характеристика указывает конкретный день, в который происходила торговля акциями Starbucks.

Open - Цена открытия. Стоимость акций Starbucks в начале торгового дня. Это важный показатель, который показывает, по какой цене начались торги в конкретный день, и часто используется для сравнения с ценой закрытия для определения дневного тренда.

High - Максимальная цена за день. Наибольшая цена, достигнутая акциями Starbucks в течение торгового дня. Эта характеристика указывает, какой была самая высокая стоимость акций за день.

Low - Минимальная цена за день. Наименьшая цена, по которой торговались акции Starbucks в течение дня.

Close - Цена закрытия. Стоимость акций Starbucks в конце торгового дня. Цена закрытия — один из основных показателей, используемых для анализа акций, так как она отображает итоговую стоимость акций за день и часто используется для расчета дневных изменений и трендов на длительных временных периодах.

Adj Close - Скорректированная цена закрытия. Цена закрытия, скорректированная с учетом всех корпоративных действий.

Volume - Объем торгов. Количество акций Starbucks, проданных и купленных в течение дня.

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

In [6]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import sklearn
from sklearn.model_selection import train_test_split, GridSearchCV, cross_val_score
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LinearRegression, LogisticRegression
from sklearn.tree import DecisionTreeRegressor, DecisionTreeClassifier
from sklearn.ensemble import RandomForestRegressor, RandomForestClassifier
from sklearn.metrics import mean_squared_error, f1_score, accuracy_score, roc_auc_score, confusion_matrix, classification_report
df = pd.read_csv("..//static//csv//StarbucksDataset.csv")
print(df.head())
print(df.columns)

display(df.head(15))

print(df.isnull().sum())
         Date      Open      High       Low     Close  Adj Close     Volume
0  1992-06-26  0.328125  0.347656  0.320313  0.335938   0.260703  224358400
1  1992-06-29  0.339844  0.367188  0.332031  0.359375   0.278891   58732800
2  1992-06-30  0.367188  0.371094  0.343750  0.347656   0.269797   34777600
3  1992-07-01  0.351563  0.359375  0.339844  0.355469   0.275860   18316800
4  1992-07-02  0.359375  0.359375  0.347656  0.355469   0.275860   13996800
Index(['Date', 'Open', 'High', 'Low', 'Close', 'Adj Close', 'Volume'], dtype='object')
Date Open High Low Close Adj Close Volume
0 1992-06-26 0.328125 0.347656 0.320313 0.335938 0.260703 224358400
1 1992-06-29 0.339844 0.367188 0.332031 0.359375 0.278891 58732800
2 1992-06-30 0.367188 0.371094 0.343750 0.347656 0.269797 34777600
3 1992-07-01 0.351563 0.359375 0.339844 0.355469 0.275860 18316800
4 1992-07-02 0.359375 0.359375 0.347656 0.355469 0.275860 13996800
5 1992-07-06 0.351563 0.355469 0.347656 0.355469 0.275860 5753600
6 1992-07-07 0.355469 0.355469 0.347656 0.355469 0.275860 10662400
7 1992-07-08 0.355469 0.355469 0.343750 0.347656 0.269797 15500800
8 1992-07-09 0.351563 0.359375 0.347656 0.359375 0.278891 3923200
9 1992-07-10 0.359375 0.367188 0.351563 0.363281 0.281923 11040000
10 1992-07-13 0.363281 0.371094 0.359375 0.371094 0.287986 5996800
11 1992-07-14 0.371094 0.382813 0.367188 0.371094 0.287986 17062400
12 1992-07-15 0.375000 0.382813 0.371094 0.382813 0.297080 4992000
13 1992-07-16 0.382813 0.414063 0.378906 0.408203 0.316784 17062400
14 1992-07-17 0.410156 0.437500 0.398438 0.429688 0.333458 15667200
Date         0
Open         0
High         0
Low          0
Close        0
Adj Close    0
Volume       0
dtype: int64

Выберем три модели для задач регрессии и классификации

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

Для регрессии выберем:

  • LinearRegression
  • DecisionTreeRegressor
  • GradientBoostingRegressor

Для классификации выберем:

  • LogisticRegression
  • RandomForestClassifier
  • GradientBoostingClassifier

Разбиение на выборки и создание ориентира для задач регрессии

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

In [11]:
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score

# Определяем признаки и целевой признак для задачи регрессии
features = ['Date', 'Open', 'High', 'Low', 'Adj Close', 'Volume'] 
target = 'Close'  # Целевая переменная

X_train, X_test, y_train, y_test = train_test_split(df[features], df[target], test_size=0.2, random_state=42)

print("Размер обучающей выборки:", X_train.shape)
print("Размер тестовой выборки:", X_test.shape)

baseline_predictions = [y_train.mean()] * len(y_test)

print('Baseline MAE:', mean_absolute_error(y_test, baseline_predictions))
print('Baseline MSE:', mean_squared_error(y_test, baseline_predictions))
print('Baseline R²:', r2_score(y_test, baseline_predictions))
Размер обучающей выборки: (6428, 6)
Размер тестовой выборки: (1608, 6)
Baseline MAE: 28.47632651321604
Baseline MSE: 1124.2882179711
Baseline R²: -3.372086434416666e-05

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

Построим конвейер где проведем обучение моделей, а так же переделаем характеристику 'Date' под числовые данные.

In [19]:
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LinearRegression
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score

df['Date'] = pd.to_datetime(df['Date'], errors='coerce')

# Извлечение признаков из даты
df['Year'] = df['Date'].dt.year
df['Month'] = df['Date'].dt.month
df['Day'] = df['Date'].dt.day

categorical_features = [] 
numeric_features = ['Year', 'Month', 'Day', 'Open', 'High', 'Low', 'Adj Close', 'Volume']

target = 'Close'
features = numeric_features + categorical_features

X_train, X_test, y_train, y_test = train_test_split(df[features], df[target], test_size=0.2, random_state=42)

preprocessor = ColumnTransformer(
    transformers=[
        ('num', StandardScaler(), numeric_features),
        ('cat', OneHotEncoder(), categorical_features)], 
    remainder='passthrough')

pipeline_linear_regression = Pipeline(steps=[
    ('preprocessor', preprocessor),
    ('regressor', LinearRegression())
])

pipeline_decision_tree = Pipeline(steps=[
    ('preprocessor', preprocessor),
    ('regressor', DecisionTreeRegressor(random_state=42))
])

pipeline_gradient_boosting = Pipeline(steps=[
    ('preprocessor', preprocessor),
    ('regressor', GradientBoostingRegressor(random_state=42))
])

pipelines = [
    ('Linear Regression', pipeline_linear_regression),
    ('Decision Tree', pipeline_decision_tree),
    ('Gradient Boosting', pipeline_gradient_boosting)
]

for name, pipeline in pipelines:
    pipeline.fit(X_train, y_train)
    print(f"Model: {name} trained.")
Model: Linear Regression trained.
Model: Decision Tree trained.
Model: Gradient Boosting trained.

Оценка качества моделей для регрессии

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

In [20]:
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score

for name, pipeline in pipelines:
    y_pred = pipeline.predict(X_test)
    print(f"Model: {name}")
    print('MAE:', mean_absolute_error(y_test, y_pred))
    print('MSE:', mean_squared_error(y_test, y_pred))
    print('R²:', r2_score(y_test, y_pred))
    print()
Model: Linear Regression
MAE: 0.1281297170692111
MSE: 0.05669059396494051
R²: 0.999949574757865

Model: Decision Tree
MAE: 0.15318700746268657
MSE: 0.12959596917987073
R²: 0.9998847267656137

Model: Gradient Boosting
MAE: 0.20665879042876864
MSE: 0.1305083114366548
R²: 0.9998839152539326

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

  • MAE (Mean Absolute Error) — средняя абсолютная ошибка. Она измеряет среднюю величину отклонений предсказанных значений от фактических, что позволяет понять, насколько в среднем модель ошибается. MAE удобна для интерпретации, так как измеряется в тех же единицах, что и целевая переменная.

  • MSE (Mean Squared Error) — среднеквадратичная ошибка, которая учитывает квадраты ошибок, что увеличивает вес больших ошибок по сравнению с MAE. Это полезно, когда нам нужно сильнее штрафовать крупные отклонения.

  • R² (коэффициент детерминации) — доля объясненной дисперсии, которая показывает, насколько хорошо модель объясняет изменчивость целевой переменной. Значение R² близкое к 1 указывает на высокую точность модели, а отрицательные значения — на низкое качество, когда модель хуже, чем простое усреднение.

Анализ метрик для моделей:

  • Линейная регрессия: MAE и MSE близки к нулю, а R² почти 1 (0.9999), что указывает на высокое качество предсказаний и низкое смещение. Это значит, что линейная регрессия хорошо подходит для данной задачи, объясняя почти всю дисперсию данных.

  • Дерево решений: MAE и MSE немного выше, чем у линейной регрессии, а R² всё ещё очень высок (0.9998). Хотя дерево решений немного уступает линейной модели в точности, оно способно находить нелинейные зависимости, которые могут улучшить результат в более сложных сценариях.

  • Градиентный бустинг: MAE и MSE также несколько выше, чем у линейной регрессии, но R² (0.9998) остаётся на высоком уровне. Градиентный бустинг обычно справляется лучше в задачах с более сложными нелинейными зависимостями. В данном случае его результаты аналогичны дереву решений, но не превосходят линейную регрессию.

Вывод

Поскольку R² для всех моделей близок к 1, каждая модель справляется с задачей на высоком уровне. Тем не менее, линейная регрессия имеет наименьшие значения MAE и MSE, что указывает на её лучшее соответствие данным.

Разбиение на выборки и создание ориентира для задач классификации

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

In [5]:
import pandas as pd
from sklearn.model_selection import train_test_split

df = pd.read_csv("..//static//csv//StarbucksDataset.csv")
# Создание целевой переменной для классификации направления изменения цены
# Если цена закрытия следующего дня выше текущего дня — 1 (повышение), иначе — 0 (снижение)
df['Price_Up'] = (df['Close'].shift(-1) > df['Close']).astype(int)

features = ['Open', 'High', 'Low', 'Volume'] 
target = 'Price_Up'

# Удаление последней строки, так как для неё нет значения следующего дня
df = df.dropna()

X_train, X_test, y_train, y_test = train_test_split(df[features], df[target], test_size=0.2, random_state=42)

print("Размер обучающей выборки:", X_train.shape)
print("Размер тестовой выборки:", X_test.shape)
Размер обучающей выборки: (6428, 4)
Размер тестовой выборки: (1608, 4)

Построение конвейера и обучение моделей для задач классификации

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

In [6]:
import pandas as pd
from imblearn.over_sampling import SMOTE
from sklearn.model_selection import train_test_split, RandomizedSearchCV
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score
from scipy.stats import uniform, randint

features = ['Open', 'High', 'Low', 'Volume']
target = 'Price_Up'

X = df[features]
y = df[target]

smote = SMOTE(random_state=42)
X_resampled, y_resampled = smote.fit_resample(X, y)

X_train, X_test, y_train, y_test = train_test_split(X_resampled, y_resampled, test_size=0.2, random_state=42)

def evaluate_model(model, X_test, y_test):
    y_pred = model.predict(X_test)
    y_pred_proba = model.predict_proba(X_test)[:, 1]
    
    accuracy = accuracy_score(y_test, y_pred)
    precision = precision_score(y_test, y_pred)
    recall = recall_score(y_test, y_pred)
    f1 = f1_score(y_test, y_pred)
    roc_auc = roc_auc_score(y_test, y_pred_proba)
    
    print(f"Accuracy: {accuracy:.4f}")
    print(f"Precision: {precision:.4f}")
    print(f"Recall: {recall:.4f}")
    print(f"F1-Score: {f1:.4f}")
    print(f"ROC-AUC: {roc_auc:.4f}")
    
    return {'accuracy': accuracy, 'precision': precision, 'recall': recall, 'f1': f1, 'roc_auc': roc_auc}


# Логистическая регрессия
logreg_pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('classifier', LogisticRegression(max_iter=1000, random_state=42))
])
logreg_param_dist = {
    'classifier__C': uniform(loc=0, scale=4),
    'classifier__penalty': ['l1', 'l2'],
    'classifier__solver': ['liblinear', 'saga']
}
logreg_random_search = RandomizedSearchCV(logreg_pipeline, param_distributions=logreg_param_dist, n_iter=50, cv=5, random_state=42, n_jobs=-1)
logreg_random_search.fit(X_train, y_train)
logreg_best_model = logreg_random_search.best_estimator_
logreg_results = evaluate_model(logreg_best_model, X_test, y_test)

# Случайный лес
rf_pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('classifier', RandomForestClassifier(random_state=42))
])
rf_param_dist = {
    'classifier__n_estimators': randint(100, 1000),
    'classifier__max_depth': [None] + list(randint(10, 100).rvs(10)),
    'classifier__min_samples_split': randint(2, 20),
    'classifier__min_samples_leaf': randint(1, 20),
    'classifier__bootstrap': [True, False]
}
rf_random_search = RandomizedSearchCV(rf_pipeline, param_distributions=rf_param_dist, n_iter=50, cv=5, random_state=42, n_jobs=-1)
rf_random_search.fit(X_train, y_train)
rf_best_model = rf_random_search.best_estimator_
rf_results = evaluate_model(rf_best_model, X_test, y_test)

# Градиентный бустинг
gb_pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('classifier', GradientBoostingClassifier(random_state=42))
])
gb_param_dist = {
    'classifier__n_estimators': randint(100, 1000),
    'classifier__learning_rate': uniform(0.01, 0.5),
    'classifier__max_depth': [None] + list(randint(10, 100).rvs(10)),
    'classifier__min_samples_split': randint(2, 20),
    'classifier__min_samples_leaf': randint(1, 20),
    'classifier__subsample': uniform(0.5, 0.5)
}
gb_random_search = RandomizedSearchCV(gb_pipeline, param_distributions=gb_param_dist, n_iter=50, cv=5, random_state=42, n_jobs=-1)
gb_random_search.fit(X_train, y_train)
gb_best_model = gb_random_search.best_estimator_
gb_results = evaluate_model(gb_best_model, X_test, y_test)

print("\nРезультаты моделей:")
print("\nLogistic Regression:")
for metric, value in logreg_results.items():
    print(f"{metric.capitalize()}: {value:.4f}")

print("\nRandom Forest:")
for metric, value in rf_results.items():
    print(f"{metric.capitalize()}: {value:.4f}")

print("\nGradient Boosting:")
for metric, value in gb_results.items():
    print(f"{metric.capitalize()}: {value:.4f}")
Accuracy: 0.5043
Precision: 0.5017
Recall: 0.3556
F1-Score: 0.4162
ROC-AUC: 0.5056
Accuracy: 0.5302
Precision: 0.5275
Recall: 0.5229
F1-Score: 0.5252
ROC-AUC: 0.5463
Accuracy: 0.5302
Precision: 0.5274
Recall: 0.5242
F1-Score: 0.5258
ROC-AUC: 0.5434

Результаты моделей:

Logistic Regression:
Accuracy: 0.5043
Precision: 0.5017
Recall: 0.3556
F1: 0.4162
Roc_auc: 0.5056

Random Forest:
Accuracy: 0.5302
Precision: 0.5275
Recall: 0.5229
F1: 0.5252
Roc_auc: 0.5463

Gradient Boosting:
Accuracy: 0.5302
Precision: 0.5274
Recall: 0.5242
F1: 0.5258
Roc_auc: 0.5434

Оценка качества моделей для классификации

Оценим качество моделей для решения задач классификации и обоснуем выбор метрик.

In [15]:
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.metrics import confusion_matrix, roc_curve, auc, accuracy_score, precision_score, recall_score, f1_score, roc_auc_score

sns.set(style="whitegrid")

def plot_confusion_matrix(y_true, y_pred, title):
    cm = confusion_matrix(y_true, y_pred)
    plt.figure(figsize=(6, 5))
    ax = sns.heatmap(cm, annot=True, fmt='d', cmap='coolwarm', cbar=True, annot_kws={"size": 14})
    plt.title(title, fontsize=16)
    plt.xlabel('Предсказанные значения', fontsize=12)
    plt.ylabel('Истинные значения', fontsize=12)
    plt.xticks(fontsize=10)
    plt.yticks(fontsize=10)
    cbar = ax.collections[0].colorbar
    cbar.set_label('Count', rotation=270, labelpad=20, fontsize=12)
    plt.show() 

def plot_roc_curve(y_true, y_pred_proba, title):
    fpr, tpr, _ = roc_curve(y_true, y_pred_proba)
    roc_auc = auc(fpr, tpr)
    
    plt.figure(figsize=(8, 6))
    plt.plot(fpr, tpr, color='#FF6347', lw=2, label=f'ROC curve (AUC = {roc_auc:.2f})')
    plt.plot([0, 1], [0, 1], color='gray', linestyle='--', lw=1.5, label='Random Guess')
    plt.xlim([0.0, 1.0])
    plt.ylim([0.0, 1.05])
    plt.xlabel('Показатель ложных положительных результатов', fontsize=12)
    plt.ylabel('Показатель истинных положительных результатов', fontsize=12)
    plt.title(title, fontsize=16)
    plt.legend(loc="lower right", fontsize=10)
    plt.grid(True, linestyle='--', alpha=0.6)
    plt.show()

def evaluate_and_plot_model(model, X_test, y_test, model_name):
    y_pred = model.predict(X_test)
    y_pred_proba = model.predict_proba(X_test)[:, 1]
    
    accuracy = accuracy_score(y_test, y_pred)
    precision = precision_score(y_test, y_pred, pos_label=1)
    recall = recall_score(y_test, y_pred, pos_label=1)
    f1 = f1_score(y_test, y_pred, pos_label=1)
    roc_auc = roc_auc_score(y_test, y_pred_proba)
    
    print(f"\n{model_name} Metrics:")
    print(f"Accuracy: {accuracy:.4f}")
    print(f"Precision: {precision:.4f}")
    print(f"Recall: {recall:.4f}")
    print(f"F1-Score: {f1:.4f}")
    print(f"ROC-AUC: {roc_auc:.4f}")
    
    plot_confusion_matrix(y_test, y_pred, f'Confusion Matrix for {model_name}')
    plot_roc_curve(y_test, y_pred_proba, f'ROC Curve for {model_name}')

evaluate_and_plot_model(logreg_best_model, X_test, y_test, 'Logistic Regression')
evaluate_and_plot_model(rf_best_model, X_test, y_test, 'Random Forest')
evaluate_and_plot_model(gb_best_model, X_test, y_test, 'Gradient Boosting')
Logistic Regression Metrics:
Accuracy: 0.5043
Precision: 0.5017
Recall: 0.3556
F1-Score: 0.4162
ROC-AUC: 0.5056
No description has been provided for this image
No description has been provided for this image
Random Forest Metrics:
Accuracy: 0.5302
Precision: 0.5275
Recall: 0.5229
F1-Score: 0.5252
ROC-AUC: 0.5463
No description has been provided for this image
No description has been provided for this image
Gradient Boosting Metrics:
Accuracy: 0.5302
Precision: 0.5274
Recall: 0.5242
F1-Score: 0.5258
ROC-AUC: 0.5434
No description has been provided for this image
No description has been provided for this image

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

Анализ метрик для моделей:

  • Логистическая регрессия: Данная модель показала точность (Accuracy) 0.5043 и F1-меру 0.4162. Значения Precision (0.5017) и Recall (0.3556) также указывают на трудности модели с корректной классификацией. ROC-AUC на уровне 0.5056 близок к случайному значению (0.5), что говорит о слабой предсказательной способности.

  • Случайный лес: Случайный лес продемонстрировал лучшие результаты по сравнению с логистической регрессией: точность 0.5302 и F1-меру 0.5252. Метрика ROC-AUC составила 0.5463, что также превышает уровень случайного угадывания, но не является достаточным показателем качества.

  • Градиентный бустинг: Градиентный бустинг показал схожие результаты со случайным лесом: точность 0.5302, F1-меру 0.5258, и ROC-AUC на уровне 0.5434. Данные значения говорят о том, что, несмотря на сложности задачи, эта модель на данный момент является наилучшей из предложенных.