641 KiB
Датасет: Цены на акции¶
https://www.kaggle.com/datasets/nancyalaswad90/yamana-gold-inc-stock-Volume
О наборе данных:¶
Yamana Gold Inc. — это канадская компания, которая занимается разработкой и управлением золотыми, серебряными и медными рудниками, расположенными в Канаде, Чили, Бразилии и Аргентине. Головной офис компании находится в Торонто.
Yamana Gold была основана в 1994 году и уже через год была зарегистрирована на фондовой бирже Торонто. В 2007 году она стала участником Нью-Йоркской фондовой биржи, а в 2020 году — Лондонской. В 2003 году компания претерпела значительные изменения: была проведена реструктуризация, в результате которой Питер Марроне занял пост главного исполнительного директора. Кроме того, Yamana объединилась с бразильской компанией Santa Elina Mines Corporation. Благодаря этому слиянию Yamana получила доступ к капиталу, накопленному Santa Elina, что позволило ей начать разработку и эксплуатацию рудника Чапада. Затем компания объединилась с другими организациями, зарегистрированными на бирже TSX: RNC Gold, Desert Sun Mining, Viceroy Exploration, Northern Orion Resources, Meridian Gold, Osisko Mining и Extorre Gold Mines. Каждая из них внесла свой вклад в разработку месторождения или проект, который в итоге был успешно запущен.
Таким образом:¶
- Объект наблюдения - цены и объемы акций компании
- Атрибуты: 'Date', 'Open', 'High', 'Low', 'Close', 'Adj Close', 'Volume'
Бизнес цели:¶
- Прогнозирование будущей цены акций. Использование данных для создания модели, которая будет предсказывать цену акций компании в будущем.
- Определение волатильности акций. Определение, колебаний цен акций, что поможет инвесторам понять риски.
Технические цели:¶
- Разработать модель машинного обучения для прогноза цены акций на основе имеющихся данных.
- Разработать метрику и модель для оценки волатильности акций на основе исторических данных.
import pandas as pd
df = pd.read_csv(".//static//csv//Stocks.csv", sep=",")
print('Количество колонок: ' + str(df.columns.size))
print('Колонки: ' + ', '.join(df.columns)+'\n')
df['Date'] = pd.to_datetime(df['Date'], errors='coerce')
df.info()
df.head()
Подготовка данных:¶
1. Получение сведений о пропущенных данных¶
Типы пропущенных данных:
- None - представление пустых данных в Python
- NaN - представление пустых данных в Pandas
- '' - пустая строка
import numpy as np
# Количество пустых значений признаков
print(df.isnull().sum())
print()
# Есть ли пустые значения признаков
print(df.isnull().any())
print()
# Проверка на бесконечные значения
print("Количество бесконечных значений в каждом столбце:")
print(np.isinf(df).sum())
# Процент пустых значений признаков
for i in df.columns:
null_rate = df[i].isnull().sum() / len(df) * 100
print(f"{i} процент пустых значений: %{null_rate:.2f}")
Таким образом, пропущенных значений не найдено.
2. Проверка выбросов данных и устранение их при наличии:¶
numeric_columns = ['Open', 'High', 'Low', 'Close', 'Adj Close', 'Volume']
for column in numeric_columns:
if pd.api.types.is_numeric_dtype(df[column]): # Проверяем, является ли колонка числовой
q1 = df[column].quantile(0.25) # Находим 1-й квартиль (Q1)
q3 = df[column].quantile(0.75) # Находим 3-й квартиль (Q3)
iqr = q3 - q1 # Вычисляем межквартильный размах (IQR)
# Определяем границы для выбросов
lower_bound = q1 - 1.5 * iqr # Нижняя граница
upper_bound = q3 + 1.5 * iqr # Верхняя граница
# Подсчитываем количество выбросов
outliers = df[(df[column] < lower_bound) | (df[column] > upper_bound)]
outlier_count = outliers.shape[0]
print("До устранения выбросов:")
print(f"Колонка {column}:")
print(f" Есть выбросы: {'Да' if outlier_count > 0 else 'Нет'}")
print(f" Количество выбросов: {outlier_count}")
print(f" Минимальное значение: {df[column].min()}")
print(f" Максимальное значение: {df[column].max()}")
print(f" 1-й квартиль (Q1): {q1}")
print(f" 3-й квартиль (Q3): {q3}\n")
# Устраняем выбросы: заменяем значения ниже нижней границы на саму нижнюю границу, а выше верхней — на верхнюю
if outlier_count != 0:
df[column] = df[column].apply(lambda x: lower_bound if x < lower_bound else upper_bound if x > upper_bound else x)
# Подсчитываем количество выбросов
outliers = df[(df[column] < lower_bound) | (df[column] > upper_bound)]
outlier_count = outliers.shape[0]
print("После устранения выбросов:")
print(f"Колонка {column}:")
print(f" Есть выбросы: {'Да' if outlier_count > 0 else 'Нет'}")
print(f" Количество выбросов: {outlier_count}")
print(f" Минимальное значение: {df[column].min()}")
print(f" Максимальное значение: {df[column].max()}")
print(f" 1-й квартиль (Q1): {q1}")
print(f" 3-й квартиль (Q3): {q3}\n")
Выбросы присутствовали, но мы их устранили.
Разбиение на выборки:¶
Разобьем наш набор на обучающую, контрольную и тестовую выборки для устранения проблемы просачивания данных.
from sklearn.model_selection import train_test_split
# Разделение данных на обучающую и тестовую выборки (80% - обучение, 20% - тестовая)
X_train, X_test = train_test_split(df, test_size=0.2, random_state=42)
# Разделение данных на обучающую и контрольную выборки (80% - обучение, 20% - контроль)
X_train, X_val = train_test_split(df, test_size=0.2, random_state=42)
print("Размер обучающей выборки: ", len(X_train))
print("Размер контрольной выборки: ", len(X_test))
print("Размер тестовой выборки: ", len(X_val))
import seaborn as sns
import matplotlib.pyplot as plt
# Гистограмма распределения цены закрытия в обучающей выборке
plt.figure(figsize=(12, 6))
sns.histplot(X_train['Close'], bins=30, kde=False)
plt.title("Распределение классов (до балансировки)")
plt.xlabel('Целевая переменная: Close')
plt.ylabel('Частота')
plt.show()
# Гистограмма распределения цены закрытия в контрольной выборке
plt.figure(figsize=(12, 6))
sns.histplot(X_val['Close'], bins=30, kde=False)
plt.title("Распределение классов (до балансировки)")
plt.xlabel('Целевая переменная: Close')
plt.ylabel('Частота')
plt.show()
# Гистограмма распределения цены закрытия в тестовой выборке
plt.figure(figsize=(12, 6))
sns.histplot(X_test['Close'], bins=30, kde=False)
plt.title("Распределение классов (до балансировки)")
plt.xlabel('Целевая переменная: Close')
plt.ylabel('Частота')
plt.show()
Применим овер- и андерсемплинг к обучающей выборке:¶
from sklearn.model_selection import train_test_split
from imblearn.over_sampling import RandomOverSampler
from imblearn.under_sampling import RandomUnderSampler
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
# Преобразование целевой переменной (цены) в категориальные диапазоны с использованием квантилей
X_train['closePrice_category'] = pd.qcut(X_train['Close'], q=4, labels=['low', 'medium', 'high', 'very_high'])
print(X_train.head())
# Визуализация распределения цен после преобразования в категории
sns.countplot(x=X_train['closePrice_category'])
plt.title('Распределение категорий закрывающей цены в обучающей выборке')
plt.xlabel('Категория закрывающей цены')
plt.ylabel('Частота')
plt.show()
# Балансировка категорий с помощью RandomOverSampler (увеличение меньшинств)
ros = RandomOverSampler(random_state=42)
y_train = X_train['closePrice_category']
X_train = X_train.drop(columns=['closePrice_category'])
# Применяем oversampling. Здесь важно, что мы используем X_train как DataFrame и y_train_categories как целевую переменную
X_resampled, y_resampled = ros.fit_resample(X_train, y_train)
# Визуализация распределения цен после oversampling
sns.countplot(x=y_resampled)
plt.title('Распределение категорий закрывающей цены после oversampling')
plt.xlabel('Категория закрывающей цены')
plt.ylabel('Частота')
plt.show()
# Применение RandomUnderSampler для уменьшения большего класса
rus = RandomUnderSampler(random_state=42)
X_resampled, y_resampled = rus.fit_resample(X_resampled, y_resampled)
# Визуализация распределения цен после undersampling
sns.countplot(x=y_resampled)
plt.title('Распределение категорий закрывающей цены после undersampling')
plt.xlabel('Категория закрывающей цены')
plt.ylabel('Частота')
plt.show()
print("Размер обучающей выборки до oversampling и undersampling: ", len(X_train))
print("Размер обучающей выборки после oversampling и undersampling: ", len(X_resampled))
X_resampled.head()
По сути, балансировка так то не требовалась, но все же мы ее провели, добавив в обучающую выборку 5 значений (ーー;)
Конструирование признаков¶
- Унитарное кодирование категориальных признаков. Преобразование категориальных признаков в бинарные векторы.
- В данном датасете категориальные признаки отсутствуют, так что пропустим этот пункт.
- Дискретизация числовых признаков. Преобразование непрерывных числовых значений в дискретные категории или интервалы (бины).
#Пример дискретизации по цене закрытия
# Проверка на наличие числовых признаков
print("Названия столбцов в датасете:")
print(df.columns)
# Выводим основные статистические параметры для количественных признаков
print("Статистические параметры:")
print(df.describe())
# Дискретизация столбца 'Close' на группы
bins = [0, 2, 4, 6, 8, 10, 12, 14, 16, 30] # Определяем границы корзин
labels = ['0-2', '2-4', '4-6', '6-8', '8-10', '10-12', '12-14', '14-16', '16+'] # Названия категорий
# Создание нового столбца 'Close_Disc' на основе дискретизации
df['Close_Disc'] = pd.cut(df['Close'], bins=bins, labels=labels, include_lowest=True) #pd.cut выполняет дискретизацию переменной
#include_lowest=True: Этот параметр гарантирует, что самое нижнее значение (в данном случае 0), будет входить в первую категорию.
# Проверка результата
print("После дискретизации 'Close':")
print(df.head())
n = len(df)
middle_index = n // 2
print(df.iloc[middle_index - 2: middle_index + 3])
print(df.tail())
Конструирование новых признаков:¶
print('\nИсходный датасет: ')
print(df.tail())
print('\nОбучающая выборка: ')
print(X_resampled.tail())
print('\nТестовая выборка: ')
print(X_test.tail())
print('\nКонтрольная выборка: ')
print(X_val.tail())
#Объем изменений
df['Volume_Change'] = df['Volume'].pct_change()
X_resampled['Volume_Change'] = X_resampled['Volume'].pct_change()
X_test['Volume_Change'] = X_test['Volume'].pct_change()
X_val['Volume_Change'] = X_val['Volume'].pct_change()
# Результатом работы pct_change() является серия, где каждое значение представляет собой
# процентное изменение относительно предыдущего значения. Первое значение всегда будет NaN,
# так как для него нет предшествующего значения для сравнения.
# Проверка создания новых признаков
print("\nНовые признаки в обучающей выборке:")
print(X_resampled[['Volume_Change']].tail())
print("\nНовые признаки в тестовой выборке:")
print(X_test[['Volume_Change']].tail())
print("\nНовые признаки в контрольной выборке:")
print(X_val[['Volume_Change']].tail())
print("\nНовые признаки в датасете:")
print(df[['Volume_Change']].tail())
Проверим новые признаки:¶
print('\nИсходный датасет: ')
print(df[['Volume_Change']].isnull().sum())
print('\nОбучающая выборка: ')
print(X_resampled[['Volume_Change']].isnull().sum())
print('\nТестовая выборка: ')
print(X_test[['Volume_Change']].isnull().sum())
print('\nКонтрольная выборка: ')
print(X_val[['Volume_Change']].isnull().sum())
print()
# Есть ли пустые значения признаков
print('Есть ли пустые значения признаков: ')
print('\nИсходный датасет: ')
print(df[['Volume_Change']].isnull().any())
print('\nОбучающая выорка: ')
print(X_resampled[['Volume_Change']].isnull().any())
print('\nТестовая выборка: ')
print(X_test[['Volume_Change']].isnull().any())
print('\nКонтрольная выборка: ')
print(X_val[['Volume_Change']].isnull().any())
print()
# Проверка на бесконечные значения
print("Количество бесконечных значений в каждом столбце:")
print('\nИсходный датасет: ')
print(np.isinf(df[['Volume_Change']]).sum())
print('\nОбучающая выборка: ')
print(np.isinf(X_resampled[['Volume_Change']]).sum())
print('\nТестовая выборка: ')
print(np.isinf(X_test[['Volume_Change']]).sum())
print('\nКонтрольная выборка: ')
print(np.isinf(X_val[['Volume_Change']]).sum())
# Процент пустых значений признаков
for i in df[['Volume_Change']].columns:
null_rate = df[['Volume_Change']][i].isnull().sum() / len(df[['Volume_Change']]) * 100
print(f"{i} процент пустых значений в датасете: %{null_rate:.2f}")
# Процент пустых значений признаков
for i in X_resampled[['Volume_Change']].columns:
null_rate = X_resampled[['Volume_Change']][i].isnull().sum() / len(X_resampled[['Volume_Change']]) * 100
print(f"{i} процент пустых значений в обучающей выборке: %{null_rate:.2f}")
# Процент пустых значений признаков
for i in X_test[['Volume_Change']].columns:
null_rate = X_test[['Volume_Change']][i].isnull().sum() / len(X_test[['Volume_Change']]) * 100
print(f"{i} процент пустых значений в тестовой выборке: %{null_rate:.2f}")
# Процент пустых значений признаков
for i in X_val[['Volume_Change']].columns:
null_rate = X_val[['Volume_Change']][i].isnull().sum() / len(X_val[['Volume_Change']]) * 100
print(f"{i} процент пустых значений в контрольной выборке: %{null_rate:.2f}")
Заполним пустые данные
# Заменяем бесконечные значения на NaN
df.replace([np.inf, -np.inf], np.nan, inplace=True)
X_resampled.replace([np.inf, -np.inf], np.nan, inplace=True)
X_test.replace([np.inf, -np.inf], np.nan, inplace=True)
X_val.replace([np.inf, -np.inf], np.nan, inplace=True)
fillna_df = df[['Volume_Change']].fillna(0)
fillna_X_resampled = X_resampled[['Volume_Change']].fillna(0)
fillna_X_test = X_test[['Volume_Change']].fillna(0)
fillna_X_val = X_val[['Volume_Change']].fillna(0)
# используется для заполнения всех значений NaN
# (Not a Number) в DataFrame или Series указанным значением.
# В данном случае, fillna(0) заполняет все ячейки, содержащие NaN, значением 0.
print(fillna_df.shape)
print(fillna_X_resampled.shape)
print(fillna_X_test.shape)
print(fillna_X_val.shape)
print(fillna_df.isnull().any())
print(fillna_X_resampled.isnull().any())
print(fillna_X_test.isnull().any())
print(fillna_X_val.isnull().any())
# Замена пустых данных на 0
df["Volume_Change"] = df["Volume_Change"].fillna(0)
X_resampled["Volume_Change"] = X_resampled["Volume_Change"].fillna(0)
X_test["Volume_Change"] = X_test["Volume_Change"].fillna(0)
X_val["Volume_Change"] = X_val["Volume_Change"].fillna(0)
# Вычисляем медиану для колонки "Volume_Change"
median_Volume_Change_df = df["Volume_Change"].median()
median_Volume_Change_train = X_resampled["Volume_Change"].median()
median_Volume_Change_test = X_test["Volume_Change"].median()
median_Volume_Change_val = X_val["Volume_Change"].median()
# Заменяем значения 0 на медиану
df[['Volume_Change']].loc[df["Volume_Change"] == 0, "Volume_Change"] = median_Volume_Change_df
X_resampled[['Volume_Change']].loc[X_resampled["Volume_Change"] == 0, "Volume_Change"] = median_Volume_Change_train
X_test[['Volume_Change']].loc[X_test["Volume_Change"] == 0, "Volume_Change"] = median_Volume_Change_test
X_val[['Volume_Change']].loc[X_val["Volume_Change"] == 0, "Volume_Change"] = median_Volume_Change_val
print(df[['Volume_Change']].tail())
print(X_resampled[['Volume_Change']].tail())
print(X_test[['Volume_Change']].tail())
print(X_val[['Volume_Change']].tail())
Удалим наблюдения с пропусками
dropna_df = df[['Volume_Change']].dropna()
dropna_X_resampled = X_resampled[['Volume_Change']].dropna()
dropna_X_test = X_test[['Volume_Change']].dropna()
dropna_X_val = X_val[['Volume_Change']].dropna()
print(dropna_df.shape)
print(dropna_X_resampled.shape)
print(dropna_X_test.shape)
print(dropna_X_val.shape)
print(dropna_df.isnull().any())
print(df[['Volume_Change']].tail())
print(dropna_X_resampled.isnull().any())
print(X_resampled[['Volume_Change']].tail())
print(dropna_X_test.isnull().any())
print(X_test[['Volume_Change']].tail())
print(dropna_X_val.isnull().any())
print(X_val[['Volume_Change']].tail())
Масштабируем новые признаки:¶
from sklearn.preprocessing import StandardScaler, MinMaxScaler
# Пример масштабирования числовых признаков
numerical_features = ['Volume_Change']
scaler = StandardScaler()
df[numerical_features] = scaler.fit_transform(df[numerical_features])
X_resampled[numerical_features] = scaler.fit_transform(X_resampled[numerical_features])
X_val[numerical_features] = scaler.transform(X_val[numerical_features])
X_test[numerical_features] = scaler.transform(X_test[numerical_features])
# fit() - вычисляет среднее и стандартное отклонение для каждого признака в наборе данных.
# transform() - применяет расчеты, чтобы стандартизировать данные по приведенной выше формуле.
# Вывод результатов после масштабирования
print("Результаты после масштабирования:")
print("\n Датафрейм:")
print(df[numerical_features].tail())
print("\n Обучающая:")
print(X_resampled[numerical_features].tail())
print("\n Тестовая:")
print(X_val[numerical_features].tail())
print("\n Контрольная:")
print(X_test[numerical_features].tail())
Данные признаки предоставляют важную информацию о текущем тренде и возможных изменениях в будущих ценах. Положительные значения Price_Change и Percentage_Change, наряду с высоким Volume_Change, могут поддерживать гипотезу о росте цен на акции.
Также, эти признаки помогают понять уровень рискованности инвестиций. Высокие значения Price_Range и резкие изменения в Volume_Change могут указывать на склонность к большим колебаниям, что требует внимательного управления рисками.
Применим featuretools для конструирования признаков:¶
import featuretools as ft
df['id'] = df.index
X_resampled['id'] = X_resampled.index
X_val['id'] = X_val.index
X_test['id'] = X_test.index
# Добавляем уникальный идентификатор
# Предобработка данных (например, кодирование категориальных признаков, удаление дубликатов)
# Удаление дубликатов по идентификатору
df = df.drop_duplicates(subset='id')
duplicates = X_resampled[X_resampled['id'].duplicated(keep=False)]
# Удаление дубликатов из столбца "id", сохранив первое вхождение
df = df.drop_duplicates(subset='id', keep='first')
print(duplicates)
# Создание EntitySet
es = ft.EntitySet(id='stock_data')
# Добавление датафрейма с акциями
es = es.add_dataframe(dataframe_name='stocks', dataframe=df, index='id')
# Генерация признаков с помощью глубокой синтезы признаков
feature_matrix, feature_defs = ft.dfs(entityset=es, target_dataframe_name='stocks', max_depth=2)
# Выводим первые 5 строк сгенерированного набора признаков
print(feature_matrix.head())
X_resampled = X_resampled.drop_duplicates(subset='id')
X_resampled = X_resampled.drop_duplicates(subset='id', keep='first') # or keep='last'
# Определение сущностей (Создание EntitySet)
es = ft.EntitySet(id='stock_data')
es = es.add_dataframe(dataframe_name='stocks', dataframe=X_resampled, index='id')
# Генерация признаков
feature_matrix, feature_defs = ft.dfs(entityset=es, target_dataframe_name='stocks', max_depth=2)
# Она автоматически генерирует новые признаки из исходного датафрейма и производит агрегацию по связям.
# Преобразование признаков для контрольной и тестовой выборок
val_feature_matrix = ft.calculate_feature_matrix(features=feature_defs, entityset=es, instance_ids=X_val.index)
test_feature_matrix = ft.calculate_feature_matrix(features=feature_defs, entityset=es, instance_ids=X_test.index)
#генерирует матрицы признаков для контрольной и тестовой выборок, используя идентификаторы экземпляров из X_val.index и X_test.index соответственно.
print(feature_matrix.head())
Система сгенерировала следующие признаки:
Open, High, Low, Close, Adj Close: Это стандартные финансовые параметры акций, отражающие цены открытия, максимальные, минимальные и закрытия за определенный период. Volume: Объем торгов акциями, который показывает, сколько акций было куплено/продано за определенный период.
Сложные признаки: Close_Disc: Это диапазон цены закрытия. Price_Change: Изменение цены, т.е. разница между ценой закрытия и ценой открытия акций. Percentage_Change: Процентное изменение цен, которое позволяет оценить относительное изменение стоимости акций. Average_Price: Средняя цена акций за указанный период. Этот показатель может быть использован для оценки общей тенденции рынка.
Также произошло разбиение даты на месяц, день недели и год, что может помочь в анализе сезонных и временных закономерностей.
Оценим качество каждого набора признаков:¶
import time
from sklearn.model_selection import train_test_split
from sklearn.metrics import r2_score, mean_absolute_error
from sklearn.linear_model import LinearRegression
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import RandomForestRegressor
from sklearn.linear_model import Lasso
from sklearn.linear_model import Ridge
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import cross_val_score
# Разделение данных на обучающую и тестовую выборки. Удаляем целевую переменную
#y = feature_matrix['Close'] #- целевая переменная
#X = feature_matrix.drop('Close', axis=1)
# Удаление строк с NaN
feature_matrix = feature_matrix.dropna()
val_feature_matrix = val_feature_matrix.dropna()
test_feature_matrix = test_feature_matrix.dropna()
feature_matrix = pd.get_dummies(feature_matrix, drop_first=True)
val_feature_matrix = pd.get_dummies(val_feature_matrix, drop_first=True)
test_feature_matrix = pd.get_dummies(test_feature_matrix, drop_first=True)
feature_matrix.fillna(feature_matrix.median(), inplace=True)
val_feature_matrix.fillna(val_feature_matrix.median(), inplace=True)
test_feature_matrix.fillna(test_feature_matrix.median(), inplace=True)
# Разделение данных на обучающую и тестовую выборки
y_train = feature_matrix['Close']
X_train = feature_matrix.drop('Close', axis=1)
y_test = test_feature_matrix['Close']
X_test = test_feature_matrix.drop('Close', axis=1)
# Обучение модели
model1 = LinearRegression()
#Линейная регрессия — это простая модель, которая пытается установить связь между двумя переменными, рисуя прямую линию на графике.
# Она прогнозирует значение зависимой переменной (Y) на основе одной или нескольких независимых переменных (X).
model2 = DecisionTreeRegressor()
#Это модель, которая принимает решения, дробя данные на «ветви», как дерево. На каждом уровне дерева модель выбирает,
# какой признак (фактор) использовать для разделения данных.
model3 = RandomForestRegressor(n_estimators=100)
#Случайный лес — это ансамблевая модель, которая использует множество деревьев решений.
# Вместо того чтобы полагаться на одно дерево, она комбинирует результаты нескольких деревьев, чтобы получить более точные предсказания.
model4 = Lasso(alpha=0.1)
#Lasso регрессия — это разновидность линейной регрессии с добавлением регуляризации.
# Она помогает избежать переобучения модели, уменьшая влияние некоторых признаков.
model5 = Ridge(alpha=0.1)
#Ridge регрессия похожа на Lasso, но вместо полного исключения некоторых переменных она уменьшает значения всех коэффициентов.
print('\nLinearRegression:')
start_time = time.time()
model1.fit(X_train, y_train)
#Метод fit обучает модель на обучающем наборе данных, состоящем из X_train (набор данных) и y_train (целевая переменная).
# Время обучения модели
train_time = time.time() - start_time
y_predict = model1.predict(X_test)
mse = mean_squared_error(y_test, y_predict, squared=False)
# Этот показатель показывает, насколько в среднем наши предсказания отклоняются от фактических значений. Чем меньше RMSE, тем лучше модель.
r2 = r2_score(y_test, y_predict)
# Коффициент детерминации - показывает, насколько модель объясняет разброс значений в наборе данных
mae = mean_absolute_error(y_test, y_predict)
# Измеряет среднее расстояние между предсказанными значениями и фактическими значениями, игнорируя направление ошибок.
print(f'Коэффициент детерминации R²: {r2:.2f}')
print(f'Время обучения модели: {train_time:.2f} секунд')
print(f'Среднеквадратичная ошибка: {mse:.2f}')
print(f'Средняя абсолютная ошибка: {mae:.2f}')
# Кросс-валидация
scores = cross_val_score(model1, X_train, y_train, cv=5, scoring='neg_mean_squared_error')
rmse_cv = (-scores.mean())**0.5
print(f"Кросс-валидация RMSE: {rmse_cv} \n")
# Здесь мы используем метод cross_val_score для оценки модели с помощью кросс-валидации.
# cv=5 означает, что мы будем разбивать наш обучающий набор на 5 частей (фолдов) и
# использовать каждую часть для тестирования модели, обученной на остальных частях.
# (по сути разбивка на выборки но несколько раз с использованием разных разбиений, чтобы получить норм оценку)
# Параметр scoring='neg_mean_squared_error' говорит о том, что мы хотим получать отрицательные значения среднеквадратичной ошибки,
# так как cross_val_score возвращает лучшие результаты как положительные значения. Таким образом, использование отрицательного
# значения MSE позволяет "перевернуть" метрику так, чтобы более низкие значения (более точные предсказания) приводили
# к более высоким (в терминах абсолютного значения) результатам.
# После этого мы берем среднее значение отрицательной MSE и берем его корень (RMSE)
# для получения усредненной оценки ошибки модели через кросс-валидацию.
# Визуализация результатов
plt.figure(figsize=(10, 6))
plt.scatter(y_test, y_predict, alpha=0.5)
plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], 'k--', lw=2)
plt.xlabel('Фактическая цена')
plt.ylabel('Прогнозируемая цена')
plt.title('Фактическая цена по сравнению с прогнозируемой')
plt.show()
#//////////////////////////
print('\nDecisionTreeRegressor:')
start_time = time.time()
model2.fit(X_train, y_train)
# Время обучения модели
train_time = time.time() - start_time
y_predict = model2.predict(X_test)
mse = mean_squared_error(y_test, y_predict, squared=False)
r2 = r2_score(y_test, y_predict)
mae = mean_absolute_error(y_test, y_predict)
print(f'Коэффициент детерминации R²: {r2:.2f}')
print(f'Время обучения модели: {train_time:.2f} секунд')
print(f'Среднеквадратичная ошибка: {mse:.2f}')
print(f'Средняя абсолютная ошибка: {mae:.2f}')
# Кросс-валидация
scores = cross_val_score(model2, X_train, y_train, cv=5, scoring='neg_mean_squared_error')
rmse_cv = (-scores.mean())**0.5
print(f"Кросс-валидация RMSE: {rmse_cv} \n")
# Анализ важности признаков
feature_importances = model2.feature_importances_
feature_names = X_train.columns
# Визуализация результатов
plt.figure(figsize=(10, 6))
plt.scatter(y_test, y_predict, alpha=0.5)
plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], 'k--', lw=2)
plt.xlabel('Фактическая цена')
plt.ylabel('Прогнозируемая цена')
plt.title('Фактическая цена по сравнению с прогнозируемой')
plt.show()
#//////////////////////////
print('\nRandomForestRegressor:')
start_time = time.time()
model3.fit(X_train, y_train)
# Время обучения модели
train_time = time.time() - start_time
y_predict = model3.predict(X_test)
mse = mean_squared_error(y_test, y_predict, squared=False)
r2 = r2_score(y_test, y_predict)
mae = mean_absolute_error(y_test, y_predict)
print(f'Коэффициент детерминации R²: {r2:.2f}')
print(f'Время обучения модели: {train_time:.2f} секунд')
print(f'Среднеквадратичная ошибка: {mse:.2f}')
print(f'Средняя абсолютная ошибка: {mae:.2f}')
# Кросс-валидация
scores = cross_val_score(model3, X_train, y_train, cv=5, scoring='neg_mean_squared_error')
rmse_cv = (-scores.mean())**0.5
print(f"Кросс-валидация RMSE: {rmse_cv} \n")
# Анализ важности признаков
feature_importances = model3.feature_importances_
feature_names = X_train.columns
# Визуализация результатов
plt.figure(figsize=(10, 6))
plt.scatter(y_test, y_predict, alpha=0.5)
plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], 'k--', lw=2)
plt.xlabel('Фактическая цена')
plt.ylabel('Прогнозируемая цена')
plt.title('Фактическая цена по сравнению с прогнозируемой')
plt.show()
#//////////////////////////
print('\nLasso:')
start_time = time.time()
model4.fit(X_train, y_train)
# Время обучения модели
train_time = time.time() - start_time
y_predict = model4.predict(X_test)
mse = mean_squared_error(y_test, y_predict, squared=False)
r2 = r2_score(y_test, y_predict)
mae = mean_absolute_error(y_test, y_predict)
print(f'Коэффициент детерминации R²: {r2:.2f}')
print(f'Время обучения модели: {train_time:.2f} секунд')
print(f'Среднеквадратичная ошибка: {mse:.2f}')
print(f'Средняя абсолютная ошибка: {mae:.2f}')
# Кросс-валидация
scores = cross_val_score(model4, X_train, y_train, cv=5, scoring='neg_mean_squared_error')
rmse_cv = (-scores.mean())**0.5
print(f"Кросс-валидация RMSE: {rmse_cv} \n")
# Визуализация результатов
plt.figure(figsize=(10, 6))
plt.scatter(y_test, y_predict, alpha=0.5)
plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], 'k--', lw=2)
plt.xlabel('Фактическая цена')
plt.ylabel('Прогнозируемая цена')
plt.title('Фактическая цена по сравнению с прогнозируемой')
plt.show()
#//////////////////////////
print('\nRidge:')
start_time = time.time()
model5.fit(X_train, y_train)
# Время обучения модели
train_time = time.time() - start_time
y_predict = model5.predict(X_test)
print(y_predict)
print(y_test.head())
mse = mean_squared_error(y_test, y_predict, squared=False)
r2 = r2_score(y_test, y_predict)
mae = mean_absolute_error(y_test, y_predict)
print(f'Коэффициент детерминации R²: {r2:.2f}')
print(f'Время обучения модели: {train_time:.2f} секунд')
print(f'Среднеквадратичная ошибка: {mse:.2f}')
print(f'Средняя абсолютная ошибка: {mae:.2f}')
# Кросс-валидация
scores = cross_val_score(model5, X_train, y_train, cv=5, scoring='neg_mean_squared_error')
rmse_cv = (-scores.mean())**0.5
print(f"Кросс-валидация RMSE: {rmse_cv} \n")
# Визуализация результатов
plt.figure(figsize=(10, 6))
plt.scatter(y_test, y_predict, alpha=0.5)
plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], 'k--', lw=2)
plt.xlabel('Фактическая цена')
plt.ylabel('Прогнозируемая цена')
plt.title('Фактическая цена по сравнению с прогнозируемой')
plt.show()
На основании представленных данных можно сделать несколько выводов:
- Общие выводы по точности В данном случае среднеквадратичные ошибки близки или равны нулю, к тому же коэффициент детерминации 1.00 - это говорит либо о том, что модель обучается идеально, либо о том, что модель запомнила значения. Поэтому я проверила ее на нескольких моделях и отдельно вывела для сравнения список предсказанной целевой переменной и тестовую(с которой сравниваем) целевую переменную - результаты оказались весьма близки к тестовым показателям, но не точь в точь, что, скорее всего, говорит о том, что модель все же обучается идеально... Среднеквадратичная ошибка (RMSE) и Средняя абсолютная ошибка (MAE)
- LinearRegression: MAE = 0.04 и RMSE = 0.05 указывает на весьма точные предсказания.
- DecisionTreeRegressor: MAE и RMSE равны 0.00, что может указывать на чрезмерное подстраивание модели к обучающим данным.
- RandomForestRegressor: MAE = 0.02 и RMSE = 0.03 показывают высокую точность прогнозов, но не столь идеальные результаты, как у дерева решений.
- Lasso и Ridge: Обе модели имеют MAE = 0.10 и 0.04 соответственно, что также предполагает приемлемую точность, но с возможностью недопущения переобучения.
- Переобучение модели Высокие значения R² и нулевые ошибки (MAE и RMSE) у DecisionTreeRegressor могут указывать на переобучение модели. Это значит, что модель отлично работает на обучающих данных, но может быть неэффективной на новых, невидимых данных. Для линейной регрессии и других регуляризованных моделей (например, Lasso и Ridge) результаты более сбалансированы, что делает их менее подверженными переобучению.
- Производительность модели Время обучения у моделей варьируется значительно. Например, DecisionTreeRegressor обучается за короткое время (0.31 секунды), в то время как LinearRegression и RandomForestRegressor требуют больше времени. Это может быть критичным для сценариев, требующих частых обновлений модели.
- Соответствие бизнес-целям Учитывая высокую точность модели и ее способность к обучению на исторических данных, можно использовать ее для прогнозирования цен на акции. Однако рекомендуется дополнительно проверять результаты на тестовых данных, чтобы избежать проблем с переобучением.