Forum Discussion

naelske_cronos's avatar
naelske_cronos
Icon for Advocate II rankAdvocate II
9 months ago
Solved

Handle AverageX on different granularities matrix drilldown

Hello,

 

I have a matrix in Power BI where you can drill down to different granularities, from region on top to customer level (3 levels).
The problem is that when drilling down, my averageX is not correct on total level as it takes the same values as the one on top. I have started a DAX query to make it dynamic:

 

This is the base measure to have the values at row/column level as I'm using th

_DivideAccuracy = 
VAR actuals = CALCULATE ( SUM ( 'FACT_ActualsBudget'[Actuals] ) )
VAR forecast = CALCULATE ( SUM ( FACT_Forecast[Forecast] ) )
VAR TempCheck =
    MAX ( actuals, forecast )
VAR TempAccuracy =
    DIVIDE ( ABS ( actuals - forecast ), TempCheck, 0 )
VAR CheckIfNull =
    COALESCE ( actuals, -1 )
RETURN
    IF (
        CheckIfNull <> -1
            && AND ( actuals = 0, forecast = 0 ),
        1,
        IF ( CheckIfNull <> -1, 1 - TempAccuracy )
    )

 

This is the measure to do the averageX on total level. As you can see, the values_rows variable does now on CustomerRegion level but how do I do this for other granularities. When using ISINSCOPE or ISFILTERED is difficult because on total level on column level we don't know if it is on region or sales person level.

_DynamicMeasureAccuracy = 
VAR values_rows =
    VALUES ( DIM_Customer[CustomerRegion] )
VAR values_columns =
    VALUES ( DIM_CalendarDate[MonthName] )
VAR all_combinations =
    CROSSJOIN ( values_rows, values_columns )
RETURN
    AVERAGEX ( all_combinations, CALCULATE ( [_DivideAccuracy] ) )

 




It should be:


This is a simple example I want but maybe it is more difficult with the base measure I wrote:


Region Salesperson January
APR A 5
APR B 10
APR C 4
APR D 8
AverageX = 6.75

Region January
APR 27
AverageX = 27


Kind regards

Kevin

 

 

  • naelske_cronos's avatar
    naelske_cronos
    9 months ago

    Hello,

     

    The question has not been resolved but I have found a workaround using bookmarks and seperate measures for each level of hierarchy. I guess the problem lies in the hierarchy context for the totals for which a single measure is "impossible" to use.

     


    Kind regards

     

    Kevin

21 Replies

  • King7son1's avatar
    King7son1
    Frequent Visitor
    _DynamicMeasureAccuracy =
    VAR HasRegion = ISINSCOPE( 'Region'[Region] )
    VAR HasSalesPerson = ISINSCOPE( 'SalesPerson'[SalesPerson] )
     
    RETURN
    SWITCH( TRUE(),
     
        -- 1. DETAIL ROW (SalesPerson)
        -- If we are on a salesperson row, just run the standard accuracy math.
        HasSalesPerson,
        [_DivideAccuracy],
     
        -- 2. REGION ROW (The Fix)
         HasRegion,
        AVERAGEX(
            VALUES('SalesPerson'[SalesPerson]),
            [_DivideAccuracy]
        ),
     
        -- 3. GRAND TOTAL
        -- If we are at the total, Average the Regions (which are averages of salespeople).
        AVERAGEX(
            VALUES('Region'[Region]),
            CALCULATE(
                AVERAGEX(
                    VALUES('SalesPerson'[SalesPerson]),
                    [_DivideAccuracy]
                )
            )
        )
    )
     
     
     
  • King7son1's avatar
    King7son1
    Frequent Visitor
    #AverageAccuracyCorrectedV1 =
    -- Define the Virtual Table with hierachy and Accuracy
    VAR _VirtualTable =
    ADDCOLUMNS(
    ADDCOLUMNS(
    ADDCOLUMNS(
    ADDCOLUMNS(
        DISTINCT(
        UNION(
            SUMMARIZE(FACT_FORECAST,FACT_FORECAST[MonthKey],FACT_FORECAST[ProductGroupKey],FACT_FORECAST[SalesPersonKey]),
            SUMMARIZE(FACT_SALES,FACT_SALES[MonthKey],FACT_Sales[ProductGroupKey],FACT_SALES[SalesPersonKey]))
            ),
            "_Actual" , CALCULATE( SUM(FACT_SALES[Sales]), FACT_SALES[MonthKey] = EARLIER([MonthKey]) , FACT_SALES[ProductGroupKey] = EARLIER([ProductGroupKey]), FACT_SALES[SalesPersonKey] = EARLIER([SalesPersonKey] )) ,
            "_Forecast" , CALCULATE( SUM(FACT_FORECAST[Forecast]), FACT_FORECAST[MonthKey] = EARLIER([MonthKey]) , FACT_FORECAST[ProductGroupKey] = EARLIER([ProductGroupKey]), FACT_FORECAST[SalesPersonKey] = EARLIER([SalesPersonKey] ))),
                "_Diff" , ABS([_Actual] -[_Forecast]),
                "_Denom" , MAX([_Actual], [_Forecast])),
                "_Error" , DIVIDE ([_diff] , [_Denom],0)),
                "Accuracy", 1 - [_Error])

     
    RETURN
        SWITCH(
            TRUE(),
     
            -- [CASE 1] Lowest Level: Product Group
            -- If we are looking at a specific Product, calculate the simple math directly.
            ISINSCOPE(DIM_PRODUCTGROUP[ProductGroupKey]) || ISINSCOPE(DIM_SALESPERSON[SalesPersonKey]),
            [#DivideAccuracy],
     
            -- [CASE 2] Totals & Subtotals (Default)
            -- If we are on a SalesPerson Total, Region Total, or Grand Total,
            -- calculate the AVERAGE of the rows in the Virtual Table.
            AVERAGEX(_VirtualTable, [Accuracy])
        )
  • naelske_cronos , You can use isinscope and do different calculations =. This should work from leaf (Lowest) level to higher 

    Switch( True(), 
    isinscope(DIM_CalendarDate[MonthName] ), _DivideAccuracy, 
    isinscope(DIM_Customer[CustomerRegion]), AverageX(values(DIM_CalendarDate[MonthName] ),  _DivideAccuracy) 

    )

     

    and so on 
    IsInScope - Switch Rank at different levels: https://youtu.be/kh0gezKICEM
    How to Switch Subtotal and Grand Total in Power BI | Power BI Tutorials| isinscope: https://youtu.be/smhIPw3OkKA

    • naelske_cronos's avatar
      naelske_cronos
      Icon for Advocate II rankAdvocate II

      Hello amitchandak,

       

      I did like you told me too. This is my code:

      _DynamicMeasureAccuracy2 =
      VAR values_columns =
          VALUES ( DIM_CalendarDate[MonthName] )
      RETURN
          SWITCH (
              TRUE (),
              ISINSCOPE ( DIM_CalendarDate[MonthName] ), [_DivideAccuracy],
              ISINSCOPE ( DIM_Customer[CustomerRegion] ), AVERAGEX ( values_columns, [_DivideAccuracy] ),
              ISINSCOPE ( DIM_Customer[CustomerSalesPersonDescr] ),
                  AVERAGEX (
                      CROSSJOIN ( VALUES ( DIM_Customer[CustomerRegion] ), values_columns ),
                      [_DivideAccuracy]
                  ),
              AVERAGEX (
                  CROSSJOIN ( VALUES ( DIM_Customer[CustomerSalesPersonDescr] ), values_columns ),
                  [_DivideAccuracy]
              )
          )

       

      As you can see, the grand total for region is not correct because it is it 78.52% instead of 91.59%. That is because it checks the last condition of the switch for sales person:

      On sales person the grand total is correct but the subtotals are not correct as for january it should be 85.84%

       

      I guess it is a good base with the ISINSCOPE but how do my subtotals change based on the filter context if region or if salesperson => region is 1 row so for january 89.61% / 1  = 89.61% but when on sales person (74.02% + 96.23% + 75.06% + 100.00% + 78.94% + 96.65% + 80.00%) / 7 = 85.84%

       

      Thanks

      Kind regards

       

      Kevin

  • King7son1's avatar
    King7son1
    Frequent Visitor

    As Amit said you can use INSCOPE to perform different calculations

     

    DynamicMeasureAccuracy =

    VAR HasRegion = ISINSCOPE( 'Region'[Region] )

    VAR HasSalesPerson = ISINSCOPE( 'SalesPerson'[SalesPerson] )

     

    RETURN

    SWITCH( TRUE(),

     

        -- 1. DETAIL ROW (SalesPerson)

        -- If we are on a salesperson row, just run your base measure

        HasSalesPerson,

        [_DivideAccuracy],

     

        -- 2. REGION ROW (The Fix)

        -- If we are on a Region row, we can’t just use base measure

        -- Instead, iterate through the Salespeople and average their individual scores

        HasRegion,

        AVERAGEX(

            VALUES('SalesPerson'[SalesPerson]),

            [_DivideAccuracy]

        ),

     

        -- 3. GRAND TOTAL

        -- If we are at the total, Average the Regions (which are averages of salespeople).

        -- Or change this if you want average across every salesperson ignoring region ( AVERAGEX(VALUES(‘SalesPerson’[SalesPerson]), [_DivideAccuracy])     ….)

               

        AVERAGEX(

            VALUES('Region'[Region]),

            CALCULATE(

                AVERAGEX(

                    VALUES('SalesPerson'[SalesPerson]),

                    [_DivideAccuracy]

                )

            )

        )

    )

     

    • naelske_cronos's avatar
      naelske_cronos
      Icon for Advocate II rankAdvocate II

      Hello,

       

      Well I tried this DAX code on my example that I showed instead of the complex one:

      ExcelStyle = 
      VAR RowIsDetail   = ISINSCOPE(SalesPerson[Key])
      VAR region   = ISINSCOPE(Region[Region])
      VAR ColIsDetail   = ISINSCOPE('Date'[Month])
      RETURN
      
      SWITCH(
          TRUE(),
      
          -- DETAIL CELL: salesperson + month → show sum
          ColIsDetail && (RowIsDetail || region),
              [SumValue],
      
          -- ROW SUBTOTAL (region) → show average
          NOT RowIsDetail && ColIsDetail,
              average('FACT'[Value]),
      
          -- COLUMN TOTAL (month total row) → show average
          RowIsDetail && NOT ColIsDetail,
               average('FACT'[Value]),
      
          -- GRAND TOTAL (neither in scope) → show average
           average('FACT'[Value])
      )

      but the problem is that the total does not know when to use regional level or sales person level because there is no filter context on total row, so still no luck...

       

       

      Kind regards

       

      Kevin

    • King7son1's avatar
      King7son1
      Frequent Visitor

      Please let us know if this solution works.  Also, for a more performant solution try the following.  I was not able to save the file in GIT as I did not have permissions 

      #AverageAccuracyCorrected =
      --[1]Instead of DISTINCT(UNION pattern create gain by grouping dimension columns.  This has a significant performance boost vs scanning the fact tables
      -- Because we are using Dimension columns this virtual table should maintain lineage to the Dimensions.
      VAR _Grain =
          DISTINCT(
              UNION(
                  -- 1. Get keys from Forecast,
                  SUMMARIZE(
                      FACT_FORECAST,
                      DIM_DATE[MonthKey],            
                      DIM_PRODUCTGROUP[ProductGroupKey],
                      DIM_SALESPERSON[SalesPersonKey]
                  ),
                  -- 2. Get keys from Sales,
                  SUMMARIZE(
                      FACT_SALES,
                      DIM_DATE[MonthKey],
                      DIM_PRODUCTGROUP[ProductGroupKey],
                      DIM_SALESPERSON[SalesPersonKey]
                  )
              )
          )
       
      -- [2] now add the facts for sales and forecast
      VAR _TableWithMetrics =
          ADDCOLUMNS(
              _Grain,
              "@Actual", [#SumSales],       -- Measure with CALCULATE(SUM(...))
              "@Forecast", [#SumForecast]   -- Measure with CALCULATE(SUM(...))
          )

      -- [3] Add the Math (Accuracy logic)
      -- We create a new variable so we can reference [@Actual] and [@Forecast] created in the previous step
      VAR _TableWithAccuraryLogic =
          ADDCOLUMNS(
              _TableWithMetrics,
              "@Diff", ABS( [@Actual] - [@Forecast] ),
              "@Denom", MAX( [@Actual], [@Forecast] ),
              "@AccuracyRaw",
                  VAR _Diff = ABS( [@Actual] - [@Forecast] )
                  VAR _Denom = MAX( [@Actual], [@Forecast] )
                  RETURN
                  IF( _Denom = 0, 0, 1 - DIVIDE( _Diff, _Denom ) )
          )
      RETURN
          SWITCH(
              TRUE(),
       
              -- [CASE 1] Lowest Level: Product Group or salesperson
              -- If we are looking at a specific SalesPerson or Product complete the accuaracy math
              ISINSCOPE(DIM_PRODUCTGROUP[ProductGroupKey]) || ISINSCOPE(DIM_SALESPERSON[SalesPersonKey]),
              [#DivideAccuracy],
       
              -- [CASE 2] Totals & Subtotals (Default)
              -- If we are on a SalesPerson Total, Region Total, or Grand Total,
              -- calculate the AVERAGE of the rows in the Virtual Table.
              AVERAGEX(_TableWithAccuraryLogic, [@AccuracyRaw])
          )
      • naelske_cronos's avatar
        naelske_cronos
        Icon for Advocate II rankAdvocate II

        Hello,

         

        Thank you for your dedication on working on this particular problem and I see what you are trying to do. If I'm not mistaken you are calculating the accuracy of each row seperately and then doing an average.

        For example for APR => George Jefferson => January I have 71.10% and you have 30.30%.

        It is another way of calculating but the most important part is that the subtotals remains the same for each level in the hierarchy and that is what I am also trying to change. I still don't have the result as desired. I don't think it is possible to have some kind of hierarchy context filter on (sub)total level because this is where the dynamic change is important based on the current hierarchy.

         

        A workaround is to work with seperate measures and bookmarks. It is an ugly way to workaround but I don't think there is another short term solution.

         

         

        Thank you

        Kind regards


        Kevin

  • King7son1's avatar
    King7son1
    Frequent Visitor

    The measures are not working as expected because in some DIM and Fact tables you have the key as text and in some it is a whole number.  These need to be the same.  Since you are not changing the calculation at different granularity you do not need INSCOPE().    You just need an iterator.  Also, because Region is a column in SlaesPerson you do not need to iteratrate over this level of the hierachy. You can use one measure for all visuals. 

     
    #AverageAccuracyCorrected =
    --get unique combo SalesPerson & Product for Sales
    VAR _SalesKeys =
        SUMMARIZE(
            'FACT_SALES',
            'FACT_SALES'[SalesPersonKey],
            'FACT_SALES'[ProductGroupKey]
        )
     
    --get unique combo SalesPerson & Product for Forecast
    VAR _ForecastKeys =
        SUMMARIZE(
            'FACT_FORECAST',
            'FACT_FORECAST'[SalesPersonKey],
            'FACT_FORECAST'[ProductGroupKey]
        )
     
    -- Combine them into one consolidated list of keys and removes duplicates.  This is just incase the salespeople in both tables don't match
    VAR _AllKeys =
        DISTINCT( UNION( _SalesKeys, _ForecastKeys ) )
     
    -- Map these raw keys to your the keys
    VAR _VirtualGranularity =
        TREATAS(
            _AllKeys,
            'DIM_SALESPERSON'[SalesPersonKey],  -- Handles Region/SalesPerson hierarchy
            'DIM_PRODUCTGROUP'[ProductGroupKey] -- Handles the Product
        )
     
    RETURN
    -- Iterate over the Virtual Table
    AVERAGEX(
        _VirtualGranularity,
        [#DivideAccuracy]
    )
    • naelske_cronos's avatar
      naelske_cronos
      Icon for Advocate II rankAdvocate II

      Hello,

       

      The keys were indeed in text format and I changed them but it didn't change anything to the measures. I did the calculations in EXCEL and the averages seems correct to me.

       

      I have added additional matrices with your formulas but the values don't seem to be the same with one formula, unfortunately. You can find it attached in the Github link I provided you earlier.

       

      Mine is INITIAL and yours is CORRECTED. When doing the math:

      • January = (16,91% + 71,10% + 91,25% + 73,96% + 9,11%) / 5 = 52.47% (as blanks are ignored)
      • APR = Abraham Lincoln =  (16,91% + 92.04%) / 2 = 54.48% (as blanks are ignored)

      I don't know why your formula shows a different output.

       

      Thanks

      Kind regards

       

      Kevin

  • Hi naelske_cronos 

    I wanted to check if you had the opportunity to review the valuable information provided by King7son1  Please feel free to contact us if you have any further questions.


    Thank you.

  • Hi naelske_cronos 

     

    May I check if this issue has been resolved? If not, Please feel free to contact us if you have any further questions.


    Thank you

    • naelske_cronos's avatar
      naelske_cronos
      Icon for Advocate II rankAdvocate II

      Hello,

       

      The question has not been resolved but I have found a workaround using bookmarks and seperate measures for each level of hierarchy. I guess the problem lies in the hierarchy context for the totals for which a single measure is "impossible" to use.

       


      Kind regards

       

      Kevin

      • v-nmadadi-msft's avatar
        v-nmadadi-msft
        Icon for Community Support rankCommunity Support

        Hi naelske_cronos ,

        We really appreciate your efforts and for letting us know the update on the issue.

        Please continue using fabric community forum for your further assistance.


        Regards