CostMatrix#

class empulse.metrics.CostMatrix[source]#

Class to create a custom value/cost-sensitive cost matrix.

You add the costs and benefits that make up the cost matrix for each case (true positive, true negative, false positive, false negative). The costs and benefits are specified using sympy symbols or expressions. Stochastic variables are supported and can be specified using sympy.stats random variables. Stochastic variables are assumed to be independent of each other.

Read more in the User Guide.

Attributes:
tp_benefitsympy.Expr

The benefit of a true positive. See add_tp_benefit for more details.

tn_benefitsympy.Expr

The benefit of a true negative. See add_tn_benefit for more details.

fp_benefitsympy.Expr

The benefit of a false positive. See add_fp_benefit for more details.

fn_benefitsympy.Expr

The benefit of a false negative. See add_fn_benefit for more details.

tp_costsympy.Expr

The cost of a true positive. See add_tp_cost for more details.

tn_costsympy.Expr

The cost of a true negative. See add_tn_cost for more details.

fp_costsympy.Expr

The cost of a false positive. See add_fp_cost for more details.

fn_costsympy.Expr

The cost of a false negative. See add_fn_cost for more details.

Examples

Reimplementing the empc_score cost matrix.

import sympy as sp
from empulse.metrics import CostMatrix

clv, d, f, alpha, beta = sp.symbols(
    'clv d f alpha beta'
)  # define deterministic variables
gamma = sp.stats.Beta('gamma', alpha, beta)  # define gamma to follow a Beta distribution

cost_matrix = (
    CostMatrix()
    .add_tp_benefit(gamma * (clv - d - f))  # when churner accepts offer
    .add_tp_benefit((1 - gamma) * -f)  # when churner does not accept offer
    .add_fp_cost(d + f)  # when you send an offer to a non-churner
    .alias({'incentive_cost': 'd', 'contact_cost': 'f'})
)
add_fn_benefit(term)[source]#

Add a term to the benefit of classifying a false negative.

Parameters:
termsympy.Expr | str

The term to add to the benefit of classifying a false negative.

Returns:
CostMatrix

The cost matrix, to allow method chaining.

add_fn_cost(term)[source]#

Add a term to the cost of classifying a false negative.

Parameters:
termsympy.Expr | str

The term to add to the cost of classifying a false negative.

Returns:
CostMatrix

The cost matrix, to allow method chaining.

add_fp_benefit(term)[source]#

Add a term to the benefit of classifying a false positive.

Parameters:
termsympy.Expr | str

The term to add to the benefit of classifying a false positive.

Returns:
CostMatrix

The cost matrix, to allow method chaining.

add_fp_cost(term)[source]#

Add a term to the cost of classifying a false positive.

Parameters:
termsympy.Expr | str

The term to add to the cost of classifying a false positive.

Returns:
CostMatrix

The cost matrix, to allow method chaining.

add_tn_benefit(term)[source]#

Add a term to the benefit of classifying a true negative.

Parameters:
termsympy.Expr | str

The term to add to the benefit of classifying a true negative.

Returns:
CostMatrix

The cost matrix, to allow method chaining.

add_tn_cost(term)[source]#

Add a term to the cost of classifying a true negative.

Parameters:
termsympy.Expr | str

The term to add to the cost of classifying a true negative.

Returns:
CostMatrix

The cost matrix, to allow method chaining.

add_tp_benefit(term)[source]#

Add a term to the benefit of classifying a true positive.

Parameters:
termsympy.Expr | str

The term to add to the benefit of classifying a true positive.

Returns:
CostMatrix

The cost matrix, to allow method chaining.

add_tp_cost(term)[source]#

Add a term to the cost of classifying a true positive.

Parameters:
termsympy.Expr | str

The term to add to the cost of classifying a true positive.

Returns:
CostMatrix

The cost matrix, to allow method chaining.

alias(alias, symbol=None)[source]#

Add an alias for a symbol.

Parameters:
aliasstr | MutableMapping[str, sympy.Symbol | str]

The alias to add. If a MutableMapping (e.g., dictionary) is passed, the keys are the aliases and the values are the symbols.

symbolsympy.Symbol, optional

The symbol to alias to. Required unless alias is a mapping.

Returns:
CostMatrix

The cost matrix, to allow method chaining.

Raises:
TypeError

If a mapping value is not a str or sympy.Symbol.

ValueError

If neither a mapping nor both an alias and a symbol are given.

Examples

import sympy as sp
from empulse.metrics import CostMatrix, Metric, Cost

clv, delta, f, gamma = sp.symbols('clv delta f gamma')
cost_matrix = (
    CostMatrix()
    .add_tp_benefit(gamma * (clv - delta * clv - f))  # when churner accepts offer
    .add_tp_benefit((1 - gamma) * -f)  # when churner does not accept offer
    .add_fp_cost(delta * clv + f)  # when you send an offer to a non-churner
    .alias({'incentive_fraction': 'delta', 'contact_cost': 'f', 'accept_rate': 'gamma'})
)
cost_loss = Metric(cost_matrix, Cost())

y_true = [1, 0, 1, 0, 1]
y_proba = [0.9, 0.1, 0.8, 0.2, 0.7]
cost_loss(
    y_true, y_proba, clv=100, incentive_fraction=0.05, contact_cost=1, accept_rate=0.3
)
constrain(target, lower=None, upper=None, *, message=None)[source]#

Restrict the values a parameter is allowed to take.

Constraints are checked when the metric is called, and when a model fitting on this metric first receives its parameters. A violation raises a ValueError.

Two forms are supported. Passing a symbol (or alias) with lower and/or upper bounds the values of that one parameter. Passing a callable expresses a condition over several parameters at once.

Parameters:
targetstr, sympy.Symbol or callable

The symbol or alias to bound, or a callable taking the mapping of resolved parameter values and returning whether they are acceptable.

lowerfloat, optional

Smallest allowed value, inclusive. Only used when target is a symbol or alias.

upperfloat, optional

Largest allowed value, inclusive. Only used when target is a symbol or alias.

messagestr, optional

Explanation to report when a callable target rejects the parameters. Required when target is a callable.

Returns:
selfCostMatrix

The cost matrix with the constraint added.

Raises:
TypeError

If target is neither a str, a sympy.Symbol, nor a callable.

ValueError

If target is a symbol and neither lower nor upper is given, if lower is greater than upper, or if target is a callable and message is not given.

Notes

Bounds are stored against the resolved symbol, so call alias before constrain if you want to constrain a parameter by its alias.

A callable receives the parameters keyed by symbol name, with aliases already resolved and defaults already applied.

Distribution parameters are validated automatically and do not need a constraint: the shape of a sympy.stats random variable is checked by the distribution itself, so alpha=-1 on a Beta-distributed term is rejected without any declaration here.

Examples

Bound a probability to the unit interval:

import sympy as sp
from empulse.metrics import CostMatrix, Metric, MaxProfit

clv, d, f, gamma = sp.symbols('clv d f gamma')
cost_matrix = (
    CostMatrix()
    .add_tp_benefit(gamma * (clv - d - f))
    .add_fp_cost(d + f)
    .alias('accept_rate', gamma)
    .constrain('accept_rate', 0, 1)
)
metric = Metric(cost_matrix, MaxProfit())

Express a condition spanning several parameters:

import sympy as sp
from empulse.metrics import CostMatrix, Metric, MaxProfit

clv, d, f, gamma = sp.symbols('clv d f gamma')
cost_matrix = (
    CostMatrix()
    .add_tp_benefit(gamma * (clv - d - f))
    .add_fp_cost(d + f)
    .alias({'incentive_cost': 'd'})
    .constrain(
        lambda params: params['clv'] > params['d'],
        message='clv must exceed the incentive cost',
    )
)
metric = Metric(cost_matrix, MaxProfit())
mark_outlier_sensitive(symbol)[source]#

Mark a symbol as outlier-sensitive.

This is used to indicate that the symbol is sensitive to outliers. When the metric is used as a loss function or criterion for training a model, RobustCSClassifier will impute outliers for this symbol’s value. This is ignored when not using a RobustCSClassifier model.

Parameters:
symbolstr | sympy.Symbol

The symbol to mark as outlier-sensitive.

Returns:
CostMatrix

The cost matrix, to allow method chaining.

Raises:
TypeError

If symbol is not a str or sympy.Symbol.

Examples

import numpy as np
import sympy as sp
from empulse.metrics import CostMatrix, Metric, Cost
from empulse.models import CSLogitClassifier, RobustCSClassifier
from sklearn.datasets import make_classification

X, y = make_classification()
a, b = sp.symbols('a b')
cost_matrix = CostMatrix().add_fp_cost(a).add_fn_cost(b).mark_outlier_sensitive(a)
cost_loss = Metric(cost_matrix, Cost())

model = RobustCSClassifier(CSLogitClassifier(loss=cost_loss))
model.fit(X, y, a=np.random.rand(y.size), b=5)
set_default(**defaults)[source]#

Set default values for symbols or their aliases.

Parameters:
**defaultsfloat

Default values for symbols or their aliases. These default values will be used if not provided in __call__.

Returns:
CostMatrix

The cost matrix, to allow method chaining.

Notes

If you want to set a default using an alias name, you must call alias before calling set_default. Defaults passed via alias names are immediately resolved to their underlying symbol names during this call; any alias registered afterwards will not retroactively match previously stored defaults.

Examples

import sympy as sp
from empulse.metrics import CostMatrix, Metric, Cost

clv, delta, f, gamma = sp.symbols('clv delta f gamma')
cost_matrix = (
    CostMatrix()
    .add_tp_benefit(gamma * (clv - delta * clv - f))  # when churner accepts offer
    .add_tp_benefit((1 - gamma) * -f)  # when churner does not accept offer
    .add_fp_cost(delta * clv + f)  # when you send an offer to a non-churner
    .alias({'incentive_fraction': 'delta', 'contact_cost': 'f', 'accept_rate': 'gamma'})
    .set_default(incentive_fraction=0.05, contact_cost=1, accept_rate=0.3)
)
cost_loss = Metric(cost_matrix, Cost())

y_true = [1, 0, 1, 0, 1]
y_proba = [0.9, 0.1, 0.8, 0.2, 0.7]
cost_loss(y_true, y_proba, clv=100, incentive_fraction=0.1)