Pandas Групповой фильтр для удаления выбросов внутри каждой группы - PullRequest
2 голосов
/ 27 февраля 2020

У меня есть Pandas DataFrame, содержащий 3 категориальных переменных группировки и 1 числовую переменную результата. В каждой группе существует n = 6, где одно из этих значений может быть выбросом (как определено распределением в каждой группе: выброс может либо превысить квартиль 3 в 1,5 раза между квартилями, либо быть меньше, чем квартиль 1 в 1,5 раза превышает межквартильный диапазон).

Пример DataFrame показан ниже:

# Making the df without our outcome variable

import numpy as np
import pandas as pd

G1 = np.repeat(['E', 'F'], 24)
G2 = np.tile(np.repeat(['C', 'D'], 6), 4)
G3 = np.tile(np.repeat(['A', 'B'], 12), 2)

dummy_data = pd.DataFrame({'G1' : G1, 'G2' : G2, 'G3': G3})

# Defining a function to generate a numpy array with n = 6, where one of these values is an outlier # by our previous definition

np.random.seed(0)

def outlier_arr(low, high):
    norm_arr = np.random.randint(low, high, 5)

    IQR = np.percentile(norm_arr, 75) - np.percentile(norm_arr, 25)
    upper_fence = np.percentile(norm_arr, 75) + (IQR * 1.5)
    lower_fence = np.percentile(norm_arr, 25) - (IQR * 1.5)
    rand_decision = np.random.randint(0, 2, 1)[0]

    if rand_decision == 1:
        high_outlier = np.round(upper_fence * 3, decimals = 0)
        final_arr = np.hstack([norm_arr, high_outlier])

    else:
        low_outlier = np.round(lower_fence * (1/3), decimals = 0)
        final_arr = np.hstack([norm_arr, low_outlier])

    return final_arr.astype(int)

# Making a list to add into the dataframe to represent our values

abund_arr = []

for i in range(0, 8):
    abund_arr = abund_arr + outlier_arr(700, 800).tolist()

abund_arr = np.array(abund_arr)

# Appending this list as a new row

dummy_data['V1'] = abund_arr

Это должно сгенерировать DataFrame с 3 группирующими переменными G1, G2 и G3 и единственная переменная результата V1, где в каждой группе должен быть один выброс, который необходимо удалить. Мы можем просмотреть первые 6 строк (одну группу) с dummy_data.head(6) ниже, чтобы увидеть, что одно из этих значений (последняя строка) является выбросом, который мы хотели бы отфильтровать.


    G1  G2  G3  V1
0   E   C   A   744
1   E   C   A   747
2   E   C   A   764
3   E   C   A   767
4   E   C   A   767
5   E   C   A   2391 <--- outlier

Насколько я понимаю, хорошим подходом может быть использование df.groupby (). Filter () и группирование по переменным G1, G2 и G3 и реализация пользовательской функции для filter(). который возвращает T / F на основе критериев выбросов, описанных выше.

Я пробовал это, где функция для обнаружения выбросов (возвращает массив True или False) в массиве находится ниже:

def is_outlier(x): 

    IQR = np.percentile(x, 75) - np.percentile(x, 25)
    upper_fence = np.percentile(x, 75) + (IQR * 1.5)
    lower_fence = np.percentile(x, 25) - (IQR * 1.5)

    return (x > upper_fence) | (x < lower_fence)

, который правильно обнаруживает выброс как показано ниже:

test_arr = outlier_arr(300, 500)

is_outlier(test_arr)

# returns an array of [False, False, False, False, False,  True]

Однако при использовании метода, описанного выше для объекта pandas, следующий код не выдает ошибок, но также не фильтрует ни одно из выбросов:

dummy_data.groupby(['G1', 'G2', 'G3']).filter(lambda x: (is_outlier(x['V1'])).any())

ПРИМЕЧАНИЕ: Я действительно нашел способ сделать это здесь , где вы используете apply() вместо filter().

Running dummy_data[~dummy_data.groupby(['G1', 'G2', 'G3'])['V1'].apply(is_outlier)] дал желаемый результат.

Однако, просто ради того, чтобы сделать это с помощью этого метода, что нужно настроить, чтобы заставить это работать, используя filter()? Если это возможно, какой из двух способов является правильным / предпочтительным?

Заранее спасибо.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...