Agrupar una columna con pandas

Resuelto Night Walker asked hace 7 años • 4 respuestas

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 cuenta el contenedor :

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

¿ Cómo puedo obtener el resultado como contenedores con sus recuentos de valores ?

[0, 1] bin amount
[1, 5] etc
[5, 10] etc
...
Night Walker avatar Jul 24 '17 13:07 Night Walker
Aceptado

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 luego value_countso groupbyy 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 cutdevuelve categorical.

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

jezrael avatar Jul 24 '2017 06:07 jezrael

Usando el módulo Numba para acelerar.

En conjuntos de datos grandes (más de 500 000), pd.cutpuede resultar bastante lento agrupar datos.

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

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 puedes 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)
Erfan avatar Jan 18 '2020 00:01 Erfan

Opción conveniente y rápida usando Numpy

np.digitize es una opción cómoda y rápida:

import pandas as pd
import numpy as np

df = pd.DataFrame({'x': [1,2,3,4,5]})
df['y'] = np.digitize(df['x'], bins=[3,5]) # convert column to bin

print(df)

devoluciones

   x  y
0  1  0
1  2  0
2  3  1
3  4  1
4  5  2
Scriddie avatar Jan 27 '2023 10:01 Scriddie

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
 avatar Feb 25 '2022 00:02