Привет! меня зовут Люман Аблаев. Сегодня я проверю твой проект.
Дальнейшее общение будет происходить на "ты" если это не вызывает никаких проблем.
Желательно реагировать на каждый мой комментарий ('исправил', 'не понятно как исправить ошибку', ...)
Пожалуйста, не удаляй комментарии ревьюера, так как они повышают качество повторного ревью.
Комментарии будут в зеленой, синей или красной рамках:
Будет очень хорошо, если ты будешь помечать свои действия следующим образом:
Общее впечатление
- Спасибо за качественную работу, было интересно ее проверять.
- Я оставил некоторые советы надеюсь они будут полезными или интересными.
- Техника бутстрап применилась корректно, было проведено тщательное исследование и выбран правильный регион!
- Отправляю проект назад, чтобы у тебя была возможность задать вопросы, если они у тебя есть. Если их нет, то можешь просто отправить проект еще раз и я его зачту
Я готов завершить этот проект, особых вопросов не имею.
Выбор локации для скважины¶
Допустим, вы работаете в добывающей компании «ГлавРосГосНефть». Нужно решить, где бурить новую скважину.
Вам предоставлены пробы нефти в трёх регионах: в каждом 10 000 месторождений, где измерили качество нефти и объём её запасов. Постройте модель машинного обучения, которая поможет определить регион, где добыча принесёт наибольшую прибыль. Проанализируйте возможную прибыль и риски техникой Bootstrap.
Шаги для выбора локации:
В избранном регионе ищут месторождения, для каждого определяют значения признаков;
Строят модель и оценивают объём запасов;
Выбирают месторождения с самым высокими оценками значений. Количество месторождений зависит от бюджета компании и стоимости разработки одной скважины;
Прибыль равна суммарной прибыли отобранных месторождений.
Вам предоставлены пробы нефти в трёх регионах. Характеристики для каждой скважины в регионе уже известны. Постройте модель для определения региона, где добыча принесёт наибольшую прибыль. Проанализируйте возможную прибыль и риски техникой Bootstrap.
Условия задачи:
Для обучения модели подходит только линейная регрессия (остальные — недостаточно предсказуемые).
При разведке региона исследуют 500 точек, из которых с помощью машинного обучения выбирают 200 лучших для разработки.
Бюджет на разработку скважин в регионе — 10 млрд рублей.
При нынешних ценах один баррель сырья приносит 450 рублей дохода. Доход с каждой единицы продукта составляет 450 тыс. рублей, поскольку объём указан в тысячах баррелей.
После оценки рисков нужно оставить лишь те регионы, в которых вероятность убытков меньше 2.5%. Среди них выбирают регион с наибольшей средней прибылью.
Загрузка и подготовка данных¶
Загрузим данные, создадим раздельные датфреймы и определимся есть ли у нас проблемы с данными. Будем максимально пользоваться функциями т.к. 3 набора данных.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score
from scipy import stats as st
df_1 = pd.read_csv('/datasets/geo_data_0.csv')
df_1.head()
| 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 |
df_2 = pd.read_csv('/datasets/geo_data_1.csv')
df_2.head()
| 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 |
df_3 = pd.read_csv('/datasets/geo_data_2.csv')
df_3.head()
| 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 |
def info_graph(data):
plt.title('Распределение объемов')
plt.xlabel('Объем запасов в скважине', size = '12')
plt.ylabel('Количество скважин', size = '12')
data['product'].hist(figsize=(6,6),bins = 50)
return (print(data.info()),
print('\nПропущенные значения:\n',data.isna().sum()),
print('\nЯвные дубликаты:',data.duplicated().sum()),
print('\nКол-во уникальных id:', len(data['id'].unique())))
info_graph(df_1)
<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 0 f0 0 f1 0 f2 0 product 0 dtype: int64 Явные дубликаты: 0 Кол-во уникальных id: 99990
(None, None, None, None)
info_graph(df_2)
<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 0 f0 0 f1 0 f2 0 product 0 dtype: int64 Явные дубликаты: 0 Кол-во уникальных id: 99996
(None, None, None, None)
info_graph(df_3)
<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 0 f0 0 f1 0 f2 0 product 0 dtype: int64 Явные дубликаты: 0 Кол-во уникальных id: 99996
(None, None, None, None)
Неявные дубликаты¶
def uniq_id(df):
ss = df.groupby('id').count()
ind = ss.loc[ss['f0'] > 1].index
for i in ind:
print(df.loc[df['id'].isin([i])])
uniq_id(df_1)
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
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
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
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
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
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
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
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
16633 fiKDv 0.157341 1.028359 5.585586 95.817889
90815 fiKDv 0.049883 0.841313 6.394613 137.346586
uniq_id(df_2)
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
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
uniq_id(df_3)
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
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
44378 Vcm5J -1.229484 -2.439204 1.222909 137.968290
95090 Vcm5J 2.587702 1.986875 2.482245 92.327572
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
Промежуточные выводы:
1) Пропусков нет.
2) Явные дубликаты отсутствуют.
3)Имеются повторяющиеся ID. Они должны быть уникальными. Но предполагаем это не ошибка, т.к таких значений очень мало скорее всего повторение ID связано с тем что скважины очень близко друг к другу(относятся к одному объекту).
Очень просты в использоовании, на вход кладется датафрейм: pandas_profiling.ProfileReport(df)
Сохраним уникальные id в датафрейме¶
Колонка 'ID' содержит уникальные имена и нам она не пригодиться для обучения моделей и предсказаний. Тем не менее сохраним эти значения в отдельный датасет(а вдруг нам пригодиться)
id_table = pd.DataFrame()
id_table['id_1'] = df_1['id']
id_table['id_2'] = df_2['id']
id_table['id_3'] = df_3['id']
df_1.drop(columns=['id'],inplace = True)
df_2.drop(columns=['id'],inplace = True)
df_3.drop(columns=['id'],inplace = True)
Разделение на выборки¶
def split(data, rs):
features = data.drop(columns=['product'])
target = data['product']
(features_train,
features_valid,
target_train,
target_valid) = train_test_split(features, target, test_size=0.25, random_state = rs)
print('Размеры выборок: \n','Обучающая:', features_train.shape,'\n Валидационная:', features_valid.shape)
return (features_train,
features_valid,
target_train,
target_valid)
rs = 12345
(features_train_1,
features_valid_1,
target_train_1,
target_valid_1) = split(df_1, rs)
Размеры выборок: Обучающая: (75000, 3) Валидационная: (25000, 3)
(features_train_2,
features_valid_2,
target_train_2,
target_valid_2) = split(df_2, rs)
Размеры выборок: Обучающая: (75000, 3) Валидационная: (25000, 3)
(features_train_3,
features_valid_3,
target_train_3,
target_valid_3) = split(df_3, rs)
Размеры выборок: Обучающая: (75000, 3) Валидационная: (25000, 3)
Вывод:
1) Данные проблем не имеют.
2)Данные разделены на выборки.
3)Судя по графической информации наименьшая средне-квадратичная ошибка будет на 2-ом датасете.(предположение)
Обучение и проверка модели¶
По условию задачи обучим модель "LinearRegression". Посчитаем метрики RMSE, R2 и посмотрим среднее кол-во предсказанного сырья.
def fast_model(features_train, features_valid, target_train, target_valid):
model=LinearRegression()
model.fit(features_train,target_train)
predictions=model.predict(features_valid)
rmse = mean_squared_error(target_valid, predictions)**0.5
r2 = r2_score(target_valid, predictions)
mean_product = predictions.mean()
print('Среднее количество сырья:', mean_product)
print('Среднеквадратичная ошибка RMSE: %.2f'% rmse)
print('Коэффициент детерминации:%.4f'% r2 )
return (predictions)
predictions_1 = fast_model(features_train_1, features_valid_1, target_train_1, target_valid_1)
Среднее количество сырья: 92.59256778438035 Среднеквадратичная ошибка RMSE: 37.58 Коэффициент детерминации:0.2799
predictions_2 = fast_model(features_train_2, features_valid_2, target_train_2, target_valid_2)
Среднее количество сырья: 68.728546895446 Среднеквадратичная ошибка RMSE: 0.89 Коэффициент детерминации:0.9996
predictions_3 = fast_model(features_train_3, features_valid_3, target_train_3, target_valid_3)
Среднее количество сырья: 94.96504596800489 Среднеквадратичная ошибка RMSE: 40.03 Коэффициент детерминации:0.2052
Вывод:
1) Обученная модель не очень хорошо предсказывает данные по 1 и 3 выборке. Ошибка RMSE составляет до 40% от среднего значения. Однако R2 метрика говорит о том что наша модель лучше чем предсказание средним.
2) Обученная модель по 2 ДФ дает крайне высокие результаты. R2 стремиться к 1, значит наша модель
идеально предсказывает. И среднеквадратичная ошибка составляет 0.89.
Подготовка к расчёту прибыли¶
Посчитаем пороговые значения для рентабельности, и проверим эти пороги на средних значениях сырья.
total_budget = 10000000 #весь бюджет в тыс.
revenue_unit = 450 #стоимость единицы в тыс. (1ед=1000 барелей)
target_propabilites = 0.025 # заданная вероятность убытков
number_wells =200 # количество скважин
total_wells = 500 # количество точек для проб
de = 1000 # делитель
Совет: Константы принято записывать в ВЕРХНЕМ РЕГИСТРЕ =) Читем здесь https://pythonworld.ru/osnovy/pep-8-rukovodstvo-po-napisaniyu-koda-na-python.html#section-29
А большие значение можно записывать так: 10_000_000.
budget = total_budget / number_wells
print(f'Расходы на одну скважину: {budget /de} млн.')
min_materials = budget / revenue_unit
print(f'Минимальное количество барылей: {min_materials:.2f} тыс. баррелей')
Расходы на одну скважину: 50.0 млн. Минимальное количество барылей: 111.11 тыс. баррелей
def comparison(predictions, materials):
if predictions.mean() > materials:
print(f'Количество баррелей достаточно : {predictions.mean()-materials}')
else:
print(f'Количество баррелей не достаточно: {predictions.mean()-materials}')
print('Регион №1')
comparison(predictions_1, min_materials)
print('\nРегион №2')
comparison(predictions_2, min_materials)
print('\nРегион №3')
comparison(predictions_3, min_materials)
Регион №1 Количество баррелей не достаточно: -18.518543326730764 Регион №2 Количество баррелей не достаточно: -42.38256421566511 Регион №3 Количество баррелей не достаточно: -16.146065143106227
Вывод:
***Расходы на разработку одной скважины составили 50млн. Минимальное количество барылей для безубыточной добычи: 111.11 тыс. баррелей. ***
Исходя из средних значений, добыча в этих регионах не ликвидна. Но опираться только на что-то среднее было бы ошибкой. Далее мы рассмотрим задачу с лучшими показателями.
Расчёт прибыли и рисков¶
Прибыль по лучшим показателям¶
def profit (predictions, number_wells, de):
predictions_series = pd.Series(predictions)
sort_wells = predictions_series.sort_values(ascending = False).reset_index(drop =True)
sort_wells = sort_wells[:number_wells]
profit = sort_wells.sum()*revenue_unit - total_budget
print(f'Количество сырья с {number_wells} лучших:{sort_wells.sum():.2f} тыс.баррелей')
print(f'Суммарный доход с {number_wells} скважин:{profit / de:.2f} млн.')
return sort_wells.sum()
print('Регион №1')
profit(predictions_1, number_wells, de)
Регион №1 Количество сырья с 200 лучших:31102.33 тыс.баррелей Суммарный доход с 200 скважин:3996.05 млн.
31102.3308388114
print('Регион №2')
profit(predictions_2, number_wells, de)
Регион №2 Количество сырья с 200 лучших:27746.03 тыс.баррелей Суммарный доход с 200 скважин:2485.71 млн.
27746.026782163426
print('Регион №3')
profit(predictions_3, number_wells, de)
Регион №3 Количество сырья с 200 лучших:29603.90 тыс.баррелей Суммарный доход с 200 скважин:3321.75 млн.
29603.898658318347
Вывод:
Выбрав по 200 луших скважин в каждом регионе и подсчитав прибыль можно заключить, что в каждом регионе разработка прибыльна. Распределение по доходам: 1) Регион №1 ~40 млрд. 2) Регион №3 33 млрд. 3) Регион №2 ~ 25 млрд.
Но стоит учитывать и риски до начала разработки кого либо из регионов. Далее мы займемся оценками рисков и определим доверительные интервалы.
Риски и прибыль по регионам¶
def profit_2 (target_subsample, predictions, number_wells):
sort_index = predictions.sort_values(ascending = False).index
sort_wells = target_subsample[sort_index][:number_wells]
profit = sort_wells.sum()*revenue_unit - total_budget
return profit
def risk(target, predictions, total_wells, number_wells, de):
values = []
state = np.random.RandomState(12345)
predictions_s =pd.Series(predictions, index=target.index)
count_1 = 0
for i in range(1000):
target_subsample = target.sample(n = total_wells, replace=True, random_state=state)
probs_subsample = predictions_s[target_subsample.index]
values.append(profit_2(target_subsample,probs_subsample, number_wells))
values = pd.Series(values)
mean = values.mean()
low_95 = values.quantile(0.025)
high_95 = values.quantile(0.975)
confidence_interval = st.t.interval(0.95, len(values)-1, values.mean(), values.sem())
for j in values:
if j < 0:
count_1 += 1
if target_propabilites > count_1/1000:
print(f'Регион нам подходит, вероятность убытков: {count_1/1000:.2%}')
else:
print(f'Регион нам не подходит, вероятность убытков: {count_1/1000:.2%}')
print(f'Прибыль с вероятностью 95% находиться в интервале от {low_95} до {high_95}')
print(f'95%-ый доверительный интервал: {confidence_interval}')
print("Средняя выручка:", mean)
Риски можно лаконично считать в 1 строку: (values < 0).mean() * 100
risk(target_valid_1, predictions_1, total_wells, number_wells, de)
Регион нам не подходит, вероятность убытков: 6.00% Прибыль с вероятностью 95% находиться в интервале от -102090.09483793723 до 947976.353358369 95%-ый доверительный интервал: (408732.20706869866, 443144.84675248613) Средняя выручка: 425938.5269105924
risk(target_valid_2, predictions_2, total_wells, number_wells, de)
Регион нам подходит, вероятность убытков: 1.00% Прибыль с вероятностью 95% находиться в интервале от 68873.22537050233 до 931547.5912570491 95%-ый доверительный интервал: (501621.47617632174, 528824.070710258) Средняя выручка: 515222.77344328986
risk(target_valid_3, predictions_3, total_wells, number_wells, de)
Регион нам не подходит, вероятность убытков: 6.40% Прибыль с вероятностью 95% находиться в интервале от -128880.54732978888 до 969706.9541802666 95%-ый доверительный интервал: (417453.5520413578, 452563.17352415336) Средняя выручка: 435008.3627827556
Вывод:
Изходя из условий задачи нам подходит только один регион и это регион №2(geo_data_1.csv). Вероятность убытков составила 1%.
Средняя выручка здесь максимальна и размер ее 51,522 млрд.
Доверительный интервал (95%) расположился в пределах от 50 млрд. до 52.88 млрд.
Оценивать возможности разработки по средним значениям это не корректно. Они не отражают картины в целом. Оценка получается односторонней (разработкой в данных регионах с доходом не возможно).
Рассматривать задачу надо в комплексе, как модель предсказывает (насколько велики ошибки), какие риски и конечно какой у нас получается доверительный интервал.
- "Bootstrap Hypothesis Testing in Statistics with Example"
https://www.youtube.com/watch?v=9STZ7MxkNVg 2. "How to Calculate Bootstrap Confidence Intervals For Machine Learning Results in Python" https://machinelearningmastery.com/calculate-bootstrap-confidence-intervals-machine-learning-results-python/ 3. "A Gentle Introduction to the Bootstrap Method"
https://machinelearningmastery.com/a-gentle-introduction-to-the-bootstrap-method/ 4. "An Introduction to the Bootstrap Method" https://towardsdatascience.com/an-introduction-to-the-bootstrap-method-58bcb51b4d60#:~:text=The%20basic%20idea%20of%20bootstrap,population%20mean)%20on%20sample%20data.&text=A%20sample%20from%20population%20with%20sample%20size%20n 5. "Bootstrapping and Resampling in Statistics with Example" https://www.youtube.com/watch?v=O_Fj4q8lgmc
Чек-лист готовности проекта¶
Поставьте 'x' в выполненных пунктах. Далее нажмите Shift+Enter.
- Jupyter Notebook открыт
- Весь код выполняется без ошибок
- Ячейки с кодом расположены в порядке исполнения
- Выполнен шаг 1: данные подготовлены
- Выполнен шаг 2: модели обучены и проверены
- Данные корректно разбиты на обучающую и валидационную выборки
- Модели обучены, предсказания сделаны
- Предсказания и правильные ответы на валидационной выборке сохранены
- На экране напечатаны результаты
- Сделаны выводы
- Выполнен шаг 3: проведена подготовка к расчёту прибыли
- Для всех ключевых значений созданы константы Python
- Посчитано минимальное среднее количество продукта в месторождениях региона, достаточное для разработки
- По предыдущему пункту сделаны выводы
- Написана функция расчёта прибыли
- Выполнен шаг 4: посчитаны риски и прибыль
- Проведена процедура Bootstrap
- Все параметры бутстрепа соответствуют условию
- Найдены все нужные величины
- Предложен регион для разработки месторождения
- Выбор региона обоснован