Como calcular média ponderada por grupo em DAX: passo a passo
Vamos calcular uma média ponderada por grupo em DAX — por exemplo, a média de preços ponderada por quantidade por categoria. Isto é útil quando queres um valor representativo que tenha em conta o peso de cada linha (quantidade, importância, pontuação). Em relatórios comerciais, por exemplo, uma média simples pode enganar: se vendeste 2 unidades a 100€ e 100 unidades a 50€, a média simples dos preços é (100+50)/2 = 75€, enquanto a média ponderada por quantidade é (100*2 + 50*100) / (2+100) = 51,92€, que reflecte melhor o preço médio efectivo.
Pré-requisitos
- Power BI Desktop (ou Excel com modelo de dados e DAX).
- Uma tabela de factos com colunas: Category, Price, Quantity. Idealmente, a tabela chama-se 'Sales' e as colunas 'Sales'[Price], 'Sales'[Quantity], 'Sales'[Category].
- Noções básicas de medidas e contexto de filtro em DAX: entender row context vs filter context, e como CALCULATE altera filtros.
- Dados de exemplo com algumas dezenas a milhares de linhas — a abordagem funciona em ambos os casos; apenas atenção à performance se tiveres milhões de linhas.
Passo 1: Entender o que é uma média ponderada
Uma média ponderada calcula a soma(Valor * Peso) dividida pela soma(Peso). Em termos práticos, para cada categoria queremos: SUMX(lines, Price * Quantity) / SUM(Quantity). Para ver com números concretos, suponhamos Category = "A" com três registos:
- Linha 1: Price = 10, Quantity = 2
- Linha 2: Price = 12, Quantity = 3
- Linha 3: Price = 9, Quantity = 5
O numerador é 10*2 + 12*3 + 9*5 = 20 + 36 + 45 = 101. O denominador é 2+3+5 = 10. A média ponderada é 101 / 10 = 10,1. Note que usar SUM('Sales'[Price]) / COUNTROWS('Sales') daria uma média simples diferente; é por isso que precisamos de multiplicar por Quantity em cada linha.
Passo 2: Criar as medidas base
Primeiro cria duas medidas simples: o numerador (soma ponderada) e o denominador (soma dos pesos). Estas medidas ficam reutilizáveis e evitam repetição. Usar SUMX assegura que o cálculo é feito por linha da tabela de factos antes de agregar — isso é crucial quando tens várias colunas ou expressões por linha.
WeightedAmount = SUMX( 'Sales', 'Sales'[Price] * 'Sales'[Quantity] )
TotalQuantity = SUM( 'Sales'[Quantity] )
Notas práticas: se a coluna Quantity puder ter nulos, assegura que está a zero ou usa COALESCE('Sales'[Quantity],0). Formata TotalQuantity como inteiro e WeightedAmount como decimal com 2-4 casas conforme necessário.
Passo 3: Criar a medida da média ponderada segura
Agora criamos a medida final que divide o numerador pelo denominador. Usamos DIVIDE para evitar divisão por zero e garantir resultados limpos quando não há dados no contexto. Além disso, é boa prática usar VAR para calcular cada componente uma única vez, o que ajuda a performance e legibilidade.
WeightedAverage Price =
DIVIDE(
[WeightedAmount],
[TotalQuantity]
)
Outra opção, com VARs explícitas, é:
WeightedAverage Price =
VAR Num = [WeightedAmount]
VAR Den = [TotalQuantity]
RETURN DIVIDE(Num, Den)
Formatar a medida para duas casas decimais é comum. Se tens percentagens ou pontuações com escala diferente, ajusta a formatação conforme o caso.
Passo 4: Tratar cenários com filtros ou diferentes granularidades
Se quiseres calcular a média ponderada sempre por Category, independentemente de outros filtros, usa ALL ou ALLEXCEPT. Por exemplo, se tens um slicer por Product e queres a média por Category ignorando o slicer de produto, ALLEXCEPT mantém o filtro de Category e remove os restantes.
WeightedAverage Price by Category =
VAR Num =
CALCULATE(
SUMX('Sales', 'Sales'[Price] * 'Sales'[Quantity]),
ALLEXCEPT('Sales', 'Sales'[Category])
)
VAR Den =
CALCULATE(
SUM('Sales'[Quantity]),
ALLEXCEPT('Sales', 'Sales'[Category])
)
RETURN
DIVIDE(Num, Den)
Exemplo: se Category = "A" tem 100 unidades, mas um slicer de Product limita a 20 unidades, a medida normal mostrará o valor para as 20 unidades; a versão com ALLEXCEPT mostrará o valor para as 100 unidades da Category. Usa esta abordagem com cuidado — podes contrariar expectativas dos utilizadores se transformares filtros aplicados no relatório.
Passo 5: Exemplo com filtros complexos (datas e produtos)
Quando tens dimensões separadas (Date, Product, Category), garante que usas a tabela de factos para pesos e valores e usa relações adequadas. Se quiseres a média ponderada apenas para o ano corrente, combina com FILTER. Uma forma robusta é filtrar a tabela de datas explicitamente:
WeightedAvg This Year =
VAR Num =
CALCULATE(
SUMX('Sales', 'Sales'[Price] * 'Sales'[Quantity]),
FILTER(ALL('Date'), YEAR('Date'[Date]) = YEAR(TODAY()))
)
VAR Den =
CALCULATE(
SUM('Sales'[Quantity]),
FILTER(ALL('Date'), YEAR('Date'[Date]) = YEAR(TODAY()))
)
RETURN
DIVIDE(Num, Den)
Assim consegues combinar filtros de tempo com outros slicers. Se as relações entre tabelas não estiverem activas, podes precisar de USERELATIONSHIP; se tens múltiplos anos e queres ALLSELECTED para respeitar selecções do utilizador, usa ALLSELECTED('Date').
Verificar o resultado
Coloca Category numa tabela visual e adiciona as medidas WeightedAverage Price e TotalQuantity. Verifica manualmente uma linha: calcula numa folha de cálculo a soma(Price*Quantity) e a soma(Quantity) para a category e confirma que a divisão coincide. Erros comuns: esquecer SUMX (levando a multiplicação de agregados como SUM(Price)*SUM(Quantity) que está errado), não usar DIVIDE (pode dar erro por divisão por zero) ou aplicar ALLEXCEPT de forma incorreta e perder filtros importantes. Testa também cenários com 0 quantidades e valores nulos.
Conclusão
Com estas medidas consegues calcular médias ponderadas robustas em diferentes contextos de filtro. Próximos passos: aplicar a técnica a outras variáveis (custos, pontuações) e comparar com médias simples. Dica prática: valida sempre pelo menos 3 linhas manualmente (pequeno conjunto de exemplo) e depois testa com agregados maiores; isso ajuda a perceber onde o contexto de filtro influencia o resultado. Se precisares, partilha um exemplo concreto e eu ajudo a debugar.