
Picture this: your sales director walks in and asks for a report that shows each region's revenue alongside the company-wide total, so leadership can see what percentage each region contributes — all in a single table visual. You build a measure for total sales, drop it into a matrix, and it works fine. Then you add a region slicer, and suddenly the "company total" column changes with every selection. It's not a company total anymore — it's just the same number as the region total, filtered differently.
This is one of the most common moments where new DAX developers realize they need to understand filter context at a deeper level. CALCULATE is the function that lets you step into that filter context, reshape it, and compute aggregations over exactly the subset of data you intend. But CALCULATE alone isn't enough. To use it with precision, you need to understand how Boolean filters and table filters behave differently, when to use KEEPFILTERS versus letting CALCULATE override existing filters, and when to use REMOVEFILTERS to wipe the slate clean.
By the end of this lesson, you'll be able to write measures that aggregate over specific, controlled subsets of data — independently of what slicers, rows, or columns are doing to your report. You'll understand why each pattern works, not just what to type.
What you'll learn:
This lesson assumes you are comfortable with the following:
You do not need to have used CALCULATE before. We'll build it up from scratch.
Before we touch CALCULATE, we need to be precise about filter context — because CALCULATE's entire purpose is to control it.
Every time Power BI evaluates a DAX measure, it does so inside a filter context. Think of filter context as a set of rules that restricts which rows are visible to the calculation at that moment. When you drop a measure into a table visual, each cell in that table has its own filter context. The row header (say, "West Region") applies a filter that says "only show rows where Region = West." The column header, slicers, page filters, and report filters all add more rules on top of that.
Here's the key insight: DAX functions like SUM don't see the full table. They only see the rows that survive the current filter context. If filter context says "Region = West," then SUM(Sales[Revenue]) adds up only West revenue — not because you told it to, but because most rows are invisible.
CALCULATE is the only function in DAX that can change filter context before evaluating an expression. Everything else just works within whatever context it finds itself in.
CALCULATE has this signature:
CALCULATE(<expression>, <filter1>, <filter2>, ...)
Here's the mental model: CALCULATE takes the current filter context, applies your filter arguments to modify it, and then evaluates the expression inside that new context.
Let's use a concrete example. Suppose you have a Sales table with columns: OrderDate, Region, Category, Revenue.
A simple total revenue measure:
Total Revenue = SUM(Sales[Revenue])
Now suppose you want revenue specifically for the "Electronics" category, regardless of what the current filter context says about category:
Electronics Revenue = CALCULATE(SUM(Sales[Revenue]), Sales[Category] = "Electronics")
When this measure runs in a cell that already has a region filter applied (say, "East"), CALCULATE:
Region = EastCategory = ElectronicsSUM(Sales[Revenue]) in the combined context: Region = East AND Category = ElectronicsThat's not so surprising. The interesting behavior — and the source of most confusion — is what happens when your filter argument conflicts with the existing filter context.
When you write Sales[Category] = "Electronics", that's a Boolean filter argument. DAX evaluates it as a predicate and internally converts it to a list of values that column is allowed to take. In this case, it becomes a filter on the Category column that allows only the value "Electronics."
Here's the critical default behavior: Boolean filter arguments replace the existing filter on that column. If a slicer already filters Category to "Furniture," and your CALCULATE says Sales[Category] = "Electronics", the Electronics filter wins. The slicer is overridden for that column.
Electronics Revenue = CALCULATE(SUM(Sales[Revenue]), Sales[Category] = "Electronics")
This measure will always show Electronics revenue, no matter what the Category slicer says. That's sometimes exactly what you want. Other times, it's a bug.
Table filters work differently. A table filter passes a physical table — usually constructed with FILTER — as the filter argument. This gives you fine-grained control but also changes the override behavior.
High Value Electronics =
CALCULATE(
SUM(Sales[Revenue]),
FILTER(
Sales,
Sales[Category] = "Electronics" && Sales[Revenue] > 1000
)
)
Here, FILTER iterates over the Sales table (under the current filter context) and returns only rows that match both conditions. The result of FILTER is a table, and CALCULATE uses that table as a filter. Table filters can span multiple columns simultaneously, which Boolean filters cannot do elegantly.
Important distinction: Boolean filters are evaluated against the column's domain — they override the existing filter on that column. Table filters pass a specific set of rows — they interact differently with context, especially when KEEPFILTERS is involved. We'll get to that shortly.
Back to our original problem: you want a company-wide total that doesn't change when the Region slicer is used. This is where REMOVEFILTERS comes in.
REMOVEFILTERS is a modern, explicit version of passing ALL as a filter argument. Both remove filters from columns or tables, letting your expression see more data than the current filter context would normally allow.
Company Total Revenue =
CALCULATE(
SUM(Sales[Revenue]),
REMOVEFILTERS(Sales[Region])
)
In a matrix visual where rows are Regions and there's a Category slicer, this measure:
Sales[Region]SUM(Sales[Revenue]) with Region unrestricted, but Category still filteredSo if the slicer says "Electronics" and the row is "East," this measure returns the total Electronics revenue for all regions — not just East.
To remove all filters on an entire table:
Grand Total Revenue =
CALCULATE(
SUM(Sales[Revenue]),
REMOVEFILTERS(Sales)
)
This wipes out all column filters on the Sales table, giving you the genuine grand total. The Region slicer, Category filter, date filter — none of them affect it.
Tip:
REMOVEFILTERSis preferred overALLwhen used as a CALCULATE modifier because it makes the intent explicit.ALLused inside CALCULATE as a filter argument is functionally equivalent, butREMOVEFILTERSreads more clearly in complex measures.
You can remove filters from multiple tables or columns by passing them as additional arguments:
Revenue Ignoring Region and Category =
CALCULATE(
SUM(Sales[Revenue]),
REMOVEFILTERS(Sales[Region]),
REMOVEFILTERS(Sales[Category])
)
Now we can solve the original problem properly. The contribution of each region to the grand total:
Region % of Total =
DIVIDE(
SUM(Sales[Revenue]),
CALCULATE(SUM(Sales[Revenue]), REMOVEFILTERS(Sales[Region]))
)
In a matrix with Region on rows:
This pattern — dividing a naturally filtered value by a CALCULATE with REMOVEFILTERS — is one of the most useful templates in DAX. Memorize the shape of it.
Remember how Boolean filters replace the existing filter on a column? Sometimes you want the opposite: you want to add a constraint on top of what's already there, without overriding it.
This is exactly what KEEPFILTERS does.
Here's the scenario: you're building a measure that should show revenue only for "Electronics," but respecting whatever the user has selected in the Category slicer. If the user has filtered to "Furniture," the measure should return blank (or zero) — because you want Electronics, and the context says Furniture, and those don't overlap.
Without KEEPFILTERS:
Electronics Revenue (Override) =
CALCULATE(SUM(Sales[Revenue]), Sales[Category] = "Electronics")
If the slicer filters to "Furniture," this measure still shows Electronics revenue. The slicer is overridden.
With KEEPFILTERS:
Electronics Revenue (Respected) =
CALCULATE(
SUM(Sales[Revenue]),
KEEPFILTERS(Sales[Category] = "Electronics")
)
Now CALCULATE intersects the existing filter with your Electronics condition. If the slicer says "Furniture" and your filter says "Electronics," the intersection is empty. No rows survive. The measure returns blank.
If the slicer says "Electronics," the intersection is "Electronics" — you get the electronics total. If there's no category slicer at all, the context has no category filter, so your Electronics filter applies cleanly.
Think of KEEPFILTERS as "AND this with whatever already exists" rather than "replace whatever exists."
Warning: KEEPFILTERS can produce surprising blank results if the existing filter and your filter don't overlap. Always test your measure with and without slicer selections to make sure it behaves the way you intend in all scenarios.
Suppose you're building a "budget vs. actuals" report. You have a table called Scenario with values "Actual" and "Budget." You want a measure for Actual revenue that respects other slicer selections but always restricts to Actual:
Actual Revenue =
CALCULATE(
SUM(Sales[Revenue]),
KEEPFILTERS(Sales[Scenario] = "Actual")
)
If a user also filters by Scenario = "Budget," the measure returns blank — which is correct, because there's no such thing as Actual revenue in a Budget context. The two filters don't intersect.
Without KEEPFILTERS, a user filtering to "Budget" would still see Actual revenue labeled as "Actual Revenue," which would be confusing and potentially dangerous in a financial report.
Let's build a measure that combines everything we've covered. The business question: "What percentage of overall company revenue (across all time periods) does each product category's current-year sales represent?"
Assume your model has:
Sales[Revenue]Sales[Category]Sales[OrderDate] (with a Date table related to it)Date[Year]You have a Year slicer on the report. When a user selects 2024, you want the numerator to reflect 2024 sales per category (naturally filtered by the visual and slicer), and the denominator to reflect all-time revenue for all categories.
% of All-Time Company Revenue =
DIVIDE(
SUM(Sales[Revenue]),
CALCULATE(
SUM(Sales[Revenue]),
REMOVEFILTERS(Sales[Category]),
REMOVEFILTERS('Date')
)
)
Breaking this down:
SUM(Sales[Revenue]) — filtered by whatever row (Category) and slicer (Year) are activeIf instead you wanted the denominator to reflect all-time revenue within the current category (ignoring only the year filter), you'd write:
% of Category All-Time Revenue =
DIVIDE(
SUM(Sales[Revenue]),
CALCULATE(
SUM(Sales[Revenue]),
REMOVEFILTERS('Date')
)
)
Here, we only remove the Date filter. The Category filter from the row context stays in place, so the denominator is all-time revenue for this specific category.
Adjusting which filters you remove with REMOVEFILTERS is how you control the exact subset being compared.
Here's a structured exercise you can try in Power BI Desktop with any sales dataset. If you don't have one handy, load the sample Contoso dataset or use the AdventureWorks sample available from Microsoft.
Setup: Create a matrix visual with Product Category on rows and add a Date slicer. Load or confirm you have a Sales or Internet Sales table with a Revenue/Sales Amount column.
Exercise Steps:
Create a baseline measure:
Total Sales = SUM(Sales[SalesAmount])
Drop it into the matrix. Observe how it changes with slicer selections and by row.
Create a category-override measure using a Boolean filter:
Always Bikes =
CALCULATE([Total Sales], Sales[ProductCategory] = "Bikes")
Place this beside Total Sales. Select "Accessories" in a Category slicer if you have one. Notice: Always Bikes still shows bike revenue regardless of the slicer.
Create a KEEPFILTERS version:
Bikes If Selected =
CALCULATE([Total Sales], KEEPFILTERS(Sales[ProductCategory] = "Bikes"))
Now filter the Category slicer to "Accessories." This measure should return blank in every row, because the slicer and the KEEPFILTERS condition don't overlap.
Create a grand total measure:
Grand Total All Time =
CALCULATE([Total Sales], REMOVEFILTERS(Sales))
This should show the same number in every cell, regardless of row or slicer.
Create the contribution percentage:
% of Grand Total =
DIVIDE([Total Sales], [Grand Total All Time])
Format this as a percentage. Each row should show what share of all-time, all-category sales that row represents.
Mistake 1: Using REMOVEFILTERS on the wrong table
If your date dimension is in a separate Date table and you write REMOVEFILTERS(Sales[OrderDate]), you're removing the filter on the Sales table's date column directly — but if your Date slicer filters through the Date table via a relationship, you need REMOVEFILTERS('Date') instead.
Mistake 2: Expecting KEEPFILTERS to filter rows inside FILTER KEEPFILTERS works as a modifier for CALCULATE's filter arguments. It does not change how FILTER iterates rows. If you put KEEPFILTERS inside a FILTER call, it won't do what you expect.
Mistake 3: Confusing REMOVEFILTERS with filtering to blank REMOVEFILTERS removes a filter, expanding what's visible. It does not add a filter for blank or null values. If your measure returns blank after using REMOVEFILTERS, the issue is elsewhere — usually a missing relationship or a KEEPFILTERS that's eliminating all rows.
Mistake 4: Nesting CALCULATE inside FILTER with REMOVEFILTERS
When you write FILTER(Sales, CALCULATE([Total Sales], REMOVEFILTERS(...))), you're evaluating CALCULATE inside row context during iteration. This is a context transition situation. It's not always wrong, but it's expensive and often unnecessary. Prefer Boolean filter arguments when you can.
Mistake 5: Forgetting that Boolean filters override, table filters don't automatically
A Boolean filter like Sales[Region] = "East" inside CALCULATE replaces any existing filter on Sales[Region]. A table filter from FILTER doesn't automatically replace — it passes a table of valid rows. Wrapping in KEEPFILTERS changes this behavior. If your numbers seem unaffected by slicers when they should be, check whether you're using a Boolean filter that's overriding context you meant to preserve.
Let's consolidate what you've learned:
Filter context is the set of active filters that determine which rows any DAX expression can "see." Every cell in a visual has its own filter context, shaped by row headers, column headers, slicers, and page/report filters.
CALCULATE is the only function that changes filter context. It takes your expression, modifies the filter context using its arguments, and evaluates the expression in that new context.
Boolean filter arguments (like Sales[Category] = "Electronics") replace the existing filter on that column by default. Use them when you want to lock in a value regardless of what slicers say.
Table filter arguments (via FILTER) give you multi-column conditions and more nuanced control. They're more flexible but more expensive to compute.
REMOVEFILTERS expands the visible data by removing filters from specified columns or tables. Use it to compute grand totals, cross-category comparisons, or any aggregation that should ignore certain slicers.
KEEPFILTERS changes the Boolean filter behavior from "replace" to "intersect." Use it when your measure should respect the user's slicer selections rather than overriding them.
Next steps in your DAX Mastery path:
Once you're comfortable with these patterns, the natural progressions are:
Every advanced DAX pattern you'll encounter — from ranking calculations to dynamic segmentation to what-if analysis — is built on the foundation you've just laid. The more precisely you understand how CALCULATE controls filter context, the more quickly you'll be able to reason through any DAX problem you encounter.