Коэффициент корреляции общей популяции больше, чем выборки популяции - PullRequest
1 голос
/ 29 февраля 2020

Я создал два классификатора: расширенное дерево решений (BDT) и нейронную сеть (NN) для классификации событий как принадлежащих к классу сигналов или классу фона. Они выводят непрерывную вероятность от 0 до 1 принадлежности к классу сигналов. Я хочу сравнить два метода и wi sh, чтобы найти корреляцию между ними.

Однако я обнаружил, что если я вычисляю коэффициент корреляции только событий, которые принадлежат фоновому классу, или событий, которые просто принадлежат классу сигналов, эти корреляции меньше, чем корреляция всего набора данных. Я бы предположил, что, поскольку оба классификатора проверяются на одном и том же наборе данных, общая корреляция будет средневзвешенной величиной двух отдельных корреляций. Обратите внимание, что общий набор данных состоит из ~ 100 000 событий.

Здесь я вычисляю корреляцию для всего набора данных, используя функцию pandas .corr (), которая вычисляет матрицу корреляции Пирсона:

dfBDT = pd.read_csv("BDTResults.csv")
dfNN = pd.read_csv("NNResults.csv")

# not defaulted by Event Number by default
dfBDT = dfBDT.sort_values('EventNumber')
dfNN = dfNN.sort_values('EventNumber')

# Resets index of sorted dataframe so sorted dataframe index begins at 0
dfBDT.reset_index(drop=True, inplace=True)
dfNN.reset_index(drop=True, inplace=True)

dfscore = pd.concat([dfBDT['score'],dfNN['score']], axis = 1)
dfnum = pd.concat([dfBDT['EventNumber'],dfNN['EventNumber']], axis = 1)

dfTotal = pd.concat([dfnum,dfscore], axis = 1)
dfTotal.columns = ['EventNumberBDT', 'EventNumberNN', 'BDT', 'NN']

dfTotal.corr()

Это дает корреляцию 97%. Затем я делаю то же самое только для фоновых событий, для которых я определил фоновые события, имеющие класс 0:

BDT_back = (dfBDT.loc[dfBDT['Class'] == 0])['score']
BDT_back.reset_index(drop=True, inplace=True)

BDT_back_num = (dfBDT.loc[dfBDT['Class'] == 0])['EventNumber']
BDT_back_num.reset_index(drop=True, inplace=True)


NN_back = (dfNN.loc[dfNN['Class'] == 0])['score']
NN_back.reset_index(drop=True, inplace=True)

NN_back_num = (dfBDT.loc[dfBDT['Class'] == 0])['EventNumber']
NN_back_num.reset_index(drop=True, inplace=True)



dfBack = pd.concat([BDT_back_num,NN_back_num,BDT_back,NN_back],
                   axis = 1)
dfBack.reset_index(drop=True, inplace=True)

dfBack.columns = ['EventNumberBDT','EventNumberNN','BDT','NN']

dfBack.corr()

. Это дает мне корреляцию около 96%. Затем я просто повторяю выше для сигнальных событий, то есть заменяю class = 0 на class = 1, и я получаю корреляции 91%.

Затем, если я пытаюсь объединить два кадра данных и снова вычислить общую корреляцию, я получаю более высокая корреляция, чем раньше, на 98%:

ab = pd.concat([dfBack['BDT'],dfSig['BDT']])
ba = pd.concat([dfBack['NN'],dfSig['NN']])

abba =pd.concat([ab,ba], axis = 1)
abba.corr()

Тот факт, что эти два значения различны, должен означать, что что-то идет не так, но я не знаю, где.

1 Ответ

1 голос
/ 01 марта 2020

В конечном счете, все сводится к горизонтальным слияниям, выполняемым по индексам.

Несопоставленные строки

Если строки различаются по обоим фреймам данных, concat по умолчанию к внешнему объединению сгенерирует NaN по несопоставленным индексам (на фрейме данных меньших строк), которые будут больше строк, чем исходный фрейм данных до разделения.

Несопоставленные классы

Кроме того, если Class имеют разные доли% между двумя фреймами данных, dfBDT и dfNN , их соответствующие объединения вернут NaN по несопоставленным индексам.

Например, скажем, dfBDT поддерживает 60% и 40% между классами 0 и 1, а dfNN поддерживает 50% и 50% между классами 0 и 1, где сравнения включают :

  • BDT Class 0 будет иметь больше строк, чем NN Class 0
  • BDT Class 1 будет иметь меньше строк, чем NN Class 1

После горизонтального соединения с pd.concat(..., axis = 1), по умолчанию внешнее соединение, how = 'outer', в результате несоответствия будут генерироваться NaN с обеих сторон. Даже если вы используете how='inner ', вы будете отфильтровывать несоответствия, но dfTotal никогда не отфильтровывает никакие строки, но включает все строк.

Сортировка Заказ

Тестирование между Linux и Windows машинами с отобранным, воспроизводимым примером указывает на сортировку, особенно по Class сначала, затем EventNumber, имеет значение.


Это может быть продемонстрировано с помощью случайных данных с семенами для воспроизводимого примера. Ниже приведен рефакторинг вашего кода, чтобы избежать множества вызовов pd.concat с использованием join (с настройкой по умолчанию how='outer'). Далее этот код эквивалентен исходной настройке OP.

Данные

import numpy as np
import pandas as pd

np.random.seed(2292020)
dfBDT = pd.DataFrame({'EventNumber': np.random.randint(1, 15, 500),
                      'Class': np.random.randint(0, 1, 500),
                      'score': np.random.randn(500)
                     })


dfNN = pd.DataFrame({'EventNumber': np.random.randint(1, 15, 500),
                     'Class': np.random.randint(0, 1, 500),
                     'score': np.random.randn(500)
                    })

Код

dfBDT = dfBDT.sort_values(['Class', 'EventNumber']).reset_index(drop=True)    
dfNN = dfNN.sort_values(['Class', 'EventNumber']).reset_index(drop=True)  

# ALL ROWS (NO FILTER)
dfTotal = (dfBDT.reindex(['EventNumber', 'score'], axis='columns')
                .join(dfNN.reindex(['EventNumber', 'score'], axis='columns'),
                      rsuffix = '_')
                .set_axis(['EventNumberBDT', 'BDT', 'EventNumberNN', 'NN'], 
                          axis='columns', inplace = False)
                .reindex(['EventNumberBDT','EventNumberNN','BDT','NN'], 
                         axis='columns'))    
dfTotal.corr()

# TWO FILTERED DATA FRAMES CLASS (0 FOR BACKGROUND, 1 FOR SIGNAL)
df_list = [(dfBDT.query('Class == {}'.format(i))
                 .reindex(['EventNumber', 'score'], axis='columns')
                 .join(dfNN.query('Class == {}'.format(i))
                           .reindex(['EventNumber', 'score'], axis='columns'),
                       rsuffix = '_')
                 .set_axis(['EventNumberBDT', 'BDT', 'EventNumberNN', 'NN'],
                           axis='columns', inplace = False)

                 .reindex(['EventNumberBDT','EventNumberNN','BDT','NN'],
                          axis='columns')
           ) for i in range(0,2)]

dfSub = pd.concat(df_list)

dfSub.corr()

Вывод (обратите внимание, они дают разные результаты)

dfTotal.corr()
#                 EventNumberBDT  EventNumberNN       BDT        NN
# EventNumberBDT        1.000000       0.912279 -0.024121  0.115754
# EventNumberNN         0.912279       1.000000 -0.039038  0.122905
# BDT                  -0.024121      -0.039038  1.000000  0.012143
# NN                    0.115754       0.122905  0.012143  1.000000

dfSub.corr()
#                 EventNumberBDT  EventNumberNN       BDT        NN
# EventNumberBDT        1.000000       0.974140 -0.024121  0.120102
# EventNumberNN         0.974140       1.000000 -0.026026  0.122905
# BDT                  -0.024121      -0.026026  1.000000  0.025548
# NN                    0.120102       0.122905  0.025548  1.000000

Однако, если мы приравниваем Class акций (например, 50% и 50% через оба кадра данных или любой эквивалентный ресурс в обоих кадрах данных), выходы точно совпадают.

np.random.seed(2292020)
dfBDT = pd.DataFrame({'EventNumber': np.random.randint(1, 15, 500),
                      'Class': np.concatenate((np.zeros(250), np.ones(250))),
                      'score': np.random.randn(500)
                     })


dfNN = pd.DataFrame({'EventNumber': np.random.randint(1, 15, 500),
                     'Class': np.concatenate((np.zeros(250), np.ones(250))),
                     'score': np.random.randn(500)
                    })

...

dfTotal.corr()
#                 EventNumberBDT  EventNumberNN       BDT        NN
# EventNumberBDT        1.000000       0.992846 -0.026130  0.023623
# EventNumberNN         0.992846       1.000000 -0.023411  0.022093
# BDT                  -0.026130      -0.023411  1.000000 -0.026454
# NN                    0.023623       0.022093 -0.026454  1.000000


dfSub.corr()
#                 EventNumberBDT  EventNumberNN       BDT        NN
# EventNumberBDT        1.000000       0.992846 -0.026130  0.023623
# EventNumberNN         0.992846       1.000000 -0.023411  0.022093
# BDT                  -0.026130      -0.023411  1.000000 -0.026454
# NN                    0.023623       0.022093 -0.026454  1.000000

Наконец, это было проверено с использованием исходного кода OP:

def op_approach_total():
    dfscore = pd.concat([dfBDT['score'],dfNN['score']], axis = 1)
    dfnum = pd.concat([dfBDT['EventNumber'],dfNN['EventNumber']], axis = 1)

    dfTotal = pd.concat([dfnum,dfscore], axis = 1)
    dfTotal.columns = ['EventNumberBDT', 'EventNumberNN', 'BDT', 'NN']

    return dfTotal.corr()


def op_approach_split():
    # not defaulted by Event Number by default
    BDT_back = (dfBDT.loc[dfBDT['Class'] == 0])['score']
    BDT_back.reset_index(drop=True, inplace=True)

    BDT_back_num = (dfBDT.loc[dfBDT['Class'] == 0])['EventNumber']
    BDT_back_num.reset_index(drop=True, inplace=True)


    NN_back = (dfNN.loc[dfNN['Class'] == 0])['score']
    NN_back.reset_index(drop=True, inplace=True)

    NN_back_num = (dfNN.loc[dfNN['Class'] == 0])['EventNumber'] 
    NN_back_num.reset_index(drop=True, inplace=True)


    dfBack = pd.concat([BDT_back_num,NN_back_num,BDT_back,NN_back],
                       axis = 1)
    dfBack.reset_index(drop=True, inplace=True)
    dfBack.columns = ['EventNumberBDT','EventNumberNN','BDT','NN']


    # not defaulted by Event Number by default
    BDT_sig = (dfBDT.loc[dfBDT['Class'] == 1])['score']
    BDT_sig.reset_index(drop=True, inplace=True)

    BDT_sig_num = (dfBDT.loc[dfBDT['Class'] == 1])['EventNumber']
    BDT_sig_num.reset_index(drop=True, inplace=True)

    NN_sig = (dfNN.loc[dfNN['Class'] == 1])['score']
    NN_sig.reset_index(drop=True, inplace=True)

    NN_sig_num = (dfNN.loc[dfNN['Class'] == 1])['EventNumber']
    NN_sig_num.reset_index(drop=True, inplace=True)


    dfSig = pd.concat([BDT_sig_num, NN_sig_num, BDT_sig, NN_sig],
                       axis = 1)
    dfSig.reset_index(drop=True, inplace=True)
    dfSig.columns = ['EventNumberBDT','EventNumberNN','BDT','NN']

    # ADDING EventNumber COLUMNS
    ev_back = pd.concat([dfBack['EventNumberBDT'], dfSig['EventNumberBDT']])
    ev_sig = pd.concat([dfBack['EventNumberNN'], dfSig['EventNumberNN']])


    ab = pd.concat([dfBack['BDT'], dfSig['BDT']])

    ba = pd.concat([dfBack['NN'], dfSig['NN']])

    # HORIZONTAL MERGE
    abba = pd.concat([ev_back, ev_sig, ab, ba], axis = 1)

    return abba.corr()

opTotal = op_approach_total()
opSub = op_approach_split()

Выход

opTotal = op_approach_total()
opTotal
#                 EventNumberBDT  EventNumberNN       BDT        NN
# EventNumberBDT        1.000000       0.992846 -0.026130  0.023623
# EventNumberNN         0.992846       1.000000 -0.023411  0.022093
# BDT                  -0.026130      -0.023411  1.000000 -0.026454
# NN                    0.023623       0.022093 -0.026454  1.000000

opSub = op_approach_split()
opSub
#                 EventNumberBDT  EventNumberNN       BDT        NN
# EventNumberBDT        1.000000       0.992846 -0.026130  0.023623
# EventNumberNN         0.992846       1.000000 -0.023411  0.022093
# BDT                  -0.026130      -0.023411  1.000000 -0.026454
# NN                    0.023623       0.022093 -0.026454  1.000000
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...