{"id":3347,"date":"2022-03-27T21:49:38","date_gmt":"2022-03-27T21:49:38","guid":{"rendered":"https:\/\/akyalab.com\/?p=3347"},"modified":"2022-03-27T22:24:37","modified_gmt":"2022-03-27T22:24:37","slug":"create-an-etl-pipeline-in-python-with-pandas","status":"publish","type":"post","link":"https:\/\/akyalab.com\/fr\/create-an-etl-pipeline-in-python-with-pandas\/","title":{"rendered":"Cr\u00e9er un pipeline ETL en Python avec Pandas"},"content":{"rendered":"<p id=\"91e8\">Un pipeline ETL (extraire, transformer, charger) est un type de flux de travail fondamental dans l'ing\u00e9nierie des donn\u00e9es. L'objectif est de prendre des donn\u00e9es qui pourraient \u00eatre non structur\u00e9es ou difficiles \u00e0 utiliser ou \u00e0 acc\u00e9der et de servir une source de donn\u00e9es propres et structur\u00e9es.<\/p>\n\n\n\n<p id=\"e816\">Il est \u00e9galement tr\u00e8s simple et facile de cr\u00e9er un pipeline simple en tant que script Python. Le code source complet de cet exercice est <a rel=\"noreferrer noopener\" href=\"https:\/\/github.com\/habibdraft\/tmdb\/blob\/main\/tmdb.py\" target=\"_blank\">ici<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"1d52\">Qu'est-ce qu'un pipeline ETL ?<\/h3>\n\n\n\n<p id=\"7486\">Un pipeline ETL se compose de trois composants g\u00e9n\u00e9raux :<\/p>\n\n\n\n<p id=\"4667\"><strong>Extraire<\/strong> - obtenir des donn\u00e9es \u00e0 partir d'une source telle qu'une API. Dans cet exercice, nous n'extraireons les donn\u00e9es qu'une seule fois pour montrer comment c'est fait. Dans de nombreux cas, nous devrons interroger l'API \u00e0 intervalles r\u00e9guliers pour obtenir de nouvelles donn\u00e9es (appel\u00e9es batch), ce que nous faisons en cr\u00e9ant des workflows ETL planifi\u00e9s. Nous pouvons \u00e9galement travailler avec des sources de donn\u00e9es en continu (continues) que nous g\u00e9rons via des syst\u00e8mes de messagerie.<\/p>\n\n\n\n<p id=\"14e0\"><strong>Transformer<\/strong> \u2014 structurer, formater ou nettoyer les donn\u00e9es, en fonction de la raison pour laquelle nous en avons besoin et de la mani\u00e8re dont elles doivent \u00eatre livr\u00e9es. La transformation des donn\u00e9es peut se produire \u00e0 de nombreuses \u00e9tapes du cycle de vie des donn\u00e9es, et diff\u00e9rents utilisateurs peuvent avoir besoin de les transformer pour diff\u00e9rentes raisons. Dans cet exercice, nous allons formater les donn\u00e9es des r\u00e9ponses JSON aux dataframes pandas et les \u00e9crire au format CSV.<\/p>\n\n\n\n<p id=\"2234\"><strong>Charger<\/strong>\u2014 \u00e9crire les donn\u00e9es dans une destination externe o\u00f9 elles peuvent \u00eatre utilis\u00e9es par une autre application. Dans cet exercice, nous allons \u00e9crire chaque tableau que nous cr\u00e9ons au format CSV. Dans un pipeline de donn\u00e9es r\u00e9el, nous \u00e9crivions dans des bases de donn\u00e9es ou d'autres magasins de donn\u00e9es, stockions nos fichiers dans diff\u00e9rents formats ou envoyions des donn\u00e9es \u00e0 diff\u00e9rents endroits dans le monde.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"d4b3\">Commencer<\/h3>\n\n\n\n<p id=\"3d6d\">Nous allons demander des enregistrements \u00e0 l'API The Movie Database. Avant de commencer, vous devrez obtenir une cl\u00e9 API pour faire des demandes. Vous pouvez trouver les instructions pour obtenir une cl\u00e9 <a href=\"https:\/\/developers.themoviedb.org\/3\/getting-started\/authentication\" rel=\"noreferrer noopener\" target=\"_blank\">ici<\/a>.<\/p>\n\n\n\n<p id=\"a49f\">Une fois que vous avez votre cl\u00e9, vous devrez vous assurer de ne pas la mettre directement dans votre code source. Un moyen rapide de le faire est de cr\u00e9er un fichier appel\u00e9 <code>config.py<\/code> dans le m\u00eame r\u00e9pertoire que vous allez cr\u00e9er votre script ETL. Mettez ceci dans le fichier :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#config.py\napi_key = &lt;YOUR API KEY HERE&gt;<\/code><\/pre>\n\n\n\n<p id=\"f0e0\">Si vous publiez votre code n'importe o\u00f9, vous devez mettre votre <code>config.py<\/code> dans un fichier .gitignore ou similaire pour s'assurer qu'il n'est pas pouss\u00e9 vers des r\u00e9f\u00e9rentiels distants. Vous pouvez \u00e9galement stocker la cl\u00e9 API en tant que variable d'environnement ou utiliser une autre m\u00e9thode pour la masquer. L'objectif est de le prot\u00e9ger contre l'exposition dans le script ETL.<\/p>\n\n\n\n<p id=\"0492\">Cr\u00e9ez maintenant un fichier appel\u00e9 <code>tmdb.py<\/code> et importer les \u00e9l\u00e9ments n\u00e9cessaires.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import pandas as pd\nimport requests\nimport config<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"822b\">Extraire<\/h3>\n\n\n\n<p id=\"107a\">Voici comment nous envoyons une seule requ\u00eate GET \u00e0 l'API. Dans la r\u00e9ponse, nous recevons un enregistrement JSON avec le <code>movie_id<\/code> nous pr\u00e9cisons :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>API_KEY = config.api_key\nurl = 'https:\/\/api.themoviedb.org\/3\/movie\/{}?api_key={}'.format(movie_id, API_KEY)\n\nr = requests.get(url)<\/code><\/pre>\n\n\n\n<p>Pour cet exercice, nous allons demander 6 films avec <code>movie_id<\/code> allant de 550 \u00e0 555. Nous cr\u00e9ons une boucle qui demande chaque film un par un et ajoute la r\u00e9ponse \u00e0 une liste.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>response_list = &#91;]\nAPI_KEY = config.api_key\n\nfor movie_id in range(550,556): \n  url = 'https:\/\/api.themoviedb.org\/3\/movie\/{}?api_key={}'.format(movie_id, API_KEY)\n  r = requests.get(url)\n  response_list.append(r.json())<\/code><\/pre>\n\n\n\n<p>Nous avons maintenant une liste d'enregistrements JSON longs et peu maniables qui nous sont livr\u00e9s \u00e0 partir de l'API. Cr\u00e9ez une base de donn\u00e9es pandas \u00e0 partir des enregistrements \u00e0 l'aide de <code>from_dict()<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>df = pd.DataFrame.from_dict(response_list)<\/code><\/pre>\n\n\n\n<p>Si tout se passe bien, vous devriez avoir un dataframe proprement format\u00e9 avec 6 lignes et 38 colonnes.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"1010\" height=\"550\" src=\"http:\/\/akyalab.com\/wp-content\/uploads\/2022\/03\/1-wK_7p_lbn8e8-Lz6jvOOUw.png\" alt=\"\" class=\"wp-image-3349\" srcset=\"https:\/\/akyalab.com\/wp-content\/uploads\/2022\/03\/1-wK_7p_lbn8e8-Lz6jvOOUw.png 1010w, https:\/\/akyalab.com\/wp-content\/uploads\/2022\/03\/1-wK_7p_lbn8e8-Lz6jvOOUw-300x163.png 300w, https:\/\/akyalab.com\/wp-content\/uploads\/2022\/03\/1-wK_7p_lbn8e8-Lz6jvOOUw-768x418.png 768w, https:\/\/akyalab.com\/wp-content\/uploads\/2022\/03\/1-wK_7p_lbn8e8-Lz6jvOOUw-18x10.png 18w\" sizes=\"(max-width: 1010px) 100vw, 1010px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"b32c\">Transformer<\/h3>\n\n\n\n<p id=\"a5fc\">Nous n'allons pas travailler avec toutes ces colonnes dans cet ensemble de donn\u00e9es, alors s\u00e9lectionnons celles que nous allons utiliser. Voici ceux qui nous int\u00e9ressent pour cet exercice :<\/p>\n\n\n\n<ul><li><code>budget<\/code><\/li><li><code>id<\/code><\/li><li><code>imdb_id<\/code><\/li><li><code>genres<\/code><\/li><li><code>original_title<\/code><\/li><li><code>release_date<\/code><\/li><li><code>revenue<\/code><\/li><li><code>runtime<\/code><\/li><\/ul>\n\n\n\n<p id=\"be3f\">Nous cr\u00e9ons une liste de noms de colonnes appel\u00e9e <code>df_columns<\/code> qui nous permet de s\u00e9lectionner les colonnes que nous voulons \u00e0 partir de la base de donn\u00e9es principale.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>df_columns = &#91;'budget', 'genres', 'id', 'imdb_id', 'original_title', 'release_date', 'revenue', 'runtime']<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"f69f\">Travailler avec des colonnes cat\u00e9gorielles<\/h2>\n\n\n\n<p id=\"8f3d\">Travailler avec des colonnes cat\u00e9gorielles <code>genres<\/code> la colonne ressemble \u00e0 :<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full is-resized\"><img decoding=\"async\" loading=\"lazy\" src=\"http:\/\/akyalab.com\/wp-content\/uploads\/2022\/03\/1-daXmyU41isbj4UflCGWK-A.png\" alt=\"\" class=\"wp-image-3410\" width=\"61\" height=\"474\"\/><\/figure><\/div>\n\n\n\n<p id=\"4a00\">Il s'agit d'une colonne de listes d'enregistrements JSON, difficile \u00e0 lire ou \u00e0 comprendre rapidement dans ce format. Nous souhaitons d\u00e9velopper cette colonne afin de pouvoir facilement voir et utiliser les enregistrements internes.<\/p>\n\n\n\n<p id=\"9851\">Une fa\u00e7on de proc\u00e9der consiste \u00e0 exploser la colonne de listes en colonnes cat\u00e9gorielles uniques. Cela se fait en cr\u00e9ant une seule colonne pour chaque valeur cat\u00e9gorielle et en d\u00e9finissant la valeur de la ligne sur 1 si le film appartient \u00e0 cette cat\u00e9gorie et sur 0 si ce n'est pas le cas. Nous en verrons les r\u00e9sultats sous peu.<\/p>\n\n\n\n<p id=\"5519\">Nous n'utiliserons que le <code>name<\/code> la propri\u00e9t\u00e9, pas le <code>id<\/code>. Nous pourrons acc\u00e9der au <code>id<\/code> valeur dans un tableau s\u00e9par\u00e9.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>genres_list = df&#91;'genres'].tolist()\nflat_list = &#91;item for sublist in genres_list for item in sublist]<\/code><\/pre>\n\n\n\n<p>Nous voulons cr\u00e9er un tableau s\u00e9par\u00e9 pour les genres et une colonne de listes \u00e0 exploser. Nous allons cr\u00e9er une colonne temporaire appel\u00e9e <code>genres_all<\/code> comme une liste de listes de genres que nous pouvons ensuite d\u00e9velopper dans une colonne distincte pour chaque genre.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>result = &#91;]\nfor l in genres_list:\n    r = &#91;]\n    for d in l:\n        r.append(d&#91;'name'])\n    result.append(r)\ndf = df.assign(genres_all=result)<\/code><\/pre>\n\n\n\n<p>Voici o\u00f9 nous cr\u00e9ons la table des genres :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>df_genres = pd.DataFrame.from_records(flat_list).drop_duplicates()<\/code><\/pre>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"162\" height=\"295\" src=\"http:\/\/akyalab.com\/wp-content\/uploads\/2022\/03\/2-zBUtr2aXUhtUWkKR023R7Q.png\" alt=\"\" class=\"wp-image-3407\" srcset=\"https:\/\/akyalab.com\/wp-content\/uploads\/2022\/03\/2-zBUtr2aXUhtUWkKR023R7Q.png 162w, https:\/\/akyalab.com\/wp-content\/uploads\/2022\/03\/2-zBUtr2aXUhtUWkKR023R7Q-7x12.png 7w\" sizes=\"(max-width: 162px) 100vw, 162px\" \/><\/figure><\/div>\n\n\n\n<p id=\"58c5\">Cela nous donne un tableau des propri\u00e9t\u00e9s de genre <code>name<\/code> et <code>id<\/code>. We attach the list of genre names onto our <code>df_columns<\/code> list as shown below and remove the original <code>genres<\/code> column from the list.<\/p>\n\n\n\n<p id=\"f00d\">Les deux derni\u00e8res lignes sont celles o\u00f9 nous utilisons le <code>explode()<\/code> et <code>crosstab()<\/code> fonctions pour cr\u00e9er les colonnes de genre et les joindre sur la table principale. Cela prend une colonne de listes et la transforme en un ensemble de colonnes de valeurs de fr\u00e9quence.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>df_columns = &#91;'budget', 'id', 'imdb_id', 'original_title', 'release_date', 'revenue', 'runtime']\ndf_genre_columns = df_genres&#91;'name'].to_list()\ndf_columns.extend(df_genre_columns)\n\ns = df&#91;'genres_all'].explode()\ndf = df.join(pd.crosstab(s.index, s))<\/code><\/pre>\n\n\n\n<p>Nous avons maintenant les colonnes suivantes dans notre table de films :<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" loading=\"lazy\" width=\"1024\" height=\"268\" src=\"http:\/\/akyalab.com\/wp-content\/uploads\/2022\/03\/3-WvWF2e7Zfc2UNVI4KmJQJg-1024x268.png\" alt=\"\" class=\"wp-image-3408\" srcset=\"https:\/\/akyalab.com\/wp-content\/uploads\/2022\/03\/3-WvWF2e7Zfc2UNVI4KmJQJg-1024x268.png 1024w, https:\/\/akyalab.com\/wp-content\/uploads\/2022\/03\/3-WvWF2e7Zfc2UNVI4KmJQJg-300x79.png 300w, https:\/\/akyalab.com\/wp-content\/uploads\/2022\/03\/3-WvWF2e7Zfc2UNVI4KmJQJg-768x201.png 768w, https:\/\/akyalab.com\/wp-content\/uploads\/2022\/03\/3-WvWF2e7Zfc2UNVI4KmJQJg-18x5.png 18w, https:\/\/akyalab.com\/wp-content\/uploads\/2022\/03\/3-WvWF2e7Zfc2UNVI4KmJQJg.png 1400w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Remarquez les colonnes de genre \u00e0 droite. Si un film appartient \u00e0 un genre, la valeur de ligne est 1, et sinon, la valeur est 0. Il est maintenant facile pour nous de filtrer sur des genres sp\u00e9cifiques et de dire rapidement si un film appartient \u00e0 un genre ou non.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"62ee\">Travailler avec les dates-heures<\/h2>\n\n\n\n<p id=\"dd6e\">Enfin, nous allons d\u00e9velopper la colonne datetime dans un tableau. Pandas a des fonctions int\u00e9gr\u00e9es pour extraire des parties sp\u00e9cifiques d'une date\/heure. Remarquez que nous devons convertir le <code>release_date<\/code> colonne dans un datetime d'abord.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>df&#91;'release_date'] = pd.to_datetime(df&#91;'release_date'])\ndf&#91;'day'] = df&#91;'release_date'].dt.day\ndf&#91;'month'] = df&#91;'release_date'].dt.month\ndf&#91;'year'] = df&#91;'release_date'].dt.year\ndf&#91;'day_of_week'] = df&#91;'release_date'].dt.day_name()\ndf_time_columns = &#91;'id', 'release_date', 'day', 'month', 'year', 'day_of_week']<\/code><\/pre>\n\n\n\n<p>Voici la trame de donn\u00e9es d\u00e9finie par ce code :<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"357\" height=\"249\" src=\"http:\/\/akyalab.com\/wp-content\/uploads\/2022\/03\/4-UU4KT4L-hZ2hyuoys_SUSQ.png\" alt=\"\" class=\"wp-image-3409\" srcset=\"https:\/\/akyalab.com\/wp-content\/uploads\/2022\/03\/4-UU4KT4L-hZ2hyuoys_SUSQ.png 357w, https:\/\/akyalab.com\/wp-content\/uploads\/2022\/03\/4-UU4KT4L-hZ2hyuoys_SUSQ-300x209.png 300w, https:\/\/akyalab.com\/wp-content\/uploads\/2022\/03\/4-UU4KT4L-hZ2hyuoys_SUSQ-18x12.png 18w\" sizes=\"(max-width: 357px) 100vw, 357px\" \/><\/figure><\/div>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"7b3f\">Charger<\/h3>\n\n\n\n<p id=\"2a8d\">Nous avons fini par cr\u00e9er 3 tables pour le <code>tmdb<\/code> sch\u00e9ma que nous appellerons <code>movies<\/code>, <code>genres<\/code>et <code>datetimes<\/code>. Nous exportons nos tables en les \u00e9crivant dans un fichier. Cela cr\u00e9era 3 fichiers .csv dans le m\u00eame r\u00e9pertoire que notre script.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>df&#91;df_columns].to_csv('tmdb_movies.csv', index=False)\ndf_genres.to_csv('tmdb_genres.csv', index=False)\ndf&#91;df_time_columns].to_csv('tmdb_datetimes.csv', index=False)<\/code><\/pre>\n\n\n\n<p>C'est \u00e7a! Nous avons cr\u00e9\u00e9 notre premier pipeline ETL. Nous avons construit un sch\u00e9ma structur\u00e9 \u00e0 partir d'une liste d'enregistrements JSON et transform\u00e9 nos donn\u00e9es dans un format propre et utilisable. Vous pouvez exp\u00e9rimenter de nombreuses fa\u00e7ons diff\u00e9rentes d'explorer et de structurer ces donn\u00e9es, car nous n'avons fait qu'effleurer la surface ici et utilis\u00e9 une petite partie de ce dont nous disposions.<\/p>","protected":false},"excerpt":{"rendered":"<p>Un pipeline ETL (extraire, transformer, charger) est un type de flux de travail fondamental dans l'ing\u00e9nierie des donn\u00e9es. L'objectif est de prendre des donn\u00e9es qui pourraient \u00eatre non structur\u00e9es ou difficiles \u00e0 utiliser ou \u00e0 acc\u00e9der et de servir une source de donn\u00e9es propres et structur\u00e9es. Il est \u00e9galement tr\u00e8s simple et facile de cr\u00e9er un pipeline simple en tant que script Python. Le [\u2026]<\/p>","protected":false},"author":3,"featured_media":3350,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0},"categories":[94,91],"tags":[],"_links":{"self":[{"href":"https:\/\/akyalab.com\/fr\/wp-json\/wp\/v2\/posts\/3347"}],"collection":[{"href":"https:\/\/akyalab.com\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/akyalab.com\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/akyalab.com\/fr\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/akyalab.com\/fr\/wp-json\/wp\/v2\/comments?post=3347"}],"version-history":[{"count":4,"href":"https:\/\/akyalab.com\/fr\/wp-json\/wp\/v2\/posts\/3347\/revisions"}],"predecessor-version":[{"id":3413,"href":"https:\/\/akyalab.com\/fr\/wp-json\/wp\/v2\/posts\/3347\/revisions\/3413"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/akyalab.com\/fr\/wp-json\/wp\/v2\/media\/3350"}],"wp:attachment":[{"href":"https:\/\/akyalab.com\/fr\/wp-json\/wp\/v2\/media?parent=3347"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/akyalab.com\/fr\/wp-json\/wp\/v2\/categories?post=3347"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/akyalab.com\/fr\/wp-json\/wp\/v2\/tags?post=3347"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}