Описание проекта
Компании «ГлавРосГосНефть» нужно решить, где бурить новую скважину. Вам предоставлены пробы нефти в трёх регионах: в каждом 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 Чек-лист готовности проекта
Загрузка и подготовка данных¶
Подготовка тетради и загрузка данных¶
In [1]:
# Базовые библиотеки
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
In [2]:
# Отображение всех столбцов таблицы
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
In [3]:
# Чтение файла с датафреймами
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')
Анализ данных датафрейма первого региона¶
In [4]:
# Проверка датафрейма первого региона
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
Out[4]:
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 |
In [5]:
# Распределение значений датафрейма первого региона
geo_data_0.describe()
Out[5]:
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 |
In [6]:
# Соответствие количества уникальных идентификаторов
# и количества объектов в датафрейме первого региона
# (сколько месторождений в регионе)
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
In [7]:
# Анализ объектов с дубликатами в аттрибуте "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
In [8]:
# Гистограммы числовых данных
# первого региона
geo_data_0.hist();
In [9]:
# Анализ выбросов данных
# первого региона
for i in geo_data_0:
if i != 'id':
geo_data_0.boxplot(column=i);
plt.show()
In [10]:
# Анализ корреляции аттрибутов
# датафрейма первого региона
geo_data_0.corr()
Out[10]:
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 |
In [11]:
# Коэффициент инфляции дисперсии аттрибутов третьего региона
# (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
Out[11]:
[1.5342414107959919, 1.275466160458148, 2.0723577504192945, 2.828465688943373]
Анализ данных датафрейма второго региона¶
In [12]:
# Проверка датафрейма второго региона
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
Out[12]:
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 |
In [13]:
# Распределение значений датафрейма второго региона
geo_data_1.describe()
Out[13]:
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 |
In [14]:
# Соответствие количества уникальных идентификаторов
# и количества объектов в датафрейме второго региона
# (сколько месторождений в регионе)
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
In [15]:
# Анализ объектов с дубликатами в аттрибуте "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
In [16]:
# Гистограммы числовых данных
# второго региона
geo_data_1.hist();
In [17]:
# Анализ выбросов данных
# второго региона
for i in geo_data_1:
if i != 'id':
geo_data_0.boxplot(column=i);
plt.show()
In [18]:
# Анализ корреляции аттрибутов
# датафрейма второго региона
geo_data_1.corr()
Out[18]:
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 |
In [19]:
# Коэффициент инфляции дисперсии аттрибутов второго региона
# (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
Out[19]:
[1.9873199714997407, 1.8282400357569757, 4660.924528638744, 4692.5535256238845]