<jupyter_start><jupyter_text>Проект "Определение региона добычи нефти"  Краткое описание проекта В компании «ГлавРосГосНефть» нужно решить, где бурить новую скважину.Нам предоставлены пробы нефти в трёх регионах: в каждом 10 000 месторождений, где измерили качество нефти и объём её запасов. В рамках проекта нужно построить модель машинного обучения, которая поможет определить регион, где добыча принесёт наибольшую прибыль. Для этой модели необходимо проанализировать возможную прибыль и риски техникой *Bootstrap.*Шаги для выбора локации:- В избранном регионе ищут месторождения, для каждого определяют значения признаков;- Строят модель и оценивают объём запасов;- Выбирают месторождения с самым высокими оценками значений. Количество месторождений зависит от бюджета компании и стоимости разработки одной скважины;- Прибыль равна суммарной прибыли отобранных месторождений.  Оглавление- [Краткое  описание проекта](description_project)- [Условия задачи](task_conditions)- [Описание данных](description_data)- [Шаг 1. Импорт всех необходимых библиотек и добавление необходимых функций](step_1)- [Шаг 2. Подготовка данных](step_2)- [Шаг 3. Обучение и проверка модели](step_3)- [Шаг 4. Подготовка к расчёту прибыли](step_4) - [Шаг 5. Расчёт прибыли и рисков](step_5)- [Шаг 6. Выводы по результатам исследования](step_6)  Условия задачи - Для обучения модели подходит только линейная регрессия (остальные — недостаточно предсказуемые).- При разведке региона исследуют 500 точек, из которых с помощью машинного обучения выбирают 200 лучших для разработки.- Бюджет на разработку скважин в регионе — 10 млрд рублей.- При нынешних ценах один баррель сырья приносит 450 рублей дохода. Доход с каждой единицы продукта составляет 450 тыс. рублей, поскольку объём указан в тысячах баррелей.- После оценки рисков нужно оставить лишь те регионы, в которых вероятность убытков меньше 2.5%. Среди них выбирают регион с наибольшей средней прибылью.  Описание данных - `id` — уникальный идентификатор скважины;- `f0`, `f1`, `f2` — три признака точек (неважно, что они означают, но сами признаки значимы);- `product` — объём запасов в скважине (тыс. баррелей).  Шаг 1. Импорт всех необходимых библиотек и добавление необходимых функций Импортируем все необходимые в проекте библиотеки:<jupyter_code>import pandas as pd
import numpy as np
from pandas import DataFrame, Series
from dataclasses import dataclass
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn import preprocessing
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error<jupyter_output><empty_output><jupyter_text>**Структура данных для храненения тренировочных и валидационных выборок с целевым признаком и с остальными признаками**<jupyter_code>@dataclass
class TrainAndValidData:
    features_train: pd.DataFrame
    features_valid: pd.DataFrame
    target_train: pd.Series
    target_valid: pd.Series<jupyter_output><empty_output><jupyter_text>**Функция для разделения данных на тренировочные и валидационные выбороки с целевым признаком и с остальными признаками**<jupyter_code>def separate_on_train_and_valid_data(
        separating_data: pd.DataFrame,
        target_field: str,
        valid_size_value=0.25
) -> TrainAndValidData:
    data_train, data_valid = train_test_split(separating_data, test_size=valid_size_value, random_state=12345)

    features_train = data_train.drop([target_field], axis=1).reset_index(drop=True)
    features_valid = data_valid.drop([target_field], axis=1).reset_index(drop=True)

    target_train = data_train[target_field].reset_index(drop=True)
    target_valid = data_valid[target_field].reset_index(drop=True)

    return TrainAndValidData(features_train, features_valid, target_train, target_valid)<jupyter_output><empty_output><jupyter_text>**Функция для инициализации модели Линейной регрессии.**Функции в качестве параметров передаются `target_train` и `features_train` - объект Series с целевым признаком и DataFrame с остальными признаками. Кроме этого передается значение параметра `count_jobs`.<jupyter_code>def build_linear_regression_model(features_train, target_train, count_jobs=1) -> LinearRegression:
    linear_regression_model = LinearRegression(n_jobs=count_jobs)
    linear_regression_model.fit(features_train, target_train)

    return linear_regression_model<jupyter_output><empty_output><jupyter_text>**Функцию которая подсчитывает и возвращает выручку.**Она получает на вход:- `target` — реальные значения целевого признака- `predictions` — значения предсказанные моделью- `count` - размер среза с максимальными значениями- `additional_multiplier` - дополнительный множитель результирующий суммы (в нашем случае будет доход единицы товара)Функция должна будет выбрать скважины с наибольшим кол-ом продукта и на основе ответов подсчитать возможную выручку.<jupyter_code>def revenue(target, predictions, count, additional_multiplier=1):
    predict_sorted = predictions.sort_values(ascending=False)
    selected = target[predict_sorted.index][:count]

    return additional_multiplier * selected.sum()<jupyter_output><empty_output><jupyter_text>**Функция для того чтобы найти n-ый%-квантиль выручки процедурой Bootstrap с 1000 повторений.**Она возварщает значения нижнего (`lower_quantile`) и верхнего (`upper_quantile`) квантилей (переменные `lower` и `upper`), а также среднюю выручку (переменная `mean`).Значение переменной `lower` означает, что с вероятностью `(100 - lower_quantile)%` выручка может быть не менее `lower` руб. Аналогично для переменной `upper`.<jupyter_code>def get_mean_and_quantile_by_bootstrap(
        target: Series, predictions: Series,
        lower_quantile: float,
        upper_quantile: float,
        count_samples=200,
        additional_multiplier=1
):
    state = np.random.RandomState(12345)
    values = []
    for i in range(1000):
        target_subsample = target.sample(n=500, replace=True, random_state=state)
        predict_subsample = predictions[target_subsample.index]
        values.append(revenue(target_subsample, predict_subsample, count_samples, additional_multiplier))

    values = pd.Series(values)
    lower = values.quantile(lower_quantile)
    upper = values.quantile(upper_quantile)
    mean = values.mean()

    return lower, upper, mean, values<jupyter_output><empty_output><jupyter_text>**Небольшая функция для вычисления прибыльности скважен, на основе среза прдесказаний модели**<jupyter_code>def profit_of_wells_by_predictions(predictions: Series, product_profit, break_even_product_quantity):
    return (predictions.sum() - len(predictions) * break_even_product_quantity) * product_profit<jupyter_output><empty_output><jupyter_text>**Функция выбирает случайные 500 точек из датасета, на основе которых с помощью переданной модели, вычисляет предсказанья по ключевому признаку**<jupyter_code>def get_predictions_500(data: DataFrame, model) -> Series:
    data_500 = data.sample(500, replace=True, random_state=12345).reset_index(drop=True)
    features_train_500 = data_500.drop(['product'], axis=1).reset_index(drop=True)
    predictions = model.predict(features_train_500)

    return pd.Series(predictions)<jupyter_output><empty_output><jupyter_text>**Небольшая функция для перевода значений рублей в более удобночитаемый вид -> млн. руб.**<jupyter_code>def transform_to_million_rub(value: float) -> str:
    return '{:.3f} млн. руб.'.format(value / 10 ** 6)<jupyter_output><empty_output><jupyter_text>Шаг 2. Подготовка данных Загрузим данные и визуально проверим их.<jupyter_code>data_0 = pd.read_csv('/datasets/geo_data_0.csv')
data_1 = pd.read_csv('/datasets/geo_data_1.csv')
data_2 = pd.read_csv('/datasets/geo_data_2.csv')
display(data_0.head(10))
data_0.info()
display(data_1.head(10))
data_1.info()
display(data_2.head(10))
data_2.info()<jupyter_output><empty_output><jupyter_text>В данных нет пропусков, все типы соответствуют ожидаемым. Но есть один столбец - `id` (уникальный идентификатор скважины), который не нужен при построении модели и наших дальнейших расчетах. Но прежде чем избавиться от него, давайте проверим, нет ли дубликатов:<jupyter_code>print(data_0.id.nunique())
print(data_1.id.nunique())
print(data_2.id.nunique())<jupyter_output>99990
99996
99996<jupyter_text>Оказалось, что в каждом датасете есть неуникальные "уникальные" скважины. Так как их немного (10, 4, 4 в каждом датасете соответственно), то просто удалим эти записи:<jupyter_code>data_0 = data_0.drop_duplicates(subset=['id'])
data_1 = data_1.drop_duplicates(subset=['id'])
data_2 = data_2.drop_duplicates(subset=['id'])
data_0.info()
data_1.info()
data_2.info()<jupyter_output><class 'pandas.core.frame.DataFrame'>
Int64Index: 99996 entries, 0 to 99999
Data columns (total 5 columns):
id         99996 non-null object
f0         99996 non-null float64
f1         99996 non-null float64
f2         99996 non-null float64
product    99996 non-null float64
dtypes: float64(4), object(1)
memory usage: 4.6+ MB<jupyter_text>А теперь, с чистой совестью дропнем и столбец `id`:<jupyter_code>data_0 = data_0.drop(columns=['id'])
data_1 = data_1.drop(columns=['id'])
data_2 = data_2.drop(columns=['id'])<jupyter_output><empty_output><jupyter_text>Шаг 3. Обучение и проверка модели Обучение и проверку модели выполним в виде небольшой функции и вызовем её для каждого региона:<jupyter_code>def step_3(researched_data: DataFrame):
    # Разобъем данные на обучающую и валидационную выборки в соотношении 75:25
    data_train_valid = separate_on_train_and_valid_data(researched_data, target_field='product', valid_size_value=0.25)
    # Обучим модель и сделайте предсказания на валидационной выборке.
    model = build_linear_regression_model(
        data_train_valid.features_train,
        data_train_valid.target_train,
        count_jobs=-1
    )
    # Сохраним предсказания и правильные ответы на валидационной выборке.
    predictions = model.predict(data_train_valid.features_valid)
    real_target_values = data_train_valid.target_valid
    # Выведем на экране средний запас предсказанного сырья и RMSE модели.
    series_predictions = pd.Series(predictions)
    mean = series_predictions.mean()
    mse = mean_squared_error(real_target_values, predictions)
    rmse = mse ** 0.5
    print('Средний запас предсказанного сырья:', mean)
    print('RMSE (Среднеквадратическая ошибка модели):', rmse)
    print('Доверительный интервал прогноза: ({},{})'.format(mean - 2 * rmse, mean + 2 * rmse))

    return model, series_predictions, real_target_values
model_0, predictions_0, target_valid_0 = step_3(data_0)
model_1, predictions_1, target_valid_1 = step_3(data_1)
model_2, predictions_2, target_valid_2 = step_3(data_2)<jupyter_output>Средний запас предсказанного сырья: 94.86572480562035
RMSE (Среднеквадратическая ошибка модели): 40.07585073246016
Доверительный интервал прогноза: (14.714023340700024,175.01742627054068)<jupyter_text>Средние запасы нефти в скважинах, предсказанные моделями для каждого региона, отличаются. Для 1-го и 3-го региона это занчение 92 и 94 единицы продукта соответственно, а для 2-го около 69.Из этого можно было бы сделать вывод о большей персепективности 1-го и 3-го региона, если бы не значение среднеквадратической ошибки их моделей, а оно для этих регионов достаточно велико - 37.8 для 1-го и 40.0 для 3-го.Это говорит о том, что модель не смогла найти достаточно точную функцию, описывающю значения ключевого параметра (в нашем случае `product`) от остальных, поэтому предсказанное занчение добычи по скважине для 1-ого региона может находится в диапазоне от 54,2 до 129,8, а для 3-го региона от 54 до 134.Модель для 2-го региона получилась достаточно точной - ошибка всего около 0.9 ед. Теперь необходимо решить, какую скважину считать прибыльной, а какую нет. Для этого нужно знаять, какой количество нефти должна вырабатывать скважина для покрытия расходов на неё. Сделаем это в следующем разделе.  Шаг 4. Подготовка к расчёту прибыли  Все ключевые значения для расчётов сохраним в отдельные переменные:<jupyter_code>region_budget = 10000000000 # бюджет на разработку скважин в регионе, руб.
count_researching_points = 500 # количество исследуемых точек, шт.
count_best_points = 200 # количество лучших точек для разработки, шт.
product_profit = 450000 # доход с каждой единицы продукта, руб.
need_quantile = .025 # максимальная вероятность убытков<jupyter_output><empty_output><jupyter_text>Расчитаем стоимость 1-ой скважины:<jupyter_code>cost_price_one_well = region_budget / count_best_points
print(transform_to_million_rub(cost_price_one_well))<jupyter_output>50.000 млн. руб.<jupyter_text>Рассчитаем достаточный объём сырья для безубыточной разработки новой скважины:<jupyter_code>break_even_product_quantity = np.ceil(cost_price_one_well / product_profit)
print(break_even_product_quantity)<jupyter_output>112.0<jupyter_text>Теперь становится понятно, что средние запасы нефти в скважинах, расчитанные в предыдущем раздаеле, меньше этого значения. Но к счастью, по условиям задачи, нам нет необходимости вырабатывать все скважины регионов, а только 200 в каждом.  Шаг 5. Расчёт прибыли и рисков  Расчитаем прибыли и рисков для каждого региона используя ранее полученные модели. Для этого выберем 500 случайных точек в каждом регионе и, используя предсказания модели, расчитаем прибыльность 200 из них с максимальными значениями `product`:<jupyter_code>def step_5(data, model, region_number):
    predictions_500 = get_predictions_500(data, model)
    predictions_500 = predictions_500.sort_values(ascending=False)
    print('Прибыль для полученного объёма сырья для {}-го региона:'.format(region_number),
          transform_to_million_rub(profit_of_wells_by_predictions(
                  predictions_500.iloc[0:count_best_points],
                  product_profit,
                  break_even_product_quantity
               )
          )
    )
step_5(data_0, model_0, region_number=1)
step_5(data_1, model_1, region_number=2)
step_5(data_2, model_2, region_number=3)<jupyter_output>Прибыль для полученного объёма сырья для 3-го региона: 211.305 млн. руб.<jupyter_text>Все три региона оказались прибыльными, причем 1-ый выглядит наиболее перспективным с его 371.266 млн. руб. прибыли. Но для обеспечения большей надежности наших результатов (500 точек из 10000 - это всего лишь 5% от общего числа) применим технику Bootstrap с 1000 выборок, и найдем потенциальную прибыль в каждом регионе, а также 95%-й доверительный интервал и риск убытков.<jupyter_code>def step_5_using_bootstrap(target_valid, predictions):
    lower, upper, mean, values = get_mean_and_quantile_by_bootstrap(
        target_valid,
        predictions,
        need_quantile,
        (1 - need_quantile),
        additional_multiplier=product_profit
    )
    print('Средняя выручка:', transform_to_million_rub(mean))
    print('Прибыль опираясь на среднюю выручку:', transform_to_million_rub(mean - region_budget))
    print('ДИ истинного среденего: ({}, {})'.format(transform_to_million_rub(lower), transform_to_million_rub(upper)))

    up_profit = upper - region_budget
    down_profit = lower - region_budget
    print('Интервал прибыли опираясь на ДИ истинного среденего: ({}, {})'.format(
        transform_to_million_rub(down_profit),
        transform_to_million_rub(up_profit))
    )

    if up_profit < 0:
        probability_of_losses_by_cond_interval = 100.0
    elif up_profit > 0 and down_profit < 0:
        probability_of_losses_by_cond_interval = (abs(down_profit) / (abs(down_profit) + up_profit)) * 100
    else:
        probability_of_losses_by_cond_interval = 0.0

    print('Доля отрицательной части ДИ истинного среденего: {:.2f}%'.format(probability_of_losses_by_cond_interval))

    profit_values = values.apply(lambda value: value - region_budget)
    probability_of_losses = len(profit_values[profit_values < 0]) / len(profit_values)
    print('Риск убытка: {:.2f}%'.format(probability_of_losses * 100))
step_5_using_bootstrap(target_valid_0, predictions_0)
step_5_using_bootstrap(target_valid_1, predictions_1)
step_5_using_bootstrap(target_valid_2, predictions_2)<jupyter_output>Средняя выручка: 10353.790 млн. руб.
Прибыль опираясь на среднюю выручку: 353.790 млн. руб.
ДИ истинного среденего: (9840.806 млн. руб., 10875.653 млн. руб.)
Интервал прибыли опираясь на ДИ истинного среденего: (-159.194 млн. руб., 875.653 млн. руб.)
Доля отрицательной части ДИ истинного среденего: 15.38%
Риск убытка: 10.20%
{'fraction_non_alphanumeric': 0.07892174759644639, 'fraction_numerical': 0.03218936351466472, 'mean_word_length': 5.73013923013923, 'pattern_counts': {'":': 0, '<': 74, '<?xml version=': 0, '>': 77, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 1, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '19', 'dir': 'jupyter-structured-clean-dedup', 'id': '303984', 'license': 'MIT', 'n_tokens_mistral': 6513, 'n_tokens_neox': 6951, 'n_words': 1442, 'path': 'jupiter/8_region_of_oil production_ru.ipynb', 'repo_name': 'TzepART/ml-projects', 'size': '16434'}