Skip to content

Commit 3c5db73

Browse files
AnneBeyerbetatim
andauthored
DOC Fix typos and improve wording in plot_cost_sensitive_learning.py (scikit-learn#34417)
Co-authored-by: Tim Head <betatim@gmail.com>
1 parent ff88d16 commit 3c5db73

1 file changed

Lines changed: 34 additions & 34 deletions

File tree

examples/model_selection/plot_cost_sensitive_learning.py

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,8 @@ def fpr_score(y, y_pred, neg_label, pos_label):
157157
# - a gain of `-5` for each false negative ("bad" credit labeled as "good"),
158158
# - a `0` gain for true positives and true negatives.
159159
#
160-
# Note that theoretically, given that our model is calibrated and our data
161-
# set representative and large enough, we do not need to tune the
160+
# Note that theoretically, given that our model is calibrated, our dataset
161+
# is representative and large enough, we do not need to tune the
162162
# threshold, but can safely set it to 1/5 of the cost ratio, as stated by
163163
# Eq. (2) in Elkan's paper [2]_.
164164
import numpy as np
@@ -372,20 +372,20 @@ def plot_roc_pr_curves(vanilla_model, tuned_model, *, title):
372372
#
373373
# The second remark is that the cut-off points of the vanilla and tuned model are
374374
# different. To understand why the tuned model has chosen this cut-off point, we can
375-
# look at the right-hand side plot that plots the objective score that is our exactly
375+
# look at the right-hand side plot that plots the objective score that is exactly
376376
# the same as our business metric. We see that the optimum threshold corresponds to the
377377
# maximum of the objective score. This maximum is reached for a decision threshold
378378
# much lower than 0.5: the tuned model enjoys a much higher recall at the cost of
379-
# of significantly lower precision: the tuned model is much more eager to
380-
# predict the "bad" class label to larger fraction of individuals.
379+
# significantly lower precision: the tuned model is much more eager to
380+
# predict the "bad" class label for a larger fraction of individuals.
381381
#
382382
# We can now check if choosing this cut-off point leads to a better score on the testing
383383
# set:
384384
print(f"Business defined metric: {scoring['credit_gain'](tuned_model, X_test, y_test)}")
385385

386386
# %%
387-
# We observe that tuning the decision threshold almost improves our business gains
388-
# by factor of 2.
387+
# We observe that tuning the decision threshold improves our business gains
388+
# by almost a factor of 2.
389389
#
390390
# .. _TunedThresholdClassifierCV_no_cv:
391391
#
@@ -401,8 +401,8 @@ def plot_roc_pr_curves(vanilla_model, tuned_model, *, title):
401401
# These two strategies can be changed by providing the `refit` and `cv` parameters.
402402
# For instance, one could provide a fitted `estimator` and set `cv="prefit"`, in which
403403
# case the cut-off point is found on the entire dataset provided at fitting time.
404-
# Also, the underlying classifier is not be refitted by setting `refit=False`. Here, we
405-
# can try to do such experiment.
404+
# This also requires to set `refit=False`, so that the underlying classifier will not be
405+
# refitted. Here, we can try to do such an experiment:
406406
model.fit(X_train, y_train)
407407
tuned_model.set_params(cv="prefit", refit=False).fit(X_train, y_train)
408408
print(f"{tuned_model.best_threshold_=:0.2f}")
@@ -414,29 +414,31 @@ def plot_roc_pr_curves(vanilla_model, tuned_model, *, title):
414414
plot_roc_pr_curves(model, tuned_model, title=title)
415415

416416
# %%
417-
# We observe the that the optimum cut-off point is different from the one found
417+
# We observe that the optimum cut-off point is different from the one found
418418
# in the previous experiment. If we look at the right-hand side plot, we
419-
# observe that the business gain has large plateau of near-optimal 0 gain for a
420-
# large span of decision thresholds. This behavior is symptomatic of an
421-
# overfitting. Because we disable cross-validation, we tuned the cut-off point
419+
# can see that the business gain has a large plateau of near-optimal 0 gain for a
420+
# large span of decision thresholds. This behavior is symptomatic of
421+
# overfitting. Because we disabled cross-validation, we tuned the cut-off point
422422
# on the same set as the model was trained on, and this is the reason for the
423423
# observed overfitting.
424424
#
425425
# This option should therefore be used with caution. One needs to make sure that the
426426
# data provided at fitting time to the
427427
# :class:`~sklearn.model_selection.TunedThresholdClassifierCV` is not the same as the
428-
# data used to train the underlying classifier. This could happen sometimes when the
428+
# data used to train the underlying classifier. This can be the case when the
429429
# idea is just to tune the predictive model on a completely new validation set without a
430430
# costly complete refit.
431431
#
432432
# When cross-validation is too costly, a potential alternative is to use a
433433
# single train-test split by providing a floating number in range `[0, 1]` to the `cv`
434-
# parameter. It splits the data into a training and testing set. Let's explore this
435-
# option:
434+
# parameter. It splits the data into a training and testing set. (Note that
435+
# `refit=False` is still set, which means the model will not be refitted on the entire
436+
# training set once the threshold has been found.)
437+
# Let's explore this option:
436438
tuned_model.set_params(cv=0.75).fit(X_train, y_train)
437439

438440
# %%
439-
title = "Tuned GBDT model without refitting and using the entire dataset"
441+
title = "Tuned GBDT model using a single train-test split"
440442
plot_roc_pr_curves(model, tuned_model, title=title)
441443

442444
# %%
@@ -467,8 +469,7 @@ def plot_roc_pr_curves(vanilla_model, tuned_model, *, title):
467469
# The dataset contains information about credit card records from which some are
468470
# fraudulent and others are legitimate. The goal is therefore to predict whether or
469471
# not a credit card record is fraudulent.
470-
columns_to_drop = ["Class"]
471-
data = credit_card.frame.drop(columns=columns_to_drop)
472+
data = credit_card.frame.drop(columns=["Class"])
472473
target = credit_card.frame["Class"].astype(int)
473474

474475
# %%
@@ -488,19 +489,18 @@ def plot_roc_pr_curves(vanilla_model, tuned_model, *, title):
488489
# fraudulent transactions.
489490
fraud = target == 1
490491
amount_fraud = data["Amount"][fraud]
491-
_, ax = plt.subplots()
492-
ax.hist(amount_fraud, bins=30)
493-
ax.set_title("Amount of fraud transaction")
494-
_ = ax.set_xlabel("Amount (€)")
492+
_ = amount_fraud.plot.hist(
493+
bins=30, title="Amount of fraud transaction", xlabel="Amount (€)"
494+
)
495495

496496
# %%
497497
# Addressing the problem with a business metric
498498
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
499499
#
500-
# Now, we create the business metric that depends on the amount of each transaction. We
500+
# Now, we create a business metric that depends on the amount of each transaction. We
501501
# define the cost matrix similarly to [2]_. Accepting a legitimate transaction provides
502502
# a gain of 2% of the amount of the transaction. However, accepting a fraudulent
503-
# transaction result in a loss of the amount of the transaction. As stated in [2]_, the
503+
# transaction results in a loss of the amount of the transaction. As stated in [2]_, the
504504
# gain and loss related to refusals (of fraudulent and legitimate transactions) are not
505505
# trivial to define. Here, we define that a refusal of a legitimate transaction
506506
# is estimated to a loss of 5€ while the refusal of a fraudulent transaction is
@@ -521,17 +521,17 @@ def business_metric(y_true, y_pred, amount):
521521

522522

523523
# %%
524-
# From this business metric, we create a scikit-learn scorer that given a fitted
525-
# classifier and a test set compute the business metric. In this regard, we use
526-
# the :func:`~sklearn.metrics.make_scorer` factory. The variable `amount` is an
527-
# additional metadata to be passed to the scorer and we need to use
528-
# :ref:`metadata routing <metadata_routing>` to take into account this information.
524+
# From this business metric, we create a scikit-learn scorer that, given a fitted
525+
# classifier and a test set, computes the business metric. We use the
526+
# :func:`~sklearn.metrics.make_scorer` factory again here. The variable `amount` is an
527+
# additional metadata to be passed to the scorer and we need to use :ref:`metadata
528+
# routing <metadata_routing>` to take this information into account.
529529
sklearn.set_config(enable_metadata_routing=True)
530530
business_scorer = make_scorer(business_metric).set_score_request(amount=True)
531531

532532
# %%
533533
# So at this stage, we observe that the amount of the transaction is used twice: once
534-
# as a feature to train our predictive model and once as a metadata to compute the
534+
# as a feature to train our predictive model and once as metadata to compute the
535535
# the business metric and thus the statistical performance of our model. When used as a
536536
# feature, we are only required to have a column in `data` that contains the amount of
537537
# each transaction. To use this information as metadata, we need to have an external
@@ -573,7 +573,7 @@ def business_metric(y_true, y_pred, amount):
573573

574574

575575
# %%
576-
# Such a policy would entail a catastrophic loss: around 670,000€. This is
576+
# Such a policy would entail a catastrophic loss: almost 700,000€. This is
577577
# expected since the vast majority of the transactions are legitimate and the
578578
# policy would refuse them at a non-trivial cost.
579579
#
@@ -614,7 +614,7 @@ def business_metric(y_true, y_pred, amount):
614614
# Tuning the decision threshold
615615
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
616616
#
617-
# Now the question is: is our model optimum for the type of decision that we want to do?
617+
# Now the question is: is our model optimal for the type of decision that we want to do?
618618
# Up to now, we did not optimize the decision threshold. We use the
619619
# :class:`~sklearn.model_selection.TunedThresholdClassifierCV` to optimize the decision
620620
# given our business scorer. To avoid a nested cross-validation, we will use the
@@ -689,5 +689,5 @@ def business_metric(y_true, y_pred, amount):
689689
# on live data (online evaluation). Note however that A/B testing models is
690690
# beyond the scope of the scikit-learn library itself.
691691
#
692-
# At the end, we disable the configuration flag for metadata routing::
692+
# At the end, we disable the configuration flag for metadata routing:
693693
sklearn.set_config(enable_metadata_routing=False)

0 commit comments

Comments
 (0)