Допустим, вы работаете в добывающей компании «ГлавРосГосНефть». Нужно решить, где бурить новую скважину.
Вам предоставлены пробы нефти в трёх регионах: в каждом 10 000 месторождений, где измерили качество нефти и объём её запасов. Постройте модель машинного обучения, которая поможет определить регион, где добыча принесёт наибольшую прибыль. Проанализируйте возможную прибыль и риски техникой Bootstrap.
Шаги для выбора локации:
Цели:
Провести исследование с целью построения модели машинного обучения, которая поможет определить регион, где добыча нефти принесёт наибольшую прибыль.
Результаты исследования позволят увеличить прибыль добывающей компании «ГлавРосГосНефть».
Задачи:
Загрузим и подготовим данные. Поясним порядок действий.
Обучим и проверим модель для каждого региона:
2.1. Разобьём данные на обучающую и валидационную выборки в соотношении 75:25.
2.2. Обучим модель и сделаем предсказания на валидационной выборке.
2.3. Сохраним предсказания и правильные ответы на валидационной выборке.
2.4. Напечатаем на экране средний запас предсказанного сырья и RMSE модели.
2.5. Проанализируем результаты.
Подготовимся к расчёту прибыли:
3.1. Все ключевые значения для расчётов сохраним в отдельных переменных.
3.2. Рассчитаем достаточный объём сырья для безубыточной разработки новой скважины. Сравним полученный объём сырья со средним запасом в каждом регионе.
3.3. Напишем выводы по этапу подготовки расчёта прибыли.
Напишем функцию для расчёта прибыли по выбранным скважинам и предсказаниям модели:
4.1. Выберем скважины с максимальными значениями предсказаний.
4.2. Просуммируем целевое значение объёма сырья, соответствующее этим предсказаниям.
4.3. Рассчитаем прибыль для полученного объёма сырья.
Посчитаем риски и прибыль для каждого региона:
5.1. Применим технику Bootstrap с 1000 выборок, чтобы найти распределение прибыли.
5.2. Найдём среднюю прибыль, 95%-й доверительный интервал и риск убытков. Убыток — это отрицательная прибыль.
5.3. Напишем выводы: предложим регион для разработки скважин и обоснуем выбор.
В ходе проведения исследования нам необходимо проверить гипотезу:
Нам предоставлены пробы нефти в трёх регионах. Характеристики для каждой скважины в регионе уже известны.
Данные геологоразведки трёх регионов находятся в файлах:
geo_data_0.csv
geo_data_1.csv
geo_data_2.csv
Признаки:
id
— уникальный идентификатор скважиныf0
, f1
, f2
— три признака точек (неважно, что они означают, но сами признаки значимы)Целевой признак:
product
— объём запасов в скважине (тыс. баррелей)
Условия задачи:
import pandas as pd
import numpy as np
import scipy.stats as st
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
from tqdm import tqdm_notebook
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split
# снимаем ограничение на количество столбцов
pd.set_option('display.max_columns', None)
# снимаем ограничение на ширину столбцов
#pd.set_option('display.max_colwidth', None)
# игнорируем предупреждения
pd.set_option('chained_assignment', None)
# выставляем ограничение на показ знаков после запятой
pd.options.display.float_format = '{:,.2f}'.format
# устанавливаем стиль графиков
sns.set_style('darkgrid')
sns.set(rc={'figure.dpi':200, 'savefig.dpi':300})
sns.set_context('notebook')
sns.set_style('ticks')
state = np.random.RandomState(34215)
warnings.filterwarnings('ignore')
try:
df1 = pd.read_csv('/datasets/geo_data_0.csv')
df2 = pd.read_csv('/datasets/geo_data_1.csv')
df3 = pd.read_csv('/datasets/geo_data_2.csv')
except:
df1 = pd.read_csv('geo_data_0.csv')
df2 = pd.read_csv('geo_data_1.csv')
df3 = pd.read_csv('geo_data_2.csv')
def explorer(data):
display(data.head())
print('_' * 80, '\n')
display(data.info())
print('_' * 80, '\n')
display(f'Количествово дубликатов:{data.duplicated().sum()}')
print('_' * 80, '\n')
display(data.describe())
data.hist(figsize=(15, 7), bins=50, color='black')
def corr_matrix(data):
corr_matrix = data.corr()
display(corr_matrix)
fig, ax = plt.subplots(figsize=(15, 6))
sns.heatmap(corr_matrix, annot=True, cmap="coolwarm");
plt.show()
explorer(df1)
id | f0 | f1 | f2 | product | |
---|---|---|---|---|---|
0 | txEyH | 0.71 | -0.50 | 1.22 | 105.28 |
1 | 2acmU | 1.33 | -0.34 | 4.37 | 73.04 |
2 | 409Wp | 1.02 | 0.15 | 1.42 | 85.27 |
3 | iJLyR | -0.03 | 0.14 | 2.98 | 168.62 |
4 | Xdl7t | 1.99 | 0.16 | 4.75 | 154.04 |
________________________________________________________________________________ <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
________________________________________________________________________________
'Количествово дубликатов:0'
________________________________________________________________________________
f0 | f1 | f2 | product | |
---|---|---|---|---|
count | 100,000.00 | 100,000.00 | 100,000.00 | 100,000.00 |
mean | 0.50 | 0.25 | 2.50 | 92.50 |
std | 0.87 | 0.50 | 3.25 | 44.29 |
min | -1.41 | -0.85 | -12.09 | 0.00 |
25% | -0.07 | -0.20 | 0.29 | 56.50 |
50% | 0.50 | 0.25 | 2.52 | 91.85 |
75% | 1.07 | 0.70 | 4.72 | 128.56 |
max | 2.36 | 1.34 | 16.00 | 185.36 |
corr_matrix(df1)
f0 | f1 | f2 | product | |
---|---|---|---|---|
f0 | 1.00 | -0.44 | -0.00 | 0.14 |
f1 | -0.44 | 1.00 | 0.00 | -0.19 |
f2 | -0.00 | 0.00 | 1.00 | 0.48 |
product | 0.14 | -0.19 | 0.48 | 1.00 |
explorer(df2)
id | f0 | f1 | f2 | product | |
---|---|---|---|---|---|
0 | kBEdx | -15.00 | -8.28 | -0.01 | 3.18 |
1 | 62mP7 | 14.27 | -3.48 | 1.00 | 26.95 |
2 | vyE1P | 6.26 | -5.95 | 5.00 | 134.77 |
3 | KcrkZ | -13.08 | -11.51 | 5.00 | 137.95 |
4 | AHL4O | 12.70 | -8.15 | 5.00 | 134.77 |
________________________________________________________________________________ <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
________________________________________________________________________________
'Количествово дубликатов:0'
________________________________________________________________________________
f0 | f1 | f2 | product | |
---|---|---|---|---|
count | 100,000.00 | 100,000.00 | 100,000.00 | 100,000.00 |
mean | 1.14 | -4.80 | 2.49 | 68.83 |
std | 8.97 | 5.12 | 1.70 | 45.94 |
min | -31.61 | -26.36 | -0.02 | 0.00 |
25% | -6.30 | -8.27 | 1.00 | 26.95 |
50% | 1.15 | -4.81 | 2.01 | 57.09 |
75% | 8.62 | -1.33 | 4.00 | 107.81 |
max | 29.42 | 18.73 | 5.02 | 137.95 |
corr_matrix(df2)
f0 | f1 | f2 | product | |
---|---|---|---|---|
f0 | 1.00 | 0.18 | -0.00 | -0.03 |
f1 | 0.18 | 1.00 | -0.00 | -0.01 |
f2 | -0.00 | -0.00 | 1.00 | 1.00 |
product | -0.03 | -0.01 | 1.00 | 1.00 |
explorer(df3)
id | f0 | f1 | f2 | product | |
---|---|---|---|---|---|
0 | fwXo0 | -1.15 | 0.96 | -0.83 | 27.76 |
1 | WJtFt | 0.26 | 0.27 | -2.53 | 56.07 |
2 | ovLUW | 0.19 | 0.29 | -5.59 | 62.87 |
3 | q6cA6 | 2.24 | -0.55 | 0.93 | 114.57 |
4 | WPMUX | -0.52 | 1.72 | 5.90 | 149.60 |
________________________________________________________________________________ <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
________________________________________________________________________________
'Количествово дубликатов:0'
________________________________________________________________________________
f0 | f1 | f2 | product | |
---|---|---|---|---|
count | 100,000.00 | 100,000.00 | 100,000.00 | 100,000.00 |
mean | 0.00 | -0.00 | 2.50 | 95.00 |
std | 1.73 | 1.73 | 3.47 | 44.75 |
min | -8.76 | -7.08 | -11.97 | 0.00 |
25% | -1.16 | -1.17 | 0.13 | 59.45 |
50% | 0.01 | -0.01 | 2.48 | 94.93 |
75% | 1.16 | 1.16 | 4.86 | 130.60 |
max | 7.24 | 7.84 | 16.74 | 190.03 |
corr_matrix(df3)
f0 | f1 | f2 | product | |
---|---|---|---|---|
f0 | 1.00 | 0.00 | -0.00 | -0.00 |
f1 | 0.00 | 1.00 | 0.00 | -0.00 |
f2 | -0.00 | 0.00 | 1.00 | 0.45 |
product | -0.00 | -0.00 | 0.45 | 1.00 |
Комментарий:
Мы видим, что в трех датасетах отсутствуют дубликаты и по числовым показателям трех датасетов мы можем сделать вывод, что среднее и медиана не имеют большой разницы, кроме столбца product
в первом датасете, но есть большой разброс значений, который выражен в стандартном отклонении. Особенно ярко это выражено в данных столбцов f0
, f1
и f2
. Однако гистограммы демонстрируют, что распределения переменных существенно различаются для каждого региона. В частности, некоторые аномалии в данных, такие как в столбцах f2
и product
в первом датасете, могут препятствовать обучению.
# Удалим из каждого датафрейма столбец id. Он не несет значимости для модели.
df1 = df1.drop(['id'], axis=1)
df2 = df2.drop(['id'], axis=1)
df3 = df3.drop(['id'], axis=1)
names = ['df1', 'df2', 'df3']
data = [df1, df2, df3]
# создадим словарь для хранения фактического и предсказанных значений набора valid
targets = {}
for name, data in zip(names, data):
target = data['product']
features = data.drop('product', axis=1)
features_train, features_valid, target_train, target_valid = \
train_test_split(features, target, test_size=.25, random_state=state)
# создадим pipeline их скелера и модели линейной регрессии
model = make_pipeline(StandardScaler(), LinearRegression())
model.fit(features_train, target_train)
# запишем фактические данные и предсказанные в словарь
targets[name] = (target_valid, pd.Series(model.predict(features_valid), \
index=target_valid.index, name='product_predicted'))
# напечатаем на экране средний запас предсказанного сырья и RMSE модели
print()
print(f'Для {name} региона:')
print(f'Средний запас фактического сырья = {targets[name][0].mean():.2f} тыс. баррелей')
print(f'Средний запас предсказанного сырья = {targets[name][1].mean():.2f} тыс. баррелей')
print(f'Разница = {(1-targets[name][0].mean()/targets[name][1].mean()):.2%}')
print(f'RMSE модели = {(mean_squared_error(target_valid, targets[name][1])**0.5):.2f}')
Для df1 региона: Средний запас фактического сырья = 92.56 тыс. баррелей Средний запас предсказанного сырья = 92.46 тыс. баррелей Разница = -0.10% RMSE модели = 37.89 Для df2 региона: Средний запас фактического сырья = 69.15 тыс. баррелей Средний запас предсказанного сырья = 69.14 тыс. баррелей Разница = -0.01% RMSE модели = 0.89 Для df3 региона: Средний запас фактического сырья = 95.08 тыс. баррелей Средний запас предсказанного сырья = 95.10 тыс. баррелей Разница = 0.02% RMSE модели = 40.19
Комментарий:
В данной части работы мы провели обучение и проверку модели. Результаты экспериментов показали, что RMSE (корень из среднеквадратичной ошибки) для датасета df2
составляет 0.89. Однако, для датасетов df1
и df2
, RMSE значительно выше и составляет 37.58 и 40.03 соответственно. Это говорит о том, что в этих датасетах имеется большой разброс данных в целевых показателях, что означает, что предсказанные значения не отражают реальные запасы сырья с высокой точностью.
POINTS = 500 # количество скважин, которое исследуют при разведке региона
BEST_FOR_ML = 200 # количество лучших скважин, которые нужно выбрать для разработки в каждом регионе с помощью машинного обучения
BUDGET = 10e9 # бюджет (расходы) на разработку скважин в регионе (тыс. рублей), заложенный на 200 скважин
BARREL_PRICE = 450e3 # доход с каждой единицы продукта (тыс. рублей на 1 тыс. баррелей)
THRESHOLD = 0.025
names = ['df1', 'df2', 'df3']
data = [df1, df2, df3]
break_even = BUDGET / (BARREL_PRICE * BEST_FOR_ML)
print(f'Достаточный объём сырья для безубыточной разработки = {break_even:.2f} тыс. баррелей.')
for name, data in zip(names, data):
print()
print(f'Средний запас в {name} регионе = {data["product"].mean():.2f} тыс. баррелей.')
print(f'Процент скважин с объёмом больше чем порог = {len(data.query("product > @break_even")) / len(data):.2%}')
print(f'Количество скважин в объёмом больше чем порог = {len(data.query("product > @break_even"))}')
Достаточный объём сырья для безубыточной разработки = 111.11 тыс. баррелей. Средний запас в df1 регионе = 92.50 тыс. баррелей. Процент скважин с объёмом больше чем порог = 36.58% Количество скважин в объёмом больше чем порог = 36583 Средний запас в df2 регионе = 68.83 тыс. баррелей. Процент скважин с объёмом больше чем порог = 16.54% Количество скважин в объёмом больше чем порог = 16537 Средний запас в df3 регионе = 95.00 тыс. баррелей. Процент скважин с объёмом больше чем порог = 38.18% Количество скважин в объёмом больше чем порог = 38178
Комментарий:
Для того чтобы окупить вложения в разработку, выбранные моделью скважины должны иметь запасы не менее чем значительно больше пороговой величины, так как процент скважин с запасами выше порога не превышает 40% во всех регионах, при текущих макроусловиях, а точка безубыточности составляет в среднем 111.11 тыс. баррелей на скважину, а средние запасы находятся в диапазоне от 68 до 95 тыс. баррелей на регион. Кроме того, из 500 скважин лишь 200 (40%) имеют запасы выше порога.
def profit(target, probabilities, count):
probs_sorted = probabilities.sort_values(ascending=False)[:count]
selected = target[probs_sorted.index]
product = selected.sum()
revenue = product * BARREL_PRICE
return revenue - BUDGET
for name in tqdm_notebook(names):
values = []
for _ in range(1000):
target_subsample = targets[name][0].sample(POINTS, replace=True, random_state=state)
probs_subsample = targets[name][1][target_subsample.index]
values.append(profit(target_subsample, probs_subsample, BEST_FOR_ML))
values = pd.Series(values)
mean = values.mean() / 10e6
lower = values.quantile(.025) / 10e6
upper = values.quantile(.975) / 10e6
risk = values.apply(lambda x: x < 0).sum() / len(values)
# визуализируем
values.hist(figsize=(16, 5), bins=50, color='black')
plt.grid(True)
plt.axvline(values.quantile(0.025), color='violet')
plt.axvline(values.quantile(0.975), color='gold')
plt.legend(['Нижняя граница 95%-го доверительного интервала',
'Верхняя граница 95%-го доверительного интервала', f'Распределение прибыли для региона {name}'])
plt.xlabel('Прибыль, руб.')
plt.ylabel('Количество скважин из 200')
plt.title(f'Распределение прибыли для региона {name}')
plt.show()
print()
print(f'Для {name} региона:')
print(f'Средняя валовая прибыль с 200 лучших скважин, отобраных по предсказанию = {mean:.0f} млн. рублей.')
print(f'Доверительный интервал лежит между {lower:.0f} - {upper:.0f} млн. рублей.')
print(f'Риск убытков составляет = {risk:.2%}')
0%| | 0/3 [00:00<?, ?it/s]
Для df1 региона: Средняя валовая прибыль с 200 лучших скважин, отобраных по предсказанию = 61 млн. рублей. Доверительный интервал лежит между 0 - 125 млн. рублей. Риск убытков составляет = 2.50%
Для df2 региона: Средняя валовая прибыль с 200 лучших скважин, отобраных по предсказанию = 70 млн. рублей. Доверительный интервал лежит между 18 - 122 млн. рублей. Риск убытков составляет = 0.20%
Для df3 региона: Средняя валовая прибыль с 200 лучших скважин, отобраных по предсказанию = 59 млн. рублей. Доверительный интервал лежит между -1 - 123 млн. рублей. Риск убытков составляет = 2.80%
Комментарий:
df2
ожидается наибольшая возможная прибыль в размере 70 млн. рублей при оценке модели.df1
может принести наибольшую потенциальную прибыль в размере 125 млн. рублей.df2
для разработки, так как он обладает наибольшей средней потенциальной прибылью и минимальными рисками.Поставьте 'x' в выполненных пунктах. Далее нажмите Shift+Enter.