5.4 Statistical Functions, Semi-Additive Measures, and Calculation Groups
Key Takeaways
- Core statistical DAX: AVERAGE, MEDIAN, MIN/MAX, STDEV.P/STDEV.S, VAR.P/VAR.S, PERCENTILE.INC/EXC, RANKX.
- Semi-additive measures (balances, inventory, headcount) can be summed across most dimensions but NOT across time.
- LASTNONBLANK/LASTDATE return the most recent value; FIRSTNONBLANK returns the opening value, for snapshot measures.
- DIVIDE() is preferred over the / operator because it returns BLANK() (or an alternate) instead of erroring on divide-by-zero.
- Calculation groups define time-intelligence patterns once and apply them to any measure via SELECTEDMEASURE().
Quick Answer: Use DIVIDE() instead of / for safe division. Semi-additive measures (balances, inventory) should not be summed across dates; use LASTNONBLANK or LASTDATE for the most recent value. Calculation groups let you define time-intelligence patterns (YTD, PY, YoY) once and apply them to any measure automatically.
Aggregation Functions
| Function | Description |
|---|---|
| SUM | Adds all values |
| AVERAGE | Arithmetic mean |
| MIN / MAX | Smallest / largest value |
| COUNT | Count numeric non-blanks |
| COUNTA | Count non-blanks of any type |
| COUNTBLANK | Count blank/null values |
| COUNTROWS | Count rows in a table |
| DISTINCTCOUNT | Count unique values |
Advanced Statistical Functions
| Function | Description |
|---|---|
| MEDIAN | Middle value when sorted |
| STDEV.P / STDEV.S | Population / sample standard deviation |
| VAR.P / VAR.S | Population / sample variance |
| PERCENTILE.INC / PERCENTILE.EXC | Kth percentile, inclusive / exclusive |
| RANKX | Rank values in a table |
Choose the .S (sample) variants when your data is a sample of a larger population and .P (population) when it represents the entire population. Iterator versions (SUMX, AVERAGEX, COUNTX, MINX, MAXX) evaluate an expression per row, then aggregate.
Total Revenue = SUMX(Sales, Sales[Quantity] * Sales[UnitPrice])
Avg Order Profit = AVERAGEX(Sales, Sales[Revenue] - Sales[Cost])
The DIVIDE Function
DIVIDE(numerator, denominator [, alternateResult]) is safe division.
| Operation | / Operator | DIVIDE() |
|---|---|---|
| 10 / 5 | 2 | 2 |
| 10 / 0 | Error/Infinity | BLANK() (or alternate) |
| 0 / 0 | Error/NaN | BLANK() (or alternate) |
Profit Margin = DIVIDE(SUM(Sales[Profit]), SUM(Sales[Revenue]))
Profit Margin = DIVIDE(SUM(Sales[Profit]), SUM(Sales[Revenue]), 0)
Best Practice: Always use DIVIDE() in measures; it handles divide-by-zero gracefully and avoids runtime errors.
Semi-Additive Measures
| Measure Type | Additive Across Time? | Example |
|---|---|---|
| Fully Additive | Yes | Revenue, Quantity, Cost |
| Semi-Additive | No | Account Balance, Inventory, Headcount |
| Non-Additive | No (across any dimension) | Ratios, Percentages |
Summing monthly inventory snapshots (100 + 120 + 90 = 310) is meaningless; you want the last known value (90).
Current Inventory =
CALCULATE(SUM(Inventory[Units]), LASTNONBLANK('Date'[Date], CALCULATE(COUNTROWS(Inventory))))
Ending Balance = CALCULATE(SUM(Accounts[Balance]), LASTDATE('Date'[Date]))
Opening Balance =
CALCULATE(SUM(Accounts[Balance]), FIRSTNONBLANK('Date'[Date], CALCULATE(COUNTROWS(Accounts))))
Note that semi-additive measures are still additive across non-time dimensions, summing balances across several accounts on the same date is correct.
Calculation Groups
Without calculation groups you must author a separate measure for every base x pattern combination. With 3 base measures and 5 patterns you get 15 measures; with 20 base measures you get 100. Calculation groups define the patterns once and apply them to any selected measure.
| Calculation Item | Logic |
|---|---|
| Current | SELECTEDMEASURE() |
| YTD | CALCULATE(SELECTEDMEASURE(), DATESYTD('Date'[Date])) |
| PY | CALCULATE(SELECTEDMEASURE(), SAMEPERIODLASTYEAR('Date'[Date])) |
| YoY | SELECTEDMEASURE() - CALCULATE(SELECTEDMEASURE(), SAMEPERIODLASTYEAR('Date'[Date])) |
| QTD | CALCULATE(SELECTEDMEASURE(), DATESQTD('Date'[Date])) |
SELECTEDMEASURE() is a placeholder for whichever measure the user puts in the visual, so any base measure (Revenue, Cost, Profit) can be viewed through any pattern selected from a slicer. Calculation groups are authored in Tabular Editor or directly in Power BI Desktop (Model view > New calculation group), and items are ordered with an ordinal to control display order.
Exam Tip: Know the purpose of calculation groups and recognize the scenario, dozens of repetitive time-intelligence measures, where they are the right solution.
On the Exam
The PL-300 tests using DIVIDE() over /, identifying semi-additive measures and handling them with LASTNONBLANK/LASTDATE, choosing SUM vs. SUMX, and the purpose of calculation groups.
Additivity Is a Property of the Measure, Not the Column
The exam wants you to classify a measure by how it may be aggregated. Fully additive values (revenue, quantity, cost) can be summed across every dimension, including time. Semi-additive values (account balance, inventory on hand, headcount) can be summed across non-time dimensions but not across time, summing three monthly balances invents money. Non-additive values (ratios, percentages, averages, distinct counts) cannot be summed across any dimension and must be recomputed from their components in each context.
Misclassifying a measure produces totals that look plausible but are wrong, which is why scenarios about "the total at the bottom of the matrix is too high" usually hinge on additivity.
Picking the Right Snapshot Function
For semi-additive snapshots, the choice of function depends on whether your snapshot rows are dense or sparse. LASTDATE returns the value at the latest date in context, which works when every period has a row. LASTNONBLANK (and FIRSTNONBLANK) scan backward (or forward) for the most recent date where an expression is non-blank, which is safer when some periods are missing, for example a balance that is only recorded on business days. OPENINGBALANCE* and CLOSINGBALANCE* functions wrap these patterns for month, quarter, and year and are worth recognizing by name.
Sample vs. Population, and Inclusive vs. Exclusive
Two function-family distinctions appear regularly. Use STDEV.S/VAR.S (sample) when your rows are a sample drawn from a larger population and STDEV.P/VAR.P (population) when the rows are the entire population, the sample variants divide by n-1, the population variants by n. Similarly, PERCENTILE.INC includes the endpoints (0th and 100th percentile are valid) while PERCENTILE.EXC excludes them; Excel-compatible reports usually expect .INC. Knowing which variant a scenario calls for is a small but real source of exam points.
A table contains monthly account balances. Summing the Balance column across all months gives a misleadingly large result. What type of measure is this, and how should you handle it?
What does DIVIDE(100, 0) return by default?
You have 15 base measures and need Current, YTD, PY, and YoY versions of each. Without calculation groups, how many total measures would you need?
In a calculation group item, what does SELECTEDMEASURE() represent?