Виявлення та виключення залишків у кадрі даних Pandas


199

У мене є кадр даних панди з кількома стовпцями.

Тепер я знаю, що певні рядки - це інше, що базується на певному значенні стовпця.

Наприклад

у стовпці "Vol" є всі значення навколо, 12xxі одне значення 4000(outlier).

Тепер я хотів би виключити ті рядки, які мають такий 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 (ось = 1) гарантує, що для кожного рядка всі стовпці задовольняють обмеження.
  • Нарешті, результат цієї умови використовується для індексації фрейму даних.

6
Чи можете ви пояснити, що робить цей код? І, можливо, дайте уявлення про те, як я можу видалити всі рядки, які мають надмірне число, в одному заданому стовпці? Буде корисно. Дякую.
samthebrand

17
Для кожного стовпця спочатку він обчислює Z-бал кожного значення стовпця щодо середнього стовпця та стандартного відхилення. Тоді приймається абсолютний показник Z, тому що напрямок не має значення, лише якщо він знаходиться нижче порогового значення. .all (ось = 1) гарантує, що для кожного ряду всі стовпці задовольняють обмеження. Нарешті, результат цієї умови використовується для індексації фрейму даних.
rafaelvalle

4
Як би ви вирішили ситуацію, коли в колонках є Nulls / Nans. Як ми можемо їх ігнорувати?
asimo

6
як ми маємо справу зі стовпцями str для цього рішення? Якщо деякі стовпці нечислові, і ми хочемо видалити залишки на основі всіх числових стовпців.
ssp

6
Помилка: "TypeError: непідтримувані типи операндів для /: '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()FYI, такожDataFrame.clip()
Джефф

7
У випадку з clip()Джеффом, контури не знімаються: df.SOME_DATA.clip(-3std,+3std)призначте контури або + 3std або -3std
CT Чжу

1
Це майже те саме, @AMM
CT Чжу

1
Як ми можемо зробити те ж саме, якщо наш кадр даних панд має 100 стовпців?
СонникP

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 AND оператор або "АБО"?
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-бал, застосований до даних фрейму даних панд


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]> encl_low) & (df_in [col_name] <ogran_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 Area Versal 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 Area Versal SalePrice


2
Чудове рішення! Оскільки голова reduce=Falseбула pandasзнята з версії 0.23.0
RK1

Заміни 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()

Вікторизовані дані


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

наступний повний приклад із даними та 2 групами:

Імпорт:

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

Прочитайте текстові дані в рамці даних панди:

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