Outils de manipulation de données en ligne de commande¶
Formats d’échange de données¶
J’ai acheté un livre dont le titre m’a amusé Data Science at the Command Line où j’ai découvert 2 utilitaires, csvkit et jq, que j’utilise très fréquemment (le livre est en ligne accessible gratuitement).
Le livre utilise une définition de la science des données comme étant composée de cinq étapes :
- Obtenir les données
- Nettoyer les données
- Explorer les données
- Mettre en forme les données
- Interpréter les données
Dans les projets, on rencontre différents formats de données que l’on peut avoir à manipuler, à analyser. Parmi ces formats, on rencontre souvent : CSV, JSON, XML, … et des déclinaisons particulières de ces formats (XML-TEI, GeoXXX, …)
Ces formats sont faits pour permettre à des programmes d’échanger des données et ces données deviennent rapidement illisibles par un humain lorsqu’elles grossissent.
Il ne faut pas oublier que ces formats sont souvent bien spécifiés par des RFC, des recommandations W3C, etc. Il est important de les connaître pour ne pas partir sur des mises en oeuvre exotiques (typique avec le CSV, headers et séparateurs particuliers).
- le format CSV (Comma-Separated Values) est décrit par la RFC 4180
- le format JSON (JavaScript Object Notation) est décrit par la RFC 7159
- le XML ou (eXtensible Markup Language) est un langage informatique de balisage générique, spécifié par XML 1.1 W3C recommandation
L’avantage des outils en ligne de commande, c’est que l’on peut les chaîner
avec |
.
Outils CSV¶
Données manipulées¶
COVID19-web_ITrCX3P.csv
: fichier OMS des essais cliniques menés dans le monde pour le COVID-19 (projet COVID INS2I-INSERM)donnees-foncieres-2019-rhone.csv
: transactions immobilières et foncières dans le Rhône (data.gouv.fr)corrections_aurehal_19juin2015.xlsx
: liste de corrections faites par INRIA sur les structures de recherche dans AUReHAL
csvkit¶
csvkit est une suite d’outils pour manipuler et convertir des fichiers CSV.
installation¶
csvkit est codé en Python, il s’installe classiquement dans un environnement virtuel avec pip.
$ pip install csvkit
streaming versus buffering¶
Attention lorsqu’il y a de gros fichiers, il y a des commandes qui chargent toutes les données en mémoire et des commandes qui travaillent en flux.
https://csvkit.readthedocs.io/en/latest/contributing.html#streaming-versus-buffering
csvlook¶
Visualisation tabulaire des données CSV
$ < COVID19-web_ITrCX3P.csv head -n 100 | csvlook | less -S
| TrialID | Last Refreshed on | Public title
| -------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------
| NCT00173563 | 2015-02-19 | Induction of Cytokines in Human Monocytes by SARS-CoV in Adults and Children
| NCT00523276 | 2015-02-19 | SARS Survivor Evaluations
| NCT00533741 | 2015-02-19 | SARS Coronavirus Vaccine (SARS-CoV)
| NCT01376765 | 2015-02-19 | Phase I Dose Escalation SARS-CoV Recombinant S Protein, With and Without Adjuvant, Vaccine Study
| IRCT20180223038837N1 | 2019-01-14 | Comparative study of radiofrequency ablation & foam sclerotherapy for treatment patients suffering from varicose veins
| NCT04246242 | 2020-02-03 | A Randomized Multicenter Controlled Clinical Trial of Arbidol in Patients With 2019 Novel Coronavirus (2019-nCoV)
| ChiCTR2000029953 | 2020-02-17 | Construction and Analysis of Prognostic Predictive Model of Novel Coronavirus Pneumonia (COVID-19)
| ChiCTR2000029949 | 2020-02-17 | A Medical Records Based Study for the Effectiveness of Extracorporeal Membrane Oxygenation in Patients with Severe Novel Coronavirus Pneumonia (COVID-19)
| ChiCTR2000029947 | 2020-02-17 | A Randomized Controlled Trial for Qingyi No. 4 Compound in the treatment of Convalescence Patients of Novel Coronavirus Pneumonia (COVID-19)
less -S
permet d’afficher les données sans lire l’intégralité des données
avant de commencer (mais csvlook
charge l’intégralité des données d’où le
head -n 100`) et l’option -S
permet d’éviter les retours à la ligne
lorsque les lignes dépassent la taille de l’écran (no wrap)
Limiter le nombre de colonnes affichées avec --max-columns
$ < donnees-foncieres-2019-rhone.csv head -n 1000 | csvlook --max-columns 6 | less -S
| id_mutation | date_mutation | numero_disposition | nature_mutation | valeur_fonciere | adresse_numero | ... |
| ----------- | ------------- | ------------------ | ---------------------------------- | --------------- | -------------- | --- |
| | 2017-01-10 | 1 | Vente | 21 000,00 | 9 | ... |
| | 2017-01-05 | 1 | Vente | 578 800,00 | 31 | ... |
| | 2017-01-05 | 1 | Vente | 578 800,00 | 31 | ... |
| | 2017-01-03 | 1 | Vente | 569 800,00 | 21 | ... |
in2csv¶
in2csv convertit le fichier Excel en CSV (le premier onglet par défaut).
$ in2csv corrections_aurehal_19juin2015.xlsx >corrections_aurehal_19juin2015.csv
/home/fconil/Progs/python/csvkit/.venv/lib/python3.8/site-packages/agate/utils.py:276: UnnamedColumnWarning: Column 7 has no name. Using "h".
in2csv peut lister les onglets du fichier Excel et exporter un onglet spécifié
$ in2csv -n TrialDetails63.xlsx
TRIAL
SECONDARY ID
HEALTH CONDITION
CONDITION CODE
INTERVENTION CODE
PRIMARY OUTCOME
SECONDARY OUTCOME
HOSPITAL
POSTCODE
COUNTRY OUTSIDE AUSTRALIA
FUNDING SOURCE
SECONDARY SPONSOR
OTHER COLLABORATOR
ETHICS COMMITTEE
CONTACTS
DATA SHARING STATEMENT
SUMMARY RESULTS
$ in2csv --sheet "COUNTRY OUTSIDE AUSTRALIA" TrialDetails63.xlsx | head | csvlook
| TRIAL ID | COUNTRY | STATE |
| -------- | -------------- | ---------------------------------- |
| 383 488 | Turkey | Izmir |
| 383 373 | India | Several districts of Uttar Pradesh |
| 383 203 | Turkey | Izmir |
| 382 922 | United Kingdom | |
| 383 360 | Bangladesh | Several districts |
| 383 303 | Indonesia | West Java |
csvcut¶
Découvrir les colonnes d’un CSV avec csvcut -n
$ < COVID19-web_ITrCX3P.csv csvcut -n
1: TrialID
2: Last Refreshed on
3: Public title
4: Scientific title
...
Sélectionner les colonnes d’un CSV avec csvcut -c
, je référence
préférentiellement les colonnes par leur numéro (plus rapide et pas de problème
de caractères particuliers dans les noms de commande).
$ < COVID19-web_ITrCX3P.csv head -n 100 | csvcut -c 1,2,7,10,12,18 | csvlook | less -S
i TrialID | Last Refreshed on | Date registration | Source Register | Recruitment Status | Target size >
| -------------------- | ----------------- | ----------------- | ------------------ | ------------------ | ----------------------------------------------------------------------------------------------->
| NCT00173563 | 2015-02-19 | 12/09/2005 | ClinicalTrials.gov | Recruiting | 10 >
| NCT00523276 | 2015-02-19 | 30/08/2007 | ClinicalTrials.gov | Not recruiting | 0 >
| NCT00533741 | 2015-02-19 | 20/09/2007 | ClinicalTrials.gov | Not recruiting | 0 >
| NCT01376765 | 2015-02-19 | 16/06/2011 | ClinicalTrials.gov | Not recruiting | 0 >
| IRCT20180223038837N1 | 2019-01-14 | 2018-12-08 | IRCT | Recruiting | 60 >
| NCT04246242 | 2020-02-03 | 27/01/2020 | ClinicalTrials.gov | Not recruiting | 500 >
| ChiCTR2000029953 | 2020-02-17 | 2020-02-17 | ChiCTR | Not Recruiting | survival group:200;died:200; >
| ChiCTR2000029949 | 2020-02-17 | 2020-02-16 | ChiCTR | Not Recruiting | Case series:40; >
| ChiCTR2000029947 | 2020-02-17 | 2020-02-16 | ChiCTR | Not Recruiting | experimental group :100;control group :100; >
options générales¶
- -d pour spécifier le délimiteur si ce n’est pas une virgule
- -t pour indiquer qu’il s’agit d’un fichier tabulaire
- -e pour spécifier l’encodage
Voir https://csvkit.readthedocs.io/en/latest/common_arguments.html
csvgrep¶
Recherche de chaîne de caractère dans une colonne :
-c
indique la colonne concernée-m
recherche une correspondance exacte avec la chaîne de caractère passée en paramètre
$ csvgrep -c Sigle -m BEAGLE corrections_aurehal_19juin2015.csv | csvlook
| Sigle | idStructureHal | siidRNSR | sigleCRI | libelleEN | Id HAL disparus après modification | OK le | h |
| ------ | -------------- | ---------- | -------- | ---------------------------------------------- | ---------------------------------- | ---------- | - |
| BEAGLE | 160 051 | 201120997E | GRA | Artificial Evolution and Computational Biology | 397 150 | 2015-06-17 | |
Avec -r
on peut faire une recherche avec une expression régulière
$ csvgrep -c Sigle -r "(?i)beagle" corrections_aurehal_19juin2015.csv | csvlook
| Sigle | idStructureHal | siidRNSR | sigleCRI | libelleEN | Id HAL disparus après modification | OK le | h |
| ------ | -------------- | ---------- | -------- | ---------------------------------------------- | ---------------------------------- | ---------- | - |
| BEAGLE | 160 051 | 201120997E | GRA | Artificial Evolution and Computational Biology | 397 150 | 2015-06-17 | |
Je l’ai utilisé lorsque je recevais périodiquement les fichiers Excel de modifications de structures faites dans AUReHAL, cela me permettait de chercher rapidement si la structure que l’on surveillait était concernée.
csvsort¶
$ < donnees-foncieres-2019-rhone.csv head -n 1000 | csvsort -c type_local | csvlook --max-columns 6 | less -S
| id_mutation | date_mutation | numero_disposition | nature_mutation | valeur_fonciere | adresse_numero | ... |
| ----------- | ------------- | ------------------ | ---------------------------------- | --------------- | -------------- | --- |
| | 2017-01-05 | 1 | Vente | 578 800,00 | 31 | ... |
| | 2017-01-05 | 1 | Vente | 578 800,00 | 31 | ... |
| | 2017-01-03 | 1 | Vente | 569 800,00 | 21 | ... |
| | 2017-01-05 | 1 | Vente | 240 000,00 | 31 | ... |
| | 2017-01-09 | 1 | Vente | 240 000,00 | 50 | ... |
csvstat¶
Compter le nombre de lignes. Il peut y avoir des sauts de ligne dans les champs
donc wc -l
n’est pas sûr.
$ csvstat --count TrialDetails63.1.csv
258
$ wc -l TrialDetails63.1.csv
8901 TrialDetails63.1.csv
Affiche des statistiques sur les colonnes du CSV, permet d’avoir une information sur les données contenues et un aperçu de leur qualité.
$ csvstat COVID19-web_ITrCX3P.csv
...
12. "Recruitment Status"
Type of data: Text
Contains null values: True (excluded from calculations)
Unique values: 6
Longest value: 14 characters
Most common values: Recruiting (5030x)
Not recruiting (3837x)
Not Recruiting (3207x)
Authorised (442x)
None (43x)
...
16. "Inclusion gender"
Type of data: Text
Contains null values: True (excluded from calculations)
Unique values: 21
Longest value: 32 characters
Most common values: All (6643x)
Both (2237x)
None (1789x)
<br>Female: yes<br>Male: yes<br> (571x)
Both, male and female (410x)
csvsql¶
ATTENTION : L’utilisation naïve de csvsql
peut conduire à des noms de
tables et de colonnes difficilement utilisables dans les requêtes.
- Il faut éviter les tirets dans le nom de la table
- Il faut éviter les espaces, les tirets et les caractères spéciaux dans les noms de colonnes
Si cela n’est pas fait, on doit mettre le nom de la table et le nom des champs entre guillemets.
utilisation simple¶
csvsql --db
va créer et remplir une base de données SQLite à partir du CSV
en inférant le type des colonnes. Sans l’option --insert
, les données ne
sont pas insérées dans la table.
On précise le nom de la table créée avec --tables
pour éviter un nom de
table compliqué à manipuler.
$ csvsql --db sqlite:///donnees-foncieres-2019-rhone.db --tables immobilier --insert donnees-foncieres-2019-rhone.csv
sqlite> select count(id), type_local from immobilier group by type_local;
count(id) = 24988
type_local =
count(id) = 25171
type_local = Appartement
count(id) = 20118
type_local = Dépendance
count(id) = 6013
type_local = Local industriel. commercial ou assimilé
count(id) = 9708
type_local = Maison
csvsql --query
permet d’exécuter une requête SQL sur un fichier csv qui est
chargé dans une base SQLite en mémoire.
$ csvsql --query "select count(id), type_local from 'donnees-foncieres-2019-rhone' group by type_local;" donnees-foncieres-2019-rhone.csv | csvlook
| count(id) | type_local |
| --------- | ---------------------------------------- |
| 24 988 | |
| 25 171 | Appartement |
| 20 118 | Dépendance |
| 6 013 | Local industriel. commercial ou assimilé |
| 9 708 | Maison |
Bien que ce soit intéressant, j’ai abandonné cet usage :
- dès que le fichier CSV est gros cela prend du temps
- car on a souvent plusieurs requêtes à exécuter, ou on adapte souvent la requête initiale en fonction des résultats et cela implique de recréer la base à chaque fois
- on est confronté à plusieurs difficultés liés à la création et au remplissage de la table
difficultés¶
Les noms de colonnes du header de COVID19-web_ITrCX3P.csv
contiennent des
espaces.
$ csvsql --db sqlite:///COVID19.db --tables COVID19 --insert COVID19-web_ITrCX3P.csv
sqlite> select distinct("Recruitment Status") from COVID19;
Recruitment Status = Recruiting
Recruitment Status = Not recruiting
Recruitment Status = Not Recruiting
Recruitment Status =
Recruitment Status = Authorised
Recruitment Status = Not Available
csvsql -i
va inférer le type des colonnes (comme csvstat
) pour générer
une instruction CREATE TABLE
pour un type de base de données spécifié. Cela
permet d’adapter le nom de la table et des colonnes pour créer une base propre.
$ csvsql -i sqlite COVID19-web_ITrCX3P.csv
CREATE TABLE "COVID19-web_ITrCX3P" (
"TrialID" VARCHAR NOT NULL,
"Last Refreshed on" DATE NOT NULL,
"Public title" VARCHAR,
"Scientific title" VARCHAR,
"Acronym" VARCHAR,
...
);
Mais je n’ai pas réussi à charger les données avec csvsql
et des noms de
colonnes modifiées
# KO
$ csvsql --db sqlite:///COVID19_fixed.db --before-insert "$(cat create-table-COVID19-fixed.sql)" --insert COVID19-web_ITrCX3P.csv
# KO
$ < COVID19-web_ITrCX3P.csv sed "1d" > COVID19-web_ITrCX3P.no-header.csv
$ csvsql --db sqlite:///COVID19_fixed.db --no-create --table COVID19 --before-insert "$(cat create-table-COVID19-fixed.sql)" -H --insert COVID19-web_ITrCX3P.no-header.csv
On a une erreur plus parlante en pur SQLite où l’import fonctionne en enlevant les contraintes CHECK générées.
sqlite> .read create-table-COVID19-fixed.sql
sqlite> .mode csv
sqlite> .import COVID19-web_ITrCX3P.no-header.csv COVID19
...
COVID19-web_ITrCX3P.no-header.csv:25792: INSERT failed: CHECK constraint failed: COVID19
sqlite> .read create-table-COVID19-fixed-no-check.sql
sqlite> .mode csv
sqlite> .import COVID19-web_ITrCX3P.no-header.csv COVID19
sqlite> select count(TrialID) from COVID19;
count(TrialID)
12564
Même sans ces contraintes CHECK, cela ne fonctionne toujours pas avec
csvsql
.
$ csvsql --db sqlite:///COVID19_fixed.db --no-create --table COVID19 --before-insert "$(cat create-table-COVID19-fixed-no-check.sql)" -H --insert COVID19-web_ITrCX3P.no-header.csv
Il faut renommer les colonnes dans le header.
$ < COVID19-web_ITrCX3P.csv sed "1s/ /_/g" > COVID19-web_ITrCX3P.header-fixed.csv
$ csvsql --db sqlite:///COVID19_header_fixed.db --table COVID19 --insert COVID19-web_ITrCX3P.header-fixed.csv
Commandes non utilisées¶
sql2csv¶
Je ne m’en suis jamais servie, quand j’ai une base SQL, j’ai tendance à utiliser SQL directement.
$ sql2csv --db sqlite:///donnees-foncieres-2019-rhone.db --query "select count(id), type_local from immobilier group by type_local;" | csvlook
| count(id) | type_local |
| --------- | ---------------------------------------- |
| 24 988 | |
| 25 171 | Appartement |
| 20 118 | Dépendance |
| 6 013 | Local industriel. commercial ou assimilé |
| 9 708 | Maison |
csvclean¶
La commande place placer les lignes valides dans un fichier [basename]_out.csv
et les lignes erronées dans un fichier [basename]_err.csv
.
Avec l’option -n
, la commande fonction en dry-run.
https://csvkit.readthedocs.io/en/latest/scripts/csvclean.html
csvjoin¶
Permet de fusionner 2 fichiers CSV ou plus (avec des colonnes différentes j’imagine).
https://csvkit.readthedocs.io/en/latest/scripts/csvclean.html
csvstack¶
Permet de fusionner les lignes de plusieurs CSV.
https://csvkit.readthedocs.io/en/latest/scripts/csvstack.html
diff de CSV¶
csv-diff¶
https://datasette.io/tools/csv-diff
$ pip install csv-diff
Différence entre 2 fichiers récupérés sur data.gouv.fr et qui semble avoir la même clé pour les député et à peu près les même colonnes.
$ csv-diff --key id --format csv deputes-historique/deputes-historique.csv deputes-actifs/deputes-15.csv | more
2 columns added, 2 columns removed, 575 rows changed, 1022 rows removed
2 columns added
placeHemicycle
premiereElection
2 columns removed
active
legislatureLast
575 rows changed
id: PA605036
age: "41" => "40"
twitter: "@damienabad" => ""
facebook: "damienabad.depute" => ""
experienceDepute: "10 ans" => "9 ans"
scoreParticipationSpecialite: "0.21" => "0.30"
scoreLoyaute: "0.911" => "0.913"
scoreMajorite: "0.352" => "0.345"
dateMaj: "2022-01-19" => "2021-02-14"
id: PA719866
age: "45" => "44"
experienceDepute: "5 ans" => "4 ans"
scoreParticipation: "0.30" => "0.29"
scoreLoyaute: "0.991" => "0.992"
scoreMajorite: "0.991" => "0.992"
dateMaj: "2022-01-19" => "2021-02-14"
...
Voir aussi https://datasette.io/
daff¶
$ pip install daff
Peut s’utiliser en ligne de commande et programmatiquement.
Indiqué dans la documentation csvkit pour le diff de fichiers CSV : https://csvkit.readthedocs.io/en/latest/cli.html
$ daff --input-format csv --id id ~/Donnees/deputes/data.gouv.fr/deputes-historique/deputes-historique.csv ~/Donnees/deputes/data.gouv.fr/deputes-actifs/deputes-15.csv
Envoie le résultat dans un navigateur avec --www
$ daff --www --input-format csv --id id ~/Donnees/deputes/data.gouv.fr/deputes-historique/deputes-historique.csv ~/Donnees/deputes/data.gouv.fr/deputes-actifs/deputes-15.csv
Voir le résultat : tmpz1kk4q6q.html.
Ignorer une ou plusieurs colonnes
$ daff --www --input-format csv --ignore "Export date" --id TrialID COVID19-web_FlhWKml.csv COVID19-web_PXQX3YP.csv
Voir le résultat : tmpk49hjf6k.html.
validation de csv¶
Je souhaitais trouver une solution de validation de la qualité des fichiers CSV.
frictionless¶
$ pip install frictionless[sql]
Validation simple d’un fichier CSV
$ frictionless validate COVID19-web_ITrCX3P.csv
# -------
# invalid: COVID19-web_ITrCX3P.csv
# -------
===== ===== ========== =============================================================================================================================
row field code message
===== ===== ========== =============================================================================================================================
593 38 type-error Type error in the cell "True " in row "593" and field "Bridging flag truefalse" at position "38": type is "boolean/default"
602 38 type-error Type error in the cell "True " in row "602" and field "Bridging flag truefalse" at position "38": type is "boolean/default"
Génération d’un schéma YAML pour la validation du CSV.
Table Schema (frictionless standard) : https://specs.frictionlessdata.io/table-schema/#language
$ frictionless describe COVID19-web_ITrCX3P.csv --type schema >oms-schema.yaml
# --------
# metadata: COVID19-web_ITrCX3P.csv
# --------
fields:
- name: TrialID
type: string
- name: Last Refreshed on
type: string
- name: Public title
type: string
- name: Scientific title
type: string
- name: Acronym
type: any
- name: Primary sponsor
type: string
- name: Date registration
type: string
- name: Date registration3
type: integer
- name: Export date
type: string
- name: Source Register
type: string
- name: web address
type: string
- name: Recruitment Status
type: string
...
- name: Retrospective flag
type: string
- name: Bridging flag truefalse
type: boolean
- name: Bridged type
type: string
- name: results yes no
type: any
Les valeurs True contiennent un espace en trop dans le CSV pour le champ « Bridging flag truefalse » : « True «
$ frictionless validate COVID19-web_ITrCX3P.csv --schema oms-schema.yaml
# invalid: COVID19-web_ITrCX3P.csv
# -------
===== ===== ========== =============================================================================================================================
row field code message
===== ===== ========== =============================================================================================================================
593 38 type-error Type error in the cell "True " in row "593" and field "Bridging flag truefalse" at position "38": type is "boolean/default"
602 38 type-error Type error in the cell "True " in row "602" and field "Bridging flag truefalse" at position "38": type is "boolean/default"
626 38 type-error Type error in the cell "True " in row "626" and field "Bridging flag truefalse" at position "38": type is "boolean/default"
639 38 type-error Type error in the cell "True " in row "639" and field "Bridging flag truefalse" at position "38": type is "boolean/default"
$ < ~/Documents/RstDocs/mespres/data-tools-in-cmde-line/csv/COVID19-web_ITrCX3P.csv csvcut -c 1,13,37,38,40 | sed -n "1p;590,600p"
TrialID,other records,Retrospective flag,Bridging flag truefalse,results yes no
ChiCTR2000030331,No,No,False,
ChiCTR2000030328,No,Yes,False,
EUCTR2020-001301-23-FR,No,Yes,False,
EUCTR2020-001188-96-FR,Yes,Yes,True ,
EUCTR2020-001457-43-FR,No,Yes,False,
EUCTR2020-001442-19-ES,No,No,False,
EUCTR2020-001409-21-ES,No,No,False,
EUCTR2020-001492-33-FR,No,Yes,False,
EUCTR2020-001194-69-ES,No,No,False,
EUCTR2020-001565-37-ES,No,No,False,
EUCTR2020-001421-31-ES,No,No,False,
Outils JSON¶
Données manipulées¶
VTANR5L15V*.json
: Votes de l’Assemblée NationaleEudraCT_*.json
: Stockage d’essais clinique du registre européen (extraits du site web) au format json
jq¶
- Manuel : https://stedolan.github.io/jq/manual/
- Tutorial : https://stedolan.github.io/jq/tutorial/
- Source : https://github.com/stedolan/jq
- Tester l’utilitaire en ligne : https://jqplay.org/
- Cookbook : https://github.com/stedolan/jq/wiki/Cookbook#list-keys-used-in-any-object-in-a-list
- FAQ : https://github.com/stedolan/jq/wiki/FAQ
S’il s’agit de données que l’on récupère (Assemblée nationale), on a besoin de découvrir progressivement l’architecture des données json que l’on récupère.
$ < votes/VTANR5L15V3732.json jq '.'
$ < votes/VTANR5L15V3732.json jq '.' -C | less -R
Le plus basique consiste à tout afficher, jq
applique une coloration et une
mise en page qui facilite la lecture de données json qui peuvent être sur une
seule ligne.
{
"scrutin": {
"@xmlns": "http://schemas.assemblee-nationale.fr/referentiel",
"@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
"uid": "VTANR5L15V3732",
"numero": "3732",
"organeRef": "PO717460",
"legislature": "15",
"sessionRef": "SCR5A2021O1",
"seanceRef": "RUANR5L15S2021IDS24293",
"dateScrutin": "2021-04-17",
"quantiemeJourSeance": "2",
"typeVote": {
"codeTypeVote": "SPO",
"libelleTypeVote": "scrutin public ordinaire",
"typeMajorite": "majorité absolue des suffrages exprimés"
},
"sort": {
"code": "rejeté",
"libelle": "L'Assemblée nationale n'a pas adopté"
},
"titre": "l'amendement n° 4548 de M. Balanant et les amendements identiques suivants après l'article 71 du projet de loi portant lutte contre le dérèglement climatique et renforcement de la résilience face à ses effets (première lecture).",
"demandeur": {
Ici la structure de base est un dictionnaire avec une seule clé.
$ < votes/VTANR5L15V3732.json jq 'keys'
[
"scrutin"
]
On va donc extraire la valeur de la clé « scrutin », qui est un dictionnaire et
dont on va demander les clés en combinant les filtres avec |
$ < votes/VTANR5L15V3732.json jq '.scrutin | keys'
[
"@xmlns",
"@xmlns:xsi",
"dateScrutin",
"demandeur",
"legislature",
"miseAuPoint",
"modePublicationDesVotes",
"numero",
"objet",
"organeRef",
"quantiemeJourSeance",
"seanceRef",
"sessionRef",
"sort",
"syntheseVote",
titre",
"typeVote",
"uid",
"ventilationVotes"
]
On peut extraire quelques valeurs du dictionnaire dans un objet avec {}
où
l’on reprend les clés à extraire.
$ < votes/VTANR5L15V3732.json jq '.scrutin | {titre, uid, demandeur}'
{
"titre": "l'amendement n° 4548 de M. Balanant et les amendements identiques suivants après l'article 71 du projet de loi portant lutte contre le dérèglement climatique et renforcement de la résilience face à ses effets (première lecture).",
"uid": "VTANR5L15V3732",
"demandeur": {
"texte": "Présidente du groupe \"Socialistes et apparentés\"",
"referenceLegislative": null
}
}
Il y a un vote par fichier, comment obtenir une liste des votes et leur fichier ?
Pour exporter en csv (@csv
), il faut générer un tableau json avec []
.
-r
permet d’éviter que chaque ligne soit encadrée par des guillemets.
$ echo '"uid","titre"' >liste-votes.csv
$ ls -1 votes/*.json | while read A
> do
> jq -r '.scrutin | [.uid, .titre] | @csv' $A >>liste-votes.csv
> done
< covid/EudraCT_2020-001385-11_ES.json jq -C '.' | less -R
< covid/EudraCT_2020-001385-11_ES.json jq 'keys'
# Ouvrir l'URL de l'essai
< covid/EudraCT_2020-001385-11_ES.json jq '.metadata'
< covid/EudraCT_2020-001385-11_ES.json jq -C '.sections' | less -R
< covid/EudraCT_2020-001385-11_ES.json jq -C '.sections[2] ' | less -R
# Afficher toutes les sous-sections en supprimant le tableau englobant de la sous-section
< covid/EudraCT_2020-001385-11_ES.json jq -C '.sections[] | .subsections | .[] ' | less -R
# Extraire le nom de toutes les sous-sections
< covid/EudraCT_2020-001385-11_ES.json jq -C '.sections[] | .subsections[] | .name'
# Y a-t-il une sous-section qui se répète
# Pas pratique sûrement combinable
< covid/EudraCT_2020-001385-11_ES.json jq -C '.sections[] | .subsections[] | .name | contains(": 2")'
< covid/EudraCT_2020-001385-11_ES.json jq -C '.sections[] | .subsections | .[]' | grep ": 2"
# Récupérer l'élément "A.1" des sections A des fichiers
ls -1 covid/EudraCT_*.json | while read A; do echo $A; cat $A | jq '.sections[] | select (.id == "section-a") | .subsections[] | .data[] | select(.id == "A.1")'; done
Outils XML¶
Matériel - code
Voir ~/COVID-19/Trial-registries/ISRCTN-UK
~/Progs/xml/xslt
~/PyEnvs-a-trier/PyEnv3.5.2/webscraping/automate-liris-tei-export
1er cas : XML-TEI pour HAL¶
Format utilisé pour le transfert des publications du Système d’Information LIRIS vers HAL.
Besoin :
- Reformatter le fichier reçu du Système d’Information pour le rendre humainement lisible
- Vérifier que le fichier est syntaxiquement correct par rapport au schéma XSD du CCSD-HAL
Outil utilisé xmllint, xmllint est un outil associé à libxml2 et qui nécessite le paquet libxml2-utils.
Installation : $ sudo apt install libxml2-utils
$ wget -O xmltei/hal-tei-01529866.xml -nv https://hal.archives-ouvertes.fr/hal-01529866v1/tei
Reformatter le fichier (pas nécessaire pour celui récupéré de HAL)
$ xmllint --format "./xmltei/liris-tei-2853.xml" >"liris-tei-2853.xml"
Vérifier que le fichier est syntaxiquement valide
La vérification ne fonctionne pas avec le fichier que je viens de récupérer de
HAL parce que le schéma XSD que j’utilise est un peu ancien et qu’il servait à
vérifier les fichiers déposés il y a quelques années. Mais ça montrer
l’affichage de Schemas validity error
.
$ xmllint --schema ./tei/aofr-sword.xsd "liris-tei-2853.xml" >"./xmltei/liris-tei-2853.xml" 2>&1
$ xmlstarlet val -s tei/aofr-sword.xsd "liris-tei-2853.xml"
2ème cas : Projet COVID INS2I-INSERM¶
Présentation simple : On récupère des métadonnées des études cliniques de différents pays / continents. On en extrait une partie que l’on transforme pour éviter au CRESS qui analyse ces études de récupérer ces métadonnées manuellement.
On manipule une grande variété de formats : CSV, XML, accès BD, scrapping de site web, …
Dernièrement, j’ai voulu extraire les métadonnées du « registre » anglais puisque les anglais ne déposent plus les métadonnées de leurs études dans le registre européen depuis le Brexit. Une API est fournie qui renvoie du XML.
Il m’a fallu plonger un peu plus sur la façon dont je pouvais analyser puis extraire des données de ce format. J’ai consulté quelques documentations (Structurez vos données avec XML) et regardé XPath (XML Path Language) pour l’extraction.
XMLStarlet¶
J’avais vu qu’il existait un outil XMLStarlet que j’ai utilisé pour explorer les données récupérées du registre anglais. Voir mémo xmlstarlet et documentation xmlstarlet.
el -v
: afficher les éléments de la structure du document.
$ xmlstarlet <commande> --help
$ xmlstarlet el -v liris-tei-2853.xml
sel -l -v "//biblFull/titleStmt/title"
: extraire des éléments selon un
template XPath.
Malheureusement, l’utilisation d’un namespace par défaut dans le XML pose problème aux 2 outils que j’ai utilisés et la commande ci dessous ne retourne rien.
$ xmlstarlet sel -t -v "//biblFull/titleStmt/title" liris-tei-2853.xml
$ head liris-tei-2853.xml | bat
Le 1er contournement nécessite de modifier tous les éléments du chemin XPath : bof
$ xmlstarlet sel -N x="http://www.tei-c.org/ns/1.0" -t -v "//x:biblFull/x:titleStmt/x:title" liris-tei-2853.xml
# Plus simplement
$ xmlstarlet sel -t -v "//_:biblFull/_:titleStmt/_:title" liris-tei-2853.xml
La 2ème solution consiste à transformer le XML en un format pyx, dont on supprime les namespace et que l’on transforme à nouveau en XML.
C’est « bourrin » mais ça m’a suffit pour mon exploration des données.
$ xmlstarlet pyx liris-tei-2853.xml | grep -v "^Axmlns" | xmlstarlet p2x
| xmlstarlet sel -t -v "//biblFull/titleStmt/title"
J’ai pris l’habitude de nommer le fichier résultant xxx.no-default-ns.xml
$ xmlstarlet pyx query-covid-and-recruiting-curl_internal.xml | grep -v "^Axmlns"
| xmlstarlet p2x >query-covid-and-recruiting-curl_internal.no-default-ns.xml
J’ai ensuite pu faire les explorations souhaitées sur le résultat d’une recherche sur les études COVID.
$ xmlstarlet sel -t -v "/allTrials/@totalCount" isrctn/query-covid-and-recruiting-curl_internal.no-default-ns.xml
$ xmlstarlet sel -t -v "//fullTrial/trial/externalRefs/doi" isrctn/query-covid-and-recruiting-curl_internal.no-default-ns.xml
$ xmlstarlet sel -t -v "count(//fullTrial/trial/externalRefs/doi)" isrctn/query-covid-and-recruiting-curl_internal.no-default-ns.xml
tr xsl/transform-search-3.xsl
: transformation selon la feuille de style
transform-search-3.xsl
.
Exemple de quelques transformations XSLT
$ xmlstarlet tr xsl/transform-search-3.xsl isrctn/query-covid-and-recruiting-curl_internal.no-default-ns.xml
$ xmlstarlet tr xsl/transform-search-3.txt.xsl isrctn/query-covid-and-recruiting-curl_internal.no-default-ns.xml
Avertissement
xmlslarlet repose sur la bibliothèque C libxslt qui met en œuvre la
version 1.0 de XSLT. Elle ne dispose donc pas de certaines fonctions comme
replace()
.
https://www.oreilly.com/library/view/xslt-2nd-edition/9780596527211/ch05s06s02.html
$ sudo apt-cache depends xmlstarlet
xmlstarlet
Dépend: libc6
Dépend: libxml2
Dépend: libxslt1.1
$ sudo apt show libxslt1.1
Description: traitement XSLT 1.0 - bibliothèque d'exécution
xml2json¶
Test de l’outil xml2json par curiosité.
$ < isrctn/ISRCTN17072692-internal.no-default-ns.xml xml2json | jq '.'
xmllint¶
Retour sur l’outil xmllint et son option --shell
très intéressante pour
explorer un fichier XML.
Dommage que l’on ne puisse pas rappeler les commandes précédentes.
$ xmllint --shell isrctn/query-covid-and-recruiting-curl_internal.no-default-ns.xml
/ > help
> xpath //fullTrial/trial/externalRefs/doi/text()
> xpath count(//fullTrial/trial/externalRefs/doi)
> cd allTrials
allTrials > dir
allTrials > cd fullTrial[1]
fullTrial > ls
fullTrial > cd trial
trial > ls
trial > cat externalRefs
-------
<externalRefs>
<doi>10.1186/ISRCTN79815558</doi>
<eudraCTNumber>2021-000522-97</eudraCTNumber>
<clinicalTrialsGovNumber>Nil known</clinicalTrialsGovNumber>
<protocolSerialNumber>VLA2001-301, IRAS 294164</protocolSerialNumber>
</externalRefs>
trial > du
trial > grep doi
trial > cd trialDescription
trialDescription > ls
trialDescription > pwd