Mendeteksi dan mengecualikan pencilan dalam bingkai data Pandas


198

Saya memiliki bingkai data panda dengan beberapa kolom.

Sekarang saya tahu bahwa baris tertentu outlier berdasarkan nilai kolom tertentu.

Misalnya

kolom 'Vol' memiliki semua nilai sekitar 12xxdan satu nilai adalah 4000(outlier).

Sekarang saya ingin mengecualikan baris-baris yang memiliki Volkolom seperti ini.

Jadi, pada dasarnya saya perlu meletakkan filter pada bingkai data sehingga kita memilih semua baris di mana nilai-nilai kolom tertentu berada dalam, katakanlah, 3 standar deviasi dari mean.

Apa cara yang elegan untuk mencapai ini?

Jawaban:


214

Jika Anda memiliki beberapa kolom dalam bingkai data dan ingin menghapus semua baris yang memiliki pencilan dalam setidaknya satu kolom, ekspresi berikut akan melakukannya dalam satu pemotretan.

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

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

deskripsi:

  • Untuk setiap kolom, pertama itu menghitung Z-score dari setiap nilai dalam kolom, relatif terhadap rata-rata kolom dan standar deviasi.
  • Maka diperlukan mutlak Z-score karena arahnya tidak masalah, hanya jika berada di bawah ambang batas.
  • all (axis = 1) memastikan bahwa untuk setiap baris, semua kolom memenuhi batasan.
  • Akhirnya, hasil dari kondisi ini digunakan untuk mengindeks dataframe.

6
Bisakah Anda jelaskan apa yang dilakukan kode ini? Dan mungkin memberikan ide bagaimana saya bisa menghapus semua baris yang memiliki pencilan dalam satu kolom tertentu? Akan sangat membantu. Terima kasih.
samthebrand

17
Untuk setiap kolom, pertama itu menghitung Z-score dari setiap nilai dalam kolom, relatif terhadap rata-rata kolom dan standar deviasi. Maka diperlukan mutlak Z-score karena arahnya tidak masalah, hanya jika berada di bawah ambang batas. .all (axis = 1) memastikan bahwa untuk setiap baris, semua kolom memenuhi batasan. Akhirnya, hasil dari kondisi ini digunakan untuk mengindeks dataframe.
rafaelvalle

4
Bagaimana Anda menangani situasi ketika ada Nulls / Nans di kolom. Bagaimana kita bisa mengabaikannya?
asimo

6
bagaimana kita menangani kolom str untuk solusi ini? Jika beberapa kolom adalah non-numerik dan kami ingin menghapus outlier berdasarkan semua kolom numerik.
ssp

6
Mendapat kesalahan: "TypeError: jenis operan yang tidak didukung untuk /: 'str' dan 'int'"
sak

142

Gunakan booleanpengindeksan seperti yang akan Anda lakukan dinumpy.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

Untuk seri itu mirip:

S = pd.Series(np.random.normal(size=200))
S[~((S-S.mean()).abs() > 3*S.std())]

6
mereka adalah DataFrame.abs()FYI, jugaDataFrame.clip()
Jeff

7
Dalam kasus clip(), Jeff, garis besarnya tidak dihapus: df.SOME_DATA.clip(-3std,+3std)tetapkan outliner ke +3 atau -3std
CT Zhu

1
Itu hampir sama, @AMM
CT Zhu

1
Bagaimana kita bisa melakukan hal yang sama jika bingkai data panda kita memiliki 100 kolom?
DreamerP

1
Luar biasa, terima kasih atas jawaban itu @CTZhu. @DreamerP Anda hanya dapat menerapkannya ke seluruh DataFrame dengan: df_new = df[np.abs(df - df.mean()) <= (3 * df.std())]. Namun berbeda dengan menerapkannya pada Seri atau kolom tunggal, ini akan menggantikan outlier dengannp.nan dan mempertahankan bentuk DataFrame, sehingga interpolasi mungkin diperlukan untuk mengisi nilai yang hilang.
Scotty1-

93

Untuk setiap kolom bingkai data Anda, Anda bisa mendapatkan kuantil dengan:

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

lalu filter dengan:

df[df["col"] < q]

Jika seseorang perlu menghapus outlier bawah dan atas, gabungkan kondisi dengan pernyataan 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
Artikel ini memberikan gambaran yang sangat baik dari outlier teknik removal machinelearningmastery.com/...
user6903745

2
ini mungkin menghapus pencilan hanya dari batas atas .. tidak lebih rendah?
indolentdeveloper

1
@indolentdeveloper Anda benar, hanya membalikkan ketidaksetaraan untuk menghapus outlier yang lebih rendah, atau menggabungkannya dengan operator ATAU.
user6903745

4
Gagasan komentar adalah untuk memperbarui jawaban;). Karena seseorang dapat melewatkan poin ini.
indolentdeveloper

@ user6903745 DAN pernyataan atau "ATAU"?
AB

38

Jawaban ini mirip dengan yang disediakan oleh @tanemaki, tetapi menggunakan lambdaekspresi sebagai ganti 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)]

Untuk memfilter DataFrame di mana hanya SATU kolom (misalnya 'B') dalam tiga standar deviasi:

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

Lihat di sini untuk cara menerapkan skor-z ini secara bergulir: Skor-skor bergulir diterapkan pada bingkai data panda


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

Saya mendapatkan kesalahan "ValueError: Tidak dapat mengindeks dengan kunci multidimensi" sejalan "df_out = df_in.loc [(df_in [col_name]> fence_low) & (df_in [col_name] <fence_high)]" Maukah Anda membantu
Imran Ahmad Ghazali

18

Untuk setiap seri dalam kerangka data, Anda bisa menggunakan betweendan quantilemenghapus outlier.

x = pd.Series(np.random.normal(size=200)) # with outliers
x = x[x.between(x.quantile(.25), x.quantile(.75))] # without outliers

3
Di sini Anda hanya memilih data dalam rentang interkuartil (IQR), tetapi perlu diingat bahwa mungkin ada nilai di luar rentang ini yang bukan outlier.
BCArg

2
Memilih misalnya 0,1 dan 0,9 akan cukup aman saya pikir. Menggunakan antara dan kuantil seperti ini adalah sintaks yang cantik.
PascalVKooten

18

Karena saya belum melihat jawaban yang berhubungan dengan atribut numerik dan non-numerik , inilah jawaban komplemen.

Anda mungkin ingin menjatuhkan outlier hanya pada atribut numerik (variabel kategori hampir tidak bisa outlier).

Definisi fungsi

Saya telah memperluas saran @ tanemaki untuk menangani data ketika atribut non-numerik juga hadir:

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)

Pemakaian

drop_numerical_outliers(df)

Contoh

Bayangkan sebuah dataset dfdengan beberapa nilai tentang rumah: gang, kontur tanah, harga jual, ... Misalnya: Dokumentasi Data

Pertama, Anda ingin memvisualisasikan data pada sebar grafik (dengan z-score 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)

Sebelum - Gr Liv Area Versus SaleHarga

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

After - Gr Liv Area Versus SaleHarga


2
Solusi bagus! Sebagai kepala reduce=Falsetelah ditinggalkan sejak pandasversi 0.23.0
RK1

Pengganti result_type='reduce'untuk reduce=False.
Ekaba Bisong

8

scipy.statsmemiliki metode trim1()dan trimboth()untuk memotong outlier dalam satu baris, sesuai dengan peringkat dan persentase yang diperkenalkan dari nilai yang dihapus.


1
trimbothpaling mudah bagi saya.
kata

6

Pilihan lain adalah mengubah data Anda sehingga efek pencilan dapat dikurangi. Anda dapat melakukan ini dengan memenangkan data Anda.

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

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

Data asli

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

Data terkorporasi


6

Jika Anda menyukai metode chaining, Anda bisa mendapatkan kondisi boolean Anda untuk semua kolom numerik seperti ini:

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

Setiap nilai dari setiap kolom akan dikonversi True/Falseberdasarkan pada apakah kurang dari tiga standar deviasi dari rata-rata atau tidak.


Ini harus le(3)sejak menghapus outlier. Dengan cara ini Anda dapatkan Trueuntuk outlier. Selain itu +1 dan jawaban ini harus lebih tinggi
Erfan

2

Anda dapat menggunakan topeng boolean:

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

keluaran:

   train  y
2      2  0
3      3  1
4      4  1
5      5  0
6      6  0
7      7  1
8      8  1

1

Karena saya berada dalam tahap awal perjalanan ilmu data saya, saya memperlakukan outlier dengan kode di bawah ini.

#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

Dapatkan persentil ke-98 dan ke-2 sebagai batas outlier kami

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

contoh lengkap dengan data dan 2 kelompok berikut:

Impor:

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

Contoh data dengan 2 grup: G1: Grup 1. G2: Grup 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
""")

Baca data teks ke bingkai data panda:

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

Definisikan outlier menggunakan standar deviasi

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

Tentukan nilai data yang difilter dan pencilan:

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

Cetak hasilnya:

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

Fungsi saya untuk menjatuhkan outlier

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

Saya lebih suka memotong daripada menjatuhkan. berikut ini akan klip inplace di pecentil ke-2 dan ke-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

Menghapus dan menghapus outlier saya yakin secara statistik salah. Itu membuat data berbeda dari data asli. Juga membuat data tidak berbentuk dan karenanya cara terbaik adalah mengurangi atau menghindari efek pencilan dengan log mentransformasikan data. Ini bekerja untuk saya:

np.log(data.iloc[:, :])

3
Tidak dapat membuat asumsi tentang mengapa OP ingin melakukan sesuatu.
RajeshM
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.