Module ranky.duel
Expand source code
# Module for pairwise comparison of performances of algorithm
################################################
#### Metrics for pairwise methods ####
#### and significance tests ####
################################################
# TODO: clarify names, add scored version of NHST and more.
import numpy as np
from scipy.stats import binom_test
from baycomp import two_on_single, two_on_multiple
def declare_ties(a, b, comparison_func=None, **kwargs):
""" Declare ties between a and b using an assymetrical boolean comparison function in both directions.
Args:
a: Ballot representing one candidate (array-like).
b: Ballot representing one candidate (array-like).
comparison_func: Assymetrical function used to compare two candidates.
The function comparison_func(a, b) should return 1 if a beats b and 0 otherwise.
By default it's p_wins (defined in the same module), performing a binomial test.
reverse: If True, a and b are considered equivalent.
kwargs: Argument of the comparison function.
"""
if comparison_func is None:
comparison_func = p_wins
return comparison_func(a, b, **kwargs) == comparison_func(b, a, **kwargs)
def hard_wins(a, b, reverse=False):
""" Function returning True if a wins against b in a majority vote.
Args:
a: Ballot representing one candidate (array-like).
b: Ballot representing one candidate (array-like).
reverse: If True, lower is better.
"""
a, b = np.array(a), np.array(b)
Wa, Wb = np.sum(a > b), np.sum(b > a)
if reverse:
Wa, Wb = np.sum(a < b), np.sum(b < a)
return Wa > Wb # hard comparisons
def copeland_wins(a, b, reverse=False):
""" Function returning 1 if a wins against b in a majority vote, 0.5 in case of a tie and 0 otherwise.
Useful for to compute Copeland's method.
Args:
a: Ballot representing one candidate (array-like).
b: Ballot representing one candidate (array-like).
reverse: If True, lower is better.
"""
a, b = np.array(a), np.array(b)
Wa, Wb = np.sum(a > b), np.sum(b > a)
if reverse:
Wa, Wb = np.sum(a < b), np.sum(b < a)
if Wa > Wb: # hard comparisons
return 1
elif Wb > Wa:
return 0
else: # Copeland's method
return 0.5
def p_wins(a, b, pval=0.05, reverse=False):
""" Function returning True if a significantly wins against b (binomial test).
Args:
a: Ballot representing one candidate (array-like).
b: Ballot representing one candidate (array-like).
pval: A win is counted only if the probability of the null hypothesis (tie) is equal or smaller than pval.
If pval is set to 1, then p_wins is equivalent to hard_wins function.
reverse: If True, lower is better.
"""
a, b = np.array(a), np.array(b)
Wa, Wb = np.sum(a > b), np.sum(b > a)
if reverse:
Wa, Wb = np.sum(a < b), np.sum(b < a)
significant = binom_test(Wa, n=len(a), p=0.5) <= pval
wins = Wa > Wb
return significant and wins # count only significant wins
def bayes_wins(a, b, width=0.1, independant=False, score=False):
""" Compare a and b using a Bayesian signed-ranks test.
Args:
a: Ballot representing one candidate (array-like).
b: Ballot representing one candidate (array-like).
width: the width of the region of practical equivalence.
independant: True if the different scores are correlated (e.g. bootstraps or cross-validation scores).
score: If True, returns the probability of winning instead of a boolean.
"""
a, b = np.array(a), np.array(b)
if independant:
p_a, p_tie, p_b = two_on_multiple(a, b, rope=width)
else:
p_a, p_tie, p_b = two_on_single(a, b, rope=width)
if score:
res = p_a
else:
res = p_a == max([p_a, p_tie, p_b])
return res
def bayes_score(a, b, **kwargs):
""" Alias for bayes_wins but returning probability of winning.
"""
return bayes_wins(a, b, score=True, **kwargs)
def success_rate(a, b, reverse=False, ties=False):
""" Returns the frequency (rate) of a > b.
Args:
a: Ballot representing one candidate (array-like).
b: Ballot representing one candidate (array-like).
reverse: If True, lower is better.
ties: If True, ties are taken into account (with value 0.5) instead of hard comparisons
"""
a, b = np.array(a), np.array(b)
if not reverse: # normal behavior
Wa = np.sum(a > b)
else:
Wa = np.sum(a < b)
if ties:
Eq = np.sum(a == b)
Wa = Wa + Eq * 0.5
return Wa / len(a) # hard comparisons
def relative_difference(a, b, reverse=False):
""" Returns the mean relative difference between a and b.
Args:
a: Ballot representing one candidate (array-like).
b: Ballot representing one candidate (array-like).
reverse: If True, lower is better.
"""
a, b = np.array(a, dtype='float'), np.array(b, dtype='float')
if reverse:
num = b - a
else:
num = a - b
denom = a + b
s = np.divide(num, denom, out=np.zeros_like(num), where=denom!=0)
return np.mean(s)
################################################
################################################
Functions
def bayes_score(a, b, **kwargs)
-
Alias for bayes_wins but returning probability of winning.
Expand source code
def bayes_score(a, b, **kwargs): """ Alias for bayes_wins but returning probability of winning. """ return bayes_wins(a, b, score=True, **kwargs)
def bayes_wins(a, b, width=0.1, independant=False, score=False)
-
Compare a and b using a Bayesian signed-ranks test.
Args
a
- Ballot representing one candidate (array-like).
b
- Ballot representing one candidate (array-like).
width
- the width of the region of practical equivalence.
independant
- True if the different scores are correlated (e.g. bootstraps or cross-validation scores).
score
- If True, returns the probability of winning instead of a boolean.
Expand source code
def bayes_wins(a, b, width=0.1, independant=False, score=False): """ Compare a and b using a Bayesian signed-ranks test. Args: a: Ballot representing one candidate (array-like). b: Ballot representing one candidate (array-like). width: the width of the region of practical equivalence. independant: True if the different scores are correlated (e.g. bootstraps or cross-validation scores). score: If True, returns the probability of winning instead of a boolean. """ a, b = np.array(a), np.array(b) if independant: p_a, p_tie, p_b = two_on_multiple(a, b, rope=width) else: p_a, p_tie, p_b = two_on_single(a, b, rope=width) if score: res = p_a else: res = p_a == max([p_a, p_tie, p_b]) return res
def copeland_wins(a, b, reverse=False)
-
Function returning 1 if a wins against b in a majority vote, 0.5 in case of a tie and 0 otherwise.
Useful for to compute Copeland's method.
Args
a
- Ballot representing one candidate (array-like).
b
- Ballot representing one candidate (array-like).
reverse
- If True, lower is better.
Expand source code
def copeland_wins(a, b, reverse=False): """ Function returning 1 if a wins against b in a majority vote, 0.5 in case of a tie and 0 otherwise. Useful for to compute Copeland's method. Args: a: Ballot representing one candidate (array-like). b: Ballot representing one candidate (array-like). reverse: If True, lower is better. """ a, b = np.array(a), np.array(b) Wa, Wb = np.sum(a > b), np.sum(b > a) if reverse: Wa, Wb = np.sum(a < b), np.sum(b < a) if Wa > Wb: # hard comparisons return 1 elif Wb > Wa: return 0 else: # Copeland's method return 0.5
def declare_ties(a, b, comparison_func=None, **kwargs)
-
Declare ties between a and b using an assymetrical boolean comparison function in both directions.
Args
a
- Ballot representing one candidate (array-like).
b
- Ballot representing one candidate (array-like).
comparison_func
- Assymetrical function used to compare two candidates.
- The function comparison_func(a, b) should return 1 if a beats b and 0 otherwise.
- By default it's p_wins (defined in the same module), performing a binomial test.
reverse
- If True, a and b are considered equivalent.
kwargs
- Argument of the comparison function.
Expand source code
def declare_ties(a, b, comparison_func=None, **kwargs): """ Declare ties between a and b using an assymetrical boolean comparison function in both directions. Args: a: Ballot representing one candidate (array-like). b: Ballot representing one candidate (array-like). comparison_func: Assymetrical function used to compare two candidates. The function comparison_func(a, b) should return 1 if a beats b and 0 otherwise. By default it's p_wins (defined in the same module), performing a binomial test. reverse: If True, a and b are considered equivalent. kwargs: Argument of the comparison function. """ if comparison_func is None: comparison_func = p_wins return comparison_func(a, b, **kwargs) == comparison_func(b, a, **kwargs)
def hard_wins(a, b, reverse=False)
-
Function returning True if a wins against b in a majority vote.
Args
a
- Ballot representing one candidate (array-like).
b
- Ballot representing one candidate (array-like).
reverse
- If True, lower is better.
Expand source code
def hard_wins(a, b, reverse=False): """ Function returning True if a wins against b in a majority vote. Args: a: Ballot representing one candidate (array-like). b: Ballot representing one candidate (array-like). reverse: If True, lower is better. """ a, b = np.array(a), np.array(b) Wa, Wb = np.sum(a > b), np.sum(b > a) if reverse: Wa, Wb = np.sum(a < b), np.sum(b < a) return Wa > Wb # hard comparisons
def p_wins(a, b, pval=0.05, reverse=False)
-
Function returning True if a significantly wins against b (binomial test).
Args
a
- Ballot representing one candidate (array-like).
b
- Ballot representing one candidate (array-like).
pval
- A win is counted only if the probability of the null hypothesis (tie) is equal or smaller than pval. If pval is set to 1, then p_wins is equivalent to hard_wins function.
reverse
- If True, lower is better.
Expand source code
def p_wins(a, b, pval=0.05, reverse=False): """ Function returning True if a significantly wins against b (binomial test). Args: a: Ballot representing one candidate (array-like). b: Ballot representing one candidate (array-like). pval: A win is counted only if the probability of the null hypothesis (tie) is equal or smaller than pval. If pval is set to 1, then p_wins is equivalent to hard_wins function. reverse: If True, lower is better. """ a, b = np.array(a), np.array(b) Wa, Wb = np.sum(a > b), np.sum(b > a) if reverse: Wa, Wb = np.sum(a < b), np.sum(b < a) significant = binom_test(Wa, n=len(a), p=0.5) <= pval wins = Wa > Wb return significant and wins # count only significant wins
def relative_difference(a, b, reverse=False)
-
Returns the mean relative difference between a and b.
Args
a
- Ballot representing one candidate (array-like).
b
- Ballot representing one candidate (array-like).
reverse
- If True, lower is better.
Expand source code
def relative_difference(a, b, reverse=False): """ Returns the mean relative difference between a and b. Args: a: Ballot representing one candidate (array-like). b: Ballot representing one candidate (array-like). reverse: If True, lower is better. """ a, b = np.array(a, dtype='float'), np.array(b, dtype='float') if reverse: num = b - a else: num = a - b denom = a + b s = np.divide(num, denom, out=np.zeros_like(num), where=denom!=0) return np.mean(s)
def success_rate(a, b, reverse=False, ties=False)
-
Returns the frequency (rate) of a > b.
Args
a
- Ballot representing one candidate (array-like).
b
- Ballot representing one candidate (array-like).
reverse
- If True, lower is better.
ties
- If True, ties are taken into account (with value 0.5) instead of hard comparisons
Expand source code
def success_rate(a, b, reverse=False, ties=False): """ Returns the frequency (rate) of a > b. Args: a: Ballot representing one candidate (array-like). b: Ballot representing one candidate (array-like). reverse: If True, lower is better. ties: If True, ties are taken into account (with value 0.5) instead of hard comparisons """ a, b = np.array(a), np.array(b) if not reverse: # normal behavior Wa = np.sum(a > b) else: Wa = np.sum(a < b) if ties: Eq = np.sum(a == b) Wa = Wa + Eq * 0.5 return Wa / len(a) # hard comparisons