如何计算曲线下的部分面积(AUC)

时间:2016-09-16 17:51:09

标签: python machine-learning statistics scikit-learn

在scikit中,您可以使用

计算二元分类器的曲线下面积
roc_auc_score( Y, clf.predict_proba(X)[:,1] )

我只对假阳性率小于0.1的曲线部分感兴趣。

  

考虑到这样的阈值误报率,我该如何计算AUC   只有曲线部分达到阈值?

以下是几个ROC曲线的示例,例如:

Illustration of ROC-curves plot for several types of a classifier.

scikit learn docs展示了如何使用roc_curve

>>> import numpy as np
>>> from sklearn import metrics
>>> y = np.array([1, 1, 2, 2])
>>> scores = np.array([0.1, 0.4, 0.35, 0.8])
>>> fpr, tpr, thresholds = metrics.roc_curve(y, scores, pos_label=2)
>>> fpr
array([ 0. ,  0.5,  0.5,  1. ])
>>> tpr
array([ 0.5,  0.5,  1. ,  1. ])
>>> thresholds
array([ 0.8 ,  0.4 ,  0.35,  0.1 ]

有一种简单的方法可以从这个到AUC吗?

似乎唯一的问题是如何在fpr = 0.1时计算tpr值,因为roc_curve并不一定能给你这个。

6 个答案:

答案 0 :(得分:7)

假设我们从

开始
import numpy as np
from sklearn import  metrics

现在我们设置真y和预测scores

y = np.array([0, 0, 1, 1])

scores = np.array([0.1, 0.4, 0.35, 0.8])

(注意y已经从问题中减少了1。这是无关紧要的:无论是预测1,2还是0,1,都可以获得完全相同的结果(fpr,tpr,阈值等),但是如果不使用0,1,则一些sklearn.metrics函数是拖动。)

让我们来看看AUC:

>>> metrics.roc_auc_score(y, scores)
0.75

如你的例子所示:

fpr, tpr, thresholds = metrics.roc_curve(y, scores)
>>> fpr, tpr
(array([ 0. ,  0.5,  0.5,  1. ]), array([ 0.5,  0.5,  1. ,  1. ]))

这给出了以下图:

plot([0, 0.5], [0.5, 0.5], [0.5, 0.5], [0.5, 1], [0.5, 1], [1, 1]);

enter image description here

通过构造,有限长度 y 的ROC将由矩形组成:

  • 对于足够低的阈值,所有内容都将被归类为否定。

  • 随着阈值持续增加,在离散点,一些负面分类将变为正数。

因此,对于有限 y ,ROC将始终以一系列连接的水平和垂直线为特征,从(0,0) (1,1)

AUC是这些矩形的总和。这里,如上所示,AUC是0.75,因为矩形具有0.5 * 0.5 + 0.5 * 1 = 0.75的面积。

在某些情况下,人们选择通过线性插值计算AUC。假设 y 的长度远大于为FPR和TPR计算的实际点数。然后,在这种情况下,线性插值是可能之间的点的近似值。在某些情况下,人们也会遵循猜想,如果 y 足够大,则两者之间的点将被线性插值。 sklearn.metrics不使用这个猜想,为了得到与sklearn.metrics一致的结果,有必要使用矩形,而不是梯形求和。

让我们自己编写函数来直接从fprtpr计算AUC:

import itertools
import operator

def auc_from_fpr_tpr(fpr, tpr, trapezoid=False):
    inds = [i for (i, (s, e)) in enumerate(zip(fpr[: -1], fpr[1: ])) if s != e] + [len(fpr) - 1]
    fpr, tpr = fpr[inds], tpr[inds]
    area = 0
    ft = zip(fpr, tpr)
    for p0, p1 in zip(ft[: -1], ft[1: ]):
        area += (p1[0] - p0[0]) * ((p1[1] + p0[1]) / 2 if trapezoid else p0[1])
    return area

此函数采用FPR和TPR,以及一个可选参数,说明是否使用梯形求和。运行它,我们得到:

>>> auc_from_fpr_tpr(fpr, tpr), auc_from_fpr_tpr(fpr, tpr, True)
(0.75, 0.875)

对于矩形求和,我们得到与sklearn.metrics相同的结果,对于梯形求和得到不同的更高的结果。

所以,现在我们只需看看如果我们以0.1的FPR终止,FPR / TPR点会发生什么。我们可以使用bisect module

执行此操作
import bisect

def get_fpr_tpr_for_thresh(fpr, tpr, thresh):
    p = bisect.bisect_left(fpr, thresh)
    fpr = fpr.copy()
    fpr[p] = thresh
    return fpr[: p + 1], tpr[: p + 1]

这是如何工作的?它只是检查threshfpr的插入点的位置。给定FPR的属性(它必须从0开始),插入点必须在水平线上。因此,在此之前的所有矩形都应该不受影响,应该删除此之后的所有矩形,并且应该缩短这个矩形。

让我们应用它:

fpr_thresh, tpr_thresh = get_fpr_tpr_for_thresh(fpr, tpr, 0.1)
>>> fpr_thresh, tpr_thresh
(array([ 0. ,  0.1]), array([ 0.5,  0.5]))

最后,我们只需要从更新版本中计算出AUC:

>>> auc_from_fpr_tpr(fpr, tpr), auc_from_fpr_tpr(fpr, tpr, True)
0.050000000000000003, 0.050000000000000003)

在这种情况下,矩形和梯形求和都给出相同的结果。请注意,一般情况下,他们不会。为了与sklearn.metrics保持一致,应使用第一个。

答案 1 :(得分:2)

我实施了目前最好的答案,并没有在所有情况下都给出正确的结果。我重新实现并测试了下面的实现。我还利用了内置的梯形AUC功能,而不是从头开始重新创建。

def line(x_coords, y_coords):
    """
    Given a pair of coordinates (x1,y2), (x2,y2), define the line equation. Note that this is the entire line vs. t
    the line segment.

    Parameters
    ----------
    x_coords: Numpy array of 2 points corresponding to x1,x2
    x_coords: Numpy array of 2 points corresponding to y1,y2

    Returns
    -------
    (Gradient, intercept) tuple pair
    """    
    if (x_coords.shape[0] < 2) or (y_coords.shape[0] < 2):
        raise ValueError('At least 2 points are needed to compute'
                         ' area under curve, but x.shape = %s' % p1.shape)
    if ((x_coords[0]-x_coords[1]) == 0):
        raise ValueError("gradient is infinity")
    gradient = (y_coords[0]-y_coords[1])/(x_coords[0]-x_coords[1])
    intercept = y_coords[0] - gradient*1.0*x_coords[0]
    return (gradient, intercept)

def x_val_line_intercept(gradient, intercept, x_val):
    """
    Given a x=X_val vertical line, what is the intersection point of that line with the 
    line defined by the gradient and intercept. Note: This can be further improved by using line
    segments.

    Parameters
    ----------
    gradient
    intercept

    Returns
    -------
    (x_val, y) corresponding to the intercepted point. Note that this will always return a result.
    There is no check for whether the x_val is within the bounds of the line segment.
    """    
    y = gradient*x_val + intercept
    return (x_val, y)

def get_fpr_tpr_for_thresh(fpr, tpr, thresh):
    """
    Derive the partial ROC curve to the point based on the fpr threshold.

    Parameters
    ----------
    fpr: Numpy array of the sorted FPR points that represent the entirety of the ROC.
    tpr: Numpy array of the sorted TPR points that represent the entirety of the ROC.
    thresh: The threshold based on the FPR to extract the partial ROC based to that value of the threshold.

    Returns
    -------
    thresh_fpr: The FPR points that represent the partial ROC to the point of the fpr threshold.
    thresh_tpr: The TPR points that represent the partial ROC to the point of the fpr threshold
    """    
    p = bisect.bisect_left(fpr, thresh)
    thresh_fpr = fpr[:p+1].copy()
    thresh_tpr = tpr[:p+1].copy()
    g, i = line(fpr[p-1:p+1], tpr[p-1:p+1])
    new_point = x_val_line_intercept(g, i, thresh)
    thresh_fpr[p] = new_point[0]
    thresh_tpr[p] = new_point[1]
    return thresh_fpr, thresh_tpr

def partial_auc_scorer(y_actual, y_pred, decile=1):
    """
    Derive the AUC based of the partial ROC curve from FPR=0 to FPR=decile threshold.

    Parameters
    ----------
    y_actual: numpy array of the actual labels.
    y_pred: Numpy array of The predicted probability scores.
    decile: The threshold based on the FPR to extract the partial ROC based to that value of the threshold.

    Returns
    -------
    AUC of the partial ROC. A value that ranges from 0 to 1.
    """        
    y_pred = list(map(lambda x: x[-1], y_pred))
    fpr, tpr, _ = roc_curve(y_actual, y_pred, pos_label=1)
    fpr_thresh, tpr_thresh = get_fpr_tpr_for_thresh(fpr, tpr, decile)
    return auc(fpr_thresh, tpr_thresh)

答案 2 :(得分:1)

这取决于FPR是 x -axis还是 y -axis(独立或因变量)。

如果它是 x ,计算是微不足道的:只计算范围[0.0,0.1]。

如果 y ,则首先需要解决 y = 0.1 的曲线。这会将x轴划分为需要计算的区域,以及高度为0.1的简单矩形区域。

为了说明,假设您在两个范围内找到超过0.1的函数:[x1,x2]和[x3,x4]。计算曲线下面积

的面积
[0, x1]
[x2, x3]
[x4, ...]

为此,在找到的两个区间内添加y = 0.1下的矩形:

area += (x2-x1 + x4-x3) * 0.1

这就是你需要的东西吗?

答案 3 :(得分:1)

仅在[0.0,0.1]范围内计算fpr和tpr值。

然后,您可以使用numpy.trapz评估部分AUC(pAUC),如下所示:

pAUC = numpy.trapz(tpr_array, fpr_array)

此函数使用复合梯形法则来评估曲线下的面积。

答案 4 :(得分:1)

Python sklearn roc_auc_score()现在允许您设置max_fpr。您可以设置max_fpr=0.1,该函数将为您计算AUC。 https://scikit-learn.org/stable/modules/generated/sklearn.metrics.roc_auc_score.html

答案 5 :(得分:0)

@eleanora认为你使用sklearn的通用metrics.auc方法的冲动是正确的(这就是我所做的)。获得tpr和fpr点集后应该很简单(并且可以使用scipy的插值方法逼近任一系列中的精确点)。