Dynamic ABC analysis in DAX-variables

Dynamic ABC analysis in DAX-variables

ABC analysis is one of the most commonly used categorization techniques. This technique divides subjects of analysis (Products, Customers, Inventory, etc.) into different categories based on subject importance.

The analysis is based on the Pareto principle which states that the most economic productivity comes from only a small part of the economy. It is mainly used with large datasets with lots of different attributes. The goal is to break large datasets into three strategic segments based on their importance. This way it is easier to focus on the vital parts of your company business.

This article will demonstrate few use cases with ABC using DAX in PowerPivot.

The idea behind Dynamic ABC formula

The focus will be on explaining the dynamic pattern with the use of variables, while the static pattern is well covered in the following article by DAX maestros Marco Russo and Alberto Ferrari.

For demonstration purposes, we will use the Sales and Marketing sample database from Microsoft. If you wish to follow along, you can download the .PBIX file here.

The idea of the article is to show you how to implement different types of ABC analysis, but alongside that also to teach you parts of intermediate DAX based on the provided examples.

To be able to do a dynamic ABC analysis we need to create a table of cumulative% of the products on the fly. In static ABC analysis we could store cumulative% in a calculated column, but since we want it dynamic, we need to create a virtual table inside the DAX formula, which will be evaluated at query time (meaning each time we filter dimensions, the table will be recalculated).

The model

The data model is a simple one, consisting of 3 dimension tables, fact table, and one parameter table.

The formula

First, let’s observe the code we will use to show TotalRevenue split by the ABC clusters. It is basically a variation of the dynamic pattern by Gerhard Brueckl, just with the use of variables for better code readability. We will also use the base of this code to create [ABC NumOfProducts] measure.

ABC Granular =
CALCULATE (
    [TotalRevenue],
    VALUES ( Dim_product[Product] ),
    FILTER (
        CALCULATETABLE (
            VAR BasicTable =
                ADDCOLUMNS ( VALUES ( Dim_product[Product] ), "ProductRevenue", [TotalRevenue] )
            RETURN
                ADDCOLUMNS (
                    BasicTable,
                    "Cumulative%",
                        DIVIDE (
                            SUMX (
                                VAR CurrentProductRevenue = [ProductRevenue]
                                RETURN
                                    FILTER ( BasicTable, [ProductRevenue] >= CurrentProductRevenue ),
                                [ProductRevenue]
                            ),
                            CALCULATE ( [TotalRevenue], VALUES ( Dim_product[Product] ) )
                        )
                ),
            ALL ( Dim_product[Product] )
        ),
        [Cumulative%] > [MinBoundary]
            && [Cumulative%] <= [MaxBoundary]
    )
)

In the code we also use 3 simple measures which are:

TotalRevenue =SUM ( Fact_sales[Revenue] )

MinBoundary =MIN ( SegmentationTable[Min Value] )

MaxBoundary =MAX ( SegmentationTable[Max Value] )

When we put [ABC Granular] and [ABC NumOfProducts] measures on a table, we get a result as shown below.

Before we start explaining the formula remember that to really understand how DAX computes its values, you have to start thinking in DAX terms. You know that the formula is the same, yet its producing different results for each field in the table. That is because in each field there are different filters applied to the data model prior the evaluation of the DAX code. So when you get a wrong result in a visual, don’t try to understand relations between figures on visuals, just focus on one single value and try to determine how it is computed in the background.

Before DAX even starts to evaluate the expression, PowerPivot model first filters the tables (and through relationship propagation the entire data model). E.q for France sales in A segment, there are two direct filters applied before formula evaluation. there is a filter coming from column header, which is France, and Youth category coming from Slicer. As we recall, segments are coming from parameter table, meaning that it cannot filter data model through relationships, but it can be used as a FILTER function argument to further restrict the data model.

From the paragraph above remember this sentence: Only after all direct filters apply the original filter context, does the PowerPivot start to Evaluate the DAX formula!

Formula evaluation

Each formula has its own evaluation order mostly impacted by the use of CALCULATE/CALCULATETABLE. The important thing to remember is that these functions have different evaluation order compared to other DAX functions. They evaluate first argument (measure or a table) only after all filter arguments are applied! you can find more about the CALCULATE evaluation order here.

With evaluation order in mind, lets split the formula into steps and explain each of them.

We will start from the outermost CALCULATE expression.

Applied steps:

  1. Original filter context coming from visuals

  2. CALCULATE function needs to evaluate [TotalRevenue] measure, but before doing so, it needs to accept two filters which will be joined by AND logic.

The first filter argument is VALUES( Dim_product[Product] ), which gives a list of products as visible in the current filter context (which is the original one). We need this argument to restore original filter context on Dim_product[Product] column in case we wish to use it in a report. This will be explained in details later on. 2. The second filter argument is FILTER function 3. FILTER function receives a table expression as the first argument, so before evaluating below conditions based on parameter table

[Cummulative%] > [MinBoundary] && [Cummulative%] <= [MaxBoundary]

,engine has to retrieve a table expression. 4. Table expression consists of CALCULATETABLE function wrapped around the inner part of formula. CALCULATETABLE first evaluates filter argument, which is ALL( Dim_product[Product] ), and then uses this argument to modify original Filter context for the inner table functions evaluation. This way table functions under step 4 and 5 will ignore original context filters coming from Dim_product[Product] column and will be able to compute correct cumulative totals in case we plot Dim_product[Product] column on a visual.

Filter argument of a CALCULATETABLE function is an important part of the formula because by changing it you can specify under which conditions will the cumulative% be calculated. If you change the argument ALL( Dim_product[Product] ) to ALL( Dim_product ) then cumulative% will ignore any filter coming from Dim_product table (e.q Category filter coming from slicer, like in a picture below). 5. After the filter contexts is modified by the CALCULATETABLE, formula starts to build virtual table with Cumulative%. First, we declare a table variable BasicTable, which consist of two columns:

Column containing a list of products - VALUES ( Dim_product[Product] ) 6. Column with total revenue of each product (created by ADDCOLUMNS function) 7. Previously created BasicTable variable is used in step 5 to create cumulative% inside a virtual table. To create cumulative% we need to divide running products total with the total revenue of all the products. To accomplish that we need to do the following.

On the table variable we add a virtual column called Cumulative% (using ADDCOLUMNS function) 8. For each row in virtual Cumulative% column we evaluate formula inside DIVIDE() function.

Numerator: to get the running total as numerator, we need to iterate through all the products and sum only the ones that have >= revenue compared to the current product. In DAX terms this is done as follows:

Before we enter nested row context by invoking FILTER function, we store the total revenue of the currently iterated row (the one of the cumulative% calculated column) in a variable CurrentProductRevenue. 9. FILTER function creates nested row context over the BasicTable, then iterates through it and checks which products in nested table have >= ProductRevenue compared to CurrentProductRevenue (the revenue stored in a variable of the outer row context). When FILTER finishes, SUMX function sums the ProductRevenue of the rows remained after filtering. Since there is a nested row context you can also use EARLIER function to achive the same result. 10. Denominator

We used CALCULATE with VALUES filter argument to return total revenue of all the products in the selection. 11. After table functions evaluate their expressions we receive a virtual table like the one below.

If you need to create a virtual table in your DAX code, but are unsure how the table looks like, you can install DAX studio to help you write a query against your data model (you can’t do this directly in PowerBI or Excel). This way you can easily manipulate your code inside studio environment until its ready to be used as filter argument in DAX formula. For more information about DAX studio, please follow this link.

Filling the gaps in the formula

Now that we got to the innermost part of the formula and received our virtual table, we start returning to the original outermost CALCULATE to provide it with second filter argument. If we look at the step num 2, we can see that FILTER part still has to evaluate conditions over the table returned by the CALCULATETABLE function.

Final step: Rows in the virtual table that survive FILTER conditions will be used as second filter argument to the outermost CALCULATE function.

I believe 2 things in the final step need additional explanation

  1. Why do we need to use VALUES( Dim_product[Product] ) as the first argument to CALCULATE?
  • The second argument to CALCULATE is CALCULATETABLE which removed the filter from Dim_product[Product] column. By doing so it removed the original filter context in case Dim_product[Product] column is to be plotted on a visual. Without the VALUES( Dim_product[Product] ) as the first argument, when we plot Dim_product[Product] column on the visual, we would see all the products with the same value (the value of the TotalRevenue of all the products in observer segment). With the use of VALUES( Dim_product[Product] ), because of the AND logic of filter arguments, we are restoring the original filter context of the products column.
  1. Why is CALCULATETABLE table even a valid filter expression for the CALCULATE function?
  • As we know, second filter argument is a virtual table, so then why does it provide the right set of products to filter Fact_sales table? The short answer is that virtual tables, in most cases, retain a lineage (connection) to the tables they originate from. In this case, a virtual table retained its lineage to Dim_products table, and through propagation was able to filter Fact_sales table, like in the picture below.
  • Although virtual tables never truly materialize inside the data model, at the query time they act just like regular tables (in case their columns preserve lineage). Lineage topic is well explained here.

Dynamic ABC variations

Counting the number of Products in each category

If you wish to count how many products there are in A,B or C segment, you can do that with the following formula.

ABC NumOfProducts =
VAR OriginalProducts =
    FILTER ( VALUES ( Dim_product[Product] ), [TotalRevenue] > 0 )
RETURN
    COUNTROWS (
        FILTER (
            FILTER (
                CALCULATETABLE (
                    VAR BasicTable =
                        ADDCOLUMNS ( VALUES ( Dim_product[Product] ), "ProductRevenue", [TotalRevenue] )
                    RETURN
                        ADDCOLUMNS (
                            BasicTable,
                            "Cumulative%",
                                DIVIDE (
                                    SUMX (
                                        VAR CurrentProductRevenue = [ProductRevenue]
                                        RETURN
                                            FILTER ( BasicTable, [ProductRevenue] >= CurrentProductRevenue ),
                                        [ProductRevenue]
                                    ),
                                    CALCULATE ( [TotalRevenue], VALUES ( Dim_product[Product] ) )
                                )
                        ),
                    ALL ( Dim_product[Product] )
                ),
                [Cumulative%] > [MinBoundary]
                    && [Cumulative%] <= [MaxBoundary]
            ),
            Dim_product[Product] IN ( OriginalProducts )
        )
    )

This formula is the same as the original one, just wrapped inside COUNTROWS function instead of being used as a filter argument in CALCULATE.

Applied steps:

0 – Original filter context coming from visuals

  1. We first need to store a list of products with revenue >0 into a variable table called OriginalProducts. This way we are eliminating products that don’t have sales but would have been counted in C segment because their cumulative% is <=1. COUNTROWS function is waiting for the table argument.
  2. The FILTER function is receiving table from step 3 and filtering it so that only rows with ProductRevenue >0 survive. The IN syntax is used to create a list of values that are valid for the given column. In this case, the IN syntax provides a dynamic set of OR statements with the same column. You can find more info about the IN syntax here.

Final step: After the virtual table is filtered so that it contains only products with revenue >0, COUNTROWS function can calculate the correct number of products in A,B, and C segment.

Providing a letter for each product based on its ABC segment.

The following example is a bit trickier. Instead of counting or summing products, we want to provide, for each product, a letter associated with the segment it belongs to, like in the picture below.

This way we can easily spot product importance across different dimension like regions, periods etc. Formula is a rather complex one.

ABC Show Letter =
VAR OriginalProducts =
    FILTER ( VALUES ( Dim_product[Product] ), [TotalRevenue] > 0 )
RETURN
    MINX (
        FILTER (
            CALCULATETABLE (
                VAR BasicTable =
                    ADDCOLUMNS ( VALUES ( Dim_product[Product] ), "ProductRevenue", [TotalRevenue] )
                RETURN
                    ADDCOLUMNS (
                        ADDCOLUMNS (
                            BasicTable,
                            "Cumulative",
                                DIVIDE (
                                    SUMX (
                                        VAR CurrentProductRevenue = [ProductRevenue]
                                        RETURN
                                            FILTER ( BasicTable, [ProductRevenue] >= CurrentProductRevenue ),
                                        [ProductRevenue]
                                    ),
                                    SUM ( Fact_sales[Revenue] )
                                )
                        ),
                        "ABC",
                            CALCULATE (
                                VALUES ( SegmentationTable[ABC] ),
                                FILTER (
                                    SegmentationTable,
                                    [Cumulative%] > [MinBoundary]
                                        && [Cumulative%] <= [MaxBoundary]
                                )
                            )
                    ),
                ALL ( Dim_product[Product] )
            ),
            Dim_product[Product] IN ( OriginalProducts )
        ),
        [ABC]
    )

Explanation:

To accomplish the desired result, we need to add an additional column to the virtual table inside CALCULATETABLE funtion containing a letter for each product based on its Cumulative%. This time we will start explaining the formula from the last step.

Step 3

  1. We add an additional column to the virtual table using ADDCOLUMNS function
  2. For each row in the newly created column, we check the boundaries of cumulative%. Since the filter part of the CALCULATE function in step 3 is designed to return only rows with one distinct value for the SegmentationTable[ABC] column from the SegmentationTable, the main argument of CALCULATE - VALUES( SegmentationTable[ABC] ) will automatically transform that distinct value to a scalar ( A, B or C).

Step 2

  1. The FILTER function is receiving the virtual table and filtering it so that only rows with ProductRevenue >0 survive (Products stored in OriginalProducts variable).

Virtual table returned in step 2 looks like the one below.

Step 1

MINX function takes the virtual table as the first argument and, respecting the original filter context, returns the letter associated with the current product.

You would have thought that MINX can only return numbers, but in fact, since it always returns only one minimal value from the values supplied in the column, it automatically transforms that value into a scalar.

In case you use this formula at the granularity greater than product, it will return the smallest letter from the array of letters valid for the current context, like in the example below.

Changing the formula behavior

As I mentioned at the beginning, you can alter all these formulas to suit your specific needs. In case you wish to alter the contexts in which cumulative% is calculated, you can play with the filter argument of the CALCULATETABLE (dark colored line in the code below).

If you want to further split your ABC analysis (for example by 10%), you can create parameter table like the one in the example and use different columns to further divide segments. The segmentation table should look like this.

Now you can acquire results like in the picture below where you can see a further split of values and number of products accounted for each 10% of sales.

The variations of these formulas are numerous and can fit your specific needs with only a few adjustments (basically just one line of the code).

Hope you enjoyed reading this article. If you have any questions please comment below!

And if you liked the article, don’t forget to like/share!

27 comments

Leave a comment

Earlier comments

  • Fabricio de Almeida September 30, 2022

    Hi all, Is there a way to use and change this DAX to evaluate margins/profitability summarizing by product family? I was trying to change the code to evaluate my worst product family (Bleeders = A) and (Leakers = B+C), but as I have a mix of positive and negative values unfortunately I couldn't. Could someone please help me? Thank you

  • Victoria November 24, 2021

    HI All i have a problem with this ABC classification . If my first product represents more than 70% of all sales amount , all the products, included the first, will be classified in 'C' section . But i want that the first product , by importance, go always in 'A' section and the others, eventually, directly in 'C' section. How can i realize it ?

  • Fred Hainsworth December 21, 2020

    I hope you can help. I am using the formulas as follows: ABC Product Revenue = CALCULATE ( [Amount], VALUES ( Actual[Brand] ), FILTER ( CALCULATETABLE ( VAR BasicTable = ADDCOLUMNS ( VALUES ( Actual[Brand]), "ProductRevenue", [Amount] ) RETURN ADDCOLUMNS ( BasicTable, "Cumulative%", DIVIDE ( SUMX ( VAR CurrentProductRevenue = [ProductRevenue] RETURN FILTER ( BasicTable, [ProductRevenue] >= CurrentProductRevenue ), [ProductRevenue] ), CALCULATE ( [Amount], VALUES ( Actual[Brand] )) ) ), ALL ( Actual[Brand] ) ), [Cumulative%] > [MinBoundary] && [Cumulative%] <= [MaxBoundary] ) ) And ABC Product Count = VAR OriginalProducts = FILTER ( VALUES ( Actual[Brand] ), [Amount] <> 0 ) RETURN COUNTROWS ( FILTER ( FILTER ( CALCULATETABLE ( VAR BasicTable = ADDCOLUMNS ( VALUES ( Actual[Brand] ), "ProductRevenue", [Amount] ) RETURN ADDCOLUMNS ( BasicTable, "Cumulative%", DIVIDE ( SUMX ( VAR CurrentProductRevenue = [ProductRevenue] RETURN FILTER ( BasicTable, [ProductRevenue] >= CurrentProductRevenue ), [ProductRevenue] ), CALCULATE ( [Amount], VALUES ( Actual[Brand] ) ) ) ), ALL ( Actual[Brand] ) ), [Cumulative%] > [MinBoundary] && [Cumulative%] <= [MaxBoundary] ), Actual[Brand] IN ( OriginalProducts ) ) ) When i place on a grid it only returns "A" and the total revenue for all products/brands. Thanks

  • Krešimir Ledinski December 23, 2020

    Hi Fred, if you wish to show product ABC on a product level, then you need to use product granularity in a formula (not the brand one). Other than that, please check your filters on a visual, maybe you misselected one from the wrong table.

  • Pablo Alvarez Flores December 8, 2020

    Thank you. Very good and detailed documentation!!. I have a question: It's possible to code ABCD classification? Where "D" are items that doesn't have sales (or null). Thanks for your help

  • Krešimir Ledinski December 13, 2020

    Hi! for that requirement, you can try the following adjusted formula (same adjustments have to be made for other formulae).

    ABC NumOfProducts = 
            COUNTROWS (
                    FILTER (
                        CALCULATETABLE (
                            VAR BasicTable =
                                ADDCOLUMNS ( VALUES ( Dim_product[Product] ), "ProductRevenue", [TotalRevenue] )
                            RETURN
                                ADDCOLUMNS (
                                    BasicTable,
                                    "Cumulative%", DIVIDE (
                                        SUMX (var CurrentProductRevenue =[ProductRevenue] return
                                            FILTER ( BasicTable, [ProductRevenue] >= CurrentProductRevenue ),
                                            [ProductRevenue]
                                        ),
                                        CALCULATE ( [TotalRevenue], VALUES ( Dim_product[Product] ) )
                                    )
                                ),
                            ALL ( Dim_product[Product] )
                        ),
                        [Cumulative%] >= [MinBoundary]
                            && [Cumulative%] < [MaxBoundary]
                    ))

    Here we removed the condition of active products (the ones that have sales over 0). Also, we changed the condition in the last part of the formula from > and <= to >= and <. Lastly, you need to add D segmentation in your data model, so that it looks like in the picture below.

  • Pablo Alvarez Flores December 14, 2020

    Thank you. It works like a charm.

  • Krešimir Ledinski December 15, 2020

    Glad it helped! Cheers!

  • Francisco C April 16, 2020

    Does this model run with 10,000 products?

  • Krešimir Ledinski April 17, 2020

    Hi Francisco, in theory it does, but it will be a very slow execution (on shared capacity you could even time-out). this technique works best with less than 1000 products.

  • Julien April 2, 2019

    Impressive documentation ! One question : what about having a "2D" ABC analysis ? For example, the sales value (you segment by Product as in the example or by customer) define the ABC But you could also define XYZ for instance for potential sales (imagine you have access to other market data and are able to estimate potential sales by product or customers). How would it be possible to set this up ?

  • Krešimir Ledinski April 3, 2019

    Hi Julien, thank you! Not sure If I understood your question. Could you please give some example so I could wrap my head around it?

  • Julien April 3, 2019

    Hi, sorry it was not so clear :-) your method is perfect and I can qualify all my customers into a ABC classification. Then in your example, you combine this with other info (such as Country), that are Columns already present in the datamodel. What about having this other criteria as another XYZ classification, based on the same method ? then i would be able to sort my customers not in 3 boxes on axis X, but in 9 boxes in axes X and Y ?

  • Federico B June 7, 2020

    HI, HAve you been able to solve the ABC/XYZ analysis?

  • Krešimir Ledinski April 3, 2019

    Yes, this is possible. To accomplish it, you will need to modify the above formula to include additional XYZ segmentation. Several steps needed: 1. Create new XYZ table in the data model (with min, max and letter column) 2. add new measures minXYZ, maxXYZ (you will use them later in the filtering part of the formula) In the formula above: 3. Add virtual calculation column for XYZ classification in the basic table variable (EQ TotalUnitsSold). 4. Add additional Cummulative% column referencing previously added virtual column. Now you will have 2 Cumm% columns in the virtual table. 5. In the filtering part, add 2 additional conditions that use newly create XYZ table in the data model. Combine them all with &&.

  • Federico B June 7, 2020

    Hi, thanks for your explanation. I have the same question that Julian did one year ago, regarding aplying the method for 2 dimensions (ABC/XYZ Analysis). Multiple customers, each with a anual revenue and potencial, the segmentation is a combination between them showing 9 possibilities. Any help with that? Kind regards

  • Krešimir Ledinski June 8, 2020

    Hi Federico, Have you tried the approach I mentioned above? That is the pattern to accomplish ABC/XYZ Analysis.

  • Danilo April 1, 2019

    Hi, Really exceptional very good and comprehensive article! Just a question, I tried to implement it on a very large table (about 600k products) but unfortunately after many minutes both using SSAS or Power BI crashed reporting the following error "Visual has exceeded the available resources". Is there any workaround to optimize this analysis? Thanks for your help

  • Krešimir Ledinski April 1, 2019

    Hi Danilo, Since you are creating a virtual table each time you interact with a report, there is a high materialization pressure at a query time. Also, there is a nested iterator on products with ADDCOLUMNS - FILTER combo, meaning that the product iterations will grow exponentially. For 1000 products it's ok (1mil iterations) but for 600k products its 36*10^10 iterations which is too much even for the fastest servers. This calculation works fast for <1k products, slow for 1k to 10k products, but almost guaranteed it will fail for 100k+ products. Not sure if there is currently available a faster calculation (since you need to include nested iterator over the products), maybe you could try a static ABC pattern by Russo&Ferrari. Regards, Kresimir

  • Pravin May 30, 2021

    this is a very important analysis, and unfortunately this Analysis is for few unique items. ,The actual issue is with the addcolumns for cumulative% in virtual table with huge number of unique products/items. One thing can be done in this case is group the products and do the same. And when you find the Group, you again do the analysis for product inside that group seperately., I have even tried the static analysis with more than 100k items, that is also very slow or doesn't work properly

  • Martin March 1, 2019

    Hi! I´m playing with this measure and works like a charm. My only issue is that I have a project where the model is uploaded to AAS and I don´t have access to create a new table. Is it possible to code the ABC letters in the measure?

  • Martin March 1, 2019

    I´ve tried generation a VAR table with DATATABLE but can´t figure out how to apply it.

  • Krešimir Ledinski March 2, 2019

    Hi Martin, For all the examples except the last one (which shows the letters inside the measure), it is not possible to get ABC segmentation W/O having an actual ABC table inside your model. For the last example, you could simplify the ADDCOLUMNS(table, "ABC", calculation ). Instead of "ABC", CALCULATE ( VALUES ( SegmentationTable[ABC] ), FILTER ( SegmentationTable, [Cumulative%] > [MinBoundary] && [Cumulative%] <= [MaxBoundary] ) ) , you could write the following condition that should provide the same result: "ABC", IF([Cumulative%]<0.7,"A",IF([Cumulative%]<0.9,"B","C")))

  • hermes January 17, 2019

    Hi there, Thank you for sharing this post. I've looked for ABC pattern displaying letters in values field everywhere and finally found it here. :) I have problem thou. The pattern (PROVIDING A LETTER FOR EACH PRODUCT BASED ON ITS ABC SEGMENT) works just fine when used in Power BI Desktop, but when used in Excel Power Pivot it fails to show proper letters. Instead it shows C for each product that has value in current filter context. I honestly have no idea why that could be. Do you?

  • Krešimir Ledinski January 17, 2019

    Hi Hermes, For this calculation to produce the correct result, you need a proper star schema of the data model, especially the dimension used as a filter for the ABC analysis. In case you try to implement formula onto a denormalized fact table, it will produce a wrong output due to incorrect filter propagation. You would need to check two things: 1. Is your model a proper star schema? 2. Are you using columns from the dimension tables to filter the visual? This would be my wild guess about what could be causing the calculation to provide the wrong output. But without knowing more about your data model it’s hard to predict what is the real reason behind it.

  • hermes January 17, 2019

    Yes to both 1. and 2. I meant that your formula works fine with my model when I run it in Power BI Desktop, but it does not work in Power Pivot in Excel. The model is exactly the same in both cases (I import a .xlsx file into Power BI) and based on the same scheme as yours. One difference I see is that the operator "IN" in PP is underlined in red as if it was wrong (but the formula does not throw error, only the result in the form of C's only), but I have the latest version of Excel and according to the documentation it supports the "IN" operator. I checked in DAX Studio the table that returns CALCULATETABLE inside this function and here the results are correct (A, B and C's), but after putting it inside the function, the results are not correct. I assume that the problem lies somewhere in step 1 or an outer most filter of step 2, but I can not identify it (unless the problem is actually in the operator "IN").

  • Krešimir Ledinski January 17, 2019

    I did some checking of the formula in the Excel environment and you are right, it does produce the wrong result. The main issue was with the use of EARLIER inside the virtual table. While it worked as expected in PowerBI data model, in the Excel PowerPivot model this function was unable to access previous row context of the virtual table, resulting in true evaluation for every row, therefore returning the same cummulative % (1) for each product. With the use of Variables code now works as expected (tested with Excel 365). This is interesting discovery and will for sure investigate this behavior in more details. Thanks for letting me know about this issue! Please check the updated formula and let me know if it works as expected on your PowerPivot model.

EXCEED ACADEMY Want to master DAX on real examples, with a trainer? See the DAX course →