Le format CSV est le format le plus utilisé pour partager des données tabulaires.
Il a été standardisé en 2005 (RFC 4180) et est accessible depuis la majorité des outils traitant des ensembles de données structurées sous forme de listes, d'arborescences ou de tableaux multi-dimensionnels.
Pourtant ce format très pauvre d'un point de vue sémantique présente de nombreuses limitations et semble mal adapté aux besoins d'échanges de données.
Cette note a pour objet de présenter les alternatives possibles à ce format pour un meilleur échange de données.
Les données tabulaires sont les données représentées sous la forme de tableaux composés de lignes et de colonnes. Ces deux notions ne sont pas équivalentes, les colonnes (ou champs) représentent la 'sémantique' des données et les lignes représentent les objets décrits suivant la structuration définie par les colonnes.
Si l'on observe maintenant comment sont utilisés les tableaux, on peut identifier trois usages principaux :
Prenons un exemple :
id | produit | aliment | quantité | prix | validité | disponibilité |
---|---|---|---|---|---|---|
11 | pomme | fruit | sachet 1 kg | 1 | du 1/7/2022 au 31/12/2022 | oui |
12 | pomme | fruit | carton 10 kg | 9 | du 1/7/2022 au 31/12/2022 | oui |
13 | orange | fruit | sachet 1 kg | 2 | du 1/7/2022 au 31/12/2022 | fin 2022 |
14 | orange | fruit | carton 10 kg | 18 | du 1/7/2022 au 31/12/2022 | fin 2022 |
15 | piment | légume | sachet 1 kg | 1.5 | du 1/7/2022 au 31/12/2022 | oui |
16 | piment | légume | carton 10 kg | 13 | du 1/7/2022 au 31/12/2022 | fin 2022 |
17 | banane | fruit | sachet 1 kg | 0.5 | du 1/7/2022 au 31/12/2022 | oui |
18 | banane | fruit | carton 10 kg | 4 | du 1/7/2022 au 31/12/2022 | oui |
Il s'agit d'une liste de prix de différents aliments en fonction d'un conditionnement pour l'année 2022.
On retrouve ici :
- la classification : entre les produits et les aliments,
- le croisement : entre les produits et les quantités,
- la caractérisation : la disponibilité du produit
On peut noter que les deux usages de classification et de croisement conduisent à dupliquer fortement les informations avec les risques d'erreurs associés.
Le format CSV (Comma-Separated Values) est un format texte (principalement ASCII) dans lequel les données sont structurées en lignes. Chaque ligne contient un nombre identique de champs. Chaque champ est une chaîne de caractères représentant une donnée à partager.
Il s'agit donc d'un format totalement adapté aux données tabulaires.
Plusieurs options de représentation sont possibles :
Voyons maintenant quels sont les avantages et inconvénients de ce format.
Plus généralement, le format CSV est considéré comme un format obsolète et ne permettant pas une réelle interopérabilité des données.
Le Référentiel Général d'Interopérabilité (RGI) dans sa dernière version de 2015 qualifie ainsi le format CSV :
Cotation : En fin de vie *
*CSV est un format informatique d’échange de données ouvert représentant des données
tabulaires sous forme de valeurs séparées par des virgules. D’autres variantes de séparateur de champ peuvent être utilisées, notamment lorsque la virgule est un élément signifiant d’une donnée. L’utilisation des séparateurs de champs doit donc être utilisée avec circonspection et adaptée au contexte sous peine de rendre le fichier inexploitable. Ce format est retenu dans le RGI par exception. Il doit être évité car il n’est pas nécessairement interopérable d’une plateforme à l’autre, la spécification précisant uniquement le format des fins de ligne mais ne précisant pas l’encodage à utiliser pour le texte en lui-même et pour les séparateurs (la RFC mentionne uniquement un encodage US-ASCII).*
Note importante : Le standard CSV est au statut recommandé uniquement* pour les échanges
entre application et utilisateur. Pour tous les autres cas, il est considéré en « fin de vie ». Le standard XML est à privilégier pour les échanges entre applications ou systèmes, qui n’impliquent donc pas d’utilisateurs.*
Son remplacement par un format plus adapté est devenu une priorité.
Avant d'examiner les formats alternatifs, il convient de définir des critères d'évaluation.
Au vu de des avantages et inconvénients identifiés au chapître précédent, on peut définir un premier ensemble de critères à respecter pour un potentiel successeur au format CSV:
Pour identifier les candidats potentiels, nous pouvons examiner la liste des formats d'import/export proposés par Pandas (pandas est la principale application open source scientifique pour les données tabulaires).
19 formats sont identifiés. Parmi ceux-ci, seuls 6 sont des formats texte : CSV, Fixed-Width text file, JSON, HTML, LaTeX, XML, Local clipboard auxquels nous pouvons ajouter YAML.
Les formats binaires sont les plus nombreux et répondent à des attentes de performances et d'accessibilité pour des structures de taille importante (ex. promotion du format parquet pour remplacer le format CSV).
Les formats Fixed-Width text file et Local clipboard sont des formats similaires au format csv et avec des limitations plus importantes. Ils ne sont donc pas à retenir.
Le format LaTeX est un format d'export uniquement (pour Pandas) et n'est pas vraiment adapté.
Les formats HTML et XML sont des formats à 'balises', ce qui conduit à ajouter une quantité de données de structure importante. Ils sont par ailleurs moins lisibles qu'un format CSV.
Le format YAML est un format qui étend la structuration définie pour JSON (ex. commentaires, références) mais est également moins compact que le format CSV.
Le seul candidat qui répond à la majorité des critères définis est le format JSON:
La rapide analyse effectuée ci-dessus (qui demande à être confortée par une étude complète) permet de tirer de premières conclusions :
Ces conclusions permettent de définir une orientation, par contre elles ne sont pas suffisantes pour définir un successeur au format CSV. En effet, le format JSON est un langage de description de données, ce n'est pas un format d'échange de données tabulaires (cf standard ECMA-404 de 2017) :
The JSON syntax is not a specification of a complete data interchange. Meaningful data interchange requires agreement between a producer and consumer on the semantics attached to a particular use of the JSON syntax. What JSON does provide is the syntactic framework to which such semantics can be attached.
Le chapitre suivant apporte un éclairage sur la définition d'un format JSON dédié aux données tabulaires.
Le format JSON est décrit dans les normes RFC 8259 et conjointement ECMA-404 de décembre 2017. Ce format a été créé en 2001, il est maintenant dans une version qui ne devrait plus évoluer.
Il est construit autour de 7 entités :
object
ensemble de paires « clé (string) / valeur (entité) »array
liste ordonnée d'entitésstring
chaîne de caractères unicode UTF-8number
valeur numérique codée sous forme de digitsfalse
et true
valeurs booléennes,null
Le format JSON a donné lieu à des formats dérivés comme par exemple GeoJSON pour les données géographiques, CBOR / BSON / UBJSON pour les formats binaires, JSON Schema pour les "schema de données", JSON-LD pour les structures du "web sémantique"...
Le choix d'une représentation JSON pour les données tabulaires n'est pas simple et de nombreuses options sont possibles. A titre d'exemple, Pandas propose 6 représentations différentes pour un même 'DataFrame' (dénomination pandas d'un tableau) !
La représentation la plus simple est la représentation matricielle "array of arrays" avec deux options
array
,array
Les représentations par lignes sont privilégiées dans les représentations dynamiques lorsqu'on fait évoluer le nombre de lignes. Les représentations par colonnes sont privilégiées dans le traitement des représentations statiques. Elles permettent de regrouper les données d'un même type afin d'en optimiser le format.
Dans le cas présent d'un format d'échange, la représentation par colonne est à privilégier.
La représentation JSON des deux premières colonnes de l'exemple du chapitre 1 est alors la suivante :
[ [ "id", 11, 12, 13, 14, 15, 16, 17, 18 ], [ "produit", "pomme", "pomme", "orange", "orange", "piment", "piment", "banane", "banane" ] ]
Cette représentation est équivalente à celle d'un format CSV.
Nota : Cette option de représentation par colonnes plutôt que par lignes peut sembler contre-intuitive puisqu'elle ne permet plus d'avoir une vue globale d'un objet qui a été enregistré dans le tableau (une ligne), par contre elle permet une meilleure visibilité de l'organisation et de la structuration des données. Par ailleurs, cette vision globale par objet (ligne) est difficle à obtenir pour des tableaux avec un nombre important de colonnes, ce qui nécessite le recours à des outils complémentaires.
Dans la majorité des utilisations, les colonnes présentent un entête qui précise le nom du champ et éventuellement le type de données. Cette information est à rendre explicite en la sortant de l'objet array
.
Dans les représentations Pandas et R, la représentation devient un 'array of objects' :
[ { "id": [ 11, 12, 13, 14, 15, 16, 17, 18 ] }, { "produit": ["pomme", "pomme", "orange", "orange", "piment", "piment", "banane", "banane" ] } ]
Elle peut également rester dans une structure de type 'array of arrays':
[ [ "id", [ 11, 12, 13, 14, 15, 16, 17, 18 ] ], [ "produit", ["pomme", "pomme", "orange", "orange", "piment", "piment", "banane", "banane" ] ] ]
Cette deuxième approche est préférable si l'on veut également intégrer un typage des colonnes (comme par exemple pour les dates) :
[ [ {"id": "integer"}, [ 11, 12, 13, 14, 15, 16, 17, 18 ] ], [ {"produit": "string"}, ["pomme", "pomme", "orange", "orange", "piment", "piment", "banane", "banane" ]]]
Un des points évoqués dans les inconvénients du format CSV est la présence importante de données dupliquées.
Cette notion est traitée dans les outils comme R et Pandas par la notion de catégories (categoricals
pour pandas et factor
pour R) qui permet d'associer une référence (un entier) à chaque donnée.
La représentation d'une colonne peut alors se décomposer en une première liste de valeurs uniques et une deuxième liste des références à ces valeurs.
L'exemple ci-dessous met en application cette représentation
liste = ["pomme", "pomme", "orange", "orange", "piment", "piment", "banane", "banane" ]
name = "produit"
# représentation avec des fonctions de base de python
codec = list(set(liste))
print([name, codec, [codec.index(val) for val in liste]])
# représentation avec pandas
import pandas as pd
sr = pd.Series(liste, dtype = 'category', name="produit")
print([sr.name, list(sr.cat.categories), list(sr.cat.codes)])
['produit', ['banane', 'pomme', 'orange', 'piment'], [1, 1, 2, 2, 3, 3, 0, 0]] ['produit', ['banane', 'orange', 'piment', 'pomme'], [3, 3, 1, 1, 2, 2, 0, 0]]
Cette représentation semble d'un premier abord plus complexe puisqu'elle ne permet pas de reconstituer le séquencement des données dans une colonne et encore moins de visualiser l'ensemble des données d'une ligne. Par contre, elle permet très rapidement d'identifier la nature des données contenues dans le tableau sans nécessiter l'ouverture dans un tableur puis l'activation de filtres.
Elle répond également au besoin exprimé de réduction du volume des données puisque la duplication de données disparait (remplacée par l'ajout de "références" de type entier peu volumineuses).
La liste des références peut être optimisée en fonction des liens qui existent entre les différentes colonnes qui composent le tableau.
Cette optimisation non détaillée ici (voir l'article spécifique sur ce sujet) conduit soit à supprimer totalement cette liste, soit à la réduire.
Rappel de l'exemple :
id | produit | aliment | quantité | prix | validité | disponibilité |
---|---|---|---|---|---|---|
11 | pomme | fruit | sachet 1 kg | 1 | du 1/7/2022 au 31/12/2022 | oui |
12 | pomme | fruit | carton 10 kg | 9 | du 1/7/2022 au 31/12/2022 | oui |
13 | orange | fruit | sachet 1 kg | 2 | du 1/7/2022 au 31/12/2022 | fin 2022 |
14 | orange | fruit | carton 10 kg | 18 | du 1/7/2022 au 31/12/2022 | fin 2022 |
15 | piment | légume | sachet 1 kg | 1.5 | du 1/7/2022 au 31/12/2022 | oui |
16 | piment | légume | carton 10 kg | 13 | du 1/7/2022 au 31/12/2022 | fin 2022 |
17 | banane | fruit | sachet 1 kg | 0.5 | du 1/7/2022 au 31/12/2022 | oui |
18 | banane | fruit | carton 10 kg | 4 | du 1/7/2022 au 31/12/2022 | oui |
Les données ci-dessous représentent au format JSON les données de l'exemple dans une structure équivalente à celle d'un fichier csv :
[['produit', ['piment', 'piment', 'banane', 'banane', 'pomme', 'pomme', 'orange', 'orange']], ['quantite', ['sachet 1 kg', 'carton 10 kg', 'sachet 1 kg', 'carton 10 kg', 'sachet 1 kg', 'carton 10 kg', 'sachet 1 kg', 'carton 10 kg']], ['aliment', ['legume', 'legume', 'fruit', 'fruit', 'fruit', 'fruit', 'fruit', 'fruit']], ['validite', ['du 1/7/2022 au 31/12/2022', 'du 1/7/2022 au 31/12/2022', 'du 1/7/2022 au 31/12/2022', 'du 1/7/2022 au 12/2022', 'du 1/7/2022 au 31/12/2022', 'du 1/7/2022 au 31/12/2022', 'du 1/7/2022 au 31/12/2022', 'du 1/7/2022 au 31/12/2022']], ['id', [15, 16, 17, 18, 11, 12, 13, 14]], ['disponibilite', ['oui', 'fin 2022', 'oui', 'oui', 'oui', 'oui', 'fin 2022', 'fin 2022']], ['prix', [1.5, 15, 0.5, 5, 1, 10, 2, 20]]]Celles-ci représentent les mêmes données mais avec une représentation optimisée :
[['produit', ['piment', 'banane', 'pomme', 'orange']], ['quantite', ['sachet 1 kg', 'carton 10 kg']], ['aliment', ['fruit', 'legume'], [0, [1, 0, 0, 0]]], ['validite', ['du 1/7/2022 au 31/12/2022']], ['id', [15, 16, 17, 18, 11, 12, 13, 14]], ['disponibilite', ['fin 2022', 'oui'], [4, [1, 0, 1, 1, 1, 1, 0, 0]]], ['prix', [1.5, 15, 0.5, 5, 1, 10, 2, 20]]]Ce format JSON peut également être partagé dans un format binaire (standard CBOR) avec un gain supplémentaire de volume de données.
Les représentations présentées ci-dessus minimisent la quantité de données incluses dans le format JSON.
Les autres représentations ne sont pas détaillées car elles ajoutent des données de structure supplémentaires et conduisent à des tailles de fchiers bien souvent supérieures à celle d'un fichier CSV.
A titre d'exemple, les autres modes de représentation proposés par pandas pour la liste 'produit' sont indiqués ci-dessous.
sr = pd.Series(liste, name="produit")
st = pd.Series([15, 16, 17, 18, 11, 12, 13, 14], name="id")
df = pd.DataFrame({'produit':sr, 'id':st})
print(sr.to_json(orient='index'), '\n')
print(sr.to_json(orient='split'), '\n')
print(df.to_json(orient='records'), '\n')
print(df.to_json(orient='table', index=False))
{"0":"pomme","1":"pomme","2":"orange","3":"orange","4":"piment","5":"piment","6":"banane","7":"banane"} {"name":"produit","index":[0,1,2,3,4,5,6,7],"data":["pomme","pomme","orange","orange","piment","piment","banane","banane"]} [{"produit":"pomme","id":15},{"produit":"pomme","id":16},{"produit":"orange","id":17},{"produit":"orange","id":18},{"produit":"piment","id":11},{"produit":"piment","id":12},{"produit":"banane","id":13},{"produit":"banane","id":14}] {"schema":{"fields":[{"name":"produit","type":"string"},{"name":"id","type":"integer"}],"pandas_version":"1.4.0"},"data":[{"produit":"pomme","id":15},{"produit":"pomme","id":16},{"produit":"orange","id":17},{"produit":"orange","id":18},{"produit":"piment","id":11},{"produit":"piment","id":12},{"produit":"banane","id":13},{"produit":"banane","id":14}]}
Pour mesurer le gain d'un format alternatif au format CSV, deux exemples de fichiers opendata sont utilisés :
Le tableau ci-dessous présente le volume des données (en Ko) de ces deux fichiers avec les formats suivants :
fichier | CSV | JSON simple | JSON unique | JSON optimisé | CSV compressé | JSON optimisé CBOR | parquet | |
---|---|---|---|---|---|---|---|---|
air | 10 871 | 14 971 | 4 048 | 1 222 | 491 | 659 | 270 | |
IRVE | 7 281 | 9 455 | 3 614 | 2 374 | 556 | 1 757 | 577 |
Nota : La taille 'JSON simple' est équivalente à celle d'un fichier CSV 'quoté' (non fourni dans ce tableau).
Ainsi, l'utilisation d'un format JSON permet d'obtenir un gain significatif par rapport à un format CSV.
Le format JSON binaire permet d'obtenir un gain complémentaire mais le volume reste plus élevé qu'avec d'autres formats binaires.
Si l'on reprend la liste des critères d'évaluation définis, on peut compléter les résultats partiels présentés au chapitre 4 sur la base du format 'JSON optimisé':
Les conclusions du chapitre 4 peuvent maintenant être complétées :
Si l'on revient à la question initiale : Quel avenir pour le format CSV ?, deux réponses peuvent être données :
Une première réponse factuelle serait :
Le format CSV présente des limitations fortes mais aucune solution alternative directement déployable n'est actuellement disponible. Ce format devra donc continuer à être utilisé dans l'attente d'un successeur.
Une deuxième réponse plus engagée pourrait être :
Le format CSV présente des limitations fortes néanmoins des solutions alternatives existent et peuvent être mises en place. L'engagement de travaux sur ce sujet permettra de définir et déployer une solution satisfaisante.
Mais c'est peut-être par une autre question que viendra la réponse :
Le déploiement des API en complément (et en alternative) au partage de fichier nécessite de standardiser les structures JSON portées par ces API. La structuration retenue pour ces API dans la cadre des données tabulaires deviendra alors une solution alternative au format CSV.