Les nouveaux modes d’accès aux données : le format parquet et les données sur le cloud

Tutoriel
Avancé
Author

Lino Galiana

Published

2024-04-12

Ce chapitre est une introduction à la question du stockage des données et aux innovations récentes dans ce domaine. L’objectif est d’abord de présenter les avantages du format Parquet et la manière dont on peut utiliser les librairies pyarrow ou duckdb pour traiter de manière efficace des données volumineuses au format Parquet. Ensuite, on présentera la manière dont ce format parquet s’intègre bien avec des systèmes de stockage cloud, qui tendent à devenir la norme dans le monde de la data science.

Elements de contexte

Principe du stockage de la donnée

Pour comprendre les apports du format Parquet, il est nécessaire de faire un détour pour comprendre la manière dont une information est stockée et accessible à un langage de traitement de la donnée.

Il existe deux approches dans le monde du stockage de la donnée. La première est celle de la base de données relationnelle. La seconde est le principe du fichier. La différence entre les deux est dans la manière dont l’accès aux données est organisé.

Les fichiers

Dans un fichier, les données sont organisées selon un certain format et le logiciel de traitement de la donnée va aller chercher et structurer l’information en fonction de ce format. Par exemple, dans un fichier .csv, les différentes informations seront stockées au même niveau avec un caractère pour les séparer (la virgule , dans les .csv anglosaxons, le point virgule dans les .csv français, la tabulation dans les .tsv). Le fichier suivant

nom ; profession 
Astérix ; 
Obélix ; Tailleur de menhir ;
Assurancetourix ; Barde

sera ainsi organisé naturellement sous forme tabulée par Python

nom profession
0 Astérix
1 Obélix Tailleur de menhir
2 Assurancetourix Barde

A propos des fichiers de ce type, on parle de fichiers plats car les enregistrements relatifs à une observation sont stockés ensemble, sans hiérarchie.

Certains formats de données vont permettre d’organiser les informations de manière différente. Par exemple, le format JSON va hiérarchiser différemment la même information [^1]:

[
  {
    "nom": "Astérix"
  },
  {
    "nom": "Obélix",
    "profession": "Tailleur de menhir"
  },
  {
    "nom": "Assurancetourix",
    "profession": "Barde"
  }
]

Cette fois, quand on n’a pas d’information, on ne se retrouve pas avec nos deux séparateurs accolés (cf. la ligne “Astérix”) mais l’information n’est tout simplement pas collectée.

Un concept supplémentaire dans le monde du fichier est celui du file system. Le file system est le système de localisation et de nommage des fichiers. Pour simplifier, le file system est la manière dont votre ordinateur saura retrouver, dans son système de stockage, les bits présents dans tel ou tel fichier appartenant à tel ou tel dossier.

Les bases de données

La logique des bases de données est différente. Elle est plus systémique. Un système de gestion de base de données (Database Management System) est un logiciel qui gère à la fois le stockage d’un ensemble de données reliée, permet de mettre à jour celle-ci (ajout ou suppression d’informations, modification des caractéristiques d’une table…) et qui gère également les modalités d’accès à la donnée (type de requête, utilisateurs ayant les droits en lecture ou en écriture…).

La relation entre les entités présentes dans une base de données prend généralement la forme d’un schéma en étoile. Une base va centraliser les informations disponibles qui seront ensuite détaillées dans des tables dédiées.

Source: La documentation Databricks sur le schéma en étoile

Le logiciel associé à la base de données fera ensuite le lien entre ces tables à partir de requêtes SQL. L’un des logiciels les plus efficaces dans ce domaine est PostgreSQL. Python est tout à fait utilisable pour passer une requête SQL à un gestionnaire de base de données. Les packages sqlalchemy et psycopg2 peuvent servir à utiliser PostgreSQL pour requêter une base de donnée ou la mettre à jour.

La logique de la base de données est donc très différente de celle du fichier. Ces derniers sont beaucoup plus légers pour plusieurs raisons. D’abord, parce qu’ils sont moins adhérents à un logiciel gestionnaire. Là où le fichier ne nécessite, pour la gestion, qu’un file system, installé par défaut sur tout système d’exploitation, une base de données va nécessiter un logiciel spécialisé. L’inconvénient de l’approche fichier, sous sa forme standard, est qu’elle ne permet pas une gestion fine des droits d’accès et amène généralement à une duplication de la donnée pour éviter que la source initiale soit ré-écrite (involontairement ou de manière intentionnelle par un utilisateur malveillant). Résoudre ce problème est l’une des innovations des systèmes cloud, sur lesquelles nous reviendrons en évoquant le système S3. Un deuxième inconvénient de l’approche base de données par rapport à l’approche fichier, pour un utilisateur de Python, est que les premiers nécessitent l’intermédiation du logiciel de gestion de base de données là où, dans le second cas, on va se contenter d’une librairie, donc un système beaucoup plus léger, qui sait comment transformer la donnée brute en DataFrame. Pour ces raisons, entre autres, les bases de données sont donc moins à la mode dans l’écosystème récent de la data science que les fichiers.

Le format parquet

Le format CSV a rencontré un grand succès par sa simplicité : il est lisible par un humain (un bloc-note suffit pour l’ouvrir et apercevoir les premières lignes), sa nature plate lui permet de bien correspondre au concept de données tabulées sans hiérarchie qui peuvent être rapidement valorisées, il est universel (il n’est pas adhérent à un logiciel). Cependant, le CSV présente plusieurs inconvénients qui justifient l’émergence d’un format concurrent :

  • le CSV est un format lourd car les informations ne sont pas compressées (ce qui le rend lisible facilement depuis un bloc-note) mais aussi parce que toutes les données sont stockées de la même manière. C’est la librairie faisant l’import qui va essayer d’optimiser le typage des données pour trouver le typage qui utilise le moins de mémoire possible sans altération de l’information. En effet, si pandas détermine qu’une colonne présente les valeurs 6 ; 5 ; 0, il va privilégier l’utilisation du type int au type double qui sera lui même préféré au type object (objets de type données textuelles). Cependant, pour faire cela, pandas va devoir scanner un nombre suffisant de valeurs, ce qui demande du temps et expose à des erreurs (en se fondant sur trop peu de valeurs, on peut se tromper de typage) ;
  • le stockage étant orienté ligne, accéder à une information donnée dans un CSV implique de le lire le fichier en entier, sélectionner la ou les colonnes d’intérêt et ensuite les lignes désirées. Par exemple, si on désire connaître uniquement la profession de la deuxième ligne dans l’exemple plus haut :point_up:, un algorithme de recherche devra: prendre le fichier, déterminer que la profession est la deuxième colonne, et ensuite aller chercher la deuxième ligne dans cette colonne. Si on désire accéder à un sous-ensemble de lignes dont les indices sont connus, le CSV est intéressant. Cependant, si on désire accéder à un sous-ensemble de colonnes dans un fichier (ce qui est un cas d’usage plus fréquent pour les data scientists), alors le CSV n’est pas le format le plus approprié ;
  • mettre à jour la donnée est coûteux car cela implique de réécrire l’ensemble du fichier. Par exemple, si après une première analyse de la donnée, on désire ajouter une colonne, on ne peut accoler ces nouvelles informations à celles déjà existantes, il est nécessaire de réécrire l’ensemble du fichier. Pour reprendre l’exemple de nos gaulois préférés, si on veut ajouter une colonne cheveux entre les deux déjà existantes, il faudra changer totalement le fichier:
"""
nom ; cheveux ; profession
Astérix; blond; ; 
Obélix; roux; Tailleur de menhir
Assurancetourix; blond; Barde
"""

Pour répondre à ces limites du CSV, le format parquet, qui est un projet open-source Apache, a émergé. La première différence entre le format parquet et le CSV est que le premier repose sur un stockage orienté colonne là où le second est orienté ligne. Pour comprendre la différence, voici un exemple issu du blog d’upsolver:

Dans notre exemple précédent, cela donnera une information prenant la forme suivante (ignorez l’élément pyarrow.Table, nous reviendrons dessus) :

pyarrow.Table
nom : string
profession: string
----
nom : [["Astérix ","Obélix ","Assurancetourix "]]
profession: [["","Tailleur de menhir","Barde"]]

Pour reprendre l’exemple fil rouge :point_up:, il sera ainsi beaucoup plus facile de récupérer la deuxième ligne de la colonne profession: on ne considère que le vecteur profession et on récupère la deuxième valeur. Le requêtage d’échantillon de données ne nécessite donc pas l’import de l’ensemble des données. A cela s’ajoute des fonctionnalités supplémentaires des librairies d’import de données parquet (par exemple pyarrow ou spark) qui vont faciliter des recherches complexes basées, par exemple, sur des requêtes de type SQL, ou permettant l’utilisation de données plus volumineuses que la RAM.

Le format parquet présente d’autres avantages par rapport au CSV:

  • Le format parquet est (très) compressé, ce qui réduit la volumétrie des données sur disque ;
  • Des métadonnées, notamment le typage des variables, sont stockées en complément dans le fichier. Cette partie, nommée le footer du fichier parquet, permet que l’import des données soit optimisé sans risque d’altération de celle-ci. Pour un producteur de données, c’est une manière d’assurer la qualité des données. Par exemple, un fournisseur de données de type code-barre sera certain que les données 000012 ne seront pas considérées identiques à un code-barre 12.
  • Il est possible de partitionner un jeu de données en fonction de différents niveaux (par exemple des niveaux géographiques) en une arborescence de fichiers parquet. Cela permet de travailler sur un échantillon pour facilement passer à l’échelle ensuite. Par exemple, une structure partitionnée, empruntée à la documentation Spark peut prendre la forme suivante :
path
└── to
    └── table
        ├── gender=male
        │   ├── ...
        │   │
        │   ├── country=US
        │   │   └── data.parquet
        │   ├── country=CN
        │   │   └── data.parquet
        │   └── ...
        └── gender=female
            ├── ...
            │
            ├── country=US
            │   └── data.parquet
            ├── country=CN
            │   └── data.parquet
            └── ...

Qu’on lise un ou plusieurs fichiers, on finira avec le schéma suivant :

root
|-- name: string (nullable = true)
|-- age: long (nullable = true)
|-- gender: string (nullable = true)
|-- country: string (nullable = true)

Ces différents avantages expliquent le succès du format parquet dans le monde du big-data. Le paragraphe suivant, extrait du post d’upsolver déjà cité, résume bien l’intérêt:

Complex data such as logs and event streams would need to be represented as a table with hundreds or thousands of columns, and many millions of rows. Storing this table in a row based format such as CSV would mean:

  • Queries will take longer to run since more data needs to be scanned, rather than only querying the subset of columns we need to answer a query (which typically requires aggregating based on dimension or category)
  • Storage will be more costly since CSVs are not compressed as efficiently as Parquet

Cependant, Parquet ne devrait pas intéresser que les producteurs ou utilisateurs de données big-data. C’est l’ensemble des producteurs de données qui bénéficient des fonctionalités de Parquet.

Pour en savoir plus sur Arrow, des éléments supplémentaires sur Parquet sont disponibles sur ce très bon post de blog d’upsolver et sur la page officielle du projet Parquet.

Lire un parquet en Python: la librairie pyarrow

La librairie pyarrow permet la lecture et l’écriture de fichiers parquet avec Python1. Elle repose sur un type particulier de dataframe, le pyarrow.Table qui peut être utilisé en substitut ou en complément du DataFrame de pandas. Il est recommandé de régulièrement consulter la documentation officielle de pyarrow concernant la lecture et écriture de fichiers et celle relative aux manipulations de données.

Pour illustrer les fonctionalités de pyarrow, repartons de notre CSV initial que nous allons enrichir d’une nouvelle variable numérique et que nous allons convertir en objet pyarrow avant de l’écrire au format parquet:

import pandas as pd
from io import StringIO
import pyarrow as pa
import pyarrow.parquet as pq

s = """
nom;cheveux;profession
Astérix;blond;
Obélix;roux;Tailleur de menhir
Assurancetourix;blond;Barde
"""

source = StringIO(s)

df = pd.read_csv(source, sep=";", index_col=False)
df["taille"] = [155, 190, 175]
table = pa.Table.from_pandas(df)

table

pq.write_table(table, "example.parquet")

Pour importer et traiter ces données, on peut conserver les données sous le format pyarrow.Table ou transformer en pandas.DataFrame. La deuxième option est plus lente mais présente l’avantage de permettre ensuite d’appliquer toutes les manipulations offertes par l’écosystème pandas qui est généralement mieux connu que celui d’Arrow.

Supposons qu’on ne s’intéresse qu’à la taille et à la couleur de cheveux de nos gaulois. Il n’est pas nécessaire d’importer l’ensemble de la base, cela ferait perdre du temps pour rien. On appelle cette approche le column pruning qui consiste à ne parcourir, dans le fichier, que les colonnes qui nous intéressent. Du fait du stockage orienté colonne du parquet, il suffit de ne considérer que les blocs qui nous intéressent (alors qu’avec un CSV il faudrait scanner tout le fichier avant de pouvoir éliminer certaines colonnes). Ce principe du column pruning se matérialise avec l’argument columns dans parquet.

Ensuite, avec pyarrow, on pourra utiliser pyarrow.compute pour effectuer des opérations directement sur une table Arrow :

import pyarrow.compute as pc

table = pq.read_table("example.parquet", columns=["taille", "cheveux"])

table.group_by("cheveux").aggregate([("taille", "mean")])

La manière équivalente de procéder en passant par l’intermédiaire de pandas est

table = pq.read_table("example.parquet", columns=["taille", "cheveux"])

table.to_pandas().groupby("cheveux")["taille"].mean()
cheveux
blond    165.0
roux     190.0
Name: taille, dtype: float64

Ici, comme les données sont peu volumineuses, deux des avantages du parquet par rapport au CSV (données moins volumineuses et vitesse de l’import) ne s’appliquent pas vraiment.

Le système de stockage S3

Si les fichiers parquet sont une solution avantageuse pour les data scientists, ils ne résolvent pas tous les inconvénients de l’approche fichier. En particulier, la question de la duplication des données pour la mise à disposition sécurisée des sources n’est pas résolue. Pour que l’utilisateur B n’altère pas les données de l’utilisateur A, il est nécessaire qu’ils travaillent sur deux fichiers différents, dont l’un peut être une copie de l’autre.

Les données sur le cloud

La mise à disposition de données dans les systèmes de stockage cloud est une réponse à ce problème. Les data lake qui se sont développés dans les institutions et entreprises utilisatrices de données

Le principe d’un stockage cloud est le même que celui d’une Dropbox ou d’un Drive mais adapté à l’analyse de données. Un utilisateur de données accède à un fichier stocké sur un serveur distant comme s’il était dans son file system local2. Donc, du point de vue de l’utilisateur Python, il n’y a pas de différence fondamentale. Cependant, les données ne sont pas hebergées dans un dossier local (par exemple Mes Documents/monsuperfichier) mais sur un serveur distant auquel l’utilisateur de Python accède à travers un échange réseau.

Dans l’univers du cloud, la hiérarchisation des données dans des dossiers et des fichiers bien rangés est d’ailleurs moins importante que dans le monde du file system local. Lorsque vous essayez de retrouver un fichier dans votre arborescence de fichiers, vous utilisez parfois la barre de recherche de votre explorateur de fichiers, avec des résultats mitigés3. Dans le monde du cloud, les fichiers sont parfois accumulés de manière plus chaotique car les outils de recherche sont plus efficaces4.

En ce qui concerne la sécurité des données, la gestion des droits de lecture et écriture peut être fine: on peut autoriser certains utilisateurs uniquement à la lecture, d’autres peuvent avoir les droits d’écriture pour modifier les données. Cela permet de concilier les avantages des bases de données (la sécurisation des données) avec ceux des fichiers.

Qu’est-ce que le système de stockage S3 ?

Dans les entreprises et administrations, un nombre croissant de données sont disponibles depuis un système de stockage nommé S3. Le système S3 (Simple Storage System) est un système de stockage développé par Amazon et qui est maintenant devenu une référence pour le stockage en ligne. Il s’agit d’une architecture à la fois sécurisée (données cryptées, accès restreints) et performante.

Le concept central du système S3 est le bucket. Un bucket est un espace (privé ou partagé) où on peut stocker une arborescence de fichiers. Pour accéder aux fichiers figurant dans un bucket privé, il faut des jetons d’accès (l’équivalent d’un mot de passe) reconnus par le serveur de stockage. On peut alors lire et écrire dans le bucket.

::: {.cell .markdown}

Comment faire avec Python ?

Les librairies principales

L’interaction entre ce système distant de fichiers et une session locale de Python est possible grâce à des API. Les deux principales librairies sont les suivantes :

  • boto3, une librairie créée et maintenue par Amazon ;
  • s3fs, une librairie qui permet d’interagir avec les fichiers stockés à l’instar d’un filesystem classique.

La librairie pyarrow que nous avons déjà présentée permet également de traiter des données stockées sur le cloud comme si elles étaient sur le serveur local. C’est extrêmement pratique et permet de fiabiliser la lecture ou l’écriture de fichiers dans une architecture cloud. Un exemple, assez court, est disponible dans la documentation officielle

Il existe également d’autres librairies permettant de gérer des pipelines de données (chapitre à venir) de manière quasi indifférente entre une architecture locale et une architecture cloud. Parmi celles-ci, nous présenterons quelques exemples avec snakemake. En arrière-plan, snakemake va utiliser boto3 pour communiquer avec le système de stockage.

Enfin, selon le même principe du comme si les données étaient en local, il existe l’outil en ligne de commande mc (Minio Client) qui permet de gérer par des lignes de commande Linux les dépôts distants comme s’ils étaient locaux.

Toutes ces librairies offrent la possibilité de se connecter depuis Python, à un dépôt de fichiers distant, de lister les fichiers disponibles dans un bucket, d’en télécharger un ou plusieurs ou de faire de l’upload Nous allons présenter quelques-unes des opérations les plus fréquentes, en mode cheatsheet.

Connexion à un bucket

Par la suite, on va utiliser des alias pour les trois valeurs suivantes, qui servent à s’authentifier.

key_id = "MY_KEY_ID"
access_key = "MY_ACCESS_KEY"
token = "MY_TOKEN"

Ces valeurs peuvent être également disponibles dans les variables d’environnement de Python. Comme il s’agit d’une information d’authentification personnelle, il ne faut pas stocker les vraies valeurs de ces variables dans un projet, sous peine de partager des traits d’identité sans le vouloir lors d’un partage de code.

boto3 👇

Avec boto3, on créé d’abord un client puis on exécute des requêtes dessus. Pour initialiser un client, il suffit, en supposant que l’url du dépôt S3 est "https://minio.lab.sspcloud.fr", de faire:

import boto3

s3 = boto3.client("s3", endpoint_url="https://minio.lab.sspcloud.fr")
S3FS 👇

La logique est identique avec s3fs.

Si on a des jetons d’accès à jour et dans les variables d’environnement adéquates:

import s3fs

fs = s3fs.S3FileSystem(client_kwargs={"endpoint_url": "https://minio.lab.sspcloud.fr"})
Arrow 👇

La logique d’Arrow est proche de celle de s3fs. Seuls les noms d’arguments changent

Si on a des jetons d’accès à jour et dans les variables d’environnement adéquates:

from pyarrow import fs

s3 = fs.S3FileSystem(endpoint_override="http://" + "minio.lab.sspcloud.fr")
Snakemake 👇

La logique de Snakemake est, quant à elle, plus proche de celle de boto3. Seuls les noms d’arguments changent

Si on a des jetons d’accès à jour et dans les variables d’environnement adéquates:

from snakemake.remote.S3 import RemoteProvider as S3RemoteProvider

S3 = S3RemoteProvider(host="https://" + os.getenv("AWS_S3_ENDPOINT"))

Il se peut que la connexion à ce stade soit refusée (HTTP error 403). Cela peut provenir d’une erreur dans l’URL utilisé. Cependant, cela reflète plus généralement des paramètres d’authentification erronés.

boto3 👇

Les paramètres d’authentification sont des arguments supplémentaires:

import boto3

s3 = boto3.client(
    "s3",
    endpoint_url="https://minio.lab.sspcloud.fr",
    aws_access_key_id=key_id,
    aws_secret_access_key=access_key,
    aws_session_token=token,
)
S3FS 👇

La logique est la même, seuls les noms d’arguments diffèrent

import s3fs

fs = s3fs.S3FileSystem(
    client_kwargs={"endpoint_url": "https://" + "minio.lab.sspcloud.fr"},
    key=key_id,
    secret=access_key,
    token=token,
)
Arrow 👇

Tout est en argument cette fois:

from pyarrow import fs

s3 = fs.S3FileSystem(
    access_key=key_id,
    secret_key=access_key,
    session_token=token,
    endpoint_override="https://" + "minio.lab.sspcloud.fr",
    scheme="https",
)
Snakemake 👇

La logique est la même, seuls les noms d’arguments diffèrent

from snakemake.remote.S3 import RemoteProvider as S3RemoteProvider

S3 = S3RemoteProvider(
    host="https://" + os.getenv("AWS_S3_ENDPOINT"),
    access_key_id=key_id,
    secret_access_key=access_key,
)

Lister les fichiers

S’il n’y a pas d’erreur à ce stade, c’est que la connexion est bien effective. Pour le vérifier, on peut essayer de faire la liste des fichiers disponibles dans un bucket auquel on désire accéder.

Par exemple, on peut vouloir tester l’accès aux bases FILOSOFI (données de revenu localisées disponibles sur https://www.insee.fr) au sein du bucket donnees-insee.

boto3 👇

Pour cela, la méthode list_objects offre toutes les options nécessaires:

import boto3

s3 = boto3.client("s3", endpoint_url="https://minio.lab.sspcloud.fr")
for key in s3.list_objects(Bucket="donnees-insee", Prefix="diffusion/FILOSOFI")[
    "Contents"
]:
    print(key["Key"])
S3FS 👇

Pour lister les fichiers, c’est la méthode ls (celle-ci ne liste pas par défaut les fichiers de manière récursive comme boto3):

import s3fs

fs = s3fs.S3FileSystem(client_kwargs={"endpoint_url": "https://minio.lab.sspcloud.fr"})
fs.ls("donnees-insee/diffusion")
Arrow 👇
from pyarrow import fs

s3 = fs.S3FileSystem(endpoint_override="https://" + "minio.lab.sspcloud.fr")
s3.get_file_info(fs.FileSelector("donnees-insee/diffusion", recursive=True))
mc 👇
mc ls -r

Télécharger un fichier depuis S3 pour l’enregistrer en local

Cette méthode n’est en général pas recommandée car, comme on va le voir par la suite, il est possible de lire à la volée des fichiers. Cependant, télécharger un fichier depuis le cloud pour l’écrire sur le disque local peut parfois être utile (par exemple, lorsqu’il est nécessaire de dézipper un fichier).

boto3 👇

On utilise cette fois la méthode download_file

import boto3

s3 = boto3.client("s3", endpoint_url="https://minio.lab.sspcloud.fr")
s3.download_file(
    "donnees-insee", "diffusion/FILOSOFI/2014/FILOSOFI_COM.csv", "data.csv"
)
S3FS 👇
import s3fs

fs = s3fs.S3FileSystem(client_kwargs={"endpoint_url": "https://minio.lab.sspcloud.fr"})
fs.download("donnees-insee/diffusion/FILOSOFI/2014/FILOSOFI_COM.csv", "test.csv")
Snakemake 👇
from snakemake.remote.S3 import RemoteProvider as S3RemoteProvider
S3 = S3RemoteProvider(host = "https://" + os.getenv('AWS_S3_ENDPOINT'))
bucket = "mon-bucket"

rule ma_super_regle_s3:
    input:
        fichier = S3.remote(f'{bucket}/moninput.csv')
    output:
        fichier='mon_dossier_local/monoutput.csv'
    run:
        shell("cp {input[0]} {output[0]}")
mc 👇
mc cp "donnees-insee/FILOSOFI/2014/FILOSOFI_COM.csv" 'data.csv'

Lire un fichier directement

La méthode précédente n’est pas optimale. En effet, l’un des intérêts des API est qu’on peut traiter un fichier sur S3 comme s’il s’agissait d’un fichier sur son PC. Cela est d’ailleurs une manière plus sécurisée de procéder puisqu’on lit les données à la volée, sans les écrire dans un filesystem local.

boto3 👇
import boto3

s3 = boto3.client("s3", endpoint_url="https://minio.lab.sspcloud.fr")
obj = s3.get_object(
    Bucket="donnees-insee", Key="diffusion/FILOSOFI/2014/FILOSOFI_COM.csv"
)
df = pd.read_csv(obj["Body"], sep=";")
df.head(2)
S3FS 👇

Le code suivant devrait permettre d’effectuer la même opération avec s3fs

import pandas as pd
import s3fs

fs = s3fs.S3FileSystem(client_kwargs={"endpoint_url": "https://minio.lab.sspcloud.fr"})
df = pd.read_csv(
    fs.open(
        "{}/{}".format("donnees-insee", "diffusion/FILOSOFI/2014/FILOSOFI_COM.csv"),
        mode="rb",
    ),
    sep=";",
)

df.head(2)
Snakemake 👇
from snakemake.remote.S3 import RemoteProvider as S3RemoteProvider
S3 = S3RemoteProvider(host = "https://" + os.getenv('AWS_S3_ENDPOINT'))
bucket = "mon-bucket"

rule ma_super_regle_s3:
    input:
        fichier = S3.remote(f'{bucket}/moninput.csv')
    run:
        import pandas as pd
        df = pd.read_csv(input.fichier)
        # PLUS D'OPERATIONS
Arrow 👇

Arrow est une librairie qui permet de lire des CSV. Il est néanmoins beaucoup plus pratique d’utiliser le format parquet avec arrow. Dans un premier temps, on configure le filesystem avec les fonctionalités d’Arrow (cf. précédemment).

from pyarrow import fs

s3 = fs.S3FileSystem(endpoint_override="http://" + "minio.lab.sspcloud.fr")

Pour lire un csv, on fera:

from pyarrow import fs
from pyarrow import csv

s3 = fs.S3FileSystem(endpoint_override="https://" + "minio.lab.sspcloud.fr")

with s3.open_input_file(
    "donnees-insee/diffusion/FILOSOFI/2014/FILOSOFI_COM.csv"
) as file:
    df = csv.read_csv(file, parse_options=csv.ParseOptions(delimiter=";")).to_pandas()

Pour un fichier au format parquet, la démarche est plus simple grâce à l’argument filesystem dans pyarrow.parquet.ParquetDataset :

import pyarrow.parquet as pq

# bucket = ""
# parquet_file=""
df = (
    pq.ParquetDataset(f"{bucket}/{parquet_file}", filesystem=s3)
    .read_pandas()
    .to_pandas()
)

Uploader un fichier

boto3 👇
s3.upload_file(file_name, bucket, object_name)
S3FS 👇
fs.put(filepath, f"{bucket}/{object_name}", recursive=True)
Arrow 👇

Supposons que df soit un pd.DataFrame Dans un système local, on convertirait en table Arrow puis on écrirait en parquet (voir la documentation officielle). Quand on est sur un système S3, il s’agit seulement d’ajouter notre connexion à S3 dans l’argument filesystem (voir la page sur ce sujet dans la documentation Arrow)

import pyarrow as pa
import pyarrow.parquet as pq

table = pa.Table.from_pandas(df)
pq.write_table(table, f"{bucket}/{path}", filesystem=s3)
Snakemake 👇
from snakemake.remote.S3 import RemoteProvider as S3RemoteProvider
S3 = S3RemoteProvider(host = "https://" + os.getenv('AWS_S3_ENDPOINT'))
bucket = "mon-bucket"

rule ma_super_regle_s3:
    input:
        fichier='mon_dossier_local/moninput.csv'
    output:
        fichier=S3.remote(f'{bucket}/monoutput.csv')
    run:
        shell("cp output.fichier input.fichier")
mc 👇
mc cp 'data.csv' "MONBUCKET/monoutput.csv"

Pour aller plus loin

Informations additionnelles

environment files have been tested on.

Python version used:

Package Version
affine 2.4.0
aiobotocore 2.12.2
aiohttp 3.9.3
aioitertools 0.11.0
aiosignal 1.3.1
alembic 1.13.1
aniso8601 9.0.1
annotated-types 0.6.0
appdirs 1.4.4
archspec 0.2.3
astroid 3.1.0
asttokens 2.4.1
attrs 23.2.0
bcrypt 4.1.2
beautifulsoup4 4.12.3
black 24.3.0
blinker 1.7.0
blis 0.7.11
bokeh 3.4.0
boltons 23.1.1
boto3 1.34.51
botocore 1.34.51
branca 0.7.1
Brotli 1.1.0
cachetools 5.3.3
cartiflette 0.0.2
Cartopy 0.23.0
catalogue 2.0.10
cattrs 23.2.3
certifi 2024.2.2
cffi 1.16.0
charset-normalizer 3.3.2
chromedriver-autoinstaller 0.6.4
click 8.1.7
click-plugins 1.1.1
cligj 0.7.2
cloudpathlib 0.16.0
cloudpickle 3.0.0
colorama 0.4.6
comm 0.2.2
conda 24.3.0
conda-libmamba-solver 24.1.0
conda-package-handling 2.2.0
conda_package_streaming 0.9.0
confection 0.1.4
contextily 1.6.0
contourpy 1.2.1
cryptography 42.0.5
cycler 0.12.1
cymem 2.0.8
cytoolz 0.12.3
dask 2024.4.1
dask-expr 1.0.10
debugpy 1.8.1
decorator 5.1.1
dill 0.3.8
distributed 2024.4.1
distro 1.9.0
docker 7.0.0
duckdb 0.10.1
en-core-web-sm 3.7.1
entrypoints 0.4
et-xmlfile 1.1.0
exceptiongroup 1.2.0
executing 2.0.1
fastjsonschema 2.19.1
fiona 1.9.6
flake8 7.0.0
Flask 3.0.2
folium 0.16.0
fontawesomefree 6.5.1
fonttools 4.51.0
fr-core-news-sm 3.7.0
frozenlist 1.4.1
fsspec 2023.12.2
GDAL 3.8.4
gensim 4.3.2
geographiclib 2.0
geopandas 0.12.2
geoplot 0.5.1
geopy 2.4.1
gitdb 4.0.11
GitPython 3.1.43
google-auth 2.29.0
graphene 3.3
graphql-core 3.2.3
graphql-relay 3.2.0
graphviz 0.20.3
greenlet 3.0.3
gunicorn 21.2.0
h11 0.14.0
hvac 2.1.0
idna 3.6
imageio 2.34.0
importlib_metadata 7.1.0
importlib_resources 6.4.0
inflate64 1.0.0
ipykernel 6.29.3
ipython 8.22.2
ipywidgets 8.1.2
isort 5.13.2
itsdangerous 2.1.2
jedi 0.19.1
Jinja2 3.1.3
jmespath 1.0.1
joblib 1.3.2
jsonpatch 1.33
jsonpointer 2.4
jsonschema 4.21.1
jsonschema-specifications 2023.12.1
jupyter-cache 1.0.0
jupyter_client 8.6.1
jupyter_core 5.7.2
jupyterlab_widgets 3.0.10
kaleido 0.2.1
kiwisolver 1.4.5
kubernetes 29.0.0
langcodes 3.3.0
lazy_loader 0.4
libmambapy 1.5.7
llvmlite 0.42.0
locket 1.0.0
lxml 5.2.1
lz4 4.3.3
Mako 1.3.2
mamba 1.5.7
mapclassify 2.6.1
Markdown 3.6
MarkupSafe 2.1.5
matplotlib 3.8.3
matplotlib-inline 0.1.6
mccabe 0.7.0
menuinst 2.0.2
mercantile 1.2.1
mizani 0.11.1
mlflow 2.11.3
mlflow-skinny 2.11.3
msgpack 1.0.7
multidict 6.0.5
multivolumefile 0.2.3
munkres 1.1.4
murmurhash 1.0.10
mypy 1.9.0
mypy-extensions 1.0.0
nbclient 0.10.0
nbformat 5.10.4
nest_asyncio 1.6.0
networkx 3.3
nltk 3.8.1
numba 0.59.1
numpy 1.26.4
oauthlib 3.2.2
opencv-python-headless 4.9.0.80
openpyxl 3.1.2
outcome 1.3.0.post0
OWSLib 0.28.1
packaging 23.2
pandas 2.2.1
paramiko 3.4.0
parso 0.8.4
partd 1.4.1
pathspec 0.12.1
patsy 0.5.6
Pebble 5.0.7
pexpect 4.9.0
pickleshare 0.7.5
pillow 10.3.0
pip 24.0
pkgutil_resolve_name 1.3.10
platformdirs 4.2.0
plotly 5.19.0
plotnine 0.13.4
pluggy 1.4.0
polars 0.20.18
preshed 3.0.9
prometheus_client 0.20.0
prometheus-flask-exporter 0.23.0
prompt-toolkit 3.0.42
protobuf 4.25.3
psutil 5.9.8
ptyprocess 0.7.0
pure-eval 0.2.2
py7zr 0.20.8
pyarrow 15.0.0
pyarrow-hotfix 0.6
pyasn1 0.5.1
pyasn1-modules 0.3.0
pybcj 1.0.2
pycodestyle 2.11.1
pycosat 0.6.6
pycparser 2.21
pycryptodomex 3.20.0
pydantic 2.7.0
pydantic_core 2.18.1
pyflakes 3.2.0
Pygments 2.17.2
PyJWT 2.8.0
pylint 3.1.0
PyNaCl 1.5.0
pynsee 0.1.7
pyOpenSSL 24.0.0
pyparsing 3.1.2
pyppmd 1.1.0
pyproj 3.6.1
pyshp 2.3.1
PySocks 1.7.1
python-dateutil 2.9.0
python-dotenv 1.0.1
python-magic 0.4.27
pytz 2024.1
pyu2f 0.1.5
pywaffle 1.1.0
PyYAML 6.0.1
pyzmq 25.1.2
pyzstd 0.15.10
QtPy 2.4.1
querystring-parser 1.2.4
rasterio 1.3.9
referencing 0.34.0
regex 2023.12.25
requests 2.31.0
requests-cache 1.2.0
requests-oauthlib 2.0.0
rpds-py 0.18.0
rsa 4.9
Rtree 1.2.0
ruamel.yaml 0.18.6
ruamel.yaml.clib 0.2.8
s3fs 2023.12.2
s3transfer 0.10.1
scikit-image 0.23.1
scikit-learn 1.4.1.post1
scipy 1.13.0
seaborn 0.13.2
selenium 4.19.0
setuptools 69.2.0
shapely 2.0.3
six 1.16.0
smart-open 6.4.0
smmap 5.0.0
sniffio 1.3.1
snuggs 1.4.7
sortedcontainers 2.4.0
soupsieve 2.5
spacy 3.7.4
spacy-legacy 3.0.12
spacy-loggers 1.0.5
SQLAlchemy 2.0.29
sqlparse 0.4.4
srsly 2.4.8
stack-data 0.6.2
statsmodels 0.14.1
tabulate 0.9.0
tblib 3.0.0
tenacity 8.2.3
texttable 1.7.0
thinc 8.2.3
threadpoolctl 3.4.0
tifffile 2024.2.12
tomli 2.0.1
tomlkit 0.12.4
toolz 0.12.1
topojson 1.8
tornado 6.4
tqdm 4.66.2
traitlets 5.14.2
trio 0.25.0
trio-websocket 0.11.1
truststore 0.8.0
typer 0.9.4
typing_extensions 4.11.0
tzdata 2024.1
Unidecode 1.3.8
url-normalize 1.4.3
urllib3 1.26.18
wasabi 1.1.2
wcwidth 0.2.13
weasel 0.3.4
webdriver-manager 4.0.1
websocket-client 1.7.0
Werkzeug 3.0.2
wheel 0.43.0
widgetsnbextension 4.0.10
wordcloud 1.9.3
wrapt 1.16.0
wsproto 1.2.0
xgboost 2.0.3
xlrd 2.0.1
xyzservices 2024.4.0
yarl 1.9.4
yellowbrick 1.5
zict 3.0.0
zipp 3.17.0
zstandard 0.22.0

View file history

SHA Date Author Description
005d89b 2023-12-20 17:23:04 Lino Galiana Finalise l’affichage des statistiques Git (#478)
3fba612 2023-12-17 18:16:42 Lino Galiana Remove some badges from python (#476)
4cd44f3 2023-12-11 17:37:50 Antoine Palazzolo Relecture NLP (#474)
1f23de2 2023-12-01 17:25:36 Lino Galiana Stockage des images sur S3 (#466)
a06a268 2023-11-23 18:23:28 Antoine Palazzolo 2ème relectures chapitres ML (#457)
09654c7 2023-11-14 15:16:44 Antoine Palazzolo Suggestions Git & Visualisation (#449)
889a71b 2023-11-10 11:40:51 Antoine Palazzolo Modification TP 3 (#443)
a771183 2023-10-09 11:27:45 Antoine Palazzolo Relecture TD2 par Antoine (#418)
8556b79 2023-09-27 17:29:23 Julien PRAMIL Typo chapitre S3 (#415)
154f09e 2023-09-26 14:59:11 Antoine Palazzolo Des typos corrigées par Antoine (#411)
9977c5d 2023-08-28 10:43:36 Lino Galiana Fix bug path pandas (#397)
3bdf3b0 2023-08-25 11:23:02 Lino Galiana Simplification de la structure 🤓 (#393)
5d4874a 2023-08-11 15:09:33 Lino Galiana Pimp les introductions des trois premières parties (#387)
3f60d55 2023-02-02 18:59:16 Lino Galiana WIP: Change path S3 (#349)
9000723 2023-01-22 12:01:25 Lino Galiana Corrige code s3fs upload (#345)
1fe65ac 2022-12-25 21:39:51 Lino Galiana Dark boxes (#336)
2227f8e 2022-12-25 18:56:57 Lino Galiana Fix problem s3 chapter (#335)
8e5edba 2022-09-02 11:59:57 Lino Galiana Ajoute un chapitre dask (#264)
688cc15 2022-09-01 18:38:59 Lino Galiana Update index.qmd
2117d2c 2022-09-01 08:54:38 Lino Galiana Ajoute des éléments sur arrow (#262)
f10815b 2022-08-25 16:00:03 Lino Galiana Notebooks should now look more beautiful (#260)
d201e3c 2022-08-03 15:50:34 Lino Galiana Pimp la homepage ✨ (#249)
bacb5a0 2022-07-04 19:05:20 Lino Galiana Enrichir la partie elastic (#241)
12965ba 2022-05-25 15:53:27 Lino Galiana :launch: Bascule vers quarto (#226)
aa945cb 2022-03-31 09:28:14 Lino Galiana corrige une ou deux typos (#225)
34b08ec 2022-03-24 16:37:37 Lino Galiana Ajoute code sur write_parquet dans S3
c51a87b 2022-03-24 16:29:39 Lino Galiana retire typo nom bucket (#223)
3b1d9ff 2022-03-09 10:38:15 Lino Galiana Ajoute détails arrow dans la partie S3 (#220)
9c71d6e 2022-03-08 10:34:26 Lino Galiana Plus d’éléments sur S3 (#218)
2a8809f 2021-10-27 12:05:34 Lino Galiana Simplification des hooks pour gagner en flexibilité et clarté (#166)
2e4d586 2021-09-02 12:03:39 Lino Galiana Simplify badges generation (#130)
80877d2 2021-06-28 11:34:24 Lino Galiana Ajout d’un exercice de NLP à partir openfood database (#98)
6729a72 2021-06-22 18:07:05 Lino Galiana Mise à jour badge onyxia (#115)
4cdb759 2021-05-12 10:37:23 Lino Galiana :sparkles: :star2: Nouveau thème hugo :snake: :fire: (#105)
7f9f97b 2021-04-30 21:44:04 Lino Galiana 🐳 + 🐍 New workflow (docker 🐳) and new dataset for modelization (2020 🇺🇸 elections) (#99)
0a0d034 2021-03-26 20:16:22 Lino Galiana Ajout d’une section sur S3 (#97)
Back to top

Footnotes

  1. Elle permet aussi la lecture et l’écriture de .csv.↩︎

  2. D’ailleurs, les générations n’ayant connu nativement que ce type de stockage ne sont pas familiarisées au concept de file system et préfèrent payer le temps de recherche. Voir cet article sur le sujet.↩︎

  3. D’ailleurs, les générations n’ayant connu nativement que ce type de stockage ne sont pas familiarisées au concept de file system et préfèrent payer le temps de recherche. Voir cet article sur le sujet.↩︎

  4. D’ailleurs, les générations n’ayant connu nativement que ce type de stockage ne sont pas familiarisées au concept de file system et préfèrent payer le temps de recherche. Voir cet article sur le sujet.↩︎

Citation

BibTeX citation:
@book{galiana2023,
  author = {Galiana, Lino},
  title = {Python Pour La Data Science},
  date = {2023},
  url = {https://pythonds.linogaliana.fr/},
  doi = {10.5281/zenodo.8229676},
  langid = {en}
}
For attribution, please cite this work as:
Galiana, Lino. 2023. Python Pour La Data Science. https://doi.org/10.5281/zenodo.8229676.