393 KiB
Начало лабораторной¶
Цены на кофе - https://www.kaggle.com/datasets/mayankanand2701/starbucks-stock-price-dataset
Бизнес-цели
- Предсказание цены закрытия акций (Close) Максимизация прибыли путем предсказания изменения цены акций, что может помочь в принятии инвестиционных решений.
- Предсказание объема торгов (Volume) Оптимизация торговых операций и улучшение ликвидности на рынке, что помогает в планировании запасов и управлении финансовыми потоками.
Технические цели проекта:
Сбор и подготовка данных Разработка подхода для сбора исторических данных о ценах акций и объемах торгов. Обеспечение качества данных: устранение дубликатов, обработка пропусков и аномалий. Факторизация данных: создание дополнительных признаков, таких как скользящие средние, индекс относительной силы (RSI) и другие технические индикаторы.
Выбор и разработка моделей Исследование различных методов машинного обучения и глубокого обучения для предсказания цен закрытия акций и объемов торгов. Проведение сравнительного анализа моделей (например, линейная регрессия, деревья решений, нейронные сети) с использованием метрик, таких как RMSE (корень среднеквадратичной ошибки) и MAE (средняя абсолютная ошибка). Оптимизация гиперпараметров моделей для повышения точности предсказаний.
Валидация и тестирование моделей Разработка стратегий кросс-валидации для оценки производительности моделей на различных временных интервалах и рыночных условиях. Оценка устойчивости моделей к различным сценариям (например, резкие падения или росты рынков).
import pandas as pd
df = pd.read_csv(".//static//csv//Starbucks Dataset.csv")
print(df.columns)
Подготовка данных¶
# Процент пропущенных значений признаков
for i in df.columns:
null_rate = df[i].isnull().sum() / len(df) * 100
if null_rate > 0:
print(f'{i} Процент пустых значений: %{null_rate:.2f}')
# Проверка на пропущенные данные
print(df.isnull().sum())
df.isnull().any()
Пропущенных значений нет.
Разбиение набора данных на обучающую, контрольную и тестовую выборки для устранения проблемы просачивания данных
from sklearn.model_selection import train_test_split
# Разделение данных на обучающую и тестовую выборки (80% - обучение, 20% - тест)
train_data, test_data = train_test_split(df, test_size=0.2, random_state=42)
# Разделение обучающей выборки на обучающую и контрольную (80% - обучение, 20% - контроль)
train_data, val_data = train_test_split(train_data, test_size=0.2, random_state=42)
print("Размер обучающей выборки:", len(train_data))
print("Размер контрольной выборки:", len(val_data))
print("Размер тестовой выборки:", len(test_data))
import pandas as pd
from sklearn.model_selection import train_test_split
# Загрузим данные
df = pd.read_csv(".//static//csv//Starbucks Dataset.csv")
# Определяем целевую переменную и признаки
target_column = 'Close'
X = df.drop(columns=[target_column, 'Date'])
y = df[target_column]
X_train, X_temp, y_train, y_temp = train_test_split(X, y, test_size=0.3, random_state=42)
X_val, X_test, y_val, y_test = train_test_split(X_temp, y_temp, test_size=0.5, random_state=42)
# Создаём DataFrame для каждой выборки
train_data = pd.DataFrame(X_train, columns=X.columns)
train_data['Close'] = y_train.reset_index(drop=True)
val_data = pd.DataFrame(X_val, columns=X.columns)
val_data['Close'] = y_val.reset_index(drop=True)
test_data = pd.DataFrame(X_test, columns=X.columns)
test_data['Close'] = y_test.reset_index(drop=True)
import seaborn as sns
import matplotlib.pyplot as plt
# Гистограмма распределения объема в обучающей выборке
sns.histplot(train_data['Volume'], kde=True)
plt.title('Распределение объема в обучающей выборке')
plt.show()
# Гистограмма распределения объема в контрольной выборке
sns.histplot(val_data['Volume'], kde=True)
plt.title('Распределение объема в контрольной выборке')
plt.show()
# Гистограмма распределения объема в тестовой выборке
sns.histplot(test_data['Volume'], kde=True)
plt.title('Распределение объема в тестовой выборке')
plt.show()
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
from sklearn.utils import resample
# Загрузим данные
df = pd.read_csv(".//static//csv//Starbucks Dataset.csv")
# Определяем целевую переменную и признаки
target_column_close = 'Close'
target_column_volume = 'Volume' # Здесь используем колонку Volume
X = df.drop(columns=[target_column_close, target_column_volume, 'Date'])
y_close = df[target_column_close]
y_volume = df[target_column_volume]
# Сначала разделяем на учебную и временную выборки
X_train, X_temp, y_train_close, y_temp_close = train_test_split(X, y_close, test_size=0.3, random_state=42)
y_train_volume, y_temp_volume = train_test_split(y_volume, test_size=0.3, random_state=42)
# Затем разделяем временную выборку на валидационную и тестовую
X_val, X_test, y_val_close, y_test_close = train_test_split(X_temp, y_temp_close, test_size=0.5, random_state=42)
y_val_volume, y_test_volume = train_test_split(y_temp_volume, test_size=0.5, random_state=42)
# Создаем DataFrame для каждой выборки
train_data = pd.DataFrame(X_train, columns=X.columns)
train_data['Close'] = y_train_close.reset_index(drop=True)
train_data['Volume'] = y_train_volume.reset_index(drop=True)
val_data = pd.DataFrame(X_val, columns=X.columns)
val_data['Close'] = y_val_close.reset_index(drop=True)
val_data['Volume'] = y_val_volume.reset_index(drop=True)
test_data = pd.DataFrame(X_test, columns=X.columns)
test_data['Close'] = y_test_close.reset_index(drop=True)
test_data['Volume'] = y_test_volume.reset_index(drop=True)
# Шаг 1: Оценка сбалансированности выборок с использованием Volume
def plot_distribution(data, column, title):
plt.figure(figsize=(10, 4))
plt.title(f'Distribution of "{column}" for {title}')
data[column].hist(bins=30, color='blue', alpha=0.7)
plt.xlabel(column)
plt.ylabel('Frequency')
plt.grid()
plt.show()
# График для каждой выборки
plot_distribution(train_data, 'Volume', 'Training Data')
plot_distribution(val_data, 'Volume', 'Validation Data')
plot_distribution(test_data, 'Volume', 'Test Data')
# Проверка на сбалансированность
def print_statistics(data, name):
print(f"{name} Statistics for 'Close':")
print(data['Close'].describe())
print(f"{name} Statistics for 'Volume':")
print(data['Volume'].describe())
print("\n")
print_statistics(train_data, 'Training Data')
print_statistics(val_data, 'Validation Data')
print_statistics(test_data, 'Test Data')
# Шаг 2: Применение аугментации данных для 'Volume'
def augment_data(data, target_column, n_samples):
# Увеличим выборку путем бутстреппинга
augmented_data = resample(data, replace=True, n_samples=n_samples, random_state=42)
return augmented_data
# Оценим нужно ли нам увеличение выборки по обучающим данным
if train_data['Volume'].value_counts(normalize=True).max() < 0.75: # 75% как порог
# Допустим, мы хотим увеличить обучающую выборку до 2000 примеров
augmented_train_data = augment_data(train_data, 'Volume', n_samples=2000)
# Объединяем оригинальные и увеличенные данные
train_data = pd.concat([train_data, augmented_train_data]).drop_duplicates().reset_index(drop=True)
print("Аугментация данных успешно применена для обучающей выборки.")
# Проверяем новую распределение после аугментации
plot_distribution(train_data, 'Volume', 'Augmented Training Data')
Обучающая выборка содержит 3935 примеров, валидационная — 199, тестовая — 199. Основные статистики по целевой переменной Close показывают, что значения варьируются от 0.35 до 126.06 с учетом значительного разброса (стандартное отклонение 33.59). Распределение объема торгов в обучающей выборке имеет большой разброс с минимумом в 1.85 миллиона и максимумом в 585.51 миллиона. Среднее значение объема составляет примерно 14.84 миллиона. Это говорит о разнообразии торговой активности.
import pandas as pd
import numpy as np
# Загрузим данные
df = pd.read_csv("./static/csv/Starbucks Dataset.csv")
# Убедимся, что дата в нужном формате
df['Date'] = pd.to_datetime(df['Date'])
# Конструирование признаков
df['Year'] = df['Date'].dt.year
df['Month'] = df['Date'].dt.month
# Условия для создания категорий
categorical_features = ['Year', 'Month']
# Применение one-hot encoding к новым категориальным признакам
train_data_encoded = pd.get_dummies(df, columns=categorical_features)
# Отделяем метки 'Close' и 'Volume'
y_close = df['Close']
y_volume = df['Volume']
X = train_data_encoded.drop(columns=['Date', 'Close', 'Volume', 'Adj Close'])
# Дискретизация числовых признаков (например, 'Close').
# Создадим категории для 'Close' на 5 категорий
train_data_encoded['Close_Category'] = pd.cut(train_data_encoded['Close'], bins=5, labels=False)
# Выводим информацию о столбцах
print("Столбцы train_data_encoded:")
print(train_data_encoded.columns.tolist())
# Пример вывода первых нескольких строк закодированного датафрейма
print("Первые 5 строк закодированного датафрейма:")
print(train_data_encoded.head())
import pandas as pd
# Загружаем данные
df = pd.read_csv(".//static//csv//Starbucks Dataset.csv")
# Проверка на наличие числовых признаков
print("Названия столбцов в датасете:")
print(df.columns)
# Выводим основные статистические параметры для количественных признаков
print("Статистические параметры:")
print(df.describe())
# Дискретизация столбца 'Close' на группы
bins = [0, 50, 100, 150, 200, 250, 300] # Определяем границы корзин
labels = ['0-50', '51-100', '101-150', '151-200', '201-250', '251-300'] # Названия категорий
# Создание нового столбца 'Close_Disc' на основе дискретизации
df['Close_Disc'] = pd.cut(df['Close'], bins=bins, labels=labels, include_lowest=True)
# Проверка результата
print("После дискретизации 'Close':")
print(df[['Close', 'Close_Disc']].head())
# Если нужно, можно повторить дискретизацию для других числовых признаков
import pandas as pd
# Загружаем данные
df = pd.read_csv(".//static//csv//Starbucks Dataset.csv")
# Просмотр первых строк, чтобы понять структуру данных
print("Первые строки данных:")
print(df.head())
# 1. Изменение цены
df['Price_Change'] = df['Close'] - df['Open']
# 2. Процентное изменение
df['Percentage_Change'] = (df['Price_Change'] / df['Open']) * 100
# 3. Средняя цена
df['Average_Price'] = (df['High'] + df['Low'] + df['Open'] + df['Close']) / 4
# 4. Диапазон цены
df['Price_Range'] = df['High'] - df['Low']
# 5. Объем изменений
df['Volume_Impact'] = df['Price_Range'] * df['Volume']
# Проверка создания новых признаков
print("Новые признаки:")
print(df[['Price_Change', 'Percentage_Change', 'Average_Price', 'Price_Range', 'Volume_Impact']].head())
import pandas as pd
from sklearn.preprocessing import MinMaxScaler, StandardScaler
# Шаг 1: Загрузка обработанных данных
df = pd.read_csv(".//static//csv//Starbucks_Dataset_Processed.csv")
# Шаг 2: Признаки для масштабирования
features_to_scale = ['Price_Change', 'Percentage_Change', 'Average_Price', 'Price_Range', 'Volume_Impact']
# Шаг 3: Нормализация (Min-Max Scaling)
min_max_scaler = MinMaxScaler()
df[features_to_scale] = min_max_scaler.fit_transform(df[features_to_scale])
# Шаг 4: Стандартизация (Z-score Scaling)
standard_scaler = StandardScaler()
df[features_to_scale] = standard_scaler.fit_transform(df[features_to_scale])
# Вывод результатов после масштабирования
print("Результаты после масштабирования:")
print(df[features_to_scale].head())
В целом, результаты масштабирования показывают высокую волатильность рынка, однако стабильность в среднем значении цены может указывать на определенные тренды, которые стоит учитывать в дальнейших аналитических прогнозах и торговых стратегиях
import pandas as pd
import featuretools as ft
# Загрузка данных
df = pd.read_csv(".//static//csv//Starbucks Dataset.csv")
print(df.columns)
# Преобразование колонки 'Date' в datetime формат
df['Date'] = pd.to_datetime(df['Date'])
df['id'] = df.index # Добавляем уникальный идентификатор
# Создание сущности
es = ft.EntitySet(id='starbucks_data')
es = es.add_dataframe(dataframe_name='prices', dataframe=df, index='id', make_index=False)
# Конструирование признаков
features, feature_defs = ft.dfs(entityset=es,
target_dataframe_name='prices',
agg_primitives=['count', 'mean', 'sum'],
trans_primitives=['month', 'year', 'weekday'],
verbose=True)
# Печать созданных признаков
print(features.head())
Система смогла успешно сгенерировать 9 признаков, среди которых температуры открытия, закрытия акций, а также даты с разбивкой по месяцам и дням недели. Это может помочь в анализе сезонных и временных закономерностей, но также требует более глубокого исследования для выявления значимых корреляций
import time
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
# Разделение данных на обучающую и валидационную выборки. Удаляем целевую переменную
X = feature_matrix.drop('Volume', axis=1)
y = feature_matrix['Volume']
# One-hot encoding для категориальных переменных (преобразование категориальных объектов в числовые)
X = pd.get_dummies(X, drop_first=True)
# Проверяем, есть ли пропущенные значения, и заполняем их медианой или другим подходящим значением
X.fillna(X.median(), inplace=True)
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42)
# Обучение модели
model = LinearRegression()
# Начинаем отсчет времени
start_time = time.time()
model.fit(X_train, y_train)
# Время обучения модели
train_time = time.time() - start_time
# Предсказания и оценка модели и вычисляем среднеквадратичную ошибку
predictions = model.predict(X_val)
mse = mean_squared_error(y_val, predictions)
print(f'Время обучения модели: {train_time:.2f} секунд')
print(f'Среднеквадратичная ошибка: {mse:.2f}')
Время обучения модели составило 9.94 секунд. Это говорит о том, что процесс обучения с использованием линейной регрессии не является очень затратным по времени
Среднеквадратичная ошибка (MSE) равна 66373961335213.34. Это значение является крайне высоким, что сигнализирует о том, что модель требует дополнительной настройки или улучшения.
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.tree import DecisionTreeRegressor
from sklearn.metrics import r2_score
import matplotlib.pyplot as plt
import seaborn as sns
# Загрузка данных
data = pd.read_csv('.//static//csv//Starbucks Dataset.csv')
# Предварительная обработка
data['Date'] = pd.to_datetime(data['Date']) # Преобразуем в datetime
data.set_index('Date', inplace=True)
# Отбираем признаки и целевую переменную
features = ['Open', 'High', 'Low', 'Volume'] # Исключаем 'Close' из признаков
target = 'Close'
# Разделение данных на обучающую и тестовую выборки
X = data[features]
y = data[target]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Модель линейной регрессии
lin_reg = LinearRegression()
lin_reg.fit(X_train, y_train)
y_pred_lin = lin_reg.predict(X_test)
# Оценка производительности
r2_lin = r2_score(y_test, y_pred_lin)
print("Линейная регрессия:")
print("MSE:", mse_lin)
print("R^2:", r2_lin)
# Модель дерева решений
tree_reg = DecisionTreeRegressor()
tree_reg.fit(X_train, y_train)
y_pred_tree = tree_reg.predict(X_test)
# Оценка производительности
r2_tree = r2_score(y_test, y_pred_tree)
print("\nДерево решений:")
print("MSE:", mse_tree)
print("R^2:", r2_tree)
# Визуализация важности признаков
feature_importance = np.abs(lin_reg.coef_)
features_names = X.columns
importance_df = pd.DataFrame({
'Feature': features_names,
'Importance': feature_importance
}).sort_values(by='Importance', ascending=False)
plt.figure(figsize=(10, 6))
sns.barplot(x='Importance', y='Feature', data=importance_df)
plt.title('Важность признаков (Линейная регрессия)')
plt.show()
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score
import matplotlib.pyplot as plt
import seaborn as sns
# 1. Загрузка данных
data = pd.read_csv('.//static//csv//Starbucks Dataset.csv')
# 2. Предварительная обработка данных
data['Date'] = pd.to_datetime(data['Date'])
data.sort_values('Date', inplace=True)
# 3. Оценка корреляции между признаками и целевой переменной
correlation_matrix = data.corr()
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, fmt='.2f', cmap='coolwarm', square=True)
plt.title('Корреляционная матрица')
plt.show()
# Рассмотрим корреляцию с целевой переменной 'Close'
correlation_with_close = correlation_matrix['Close'].sort_values(ascending=False)
print("Корреляция признаков с 'Close':")
print(correlation_with_close)
# 4. Обучение модели и оценка предсказательной способности
# Выберем признаки для модели: Open, High, Low и Volume
features = data[['Open', 'High', 'Low', 'Volume']]
target = data['Close']
# Разделим данные на обучающую и тестовую выборки
X_train, X_test, y_train, y_test = train_test_split(features, target, test_size=0.2, random_state=42)
# Обучим линейную регрессию
model = LinearRegression()
model.fit(X_train, y_train)
# Предсказание
y_pred = model.predict(X_test)
# Оценка качества
r2 = r2_score(y_test, y_pred)
print("Mean Squared Error:", mse)
print("R^2 Score:", r2)
# Визуализация результатов
plt.figure(figsize=(10, 6))
plt.scatter(y_test, y_pred, 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()
На основании представленных данных о корреляции признаков с целевой переменной 'Close', а также значений Mean Squared Error (MSE), можно сделать несколько важных выводов:
Высокая корреляция признаков
Показатели High, Low, Open и Adj Close имеют крайне высокую положительную корреляцию с целевой переменной Close: High: 0.999931 Low: 0.999930 Open: 0.999858 Adj Close: 0.997965 Это говорит о том, что данные переменные практически линейно зависимы от значения Close. Таким образом, знание значений этих признаков позволяет с высокой степенью уверенности предсказывать значение Close.
Date имеет также значимую корреляцию (0.889680), что может указывать на временную зависимость или тренды в данных по мере их изменений. Volume имеет отрицательную корреляцию (-0.319534) с Close, что может говорить о том, что увеличение объема торгов не всегда приводит к росту цен, хотя эта зависимость слабая.
Эффективность модели Значение Mean Squared Error (MSE) составляет 0.05698523693575633, что указывает на очень низкий уровень ошибок предсказания, подтверждая хорошую точность модели.
Из последнего графика видно, что результаты прогнозируемые и фактические практически совпали. Это говорит о том, что модель будет давать неплохой результат на практике.
На основании проведенного анализа с использованием моделей линейной регрессии и дерева решений на наборе данных Starbucks, можно сделать следующие выводы:
1. Модели и их производительность
Линейная регрессия показала отличные результаты с значениям: MSE (среднеквадратичная ошибка): 0.05698523693575633, что указывает на низкий уровень ошибок предсказания по сравнению с реальными значениями. R² (коэффициент детерминации): 0.999949312678354, что свидетельствует о том, что модель объясняет почти 100% дисперсии целевой переменной, показывая великолепную подгонку данных. Дерево решений также продемонстрировало высокие показатели: MSE: 0.1502495406307128, однако это значение значительно выше, чем у линейной регрессии. R²: 0.999866355793138, что, несмотря на немного менее удачную подгонку по сравнению с линейной регрессией, все еще указывает на очень хорошую соответствие модели данным.
2. Сравнение моделей
Хотя обе модели показали исключительные результаты, линейная регрессия в этом случае оказалась более точной, что можно объяснить ее способностью находить прямые зависимости в данных и меньшей склонностью к переобучению в данном контексте. Деревья решений могут быть полезны для выявления более сложных закономерностей, однако в данной задаче они не продемонстрировали такой же уровень точности.
3. Важность признаков
Результаты визуализации важности признаков, полученные из линейной регрессии, помогают понять, какие из входных переменных (Open, High, Low, Volume) наибольшим образом влияют на целевую переменную (Close). Это может быть полезным для дальнейшего анализа и при принятии бизнес-решений, связанных с управлением и стратегией Starbucks.