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


198

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

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

Например

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

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

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

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

Ответы:


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) гарантирует, что для каждой строки все столбцы удовлетворяют ограничению.
  • Наконец, результат этого условия используется для индексации кадра данных.

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())]

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)]

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

Я получаю сообщение об ошибке «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


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


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 данные


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

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

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)

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
Не могу делать предположения о том, почему ОП хочет что-то сделать.
РаджеМ
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.