Обнаружение и исключение выбросов в кадре данных Pandas

198

У меня есть фрейм данных панд с несколькими столбцами.

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

Например

столбец 'Vol' имеет все значения вокруг, 12xxи одно значение 4000(выброс).

Теперь я хотел бы исключить те строки, которые имеют Volтакой столбец.

Итак, по сути, мне нужно поместить фильтр на фрейм данных так, чтобы мы выбирали все строки, где значения определенного столбца находятся в пределах, скажем, 3 стандартных отклонения от среднего значения.

Какой элегантный способ добиться этого?

AMM
источник

Ответы:

215

Если в вашем фрейме данных есть несколько столбцов и вы хотите удалить все строки, которые имеют выбросы хотя бы в одном столбце, следующее выражение сделает это за один кадр.

df = pd.DataFrame(np.random.randn(100, 3))

from scipy import stats
df[(np.abs(stats.zscore(df)) < 3).all(axis=1)]

описание:

  • Для каждого столбца сначала вычисляется Z-оценка каждого значения в столбце относительно среднего значения столбца и стандартного отклонения.
  • Тогда это берет абсолют Z-счета, потому что направление не имеет значения, только если оно ниже порога.
  • all (axis = 1) гарантирует, что для каждой строки все столбцы удовлетворяют ограничению.
  • Наконец, результат этого условия используется для индексации кадра данных.
tanemaki
источник
6
Можете ли вы объяснить, что делает этот код? И, возможно, представить идею, как я мог бы удалить все строки, которые имеют выбросы в одном указанном столбце? Было бы полезно. Спасибо.
samthebrand
17
Для каждого столбца сначала вычисляется Z-оценка каждого значения в столбце относительно среднего значения столбца и стандартного отклонения. Тогда это берет абсолют Z-счета, потому что направление не имеет значения, только если оно ниже порога. .all (axis = 1) гарантирует, что для каждой строки все столбцы удовлетворяют ограничению. Наконец, результат этого условия используется для индексации кадра данных.
Рафаэльвалье
4
Как бы вы справились с ситуацией, когда в столбцах есть Null / Nans? Как мы можем их игнорировать?
ASIMO
6
как мы имеем дело со столбцами str для этого решения? Если некоторые столбцы не числовые, и мы хотим удалить выбросы на основе всех числовых столбцов.
ssp
6
Получена ошибка: «Ошибка типа: неподдерживаемые типы операндов для /: 'str' и 'int'"
sak
144

Используйте booleanиндексирование, как вы бы сделали вnumpy.array

df = pd.DataFrame({'Data':np.random.normal(size=200)})
# example dataset of normally distributed data. 

df[np.abs(df.Data-df.Data.mean()) <= (3*df.Data.std())]
# keep only the ones that are within +3 to -3 standard deviations in the column 'Data'.

df[~(np.abs(df.Data-df.Data.mean()) > (3*df.Data.std()))]
# or if you prefer the other way around

Для серии это похоже:

S = pd.Series(np.random.normal(size=200))
S[~((S-S.mean()).abs() > 3*S.std())]
CT Zhu
источник
6
это также к DataFrame.abs()вашему сведениюDataFrame.clip()
Джефф
7
В случае clip()Джеффа контуры не удаляются: df.SOME_DATA.clip(-3std,+3std)назначьте контуры либо на + 3std, либо на -3std
CT Zhu
1
Это почти то же самое, @AMM
CT Zhu
1
Как мы можем сделать то же самое, если наш фрейм данных Pandas имеет 100 столбцов?
DreamerP
1
Круто, спасибо за этот ответ @CTZhu. @DreamerP вы можете просто применить его ко всему DataFrame с: df_new = df[np.abs(df - df.mean()) <= (3 * df.std())]. Но в отличие от применения его к Серии или одному столбцу, это заменит выбросы np.nanи сохранит форму DataFrame, поэтому для заполнения отсутствующих значений может потребоваться интерполяция.
Scotty1-
95

Для каждого столбца данных можно получить квантиль с помощью:

q = df["col"].quantile(0.99)

а затем отфильтруйте с помощью:

df[df["col"] < q]

Если нужно удалить нижний и верхний выбросы, объедините условие с оператором AND:

q_low = df["col"].quantile(0.01)
q_hi  = df["col"].quantile(0.99)

df_filtered = df[(df["col"] < q_hi) & (df["col"] > q_low)]
user6903745
источник
3
Эта статья дает очень хороший обзор Outlier методов удаления machinelearningmastery.com/...
user6903745
2
это может удалить выбросы только из верхней границы .. не ниже?
indolentdeveloper
1
@indolentdeveloper Вы правы, просто инвертируйте неравенство, чтобы убрать нижние выбросы, или объедините их с оператором ИЛИ.
user6903745
4
Идея комментария заключалась в обновлении ответов;). Поскольку кто-то может упустить этот момент.
indolentdeveloper
@ user6903745 И оператор или "ИЛИ"?
AB
38

Этот ответ аналогичен ответу @tanemaki, но использует lambdaвыражение вместо scipy stats.

df = pd.DataFrame(np.random.randn(100, 3), columns=list('ABC'))

df[df.apply(lambda x: np.abs(x - x.mean()) / x.std() < 3).all(axis=1)]

Чтобы отфильтровать DataFrame, где только один столбец (например, «B») находится в пределах трех стандартных отклонений:

df[((df.B - df.B.mean()) / df.B.std()).abs() < 3]

Смотрите здесь о том, как применять этот z-показатель на скользящей основе: скользящий Z-показатель применяется к кадру данных pandas.

Александр
источник
22
#------------------------------------------------------------------------------
# accept a dataframe, remove outliers, return cleaned data in a new dataframe
# see http://www.itl.nist.gov/div898/handbook/prc/section1/prc16.htm
#------------------------------------------------------------------------------
def remove_outlier(df_in, col_name):
    q1 = df_in[col_name].quantile(0.25)
    q3 = df_in[col_name].quantile(0.75)
    iqr = q3-q1 #Interquartile range
    fence_low  = q1-1.5*iqr
    fence_high = q3+1.5*iqr
    df_out = df_in.loc[(df_in[col_name] > fence_low) & (df_in[col_name] < fence_high)]
    return df_out
user2708149
источник
Я получаю сообщение об ошибке «ValueError: Невозможно индексировать с помощью многомерного ключа» в строке «df_out = df_in.loc [(df_in [col_name]> fence_low) & (df_in [col_name] <fence_high)]» Поможете ли вы
Имран Ахмад Газали
20

Поскольку я не видел ответа, касающегося числовых и нечисловых атрибутов, здесь приводится ответ в виде дополнения.

Возможно, вы захотите сбросить выбросы только на числовые атрибуты (категориальные переменные вряд ли могут быть выбросами).

Определение функции

Я расширил предложение @ tanemaki для обработки данных, когда присутствуют также нечисловые атрибуты:

from scipy import stats

def drop_numerical_outliers(df, z_thresh=3):
    # Constrains will contain `True` or `False` depending on if it is a value below the threshold.
    constrains = df.select_dtypes(include=[np.number]) \
        .apply(lambda x: np.abs(stats.zscore(x)) < z_thresh, reduce=False) \
        .all(axis=1)
    # Drop (inplace) values set to be rejected
    df.drop(df.index[~constrains], inplace=True)

использование

drop_numerical_outliers(df)

пример

Вообразите набор данных dfс некоторыми значениями о домах: переулок, контур земли, цена продажи, например: документация данных

Во-первых, вы хотите визуализировать данные на графике рассеяния (с z-счетом Thresh = 3):

# Plot data before dropping those greater than z-score 3. 
# The scatterAreaVsPrice function's definition has been removed for readability's sake.
scatterAreaVsPrice(df)

До - Gr Liv Площадь против SalePrice

# Drop the outliers on every attributes
drop_numerical_outliers(train_df)

# Plot the result. All outliers were dropped. Note that the red points are not
# the same outliers from the first plot, but the new computed outliers based on the new data-frame.
scatterAreaVsPrice(train_df)

После - Gr Liv Площадь против SalePrice

KeyMaker00
источник
2
Отличное решение! Начиная reduce=Falseс pandasверсии 0.23.0,
заголовки
Подставим result_type='reduce'для reduce=False.
Экаба Бизонг
18

Для каждой серии в кадре данных вы можете использовать betweenи quantileдля удаления выбросов.

x = pd.Series(np.random.normal(size=200)) # with outliers
x = x[x.between(x.quantile(.25), x.quantile(.75))] # without outliers
Джефф Эрнандес
источник
3
Здесь вы выбираете только данные в пределах межквартильного диапазона (IQR), но имейте в виду, что могут быть значения вне этого диапазона, которые не являются выбросами.
BCArg
2
Я думаю, что выбор, например, 0,1 и 0,9, будет довольно безопасным. Использование Между и Квантилей, как это, довольно симпатичный синтаксис.
PascalVKooten
8

scipy.statsимеет методы trim1()и trimboth()вырезать выбросы в одну строку, в соответствии с ранжированием и введенным процентом удаленных значений.

Олег Николаевич Осыченко
источник
1
trimbothбыло проще для меня.
словами
6

Другой вариант - преобразовать ваши данные, чтобы уменьшить влияние выбросов. Вы можете сделать это, украсив ваши данные.

import pandas as pd
from scipy.stats import mstats
%matplotlib inline

test_data = pd.Series(range(30))
test_data.plot()

Исходные данные

# Truncate values to the 5th and 95th percentiles
transformed_test_data = pd.Series(mstats.winsorize(test_data, limits=[0.05, 0.05])) 
transformed_test_data.plot()

Worsorized данные

mgoldwasser
источник
6

Если вам нравится цепочка методов, вы можете получить логическое условие для всех числовых столбцов, например:

df.sub(df.mean()).div(df.std()).abs().lt(3)

Каждое значение каждого столбца будет преобразовано в True/Falseзависимости от того, отклонены ли его значения от трех стандартных отклонений от среднего или нет.

Тед Петру
источник
Это должно быть le(3)с момента его удаления выбросов. Таким образом, вы получаете Trueза выбросы. Кроме того +1 и этот ответ должен быть выше
Эрфан
2

Вы можете использовать логическую маску:

import pandas as pd

def remove_outliers(df, q=0.05):
    upper = df.quantile(1-q)
    lower = df.quantile(q)
    mask = (df < upper) & (df > lower)
    return mask

t = pd.DataFrame({'train': [1,1,2,3,4,5,6,7,8,9,9],
                  'y': [1,0,0,1,1,0,0,1,1,1,0]})

mask = remove_outliers(t['train'], 0.1)

print(t[mask])

вывод:

   train  y
2      2  0
3      3  1
4      4  1
5      5  0
6      6  0
7      7  1
8      8  1
Manualmsdos
источник
1

Поскольку я нахожусь на очень ранней стадии моего пути в науке о данных, я лечу выбросы с помощью приведенного ниже кода.

#Outlier Treatment

def outlier_detect(df):
    for i in df.describe().columns:
        Q1=df.describe().at['25%',i]
        Q3=df.describe().at['75%',i]
        IQR=Q3 - Q1
        LTV=Q1 - 1.5 * IQR
        UTV=Q3 + 1.5 * IQR
        x=np.array(df[i])
        p=[]
        for j in x:
            if j < LTV or j>UTV:
                p.append(df[i].median())
            else:
                p.append(j)
        df[i]=p
    return df
Арун Гупта
источник
1

Получите 98-й и 2-й процентили как пределы наших выбросов

upper_limit = np.percentile(X_train.logerror.values, 98) 
lower_limit = np.percentile(X_train.logerror.values, 2) # Filter the outliers from the dataframe
data[‘target’].loc[X_train[‘target’]>upper_limit] = upper_limit data[‘target’].loc[X_train[‘target’]<lower_limit] = lower_limit
Dheeraj
источник
0

Ниже приведен полный пример с данными и двумя группами:

Импорт:

from StringIO import StringIO
import pandas as pd
#pandas config
pd.set_option('display.max_rows', 20)

Пример данных с 2 группами: G1: группа 1. G2: группа 2:

TESTDATA = StringIO("""G1;G2;Value
1;A;1.6
1;A;5.1
1;A;7.1
1;A;8.1

1;B;21.1
1;B;22.1
1;B;24.1
1;B;30.6

2;A;40.6
2;A;51.1
2;A;52.1
2;A;60.6

2;B;80.1
2;B;70.6
2;B;90.6
2;B;85.1
""")

Прочитайте текстовые данные в pandas dataframe:

df = pd.read_csv(TESTDATA, sep=";")

Определите выбросы, используя стандартные отклонения

stds = 1.0
outliers = df[['G1', 'G2', 'Value']].groupby(['G1','G2']).transform(
           lambda group: (group - group.mean()).abs().div(group.std())) > stds

Определите значения отфильтрованных данных и выбросы:

dfv = df[outliers.Value == False]
dfo = df[outliers.Value == True]

Распечатать результат:

print '\n'*5, 'All values with decimal 1 are non-outliers. In the other hand, all values with 6 in the decimal are.'
print '\nDef DATA:\n%s\n\nFiltred Values with %s stds:\n%s\n\nOutliers:\n%s' %(df, stds, dfv, dfo)
Вагнер Киприано
источник
0

Моя функция для удаления выбросов

def drop_outliers(df, field_name):
    distance = 1.5 * (np.percentile(df[field_name], 75) - np.percentile(df[field_name], 25))
    df.drop(df[df[field_name] > distance + np.percentile(df[field_name], 75)].index, inplace=True)
    df.drop(df[df[field_name] < np.percentile(df[field_name], 25) - distance].index, inplace=True)
luminousmen
источник
0

Я предпочитаю обрезать, а не уронить. следующее будет закреплено на 2-м и 98-м пестиле.

df_list = list(df)
minPercentile = 0.02
maxPercentile = 0.98

for _ in range(numCols):
    df[df_list[_]] = df[df_list[_]].clip((df[df_list[_]].quantile(minPercentile)),(df[df_list[_]].quantile(maxPercentile)))
ФНО
источник
-2

Удаление и удаление выбросов, которые я считаю статистически неправильными. Это отличает данные от исходных данных. Также делает данные неравномерной формы и, следовательно, лучший способ состоит в том, чтобы уменьшить или избежать влияния выбросов путем логического преобразования данных. Это сработало для меня:

np.log(data.iloc[:, :])
Иезекииль Охене Асаре
источник
3
Не могу делать предположения о том, почему ОП хочет что-то сделать.
РаджеМ