Detectar e excluir discrepantes no quadro de dados do Pandas


197

Eu tenho um quadro de dados pandas com poucas colunas.

Agora eu sei que certas linhas são discrepantes com base em um determinado valor de coluna.

Por exemplo

a coluna 'Vol' tem todos os valores ao redor 12xxe um valor é 4000(outlier).

Agora eu gostaria de excluir as linhas que possuem Volcolunas como esta.

Então, basicamente, eu preciso colocar um filtro no quadro de dados para selecionar todas as linhas em que os valores de uma determinada coluna estejam dentro, digamos, de 3 desvios padrão da média.

Qual é uma maneira elegante de conseguir isso?

Respostas:


213

Se você possui várias colunas no seu quadro de dados e gostaria de remover todas as linhas que possuem discrepâncias em pelo menos uma coluna, a expressão a seguir faria isso de uma só vez.

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

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

descrição:

  • Para cada coluna, primeiro ele calcula a pontuação Z de cada valor na coluna, em relação à média e desvio padrão da coluna.
  • Então é leva o absoluto da pontuação Z porque a direção não importa, apenas se estiver abaixo do limite.
  • all (eixo = 1) garante que, para cada linha, todas as colunas satisfaçam a restrição.
  • Finalmente, o resultado dessa condição é usado para indexar o quadro de dados.

6
Você pode explicar o que esse código está fazendo? E talvez forneça uma idéia de como remover todas as linhas que possuem um outlier em uma única coluna especificada? Seria útil. Obrigado.
samthebrand

17
Para cada coluna, primeiro ele calcula a pontuação Z de cada valor na coluna, em relação à média e desvio padrão da coluna. Então é leva o absoluto da pontuação Z porque a direção não importa, apenas se estiver abaixo do limite. .all (axis = 1) garante que, para cada linha, todas as colunas satisfaçam a restrição. Finalmente, o resultado dessa condição é usado para indexar o quadro de dados.
Rafaelvalle

4
Como você lidaria com a situação quando houver Nulos / Nans nas colunas. Como podemos tê-los ignorados?
Asimo

6
como lidamos com colunas str para esta solução? Se algumas das colunas não forem numéricas e queremos remover os valores discrepantes com base em todas as colunas numéricas.
ssp

6
Erro: "TypeError: tipo (s) de operando não suportado por /: 'str' e 'int'"
sak

142

Use a booleanindexação como faria emnumpy.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

Para uma série, é semelhante:

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

6
o seu é um DataFrame.abs()FYI, tambémDataFrame.clip()
Jeff

7
No caso de clip()Jeff, os contornos não são removidos: df.SOME_DATA.clip(-3std,+3std)atribua os contornos a + 3std ou -3std
CT Zhu

1
Isso é quase o mesmo, @AMM
CT Zhu

1
Como podemos fazer a mesma coisa se nosso quadro de dados do pandas tiver 100 colunas?
DreamerP

1
Incrível, obrigado por essa resposta @CTZhu. @DreamerP você pode simplesmente aplicá-la a toda a trama de dados com: df_new = df[np.abs(df - df.mean()) <= (3 * df.std())]. Mas, ao contrário de aplicá-lo a uma série ou coluna única, isso substituirá os outliers np.nane manterá a forma do DataFrame, portanto, pode ser necessária interpolação para preencher os valores ausentes.
Scotty1-

93

Para cada coluna da estrutura de dados, você pode obter quantil com:

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

e depois filtre com:

df[df["col"] < q]

Se for necessário remover os valores discrepantes inferior e superior, combine a condição com uma instrução 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
Este artigo dá uma boa visão geral do outlier técnicas de remoção machinelearningmastery.com/...
user6903745

2
isso pode remover valores discrepantes apenas do limite superior ... não inferior?
indolentdeveloper

1
@indolentdeveloper você está certo, basta inverter a desigualdade para remover valores discrepantes mais baixos ou combiná-los com um operador OR.
user6903745

4
A ideia do comentário era atualizar as respostas;). Desde que alguém pode perder este ponto.
indolentdeveloper

@ user6903745 instrução AND ou "OR"?
AB

38

Essa resposta é semelhante à fornecida por @tanemaki, mas usa uma lambdaexpressão em vez de 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)]

Para filtrar o DataFrame em que apenas UMA coluna (por exemplo, 'B') está dentro de três desvios padrão:

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

Veja aqui como aplicar esse escore z de forma contínua: Escore Z contínuo aplicado ao quadro de dados do 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

Estou recebendo erro "ValueError: Não é possível índice com chave multidimensional" em linha "df_out = df_in.loc [(df_in [col_name]> fence_low) & (df_in [col_name] <fence_high)]" Você ajuda
Imran Ahmad Ghazali

18

Para cada série no quadro de dados, você pode usar betweene quantileremover discrepâncias.

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

3
Aqui você está selecionando apenas dados dentro do intervalo interquartil (IQR), mas lembre-se de que pode haver valores fora desse intervalo que não sejam discrepantes.
#

2
Escolher, por exemplo, 0,1 e 0,9, seria bastante seguro, eu acho. Usar entre e os quantis como este é uma sintaxe bonita.
PascalVKooten

18

Desde que eu não vi uma resposta que lida com números atributos e não numéricos , aqui está uma resposta complementar.

Convém excluir os valores discrepantes apenas em atributos numéricos (as variáveis ​​categóricas dificilmente podem ser discrepantes).

Definição de função

Estendi a sugestão de @ tanemaki para manipular dados quando atributos não numéricos também estão presentes:

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)

Uso

drop_numerical_outliers(df)

Exemplo

Imagine um conjunto de dados dfcom alguns valores sobre casas: beco, contorno do terreno, preço de venda, ... Por exemplo: Documentação de Dados

Primeiro, você deseja visualizar os dados em um gráfico de dispersão (com 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)

Antes - Gr Liv Area Versus SalePreço

# 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 SalePreço


2
Ótima solução! O heads-up reduce=Falseestá obsoleto desde a pandasversão 0.23.0
RK1

Substitua result_type='reduce'por reduce=False.
Ekaba Bisong

8

scipy.statspossui métodos trim1()e trimboth()recorte os outliers em uma única linha, de acordo com a classificação e uma porcentagem introduzida de valores removidos.


1
trimbothfoi mais fácil para mim.
wordsforthewise

6

Outra opção é transformar seus dados para que o efeito de outliers seja atenuado. Você pode fazer isso com a vitória de seus dados.

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

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

Dados originais

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

Dados Worsorized


6

Se você gosta do encadeamento de métodos, pode obter sua condição booleana para todas as colunas numéricas como esta:

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

Cada valor de cada coluna será convertido para com True/Falsebase em se está a menos de três desvios padrão da média ou não.


Isso deve ocorrer le(3)desde a remoção de outliers. Desta forma, você obtém Trueos valores extremos. Além do +1 e esta resposta deve ser maior
Erfan

2

Você pode usar a máscara booleana:

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

resultado:

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

1

Como estou em um estágio muito inicial da minha jornada de ciência de dados, estou tratando discrepâncias com o código abaixo.

#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

Obtenha o 98º e o 2º percentil como limites de nossos valores extremos

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

segue um exemplo completo com dados e 2 grupos:

Importações:

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

Exemplo de dados com 2 grupos: G1: Grupo 1. G2: Grupo 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
""")

Leia dados de texto no panda dataframe:

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

Definir os valores extremos usando desvios padrão

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

Defina os valores dos dados filtrados e os outliers:

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

Imprima o resultado:

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

Minha função de eliminar valores extremos

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

Eu prefiro recortar do que largar. o seguinte será colocado no segundo e no 98º pecentiles.

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

Excluindo e eliminando valores discrepantes, acredito que esteja errado estatisticamente. Torna os dados diferentes dos dados originais. Também torna os dados de forma desigual e, portanto, a melhor maneira é reduzir ou evitar o efeito de outliers, transformando os dados em log. Isso funcionou para mim:

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

3
Não é possível fazer suposições sobre por que o OP quer fazer alguma coisa.
RajeshM
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.