CALCULATE et les modifications du filtre
Introduction
SI vous souhaitez approfondir vos connaissances sur la fonction CALCULATE, je vous conseille mon précédent ouvrage, Power BI Desktop : Renforcer, approfondir, explorer, aux Éditions ENI, dont le chapitre 3 décrit notamment des motifs courants et des analyses fréquentes rendues possibles avec CALCULATE. Je ne reviendrai pas sur ces points, mais je résume les caractéristiques essentielles de cette fonction et j’ajoute ici d’autres aspects de son fonctionnement.
Les principes de CALCULATE
Vous l’avez vu depuis le début de cet ouvrage, la fonction CALCULATE revient tout le temps. La raison en est simple, et fondamentale : CALCULATE a le pouvoir de modifier le contexte de filtre généré par le visuel, les segments, le rapport en général.
La syntaxe
Elle est très simple :
CALCULATE ( <Expression> [ , <Filtre1> ] [ , <Filtre2> ] [ , ...])
Mais ceci amène à de nombreuses remarques.
CALCULATE et la transition de contexte
Tout d’abord, CALCULATE peut s’utiliser avec pour seul argument l’expression qu’il s’agit de calculer. En effet, c’est un rôle second de la fonction : lorsqu’elle est invoquée dans un contexte de ligne (c’est-à-dire lors de la création d’une colonne ou lors de son utilisation dans une fonction itérative - SUMX par exemple), la formule CALCULATE(<expression>) déclenche une transition de contexte, et la ligne dans son intégralité devient un filtre, qui se propage donc aux tables du modèle.
Ce phénomène est loin d’être rare : en fait, il est essentiel de se rappeler qu’à chaque fois qu’une mesure est appelée dans une formule, un CALCULATE implicite est systématiquement ajouté.
Prenons un exemple. La mesure [Montant facturé] est définie par la formule :
[Montant facturé] = SUM(Ventes[Montant])
Si l’on souhaite connaître le montant moyen facturé chaque jour, la formule s’écrit :
[Montant moyen facturé] =
AVERAGEX (
Date[Date] ,
[Montant facturé]
)
Soit, en développant la formule en incluant le CALCULATE implicite :
[Montant moyen facturé] =
AVERAGEX ( ...
Les arguments de filtres complexes (AND, OR)
Il existe de nombreux cas de figure : vous en trouverez à la fin de cette section un tableau récapitulatif simplifié.
Sur une colonne
Par filtre complexe, il faut comprendre un filtre portant plusieurs fois sur la même colonne, pour encadrer des dates par exemple ou encore, indiquer plusieurs catégories.
Deux cas se présentent : celui où les deux conditions doivent s’appliquer simultanément et celui où l’une ou l’autre des conditions doit s’appliquer.
Lorsque la date doit être comprise entre deux bornes, alors les conditions doivent s’appliquer simultanément. C’est ce qui se passe lorsque vous indiquez plusieurs filtres dans CALCULATE :
montant janv-mars 2019 =
CALCULATE(
[montant] ,
Datum[Date] >= DATE(2019,01,01) ,
Datum[Date] <= DATE(2019,03,31)
)
Il n’est sans doute pas inutile de rappeler que la table Datum ayant été marquée comme table de dates, le contexte de filtre induit par le visuel (donc ici le champ Annee mois) est ramené à un filtre sur un ensemble de dates (donc sur le champ Date). Dans le CALCULATE ci-dessus, le filtre porte lui aussi sur le champ Date, il remplace donc le contexte de filtre existant. C’est la raison pour laquelle nous retrouvons le même montant sur toutes les lignes du tableau. Ceci explique également pourquoi la ligne 201912 apparaît : bien qu’il n’y ait pas de montant ce mois-là, puisqu’il y en a un pour janv-mars, cette ligne est rajoutée au tableau.
Par défaut, les deux conditions dans CALCULATE sont liées par un ET (AND). La formule précédente pourrait aussi s’écrire à l’aide de l’opérateur...
Les fonctions de filtre : FILTER et KEEPFILTERS
En DAX, il n’y a guère de différence entre un filtre et une table : le résultat de la fonction FILTER est une table, et inversement, puisque le contexte de filtre se propage de table en table le long des relations, une table est bien un filtre.
FILTER
Attention à ne pas confondre FILTER et FILTERS : cette dernière retourne une liste de valeurs pour une colonne filtrée. Elle est décrite plus loin de cet ouvrage, dans le chapitre Les fonctions logiques et les fonctions d’information.
C’est une des fonctions centrales du DAX, pour les raisons évoquées ci-dessus, mais aussi parce que c’est un argument explicite ou implicite de CALCULATE.
De même, une des façons de décrire le DAX consiste à dire qu’utiliser ce langage revient à créer en permanence des tables temporaires - j’utilise le terme de « vues » - et à opérer ensuite un calcul (l’expression). FILTER est une des clés pour effectuer ce travail.
Son expression générique est :
FILTER( table, condition)
FILTER parcourt chaque ligne de la table (c’est donc un itérateur) et, pour chaque ligne, vérifie la condition. Le résultat de l’opération est une table qui contient les mêmes colonnes que la table passée en paramètre :
Que se passe-t-il maintenant si nous souhaitons filtrer à l’aide de plusieurs conditions ? Par exemple, les produits rouges dont le prix unitaire est supérieur à 50 €.
La première tentative est un échec, et nous ne pouvons même pas terminer la formule : en effet, FILTER ne peut recevoir que deux arguments (la table et la condition).
En revanche, cette formule, basée sur AND (ou &&)...
Les fonctions ALL*
Les fonctions ALL* sont une famille de fonctions incluant :
-
ALL
-
ALLEXCEPT
-
ALLSELECTED
Si les fonctions ou les usages que nous avons vus jusqu’à maintenant ont eu pour effet d’ajouter un filtre ou de remplacer le filtre, les fonctions ALL*, lorsqu’elles sont un argument de CALCULATE, ont pour rôle de supprimer le filtre. Autrement dit, là où les filtres réduisent le nombre de lignes d’une table, en vue d’un calcul, les fonctions ALL* en ajoutent.
ALL, ALLEXCEPT
ALL est une fonction primordiale. Elle a une syntaxe simple :
ALL( [<table> | <column>[, <column>[, <column>[,...]]]] )
Elle peut être utilisée comme fonction de table, c’est-à-dire fonction générant une table, dans des expressions DAX (par exemple une fonction itérative comme SUMX), ou comme argument d’un CALCULATE.
Toutes les syntaxes ci-dessous sont correctes :
-
ALL() : dans un CALCULATE uniquement, enlève tous les filtres du contexte de filtre
-
ALL(table) : enlève tous les filtres de la table
-
ALL(table[colonne]) : enlève tous les filtres de la colonne
-
ALL(table[colonne], table[colonne]) : enlève tous les filtres des deux colonnes (ou plus)
Attention : l’argument (le paramètre) de ALL ne peut pas être une expression de table, autrement dit une table calculée, par exemple le résultat de FILTER. Il faut que ce soit une table physique du modèle.
Lorsqu’elle est utilisée dans un CALCULATE, ce qui est très souvent le cas, la fonction ALL joue le rôle de modificateur de CALCULATE, plutôt que de filtre. Cette nuance a son importance dans la mesure où les modificateurs sont pris en compte avant les filtres, dans l’ordre d’exécution du CALCULATE. Ce point est précisé un peu plus loin.
Le DAX a récemment introduit...
Ordre des opérations dans CALCULATE
Il est utile, surtout lorsque votre formule devient complexe, de bien avoir en tête les étapes du fonctionnement de CALCULATE, et de décomposer ces étapes une à une pour bien comprendre le résultat (surtout si ce n’est pas ce à quoi vous vous attendiez !).
Prenons d’abord le cas d’un CALCULATE appelé en l’absence d’un contexte de ligne, pas de fonction itérative et pas non plus de création d’une colonne.
Dans l’ordre :
1. CALCULATE crée un clone du contexte de filtre initial (défini par les filtres du rapport). Ce contexte initial est mis de côté.
2. Le clone (ou nouveau contexte) va d’abord subir les altérations dues aux modificateurs (les fonctions ALL, USERRELATIONSHIP, et CROSSFILTER).
3. CALCULATE va ensuite comparer les colonnes du contexte initial et celles indiquées dans l’argument (les filtres explicites) et décider s’il faut :
-
remplacer,
-
ou ajouter.
4. Enfin l’expression est calculée en tenant compte du nouveau contexte.
Prenons un exemple pour mieux comprendre :
calculate et modificateurs =
CALCULATE(
[qté] ,
produits[couleur] = "Rouge" ,
ALL(produits[couleur])
)
Cette mesure est utilisée dans un tableau comme ceci :
Comment expliquer ce résultat ? À la première étape, le contexte de filtre contient une couleur, mettons « Turquoise ». Pour la seconde étape, CALCULATE « saute » le filtre, et passe directement au modificateur : la couleur est supprimée du filtre, qui ne contient donc aucune couleur. À la troisième étape, CALCULATE réinstaure le ou les filtres...
Exercices
Les exercices sont présentés dans Power BI, bien que la plupart des manipulations puissent aussi être exécutées dans Excel.
Utilisez le fichier chap6_librairie.pbix présent dans le répertoire des exemples correspondant au chapitre CALCULATE et les modifications du filtre.
Attention, les formules proposées sont destinées à Power BI. Pour Excel, pensez à chaque fois à remplacer les virgules par des points-virgules.
Les principes de CALCULATE
Combien de livres par catégorie ?
Nous utilisons ici le fichier chap6_librairie.pbix.
L’objectif de cet exercice est de compter le nombre de livres par catégorie, en ajoutant pour cela une nouvelle colonne dans la table Catégories.
Comprendre le contexte de filtre
Dans cet exercice, il s’agit de réfléchir au contexte de filtre. Il n’y a pas de manipulation ou de fichier associé.
Imaginez deux mesures. La première est définie par :
calculate FILTER =
CALCULATE (
[montant] ,
FILTER (
produits ,
produits[couleur] = "Rouge"
)
)
La seconde mesure est définie par :
calculate simple =
CALCULATE (
[montant] ,
produits[couleur] = "Rouge"
)
Les colonnes famille produit et couleur sont toutes deux dans la table produits.
Expliquez (en décrivant le contexte de filtre) pourquoi elles donnent le même résultat dans le premier tableau, mais un résultat différent dans le deuxième :
Les arguments de filtres complexes...
Corrigés
Attention, les formules proposées sont destinées à Power BI. Pour Excel, pensez à chaque fois à remplacer les virgules par des points-virgules.
Les principes de CALCULATE
Combien de livres par catégorie ?
Attention, ici, il s’agit bien de créer une colonne.
Une erreur consiste à utiliser simplement la fonction COUNT :
COUNT(Livres[Numéro livre])
Rappelez-vous en effet que lors de la création d’une colonne, vous êtes dans un contexte de ligne. Celui-ci ne se propage pas, contrairement au contexte de filtre. La valeur retournée par le code précédent retourne donc toujours le même chiffre, quelle que soit la catégorie.
Il y a deux façons de procéder : la première consiste à transformer le contexte de ligne en contexte de filtre (c’est la transition de contexte). C’est un des rôles de la fonction CALCULATE :
nb livres par catégorie =
CALCULATE(
COUNT(Livres[Numéro livre])
)
Vous remarquez ici que je n’ai fait qu’ajouter CALCULATE à la formule précédente.
Une autre manière de faire est d’utiliser COUNTROWS et RELATEDTABLE, puisque cette fonction entraîne elle aussi une transition de contexte :
nb livres par catégorie 2 =
COUNTROWS(
RELATEDTABLE(Livres)
)
La table Catégorie dans la vue Données
Attention, la transition de contexte peut être une opération coûteuse lorsqu’elle est utilisée sur une grande table (par exemple la table des faits). Ici, les tables Catégories et Livres sont de taille modeste.
Comprendre le contexte de filtre
Ici, la clé est de se rappeler que FILTER ne modifie pas le contexte de filtre, mais crée une vue dans le contexte...