Fórmula DAX para calcular el total acumulado acumulado en dos columnas que se divide a la mitad en días no laborables

Un usuario Pregunto ✅

Jeffreyweir

De acuerdo, esto no es estrictamente un problema de PowerBI, sino más bien un problema de DAX que estoy tratando de resolver en PowerPivot./Excel, aunque migraremos el libro de trabajo a PowerBI a su debido tiempo.

Así que aquí está mi problema:

Tengo algunos datos (90,000 filas) que estoy tratando de usar para calcular un «puntaje de fatiga» acumulativo para los turnos de trabajo populares … actualmente usando PowerPivot / Excel 2016.

Según la captura de pantalla a continuación, el conjunto de datos son datos de turno para varios empleados, que tiene un recuento acumulativo de días trabajados frente a días libres que se restablece a 1 cada vez que cambian de un estado a otro, y una columna de ‘Puntaje’ que en mis datos de producción contienen una medida de cuán fatigados están.

Me gustaría sumar acumulativamente esa puntuación de fatiga y dividir a la mitad la suma acumulada de cada línea en la que Workday sea FALSE. . Mi resultado deseado está en la columna ‘Total acumulativo’ en el extremo derecho, y utilicé resaltado gris para mostrar los días trabajados frente a los días libres, así como para poner un borde en negrita alrededor de los bloques Emp_ID separados para ayudar a demostrar los datos.

Capture.PNG

Esto es muy similar a una pregunta que hice anteriormente en StackOverflow, con la diferencia de que necesito que el total acumulativo se reduzca a la mitad cada vez que el valor de Workday sea FALSE. A Alejondro se le ocurrió una solución a ese problema que se veía así:

Puntuación acumulada =
CALCULAR (
SUM (‘Turnos'[Score] ),
FILTRO (
TODOS SELECCIONADOS (turnos),
[Helper] = MAX ( [Helper] )
&& [EMP_ID] = MAX (turnos[EMP_ID] )
&& [Date] <= MAX (turnos[Date] )
)
)

… pero necesito enmendar esto para reducir a la mitad el total acumulativo cada vez que el valor de Workday sea FALSE.

Si bien hacer este tipo de ajustes a los totales acumulados es muy simple en Excel, no estoy seguro de que esto sea posible en DAX. Actualmente, de hecho, estamos usando PowerPivot para entregar los datos en Excel, y luego usamos Excel para crear la lógica acumulativa, y luego recuperamos los totales acumulados en PowerPivot a través de una tabla vinculada, que funciona pero es torpe.

OwenAuger

En respuesta a Jeffreyweir

Hola @jeffreyweir

@Phil_Seamark me alertó sobre este hilo.

Sí, su total acumulado se puede realizar en una columna calculada por DAX. (También podría considerar Power Query).

Ejemplo de pbix aquí para ilustrar el DAX.

Una versión de la columna es:

Cumulative Total (new DAX) = 
VAR OuterDate = Shifts[Date]
RETURN
    SUMX (
        CALCULATETABLE (
            Shifts,
            ALLEXCEPT ( Shifts, Shifts[Emp_ID] ),
            Shifts[Date] <= OuterDate
        ),
        VAR InnerDate = Shifts[Date]
        RETURN
            POWER (
                0.5,
                CALCULATE (
                    COUNTROWS ( Shifts ),
                    ALLEXCEPT ( Shifts, Shifts[Emp_ID] ),
                    Shifts[Date] <= OuterDate,
                    Shifts[Date] >= InnerDate,
                    NOT ( Shifts[Workday] )
                )
            )
                * Shifts[Score]
    )

Curiosamente, una versión que funciona en «DAX antiguo» sin variables es:

Cumulative Total (old DAX) = 
SUMX (
    CALCULATETABLE (
        Shifts,
        ALLEXCEPT ( Shifts, Shifts[Emp_ID] ),
        Shifts[Date] <= EARLIER ( Shifts[Date] )
    ),
    POWER (
        0.5,
        CALCULATE (
            COUNTROWS ( Shifts ),
            ALLEXCEPT ( Shifts, Shifts[Emp_ID] ),
            Shifts[Date] <= EARLIER ( Shifts[Date], 2 ),
            Shifts[Date] >= EARLIER ( Shifts[Date] ),
            NOT ( Shifts[Workday] )
        )
    )
        * Shifts[Score]
)

La forma en que he replicado el cálculo recursivo es

  1. Tome las filas de turnos del empleado actual hasta la fecha actual.
  2. Para cada una de esas filas, cuente el número de días no laborables desde la fecha de esa fila hasta la fecha actual.
  3. Calcule 0.5 ^ (recuento de filas del paso 2) y multiplique por la puntuación de cada fila, luego sume.

La razón por la que esto funciona es que si imagina calcular el total acumulado para cada fila por turno, cada vez que llega a otro día no laborable, cada fila anterior se multiplica por otro factor de 0,5.

Salud,

Owen

Jeffreyweir

Esto funciona casi perfectamente en producción. Casi, porque no maneja correctamente la situación en la que un empleado no

Jeffreyweir

En respuesta a Jeffreyweir

Esto funciona casi perfectamente en producción. Casi, porque no maneja correctamente la situación cuando los primeros días no son laborables. A la izquierda está lo que busco, a la derecha es lo que consigo.

Dicho esto, probablemente esto sea lo suficientemente bueno para mis necesidades. Pero sería genial si la función pudiera modificarse para comenzar a calcular solo desde el primer día hábil, de modo que esos días no hábiles iniciales no hagan que la primera lectura se reduzca a la mitad.

.Capture.PNG

OwenAuger

En respuesta a Jeffreyweir

Hola @jeffreyweir

¿Podría mostrar las otras columnas junto con su extracto anterior?

Solo estaba tratando de averiguar si tiene una puntuación distinta de cero en esos días iniciales no laborales.

¿Está diciendo que si hay una puntuación en los días no laborables iniciales, solo desea que se incluya en la suma acumulada desde el primer día laborable en adelante y que no se produzca una reducción a la mitad antes del primer día laborable?

Salud,

Owen

Jeffreyweir

En respuesta a OwenAuger

Hola Owen. Fue una puntuación de cero … siempre es una puntuación de cero (es decir, sin «puntos de fatiga») en un día no laborable. Entonces

  • Los días laborables, el personal acumula ‘puntos de fatiga’
  • En los días no laborables, el personal no obtiene puntos de fatiga y su cantidad acumulada se reduce a la mitad de lo que era anteriormente.

Aparte de eso, su DAX funciona bien. Pero debe ser REALMENTE costoso computacionalmente, ya que a veces tengo que esperar alrededor de 6 minutos al guardar el archivo, o al cambiar entre Excel y la ventana de PowerPivot, o al hacer cualquier tipo de cambio en la ventana de PowerPivot, como cambiar el nombre de un columna o eliminándola.

Anteriormente, estaba haciendo el bit acumulativo en Excel (usando una tabla dinámica para obtener los datos sin procesar allí para luego poder hacer los cálculos en una tabla al lado), luego volviéndolos a absorber en el modelo de datos a través de una tabla vinculada. Que funciona bien, y debo decir que es mucho más rápido. Pero más complicado que una solución totalmente DAX.

Probaré la solución PowerQuery y veré qué impacto tiene en el rendimiento. También intentaré migrar todo el proyecto de PowerPIvot a Analysis Services y veré qué tipo de rendimiento obtengo. Pero es posible que todavía tenga que seguir haciendo los cálculos acumulativos en Excel. Lo cual no es un problema … solo requiere una actualización adicional, ya que necesito obtener datos actualizados en Excel, y luego, una vez que Excel crea el total acumulativo, necesito actualizar la tabla acumulativa vinculada para que se obtengan los cálculos acumulativos recién actualizados publicado de nuevo en el modelo de datos.

Ciertamente aprecio el interés en este hilo … será una muy buena oportunidad de aprendizaje incluso si terminamos con el status quo.

MarcelBeug

En respuesta a OwenAuger

Extraño: cuando probé las 3 soluciones, estos incluyeron casos que comenzaron con un día no laborable y todos funcionaron bien.

Sin embargo, en mis datos de prueba, todos los días no laborables tenían una puntuación de 0 (como en los ejemplos de la publicación n. ° 1).

En el caso de que un primer día no laborable tenga una puntuación, las soluciones de Power Query aún funcionan bien Lengua sonriente pero la solución DAX comienza con esta puntuación.

Supongo que no sería difícil ajustar eso, si es necesario,

Hola @jeffreyweir,

Entonces, ¿qué columnas ya tiene su conjunto de datos y para qué columnas le gustaría una columna / medida calculada?

¿Tiene una versión txt de los datos para facilitar la importación, en lugar de una foto para ayudar a acelerar las sugerencias? 🙂

Jeffreyweir

En respuesta a Phil_Seamark

Vaya, lo siento Phil … olvidé subir ese archivo. Subiré a onedrive y publicaré el enlace tan pronto como pueda (no puedo hacerlo desde el trabajo), pero mientras tanto le he enviado una copia por correo electrónico en forma de hoja de cálculo.

Con respecto al resto de su pregunta, no estoy muy seguro de seguirla. Con los datos de la muestra, la columna calculada debe coincidir con los resultados de la muestra en la columna ‘Total acumulado’ y debe hacer referencia a la columna Emp_ID, la columna Workday y la columna Score.

Jeffreyweir

En respuesta a Jeffreyweir

Aquí hay un enlace al archivo de muestra:
https://1drv.ms/x/s!Ah_zTnaUo4Dzjnt6KWy8ZxWAEfPC

OwenAuger

En respuesta a Jeffreyweir

Hola @jeffreyweir

@Phil_Seamark me alertó sobre este hilo.

Sí, su total acumulado se puede realizar en una columna calculada por DAX. (También podría considerar Power Query).

Ejemplo de pbix aquí para ilustrar el DAX.

Una versión de la columna es:

Cumulative Total (new DAX) = 
VAR OuterDate = Shifts[Date]
RETURN
    SUMX (
        CALCULATETABLE (
            Shifts,
            ALLEXCEPT ( Shifts, Shifts[Emp_ID] ),
            Shifts[Date] <= OuterDate
        ),
        VAR InnerDate = Shifts[Date]
        RETURN
            POWER (
                0.5,
                CALCULATE (
                    COUNTROWS ( Shifts ),
                    ALLEXCEPT ( Shifts, Shifts[Emp_ID] ),
                    Shifts[Date] <= OuterDate,
                    Shifts[Date] >= InnerDate,
                    NOT ( Shifts[Workday] )
                )
            )
                * Shifts[Score]
    )

Fuera de interés, una versión que funciona en «DAX antiguo» sin variables es:

Cumulative Total (old DAX) = 
SUMX (
    CALCULATETABLE (
        Shifts,
        ALLEXCEPT ( Shifts, Shifts[Emp_ID] ),
        Shifts[Date] <= EARLIER ( Shifts[Date] )
    ),
    POWER (
        0.5,
        CALCULATE (
            COUNTROWS ( Shifts ),
            ALLEXCEPT ( Shifts, Shifts[Emp_ID] ),
            Shifts[Date] <= EARLIER ( Shifts[Date], 2 ),
            Shifts[Date] >= EARLIER ( Shifts[Date] ),
            NOT ( Shifts[Workday] )
        )
    )
        * Shifts[Score]
)

La forma en que he replicado el cálculo recursivo es

  1. Tome las filas de turnos del empleado actual hasta la fecha actual.
  2. Para cada una de esas filas, cuente el número de días no laborables desde la fecha de esa fila hasta la fecha actual.
  3. Calcule 0.5 ^ (recuento de filas del paso 2) y multiplique por la puntuación de cada fila, luego sume.

La razón por la que esto funciona es que si imagina calcular el total acumulado para cada fila por turno, cada vez que llega a otro día no laborable, cada fila anterior se multiplica por otro factor de 0,5.

Salud,

Owen

Jeffreyweir

En respuesta a OwenAuger

¡Lejos! Cuando le expliqué a medias este asunto a Phil el otro día y él no dijo con dulzura «sí, DAX puede hacer eso», bueno … para citar a The Castle, pensé que estaba «soñando».

Esta solución va «directamente a la sala de billar».

Ahora, para recorrerlo. Soy un tipo de Excel Formulas y VBA, por lo que DAX todavía es bastante nuevo para mí. Esto ha duplicado mi interés en DAX. Muchas muchas gracias.

Sean

En respuesta a OwenAuger

@OwenAuger ¡Gran trabajo de nuevo! Smiley feliz

@Phil_Seamark No te estaba respondiendo porque estaba investigando esto …

HALF RT en Non Workdays.png

Ahora tenemos que procesar la solución de Owen Smiley feliz

EDITAR: @MarcelBeug ??? todos estamos esperando la solución M Smiley feliz

En respuesta a Sean

Eché un vistazo a un enfoque M anteriormente usando List.Accumulate pero no proporcionó suficiente granularidad sobre las iteraciones para cambiar la operación. No busqué la creación de una función. Definitivamente creo que @MarcelBeug podrá encontrar algo muy bueno.

MarcelBeug

En respuesta a Phil_Seamark

@Sean, @Phil_Seamark

¿Esta solución M basada en List.Generate sería lo suficientemente genial?

let
    Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
    Typed1 = Table.TransformColumnTypes(Source,{{"Emp_ID", Int64.Type}, {"Date", type date}, {"Cumulative Days", Int64.Type}, {"Workday", type logical}, {"Score", Int64.Type}, {"Cumulative Total", type number}}),
    Removed = Table.RemoveColumns(Typed1,{"Cumulative Total"}),
    Cumulation = List.Generate(() => [Index = 1,
                                     Employee = Removed[Emp_ID]{0},
                                     Total = if Removed[Workday]{0} then Removed[Score]{0} else 0],

                            each [Index] <= Table.RowCount(Removed),

                            each [Index = [Index] + 1,
                                  Employee = Removed[Emp_ID]{[Index]},
                                  Total = if Employee <> [Employee]
                                          then if Removed[Workday]{[Index]} 
                                               then Removed[Score]{[Index]}
                                               else 0
                                          else if Removed[Workday]{[Index]}
                                               then [Total] + Removed[Score]{[Index]}
                                               else [Total] * 0.5],

                            each [Total]),

    Records = Table.ToRecords(Removed),
    CombinedTable = Table.FromColumns({Records,Cumulation},{"Records","Cumulative Total"}),
    ExpandedRecords = Table.ExpandRecordColumn(CombinedTable, "Records", {"Emp_ID", "Date", "Cumulative Days", "Workday", "Score"}, {"Emp_ID", "Date", "Cumulative Days", "Workday", "Score"}),
    Typed2 = Table.TransformColumnTypes(ExpandedRecords,{{"Cumulative Total", type number}})
in
    Typed2

En respuesta a MarcelBeug

Definitivamente @MarcelBeug. Aprenderé mucho de ese 🙂

MarcelBeug

En respuesta a Phil_Seamark

@Phil_Seamark, @jeffreyweir, @Sean, @OwenAuger

Chicos (y estoy bastante seguro de que @ImkeF también estará interesado),

Para ser honesto, no me sentía muy cómodo con la eficiencia de la solución M que proporcioné anteriormente, ya que incluía referencias a valores individuales, por ejemplo, eliminada.[Emp_ID]{[Index]}, que suele ser un asesino del rendimiento.

Así que seguí pensando en una solución mejor y encontré una.

Tomó de 3 a 4 segundos completar una prueba con 5000 registros de entrada, mientras que mi solución anterior funcionó durante unos 10 minutos.

Los trucos con esta nueva solución son:

  1. Una fórmula se crea primero en texto y luego se evalúa con Expression.
    Transformar Ejemplo screenshot.png
  2. Usando group by Emp_Id, por lo que no es necesario fusionar la tabla consigo misma para obtener el Emp_Id con el Emp_Id anterior en la misma fila.

Otra característica interesante es que utiliza una estructura de consulta, similar a la creada con la funcionalidad «Combinar binarios» desde la actualización de noviembre de 2016.

Entonces tengo un consulta principal:

let
    Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
    #"Changed Type" = Table.TransformColumnTypes(Source,{{"Emp_ID", Int64.Type}, {"Date", type datetime}, {"Cumulative Days", Int64.Type}, {"Workday", type logical}, {"Score", Int64.Type}, {"Cumulative Total", type number}}),
    #"Removed Columns" = Table.RemoveColumns(#"Changed Type",{"Cumulative Total"}),
    #"Added Index2" = Table.AddIndexColumn(#"Removed Columns", "Original Sort", 1, 1),
    #"Sorted Rows3" = Table.Sort(#"Added Index2",{{"Emp_ID", Order.Ascending}, {"Date", Order.Ascending}}),
    #"Added Custom" = Table.AddColumn(#"Sorted Rows3", "FormulaTextPart", each if [Workday] then "+" & Text.From([Score]) &")" else "*.5)"),
    #"Grouped Rows" = Table.Buffer(Table.Group(#"Added Custom", {"Emp_ID"}, {{"AllData", each _, type table}})),
    #"Invoked Custom Function" = Table.AddColumn(#"Grouped Rows", "Cumulate", each Cumulate([AllData])),
    #"Removed Columns1" = Table.RemoveColumns(#"Invoked Custom Function",{"AllData"}),
    #"Expanded Cumulate" = Table.ExpandTableColumn(#"Removed Columns1", "Cumulate", {"Date", "Cumulative Days", "Workday", "Score", "Original Sort", "Total Cumulative"}, {"Date", "Cumulative Days", "Workday", "Score", "Original Sort", "Total Cumulative"}),
    #"Sorted Rows" = Table.Sort(#"Expanded Cumulate",{{"Original Sort", Order.Ascending}}),
    #"Removed Columns2" = Table.RemoveColumns(#"Sorted Rows",{"Original Sort"}),
    #"Changed Type1" = Table.TransformColumnTypes(#"Removed Columns2",{{"Date", type date}, {"Cumulative Days", Int64.Type}, {"Workday", type logical}, {"Score", Int64.Type}, {"Total Cumulative", type number}})
in
    #"Changed Type1"

Un Ejemplo consulta:

let
    Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
    #"Changed Type" = Table.TransformColumnTypes(Source,{{"Emp_ID", Int64.Type}, {"Date", type datetime}, {"Cumulative Days", Int64.Type}, {"Workday", type logical}, {"Score", Int64.Type}, {"Cumulative Total", type number}}),
    #"Removed Columns" = Table.RemoveColumns(#"Changed Type",{"Cumulative Total"}),
    #"Added Index2" = Table.AddIndexColumn(#"Removed Columns", "Original Sort", 1, 1),
    #"Sorted Rows3" = Table.Sort(#"Added Index2",{{"Emp_ID", Order.Ascending}, {"Date", Order.Ascending}}),
    #"Added Custom" = Table.AddColumn(#"Sorted Rows3", "FormulaTextPart", each if [Workday] then "+" & Text.From([Score]) &")" else "*.5)"),
    #"Grouped Rows" = Table.Group(#"Added Custom", {"Emp_ID"}, {{"AllData", each _, type table}}),
    GetFirstTable = Table.Buffer(#"Grouped Rows"{0}[AllData])
in
    GetFirstTable

A Transformar ejemplo consulta:

let
    Source = #"Example",
    #"Added Index" = Table.AddIndexColumn(Source, "Index", 1, 1),
    #"Added Custom1" = Table.AddColumn(#"Added Index", "Formula", each Text.Repeat("(",[Index])&"0"&Text.Combine(List.Range(#"Added Index"[FormulaTextPart],0,[Index]))),
    #"Added Custom2" = Table.AddColumn(#"Added Custom1", "Total Cumulative", each Expression.Evaluate([Formula])),
    #"Removed Other Columns" = Table.Buffer(Table.SelectColumns(#"Added Custom2",{"Date", "Cumulative Days", "Workday", "Score", "Original Sort", "Total Cumulative"}))
in
    #"Removed Other Columns"

A partir de la cual creé la función Recogido en montón:

let
    Source = (#"Example") => let
    Source = #"Example",
    #"Added Index" = Table.AddIndexColumn(Source, "Index", 1, 1),
    #"Added Custom1" = Table.AddColumn(#"Added Index", "Formula", each Text.Repeat("(",[Index])&"0"&Text.Combine(List.Range(#"Added Index"[FormulaTextPart],0,[Index]))),
    #"Added Custom2" = Table.AddColumn(#"Added Custom1", "Total Cumulative", each Expression.Evaluate([Formula])),
    #"Removed Other Columns" = Table.SelectColumns(#"Added Custom2",{"Date", "Cumulative Days", "Workday", "Score", "Original Sort", "Total Cumulative"})
in
    #"Removed Other Columns"
in
    Source

Observación: una pequeña diferencia con la estructura de código creada a partir de la funcionalidad «Combinar binarios»: uso # «Ejemplo» como parámetro, aunque no es un parámetro real sino una consulta regular. Como resultado, no es posible que la función se actualice automáticamente con cambios en el Transformar ejemplo consulta.

La razón de esta diferencia es que no es posible definir un parámetro como tipo tabla, mientras que puede definir un parámetro como tipo binario,

ImkeF

En respuesta a MarcelBeug

Hola Marcel,

esta parece una versión muy buena, pero como no me gusta la experiencia de combinar binarios, personalmente prefiero la Lista. 😉

Me interesaría su rendimiento si ajustas estos 2 aspectos:

1) No lo aplique en toda la tabla, pero «particione» la tabla antes agrupando en EmpID (puede omitir este comando entonces: Empleado = Eliminado[Emp_ID]{0},)

2) Nunca jamás alimentar una tabla o una lista a la lista Generar sin almacenar en búfer primero

Saludos, Imke 🙂

MarcelBeug

En respuesta a ImkeF

Lo siento @ImkeF, nunca lo volveré a hacer. Smiley avergonzado Smiley muy feliz

Tiene razón sobre el rendimiento: ajusté la solución List.Generate y ahora tiene un rendimiento comparable con mi segunda solución:

Ambos me había ejecutado al mismo tiempo en 25,000 filas de entrada y ambos terminaron después de aproximadamente 15 segundos (nivel científico de la prueba cercano a 0, pero lo suficientemente justo para mí).

Para que verifiques si entendí correctamente: ajusté la consulta y la dividí en una consulta y una función de la siguiente manera:

let
    Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
    Typed1 = Table.TransformColumnTypes(Source,{{"Emp_ID", Int64.Type}, {"Date", type date}, {"Cumulative Days", Int64.Type}, {"Workday", type logical}, {"Score", Int64.Type}, {"Cumulative Total", type number}}),
    Removed = Table.Buffer(Table.RemoveColumns(Typed1,{"Cumulative Total"})),
    #"Grouped Rows" = Table.Group(Removed, {"Emp_ID"}, {{"AllData", each _, type table}}),
    #"Invoked Custom Function" = Table.AddColumn(#"Grouped Rows", "Cumulated", each Generate([AllData])),
    #"Removed Columns" = Table.RemoveColumns(#"Invoked Custom Function",{"AllData"}),
    #"Expanded Cumulated" = Table.ExpandTableColumn(#"Removed Columns", "Cumulated", {"Date", "Cumulative Days", "Workday", "Score", "Cumulative Total"}, {"Date", "Cumulative Days", "Workday", "Score", "Cumulative Total"}),
    #"Changed Type" = Table.TransformColumnTypes(#"Expanded Cumulated",{{"Date", type date}, {"Cumulative Days", Int64.Type}, {"Workday", type logical}, {"Score", Int64.Type}, {"Cumulative Total", type number}})
in
    #"Changed Type"

Función Generar:

let
    Source = (TableSoFar as table) =>
let
    Removed = Table.Buffer(TableSoFar),
    Cumulation = List.Generate(() => [Index = 1,
                                     Total = if Removed[Workday]{0} then Removed[Score]{0} else 0],

                            each [Index] <= Table.RowCount(Removed),

                            each [Index = [Index] + 1,
                                  Total = if Removed[Workday]{[Index]}
                                          then [Total] + Removed[Score]{[Index]}
                                          else [Total] * 0.5],

                            each [Total]),

    Records = Table.ToRecords(Removed),
    CombinedTable = Table.FromColumns({Records,Cumulation},{"Records","Cumulative Total"}),
    ExpandedRecords = Table.ExpandRecordColumn(CombinedTable, "Records", {"Emp_ID", "Date", "Cumulative Days", "Workday", "Score"}, {"Emp_ID", "Date", "Cumulative Days", "Workday", "Score"})
in
    ExpandedRecords
in
    Source

ExcelApostle

En respuesta a MarcelBeug

Hola a todos,

He estado usando bastante los totales acumulados en mi carrera de powerquery. Hasta ahora, estaba usando el método List.Sum publicado aquí: https://www.excelguru.ca/blog/2015/03/31/create-running-totals-in-power-query/#comment-264936

Tengo entendido que List.Sum tendrá que volver a leer continuamente todas las filas para hacer la suma, mientras que la solución publicada aquí por @MarcelBeug y @ImkeF solo necesita leer cada campo una vez y debería ser considerablemente más rápido, ¿correcto?

Por lo tanto, estoy tratando de crear una función total en ejecución genérica que también funcione con ‘grupos’. Consulte el código modificado a continuación. Dos preguntas:

  1. ¿Cómo podemos implementar fácilmente con nombres de columnas de valores y grupos dinámicos? He intentado pasar el nombre como un argumento, pero ¿cómo podemos hacer esto sin hacernos intensivos en computación?
  2. ¿Cómo podemos implementar la expansión con nombres de columna dinámicos?

let
    Source = (TableSoFar as table) =>
let
    Removed = Table.Buffer(TableSoFar),
    Cumulation = List.Generate(
                            
                            //initial
                            () => [
                                    Index = 1,
                                    Total = Removed[Value]{0},
                                    Group = Removed[Group]{0}
                                  ],

                            //condition
                            each [Index] <= Table.RowCount(Removed),
                           
                            //next
                            each [ 
                                    Index = [Index] + 1,
                                    Total = if [Group] = Removed[Group]{[Index]} then [Total] + Removed[Value]{[Index]} else Removed[Value]{[Index]},
                                    Group = Removed[Group]{[Index]}
                                 ],

                            //selector
                            each [Total]),

    Records = Table.ToRecords(Removed),
    CombinedTable = Table.FromColumns({Records,Cumulation},{"Records","Cumulative Total"})
in
    CombinedTable
in
    Source

let
    Source = Table.FromRecords({[CustomerID = 1, Name = "Bob", Value= "1"], [CustomerID = 2, Name = "Bob", Value= "3"], [CustomerID = 3, Name = "Paul", Value= "5"], [CustomerID = 3, Name = "Paul", Value= "5"], [CustomerID = 3, Name = "Paul", Value= "5"], [CustomerID = 3, Name = "Jeff", Value= "5"], [CustomerID = 3, Name = "Jeff", Value= "2"], [CustomerID = 3, Name = "Jeff", Value= "3"]}),
    #"Changed Type" = Table.TransformColumnTypes(Source,{{"Value", Int64.Type}}),
    #"Renamed Columns" = Table.RenameColumns(#"Changed Type",{{"Name", "Group"}}),
    Cumulative = Generate(#"Renamed Columns"),
    #"Expanded Records" = Table.ExpandRecordColumn(Cumulative, "Records", {"CustomerID", "Group", "Value"}, {"CustomerID", "Group", "Value"})
in
    #"Expanded Records"

ImkeF

En respuesta a ExcelApostle

Hola,

No tengo mucho tiempo actualmente, así que solo publicaré el código aquí para que lo sigas (debería escribir una publicación de blog al respecto, ya que el código es realmente rápido):

let
func = (Table as table, AmountColumn as text, optional SortColumn as text, optional GroupColumns) =>
let

/* Debug parameters
Table = #"Query1", 
SortColumn = null, 
AmountColumn = "Value",
GroupColumns = {"Name"},
*/
    GroupedTable = Table.Group(Table, GroupColumns, {{"Partition", each _, type table}}),
    fnRunningTotal = (Table as table, AmountColumn as text, optional SortColumn as text, optional GroupColumns) =>

    let
    Source = if SortColumn = null then Table else Table.Buffer(Table.Sort(Table,{{SortColumn, Order.Ascending}})),
// Sort table and buffer it
    Buffered = Table.Buffer(Table.AddIndexColumn(Source, "Index",1,1)),
// Select the Columns
    SelectColumns = Table.SelectColumns(Buffered, if SortColumn = null then {AmountColumn, "Index"} else {SortColumn, AmountColumn, "Index"}),
// Extract Amount column and buffer it
    ExtractAmountColumn = List.Buffer(Table.Column(SelectColumns, AmountColumn)),
// Calculate a list with all running Totals
    RunningTotal = List.Skip(List.Generate( ()=> [ListItem=0, Counter=0]
                                            ,each [Counter] <= List.Count(ExtractAmountColumn)
                                            ,each [ ListItem = ExtractAmountColumn{[Counter]}+[ListItem],
                                                    Counter = [Counter]+1
                                                   ]
                                            ),1),
    ConvertedTable = Table.FromList(RunningTotal, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
    ExpandedColumn = Table.ExpandRecordColumn( ConvertedTable, "Column1", {"ListItem", "Counter"}, {"ListItem", "Counter"}),
    MergedQueries = Table.NestedJoin(Buffered,{"Index"}, ExpandedColumn,{"Counter"},"Expanded Column1",JoinKind.LeftOuter),
    Expand = Table.ExpandTableColumn( MergedQueries, "Expanded Column1", {"ListItem"}, {"RunningTotal"}),
    #"Removed Columns" = Table.RemoveColumns(Expand,{"Index"}),
    #"Changed Type" = Table.TransformColumnTypes(#"Removed Columns",{{"RunningTotal", type number}})
in
    #"Changed Type",

    AddRTToGroup = Table.AddColumn(GroupedTable, "Custom", each fnRunningTotal([Partition], AmountColumn)),
    #"Removed Other Columns" = Table.SelectColumns(AddRTToGroup,{"Custom"}),
    ExpandedGroup = Table.ExpandTableColumn(#"Removed Other Columns", "Custom", Table.ColumnNames(Table) & {"RunningTotal"}),
    WithoutGroup = fnRunningTotal(Table, "Value"),
    Result = if GroupColumns = null then WithoutGroup else ExpandedGroup 
in
    Result
//*
, 
    documentation = [ Documentation.Name =  " Table.ColumnRunningTotal"
        ,Documentation.Description = " Fast way to add running total to a table"
        ,Documentation.LongDescription = " Fast way to add running total to a table"
        ,Documentation.Category = " Table"
        ,Documentation.Source = " local"
        ,Documentation.Author = " Imke Feldmann: www.TheBIccountant.com"
        ,Documentation.Examples = {[Description =  " ", Code = " ", Result = " "]}] 
 in 
  Value.ReplaceType(func, Value.ReplaceMetadata(Value.Type(func), documentation))

Para su tabla, lo llamaría así:

MyFunction(YourTable, "Value", null, {"Name"})

ExcelApostle

En respuesta a MarcelBeug

Hola a todos,

He estado usando bastante los totales acumulados en mi carrera de powerquery. Hasta ahora, estaba usando el método List.Sum publicado aquí: https://www.excelguru.ca/blog/2015/03/31/create-running-totals-in-power-query/#comment-264936

Tengo entendido que List.Sum tendrá que volver a leer continuamente todas las filas para hacer la suma, mientras que la solución publicada aquí por @MarcelBeug y @ImkeF solo necesita leer cada campo una vez y debería ser considerablemente más rápido, ¿correcto?

Por lo tanto, estoy tratando de crear una función total en ejecución genérica que también funcione con ‘grupos’. Consulte el código modificado a continuación. Dos preguntas:

  1. ¿Cómo podemos implementar fácilmente con nombres de columnas de valores y grupos dinámicos? He intentado pasar el nombre como un argumento, pero ¿cómo podemos hacer esto sin hacernos intensivos en computación?
  2. ¿Cómo podemos implementar la expansión con nombres de columna dinámicos?

let
    Source = (TableSoFar as table) =>
let
    Removed = Table.Buffer(TableSoFar),
    Cumulation = List.Generate(
                            
                            //initial
                            () => [
                                    Index = 1,
                                    Total = Removed[Value]{0},
                                    Group = Removed[Group]{0}
                                  ],

                            //condition
                            each [Index] <= Table.RowCount(Removed),
                           
                            //next
                            each [ 
                                    Index = [Index] + 1,
                                    Total = if [Group] = Removed[Group]{[Index]} then [Total] + Removed[Value]{[Index]} else Removed[Value]{[Index]},
                                    Group = Removed[Group]{[Index]}
                                 ],

                            //selector
                            each [Total]),

    Records = Table.ToRecords(Removed),
    CombinedTable = Table.FromColumns({Records,Cumulation},{"Records","Cumulative Total"})
in
    CombinedTable
in
    Source

let
Source = Table.FromRecords({[CustomerID = 1, Name = "Bob", Value= "1"], [CustomerID = 2, Name = "Bob", Value= "3"], [CustomerID = 3, Name = "Paul", Value= "5"], [CustomerID = 3, Name = "Paul", Value= "5"], [CustomerID = 3, Name = "Paul", Value= "5"], [CustomerID = 3, Name = "Jeff", Value= "5"], [CustomerID = 3, Name = "Jeff", Value= "2"], [CustomerID = 3, Name = "Jeff", Value= "3"]}),
#"Changed Type" = Table.TransformColumnTypes(Source,{{"Value", Int64.Type}}),
#"Renamed Columns" = Table.RenameColumns(#"Changed Type",{{"Name", "Group"}}),
#"Invoked Custom Function" = Generate(#"Renamed Columns"),
#"Expanded Records" = Table.ExpandRecordColumn(#"Invoked Custom Function", "Records", {"CustomerID", "Group", "Value"}, {"CustomerID", "Group", "Value"})
in
#"Expanded Records"

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *