График ROC-прямой и расчет площади AUC

Есть готовые реализации функций, однако я пытаюсь написать код вручную. И построить график. Имеем следующий подход: мы по обученной модели соотносим истинные и ложные предсказания с тестовой выборкой. Если спрогнозировали с вероятностью больше 0.5, то величина берется за единицу и при тесте, равном также единице, относится к категории tp (то есть true positive), при нулевом тесте - к fp ( то есть false positive). Если тест единица, а у нас предсказание ноль, то имеем fn (false negative), если везде нули - tn (true negative). На основе рассчитанных счетчиков, получаем метрики tpr (true positive rate) и fpr (false positive rate).

Проделав все это, затем пытаюсь готовой функцией рассчитать площадь под графиком (AUC - area under curve) и построить сам график. Площадь и график не получаются: AUC - потому что передаются float (хотя чему там еще передаваться - это же прогноз методом predict_proba!); график - пытался передать точки циклом, но тоже споткнулся о float (этот тип в цикле не перебирается)

import pandas as pd
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from matplotlib import pyplot as plt
from sklearn import metrics


# Создаём сэмпл
n_samples = 890

# числа в диапазоне до 3 в количестве n_samples (т.е. 890 штук)+2 к каждому числу:
number_of_passengers = np.random.choice(3, n_samples) + 2 
price_of_ticket = np.random.choice(35000, n_samples) + 28
invoice_amount = ((np.random.choice(12, n_samples)) * price_of_ticket) + 1538
age_of_passengers = np.random.choice(78, n_samples) + 5

data = pd.DataFrame({'Number_of_passengers': number_of_passengers, 'Age': age_of_passengers, 'Price': price_of_ticket, 'Amount': invoice_amount}) 
# вообще я использовал файл с датасетом, однако здесь попробовал реализовать линейную модель, где 
# переменная 'Amount' при обучении может вступить целевой:
X = data[['Number_of_passengers', 'Age', 'Price']]
y = data['Amount']

model = LogisticRegression()
X_train, X_test, y_train, y_test = train_test_split(X, y, 
                                                        test_size=0.3, 
                                                        random_state=42 )
model.fit(X_train, y_train)
tp = 0 # True positive
fp = 0 # False positive
fn = 0 # False negative
tn = 0 # True negative
y_pred = model.predict_proba( X_test )
for predicted_prob, actual in zip( y_pred[:,1], y_test ):    
    if predicted_prob >= 0.5:
        predicted = 1
    else:
        predicted = 0
    
    if predicted == 1:
        if actual == 1:
            tp += 1
        else:
            fp += 1
    
    else:
        if actual == 1:
            fn += 1
        else:
            tn += 1
tpr = tp/(tp+fn)
fpr = fp/(fp+tn)
print('Площадь, рассчитанная ручным способом: ', metrics.auc(fpr, tpr))
plt.plot(fpr, tpr)

Ответы (1 шт):

Автор решения: CrazyElf

Переделал немного ваш код, добавил бинарную целевую переменную. Поскольку, как я уже писал в комментарии, ROC-AUC можно считать для задачи бинарной классификации, но никак не для задачи регрессии, которая у вас получалась с вашими данными.

В итоге получился ROC-AUC = 0.5 и прямая диагональная линия на графике, как это и должно быть для случайных данных, в которых отсутствует сигнал:

import pandas as pd
import numpy as np
from sklearn.linear_model import LogisticRegression,LinearRegression
from sklearn.model_selection import train_test_split
from matplotlib import pyplot as plt
from sklearn import metrics


# Создаём сэмпл
n_samples = 890

# числа в диапазоне до 3 в количестве n_samples (т.е. 890 штук)+2 к каждому числу:
number_of_passengers = np.random.choice(3, n_samples) + 2 
price_of_ticket = np.random.choice(35000, n_samples) + 28
invoice_amount = ((np.random.choice(12, n_samples)) * price_of_ticket) + 1538
age_of_passengers = np.random.choice(78, n_samples) + 5
survival = np.random.choice(2, n_samples)

data = pd.DataFrame({'Number_of_passengers': number_of_passengers, 'Age': age_of_passengers, 'Price': price_of_ticket, 'Amount': invoice_amount, 'Survival': survival}) 
# вообще я использовал файл с датасетом, однако здесь попробовал реализовать линейную модель, где 
# переменная 'Amount' при обучении может вступить целевой:
X = data[['Number_of_passengers', 'Age', 'Price', 'Amount']]
y = data['Survival']

model = LogisticRegression()
X_train, X_test, y_train, y_test = train_test_split(X, y, 
                                                        test_size=0.3, 
                                                        random_state=42 )
model.fit(X_train, y_train)
tpr = []
fpr = []
for threshold in np.arange(0,1,0.1):
    tp = 0 # True positive
    fp = 0 # False positive
    fn = 0 # False negative
    tn = 0 # True negative
    y_pred = model.predict_proba( X_test )
    for predicted_prob, actual in zip( y_pred[:,1], y_test ):    
        if predicted_prob >= threshold:
            predicted = 1
        else:
            predicted = 0
        
        if predicted == 1:
            if actual == 1:
                tp += 1
            else:
                fp += 1
        
        else:
            if actual == 1:
                fn += 1
            else:
                tn += 1
    tpr.append(tp/(tp+fn))
    fpr.append(fp/(fp+tn))

print('Площадь, рассчитанная ручным способом: ', metrics.auc(fpr, tpr))
plt.plot(fpr, tpr)

Вывод:

Площадь, рассчитанная ручным способом:  0.5

roc-auc

→ Ссылка