2023-01-18
On est habitué à utiliser des hiérarchies où la somme des éléments d’un niveau donne le résultat pour le niveau parent. Par exemple le CA de chaque produit vendu dans une boutique donne le CA de la boutique ; le CA de toutes les boutiques dans un pays donne le CA du pays. Mais certaines hiérarchies ne fonctionnent pas comme ça :
Dans ces deux cas une agrégation naturelle n’est pas possible, il faut adapter la donnée ou son traitement.
Pour les valeurs non-additives on peut ajouter des valeurs de régulation pour corriger les écarts entre les sous-niveaux et le total. Par exemple, si j’ai une hiérarchie Pays –> Boutique et que je veux afficher les valeurs :
Si j’intègre les données correctement :
Pays | Boutique | CA |
---|---|---|
FR | Paris | 67 |
FR | Lyon | 38 |
FR | Perpignan | 15 |
Dans ce cas le cumulé des boutiques est 120. Mais je veux afficher 123 (ne me demandez pas pourquoi, c’est le problème du comptable). Je dois donc ajouter une ligne de régulation :
Pays | Boutique | CA |
---|---|---|
FR | Paris | 67 |
FR | Lyon | 38 |
FR | Perpignan | 15 |
FR | NA | 3 |
└───FR = 123
├───Paris = 67
├───Lyon = 38
├───Perpigna = 15
└───NA = 3
Néanmoins la vérification des données et l’ajout de chaque ligne de régulation peuvent être très fastidieux et couteux dès qu’on a plusieurs catégories et plusieurs niveaux. On risque aussi d’ajouter des erreurs dans les données alors qu’elles nous ont toutes été fournies est qu’il ne devrait pas être nécessaire de les recalculer. Enfin, la régulation sera visible, ce qui n’est pas toujours souhaité par l’utilisateur.
Lorsqu’on a un nombre de niveaux différent dans les branches d’une hiérarchie, la solution la plus courante est d’ajouter des niveaux fictifs pour égaliser les branches. Si dans ma base de données, j’ai en plus des données de la France, les valeurs d’autres pays mais sans le détail par boutique :
Pour intégrer les données je vais devoir préciser une boutique pour ces pays : Pays | Boutique | CA — | — | — FR | Paris | 67 FR | Lyon | 38 FR | Perpignan | 15 FR | NA | 3 ES | Boutiques ES | 80 IT | Boutiques IT | 55
├───FR = 123
│ ├───Paris = 67
│ ├───Lyon = 38
│ ├───Perpignan = 15
│ └───NA = 3
├───ES = 80
│ └───Boutiques ES = 80
└───IT = 55
└───Boutiques IT = 55
La méthode idéale (à mon avis) pour traiter les deux situations décrites ci-dessus est d’intégrer l’ensemble des données, sans régulation, pour recréer la hiérarchie et les mesures en DAX.
Par exemple, on souhaite analyser l’efficacité d’une population pour exécuter une tâche quelconque. Les personnes sont rassemblées en sections, plusieurs sections forment une équipe, plusieurs équipes forment un groupe. Mais les groupes peuvent aussi être un ensemble de personnes sans répartition par équipe. Et les équipes peuvent être un ensemble de personnes sans répartition par section On a deux mesures :
└───Root : Eff = 55% / Pax = 35
├───Grp_A : Eff = 60% / Pax = 9
│ ├───Eqp_A1 : Eff = 40% / Pax = 5
│ └───Eqp_A2 : Eff = 70% / Pax = 4
│ ├───Sec_A2_ker : Eff = 75% / Pax = 2
│ └───Sec_A2_bis : Eff = 55% / Pax = 2
├───Grp_B : Eff = 65% / Pax = 16
│ ├───Eqp_B1 : Eff = 40% / Pax = 6
│ │ ├───Sec_B1_ker : Eff = 70% / Pax = 2
│ │ ├───Sec_B1_bis : Eff = 40% / Pax = 2
│ │ └───Sec_B1_ter : Eff = 20% / Pax = 2
│ ├───Eqp_B2 : Eff = 35% / Pax = 5
│ └───Eqp_B3 : Eff = 30% / Pax = 5
└───Grp_C : Eff = 30% / Pax = 10
On récupère les données au format tabulaire : Elément | Parent | ValeurA | ValeurB | ...
Elément | Parent | Eff | Pax |
---|---|---|---|
Root | null | 55 | null |
Grp_A | Root | 60 | null |
Eqp_A1 | Grp_A | 40 | 5 |
Eqp_A2 | Grp_A | 70 | null |
Sec_A2_ker | Eqp_A2 | 75 | 2 |
Sec_A2_bis | Eqp_A2 | 55 | 2 |
Grp_B | Root | 65 | null |
Eqp_B1 | Grp_B | 40 | null |
Sec_B1_ker | Eqp_B1 | 70 | 2 |
Sec_B1_bis | Eqp_B1 | 40 | 2 |
Sec_B1_ter | Eqp_B1 | 20 | 2 |
Eqp_B2 | Grp_B | 35 | 5 |
Eqp_B3 | Grp_B | 30 | 5 |
Grp_C | Root | 30 | 10 |
Il est important que la racine de la hiérarchie (Root) soit une valeur nulle comme parent (et pas une valeur vide). Il est possible d’avoir plusieurs racines qui sont chacunes au début d’une hiérarchie.
On ajoute également une colonne d’index.
Un fois la table chargée dans le modèle, on va créer plusieurs colonnes et mesures pour rendre la hiérarchie facilement utilisable. Pour cela on va notamment utiliser les fonctions parents et enfants.
Path = PATH(AnalysePerf[Elément], AnalysePerf[Parent])
Depth = PATHLENGTH(AnalysePerf[Path])
Leaf = IF(COUNTROWS(FILTER(AnalysePerf, PATHCONTAINS(AnalysePerf[Path], EARLIER(AnalysePerf[Elément])))) = 1, 1, 0)
On va à présent recomposer la hiérarchie en ajoutant une colonne par niveau. Il y a 4 niveaux dans notre exemple, il faut donc ajouter 4 colonnes.
Niveau 1 = PATHITEM(AnalysePerf[Path], 1)
Niveau 2 = PATHITEM(AnalysePerf[Path], 2)
Niveau 3 = PATHITEM(AnalysePerf[Path], 3)
Niveau 4 = PATHITEM(AnalysePerf[Path], 4)
Les colonnes créées peuvent être réunies dans une hiérarchie.
NB : La méthode pour répéter les niveaux à la place de valeurs vides est décrite plus bas.
La table est prète à être utilisée, on va créer les mesures qui vont gérer les valeurs non-additives et additives.
RowDepth = MIN(AnalysePerf[Depth])
BrowseDepth = SWITCH(TRUE()
, ISINSCOPE(AnalysePerf[Niveau 4]), 4
, ISINSCOPE(AnalysePerf[Niveau 3]), 3
, ISINSCOPE(AnalysePerf[Niveau 2]), 2
, ISINSCOPE(AnalysePerf[Niveau 1]), 1
)
RowPath = FIRSTNONBLANK(AnalysePerf[Path], TRUE())
Val Eff = VAR _d = [BrowseDepth]
RETURN CALCULATE(SUM(AnalysePerf[Eff]), FILTER(AnalysePerf, [Depth] = _d))
Val Pax = IF([BrowseDepth] >= MIN(AnalysePerf[Depth]) && [BrowseDepth] <= MAX(AnalysePerf[Depth])
, CALCULATE(SUM(AnalysePerf[Pax]), FILTER(AnalysePerf, AnalysePerf[Leaf] = 1))
)
Attention, en filtrant sur un élément en particulier de la hiérarchie : les mesures renvoient des valeurs uniquement pour cet élément et ses enfants. Par exemple si je sélectionne Eqp_2 j’aurai le résultat pour Eqp_2, Sec_A2_ker et Sec_A2_bis. Pour les niveaux supérieurs je n’ai pas de données. Ce comportement n’est pas incohérent et est celui attendu dans la plupart des cas.
Si on souhaite afficher les valeurs des niveaux supérieurs (les totaux), lorsque la hiérarchie est filtrée, il faut modifier les mesures :
Val Eff 2 = VAR _path = [RowPath]
VAR _d = [BrowseDepth]
RETURN CALCULATE(SUM(AnalysePerf[Eff]), FILTER(ALL(AnalysePerf), AnalysePerf[Depth] = _d && PATHCONTAINS(_path, [Elément])))
Val Pax 2 = VAR _d = [BrowseDepth]
RETURN If(_d,
VAR _p = [RowPath]
VAR _e = MINX(FILTER(ALL(AnalysePerf), AnalysePerf[Depth] = _d && PATHCONTAINS(_p, AnalysePerf[Elément])), [Elément])
RETURN CALCULATE(SUM(AnalysePerf[Pax]), FILTER(ALL(AnalysePerf), PATHCONTAINS(AnalysePerf[Path], _e) && AnalysePerf[Leaf] = 1))
)
Si on affiche ces mesures dans un histogramme, seules les valeurs pour le niveau en cours sont visibles. Par exemple si on se place dans la hiérarchie au niveau 3 (Eqp_A1, Eqp_A2, etc.) on ne verra pas de résultat pour l’élément Grp_C qui ne contient pas de niveau enfant.
Si on souhaite afficher les éléments parents dans le visuel il faut à nouveau modifier les mesures. On reprend la version 2 des mesures et on remplace l’appel à [BrowseDepth]
par le calcul MIN([BrowseDepth], [RowDepth])
: on garde le plus petit niveau entre les deux indicateurs.
Val Eff 3 = VAR _path = [RowPath]
VAR _d = MIN([BrowseDepth], [RowDepth])
RETURN CALCULATE(SUM(AnalysePerf[Eff]), FILTER(ALL(AnalysePerf), AnalysePerf[Depth] = _d && PATHCONTAINS(_path, [Elément])))
Val Pax 3 = VAR _d = MIN([BrowseDepth], [RowDepth])
RETURN If(_d,
VAR _p = [RowPath]
VAR _e = MINX(FILTER(ALL(AnalysePerf), AnalysePerf[Depth] = _d && PATHCONTAINS(_p, AnalysePerf[Elément])), [Elément])
RETURN CALCULATE(SUM(AnalysePerf[Pax]), FILTER(ALL(AnalysePerf), PATHCONTAINS(AnalysePerf[Path], _e) && AnalysePerf[Leaf] = 1))
)
Le visuel Segment dans Power BI affiche tous les niveaux d’une hiérarchie. Si on l’utilise pour afficher les 4 niveaux, des valeurs (Vide) seront affichées pour chaque niveau. Il faut donc filtrer ces valeurs.
La mesure BrowseDepth est non nulle lorsqu’il faut afficher le niveau, on pourrait vouloir l’utiliser pour filtrer le segment. Mais en faisant cela on ne garde que les branches de la hiérarchie avec des niveaux complets (de 1 à 4) ; en effet le visuel segment n’affiche que des branches complètes. Ce n’est donc pas la bonne solution.
On peut utiliser la colonne Leaf pour ne conserver que les éléments qui terminent une branche. Dans ce cas on ne perd pas d’élément dans la hiérarchie. Mais les braches incomplètes (avec moins de 4 niveaux) comportent des valeurs (Vide).
On remplace ses valeurs (Vide) en “complétant” les branches. Si un niveau est vide, on prend le nom du niveau précédent :
Niveau 1 = PATHITEM(AnalysePerf[Path], 1)
Niveau 2 = IF(PATHITEM(AnalysePerf[Path], 2) <> "", PATHITEM(AnalysePerf[Path], 2), [Niveau 1])
Niveau 3 = IF(PATHITEM(AnalysePerf[Path], 3) <> "", PATHITEM(AnalysePerf[Path], 3), [Niveau 2])
Niveau 4 = IF(PATHITEM(AnalysePerf[Path], 4) <> "", PATHITEM(AnalysePerf[Path], 4), [Niveau 3])