Niveau
Moyen
Env.
Google Colab
Code
Python
Libs
jinja, pandas
Sources

Jinja amis des pandas ?

Introduction

Connaissez-vous Jinja ? personnellement j’en ai fait la connaissance quand je me suis amusé à créer des applications web avec Python Flask. En effet impossible (ou alors il faut être vraiment courageux et aimer faire de la ligne …) de passer à coté de cet outil de gestion template tant il est pratique et utile dans ce type d’usage.

Bon ok pour le développement de pages web, où en effet l’utilisation de template fait totalement sens. Mais lors du développement de pipelite je me suis interrogé aussi sur la possibilité d’intégrer cette capacité de masque/template sur des données directement. En bref serait-il possible d’utiliser des templates jinja sur des colonnes pour effectuer des transformations plutôt complexes ?

Cela pose donc plusieurs questions, la première étant peut-on appliquer ces templates jinja à des datasets ou plutôt des DataFrame Pandas ? On peut en effet dire que c’est une utilisation quelque peu détournée mais ça serait vraiment sympa d’utiliser toute la puissance de jinja sur des données directement n’est-ce pas ? étonnamment (ou pas) c’est plutôt simple et je vous propose de voir ensemble comme proceder, suivez le guide 😉

Utilisation basique de jinja

Evidemment et avant de commencer nous devons importer le package jinja :

Python
import pandas as pd
from jinja2 import Template

Je recommande bien sur pour tirer pleinement de la puissance de cet outil d’aller consulter la documentation ici

Commençons par quelque chose de très simple :

Python
t1 = Template('Hello, {{ firstname }} {{ familyname }}!')
print(t1.render(firstname='benoit', familyname="cayla"))

Tout d’abord (ligne 1) on créé le template ou le masque avec une syntaxe un peu particulière. ici les doubles accolades encadrant un nom de variables: {{ firstname }} précisent en fait les éléments qui vont devoir être remplacés par des valeurs concrètes (ce sont des « placeholders »).

La ligne 2 fait appel au template jinja mais lui précise les valeurs à remplacer (juste avant). on trouve donc naturellement les deux placeholders du template mais cette fois ci avec leur valeurs réelle.

Le résultat affiché est donc:

Hello, benoit cayla!

Simple n’est-ce pas ? on peut aussi aller plus loin et faire des opérations directement dans les placeholders jinja. Par exemple une concaténation de chaîne de caractères :

Python
t1 = Template('Hello, {{ firstname + " " + familyname }}!')
print(t1.render(firstname='benoit', familyname="cayla"))
Hello, benoit cayla!

Évidemment le résultat est exactement le même, mais cette fois ci la transformation a été calculée au préalable et non lors de l’affichage. Vous vous demandez peut être si on peut utiliser autre chose que des chaînes de caractères ? essayons avec des nombres, et une somme:

Python
t2 = Template('Somme = {{ n1 + n2 }}!')
print(t2.render(n1=1, n2=2))
Somme = 3!

Mais l’où je trouve que jinja est particulièrement intéressant c’est quand on veut y intégrer par exemple des conditions ou des boucles. Dans l’exemple suivant par exemple le résultat de la transformation sera différent selon que le nom de famille est renseigné ou pas:

Python
t = Template('Hello {% if familyname %}{{ firstname + " " + familyname }} {% else %}Hey {{ firstname }} what\'s your family name ?{% endif %}')
print(t.render(firstname='benoit', familyname="cayla"))
print(t.render(firstname='benoit'))
Hello benoit cayla 
Hello Hey benoit what's your family name ?

Bien sur il est possible d’utiliser les dictionnaires Python pour paramétrer les valeurs variables. Ca pourra être très pratique ultérieurement si on veut par exemple gérer une configuration séparée des valeurs:

Python
variables = {"firstname":'benoit',
             "familyname":"cayla"}
print(t1.render(variables))
Hello, benoit cayla!

Jinja & Pandas

Voyons maintenant comment on peut généraliser ce type de transformation à un DataFrame Pandas, c’est en effet tout l’objet de cet article n’est-ce pas ?

Commençons par un simple DataFrame et template Jinja tout aussi simple dont l’objectif va être de combiner (concaténer) les deux premières colonnes (col1 et col2):

Python
data = {'col1': ['a1', 'a2', 'a3'],
        'col2': ['b1', 'b2', 'b3'],
        'col3': ['c1', 'c2', 'c3']}
dataset = pd.DataFrame(data)
t1 = Template('[{{ col1 + "-" + col2 }}]')

La syntaxe du template est plutôt simple et logique et il suffit de réutiliser les noms de colonnes du DataFrame tels quels. Voyons le résultat:

Python
dataset["col1"] = dataset.apply(lambda r: t1.render(r), axis=1)
print(dataset)

Bien sur l’astuce est d’utiliser la méthode magique apply() qui va opérer sur chaque ligne de la colonne du DataFrame la transformation, le résultat est plutôt bluffant en 2 lignes :

      col1 col2 col3
0  [a1-b1]   b1   c1
1  [a2-b2]   b2   c2
2  [a3-b3]   b3   c3

Mixer DataFrame et constantes

On vient de voir commet il est simple d’utiliser un template Jina pour transformer des données d’un DataFrame. Mais parfois il est aussi nécessaire de combiner des données externes aux DataFrame (comme des constantes). Dans l’exemple précédent imaginons que l’on veuille rendre paramétrable le séparateur de concaténation.

En fait c’est tout aussi simple car il suffit d’ajouter un nouveau placeholder au template et le préciser dans la fonction render():

Python
data = {'col1': ['a1', 'a2', 'a3'],
        'col2': ['b1', 'b2', 'b3'],
        'col3': ['c1', 'c2', 'c3']}
dataset = pd.DataFrame(data)
t1 = Template('[{{ col1 + sep + col2 }}]')
dataset["col1"] = dataset.apply(lambda r: t1.render(r, sep = "/"), axis=1)
      col1 col2 col3
0  [a1/b1]   b1   c1
1  [a2/b2]   b2   c2
2  [a3/b3]   b3   c3

Maintenant si on veut gérer plusieurs variables externes au DataFrame et rendre tout celà paramétrable via des dictionnaires Python:

Python
constants = {"sep": "x"}
myTemplate = '[{{ col1 + sep + col2 }}]'
data = {'col1': ['a1', 'a2', 'a3'],
        'col2': ['b1', 'b2', 'b3'],
        'col3': ['c1', 'c2', 'c3']}
dataset = pd.DataFrame(data)
t1 = Template(myTemplate)
dataset["col1"] = dataset.apply(lambda r: t1.render(r.to_dict() | constants), axis=1)
      col1 col2 col3
0  [a1xb1]   b1   c1
1  [a2xb2]   b2   c2
2  [a3xb3]   b3   c3

Conclusion

Dans cet article je n’ai fait qu’effleurer les possibilités qu’offrent Jinja et Pandas mais je trouve que cette type usage est plutôt puissant notamment dés lors que l’on a besoin d’utiliser les conditions. En effet quand on fait de la transformation de données, c’est une capacité qui est souvent manquante ou qui parfois peut s’avérer complexe à mettre en œuvre simplement.

Partager cet article

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.