Comment utiliser Pandas avec Django ORM
Après presque une année d’absence pour des diverses raisons, je me suis dit qu’il est temps de retourner à mon blog et d’ajouter des nouveaux articles et surtout des tutoriels qu’on ne trouve pas facilement sur le net. Ceci dit, je vais vous parler aujourd’hui de comment faire pour utiliser Pandas et Django ORM pour aboutir aux memes résultats. Je tiens à noter qu’on va ignorer les points de différences et similitudes entre les deux outils et on va se consacrer uniquement, au moins dans ce tutoriel, à lire un document EXCEL (.XLSX), de remplir une base de donnée SQLite, de la lire et d’effecter quelques opérations avec les données enregistrés dans cette base de donnée.
Toutefois, avant de commencer, je tiens à vous inviter à créer vos environnements virtuels de Python pour ne pas altérer une autre applications que vous développer/utiliser. Et surtout, car c’est une bonne pratique de séparer les environnements :D .
Donc, commençons:
Création d’un projet Django:
──╼ [>] django-admin startproject django_pandas
──╼ [>] cd django_pandas
──╼ [>] python manage.py startapp example_project
Ensuite, on va modifier un peu la structure de notre projet Django. Mais avant tout, voici la structure des dossier et fichier qu’on devra avoir pour bien suivre ce tutoriel:
└──╼ [>] tree django_pandas/ -I "*.pyc|__pycache__"
django_pandas/
├── db.sqlite3
├── django_pandas
│ ├── __init__.py
│ ├── settings.py
│ └── wsgi.py
├── example_project
│ ├── apps.py
│ ├── dataset.xlsx
│ ├── __init__.py
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ └── __init__.py
│ ├── models.py
│ └── process.py
├── manage.py
└── requirements.txt
3 directories, 13 files
Ceci dit, notre fichier settings, au minimum, devra ressembler à cet exemple:
#
# Settings file
#
import os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'SECRET_KEY'
# Application definition
INSTALLED_APPS = [
'example_project'
]
WSGI_APPLICATION = 'django_pandas.wsgi.application'
# Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
Ensuite nos modèles sont:
#
# Déclaration des modèles de l'application example_project
#
from django.db import models
class Manager(models.Model):
"""Chaque employé a un manager"""
name = models.CharField(max_length=200, verbose_name='Manager')
def __str__(self):
return self.name
class Meta:
db_table = 'manager'
verbose_name = 'Manager'
verbose_name_plural = 'Managers'
class Job(models.Model):
"""Chaque développeur a un job"""
name = models.CharField(max_length=200, verbose_name='Job')
def __str__(self):
return self.name
class Meta:
db_table = 'job'
verbose_name = 'Job'
verbose_name_plural = 'Jobs'
class Developer(models.Model):
"""Model des développeurs"""
name = models.CharField(max_length=200, verbose_name='Name')
age = models.IntegerField(default=0, verbose_name='Age')
job = models.ForeignKey(
Job,
verbose_name='Job',
related_name='dev_job',
null=True,
blank=True,
on_delete=models.DO_NOTHING
)
manager = models.ForeignKey(
Manager,
verbose_name='Manager',
related_name='dev_manager',
null=True,
blank=True,
on_delete=models.DO_NOTHING
)
date = models.DateField(verbose_name='Date')
def __str__(self):
return '{dev_name} - {job} <{manager}>'.format(
dev_name=self.name,
job=self.job,
manager=self.manager
)
class Meta:
db_table = 'developer'
verbose_name = 'Developer'
verbose_name_plural = 'Developers'
Ensuite, on va créer notre premier fichier de migrations:
──╼ [>] python manage.py makemigrations
Migrations for 'example_project':
example_project/migrations/0001_initial.py
- Create model Job
- Create model Manager
- Create model Developer
Et évidemment, appliquer ces changements dans la base de donnée
──╼ [>] python manage.py migrate
Operations to perform:
Apply all migrations: example_project
Running migrations:
Applying example_project.0001_initial... OK
Et pour un meilleur debug, il est préférable d’installer SQLite3-cli via:
──╼ [>] sudo apt install sqlite3
Maintenant, si on cherche si la migrations a bien eu lieu. On peut entrer dans le Database Shell
que Django nous offre et voir les tableau (Ou bien exécuter une requete avec SQLite3 et vous devrez avoir les memes résultats)
──╼ [>] python manage.py dbshell
SQLite version 3.11.0 2016-02-15 17:29:24
Enter ".help" for usage hints.
sqlite> .tables
developer django_migrations job manager
sqlite>
Et si on essaie d’exécuter des requetes sur les tables de cette base de donnée, on va remarquer que notre table est vide. (Bien sur car on n’a rien insérer jusqu’au moment):
sqlite> select * from developer;
sqlite> select * from manager;
sqlite> select * from job;
sqlite>
Les memes résultats avec Django Shell
──╼ [>] python manage.py shell
Python 3.6.8 (default, May 7 2019, 14:58:50)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from example_project import models
>>> models.Manager.objects.all()
<QuerySet []>
>>> models.Job.objects.all()
<QuerySet []>
>>> models.Developer.objects.all()
<QuerySet []>
>>>
Comment lire un document EXCEL (.XLSX) avec Python?
Pour répondre à cette question, j’ai ajouté un document EXCEL dans le repository du tutoriel sur github qui comporte 4 colonnes avec des données.
Et pour lire un document EXCEL avec Python, il y a plusieurs paquets qui nous facilitent la vie tel:
openpyxl
: Qui permet de lire et écrire sur un document EXCEL 2010 (.xlsx)xlswriter
: Comment sont nom l’indique c’est seulement pour l’écriture dans des documents EXCEL 2010xlrd
: Qui permet de lire et écrire sur les anciennces versions des documents EXCELxlwt
: commentxlswriter
mais seulement pour les anciennes versions des documents EXCEL
Par contre dans ce tutoriel on va utiliser openpyxl
car on va travailler avec une récentre version de LibreOffice
qui peut générer des documents EXCEL compatibles avec EXCEL 2003/2010
… Donc, voici une fonction qui permet de tout lire dans un tableur de document EXCEL. En théorie, ça permet de lire toute dans le document EXCEL, mais je n’ai pas creusé beaucoup avec les différentes formes qu’un document EXCEL peut avoir pour générer une fonction qui peut tout lire. Mais, si votre documents EXCEL est composé par des colonnes et des lignes, cette fonction fera l’affaire:
import openpyxl
def read_excel_file(file_path='', sheet_name='Sheet1'):
"""On va lire le document EXCEL avec openpyxl
NB: On peut le lire directement avec Pandas
Mais je préfère le lire avec openpyxl ...
Parce que je veux controler ce que je fais :D"""
wb = openpyxl.load_workbook(file_path)
ws = wb[sheet_name]
# Balayer les colonnes une par une et générer des sous listes
# Qui vont contenir les noms des colonnes (première position)
# ensuite chaque transposer les données des colonnes
# pour créer des sous listes qui vont contenir les données de chaque ligne
# sous forme d'une sous liste de la liste finale
# Ceci dit:
# colonnes = values[0]
# lignes = values[1:]
values = [[k.value for k in elm] for elm in zip(*ws.columns)]
return values
Et pour insérer les données dans la base de données en utilisant Django ORM:
from django.db.models import transaction
from example_project import models
def insert_data(values):
"""Insertion des données dans la database"""
with transaction.atomic():
for name, age, job_name, date, manager_name in values:
manager, _ = models.Manager.objects.get_or_create(
name=manager_name
)
job, _ = models.Job.objects.get_or_create(name=job_name)
developer, _ = models.Developer.objects.get_or_create(
manager=manager,
job=job,
name=name,
age=age,
date=date
)
print(developer)
NB: On doit ajouter DJANGO_SETTINGS_MODULE
path à nos variables d’environnement.
Et l’exécution de cette fonction va générer comme output:
──╼ [>] python process.py
Alex - Python developer <Chiheb>
Jean - Go developer <Chiheb>
Janet - PHP developer <Ahmed>
Ali - Python developer <Chiheb>
Comment générer un DataFrame avec les données de la base de données?
Pour que ça soit possible, on doit installer Pandas
en premier lieu, ensuite, on doit lire les données de notre base de données pour au final créer un DataFrame.
Pour lire les données de notre base de donnée:
from example_project import models
def read_data():
"""Read data from Database"""
# Ceci sont les noms des colonnes de notre base de données
names = [
'id',
'name',
'age',
'job__name', # Ceci permet à Django ORM d'accèder au contenu du ForeignKey
'manager__name', # Permet à django ORM d'accèder au contenu du ForeignKey
'date'
]
# retourner une liste contenant par ordre les valeurs des noms de colonnes insérés
return models.Developer.objects.all().values_list(*names)
Ensuite, pour créer un DataFrame des données lues de la base de données on peut faire:
from example_project import models
import pandas as pd
def create_df(data):
"""Create DataFrame from database"""
# Ceci permet de générer une liste des verbose_name qu'on a utilisé
# Dans la définition de nos models dans l'ORM de Django
# Et dans notre cas, ces verbose_name sont identiques aux noms
# de colonnes du document EXCEL
column_names = [
field.verbose_name for field in models.Developer._meta.fields
]
return pd.DataFrame(
data,
columns=column_names
)
Et si on affiche le contenu du DataFrame on aura cette résultat:
──╼ [>] python process.py
ID Name Age Job Manager Date
0 1 Alex 22 Python developer Chiheb 2019-01-05
1 2 Jean 24 Go developer Chiheb 2019-03-04
2 3 Janet 23 PHP developer Ahmed 2019-02-11
3 4 Ali 25 Python developer Chiheb 2018-04-03
Faire des opérations sur la base de données et sur le DataFrame:
Arrivons, donc, au choses sérieuses. On va essayer de faire des opérations de calcul sur la base de données et on va essayer de faire la meme chose avec le DataFrame généré:
voici des exemples:
from django.db.models import Avg, Sum
from example_project import models
def pprint(dataframe=None, database=None, field='', header=''):
"""Pretty print data"""
print('#' * 5, header, '#' * 5)
if database:
print("""Database: {field}: {data}""".format(
field=field,
data=database.get(field)
))
if dataframe:
print("""DataFrame: {field}: {data}""".format(
field=field,
data=dataframe
))
print()
def operations(df):
"""Faire les memes opérations sur la database et sur le DataFrame"""
# Average age
db_avg = models.Developer.objects.all().aggregate(
average=Avg('age')
)
df_avg = df['Age'].mean()
pprint(
dataframe=df_avg,
database=db_avg,
field='average',
header='Average Age'
)
# Sum age
db_sum = models.Developer.objects.all().aggregate(
sum=Sum('age')
)
df_sum = df['Age'].sum()
pprint(
dataframe=df_sum,
database=db_sum,
field='sum',
header='Sum Age'
)
# Average age uniquement pour Manager == Chiheb
db_avg_chiheb = models.Developer.objects.filter(
manager__name='Chiheb'
).aggregate(
average=Avg('age')
)
df_avg_chiheb = df.loc[df['Manager'].isin(['Chiheb'])]['Age'].mean()
pprint(
dataframe=df_avg_chiheb,
database=db_avg_chiheb,
field='average',
header='Average only chiheb'
)
# Average age uniquement pour job = Python developer
db_avg_python = models.Developer.objects.filter(
job__name='Python developer'
).aggregate(
average=Avg('age')
)
df_avg_python = df.loc[df['Job'].isin(['Python developer'])]['Age'].mean()
pprint(
dataframe=df_avg_python,
database=db_avg_python,
field='average',
header='Average Age only Python developer'
)
Et on lance la fonction operations
on aura comme affichage:
└──╼ [>] python process.py
##### Average Age #####
Database: average: 23.5
DataFrame: average: 23.5
##### Sum Age #####
Database: sum: 94
DataFrame: sum: 94
##### Average only chiheb #####
Database: average: 23.666666666666668
DataFrame: average: 23.666666666666668
##### Average Age only Python developer #####
Database: average: 23.5
DataFrame: average: 23.5
Bien sur, on peut faire des opérations plus compliquées que ce qu’on a fait. Mais l’essentiel c’est de noter que ces deux outils sont hyper robustes et peuvent nous faire gagner beaucoup de temps lors de nos taches journalières.
Et pour ne pas oublier, voici l’URL de la repository github du tutoriel avec le fichier requirements, le fichier EXCEL et la base de donnée SQLite que j’ai utilisé pour l’élaboration des ces exemples.
Au plaisir de vous écrire une prochaine fois.
Commentaires