Описание проекта
Компании «ГлавРосГосНефть» нужно решить, где бурить новую скважину. Вам предоставлены пробы нефти в трёх регионах: в каждом 10 000 месторождений, где измерили качество нефти и объём её запасов. Постройте модель машинного обучения, которая поможет определить регион, где добыча принесёт наибольшую прибыль. Проанализируйте возможную прибыль и риски техникой Bootstrap.
Шаги для выбора локации:
- В избранном регионе ищут месторождения, для каждого определяют значения признаков;
- Строят модель и оценивают объём запасов;
- Выбирают месторождения с самым высокими оценками значений. Количество месторождений зависит от бюджета компании и стоимости разработки одной скважины;
- Прибыль равна суммарной прибыли отобранных месторождений.
Инструкция по выполнению проекта
- Загрузите и подготовьте данные. Поясните порядок действий.
- Обучите и проверьте модель для каждого региона:
- Разбейте данные на обучающую и валидационную выборки в соотношении 75:25.
- Обучите модель и сделайте предсказания на валидационной выборке.
- Сохраните предсказания и правильные ответы на валидационной выборке.
- Напечатайте на экране средний запас предсказанного сырья и RMSE модели.
- Проанализируйте результаты.
- Подготовьтесь к расчёту прибыли:
- Все ключевые значения для расчётов сохраните в отдельных переменных.
- Рассчитайте достаточный объём сырья для безубыточной разработки новой скважины. Сравните полученный объём сырья со средним запасом в каждом регионе.
- Напишите выводы по этапу подготовки расчёта прибыли.
- Напишите функцию для расчёта прибыли по выбранным скважинам и предсказаниям модели:
- Выберите скважины с максимальными значениями предсказаний.
- Просуммируйте целевое значение объёма сырья, соответствующее этим предсказаниям.
- Рассчитайте прибыль для полученного объёма сырья.
- Посчитайте риски и прибыль для каждого региона:
- Примените технику Bootstrap с 1000 выборок, чтобы найти распределение прибыли.
- Найдите среднюю прибыль, 95%-й доверительный интервал и риск убытков. Убыток — это отрицательная прибыль.
- Напишите выводы: предложите регион для разработки скважин и обоснуйте выбор.
Описание данных
Данные геологоразведки трёх регионов находятся в файлах:
/datasets/geo_data_0.csv
./datasets/geo_data_1.csv
./datasets/geo_data_2.csv
.
Атрибуты:
id
— уникальный идентификатор скважины;f0
,f1
,f2
— три признака точек (неважно, что они означают, но сами признаки значимы);product
— объём запасов в скважине (тыс. баррелей).
Условия задачи
- Для обучения модели подходит только линейная регрессия (остальные — недостаточно предсказуемые).
- При разведке региона исследуют 500 точек, из которых с помощью машинного обучения выбирают 200 лучших для разработки.
- Бюджет на разработку скважин в регионе — 10 млрд рублей.
- При нынешних ценах один баррель сырья приносит 450 рублей дохода. Доход с каждой единицы продукта составляет 450 тыс. рублей, поскольку объём указан в тысячах баррелей.
- После оценки рисков нужно оставить лишь те регионы, в которых вероятность убытков меньше 2.5%. Среди них выбирают регион с наибольшей средней прибылью.
Данные синтетические: детали контрактов и характеристики месторождений не разглашаются.
Оглавление
- 1 Загрузка и подготовка данных
- 2 Обучение и проверка модели
- 3 Подготовка к расчёту прибыли
- 4 Расчёт прибыли и рисков
- 5 Выводы проекта
- 6 Чек-лист готовности проекта
Загрузка и подготовка данных¶
Подготовка тетради и загрузка данных¶
# Базовые библиотеки
import pandas as pd # Датафреймы
import numpy as np # Математика для массивов
from math import factorial # Факториалы
from scipy import stats as st # Статистика
# Pipeline (пайплайн)
from sklearn.pipeline import(
Pipeline, # Pipeline с ручным вводом названий шагов.
make_pipeline # Pipeline с автоматическим названием шагов.
)
# Функция для поддержки экспериментальной функции HavingGridSearchSV
from sklearn.experimental import enable_halving_search_cv
# Ускоренная автоматизация поиска лучших моделей и их параметров
from sklearn.model_selection import HalvingGridSearchCV
# Ускоренная автоматизация рандомного поиска лучших моделей и их параметров
from sklearn.model_selection import HalvingRandomSearchCV
# Автоматизация раздельного декодирования признаков
from sklearn.compose import(
make_column_selector,
make_column_transformer,
ColumnTransformer
)
# Обработка данных для машинного обучения
# Стандартизация данных
import re
from sklearn.preprocessing import(
OneHotEncoder, # Создание отдельных столбцов для каждого категориального значения, drop='first' (удаление первого столбца против dummy-ловушки), sparse=False (?)
OrdinalEncoder, # Кодирование порядковых категориальных признаков
LabelEncoder,
StandardScaler,
MinMaxScaler
)
from sklearn.utils import shuffle # Перемешивание данных для уравновешивания их в разных выборках
from statsmodels.stats.outliers_influence import variance_inflation_factor # Коэффициент инфляции дисперсии (5 и более - признак коррелирует со всеми остальными, его можно удалить и выразить через другие признаки)
from sklearn.model_selection import(
GridSearchCV, # Поиск гиперпараметров по сетке (GridSearch)
train_test_split, # Разделение выборок с целевыми и нецелевыми признаками на обучающую и тестовую
validation_curve,
StratifiedKFold, # Кроссвалидация с указанием количества фолдов (частей, на которые будет разбита обучающая выборка, одна из которых будет участвовать в валидации)
KFold, # Кроссвалидация
cross_val_score # Оценка качества модели на кроссвалидации
)
# Различные модели машинного обучения (в данном проекте требуется регрессия)
# (есть разбор на https://russianblogs.com/article/83691573909/)
# Линейная модель
from sklearn.linear_model import(
#LogisticRegression, # Линейная классификация
LinearRegression, # Линейная регрессия
Ridge , # Линейная регрессия. "Хребтовая" регрессия (метод наименьших квадратов)
BayesianRidge , # Линейная регрессия. Байесовская "хребтовая" регрессия (максимизации предельного логарифмического правдоподобия)
SGDRegressor # Линейная регрессия. SGD - Стохастический градиентный спуск (минимизирует регуляризованные эмпирические потери за счет стохастического градиентного спуска)
)
# Решающее дерево
from sklearn.tree import(
#DecisionTreeClassifier, # Решающее дерево. Классификация
DecisionTreeRegressor # Решающее дерево. Регрессия
)
# Случайный лес
from sklearn.ensemble import(
#RandomForestClassifier, # Случайный лес. Классификация
RandomForestRegressor # Случайный лес. Регрессия
)
# Машина опорных векторов
from sklearn.svm import(
SVR # # Линейная модель. Регрессия с использованием опорных векторов
)
# Нейронная сеть
from sklearn.neural_network import(
MLPRegressor # Нейронная сеть. Регрессия
)
# Метрики (Показатели качества моделей)
from sklearn.metrics import(
# Метрики для моделей регрессии
mean_absolute_error, # MAE, Средняя абсолютная ошибка (не чувствительная к выбросам)
mean_absolute_percentage_error, # MAPE, Средняя абсолютная ошибка в % (универсальная в %)
mean_squared_error, # MSE, Средняя квадратичная ошибка (дисперсия, чувствительная к выбросам), RMSE (сигма) = mean_squared_error(test_y, preds, squared=False)
r2_score, # R^2, Коэффициент детерминации (универсальная в %, чувствительная к выбросам, может быть отрицательной и возвращать NaN)
# Другое
ConfusionMatrixDisplay
)
# Визуализация графиков
import seaborn as sns
import matplotlib
%matplotlib inline
from matplotlib import pyplot as plt
from matplotlib import rcParams, rcParamsDefault
from pandas.plotting import scatter_matrix
# Отображение всех столбцов таблицы
pd.set_option('display.max_columns', None)
# Обязательно для нормального отображения графиков plt
rcParams['figure.figsize'] = 10, 6
%config InlineBackend.figure_format = 'svg'
# Дополнительно и не обязательно для декорирования графиков
factor = .8
default_dpi = rcParamsDefault['figure.dpi']
rcParams['figure.dpi'] = default_dpi * factor
# Чтение файла с датафреймами
try:
geo_data_0 = pd.read_csv('/datasets/geo_data_0.csv')
geo_data_1 = pd.read_csv('/datasets/geo_data_1.csv')
geo_data_2 = pd.read_csv('/datasets/geo_data_2.csv')
except:
geo_data_0 = pd.read_csv('datasets/geo_data_0.csv')
geo_data_1 = pd.read_csv('datasets/geo_data_1.csv')
geo_data_2 = pd.read_csv('datasets/geo_data_2.csv')
Анализ данных датафрейма первого региона¶
# Проверка датафрейма первого региона
print(geo_data_0.info())
geo_data_0.head()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 100000 entries, 0 to 99999 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id 100000 non-null object 1 f0 100000 non-null float64 2 f1 100000 non-null float64 3 f2 100000 non-null float64 4 product 100000 non-null float64 dtypes: float64(4), object(1) memory usage: 3.8+ MB None
id | f0 | f1 | f2 | product | |
---|---|---|---|---|---|
0 | txEyH | 0.705745 | -0.497823 | 1.221170 | 105.280062 |
1 | 2acmU | 1.334711 | -0.340164 | 4.365080 | 73.037750 |
2 | 409Wp | 1.022732 | 0.151990 | 1.419926 | 85.265647 |
3 | iJLyR | -0.032172 | 0.139033 | 2.978566 | 168.620776 |
4 | Xdl7t | 1.988431 | 0.155413 | 4.751769 | 154.036647 |
# Распределение значений датафрейма первого региона
geo_data_0.describe()
f0 | f1 | f2 | product | |
---|---|---|---|---|
count | 100000.000000 | 100000.000000 | 100000.000000 | 100000.000000 |
mean | 0.500419 | 0.250143 | 2.502647 | 92.500000 |
std | 0.871832 | 0.504433 | 3.248248 | 44.288691 |
min | -1.408605 | -0.848218 | -12.088328 | 0.000000 |
25% | -0.072580 | -0.200881 | 0.287748 | 56.497507 |
50% | 0.502360 | 0.250252 | 2.515969 | 91.849972 |
75% | 1.073581 | 0.700646 | 4.715088 | 128.564089 |
max | 2.362331 | 1.343769 | 16.003790 | 185.364347 |
# Соответствие количества уникальных идентификаторов
# и количества объектов в датафрейме первого региона
# (сколько месторождений в регионе)
geo_data_id = len(geo_data_0.id.unique())
if geo_data_id == len(geo_data_0):
print(f'Количество уникальных идентификаторов: {geo_data_id} == Количество объектов {len(geo_data_0)}')
else:
print(f'Количество уникальных идентификаторов: {geo_data_id} < Количество объектов {len(geo_data_0)}')
Количество уникальных идентификаторов: 99990 < Количество объектов 100000
# Анализ объектов с дубликатами в аттрибуте "id"
id = geo_data_0.loc[geo_data_0['id'].duplicated(), 'id'].values
for i in id:
print(geo_data_0.loc[geo_data_0['id'] == i])
print()
id f0 f1 f2 product 931 HZww2 0.755284 0.368511 1.863211 30.681774 7530 HZww2 1.061194 -0.373969 10.430210 158.828695 id f0 f1 f2 product 1364 bxg6G 0.411645 0.856830 -3.653440 73.604260 41724 bxg6G -0.823752 0.546319 3.630479 93.007798 id f0 f1 f2 product 3389 A5aEY -0.039949 0.156872 0.209861 89.249364 51970 A5aEY -0.180335 0.935548 -2.094773 33.020205 id f0 f1 f2 product 1949 QcMuo 0.506563 -0.323775 -2.215583 75.496502 63593 QcMuo 0.635635 -0.473422 0.862670 64.578675 id f0 f1 f2 product 64022 74z30 0.741456 0.459229 5.153109 140.771492 66136 74z30 1.084962 -0.312358 6.990771 127.643327 id f0 f1 f2 product 42529 AGS9W 1.454747 -0.479651 0.683380 126.370504 69163 AGS9W -0.933795 0.116194 -3.655896 19.230453 id f0 f1 f2 product 21426 Tdehs 0.829407 0.298807 -0.049563 96.035308 75715 Tdehs 0.112079 0.430296 3.218993 60.964018 id f0 f1 f2 product 16633 fiKDv 0.157341 1.028359 5.585586 95.817889 90815 fiKDv 0.049883 0.841313 6.394613 137.346586 id f0 f1 f2 product 60140 TtcGQ 0.569276 -0.104876 6.440215 85.350186 92341 TtcGQ 0.110711 1.022689 0.911381 101.318008 id f0 f1 f2 product 89582 bsk9y 0.398908 -0.400253 10.122376 163.433078 97785 bsk9y 0.378429 0.005837 0.160827 160.637302
# Гистограммы числовых данных
# первого региона
geo_data_0.hist();
# Анализ выбросов данных
# первого региона
for i in geo_data_0:
if i != 'id':
geo_data_0.boxplot(column=i);
plt.show()
# Анализ корреляции аттрибутов
# датафрейма первого региона
geo_data_0.corr()
f0 | f1 | f2 | product | |
---|---|---|---|---|
f0 | 1.000000 | -0.440723 | -0.003153 | 0.143536 |
f1 | -0.440723 | 1.000000 | 0.001724 | -0.192356 |
f2 | -0.003153 | 0.001724 | 1.000000 | 0.483663 |
product | 0.143536 | -0.192356 | 0.483663 | 1.000000 |
# Коэффициент инфляции дисперсии аттрибутов третьего региона
# (5 и более - признак коррелирует со всеми остальными признаками
# и его можно удалить и выразить через другие признаки)
geo_data = geo_data_0.select_dtypes('number')#.columns
vif = [variance_inflation_factor(geo_data, i) for i in range(len(geo_data.columns))]
_ = sns.barplot(x=geo_data.columns.tolist(), y=vif)
vif
[1.5342414107959919, 1.275466160458148, 2.0723577504192945, 2.828465688943373]
Анализ данных датафрейма второго региона¶
# Проверка датафрейма второго региона
print(geo_data_1.info())
geo_data_1.head()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 100000 entries, 0 to 99999 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id 100000 non-null object 1 f0 100000 non-null float64 2 f1 100000 non-null float64 3 f2 100000 non-null float64 4 product 100000 non-null float64 dtypes: float64(4), object(1) memory usage: 3.8+ MB None
id | f0 | f1 | f2 | product | |
---|---|---|---|---|---|
0 | kBEdx | -15.001348 | -8.276000 | -0.005876 | 3.179103 |
1 | 62mP7 | 14.272088 | -3.475083 | 0.999183 | 26.953261 |
2 | vyE1P | 6.263187 | -5.948386 | 5.001160 | 134.766305 |
3 | KcrkZ | -13.081196 | -11.506057 | 4.999415 | 137.945408 |
4 | AHL4O | 12.702195 | -8.147433 | 5.004363 | 134.766305 |
# Распределение значений датафрейма второго региона
geo_data_1.describe()
f0 | f1 | f2 | product | |
---|---|---|---|---|
count | 100000.000000 | 100000.000000 | 100000.000000 | 100000.000000 |
mean | 1.141296 | -4.796579 | 2.494541 | 68.825000 |
std | 8.965932 | 5.119872 | 1.703572 | 45.944423 |
min | -31.609576 | -26.358598 | -0.018144 | 0.000000 |
25% | -6.298551 | -8.267985 | 1.000021 | 26.953261 |
50% | 1.153055 | -4.813172 | 2.011479 | 57.085625 |
75% | 8.621015 | -1.332816 | 3.999904 | 107.813044 |
max | 29.421755 | 18.734063 | 5.019721 | 137.945408 |
# Соответствие количества уникальных идентификаторов
# и количества объектов в датафрейме второго региона
# (сколько месторождений в регионе)
geo_data_id = len(geo_data_1.id.unique())
if geo_data_id == len(geo_data_1):
print(f'Количество уникальных идентификаторов: {geo_data_id} == Количество объектов {len(geo_data_1)}')
else:
print(f'Количество уникальных идентификаторов: {geo_data_id} < Количество объектов {len(geo_data_1)}')
Количество уникальных идентификаторов: 99996 < Количество объектов 100000
# Анализ объектов с дубликатами в аттрибуте "id"
id = geo_data_1.loc[geo_data_1['id'].duplicated(), 'id'].values
for i in id:
print(geo_data_1.loc[geo_data_1['id'] == i])
print()
id f0 f1 f2 product 1305 LHZR0 11.170835 -1.945066 3.002872 80.859783 41906 LHZR0 -8.989672 -4.286607 2.009139 57.085625 id f0 f1 f2 product 2721 bfPNe -9.494442 -5.463692 4.006042 110.992147 82178 bfPNe -6.202799 -4.820045 2.995107 84.038886 id f0 f1 f2 product 47591 wt4Uk -9.091098 -8.109279 -0.002314 3.179103 82873 wt4Uk 10.259972 -9.376355 4.994297 134.766305 id f0 f1 f2 product 5849 5ltQ6 -3.435401 -12.296043 1.999796 57.085625 84461 5ltQ6 18.213839 2.191999 3.993869 107.813044
# Гистограммы числовых данных
# второго региона
geo_data_1.hist();
# Анализ выбросов данных
# второго региона
for i in geo_data_1:
if i != 'id':
geo_data_0.boxplot(column=i);
plt.show()
# Анализ корреляции аттрибутов
# датафрейма второго региона
geo_data_1.corr()
f0 | f1 | f2 | product | |
---|---|---|---|---|
f0 | 1.000000 | 0.182287 | -0.001777 | -0.030491 |
f1 | 0.182287 | 1.000000 | -0.002595 | -0.010155 |
f2 | -0.001777 | -0.002595 | 1.000000 | 0.999397 |
product | -0.030491 | -0.010155 | 0.999397 | 1.000000 |
# Коэффициент инфляции дисперсии аттрибутов второго региона
# (5 и более - признак коррелирует со всеми остальными признаками
# и его можно удалить и выразить через другие признаки)
geo_data = geo_data_1.select_dtypes('number')#.columns
vif = [variance_inflation_factor(geo_data, i) for i in range(len(geo_data.columns))]
_ = sns.barplot(x=geo_data.columns.tolist(), y=vif)
vif
[1.9873199714997407, 1.8282400357569757, 4660.924528638744, 4692.5535256238845]
Анализ данных датафрейма третьего региона¶
# Проверка датафрейма третьего региона
print(geo_data_2.info())
geo_data_2.head()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 100000 entries, 0 to 99999 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id 100000 non-null object 1 f0 100000 non-null float64 2 f1 100000 non-null float64 3 f2 100000 non-null float64 4 product 100000 non-null float64 dtypes: float64(4), object(1) memory usage: 3.8+ MB None
id | f0 | f1 | f2 | product | |
---|---|---|---|---|---|
0 | fwXo0 | -1.146987 | 0.963328 | -0.828965 | 27.758673 |
1 | WJtFt | 0.262778 | 0.269839 | -2.530187 | 56.069697 |
2 | ovLUW | 0.194587 | 0.289035 | -5.586433 | 62.871910 |
3 | q6cA6 | 2.236060 | -0.553760 | 0.930038 | 114.572842 |
4 | WPMUX | -0.515993 | 1.716266 | 5.899011 | 149.600746 |
# Распределение значений датафрейма третьего региона
geo_data_2.describe()
f0 | f1 | f2 | product | |
---|---|---|---|---|
count | 100000.000000 | 100000.000000 | 100000.000000 | 100000.000000 |
mean | 0.002023 | -0.002081 | 2.495128 | 95.000000 |
std | 1.732045 | 1.730417 | 3.473445 | 44.749921 |
min | -8.760004 | -7.084020 | -11.970335 | 0.000000 |
25% | -1.162288 | -1.174820 | 0.130359 | 59.450441 |
50% | 0.009424 | -0.009482 | 2.484236 | 94.925613 |
75% | 1.158535 | 1.163678 | 4.858794 | 130.595027 |
max | 7.238262 | 7.844801 | 16.739402 | 190.029838 |
# Соответствие количества уникальных идентификаторов
# и количества объектов в датафрейме третьего региона
# (сколько месторождений в регионе)
geo_data_id = len(geo_data_2.id.unique())
if geo_data_id == len(geo_data_2):
print(f'Количество уникальных идентификаторов: {geo_data_id} == Количество объектов {len(geo_data_2)}')
else:
print(f'Количество уникальных идентификаторов: {geo_data_id} < Количество объектов {len(geo_data_2)}')
Количество уникальных идентификаторов: 99996 < Количество объектов 100000
# Анализ объектов с дубликатами в аттрибуте "id"
id = geo_data_2.loc[geo_data_2['id'].duplicated(), 'id'].values
for i in id:
print(geo_data_2.loc[geo_data_2['id'] == i])
print()
id f0 f1 f2 product 28039 xCHr8 1.633027 0.368135 -2.378367 6.120525 43233 xCHr8 -0.847066 2.101796 5.597130 184.388641 id f0 f1 f2 product 11449 VF7Jo 2.122656 -0.858275 5.746001 181.716817 49564 VF7Jo -0.883115 0.560537 0.723601 136.233420 id f0 f1 f2 product 45404 KUPhW 0.231846 -1.698941 4.990775 11.716299 55967 KUPhW 1.211150 3.176408 5.543540 132.831802 id f0 f1 f2 product 44378 Vcm5J -1.229484 -2.439204 1.222909 137.968290 95090 Vcm5J 2.587702 1.986875 2.482245 92.327572
# Гистограммы числовых данных
# третьего региона
geo_data_2.hist();
# Анализ выбросов данных
# третьего региона
for i in geo_data_2:
if i != 'id':
geo_data_0.boxplot(column=i);
plt.show()
# Анализ корреляции аттрибутов
# датафрейма третьего региона
geo_data_2.corr()
f0 | f1 | f2 | product | |
---|---|---|---|---|
f0 | 1.000000 | 0.000528 | -0.000448 | -0.001987 |
f1 | 0.000528 | 1.000000 | 0.000779 | -0.001012 |
f2 | -0.000448 | 0.000779 | 1.000000 | 0.445871 |
product | -0.001987 | -0.001012 | 0.445871 | 1.000000 |
# Коэффициент инфляции дисперсии аттрибутов третьего региона
# (5 и более - признак коррелирует со всеми остальными признаками
# и его можно удалить и выразить через другие признаки)
geo_data = geo_data_2.select_dtypes('number')#.columns
vif = [variance_inflation_factor(geo_data, i) for i in range(len(geo_data.columns))]
_ = sns.barplot(x=geo_data.columns.tolist(), y=vif)
vif
[1.0000003784119968, 1.0000043346184886, 1.8701231139033965, 1.870127315208737]
Выводы из анализа данных датафреймов всех регионов¶
Предварительный анализ данных выявил следующее:
- Все три датафрейма содержат один текстовый оттрибут с идентификаторами скважин, а также три количественных числовых нецелевых аттрибута и один количественный числовой целевой аттрибут.
- В данных отсутствуют пропуски.
- Выявлены неявные дубликаты. Во всех датафреймах обнаружены от 6 до 10 объектов (скважин) с неуникальными идентификаторами в аттрибуте
id
. Все значения аттрибутов этих дубликатов значительно отличаются друг от друга. Учитывая отсутствие возможности выяснить причину наличия схожих идентификаторов и разницы значений их аттрибутов, предлагается оставить данные объекты. - Аттрибут
id
с идентификаторами скважин имеет типObject
и около ста тысяч уникальных значений, за исключением 6-10 объектов, почти равных по количеству количеству всех объектов датафреймов. Для использования данного аттрибута в моделях, требуется его декодирование в числовые значения. Для линейной модели регрессии не подходит декодирование с помощьюLabelEncoder
, а с помощью OHE его декодировать накладно. Поэтому в модели линейной регрессии его использовать не желательно. - Аттрибуты
f0
иf1
датафрейма с объектами первого региона, а такжеf0
,f2
иproduct
датафрейма с объектами второго региона не имеют нормального распределения. При этом, данные аттрибуты имеют значительные всплески вдали от основного центра распределения. Это нужно учесть при работе с моделями. - Аттрибут
f2
датафреймов всех регионов имеет множество выбросов. Т.к. смысл данного атрибута не известен, предлагается не удалять объекты с выбросами данных в нем. - Числовые нецелевые аттрибуты имеют близкие, но не достаточно схожие диапазоны значений. Требуется их стандартизировать.
- В датасетах разных регионов разные аттрибуты по разному коррелируют между собой. Так, в первом регионе обладают ниже средней прямой корреляцией признаки
f0
сf1
иf2
сproduct
, а в третьем регионе толькоf2
сproduct
. Близкой к максимальной прямой корреляции в размере 0.999397 обладают аттрибутыf2
сproduct
во втором регионе. Коэффициент инфляции дисперсии этих двух аттрибутов близкий к 5, но не превышает это значение. Не смотря на практику удаления признаков из датафреймов в подобных случаях, аттрибутf2
не следует удалять из датафрейма, т.к. сильная корреляция с целевым признаком позволяет более точно предсказывать этот самый целевой признак.
Данные готовы для использования в машинном обучении перед которым требуется стандартизировать нецелевые числовые аттрибуты.
Обучение и проверка модели¶
Для достижения целей проекта требуется использовать регрессионную модель с кроссвалидацией, имеющей наилучшие показатели качества. В качестве средств автоматизации будет использованы технологии Pipline
и GridSearchSV
. Перед подбором параметров датафреймы будут разделены на целевые и нецелевые, а также на обучающие и треннировочные выборки в соотношении 75% и 25%. Обучающие выборки в рамках GridSearchSV
будут разделены для кроссвалидации на обучающие и валидационные выборки в соотношении 75% и 25%. Аттрибут с идентификаторами будет исключен из всех выборок, а нецелевые числовые аттрибуты будут стандартизированы.
Полезные функции подготовки данных и подбора моделей и их параметров¶
# Значение "random_state"
STATE = 42
# Массив лучших параметров моделей
# всех регионов
total_best_params = []
# Массив лучших результатов
# всех регионов
total_results= []
# Функция подготовки данных перед подбором моделей и их параметров
def data_preprocessing(geo_data):
# Разделение датафреймов на целевую и нецелевую выборку
features = geo_data.drop(['id', 'product'], axis=1)
target = geo_data['product']
# Значение "random_state"
state = 42
# Разделение целевой и нецелевой выборки
# на обучающие и тестовые выборки
features_train, features_test, target_train, target_test = train_test_split(
features,
target,
test_size=.25,
random_state=state
)
return features_train, features_test, target_train, target_test
# Функция создания структуры пайплайна
def params_and_model_selection(
features_train,
features_test,
target_train,
target_test,
model_params
):
# Значение "random_state"
state = 42
# Удаление аттрибута "id"
#features_train = features_train.drop('id', axis=1)
# Стандартизация числовых значений
numeric_transformer = make_pipeline(
StandardScaler()
)
# Шаг препроцессинга в Пайплайне
preprocessor = make_column_transformer(
(numeric_transformer, features_train.columns)
)
# Pipeline
pipe = Pipeline([
('preprocessor', preprocessor),
('regressor', model_params[0]['regressor'][0])
])
pipe.fit(features_train, target_train)
# HalvingGridSearchCV
# (о подборе оптимальных параметров:
# https://scikit-learn.ru/3-2-tuning-the-hyper-parameters-of-an-estimator/)
grid = HalvingGridSearchCV(
pipe,
model_params,
cv=4, # параметр KFold для кроссвалидации (обучющая и валидационная выборки 75:25)
n_jobs=-1, # количество параллельно выполняемых заданий (-1 - задействованы все процессоры)
# если scoring=mean_squared_error, то в результате "nan"
scoring='neg_root_mean_squared_error', # корень из средней квадратичной ошибки (чувствительна к выбросам, в результате реальные, а не квадратные значения)
random_state=state
)
grid.fit(features_train, target_train)
return grid
Модель машинного обучения для первого региона¶
Подготовка выборок из датафрейма с данными первого региона¶
# Подготовка выборок из датафрейма
# с данными первого региона
features_0_train, features_0_test, target_0_train, target_0_test = data_preprocessing(geo_data_0)
features_0_test.head()
f0 | f1 | f2 | |
---|---|---|---|
75721 | 0.599283 | -0.557623 | 2.121187 |
80184 | 0.739017 | -0.463156 | -1.347584 |
19864 | 1.422743 | -0.534917 | 3.718798 |
76699 | 1.580244 | -0.238458 | 2.805149 |
92991 | 0.918974 | 0.023961 | 2.598575 |
Модель LinearRegression с использованием технологий Pipline и GridSearchSV для первого региона¶
# LinearRegression
grids_0 = params_and_model_selection(
features_0_train,
features_0_test,
target_0_train,
target_0_test,
[{
'regressor': [LinearRegression()], # score: R^2
'regressor__fit_intercept': [True, False]
}]
)
print('Модель :', 'LinearRegression')
print('RMSE :', grids_0.best_score_)
print('Параметры:\n', grids_0.best_estimator_)
Модель : LinearRegression RMSE : -37.67176560084854 Параметры: Pipeline(steps=[('preprocessor', ColumnTransformer(transformers=[('pipeline', Pipeline(steps=[('standardscaler', StandardScaler())]), Index(['f0', 'f1', 'f2'], dtype='object'))])), ('regressor', LinearRegression())])
Предсказания для первого региона¶
# Предсказания для первого региона
#predict_0 = grids_0.predict(features_0_test.drop('id', axis=1))
predict_0 = grids_0.predict(features_0_test)
total_results.append([
1,
predict_0.mean(),
mean_squared_error(target_0_test, predict_0, squared=False)
])
print('Регион :', total_results[0][0])
print('Средний предсказанный запас сырья:', total_results[0][1])
print('RMSE :', total_results[0][2])
Регион : 1 Средний предсказанный запас сырья: 92.39879990657768 RMSE : 37.75660035026169
Модель машинного обучения для второго региона¶
Подготовка выборок из датафрейма с данными второго региона¶
# Подготовка выборок из датафрейма
# с данными второго региона
features_1_train, features_1_test, target_1_train, target_1_test = data_preprocessing(geo_data_1)
#features_1_train = features_1_train.drop('f2', axis=1)
#features_1_test = features_1_test.drop('f2', axis=1)
features_1_train.head()
f0 | f1 | f2 | |
---|---|---|---|
98980 | 9.296378 | -4.480220 | 1.999544 |
69824 | 15.836796 | 1.952969 | 4.989288 |
9928 | -1.594937 | -0.407367 | 3.003358 |
75599 | 12.335752 | -6.508978 | 2.001396 |
95621 | -4.954638 | -8.026328 | 1.001472 |
Модель LinearRegression с использованием технологий Pipline и GridSearchSV для второго региона с аттрибутом f2
, сильно коррелирующим с целевым признаком¶
# LinearRegression
grids_1 = params_and_model_selection(
features_1_train,
features_1_test,
target_1_train,
target_1_test,
[{
'regressor': [LinearRegression()], # score: R^2
'regressor__fit_intercept': [True, False]
}]
)
print('Модель :', 'LinearRegression')
print('RMSE :', grids_1.best_score_)
print('Параметры:\n', grids_1.best_estimator_)
Модель : LinearRegression RMSE : -0.8904633868175174 Параметры: Pipeline(steps=[('preprocessor', ColumnTransformer(transformers=[('pipeline', Pipeline(steps=[('standardscaler', StandardScaler())]), Index(['f0', 'f1', 'f2'], dtype='object'))])), ('regressor', LinearRegression())])
Предсказания для второго региона¶
# Предсказания для второго региона
#predict_1 = grids_1.predict(features_1_test.drop('id', axis=1))
predict_1 = grids_1.predict(features_1_test)
total_results.append([
2,
predict_1.mean(),
mean_squared_error(target_1_test, predict_1, squared=False)
])
print('Регион :', total_results[0][0])
print('Средний предсказанный запас сырья:', total_results[0][1])
print('RMSE :', total_results[0][2])
Регион : 1 Средний предсказанный запас сырья: 92.39879990657768 RMSE : 37.75660035026169
Модель машинного обучения для третьего региона¶
Подготовка выборок из датафрейма с данными третьего региона¶
# Подготовка выборок из датафрейма
# с данными третьего региона
features_2_train, features_2_test, target_2_train, target_2_test = data_preprocessing(geo_data_2)
features_2_test.head()
f0 | f1 | f2 | |
---|---|---|---|
75721 | 2.111118 | -1.679773 | 3.112240 |
80184 | 0.734759 | 0.747788 | 3.670879 |
19864 | -2.513109 | 0.844631 | -4.922889 |
76699 | -2.035301 | -1.522988 | 5.072839 |
92991 | 2.744145 | 1.429952 | -1.372661 |
Модель LinearRegression с использованием технологий Pipline и GridSearchSV для третьего региона¶
# Модель LinearRegression с использованием
# технологий Pipline и GridSearchSV для третьего региона
grids_2 = params_and_model_selection(
features_2_train,
features_2_test,
target_2_train,
target_2_test,
[{
'regressor': [LinearRegression()], # score: R^2
'regressor__fit_intercept': [True, False]
}]
)
print('Модель :', 'LinearRegression')
print('RMSE :', grids_2.best_score_)
print('Параметры:\n', grids_2.best_estimator_)
Модель : LinearRegression RMSE : -40.028747787073236 Параметры: Pipeline(steps=[('preprocessor', ColumnTransformer(transformers=[('pipeline', Pipeline(steps=[('standardscaler', StandardScaler())]), Index(['f0', 'f1', 'f2'], dtype='object'))])), ('regressor', LinearRegression())])
Предсказания для третьего региона¶
# Предсказания для третьего региона
#predict_2 = grids_2.predict(features_2_test.drop('id', axis=1))
predict_2 = grids_2.predict(features_2_test)
total_results.append([
3,
predict_2.mean(),
mean_squared_error(target_2_test, predict_2, squared=False)
])
print('Регион :', total_results[0][0])
print('Средний предсказанный запас сырья:', total_results[0][1])
print('RMSE :', total_results[0][2])
Регион : 1 Средний предсказанный запас сырья: 92.39879990657768 RMSE : 37.75660035026169
Анализ результатов обучения и проверки моделей¶
# Сводная таблица результатов моделей всех регионов
total_results = pd.DataFrame(data=total_results, columns=['region', 'average', 'rmse'])
total_results['minus_3_sigma'] = total_results['average'] - total_results['rmse']*3
total_results['minus_1_sigma'] = total_results['average'] - total_results['rmse']
total_results['plus_1_sigma'] = total_results['average'] + total_results['rmse']
total_results['plus_3_sigma'] = total_results['average'] + total_results['rmse']*3
total_results.rename(columns = {
'region':'Регион',
'average':'Среднее значение',
'rmse':'RMSE',
'minus_3_sigma':'-3 sigma',
'minus_1_sigma':'-1 sigma',
'plus_1_sigma':'+1 sigma',
'plus_3_sigma':'+3 sigma'
})
Регион | Среднее значение | RMSE | -3 sigma | -1 sigma | +1 sigma | +3 sigma | |
---|---|---|---|---|---|---|---|
0 | 1 | 92.398800 | 37.756600 | -20.871001 | 54.642200 | 130.155400 | 205.668601 |
1 | 2 | 68.712878 | 0.890280 | 66.042038 | 67.822598 | 69.603158 | 71.383718 |
2 | 3 | 94.771024 | 40.145872 | -25.666593 | 54.625152 | 134.916896 | 215.208641 |
Выводы:
- Третий регион имеет наиболее высокое предсказанное среднее значение целевого аттрибута
product
(предполагаемый объем запасов нефти), равное 95. В этом же регионе самый большое значение корня из средней квадратичной ошибки (RMSE
, далее $\sigma$) предсказаний аттрибутаproduct
, равное 40. - Во втором регионе самое низкое значение целевого аттрибута
product
, равное 69. Это почти на четверть меньше, чем в других регионах. При этом, во втором регионе самое низкое значение разброса $\sigma$ предсказаний аттрибутаproduct
. Оно равно 0.89, что почти в 40 раз меньше, чем в других регионах. Благодаря минимальному отклонению предсказанных значений от реальных, второй регион наиболее предсказуемый. В нем самые большие значения на нижней границе одной $\sigma$ и нет отрицательных значений на нижней границе трех $\sigma$. - В первом и втором регионах нижняя граница трех $\sigma$ находится существенно ниже 0. Возможно, это следует трактовать как наличие некоторого количества скважин без нефти.
Предварительно можно утверждать, что, по результатам предсказаний моделей линейной регрессии, второй регион является самым предсказуемым, но низкодоходным, а третий самым доходным и рискованным.
Подготовка к расчёту прибыли¶
Константы проекта¶
# Константы проекта
OBJECTS_UNDER_STUDY_NUM = 500 # количество исследуемых объектов, участвующих в обучении модели
BEST_OBJECTS_NUM = 200 # количество лучших объектов в регионе, которые будут разработаны (из 500 оцененных)
TOTAL_BUDGET = 10000000000 # бюджет проекта для одного региона
REVENUE_PER_UNIT = 450000 # доходность одной единицы продукта (= 450 за баррель * 1000 баррелей в одной единице объекта "total")
MAX_PROBABILITY_OF_LOSS = .025 # максимально допустимая веросятность убытков
Точка безубыточности¶
# Точка безубыточности равна частному
# от деления всего бюджета проекта для одного региона
# и стоисомти 1000 баррелей нефти,
# деленные на количество разрабатываемых скважин
# в рамках одного региона
break_event_point = (TOTAL_BUDGET / REVENUE_PER_UNIT) / BEST_OBJECTS_NUM
print(f'Точка безубыточности проекта равна добыче {break_event_point} баррелей в одной из 200 скважин одного региона.')
Точка безубыточности проекта равна добыче 111.11111111111111 баррелей в одной из 200 скважин одного региона.
# Разница между объемами добычи,
# требуемыми для безубыточности проекта
# и средними значениями предсказанных
# объемов добычи нефти во всех скважинах
# разных регионов
total_results['average'] - break_event_point
0 -18.712311 1 -42.398233 2 -16.340087 Name: average, dtype: float64
Во всех регионах средние предсказанные значения объемов добычи нефти в одной сважине меньше минимум на 16 тыс. баррелей, чем требуется для безубыточности проекта.
# Разница между объемами добычи,
# требуемыми для безубыточности проекта
# и средними значениями объемов добычи нефти
# в 200 скважинах с предсказанным
# максимальным объемом в разных регионов
print('Разница между объемами добычи, \
требуемыми для безубыточности проекта и средними значениями \
объемов добычи нефти в 200 скважинах с предсказанным \
максимальным объемом в разных регионов.\n')
print('Первый регион:', pd.Series(predict_0).sort_values(ascending=False).head(200).mean() - break_event_point)
print('Второй регион:', pd.Series(predict_1).sort_values(ascending=False).head(200).mean() - break_event_point)
print('Третий регион:', pd.Series(predict_2).sort_values(ascending=False).head(200).mean() - break_event_point)
Разница между объемами добычи, требуемыми для безубыточности проекта и средними значениями объемов добычи нефти в 200 скважинах с предсказанным максимальным объемом в разных регионов. Первый регион: 43.29620532962386 Второй регион: 27.632645722212004 Третий регион: 37.53312793016613
Средние значения 200 скважин с максимальным предсказанным объемом нефти во всех регионах превышают точку безубыточности. Лидирует первый регион с превышением в 43 тыс. баррелей.
Итоги подготовки к расчету прибыли¶
Для безубыточности проекта минимальный объем добычи в одной скважине должен быть не менее 111 тыс. баррелей. Средние же значения предсказанных объемов добычи в одной скважине всех регионов меньше этого значения минимум на 16 тыс. баррелей. Однако, средние значения 200 скважин с максимальным предсказанным объемом нефти во всех регионах превышают точку безубыточности. Лидирует первый регион.
Функция расчета прибыли¶
# Функция расчета прибыли
def profit_calc(df):
# df = columns 'id', 'product', 'predict'
# Выбор 200 скважин в первом регионе
# с максимальным предсказанным значением
# запасов нефти
df_200 = df.sort_values(
by='predict',
ascending=False
).head(BEST_OBJECTS_NUM)
# Расчет прибыли целевых значений,
# соответствующих 200 предсказаниям
# с максимальными значениями запасов нефти
return (df_200['product'].sum() * REVENUE_PER_UNIT) - TOTAL_BUDGET
Расчёт прибыли и рисков¶
Функция расчета доходов и убытков¶
# Функция расчета доходов и убытков
def profit_and_loss(target_test, predict):
# Создание единого датафрейма
df = pd.DataFrame({
'product':target_test.values,
'predict':predict
})
# Значение для random_state
state = np.random.RandomState(42)
# Массив доходности после применения техники bootstrep
profit_bootstrep = []
for i in range(0, 1000, 1):
df_sample = df.sample(
n=500,
replace=True,
random_state=state
)
profit_bootstrep.append(profit_calc(df_sample))
profit_bootstrep = pd.Series(profit_bootstrep)
# Средний доход
mean_profit = int(profit_bootstrep.mean())
# Нижний квантиль 2.5% от 95% доверительного интервала
lower_profit = int(profit_bootstrep.quantile(.025))
# Верхний квантиль 97.5% от 95% доверительного интервала
upper_profit = int(profit_bootstrep.quantile(.975))
# Средний убыток
#mean_loss = int(profit_bootstrep[profit_bootstrep < 0].mean())
# Вероятность убытка
#probability_of_loss = len(profit_bootstrep[profit_bootstrep < 0]) / len(profit_bootstrep)
probability_of_loss = (profit_bootstrep.values < 0).mean()
print(f'95% доверительный интервал доходности находится в пределах от {lower_profit} до {upper_profit}')
print('Средний доход :', mean_profit)
#print('Средний убыток :', mean_loss)
print(f'Вероятность убытка: {probability_of_loss*100}%')
return mean_profit, probability_of_loss
# Массив средней доходности и вероянтости убытков
# mppl - mean profit and probability of loss
mean_profits = []
probabilities_of_losses = []
Расчет ожидаемой средней прибыли и вероятности убытков¶
# Первый регион
mean_profit, probabilitу_of_loss = profit_and_loss(target_0_test, predict_0)
mean_profits.append(mean_profit)
probabilities_of_losses.append(probabilitу_of_loss)
95% доверительный интервал доходности находится в пределах от -110467895 до 897460327 Средний доход : 399575478 Вероятность убытка: 6.0%
# Второй регион
mean_profit, probabilitу_of_loss = profit_and_loss(target_1_test, predict_1)
mean_profits.append(mean_profit)
probabilities_of_losses.append(probabilitу_of_loss)
95% доверительный интервал доходности находится в пределах от 61684479 до 845340177 Средний доход : 452048890 Вероятность убытка: 1.5%
# Третий регион
mean_profit, probabilitу_of_loss = profit_and_loss(target_2_test, predict_2)
mean_profits.append(mean_profit)
probabilities_of_losses.append(probabilitу_of_loss)
95% доверительный интервал доходности находится в пределах от -144766727 до 888390403 Средний доход : 375009902 Вероятность убытка: 8.0%
Выводы расчета прибыли и рисков¶
# Выводы расчета прибыли и рисков
mppl_df = pd.DataFrame(
{
'profit':mean_profits,
'probability_of_loss':probabilities_of_losses
}
)
mppl_df = mppl_df.loc[mppl_df['probability_of_loss'] < MAX_PROBABILITY_OF_LOSS]
mppl_df = mppl_df.loc[mppl_df['profit'] == mppl_df['profit'].max()]
num = {1:'первый', 2:'второй', 3:'третий'}
print(f"Для инвестирования предлагается выбрать {num[mppl_df.index[0]]} регион. \
В этом регионе ожидаемая доходность {mppl_df.loc[mppl_df.index[0], 'profit']}, \
а верояность убытка {mppl_df.loc[mppl_df.index[0], 'probability_of_loss']*100}%. \
Среди регионов с вероятностью убытка, находящегося \
в пределах допустимого значения {MAX_PROBABILITY_OF_LOSS*100}%, \
в данном регионе самая высокая ожидаемая прибыль.")
Для инвестирования предлагается выбрать первый регион. В этом регионе ожидаемая доходность 452048890, а верояность убытка 1.5%. Среди регионов с вероятностью убытка, находящегося в пределах допустимого значения 2.5%, в данном регионе самая высокая ожидаемая прибыль.
Выводы проекта¶
Цели проекта достигнуты. Предложен регион для инвестирования. Выбор региона обоснован. Для достижения целей проекта были решены следующие задачи:
- Загружены и подготовлены данные. Поясните порядок действий.
- Обучены и проверены модели для каждого региона
- Проведена подготовка к расчёту прибыли:
- Рассчитаны риски и прибыль для каждого региона.
Чек-лист готовности проекта¶
Поставьте ‘x’ в выполненных пунктах. Далее нажмите Shift+Enter.
- [x] Jupyter Notebook открыт
- [x] Весь код выполняется без ошибок
- [x] Ячейки с кодом расположены в порядке исполнения
- [x] Выполнен шаг 1: данные подготовлены
- [x] Выполнен шаг 2: модели обучены и проверены
- [x] Данные корректно разбиты на обучающую и валидационную выборки
- [x] Модели обучены, предсказания сделаны
- [x] Предсказания и правильные ответы на валидационной выборке сохранены
- [x] На экране напечатаны результаты
- [x] Сделаны выводы
- [x] Выполнен шаг 3: проведена подготовка к расчёту прибыли
- [x] Для всех ключевых значений созданы константы Python
- [x] Посчитано минимальное среднее количество продукта в месторождениях региона, достаточное для разработки
- [x] По предыдущему пункту сделаны выводы
- [x] Написана функция расчёта прибыли
- [x] Выполнен шаг 4: посчитаны риски и прибыль
- [x] Проведена процедура Bootstrap
- [x] Все параметры бутстрепа соответствуют условию
- [x] Найдены все нужные величины
- [x] Предложен регион для разработки месторождения
- [x] Выбор региона обоснован