Detectar y excluir valores atípicos en el marco de datos de Pandas

198

Tengo un marco de datos de pandas con pocas columnas.

Ahora sé que ciertas filas son valores atípicos basados ​​en un cierto valor de columna.

Por ejemplo

la columna 'Vol' tiene todos los valores 12xxy un valor es 4000(atípico).

Ahora me gustaría excluir esas filas que tienen una Volcolumna como esta.

Entonces, esencialmente necesito poner un filtro en el marco de datos de modo que seleccionemos todas las filas donde los valores de una determinada columna están dentro, por ejemplo, de 3 desviaciones estándar de la media.

¿Cuál es una forma elegante de lograr esto?

AMM
fuente

Respuestas:

214

Si tiene varias columnas en su marco de datos y desea eliminar todas las filas que tienen valores atípicos en al menos una columna, la siguiente expresión lo haría de una sola vez.

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

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

descripción:

  • Para cada columna, primero calcula la puntuación Z de cada valor en la columna, en relación con la media de la columna y la desviación estándar.
  • Luego se toma el puntaje Z absoluto porque la dirección no importa, solo si está por debajo del umbral.
  • all (axis = 1) asegura que para cada fila, todas las columnas satisfagan la restricción.
  • Finalmente, el resultado de esta condición se usa para indexar el marco de datos.
tanemaki
fuente
66
¿Puedes explicar qué está haciendo este código? ¿Y quizás dar una idea de cómo podría eliminar todas las filas que tienen un valor atípico en una sola columna especificada? Sería de ayuda. Gracias.
samthebrand
17
Para cada columna, primero calcula la puntuación Z de cada valor en la columna, en relación con la media de la columna y la desviación estándar. Luego se toma el puntaje Z absoluto porque la dirección no importa, solo si está por debajo del umbral. .all (axis = 1) asegura que para cada fila, todas las columnas satisfacen la restricción. Finalmente, el resultado de esta condición se usa para indexar el marco de datos.
rafaelvalle
44
¿Cómo manejaría la situación cuando hay Nulos / Nans en las columnas? ¿Cómo podemos ignorarlos?
asimo
66
¿Cómo manejamos las columnas str para esta solución? Si algunas de las columnas no son numéricas y queremos eliminar los valores atípicos basados ​​en todas las columnas numéricas.
ssp
66
Error obtenido: "TypeError: tipos de operando no compatibles para /: 'str' e 'int'"
sak
142

Use la booleanindexación como lo haría ennumpy.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 una serie es similar:

S = pd.Series(np.random.normal(size=200))
S[~((S-S.mean()).abs() > 3*S.std())]
CT Zhu
fuente
66
su es un DataFrame.abs()FYI, tambiénDataFrame.clip()
Jeff
77
En el caso de clip()Jeff, los contornos no se eliminan: df.SOME_DATA.clip(-3std,+3std)asigne los delineadores a + 3std o -3std
CT Zhu
1
Eso es casi lo mismo, @AMM
CT Zhu
1
¿Cómo podemos hacer lo mismo si nuestro marco de datos de pandas tiene 100 columnas?
DreamerP
1
Impresionante, gracias por esa respuesta @CTZhu. @DreamerP sólo se puede aplicar a toda la trama de datos con: df_new = df[np.abs(df - df.mean()) <= (3 * df.std())]. Pero a diferencia de aplicarlo a una serie o columna única, esto reemplazará los valores atípicos np.nany mantendrá la forma del DataFrame, por lo que podría ser necesaria la interpolación para completar los valores faltantes.
Scotty1-
94

Para cada una de las columnas de su marco de datos, puede obtener cuantiles con:

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

y luego filtrar con:

df[df["col"] < q]

Si necesita eliminar valores atípicos superiores e inferiores, combine la condición con una declaración 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)]
usuario6903745
fuente
3
Este artículo ofrece una visión general muy buena de las técnicas de eliminación de valores atípicos machinelearningmastery.com/…
user6903745
2
esto podría eliminar valores atípicos solo del límite superior ... ¿no inferior?
indolentdeveloper
1
@indolentdeveloper tiene razón, simplemente invierta la desigualdad para eliminar los valores atípicos más bajos, o combínelos con un operador OR.
user6903745
44
La idea del comentario era actualizar las respuestas;). Ya que alguien puede perderse este punto.
Desarrollador indolente
@ user6903745 ¿Y la declaración o "O"?
AB
38

Esta respuesta es similar a la proporcionada por @tanemaki, pero usa una lambdaexpresión en lugar 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 el DataFrame donde solo UNA columna (por ejemplo, 'B') está dentro de tres desviaciones estándar:

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

Vea aquí cómo aplicar esta puntuación z de forma continua: Puntuación Z continua aplicada al marco de datos de pandas

Alejandro
fuente
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
usuario2708149
fuente
Recibo el error "ValueError: No se puede indexar con clave multidimensional" en la línea "df_out = df_in.loc [(df_in [col_name]> fence_low) & (df_in [col_name] <fence_high)]" ¿Ayudarán?
Imran Ahmad Ghazali
18

Para cada serie en el marco de datos, puede usar betweeny quantileeliminar valores atípicos.

x = pd.Series(np.random.normal(size=200)) # with outliers
x = x[x.between(x.quantile(.25), x.quantile(.75))] # without outliers
Jeff Hernández
fuente
3
Aquí está seleccionando solo datos dentro del rango intercuartil (IQR), pero tenga en cuenta que puede haber valores fuera de este rango que no sean valores atípicos.
BCArg
2
Por ejemplo, elegir 0.1 y 0.9 sería bastante seguro. Usar entre y los cuantiles como este es una sintaxis bonita.
PascalVKooten
18

Como no he visto una respuesta que se ocupe de numéricos y no numéricos atributos , aquí hay una respuesta complementaria.

Es posible que desee eliminar los valores atípicos solo en atributos numéricos (las variables categóricas difícilmente pueden ser valores atípicos).

Definición de la función

He extendido la sugerencia de @ tanemaki para manejar datos cuando los atributos no numéricos también están 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)

Ejemplo

Imagine un conjunto de datos dfcon algunos valores sobre casas: callejón, contorno del terreno, precio de venta, ... Por ejemplo: documentación de datos

Primero, desea visualizar los datos en un gráfico de dispersión (con 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 SalePrecio

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

Después - Gr Liv Area Versus SalePrecio

KeyMaker00
fuente
2
Gran solución! Como avisoreduce=Falsepandas
RK1
Sustituir result_type='reduce'por reduce=False.
Ekaba Bisong
8

scipy.statstiene métodos trim1()y trimboth()para cortar los valores atípicos en una sola fila, de acuerdo con la clasificación y un porcentaje introducido de valores eliminados.

Oleg N. Osychenko
fuente
1
trimbothFue más fácil para mí.
wordsforthewise
6

Otra opción es transformar sus datos para mitigar el efecto de los valores atípicos. Puedes hacerlo ganando información a tus datos.

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

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

Datos originales

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

Datos de Winsorized

mgoldwasser
fuente
6

Si le gusta el encadenamiento de métodos, puede obtener su condición booleana para todas las columnas numéricas como esta:

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

Cada valor de cada columna se convertirá en True/Falsefunción de si está a menos de tres desviaciones estándar de la media o no.

Ted Petrou
fuente
Esto debería ser le(3)desde su eliminación de valores atípicos. De esta manera, obtienes Truelos valores atípicos. Además de eso +1 y esta respuesta debería estar más arriba
Erfan
2

Puedes usar la 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])

salida:

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

Como estoy en una etapa muy temprana de mi viaje de ciencia de datos, estoy tratando los valores atípicos con el siguiente código.

#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
Arun Gupta
fuente
1

Obtenga el percentil 98 y 2 como los límites de nuestros valores atípicos

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
fuente
0

A continuación se muestra un ejemplo completo con datos y 2 grupos:

Importaciones:

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

Ejemplo de datos con 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
""")

Leer datos de texto en el marco de datos de pandas:

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

Definir los valores atípicos utilizando desviaciones estándar

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

Defina los valores de datos filtrados y los valores atípicos:

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

Imprime el 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)
Wagner Cipriano
fuente
0

Mi función para descartar valores atípicos

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)
hombres luminosos
fuente
0

Prefiero recortar en lugar de soltar. lo siguiente se enganchará en el lugar en el segundo y 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)))
tnf
fuente
-2

Suprimir y descartar valores atípicos, creo que está mal estadísticamente. Hace que los datos sean diferentes de los datos originales. También hace que los datos tengan una forma desigual y, por lo tanto, la mejor manera es reducir o evitar el efecto de los valores atípicos mediante la transformación logarítmica de los datos. Esto funcionó para mí:

np.log(data.iloc[:, :])
Ezequiel Ohene Asare
fuente
3
No puedo hacer suposiciones acerca de por qué el OP quiere hacer algo.
RajeshM