Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: calculating d-prime from confusion matrices and samples #8

Open
wants to merge 56 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
340f5d3
MISC: updating .gitignore
hahong Jul 17, 2012
fa13692
ENH: added d-prime calculation from a confusion matrix
hahong Jul 17, 2012
9db9829
Merge branch 'master' of https://github.com/npinto/bangmetric
hahong Jul 17, 2012
547d490
Merge branch 'feature_dprime'
hahong Jul 17, 2012
0b00116
MISC: small cosmetics changes and assertions to check positives and n…
hahong Jul 17, 2012
69f0974
ENH: added d-prime calcualtion function that directly takes sample va…
hahong Jul 18, 2012
43cabf5
MISC: small chanages for 2x2 confusion matrix d' calculation
hahong Jul 18, 2012
5f8f071
MISC: no need to "balance" data for d' calculation
hahong Jul 18, 2012
a056d02
Merge branch 'feature_dprime'
hahong Jul 18, 2012
d6a9cf6
MISC: addressing most stuffs in github.com/npinto/bangmetric/pull/8 (…
hahong Jul 19, 2012
6f5cbac
DOC: small retouches
hahong Jul 19, 2012
afa86fe
COSMIT
hahong Jul 19, 2012
8babb28
COSMIT
hahong Jul 19, 2012
d6ab20b
ENH: more general dprime_from_confusion (thanks, @npinto!)
hahong Jul 19, 2012
3f5eb03
Merge branch 'feature_dprime'
hahong Jul 19, 2012
60814d8
COSMIT
hahong Jul 19, 2012
1d926a2
Merge branch 'feature_dprime'
hahong Jul 19, 2012
056aa5e
ENH: refactoring out a function that computes stats of a confu matrix.
hahong Jul 19, 2012
396224b
COSMIT: refactoring confusion matrix handling part
hahong Jul 19, 2012
dd59101
Merge branch 'feature_utils' into feature_dprime
hahong Jul 19, 2012
c2f53ee
Merge branch 'feature_dprime'
hahong Jul 19, 2012
ad8e3af
COSMIT
hahong Jul 19, 2012
1339ae2
Merge branch 'feature_utils' into feature_dprime
hahong Jul 19, 2012
b1d8b77
DOC: small changes
hahong Jul 19, 2012
99f5354
Merge branch 'feature_utils' into feature_dprime
hahong Jul 19, 2012
b0d58c1
DOC: small changes
hahong Jul 19, 2012
b28f6f3
Merge branch 'feature_utils' into feature_dprime
hahong Jul 19, 2012
0deae47
Merge branch 'feature_dprime'
hahong Jul 19, 2012
15295c5
COSMIT: combined dprime() and dprime_from_samp()
hahong Jul 21, 2012
69f89ec
COSMIT
hahong Jul 21, 2012
f515857
Merge branch 'feature_utils' into feature_dprime
hahong Jul 21, 2012
341d29a
COSMIT
hahong Jul 21, 2012
6d48df3
Merge branch 'feature_dprime'
hahong Jul 21, 2012
de48e46
MISC: small errors and cosmetic changes
hahong Jul 24, 2012
cad170b
MISC: merge dprime_from_confusion_matrix and dprime
hahong Jul 24, 2012
7c47499
Merge branch 'feature_dprime'
hahong Jul 24, 2012
f0a4f1b
DOC: small changes
hahong Jul 24, 2012
2df5287
Merge branch 'master' into feature_utils
hahong Jul 24, 2012
5b07e4c
COSMIT
hahong Jul 24, 2012
962885b
Merge branch 'feature_utils'
hahong Jul 24, 2012
0748c3a
ENH: added metrics for human data
hahong Jul 24, 2012
75e3679
Merge branch 'feature_humans'
hahong Jul 24, 2012
95ed1fc
COSMIT
hahong Jul 24, 2012
8b2f3e6
Merge branch 'feature_humans'
hahong Jul 24, 2012
ab6df56
ENH: added confusion matrix support to accuracy()
hahong Jul 24, 2012
608f869
Merge branch 'feature_machlearning'
hahong Jul 24, 2012
b1dedff
DOC: misc changes
hahong Jul 24, 2012
a5357fc
Merge branch 'feature_dprime'
hahong Jul 24, 2012
2e34b76
TST: fixed bugs in reference value
hahong Jul 24, 2012
dbd5326
Merge branch 'feature_dprime'
hahong Jul 24, 2012
f3fd043
MISC: small changes to clip ppf values in dprime()
hahong Jul 25, 2012
a198d44
Merge branch 'feature_dprime'
hahong Jul 25, 2012
b58a0fe
DOC: small typos..
hahong Jul 25, 2012
e614a6f
Merge branch 'feature_humans'
hahong Jul 25, 2012
9f0cbd7
fixed a bug: np.sort makes a copy while array.sort is inplace
cadieu Oct 26, 2012
d575111
updated installation
qbilius Dec 7, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@
__pycache__
.idea
build
*.DS_Store
*~
.*swp
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# bangmetric

# License

New BSD
4 changes: 4 additions & 0 deletions bangmetric/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@
from rmse import * # pyflakes.ignore
from kernel_analysis import * # pyflakes.ignore
from nk import * # pyflakes.ignore
from utils import * # pyflakes.ignore
from human_metric import * # pyflakes.ignore

__version__ = '0.0.1'
101 changes: 76 additions & 25 deletions bangmetric/accuracy.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,49 +8,100 @@
__all__ = ['accuracy']

import numpy as np
from .utils import confusion_matrix_stats

DEFAULT_ACCURACY_MODE = 'binary'

def accuracy(y_true, y_pred, balanced=False):
"""Computes the Accuracy of the predictions (also known as the
zero-one score).

def accuracy(A, B=None, mode=DEFAULT_ACCURACY_MODE, \
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hu? Why is accuracy changing here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a support for confusion matrices in accuracy() as in dprime(). There are some changes/rearrangements, so might be good to take a look at the whole code.

balanced=False, collation=None):
"""Computes the accuracy of the predictions (also known as the
zero-one score). Depending on the choice of `mode`, this
function can take one of the following data format:

* Binary classification outputs (`mode='binary'`; default)
* Confusion matrix (`mode='confusionmat'`)

Parameters
----------
y_true: array, shape = [n_samples]
True values, interpreted as strictly positive or not
(i.e. converted to binary).
A, B:
If `mode` is 'binary' (default):

A: array, shape = [n_samples]
True values, interpreted as strictly positive or not
(i.e. converted to binary).

B: array, shape = [n_samples]
Predicted values, interpreted as strictly positive or not
(i.e. converted to binary).

y_pred: array, shape = [n_samples]
Predicted values, interpreted as strictly positive or not
(i.e. converted to binary).
if `mode` is 'confusionmat':

A: array-like, shape = [n_classes (true), n_classes (pred)]
Confusion matrix, where the element M_{rc} means
the number of times when the classifier or subject
guesses that a test sample in the r-th class
belongs to the c-th class.

B: ignored

balanced: bool, optional (default=False)
Returns the balanced accuracy (equal weight for positive and
negative values).

collation: None or array-like of shape = [n_groupings,
n_classes], optional (default=None)
Defines how to group entries in `M` to make sub-confusion matrices
when `mode` is 'confusionmat'. See `confusion_matrix_stats()`
for details.

Returns
-------
acc: float
Accuracy (zero-one score).
acc: float or array of shape = [n_groupings]
An accuracy score (zero-one score) or array of accuracies,
where each element corresponds to each grouping of
positives and negatives (when `mode` is 'confusionmat').

References
----------
http://en.wikipedia.org/wiki/Accuracy
"""
assert len(y_true) == len(y_pred)
assert np.isfinite(y_true).all()
assert np.isfinite(y_pred).all()

# -- "binarize" the arguments
y_true = np.array(y_true) > 0
assert y_true.ndim == 1
if mode == 'binary':
y_true, y_pred = A, B
assert len(y_true) == len(y_pred)
assert np.isfinite(y_true).all()
assert np.isfinite(y_pred).all()

# -- "binarize" the arguments
y_true = np.array(y_true) > 0
assert y_true.ndim == 1

y_pred = np.array(y_pred) > 0
assert y_pred.ndim == 1

i_pos = y_true > 0
i_neg = ~i_pos

y_pred = np.array(y_pred) > 0
assert y_pred.ndim == 1
P = float(i_pos.sum())
N = float(i_neg.sum())
TP = float((y_true[i_pos] == y_pred[i_pos]).sum())
TN = float((y_true[i_neg] == y_pred[i_neg]).sum())

elif mode == 'confusionmat':
# A: confusion mat
# row means true classes, col means predicted classes
P, N, TP, TN, _, _ = confusion_matrix_stats(A, \
collation=collation, fudge_mode='none')

else:
raise ValueError('Invalid mode')

if balanced:
pos = y_true > 0
neg = ~pos
pos_acc = (y_true[pos] == y_pred[pos]).mean()
neg_acc = (y_true[neg] == y_pred[neg]).mean()
acc = (pos_acc + neg_acc) / 2.
sensitivity = TP / P
specificity = TN / N
acc = (sensitivity + specificity) / 2.
else:
acc = (y_true == y_pred).mean()
acc = (TP + TN) / (P + N)

return acc
179 changes: 147 additions & 32 deletions bangmetric/dprime.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,61 +2,176 @@

# Authors: Nicolas Pinto <[email protected]>
# Nicolas Poilvert <[email protected]>
# Ha Hong <[email protected]>
#
# License: BSD

__all__ = ['dprime']

import numpy as np
from scipy.stats import norm
from .utils import confusion_matrix_stats

DEFAULT_DPRIME_MODE = 'binary'

def dprime(y_pred, y_true):
"""Computes the d-prime sensitivity index of the predictions.

def dprime(A, B=None, mode=DEFAULT_DPRIME_MODE,\
max_value=np.inf, min_value=-np.inf,\
max_ppf_value=np.inf, min_ppf_value=-np.inf,\
**kwargs):
"""Computes the d-prime sensitivity index of predictions
from various data formats. Depending on the choice of
`mode`, this function can take one of the following format:

* Binary classification outputs (`mode='binary'`; default)
* Positive and negative samples (`mode='sample'`)
* True positive and false positive rate (`mode='rate'`)
* Confusion matrix (`mode='confusionmat'`)

Parameters
----------
y_true: array, shape = [n_samples]
True values, interpreted as strictly positive or not
(i.e. converted to binary).
Could be in {-1, +1} or {0, 1} or {False, True}.
A, B:
If `mode` is 'binary' (default):

A: array, shape = [n_samples],
True values, interpreted as strictly positive or not
(i.e. converted to binary).
Could be in {-1, +1} or {0, 1} or {False, True}.

B: array, shape = [n_samples],
Predicted values (real).

If `mode` is 'sample':

A: array-like,
Positive sample values (e.g., raw projection values
of the positive classifier).

B: array-like,
Negative sample values.

If `mode` is 'rate':

A: array-like, shape = [n_groupings]
True positive rates

B: array-like, shape = [n_groupings]
False positive rates

if `mode` is 'confusionmat':

A: array-like, shape = [n_classes (true), n_classes (pred)]
Confusion matrix, where the element M_{rc} means
the number of times when the classifier or subject
guesses that a test sample in the r-th class
belongs to the c-th class.

B: ignored

mode: {'binary', 'sample', 'rate'}, optional, (default='binary')
Directs the interpretation of A and B.

max_value: float, optional (default=np.inf)
Maximum possible d-prime value.

min_value: float, optional (default=-np.inf)
Minimum possible d-prime value.

max_ppf_value: float, optional (default=np.inf)
Maximum possible ppf value.
Used only when mode is 'rate' or 'confusionmat'.

min_ppf_value: float, optional (default=-np.inf).
Minimum possible ppf value.
Used only when mode is 'rate' or 'confusionmat'.

y_pred: array, shape = [n_samples]
Predicted values (real).
kwargs: named arguments, optional
Passed to ``confusion_matrix_stats()`` and used only when `mode`
is 'confusionmat'. By assigning ``collation``,
``fudge_mode``, ``fudge_factor``, etc. one can
change the behavior of d-prime computation
(see ``confusion_matrix_stats()`` for details).

Returns
-------
dp: float or None
d-prime, None if d-prime is undefined
dp: float or array of shape = [n_groupings]
A d-prime value or array of d-primes, where each element
corresponds to each grouping of positives and negatives
(when `mode` is 'rate' or 'confusionmat')

References
----------
http://en.wikipedia.org/wiki/D'
http://en.wikipedia.org/wiki/Confusion_matrix
"""

# -- basic checks and conversion
assert len(y_true) == len(y_pred)
assert np.isfinite(y_true).all()
assert np.isfinite(y_pred).all()

y_true = np.array(y_true)
assert y_true.ndim == 1

y_pred = np.array(y_pred)
assert y_pred.ndim == 1

# -- actual computation
pos = y_true > 0
neg = ~pos
pos_mean = y_pred[pos].mean()
neg_mean = y_pred[neg].mean()
pos_var = y_pred[pos].var(ddof=1)
neg_var = y_pred[neg].var(ddof=1)

num = pos_mean - neg_mean
div = np.sqrt((pos_var + neg_var) / 2.)
if div == 0:
dp = None
if mode == 'sample':
pos, neg = np.array(A), np.array(B)

elif mode == 'binary':
y_true, y_pred = A, B

assert len(y_true) == len(y_pred)
assert np.isfinite(y_true).all()

y_true = np.array(y_true)
assert y_true.ndim == 1

y_pred = np.array(y_pred)
assert y_pred.ndim == 1

i_pos = y_true > 0
i_neg = ~i_pos

pos = y_pred[i_pos]
neg = y_pred[i_neg]

elif mode == 'rate':
TPR, FPR = np.array(A), np.array(B)
assert TPR.shape == FPR.shape

elif mode == 'confusionmat':
# A: confusion mat
# row means true classes, col means predicted classes
P, N, TP, _, FP, _ = confusion_matrix_stats(A, **kwargs)

TPR = TP / P
FPR = FP / N

else:
raise ValueError('Invalid mode')

# -- compute d'
if mode in ['sample', 'binary']:
assert np.isfinite(pos).all()
assert np.isfinite(neg).all()

if pos.size <= 1:
raise ValueError('Not enough positive samples'\
'to estimate the variance')
if neg.size <= 1:
raise ValueError('Not enough negative samples'\
'to estimate the variance')

pos_mean = pos.mean()
neg_mean = neg.mean()
pos_var = pos.var(ddof=1)
neg_var = neg.var(ddof=1)

num = pos_mean - neg_mean
div = np.sqrt((pos_var + neg_var) / 2.)

dp = num / div

else: # mode is rate or confusionmat
ppfTPR = norm.ppf(TPR)
ppfFPR = norm.ppf(FPR)
ppfTPR = np.clip(ppfTPR, min_ppf_value, max_ppf_value)
ppfFPR = np.clip(ppfFPR, min_ppf_value, max_ppf_value)
dp = ppfTPR - ppfFPR

# from Dan's suggestion about clipping d' values...
dp = np.clip(dp, min_value, max_value)

return dp
Loading