• Jobs
  • About Us
  • professionals
    • Home
    • Jobs
    • Courses and challenges
  • business
    • Home
    • Post vacancy
    • Our process
    • Pricing
    • Assessments
    • Payroll
    • Blog
    • Sales
    • Salary Calculator

0

147
Views
Agrupar por Roll up o Roll Down para cualquier tipo de áridos

TL; DR : ¿Cómo podemos lograr algo similar a Group By Roll Up con cualquier tipo de agregados en pandas? (Crédito a @Scott Boston por este término)

Tengo el siguiente marco de datos:

 PQRST 0 PLAC NR F HOL F 1 PLAC NR F NHOL F 2 TRTB NR M NHOL M 3 PLAC NR M NHOL M 4 PLAC NR F NHOL F 5 PLAC RM NHOL M 6 TRTA RF HOL F 7 TRTA NR F HOL F 8 TRTB NR F NHOL F 9 PLAC NR F NHOL F 10 TRTB NR F NHOL F 11 TRTB NR M NHOL M 12 TRTA NR F HOL F 13 PLAC NR F HOL F 14 PLAC RF NHOL F

Para una lista de columnas ['Q', 'R', 'S', 'T'] , quiero calcular algunos agregados en la columna P en la siguiente lista de 4 columnas de agrupación:

  1. ['Q']
  2. ['Q', 'R']
  3. ['Q', 'R', 'S']
  4. ['Q', 'R', 'S', 'T']

Ya he escrito el código para agrupar los marcos de datos anteriores en un número creciente de columnas, y calculo el agregado ( usando count para simplificar ) en cada uno de los objetos groupby, y finalmente los concateno:

 cols = list('QRST') aggCol = 'P' groupCols = [] result = [] for col in cols: groupCols.append(col) result.append(df.groupby(groupCols)[aggCol].agg(count='count').reset_index()) result = pd.concat(result)[groupCols+['count']]

Sin embargo, tengo la fuerte sensación de que el método anterior no es tan eficiente en términos de tiempo de CPU. ¿Existe una forma más eficiente de aplicar agregados en un número de columnas que aumenta continuamente para agrupar?

Por qué creo que no es tan eficiente es porque: para los valores anteriores, en la primera iteración, agrupa el marco de datos en la columna Q y luego calcula el agregado. Luego, en la siguiente iteración, agrupa el marco de datos en Q y R , lo que significa que nuevamente necesita agruparlo por Q y luego por R , pero ya estaba agrupado por Q en la primera iteración, por lo que se repite la misma operación. Si hay alguna forma de utilizar los grupos creados anteriormente, creo que será eficiente.

PRODUCCIÓN:

 QRST count 0 NR NaN NaN NaN 12 1 R NaN NaN NaN 3 0 NR F NaN NaN 9 1 NR M NaN NaN 3 2 RF NaN NaN 2 3 RM NaN NaN 1 0 NR F HOL NaN 4 1 NR F NHOL NaN 5 2 NR M NHOL NaN 3 3 RF HOL NaN 1 4 RF NHOL NaN 1 5 RM NHOL NaN 1 0 NR F HOL F 4 1 NR F NHOL F 5 2 NR M NHOL M 3 3 RF HOL F 1 4 RF NHOL F 1 5 RM NHOL M 1

Ya investigué ¿Hay un equivalente de SQL GROUP BY ROLLUP en Python pandas? y los subtotales de fila de las tablas Pandas Pivot , no funcionan en mi caso, ya los probé, es decir, este método se puede usar para obtener solo el recuento e inmediatamente falla incluso para recuentos únicos cuando aparece el mismo identificador para más de un valor:

 pd.pivot_table(df, aggCol, columns=cols, aggfunc='count', margins=True).T.reset_index() QRSTP 0 NR F HOL F 4 1 NR F NHOL F 5 2 NR M NHOL M 3 3 NR All 3 4 RF HOL F 1 5 RF NHOL F 1 6 RM NHOL M 1 7 R All 3

ACTUALIZAR

Para evitar confusiones innecesarias con solo obtener el count según la sugerencia en el comentario, lo he agregado para la media como agregado, cambiando la columna P a un tipo numérico:

 PQRST 0 9 NR F HOL F 1 7 NR F NHOL F 2 3 NR M NHOL M 3 9 NR M NHOL M 4 1 NR F NHOL F 5 0 RM NHOL M 6 1 RF HOL F 7 7 NR F HOL F 8 2 NR F NHOL F 9 2 NR F NHOL F 10 1 NR F NHOL F 11 2 NR M NHOL M 12 3 NR F HOL F 13 6 NR F HOL F 14 0 RF NHOL F cols = list('QRST') cols = list('QRST') aggCol = 'P' groupCols = [] result = [] for col in cols: groupCols.append(col) result.append(df.groupby(groupCols)[aggCol] .agg(agg=np.mean) .round(2).reset_index()) result = pd.concat(result)[groupCols+['agg']]
 >>> result QRST agg 0 NR NaN NaN NaN 4.33 1 R NaN NaN NaN 0.33 0 NR F NaN NaN 4.22 1 NR M NaN NaN 4.67 2 RF NaN NaN 0.50 3 RM NaN NaN 0.00 0 NR F HOL NaN 6.25 1 NR F NHOL NaN 2.60 2 NR M NHOL NaN 4.67 3 RF HOL NaN 1.00 4 RF NHOL NaN 0.00 5 RM NHOL NaN 0.00 0 NR F HOL F 6.25 1 NR F NHOL F 2.60 2 NR M NHOL M 4.67 3 RF HOL F 1.00 4 RF NHOL F 0.00 5 RM NHOL M 0.00
over 3 years ago · Santiago Trujillo
4 answers
Answer question

0

Creo que esto es un poco más eficiente:

 b = df.groupby(cols)[aggCol].count() l = list(range(b.index.nlevels-1)) p = [b] while l: p.append(b.groupby(level=l).sum()) l.pop() result = pd.concat(p)

Horarios:

7,4 ms ± 55,5 µs por bucle (media ± desviación estándar de 7 ejecuciones, 100 bucles cada una)

contra el original

20,7 ms ± 300 µs por bucle (media ± desviación estándar de 7 ejecuciones, 10 bucles cada una)

Usar la suma en lugar de contar todos los elementos cada vez. Cuente todos los elementos una vez y luego sume los niveles para que el índice disminuya.


Con mean o el promedio, podemos usar la sugerencia de @PierreD con una suma y un conteo y luego agregar:

 from itertools import zip_longest cols = list('QRST') aggCol = 'P' b = df.groupby(cols)[aggCol].agg(['sum', 'count']) l = list(range(b.index.nlevels-1)) p = [b] while l: p.append(b.groupby(level=l).sum()) l.pop() result = pd.concat(p) result = result.assign(avg=result['sum']/result['count']).drop(['sum', 'count'], axis=1) result

resultado.index = pd.MultiIndex.from_arrays(list(zip_longest(*result.index)))

Producción:

 avg (NR, F, HOL, F) 6.250000 (NR, F, NHOL, F) 2.600000 (NR, M, NHOL, M) 4.666667 (R, F, HOL, F) 1.000000 (R, F, NHOL, F) 0.000000 (R, M, NHOL, M) 0.000000 (NR, F, HOL) 6.250000 (NR, F, NHOL) 2.600000 (NR, M, NHOL) 4.666667 (R, F, HOL) 1.000000 (R, F, NHOL) 0.000000 (R, M, NHOL) 0.000000 (NR, F) 4.222222 (NR, M) 4.666667 (R, F) 0.500000 (R, M) 0.000000 NR 4.333333 R 0.333333
over 3 years ago · Santiago Trujillo Report

0

La función groupby de Pandas es versátil y puede tomar funciones personalizadas. Presento la solución con una lambda que devuelve el recuento, pero podría sustituir fácilmente np.min o np.max u otras funciones personalizadas. Tenga en cuenta que cualquiera de estas funciones debería tener sentido cuando se aplica recursivamente sobre los niveles de anidamiento de groupby (por lo que count, min, max tendrán sentido, pero si tiene una función estadística como mean, perderá la información necesaria para calcular agregados correctos en agrupaciones más altas).

 df=pd.DataFrame.from_records( [['PLAC','NR','F','HOL','F'], ['PLAC','NR', 'F', 'NHOL', 'F'], ['TRTB','NR', 'M', 'NHOL', 'M'], ['PLAC','NR', 'M', 'NHOL', 'M'], ['PLAC','NR', 'F', 'NHOL', 'F'], ['PLAC','R', 'M', 'NHOL', 'M'], ['TRTA','R', 'F', 'HOL', 'F'], ['TRTA','NR', 'F', 'HOL', 'F'], ['TRTB','NR', 'F', 'NHOL', 'F'], ['PLAC','NR', 'F', 'NHOL', 'F'], ['TRTB','NR', 'F', 'NHOL', 'F'], ['TRTB','NR', 'M', 'NHOL', 'M'], ['TRTA','NR', 'F', 'HOL', 'F'], ['PLAC','NR', 'F', 'HOL', 'F'], ['PLAC','R', 'F', 'NHOL', 'F']], columns = ['P','Q','R','S','T'])

Primero, defina un marco de datos groupby utilizando las agrupaciones más granulares:

 grdf = df.groupby(['Q','R','S','T'])['P'].apply(lambda x:len(x)).to_frame()

Ahora use el método unstack() de este marco de datos para obtener agregados en niveles de agrupación menos granulares. Por ejemplo, en un nivel superior con índice como ['Q','R','S']:

 df2 = df.unstack() result2 = df2.sum(axis=1).rename(str(df2.index.names)).to_frame()

resultado2 se verá así:

ingrese la descripción de la imagen aquí

Del mismo modo, calcule los agregados en todos los niveles de agrupación deseados y agréguelos todos al mismo marco de datos usando una función como esta (idealmente, puede convertir esto en una función recursiva, pero lo mantuve simple para que el flujo se pueda ver fácilmente):

 def combine_aggregates(df): #if type(grdf) == pd.core.frame.DataFrame: df1 = df result1 = df.sum(axis=1).rename(str(df1.index.names)).to_frame() df2 = df1.unstack() result2 = df2.sum(axis=1).rename(str(df2.index.names)).to_frame() df3 = df2.unstack() result3 = df3.sum(axis=1).rename(str(df3.index.names)).to_frame() df4 = df3.unstack() result4 = df4.sum(axis=1).rename(str(df4.index.names)).to_frame() return result1.append(result2).append(result3).append(result4) combine_aggregates(grdf)

Y el resultado final será:

ingrese la descripción de la imagen aquí

over 3 years ago · Santiago Trujillo Report

0

La mayor parte del código es el mismo excepto la indexación y un argumento de motor adicional

Preajusto el índice, luego agrupo los niveles uno a la vez

También para el rendimiento trato de usar numba para tipos numéricos Enhancingperf . Parece que dependiendo del tamaño de df, puede agregar opciones paralelas y nogil en numba.

La primera ejecución de Numba podría ser lenta a medida que se compila, pero la ejecución posterior debería ser más rápida

 l = list('QRST') df1 = df1.set_index(l) result = [ df1.groupby(level=l[:i+1])['P'].agg(np.mean, engine='numba').round(2).reset_index() for i in range(4) ] pd.concat(result)
over 3 years ago · Santiago Trujillo Report

0

Solución:

Tal vez podrías probar esto con recursividad.

Como el siguiente:

 newdf = pd.DataFrame(columns=df.columns) cols = list('QRST') aggCol = 'P' def aggregation(cols, origcols, aggCol, df, count=1): global newdf cols = origcols[:count] count += 1 newdf = newdf.append(df.groupby(cols)[aggCol].agg('mean').round(2).reset_index().T.reindex(origcols + [aggCol]).T, ignore_index=True) if cols != origcols: aggregation(cols, origcols, aggCol, df, count) aggregation(cols, cols, aggCol, df) newdf['agg'] = newdf.pop(aggCol) print(newdf)

Producción:

 QRST agg 0 NR NaN NaN NaN 4.33 1 R NaN NaN NaN 0.33 2 NR F NaN NaN 4.22 3 NR M NaN NaN 4.67 4 RF NaN NaN 0.5 5 RM NaN NaN 0 6 NR F HOL NaN 6.25 7 NR F NHOL NaN 2.6 8 NR M NHOL NaN 4.67 9 RF HOL NaN 1 10 RF NHOL NaN 0 11 RM NHOL NaN 0 12 NR F HOL F 6.25 13 NR F NHOL F 2.6 14 NR M NHOL M 4.67 15 RF HOL F 1 16 RF NHOL F 0 17 RM NHOL M 0

Horarios:

Temporización con el siguiente código (ejecutándolo 5000 veces):

 import time u11time1 = time.time() for i in range(5000): df = pd.read_clipboard() newdf = pd.DataFrame(columns=df.columns) cols = list('QRST') aggCol = 'P' def aggregation(cols, origcols, aggCol, df, count=1): global newdf cols = origcols[:count] count += 1 newdf = newdf.append(df.groupby(cols)[aggCol].agg('mean').round(2).reset_index().T.reindex(origcols + [aggCol]).T, ignore_index=True) if cols != origcols: aggregation(cols, origcols, aggCol, df, count) aggregation(cols, cols, aggCol, df) newdf['agg'] = newdf.pop(aggCol) u11time2 = time.time() print('u11 time:', u11time2 - u11time1) thepyguytime1 = time.time() for i in range(5000): df = pd.read_clipboard() cols = list('QRST') aggCol = 'P' groupCols = [] result = [] for col in cols: groupCols.append(col) result.append(df.groupby(groupCols)[aggCol].agg(count='count').reset_index()) result = pd.concat(result)[groupCols+['count']] thepyguytime2 = time.time() print('ThePyGuy time:', thepyguytime2 - thepyguytime1)

Da:

 u11 time: 120.2678394317627 ThePyGuy time: 153.01533579826355

Mi código es más rápido por 33 segundos...

Pero si lo ejecuta solo unas pocas veces, es decir, 10 veces, mi código suele ganar, pero no con un margen tan grande. Pero para más iteraciones, es decir, 5000 veces, mi código funciona mucho más rápido que su código de bucle for original.

La conclusión es: mi solución se ejecuta más rápido :)

over 3 years ago · Santiago Trujillo Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Our process Sales
Legal
Terms and conditions Privacy policy
© 2025 PeakU Inc. All Rights Reserved.

Andres GPT

Recommend me some offers
I have an error