Estoy tratando de obtener el número de valores atípicos por grupo de un marco de datos de Pandas.

Mis datos se ven así.

df = pd.DataFrame({'group':list('aaaabbbb'),
                   'val':[1,3,3,2,5,6,6,2],
                   'id':[1,1,2,2,2,3,3,3],
                   'mydate':['01/01/2011 01:00:00',
                             '01/01/2011 01:02:00',
                             '01/01/2011 01:05:00',
                             '01/01/2011 01:06:00',
                             '01/01/2011 03:00:00',
                             '01/01/2011 04:00:00',
                             '01/01/2011 05:00:00',
                             '01/01/2011 10:00:00']})
df

Para obtener el número de valores atípicos, estoy usando la siguiente función que obtiene el IQR.

def get_IQR():
    q1 = df["val"].quantile(0.25)
    q3 = df["val"].quantile(0.75)
    iqr = (df["val"] > q1) & (df["val"] < q3)
    return val.loc[iqr]

df[["group","val"]].agg([get_IQR])    

Esto no funciona y produjo los siguientes resultados

ValueError: no results

¿Alguien tiene una mejor estrategia para encontrar el número de valores atípicos por grupo, de modo que ...

group   num_outliers
a        ...
b        ...
c        ...
1
ATMA 26 feb. 2018 a las 20:43

4 respuestas

La mejor respuesta

Si desea utilizar funciones agregadas, debe definirlo de manera diferente. Los pandas pasarán un vector a la función y la función debe generar un único valor. Entonces:

def get_num_outliers (column):
 q1 = np.percentile(column, 25)
 q3 = np.percentile(column, 75)
 return sum((column<q1) | (column>q3))

Entonces llámalo así:

 df.groupby('group').agg([get_num_outliers])
2
Mikhail Venkov 26 feb. 2018 a las 18:29

Aquí hay otra forma (basada en respuesta de jpp):

q1 = df['val'].quantile(0.25)
q3 = df['val'].quantile(0.75)

df['Outlier'] = ~df['val'].between(q1, q3)

df.groupby(['group', 'Outlier'])['id'].count()

# group  Outlier
# a      False      3
#        True       1
# b      False      2
#        True       2
# Name: id, dtype: int64

Explicación :

  • Defina una columna 'Outlier' como booleana en función de si 'val' está o no dentro del rango intercuartil. Guarde esto como una columna en el DataFrame.
  • Agrupe por las columnas 'group' y 'Outlier' y count las ocurrencias de algún campo no nulo (en este caso, elegí 'id', pero podría elegir cualquiera de las columnas en su ejemplo , o simplemente llame a count en el resultado de groupby sin ninguna selección de columna.)

La ventaja de usar esta declaración de dos columnas groupby es que obtienes todas las combinaciones de grupo / Outlier gratis si quieres inspeccionarlas más tarde. Para obtener el resultado en el formato que está solicitando específicamente, subconjunto por 'Outlier' antes de agrupar:

df.loc[df['Outlier']].groupby('group')['id'].count().reset_index().rename(columns={'id': 'num_outliers'})

#   group   num_outliers
# 0 a       1
# 1 b       2
0
PaSTE 26 feb. 2018 a las 19:33

Como desea que se identifiquen los valores atípicos utilizando group - cuantiles específicos, esta es mi solución horrible:

1.Calcule los cuantiles q1 y q3:

qs = df.groupby("group")["val"].quantile([0.25,0.75])
qs = qs.unstack().reset_index()
qs.columns = ["group", "q1", "q3"]
qs
  group    q1   q3
0     a  1.75  3.0
1     b  4.25  6.0

2. Combínelo con df:

df_m = pd.merge(df, qs, on="group", how="left")
df_m
 group  id               mydate  val    q1   q3
0     a   1  01/01/2011 01:00:00    1  1.75  3.0
1     a   1  01/01/2011 01:02:00    3  1.75  3.0
2     a   2  01/01/2011 01:05:00    3  1.75  3.0
3     a   2  01/01/2011 01:06:00    2  1.75  3.0
4     b   2  01/01/2011 03:00:00    5  4.25  6.0
5     b   3  01/01/2011 04:00:00    6  4.25  6.0
6     b   3  01/01/2011 05:00:00    6  4.25  6.0
7     b   3  01/01/2011 10:00:00    2  4.25  6.0

3. Obtenga valores atípicos:

df_m["Outlier"] = ~df_m["val"].between(df_m["q1"], df_m["q3"])
df_m
  group  id               mydate  val    q1   q3  Outlier
0     a   1  01/01/2011 01:00:00    1  1.75  3.0     True
1     a   1  01/01/2011 01:02:00    3  1.75  3.0    False
2     a   2  01/01/2011 01:05:00    3  1.75  3.0    False
3     a   2  01/01/2011 01:06:00    2  1.75  3.0    False
4     b   2  01/01/2011 03:00:00    5  4.25  6.0    False
5     b   3  01/01/2011 04:00:00    6  4.25  6.0    False
6     b   3  01/01/2011 05:00:00    6  4.25  6.0    False
7     b   3  01/01/2011 10:00:00    2  4.25  6.0     True

4.Contar:

df_m.groupby("group")["Outlier"].sum().astype(int)
group
a    1
b    1
0
TYZ 26 feb. 2018 a las 18:06

Aquí hay una manera:

q1 = df['val'].quantile(0.25)
q3 = df['val'].quantile(0.75)

df['Outlier'] = ~df['val'].between(q1, q3)

df.groupby('group')['Outlier'].sum().astype(int).reset_index()

#   group  Outlier
# 0     a        1
# 1     b        2

Explicación

  • Definimos una columna Outlier como booleana en función de si val está dentro del rango intercuartil.
  • Luego agrupamos por group y sumamos la serie Outlier. Esto es posible porque bool es una subclase de int, es decir, True == 1 y False == 0.
  • Convierta a int ya que el resultado solo debe ser números enteros (float es el valor predeterminado).
1
jpp 26 feb. 2018 a las 17:52