<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-

__author__ = 'leferrad'

# Dependencias externas
import numpy as np


class ClassificationMetrics(object):
    """
    Métricas para evaluar en problemas de clasificación

    :param predicted_actual: list de (predicted, actual) pairs
    :param n_classes: int cantidad de clases

    >>> predict = [0, 1, 0, 2, 2, 1]
    >>> labels = [0, 1, 1, 2, 1, 0]
    >>> metrics = ClassificationMetrics(zip(predict, labels), 3)
    >>> metrics.accuracy()
    0.5
    >>> metrics.f_measure()
    0.5499999999999999
    >>> metrics.precision()
    0.5
    >>> metrics.recall()
    0.611111111111111
    >>> metrics.confusion_matrix()
    array([[1, 1, 0],
           [1, 1, 1],
           [0, 0, 1]])
    """

    # Ver http://machine-learning.tumblr.com/post/1209400132/mathematical-definitions-for-precisionrecall-for
    # Ver http://rali.iro.umontreal.ca/rali/sites/default/files/publis/SokolovaLapalme-JIPM09.pdf
    def __init__(self, predicted_actual, n_classes):
        self.predicted_actual = predicted_actual
        self.tp = []
        self.fp = []
        self.fn = []
        for c in xrange(n_classes):
            self.tp.append(sum(map(lambda (p, a): p == c and a == c, predicted_actual)))
            self.fp.append(sum(map(lambda (p, a): p == c and a != c, predicted_actual)))
            self.fn.append(sum(map(lambda (p, a): p != c and a == c, predicted_actual)))
        self.n_classes = n_classes
        self.n_elem = len(predicted_actual)

    def accuracy(self, label=None):
        """
        Calcula la exactitud de la clasificación, dada por la cantidad de aciertos sobre el total.

        :param label: int entre {0,C} para indicar sobre qué clase evaluar. Si es *None* se evalúa sobre todas.
        :return: double
        """
        if label is None:
            acc = sum(map(lambda (pre, act): pre == act, self.predicted_actual)) / float(self.n_elem)
        else:
            acc = self.tp[label] / float(self.tp[label] + self.fp[label] + self.fn[label])
        return acc

    def precision(self, label=None, macro=True):
        """
        Calcula la precisión de la clasificación, dado por la cantidad de **verdaderos positivos**
        (i.e. el número de items correctamente clasificados) dividido por el total de elementos clasificados
        para una clase dada (i.e. la suma de los verdaderos positivos y **falsos positivos**, que son los
        items incorrectamente clasificados de dicha clase). Ello se resume en la siguiente fórmula:

        :math:`P_i=\\dfrac{TP_i}{TP_i+FP_i}`

        :param label: int entre {0,n_classes - 1} para indicar sobre qué clase evaluar. Si es *None* se evalúa sobre todas.
        :param macro: bool, que indica cómo calcular el **precision** sobre todas las clases (True para que sea *macro* y False para que sea *micro*).

        Siendo C la cantidad de clases, las fórmulas son:

        :math:`P_{micro}=\\dfrac{\sum_{i=0}^{C-1} TP_i}{\sum_i TP_i+FP_i}`

        :math:`P_{macro}=\\dfrac{1}{C}\\sum_{i=0}^{C-1} \\frac{TP_i}{TP_i+FP_i}`

        :return: double
        """

        if label is None:
            if macro is True:
                p = sum([self.precision(c) for c in xrange(self.n_classes)])
                p /= float(self.n_classes)
            else:
                p = sum(self.tp) / float(sum(map(lambda (tp, fp): tp + fp, zip(self.tp, self.fp))))
        else:
            if self.tp[label] == 0.0 and self.fp[label] == 0.0:
                p = 1.0
            else:
                p = self.tp[label] / float(self.tp[label] + self.fp[label])
        return p

    def recall(self, label=None, macro=True):
        if label is None:
            if macro is True:
                r = sum([self.recall(c) for c in xrange(self.n_classes)])
                r /= float(self.n_classes)
            else:
                r = sum(self.tp) / float(sum(map(lambda (tp, fn): tp + fn, zip(self.tp, self.fn))))
        else:
            if self.tp[label] == 0.0 and self.fn[label] == 0.0:
                r = 1.0
            else:
                r = self.tp[label] / float(self.tp[label] + self.fn[label])
        return r

    def f_measure(self, beta=1, label=None, macro=True):
        ppv = self.precision(label, macro)
        tpr = self.recall(label, macro)
        f_score = (1 + beta*beta)*(ppv * tpr) / (beta*beta*ppv + tpr)
        return f_score

    def confusion_matrix(self):
        """
        Matriz de confusión resultante, donde las columnas corresponden a *predicted*
        y están ordenadas en forma ascendente por casa clase de *actual*.<commit_msg>Corrijo calculo de F-measure en ClassificationMetrics<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-

__author__ = 'leferrad'

# Dependencias externas
import numpy as np


class ClassificationMetrics(object):
    """
    Métricas para evaluar en problemas de clasificación

    :param predicted_actual: list de (predicted, actual) pairs
    :param n_classes: int cantidad de clases

    >>> predict = [0, 1, 0, 2, 2, 1]
    >>> labels = [0, 1, 1, 2, 1, 0]
    >>> metrics = ClassificationMetrics(zip(predict, labels), 3)
    >>> metrics.accuracy()
    0.5
    >>> metrics.f_measure()
    0.5499999999999999
    >>> metrics.precision()
    0.5
    >>> metrics.recall()
    0.611111111111111
    >>> metrics.confusion_matrix()
    array([[1, 1, 0],
           [1, 1, 1],
           [0, 0, 1]])
    """

    # Ver http://machine-learning.tumblr.com/post/1209400132/mathematical-definitions-for-precisionrecall-for
    # Ver http://rali.iro.umontreal.ca/rali/sites/default/files/publis/SokolovaLapalme-JIPM09.pdf
    def __init__(self, predicted_actual, n_classes):
        self.predicted_actual = predicted_actual
        self.tp = []
        self.fp = []
        self.fn = []
        for c in xrange(n_classes):
            self.tp.append(sum(map(lambda (p, a): p == c and a == c, predicted_actual)))
            self.fp.append(sum(map(lambda (p, a): p == c and a != c, predicted_actual)))
            self.fn.append(sum(map(lambda (p, a): p != c and a == c, predicted_actual)))
        self.n_classes = n_classes
        self.n_elem = len(predicted_actual)

    def accuracy(self, label=None):
        """
        Calcula la exactitud de la clasificación, dada por la cantidad de aciertos sobre el total.

        :param label: int entre {0,C} para indicar sobre qué clase evaluar. Si es *None* se evalúa sobre todas.
        :return: double
        """
        if label is None:
            acc = sum(map(lambda (pre, act): pre == act, self.predicted_actual)) / float(self.n_elem)
        else:
            acc = self.tp[label] / float(self.tp[label] + self.fp[label] + self.fn[label])
        return acc

    def precision(self, label=None, macro=True):
        """
        Calcula la precisión de la clasificación, dado por la cantidad de **verdaderos positivos**
        (i.e. el número de items correctamente clasificados) dividido por el total de elementos clasificados
        para una clase dada (i.e. la suma de los verdaderos positivos y **falsos positivos**, que son los
        items incorrectamente clasificados de dicha clase). Ello se resume en la siguiente fórmula:

        :math:`P_i=\\dfrac{TP_i}{TP_i+FP_i}`

        :param label: int entre {0,n_classes - 1} para indicar sobre qué clase evaluar. Si es *None* se evalúa sobre todas.
        :param macro: bool, que indica cómo calcular el **precision** sobre todas las clases (True para que sea *macro* y False para que sea *micro*).

        Siendo C la cantidad de clases, las fórmulas son:

        :math:`P_{micro}=\\dfrac{\sum_{i=0}^{C-1} TP_i}{\sum_i TP_i+FP_i}`

        :math:`P_{macro}=\\dfrac{1}{C}\\sum_{i=0}^{C-1} \\frac{TP_i}{TP_i+FP_i}`

        :return: double
        """

        if label is None:
            if macro is True:
                p = sum([self.precision(c) for c in xrange(self.n_classes)])
                p /= float(self.n_classes)
            else:
                p = sum(self.tp) / float(sum(map(lambda (tp, fp): tp + fp, zip(self.tp, self.fp))))
        else:
            if self.tp[label] == 0.0 and self.fp[label] == 0.0:
                p = 1.0
            else:
                p = self.tp[label] / float(self.tp[label] + self.fp[label])
        return p

    def recall(self, label=None, macro=True):
        if label is None:
            if macro is True:
                r = sum([self.recall(c) for c in xrange(self.n_classes)])
                r /= float(self.n_classes)
            else:
                r = sum(self.tp) / float(sum(map(lambda (tp, fn): tp + fn, zip(self.tp, self.fn))))
        else:
            if self.tp[label] == 0.0 and self.fn[label] == 0.0:
                r = 1.0
            else:
                r = self.tp[label] / float(self.tp[label] + self.fn[label])
        return r

    def f_measure(self, beta=1, label=None, macro=True):
        ppv = self.precision(label, macro)
        tpr = self.recall(label, macro)
        if ppv == 0 and tpr == 0:
            f_score = 0.0
        else:
            f_score = (1 + beta*beta)*(ppv * tpr) / (beta*beta*ppv + tpr)
        return f_score

    def confusion_matrix(self):
        """
        Matriz de confusión resultante, donde las columnas corresponden a *predicted*
        y están ordenadas en forma ascendente por casa clase de *actual*.
{'fraction_non_alphanumeric': 0.13159018143009604, 'fraction_numerical': 0.020490928495197437, 'mean_word_length': 2.242560553633218, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 51, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 4, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2988855', 'n_tokens_mistral': 3260, 'n_tokens_neox': 3085, 'n_words': 867}