5.3.5. Iranian Churn Dataset#
5.3.5.1. Summary#
A customer churn dataset from an Iranian telecom company, collected over 12 months [1] and published in the UCI Machine Learning Repository [2]. Each row is a customer, described by usage and account features, with a label indicating whether they churned by the end of the period.
What makes this dataset well suited to value-driven modelling is its Customer Value column,
which Empulse exposes as a per-customer lifetime value (clv). Because the value of retaining a
customer varies, the profit-optimal customer to target is not simply the one most likely to churn —
which is exactly the situation the maximum profit framework was
designed for.
Classes |
2 |
Churners |
495 |
Non-churners |
2755 |
Samples |
3150 |
Features |
12 |
5.3.5.2. Using the Dataset#
The dataset is fetched through fetch_iranian_churn. It is downloaded from
the UCI repository on first use and cached under ~/empulse_data (override with
$EMPULSE_DATA_HOME or the data_home argument), so later calls work offline.
It returns a Dataset object with the following attributes:
data: the feature matrixtarget: the target vectorcost_matrix: aCostMatrixwith default values pre-filledinstance_costs: a dict of per-instance cost drivers ('clv')feature_names: the feature namestarget_names: the target namesDESCR: the full description of the dataset
import pandas as pd
from empulse.datasets import fetch_iranian_churn
dataset = fetch_iranian_churn(backend=pd)
The backend argument selects the dataframe library used for data and target.
Pass the module itself — backend=pd for pandas or backend=pl for polars.
All features are numeric, so a scaler is enough preprocessing for a linear model:
import pandas as pd
from empulse.datasets import fetch_iranian_churn
from empulse.metrics import Metric, Cost
from empulse.models import CSLogitClassifier
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
dataset = fetch_iranian_churn(backend=pd)
X, y = dataset.data, dataset.target
pipeline = Pipeline([
('scaler', StandardScaler()),
('model', CSLogitClassifier(loss=Metric(dataset.cost_matrix, Cost()))),
])
pipeline.fit(X, y, model__clv=dataset.instance_costs['clv'])
Because the cost matrix is the standard churn retention matrix, the dataset also works directly with the prebuilt churn metrics:
from empulse.metrics import empc_score
y_score = pipeline.predict_proba(X)[:, 1]
expected_profit = empc_score(y, y_score, clv=dataset.instance_costs['clv'])
target_fraction = empc_score.optimal_rate(y, y_score, clv=dataset.instance_costs['clv'])
5.3.5.3. Cost Matrix#
Contacting a customer costs a fraction \(f\) of their value, whether or not they accept. A contacted customer accepts the retention offer with probability \(\gamma\), in which case their value is retained minus the incentive, a fraction \(d\) of that value. Losing a customer you did not contact costs their full value.
Actual churner \(y_i = 1\) |
Actual non-churner \(y_i = 0\) |
|
Predicted churner \(\hat{y}_i = 1\) |
|
|
Predicted non-churner \(\hat{y}_i = 0\) |
|
|
The symbolic parameters carry these defaults, and can be overridden by passing their alias:
Alias |
Default |
Meaning |
|---|---|---|
|
0.3 |
Probability a contacted customer accepts the offer |
|
0.05 |
Retention incentive, as a fraction of CLV |
|
0.01 |
Cost of contacting a customer, as a fraction of CLV |
Warning
132 of the 3150 customers have a Customer Value of exactly 0. For those rows every term of
the cost matrix evaluates to 0, which makes the profit-optimal decision undefined for that
customer. As a result optimal_threshold and
optimal_rate raise a ValueError for the
Cost and Savings strategies on this
dataset.
The MaxProfit strategy is unaffected, because it derives the
operating point from the ROC convex hull across the whole population rather than per customer.
Use empc_score.optimal_rate(...) as shown above, or drop the zero-value customers if you
need a cost-based threshold.
5.3.5.4. Data Description#
Feature |
Description |
|---|---|
|
Number of failed calls |
|
Whether the customer filed a complaint (0 = no, 1 = yes) |
|
Total months of subscription |
|
Ordinal attribute from 0 (lowest) to 9 (highest) |
|
Total seconds of calls |
|
Total number of calls |
|
Total number of text messages |
|
Number of distinct phone numbers called |
|
Ordinal age band from 1 (youngest) to 5 (oldest) |
|
Tariff plan (1 = pay as you go, 2 = contractual) |
|
Subscription status (1 = active, 2 = non-active) |
|
Age of the customer in years |
The Customer Value column is not part of data; it is returned separately as
instance_costs['clv'] because it is a cost driver rather than a predictive feature. Using it
as a feature would leak business value into the model instead of into the objective.