У меня есть 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()
? Если это возможно, какой из двух способов является правильным / предпочтительным?
Заранее спасибо.