Agrupación de una columna con pandas

3 minutos de lectura

avatar de usuario
Caminante Nocturno

Tengo una columna de marco de datos con valores numéricos:

df['percentage'].head()
46.5
44.2
100.0
42.12

Quiero ver la columna como bin cuenta:

bins = [0, 1, 5, 10, 25, 50, 100]

¿Cómo puedo obtener el resultado como contenedores con sus el valor cuenta?

[0, 1] bin amount
[1, 5] etc
[5, 10] etc
...

avatar de usuario
jezrael

Puedes usar pandas.cut:

bins = [0, 1, 5, 10, 25, 50, 100]
df['binned'] = pd.cut(df['percentage'], bins)
print (df)
   percentage     binned
0       46.50   (25, 50]
1       44.20   (25, 50]
2      100.00  (50, 100]
3       42.12   (25, 50]

bins = [0, 1, 5, 10, 25, 50, 100]
labels = [1,2,3,4,5,6]
df['binned'] = pd.cut(df['percentage'], bins=bins, labels=labels)
print (df)
   percentage binned
0       46.50      5
1       44.20      5
2      100.00      6
3       42.12      5

O numpy.searchsorted:

bins = [0, 1, 5, 10, 25, 50, 100]
df['binned'] = np.searchsorted(bins, df['percentage'].values)
print (df)
   percentage  binned
0       46.50       5
1       44.20       5
2      100.00       6
3       42.12       5

…y entonces value_counts o groupby y agregado size:

s = pd.cut(df['percentage'], bins=bins).value_counts()
print (s)
(25, 50]     3
(50, 100]    1
(10, 25]     0
(5, 10]      0
(1, 5]       0
(0, 1]       0
Name: percentage, dtype: int64

s = df.groupby(pd.cut(df['percentage'], bins=bins)).size()
print (s)
percentage
(0, 1]       0
(1, 5]       0
(5, 10]      0
(10, 25]     0
(25, 50]     3
(50, 100]    1
dtype: int64

Por defecto cut devoluciones categorical.

Series métodos como Series.value_counts() utilizará todas las categorías, incluso si algunas categorías no están presentes en los datos, operaciones en categórico.

  • sin que bins = [0, 1, 5, 10, 25, 50, 100], ¿puedo simplemente decir crear 5 contenedores y lo cortará por corte promedio? por ejemplo, tengo 110 registros, quiero dividirlos en 5 contenedores con 22 registros en cada contenedor.

    – qqqwww

    30 de mayo de 2018 a las 18:38

  • @qqqwww – No estoy seguro si entiendo, ¿usted cree qcut? Enlace

    – jezrael

    30 mayo 2018 a las 18:41

  • @qqqwww para hacer eso, el ejemplo pd.cut en su página lo muestra: pd.cut(np.array([1, 7, 5, 4, 6, 3]), 3) cortará la matriz en 3 partes iguales.

    – Ayan Mitra

    21 de julio de 2020 a las 12:22


  • @AyanMitra – ¿Crees que df.groupby(pd.cut(df['percentage'], bins=bins)).mean() ?

    – jezrael

    21 de julio de 2020 a las 12:48

  • Gracias esta respuesta me ayudo 🙂

    – Stav Cohen

    7 de junio de 2021 a las 9:34

avatar de usuario
Erfán

Utilizando el Numba Módulo para acelerar.

En grandes conjuntos de datos (más de 500k), pd.cut puede ser bastante lento para agrupar datos.

Escribí mi propia función en Numba con compilación justo a tiempo, que es aproximadamente seis veces más rápido:

from numba import njit

@njit
def cut(arr):
    bins = np.empty(arr.shape[0])
    for idx, x in enumerate(arr):
        if (x >= 0) & (x < 1):
            bins[idx] = 1
        elif (x >= 1) & (x < 5):
            bins[idx] = 2
        elif (x >= 5) & (x < 10):
            bins[idx] = 3
        elif (x >= 10) & (x < 25):
            bins[idx] = 4
        elif (x >= 25) & (x < 50):
            bins[idx] = 5
        elif (x >= 50) & (x < 100):
            bins[idx] = 6
        else:
            bins[idx] = 7

    return bins
cut(df['percentage'].to_numpy())

# array([5., 5., 7., 5.])

Opcional: también puede asignarlo a contenedores como cadenas:

a = cut(df['percentage'].to_numpy())

conversion_dict = {1: 'bin1',
                   2: 'bin2',
                   3: 'bin3',
                   4: 'bin4',
                   5: 'bin5',
                   6: 'bin6',
                   7: 'bin7'}

bins = list(map(conversion_dict.get, a))

# ['bin5', 'bin5', 'bin7', 'bin5']

Comparación de velocidad:

# Create a dataframe of 8 million rows for testing
dfbig = pd.concat([df]*2000000, ignore_index=True)

dfbig.shape

# (8000000, 1)
%%timeit
cut(dfbig['percentage'].to_numpy())

# 38 ms ± 616 µs per loop (mean ± standard deviation of 7 runs, 10 loops each)
%%timeit
bins = [0, 1, 5, 10, 25, 50, 100]
labels = [1,2,3,4,5,6]
pd.cut(dfbig['percentage'], bins=bins, labels=labels)

# 215 ms ± 9.76 ms per loop (mean ± standard deviation of 7 runs, 10 loops each)

También podríamos usar np.select:

bins = [0, 1, 5, 10, 25, 50, 100]
df['groups'] = (np.select([df['percentage'].between(i, j, inclusive="right") 
                           for i,j in zip(bins, bins[1:])], 
                          [1, 2, 3, 4, 5, 6]))

Producción:

   percentage  groups
0       46.50       5
1       44.20       5
2      100.00       6
3       42.12       5

  • Me interesaría ver cómo se compara esto en cuanto a velocidad con la solución de corte.

    – goryh

    13 de junio a las 21:12

¿Ha sido útil esta solución?