Допустим, вы работаете в добывающей компании «ГлавРосГосНефть». Нужно решить, где бурить новую скважину.
Вам предоставлены пробы нефти в трёх регионах: в каждом 10 000 месторождений, где измерили качество нефти и объём её запасов. Постройте модель машинного обучения, которая поможет определить регион, где добыча принесёт наибольшую прибыль. Проанализируйте возможную прибыль и риски техникой Bootstrap.
Шаги для выбора локации:
class Explorer:
def histogram(self, data, n_bins, range_start, range_end, grid, cumulative=False, x_label = "", y_label = "", title = ""):
"""
data - датасет
n_bins - количество корзин
range_start - минимальный икс для корзины
range_end - максимальный икс для корзины
grid - рисовать сетку или нет (False / True)
histogram(data, n_bins, range_start, range_end, grid, x_label = "", y_label = "", title = "")
Пример:
histogram(df, 100, 0, 150, True, 'Количество иксов', 'Количество игриков', 'Заголовок')
"""
# Создаем объект - график
_, ax = plt.subplots()
# Задаем параметры
ax.hist(data, bins = n_bins, range = (range_start, range_end), cumulative = cumulative, color = '#4169E1')
# Добавляем сетку
if grid == True:
ax.grid(color='grey', linestyle='-', linewidth=0.5)
else:
pass
# Добавляем медиану, среднее и квартили
ax.axvline(data.median(),linestyle = '--', color = '#FF1493', label = 'median')
ax.axvline(data.mean(),linestyle = '--', color = 'orange', label = 'mean')
ax.axvline(data.quantile(0.1),linestyle = '--', color = 'yellow', label = '1%')
ax.axvline(data.quantile(0.99),linestyle = '--', color = 'yellow', label = '99%')
ax.legend()
ax.set_ylabel(y_label)
ax.set_xlabel(x_label)
ax.set_title(title)
def scatterplot(self, x_data, y_data, x_label="", y_label="", title="", color = "r", yscale_log=False, figsize = (8, 6)):
# Создаем объект - график
_, ax = plt.subplots(figsize = (8, 6))
# Задаем параметры для графика, определяем размер (s), цвет и прозрачность точек на графике
ax.scatter(x_data, y_data, s = 10, color = color, alpha = 0.75)
if yscale_log == True:
ax.set_yscale('log')
# Создаем описание осей и заголовок для графика
ax.set_title(title)
ax.set_xlabel(x_label)
ax.set_ylabel(y_label)
def overlaid_histogram(self, data1, data2, n_bins = 0, data1_name="", data1_color="#539caf", data2_name="", data2_color="#7663b0", x_label="", y_label="", title=""):
# Устанавливаем границы для корзин так чтобы оба распределения на графике были соотносимы
max_nbins = 10
data_range = [min(min(data1), min(data2)), max(max(data1), max(data2))]
binwidth = (data_range[1] - data_range[0]) / max_nbins
if n_bins == 0:
bins = np.arange(data_range[0], data_range[1] + binwidth, binwidth)
else:
bins = n_bins
# Рисуем график
_, ax = plt.subplots(figsize=(10,8))
ax.hist(data1, bins = bins, color = data1_color, alpha = 0.65, label = data1_name)
ax.hist(data2, bins = bins, color = data2_color, alpha = 0.65, label = data2_name)
ax.axvline(data1.mean(),linestyle = '--', color = 'lime', label = 'mean for data 1')
ax.axvline(data2.mean(),linestyle = '--', color = 'coral', label = 'mean for data 2')
ax.set_ylabel(y_label)
ax.set_xlabel(x_label)
ax.set_title(title)
ax.legend(loc = 'best')
def corr_diagram(self, x):
plt.figure(figsize=(12,10), dpi= 80)
sns.heatmap(x.corr(), xticklabels=x.corr().columns, yticklabels=x.corr().columns, cmap='RdYlGn', center=0, annot=True)
plt.title('Диаграмма корреляции', fontsize=22)
plt.xticks(fontsize=12)
plt.yticks(fontsize=12)
plt.show()
def highlight_max(self, data, color='#00FF00'):
'''
highlight the maximum in a Series or DataFrame
'''
attr = 'background-color: {}'.format(color)
#remove % and cast to float
data = data.replace('%','', regex=True).astype(float)
data[data == 1] = None
if data.ndim == 1: # Series from .apply(axis=0) or axis=1
is_max = (data == data.abs().max()) & (data !=1)
return [attr if v else '' for v in is_max]
else: # from .apply(axis=None)
is_max = (data == data.abs().max()) & (data !=1)
return pd.DataFrame(np.where(is_max, attr, ''),
index=data.index, columns=data.columns)
def highlight_sorted_corr(self, data, color='#00FF00'):
'''
highlight the maximum in a Series or DataFrame
'''
attr = 'background-color: {}'.format(color)
#remove % and cast to float
data = data.replace('%','', regex=True).astype(float)
data[data == 1] = None
if data.ndim == 1: # Series from .apply(axis=0) or axis=1
is_max = (data > 0.1) & (data !=1)
return [attr if v else '' for v in is_max]
else: # from .apply(axis=None)
is_max = (data == data.abs().max()) & (data !=1)
return pd.DataFrame(np.where(is_max, attr, ''),
index=data.index, columns=data.columns)
def lineplot(self, x_data, y_data, x_label="", y_label="", title=""):
# Создаем объект - график
_, ax = plt.subplots(figsize=(8, 6))
# Задаем параметры для линии: ширину (lw), цвет и прозрачность (alpha)
ax.plot(x_data, y_data, lw = 2, color = '#539caf', alpha = 1)
# Даем имена осям и заголовок для графика
ax.set_title(title)
ax.set_xlabel(x_label)
ax.set_ylabel(y_label)
def double_lineplot(self, x_data_1, y_data_1, x_data_2, y_data_2, x_label="", y_label="", title="", label_one="", label_two=""):
# Создаем объект - график
_, ax = plt.subplots(figsize=(8, 6))
# Задаем параметры для линии: ширину (lw), цвет и прозрачность (alpha)
ax.plot(x_data_1, y_data_1, lw = 2, color = '#6400e4', alpha = 1, label = label_one)
ax.plot(x_data_2, y_data_2, lw = 2, color = '#ffc740', alpha = 1, label = label_two)
# Даем имена осям и заголовок для графика
ax.set_title(title)
ax.set_xlabel(x_label)
ax.set_ylabel(y_label)
ax.legend(loc = 'best')
def hexbin(self, data, x, y):
data.plot(x = x, y = y, kind='hexbin', gridsize=20, figsize=(15, 10), sharex=False, grid=True)
def bar_plotter(self, data):
data.plot.bar(rot=0, figsize = (16, 5))
def categorical_counter_plot(self, data, column, x = '', y = ''):
if x == '' or y == '':
plt.rcParams["figure.figsize"] = (15, 10)
else:
plt.rcParams["figure.figsize"] = (x, y)
order = data[column].value_counts().index
ax = sns.countplot(data[column], order = order)
ax.set_xticklabels(ax.get_xticklabels(), fontsize=11)
plt.xticks(rotation=90)
def sns_scatterplot(self, data, x="", y="", hue="", size="", palette=""):
sns.set(style="whitegrid")
f, ax = plt.subplots(figsize=(15, 10))
if palette == True:
sns.scatterplot(ax = ax, x=x, y=y, palette="ch:r=-.2,d=.3_r",
hue=hue, size=size, sizes=(1, 200), linewidth=0, data=data)
else:
sns.scatterplot(ax = ax, x=x, y=y,
hue=hue, size=size,
sizes=(1, 200), linewidth=0, data=data)
def sns_catplot(self, data, x="", y="", hue=""):
sns.set(style='whitegrid')
sns.catplot(x=x, y=y, hue=hue, kind='bar', errwidth=0,
data=data, height=5, aspect=3)
def sns_factorplot(self, data, x='', hue=''):
sns.axes_style('white')
g = sns.factorplot("exited", data=df, aspect=1, kind='count',
hue='hascrcard')
def squared_ratio(self, df, grouper, title=''):
df = df.groupby(grouper).size().reset_index(name='counts')
labels = df.apply(lambda x: str(x[0]) + "\n (" + str(x[1]) + ")", axis=1)
sizes = df['counts'].values.tolist()
colors = [plt.cm.Spectral(i/float(len(labels))) for i in range(len(labels))]
plt.figure(figsize=(10,6), dpi= 80)
squarify.plot(sizes=sizes, label=labels, color=colors, alpha=.8)
plt.title(title)
plt.axis('off')
plt.show()
def sorted_corr(self, data, attr):
correlated = pd.DataFrame(data.corr()[attr].sort_values(ascending = False))
return correlated
def transformer(self, data, name, grouper, func):
"""
data - датасет
name - столбец в котором меняем значения
grouper - столбец по которому группируем
func - пременяемая функция mean, median и т.д.
"""
name = name
data.loc[data[name].isnull(), name] = data.groupby(grouper)[name].transform(func)
def pr_curve(self, model, features_valid, target_valid):
probabilities_valid = model.predict_proba(features_valid)
precision, recall, thresholds = precision_recall_curve(target_valid, probabilities_valid[:, 1])
plt.figure(figsize=(6, 6))
plt.step(recall, precision, where='post')
plt.xlabel('Recall')
plt.ylabel('Precision')
plt.ylim([0.0, 1.05])
plt.xlim([0.0, 1.0])
plt.title('Кривая Precision-Recall')
plt.show()
def roc_curve(self, model, features_valid, target_valid):
probabilities_valid = model.predict_proba(features_valid)
probabilities_one_valid = probabilities_valid[:, 1]
fpr, tpr, thresholds = roc_curve(target_valid, probabilities_one_valid)
plt.figure()
plt.plot(fpr, tpr)
plt.plot([0, 1], [0, 1], linestyle='--')
plt.xlim(0,1)
plt.ylim(0,1)
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC-кривая')
plt.show()
def metrics_plot(self, model, features_valid, target_valid):
probabilities_valid = model.predict_proba(features_valid)
precision, recall, thresholds = precision_recall_curve(target_valid, probabilities_valid[:, 1])
fpr, tpr, thresholds = roc_curve(target_valid, probabilities_valid[:, 1])
fig, ax = plt.subplots(1, 2, figsize=(15, 6))
fig.subplots_adjust(hspace=0.4, wspace=0.4)
#fig, ax = plt.subplots(ncols=3)
#fig.subplots_adjust(hspace=0.4, wspace=0.4)
sns.lineplot(recall, precision, drawstyle='steps-post', ax=ax[0])
ax[0].set_xlabel('Recall')
ax[0].set_ylabel('Precision')
ax[0].set_ylim([0.0, 1.05])
ax[0].set_xlim([0.0, 1.0])
ax[0].set_title('Кривая Precision-Recall')
sns.lineplot(fpr, tpr, ax=ax[1])
ax[1].plot([0, 1], [0, 1], linestyle='--')
ax[1].set_xlim(0,1)
ax[1].set_ylim(0,1)
ax[1].set_xlabel('False Positive Rate')
ax[1].set_ylabel('True Positive Rate')
ax[1].set_title('ROC-кривая')
def auc_roc(self, model, features_valid, target_valid):
probabilities_valid = model.predict_proba(features_valid)
probabilities_one_valid = probabilities_valid[:, 1]
auc_roc = roc_auc_score(target_valid, probabilities_one_valid)
return auc_roc
def upsample(self, features, target, repeat):
features_zeros = features[target == 0]
features_ones = features[target == 1]
target_zeros = target[target == 0]
target_ones = target[target == 1]
features_upsampled = pd.concat([features_zeros] + [features_ones] * repeat)
target_upsampled = pd.concat([target_zeros] + [target_ones] * repeat)
features_upsampled, target_upsampled = shuffle(features_upsampled, target_upsampled, random_state=42)
return features_upsampled, target_upsampled
def downsample(self, features, target, fraction):
features_zeros = features[target == 0]
features_ones = features[target == 1]
target_zeros = target[target == 0]
target_ones = target[target == 1]
features_downsampled = pd.concat([features_zeros.sample(frac=fraction, random_state=42)] + [features_ones])
target_downsampled = pd.concat([target_zeros.sample(frac=fraction, random_state=42)] + [target_ones])
features_downsampled, target_downsampled = shuffle(features_downsampled, target_downsampled, random_state=42)
return features_downsampled, target_downsampled
def firstsight(self, data):
head = data.head(5)
tail = data.tail(5)
sample = data.tail(5)
return head, tail, sample
def smape(self, y_test, y_predict):
y_test, y_predict = np.array(y_test), np.array(y_predict)
return np.mean((np.abs((y_predict - y_test)) / np.mean(np.abs(y_test) + np.abs(y_predict + 0.1**99))) * 100)
def mape(self, y_test, y_predict):
y_test, y_predict = np.array(y_test), np.array(y_predict)
return np.median((np.abs((y_test - y_predict)) / (y_test + 0.1**99)) * 100)
def df_split(self, data, features_drop, target, test_size, random_state):
feature = data.drop(features_drop, axis=1)
target = data[target]
x_train, x_test, y_train, y_test = train_test_split(feature, target, test_size=test_size, random_state = random_state)
return x_train, x_test, y_train, y_test
def grid_search(self, model, param_grid, cv, x, y):
grid_model = GridSearchCV(model, param_grid=param_grid, cv=cv, verbose=1, n_jobs=-1)
grid_model.fit(x, y)
best_estimator = grid_model.best_estimator_
return best_estimator
class Display(object):
"""Выводит HTML представление нескольких объектов"""
template = """<div style="float: left; padding: 10px;">
<p style='font-family:"Courier New", Courier, monospace'>{0}</p>{1}
</div>"""
def __init__(self, *args):
self.args = args
def _repr_html_(self):
return '\n'.join(self.template.format(a, eval(a)._repr_html_())
for a in self.args)
def __repr__(self):
return '\n\n'.join(a + '\n' + repr(eval(a))
for a in self.args)
explorer = Explorer()
(... загрузка библиотек и данных ...)
head, tail, sample = explorer.firstsight(df_geo_1)
explorer.Display('head', 'tail', 'sample')
df_geo_1.info()
⚡ Вывод: в датасете с информацией по первому региону 100 000 строк, без пропусков, без проблем с типами данных.
head, tail, sample = explorer.firstsight(df_geo_2)
explorer.Display('head', 'tail', 'sample')
df_geo_2.info()
⚡ Вывод: в датасете с информацией по второму региону 100 000 строк, без пропусков, без проблем с типами данных.
head, tail, sample = explorer.firstsight(df_geo_3)
explorer.Display('head', 'tail', 'sample')
df_geo_3.info()
⚡ Вывод: в датасете с информацией по третьему региону 100 000 строк, без пропусков, без проблем с типами данных.
df_geo_1.describe().T
df_geo_2.describe().T
df_geo_3.describe().T
С учетом характера данных - общая статистика малоинформативна без точного понимания, какие именно атрибуты наблюдений содержаться в столбцах f0, f1, f2.
(df_geo_1.duplicated() == True).sum()
(df_geo_2.duplicated() == True).sum()
(df_geo_3.duplicated() == True).sum()
Полных дубликатов нет
df_geo_1.nunique()
df_geo_2.nunique()
df_geo_3.nunique()
⚡ Вывод:
Исходя из представленных данных и описаний данных, можно сделать следующие выводы:
results = []
lr = LinearRegression(n_jobs=-1)
param_grid={'fit_intercept':[True, False], 'normalize':[True, False], 'copy_X':[True, False]}
x_train, x_test, y_train, y_test = explorer.df_split(df_geo_1, ['id', 'product'], 'product', 0.25, 42)
x_train.shape, y_train.shape
x_test.shape, y_test.shape
lr_geo_one = explorer.grid_search(lr, param_grid, 5, x_train, y_train)
predicted_product = list(lr_geo_one.predict(x_test))
region_1_report = pd.DataFrame(y_test)
region_1_report['predicted'] = predicted_product
region_1_report['region'] = 1
region_1_report.head(5)
explorer.hexbin(region_1_report, 'predicted', 'product')
predicted_mean_product = region_1_report.predicted.mean()
predicted_mean_product
mse = mean_squared_error(y_test, predicted_product)
mse
rmse = np.sqrt(mse)
rmse
mape = explorer.mape(y_test, predicted_product)
mape
results.append(('lr_geo_one', lr_geo_one.score(x_test, y_test), rmse, mape, predicted_mean_product, lr_geo_one))
x_train, x_test, y_train, y_test = explorer.df_split(df_geo_2, ['id', 'product'], 'product', 0.25, 42)
x_train.shape, y_train.shape
x_test.shape, y_test.shape
lr_geo_two = explorer.grid_search(lr, param_grid, 5, x_train, y_train)
predicted_product = list(lr_geo_two.predict(x_test))
region_2_report = pd.DataFrame(y_test)
region_2_report['predicted'] = predicted_product
region_2_report['region'] = 2
region_2_report.head(5)
explorer.hexbin(region_2_report, 'predicted', 'product')
predicted_mean_product = region_2_report.predicted.mean()
predicted_mean_product
mse = mean_squared_error(y_test, predicted_product)
mse
rmse = np.sqrt(mse)
rmse
mape = explorer.mape(y_test, predicted_product)
mape
results.append(('lr_geo_two', lr_geo_two.score(x_test, y_test), rmse, mape, predicted_mean_product, lr_geo_two))
x_train, x_test, y_train, y_test = explorer.df_split(df_geo_3, ['id', 'product'], 'product', 0.25, 42)
x_train.shape, y_train.shape
x_test.shape, y_test.shape
lr_geo_three = explorer.grid_search(lr, param_grid, 5, x_train, y_train)
predicted_product = list(lr_geo_three.predict(x_test))
region_3_report = pd.DataFrame(y_test)
region_3_report['predicted'] = predicted_product
region_3_report['region'] = 3
region_3_report.head(5)
explorer.hexbin(region_3_report, 'predicted', 'product')
predicted_mean_product = region_3_report.predicted.mean()
predicted_mean_product
mse = mean_squared_error(y_test, predicted_product)
mse
rmse = np.sqrt(mse)
rmse
mape = explorer.mape(y_test, predicted_product)
mape
results.append(('lr_geo_three', lr_geo_three.score(x_test, y_test), rmse, mape, predicted_mean_product, lr_geo_three))
results_df = pd.DataFrame(results, columns=['model', 'r2', 'rmse', 'mape', 'predicted_mean_product', 'params'])
results_df[['model', 'r2', 'rmse', 'mape', 'predicted_mean_product']].style.highlight_max(color='lightgreen', axis = 0)
⚡ Вывод:
region_report = pd.concat([region_1_report, region_2_report, region_3_report])
region_report.pivot_table(index='region')
explorer.overlaid_histogram(region_1_report['product'], region_1_report['predicted'], 150,
data1_name='product-data1', data1_color='red',
data2_name='predicted-data2', data2_color='royalblue',
title='Регион 1')
explorer.overlaid_histogram(region_2_report['product'], region_2_report['predicted'], 150,
data1_name='product-data1', data1_color='red',
data2_name='predicted-data2', data2_color='royalblue',
title='Регион 2')
explorer.overlaid_histogram(region_3_report['product'], region_3_report['predicted'], 150,
data1_name='product-data1', data1_color='red',
data2_name='predicted-data2', data2_color='royalblue',
title='Регион 3')
⚡ Вывод: числовые средние двух выборок близки друг к другу до степени совпадения. Равенство средних еще не говорит о принадлежности выборок одной и той же генеральной совокупности. С учетом значений rmse и r2 моделей, принимая во внимание близость средних предсказанных и правильных ответов, опираясь на визуализированные данные, полагаю необходимым проверить однородность распределений для каждой из выборок.
1) Нулевую гипотезу, которую проверяет тест, сформулируем так: "Выборка предсказанных моделью объемов сырья и выборка правильных объемов сырья принадлежат одному закону распределения".
2) Альтернативную гипотезу, соответственно, сформулируем так: "Выборка предсказанных моделью объемов сырья и выборка правильных объемов сырья принадлежат разным законам распределения".
alpha = 0.05
ks_stat, pval_ks = st.ks_2samp(region_1_report['product'], region_1_report['predicted'])
pval_ks
pval_ks < alpha
⚡ Вывод: исходя из представленных данных, на уровне значимости 5% - есть основания отвергнуть нулевую гипотезу в пользу альтернативы. Выборка предсказанных моделью объемов сырья и выборка правильных объемов сырья в отношении региона 1 принадлежат разным законам распределения.
1) Нулевую гипотезу, которую проверяет тест, сформулируем так: "Выборка предсказанных моделью объемов сырья и выборка правильных объемов сырья принадлежат одному закону распределения".
2) Альтернативную гипотезу, соответственно, сформулируем так: "Выборка предсказанных моделью объемов сырья и выборка правильных объемов сырья принадлежат разным законам распределения".
alpha = 0.05
ks_stat, pval_ks = st.ks_2samp(region_2_report['product'], region_2_report['predicted'])
pval_ks
pval_ks < alpha
⚡ Вывод: исходя из представленных данных, на уровне значимости 5% - есть основания отвергнуть нулевую гипотезу в пользу альтернативы. Выборка предсказанных моделью объемов сырья и выборка правильных объемов сырья в отношении региона 2 принадлежат разным законам распределения.
1) Нулевую гипотезу, которую проверяет тест, сформулируем так: "Выборка предсказанных моделью объемов сырья и выборка правильных объемов сырья принадлежат одному закону распределения".
2) Альтернативную гипотезу, соответственно, сформулируем так: "Выборка предсказанных моделью объемов сырья и выборка правильных объемов сырья принадлежат разным законам распределения".
alpha = 0.05
ks_stat, pval_ks = st.ks_2samp(region_3_report['product'], region_3_report['predicted'])
pval_ks
pval_ks < alpha
⚡ Вывод: исходя из представленных данных, на уровне значимости 5% - есть основания отвергнуть нулевую гипотезу в пользу альтернативы. Выборка предсказанных моделью объемов сырья и выборка правильных объемов сырья в отношени региона 3 принадлежат разным законам распределения.
budget = 10**10
budget
well_price = 50 * 10**6
well_price
price_per_barrel = 4500
price_per_barrel
num_of_wells = int(budget / well_price)
num_of_wells
mininmum_mean_product = well_price/price_per_barrel
mininmum_mean_product
⚡ Вывод:
revenue_report = []
bootstrap_region_one = bootstrap(region_1_report['predicted'], 500)
mean_rev = bootstrap_region_one.mean()/10**9
lower = bootstrap_region_one.quantile(0.025)/10**9
confidence_interval = st.t.interval(0.95, len(bootstrap_region_one)-1,
mean_rev, bootstrap_region_one.sem()/10**9)
ax = sns.distplot(bootstrap_region_one)
ax.axvline(np.quantile(bootstrap_region_one, 0.025),linestyle = '--',color='r')
revenue_report.append(('region_1', mean_rev, lower, confidence_interval))
bootstrap_region_two = bootstrap(region_2_report['predicted'], 500)
mean_rev = bootstrap_region_two.mean()/10**9
lower = bootstrap_region_two.quantile(0.025)/10**9
confidence_interval = st.t.interval(0.95, len(bootstrap_region_two)-1,
mean_rev, bootstrap_region_two.sem()/10**9)
ax = sns.distplot(bootstrap_region_one)
ax.axvline(np.quantile(bootstrap_region_two, 0.025),linestyle = '--',color='r')
revenue_report.append(('region_2', mean_rev, lower, confidence_interval))
bootstrap_region_three = bootstrap(region_3_report['predicted'], 500)
mean_rev = bootstrap_region_three.mean()/10**9
lower = bootstrap_region_three.quantile(0.025)/10**9
confidence_interval = st.t.interval(0.95, len(bootstrap_region_three)-1,
mean_rev, bootstrap_region_three.sem()/10**9)
ax = sns.distplot(bootstrap_region_three)
ax.axvline(np.quantile(bootstrap_region_three, 0.025),linestyle = '--',color='r')
revenue_report.append(('region_3', mean_rev, lower, confidence_interval))
revenue_report_df = pd.DataFrame(revenue_report, columns=['region', 'mean_revenue', '2.5%-quantile', '95%_conf_int'])
revenue_report_df.style.highlight_max(color='lightgreen', axis = 0)
Среднняя выручка по регионам:
Исследование показало, что исходя из представленных данных, риск убытков отсутвует, 2.5%-квантиль для регионов: