<< Back

Filtres Tableau dynamiques en 50 lignes de code python

Bonjour à tous ! Aujourd’hui, un client m’a posé une question intéressante que j’ai pensé partager avec vous! La question est de savoir comment faire pour avoir des filtres dynamiques dans Tableau.

La problématique est la suivante : imaginez que vous ayez des données alimentées régulièrement, typiquement des logs. Les filtres et paramètres que vous avez mis en place par le passé ne changent pas alors que les données ont changé dans la base. Lors de l’utilisation du dashboard il vous faut donc ajuster le filtre suivant les nouvelles données ce qui prend du temps. Par ailleurs si vous aviez un Paramètre avec une plage de date et qu’une nouvelle plage de date doit être implémentée alors il faut vous armer de patience et modifier « à la main » votre dashboard pour y rajouter la nouvelle date ! 😭 

En attendant l’arrivée un jour des filtres et paramètres dynamiques nous allons regarder une solution potentielle codée en python que vous pourrez réutiliser à loisir et l’adapter à vos cas d’usages.

1/ Introduction

Tout à long du tutoriel je travaillerai avec un Workbook sans extrait afin d’avoir accès à la structure XML du dashboard. En effet, en ouvrant un twb et un twbx, vous vous apercevrez que seul le twb est lisible. J’utiliserai python en version 2.7 et la librairie ElementTree pour le parsing du XML. Je supposerai que vous avez le nom du paramètre et du filtre concerné dans un fichier JSON que vous passerez à la fonction. Pour ce tutoriel je supposerai que les valeurs extremum prises par votre filtre sont dans une fichier config.json. Il vous suffira d’adapter votre code pour récupérer vos vraies valeurs extremum (par exemple avec une requête SQL sur votre base).

2/ Structure XML d’un workbook

Si vous ouvrez votre workbook avec votre éditeur de texte favori vous y verrez que ce n’est en fait qu’un grand fichier XML. Attention, évitez d’y mettre des images ou de la donnée sinon le format XML ne sera plus visible. Vous êtes libres de le parcourir des yeux si vous le souhaitez. En cherchant un peu avec ctrl+F, vous pourrez y voir des balises filter. Vous pouvez vous amuser à regarder ce à quoi elles ressemblent pour différent filtres. En voici un exemple :

Test.twb

<filter class='quantitative'
  column='[sqlproxy.1w1nkir129inkg16ts68k09rfbta].[none:Revenue:qk]'
included-values='in-range'>
    <min>0</min>
    <max>100.0</max>
</filter>

Comme vous le voyez, les valeurs extremums sont écrites « en dur » dans le fichier. Ce n’est donc pas étonnant que nous ayons à changer « à la main » ces valeurs lorsque les données changent ! Le filtre est comme figé au moment de la sauvegarde ! Essayez de modifier directement les valeurs que vous voyez. Après avoir sauvegardé le document puis ouvert avec Tableau, vous verrez que le filtre sera effectivement changé ! 🙂

Maintenant, place au code. Je me suis fixé l’objectif d’écrire un petit programme python qui mettra à jour vos filtres en moins de 50 lignes de code ! Gardez simplement en mémoire que ce ne sera pas la manière la plus robuste ni maintenable ou performante de réaliser cette opération. Je souhaite simplement écrire le code le plus synthétique possible, alors c’est parti !

2/ Fichier de configuration JSON

Avant de commencer, j’ai besoin d’un fichier de configuration JSON très simple qui contiendra les valeurs extremums pour mes filtres. Il sera du format suivant (il vous suffit de remplacer mes valeurs par les vôtres):

config.json

{
    "Date de découverte" : {
        "min" : "2017-01-01 00:00:00",
        "max" : "2018-04-20 00:00:00"
    },
    "Revenue" : {
        "min" : 0,
        "max" : 101
    }
}

Ici « Date de découverte » correspond à un champ date sur lequel on souhaite que notre code modifie les valeurs extrêmes. « Revenue » est à l’inverse un champ numérique (correspondant à la structure XML dans l’introduction). Il sera important que ces valeurs soient très exactement reproduites dans votre fichier de configuration personnel.

3/ Récupération de l’attribut nom

Comme vous le voyez, l’attribut column de la balise filter contient des informations sur la base de donnée concernée par cette colonne. Il nous faut donc extraire le nom avec un peu d’astuce ce que fait cette fonction :

#Récupérer nom du filtre
def getFilterName(filter):
    names = filter.attrib["column"]
    lst = names.split(":")
        return names[len(lst[0])+1 : -len(lst[-1])-1]

4/ Mise à jour des valeurs extrêmes

Ici, nous mettons à jour la valeur text des balises min et max de notre filtre. Nous nous basons sur la structure de config.json pour le faire :

#MAJ du filtre
def setExtremes(filter, config):
    if isinstance(config["min"], unicode) and isinstance(config["max"], unicode):
        texte = "#{0}#"
    elif type(config["min"]) == type(config["max"]):
        texte = "{0}"
    for key, value in config.iteritems():
        filter.find(key).text = texte.format(value)

Si vous regardez attentivement la structure d’un filtre par date dans le XML vous verrez que Tableau rajoute des # sur les champs dates. J’ai donc traité séparément le cas des filtres dates et des filtres numériques en me basant sur le type de donnée issue du JSON.

5/ Récursion

La fonction recursive nous permet de parcourir l’arbre XML en entier. Ce n’est probablement la manière la plus performante de faire car nous « scannons » ainsi la totalité du XML. Cependant c’est une méthode très synthétique, simple et récursive d’écrire du code 😉

#fonction récursive
def recursive(root, config):
    lst = [x for x in root if x.tag == "filter"]
    for filter in lst:
        try:
            setExtremes(filter, config[getFilterName(filter)])
        except Exception as e:
            pass
        for x in root:
            recursive(x, config)

6/ Résultat

Et voici le résultat complet ! Il vous suffit de changer le fichier de configuration avec vos valeurs ainsi que le nom du Workbook que vous souhaitez scanner. N’oubliez pas d’enregistrer ces trois fichiers dans le même dossier ! 🙂

filtres.py

# -*- coding: utf-8 -*-
import os, json
import xml.etree.ElementTree as ET
 
#Récupérer nom du filtre
def getFilterName(filter):
    names = filter.attrib["column"]
    lst = names.split(":")
    return names[len(lst[0])+1 : -len(lst[-1])-1]
 
#MAJ du filtre
def setExtremes(filter, config):
    if isinstance(config["min"], unicode) and isinstance(config["max"], unicode):
        texte = "#{0}#"
    elif type(config["min"]) == type(config["max"]):
        texte = "{0}"
    for key, value in config.iteritems():
        filter.find(key).text = texte.format(value)
 
#fonction récursive
def recursive(root, config):
    lst = [x for x in root if x.tag == "filter"]
    for filter in lst:
        try:
            setExtremes(filter, config[getFilterName(filter)])
        except Exception as e:
            pass
    for x in root:
        recursive(x, config)
 
#fonction principale
def main(name, obj):
    config = json.load(open('config.json'))
 
    # Chargement du XML
    with open("{0}.twb".format(name), 'r') as xml_file:
        e = ET.parse(xml_file)
        #récupération de la racine
        root = e.getroot()
 
        #parcours du XML
        recursive(root, config)
 
        #on sauvegarde !
        e.write(open("{0}_updated.twb".format(name), 'wb'), "UTF-8")
 
if __name__ == '__main__':
    main("Test", "config")

Ouvrez votre outil en ligne de commande favori et lancez la commande « python filtres.py ».

Et voilà ! Il vous suffit de lancer l’exécution du code pour voir l’apparition d’un nouveau Workbook avec les valeurs mises à jour pour les filtres que vous avez décidé. Changez les valeurs des filtres à loisir avec ce code. Il ne vous restera plus qu’à remplacer le fichier config.json par de « vrais valeurs » issues de votre base de donnée. Si vous utilisez Tableau Server ou Online il pourra être intéressant d’automatiser le téléchargement puis upload du fichier avec des scripts Python. Dites-moi dans les commentaires si c’est quelque chose que vous souhaiteriez voir abordé rapidement et je ferai un tutoriel sur le sujet prochainement ! 🙂
Bon week-end ! 🙂

2 thoughts on “Filtres Tableau dynamiques en 50 lignes de code python

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée.