Me he enfrentado a un problema al aplicar sort_values () y cumsum () dentro de un grupo.

Tengo un conjunto de datos:

enter image description here

Básicamente, necesito ordenar los valores dentro de un grupo, obtener ventas acumulativas y seleccionar aquellas líneas que componen el 90% de las ventas.

Para llegar primero

enter image description here

Y luego, solo seleccione el 90% de las ventas dentro de cada región

enter image description here

He intentado lo siguiente pero la última línea no funciona. Devuelve un error: no se puede acceder al atributo invocable 'sort_values' de los objetos 'SeriesGroupBy', intente usar el método 'apply'

He intentado aplicar también ...

import pandas as pd
df = pd.DataFrame({'id':['id_1', 
'id_2','id_3','id_4','id_5','id_6','id_7','id_8', 'id_1', 
'id_2','id_3','id_4','id_5','id_6','id_7','id_8'],
               'region':[1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,],
               'sales':[54,34,23,56,78,98,76,34,27,89,76,54,34,45,56,54]})
df['%']=df['sales']/df.groupby(df['region'])['sales'].transform('sum')
df['cumul'] = df.groupby(df['region'])['sales'].sort_values(ascending=False).cumsum()

Gracias por todas las sugerencias

1
Vero 3 oct. 2019 a las 16:37

3 respuestas

La mejor respuesta

Definitivamente puede ordenar el marco de datos primero, luego haga groupby():

df.sort_values(['region','sales'], ascending=[True,False],inplace=True)

df['%']=df['sales']/df.groupby(df['region'])['sales'].transform('sum')

df['cummul'] = df.groupby('region')['%'].cumsum()

# filter
df[df['cummul'].le(0.9)]

Salida:

      id  region  sales         %    cummul
5   id_6       1     98  0.216336  0.216336
4   id_5       1     78  0.172185  0.388521
6   id_7       1     76  0.167770  0.556291
3   id_4       1     56  0.123620  0.679912
0   id_1       1     54  0.119205  0.799117
1   id_2       1     34  0.075055  0.874172
9   id_2       2     89  0.204598  0.204598
10  id_3       2     76  0.174713  0.379310
14  id_7       2     56  0.128736  0.508046
11  id_4       2     54  0.124138  0.632184
15  id_8       2     54  0.124138  0.756322
13  id_6       2     45  0.103448  0.859770
3
Quang Hoang 3 oct. 2019 a las 13:49

Si solo necesita los datos de ventas sin el porcentaje, esto se puede hacer fácilmente con el método de encadenamiento:

(
  df
  .sort_values(by='sales', ascending=False)
  .groupby('region')
  .apply(lambda x[x.sales > x.sales.quantile(.1)])
  .reset_index(level=0, drop=True)
)

Salida

      id  region  sales
5   id_6       1     98
4   id_5       1     78
6   id_7       1     76
3   id_4       1     56
0   id_1       1     54
1   id_2       1     34
7   id_8       1     34
9   id_2       2     89
10  id_3       2     76
14  id_7       2     56
11  id_4       2     54
15  id_8       2     54
13  id_6       2     45
12  id_5       2     34

Esto funciona porque obtener todos los valores superiores al 10% es esencialmente lo mismo que obtener el 90% superior.

2
adrianp 3 oct. 2019 a las 13:59

Primero usamos su lógica para crear la columna %, pero multiply por 100 y round a números enteros.

Luego ordenamos por region y %, sin necesidad de groupby.

Después de ordenar, creamos la columna cumul.

Y finalmente seleccionamos aquellos dentro del rango 90% con query:

df['%'] = df['sales'].div(df.groupby('region')['sales'].transform('sum')).mul(100).round()
df = df.sort_values(['region', '%'], ascending=[True, False])
df['cumul'] = df.groupby('region')['%'].cumsum()

df.query('cumul.le(90)')

salida

      id  region  sales     %  cumul
5   id_6       1     98  22.0   22.0
4   id_5       1     78  17.0   39.0
6   id_7       1     76  17.0   56.0
0   id_1       1     54  12.0   68.0
3   id_4       1     56  12.0   80.0
1   id_2       1     34   8.0   88.0
9   id_2       2     89  20.0   20.0
10  id_3       2     76  17.0   37.0
14  id_7       2     56  13.0   50.0
11  id_4       2     54  12.0   62.0
15  id_8       2     54  12.0   74.0
13  id_6       2     45  10.0   84.0
3
Erfan 3 oct. 2019 a las 13:51
58220369