Aplicación de una función agregada grupal personalizada para generar un resultado binario en pandas python

Tengo un conjunto de datos de transacciones del comerciante donde la variable de interés es Buy/Sell que es binario y toma el valor de 1 si la transacción fue una compra y 0 si es una venta. Un ejemplo se ve como sigue:

 Trader Buy/Sell A 1 A 0 B 1 B 1 B 0 C 1 C 0 C 0 

Me gustaría calcular la Buy/Sell neta para cada comerciante de tal manera que si el comerciante tuviera más del 50% de las operaciones como una compra, tendría una Buy/Sell de 1, si tuviera una compra de menos del 50%, entonces lo haría. tener una Buy/Sell de 0 y si fuera exactamente el 50% tendría NA (y no se tomaría en cuenta en futuros cálculos).

Entonces, para el comerciante A, la proporción de compra es (número de compras) / (número total de comerciantes) = 1/2 = 0.5 que da NA.

Para el comerciante B es 2/3 = 0.67 lo que da un 1

Para el comerciante C es 1/3 = 0,33, lo que da un 0

La tabla debe verse así:

 Trader Buy/Sell A NA B 1 C 0 

En última instancia, quiero calcular el número total agregado de compras, que en este caso es 1, y el número total agregado de operaciones (sin tener en cuenta las NA) que en este caso es 2. No estoy interesado en la segunda tabla, solo estoy interesado en el número agregado de compras y el número total agregado (recuento) de Buy/Sell .

¿Cómo puedo hacer esto en Pandas?

 import numpy as np import pandas as pd df = pd.DataFrame({'Buy/Sell': [1, 0, 1, 1, 0, 1, 0, 0], 'Trader': ['A', 'A', 'B', 'B', 'B', 'C', 'C', 'C']}) grouped = df.groupby(['Trader']) result = grouped['Buy/Sell'].agg(['sum', 'count']) means = grouped['Buy/Sell'].mean() result['Buy/Sell'] = np.select(condlist=[means>0.5, means<0.5], choicelist=[1, 0], default=np.nan) print(result) 

rendimientos

  Buy/Sell sum count Trader A NaN 1 2 B 1 2 3 C 0 1 3 

Mi respuesta original usó un agregador personalizado, categorize :

 def categorize(x): m = x.mean() return 1 if m > 0.5 else 0 if m < 0.5 else np.nan result = df.groupby(['Trader'])['Buy/Sell'].agg([categorize, 'sum', 'count']) result = result.rename(columns={'categorize' : 'Buy/Sell'}) 

Mientras que llamar a una función personalizada puede ser conveniente, el rendimiento suele ser significativamente más lento cuando se usa una función personalizada en comparación con los agregadores integrados (como groupby/agg/mean ). Los agregadores incorporados están Cythonized, mientras que las funciones personalizadas reducen el rendimiento a velocidades de bucle for Python.

La diferencia en la velocidad es particularmente significativa cuando el número de grupos es grande. Por ejemplo, con un DataFrame de 10000 filas con 1000 grupos,

 import numpy as np import pandas as pd np.random.seed(2017) N = 10000 df = pd.DataFrame({ 'Buy/Sell': np.random.randint(2, size=N), 'Trader': np.random.randint(1000, size=N)}) def using_select(df): grouped = df.groupby(['Trader']) result = grouped['Buy/Sell'].agg(['sum', 'count']) means = grouped['Buy/Sell'].mean() result['Buy/Sell'] = np.select(condlist=[means>0.5, means<0.5], choicelist=[1, 0], default=np.nan) return result def categorize(x): m = x.mean() return 1 if m > 0.5 else 0 if m < 0.5 else np.nan def using_custom_function(df): result = df.groupby(['Trader'])['Buy/Sell'].agg([categorize, 'sum', 'count']) result = result.rename(columns={'categorize' : 'Buy/Sell'}) return result 

using_select es using_select más rápido que using_custom_function :

 In [69]: %timeit using_custom_function(df) 10 loops, best of 3: 132 ms per loop In [70]: %timeit using_select(df) 100 loops, best of 3: 2.46 ms per loop In [71]: 132/2.46 Out[71]: 53.65853658536585