Ignore:
Timestamp:
03/02/12 12:16:43 (2 years ago)
Author:
anzeh <anze.staric@…>
Branch:
default
Message:

Scoring functions ((based on confusion matrices) are now classes, but documented as functions.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • Orange/evaluation/scoring.py

    r10422 r10425  
    770770    return ss, df, statc.chisqprob(ss, df) 
    771771 
    772 @deprecated_keywords({"confm": "confusion_matrix"}) 
    773 def sens(confusion_matrix): 
    774     """ 
    775     Return `sensitivity 
     772class CMScore(list): 
     773    """ 
     774    :param test_results: :obj:`~Orange.evaluation.testing.ExperimentResults` 
     775                         or list of :obj:`ConfusionMatrix`. 
     776    :rtype: list of scores, one for each learner.""" 
     777    @deprecated_keywords({"confm": "test_results"}) 
     778    def __init__(self, test_results=None): 
     779        super(CMScore, self).__init__() 
     780 
     781        if test_results is not None: 
     782            self[:] = self.__call__(test_results) 
     783 
     784    def __call__(self, test_results): 
     785        if isinstance(test_results, testing.ExperimentResults): 
     786            test_results = confusion_matrices(test_results, class_index=1) 
     787        if isinstance(test_results, ConfusionMatrix): 
     788            test_results = [test_results] 
     789 
     790        return map(self.compute, test_results) 
     791 
     792 
     793 
     794class Sensitivity(CMScore): 
     795    __doc__ = """Compute `sensitivity 
    776796    <http://en.wikipedia.org/wiki/Sensitivity_and_specificity>`_ (proportion 
    777797    of actual positives which are correctly identified as such). 
    778     """ 
    779     if type(confusion_matrix) == list: 
    780         return [sens(cm) for cm in confusion_matrix] 
    781     else: 
     798    """ + CMScore.__doc__ 
     799    @classmethod 
     800    def compute(self, confusion_matrix): 
    782801        tot = confusion_matrix.TP+confusion_matrix.FN 
    783802        if tot < 1e-6: 
    784803            import warnings 
    785804            warnings.warn("Can't compute sensitivity: one or both classes have no instances") 
    786             return -1 
     805            return None 
    787806 
    788807        return confusion_matrix.TP / tot 
    789808 
    790809 
    791 @deprecated_keywords({"confm": "confusion_matrix"}) 
    792 def recall(confusion_matrix): 
    793     """ 
    794     Return `recall <http://en.wikipedia.org/wiki/Precision_and_recall>`_ 
     810class Recall(Sensitivity): 
     811    __doc__ = """ Compute `recall 
     812    <http://en.wikipedia.org/wiki/Precision_and_recall>`_ 
    795813    (fraction of relevant instances that are retrieved). 
    796     """ 
    797     return sens(confusion_matrix) 
    798  
    799  
    800 @deprecated_keywords({"confm": "confusion_matrix"}) 
    801 def spec(confusion_matrix): 
    802     """ 
    803     Return `specificity 
     814    """ + CMScore.__doc__ 
     815    pass # Recall == Sensitivity 
     816 
     817 
     818class Specificity(CMScore): 
     819    __doc__ = """Compute `specificity 
    804820    <http://en.wikipedia.org/wiki/Sensitivity_and_specificity>`_ 
    805821    (proportion of negatives which are correctly identified). 
    806     """ 
    807     if type(confusion_matrix) == list: 
    808         return [spec(cm) for cm in confusion_matrix] 
    809     else: 
     822    """ + CMScore.__doc__ 
     823    @classmethod 
     824    def compute(self, confusion_matrix): 
    810825        tot = confusion_matrix.FP+confusion_matrix.TN 
    811826        if tot < 1e-6: 
    812827            import warnings 
    813828            warnings.warn("Can't compute specificity: one or both classes have no instances") 
    814             return -1 
     829            return None 
    815830        return confusion_matrix.TN / tot 
    816831 
    817832 
    818 @deprecated_keywords({"confm": "confusion_matrix"}) 
    819 def PPV(confusion_matrix): 
    820     """ 
    821     Return `positive predictive value 
     833class PPV(CMScore): 
     834    __doc__ = """Compute `positive predictive value 
    822835    <http://en.wikipedia.org/wiki/Positive_predictive_value>`_ (proportion of 
    823     subjects with positive test results who are correctly diagnosed).""" 
    824     if type(confusion_matrix) == list: 
    825         return [PPV(cm) for cm in confusion_matrix] 
    826     else: 
     836    subjects with positive test results who are correctly diagnosed). 
     837    """ + CMScore.__doc__ 
     838    @classmethod 
     839    def compute(self, confusion_matrix): 
    827840        tot = confusion_matrix.TP + confusion_matrix.FP 
    828841        if tot < 1e-6: 
    829842            import warnings 
    830843            warnings.warn("Can't compute PPV: one or both classes have no instances") 
    831             return -1 
     844            return None 
    832845        return confusion_matrix.TP/tot 
    833846 
    834847 
    835 @deprecated_keywords({"confm": "confusion_matrix"}) 
    836 def precision(confusion_matrix): 
    837     """ 
    838     Return `precision <http://en.wikipedia.org/wiki/Precision_and_recall>`_ 
     848class Precision(PPV): 
     849    __doc__ = """Compute `precision <http://en.wikipedia.org/wiki/Precision_and_recall>`_ 
    839850    (retrieved instances that are relevant). 
    840     """ 
    841     return PPV(confusion_matrix) 
    842  
    843 @deprecated_keywords({"confm": "confusion_matrix"}) 
    844 def NPV(confusion_matrix): 
    845     """ 
    846     Return `negative predictive value 
     851    """ + CMScore.__doc__ 
     852    pass # Precision == PPV 
     853 
     854 
     855class NPV(CMScore): 
     856    __doc__ = """Compute `negative predictive value 
    847857    <http://en.wikipedia.org/wiki/Negative_predictive_value>`_ (proportion of 
    848858    subjects with a negative test result who are correctly diagnosed). 
    849      """ 
    850     if type(confusion_matrix) == list: 
    851         return [NPV(cm) for cm in confusion_matrix] 
    852     else: 
     859     """ + CMScore.__doc__ 
     860    @classmethod 
     861    def compute(self, confusion_matrix): 
    853862        tot = confusion_matrix.FN + confusion_matrix.TN 
    854863        if tot < 1e-6: 
    855864            import warnings 
    856865            warnings.warn("Can't compute NPV: one or both classes have no instances") 
    857             return -1 
     866            return None 
    858867        return confusion_matrix.TN / tot 
    859868 
    860 @deprecated_keywords({"confm": "confusion_matrix"}) 
    861 def F1(confusion_matrix): 
    862     """ 
    863     Return `F1 score <http://en.wikipedia.org/wiki/F1_score>`_ 
     869 
     870class F1(CMScore): 
     871    __doc__ = """Return `F1 score 
     872    <http://en.wikipedia.org/wiki/F1_score>`_ 
    864873    (harmonic mean of precision and recall). 
    865     """ 
    866     if type(confusion_matrix) == list: 
    867         return [F1(cm) for cm in confusion_matrix] 
    868     else: 
    869         p = precision(confusion_matrix) 
    870         r = recall(confusion_matrix) 
    871         if p + r > 0: 
     874    """ + CMScore.__doc__ 
     875    @classmethod 
     876    def compute(self, confusion_matrix): 
     877        p = Precision.compute(confusion_matrix) 
     878        r = Recall.compute(confusion_matrix) 
     879        if p is not None and r is not None and (p+r) != 0: 
    872880            return 2. * p * r / (p + r) 
    873881        else: 
    874882            import warnings 
    875883            warnings.warn("Can't compute F1: P + R is zero or not defined") 
    876             return -1 
    877  
    878  
    879 @deprecated_keywords({"confm": "confusion_matrix"}) 
    880 def Falpha(confusion_matrix, alpha=1.0): 
    881     """ 
    882     Return the alpha-mean of precision and recall over the given confusion 
     884            return None 
     885 
     886 
     887class Falpha(CMScore): 
     888    __doc__ = """Compute the alpha-mean of precision and recall over the given confusion 
    883889    matrix. 
    884     """ 
    885     if type(confusion_matrix) == list: 
    886         return [Falpha(cm, alpha=alpha) for cm in confusion_matrix] 
    887     else: 
    888         p = precision(confusion_matrix) 
    889         r = recall(confusion_matrix) 
    890         return (1. + alpha) * p * r / (alpha * p + r) 
    891  
    892  
    893 @deprecated_keywords({"confm": "confusion_matrix"}) 
    894 def MCC(confusion_matrix): 
    895     """ 
    896     Return `Matthew correlation coefficient 
     890    """ + CMScore.__doc__ 
     891 
     892    def __init__(self, test_results, alpha=1.): 
     893        self.alpha = alpha 
     894        super(Falpha, self).__init__(test_results) 
     895 
     896    def compute(self, confusion_matrix): 
     897        p = Precision.compute(confusion_matrix) 
     898        r = Recall.compute(confusion_matrix) 
     899        return (1. + self.alpha) * p * r / (self.alpha * p + r) 
     900 
     901 
     902class MCC(CMScore): 
     903    __doc__ = """Compute `Matthew correlation coefficient 
    897904    <http://en.wikipedia.org/wiki/Matthews_correlation_coefficient>`_ 
    898905    (correlation coefficient between the observed and predicted binary 
    899906    classifications). 
    900     """ 
    901     # code by Boris Gorelik 
    902     if type(confusion_matrix) == list: 
    903         return [MCC(cm) for cm in confusion_matrix] 
    904     else: 
    905         truePositive = confusion_matrix.TP 
    906         trueNegative = confusion_matrix.TN 
    907         falsePositive = confusion_matrix.FP 
    908         falseNegative = confusion_matrix.FN 
     907    """ + CMScore.__doc__ 
     908    @classmethod 
     909    def compute(self, cm): 
     910        # code by Boris Gorelik 
     911        TP, TN, FP, FN = cm.TP, cm.TN, cm.FP, cm.FN 
    909912           
    910         try:    
    911             r = (((truePositive * trueNegative) - 
    912                   (falsePositive * falseNegative)) / 
    913                  math.sqrt((truePositive + falsePositive) * 
    914                            (truePositive + falseNegative) *  
    915                            (trueNegative + falsePositive) *  
    916                            (trueNegative + falseNegative))) 
     913        try: 
     914            return (TP*TN - FP*FN) /\ 
     915                 math.sqrt((TP+FP) * (TP+FN) * (TN+ FP) * (TN+FN)) 
    917916        except ZeroDivisionError: 
    918             # Zero difision occurs when there is either no true positives  
     917            # Zero division occurs when there is either no true positives 
    919918            # or no true negatives i.e. the problem contains only one  
    920919            # type of classes. 
    921920            import warnings 
    922921            warnings.warn("Can't compute MCC: TP or TN is zero or not defined") 
    923             r = None 
    924  
    925     return r 
    926922 
    927923 
     
    976972       ret = (prActual - prExpected) / (1.0 - prExpected) 
    977973       return ret 
     974 
     975# Backward compatibility 
     976sens = Sensitivity 
     977spec = Specificity 
     978precision = Precision 
     979recall = Recall 
     980 
     981 
    978982 
    979983@deprecated_keywords({"classIndex": "class_index", 
Note: See TracChangeset for help on using the changeset viewer.