arthur.bebou

Le site arthur.bebou.netlib.re - retour accueil

git clone git://bebou.netlib.re/arthur.bebou
Log | Files | Refs |

index.sh (36909B)


      1 #! page
      2 title: De la datascience modeste dans votre terminal
      3 author: Arthur Pons
      4 description: Explorons comment faire de la datascience modeste via des outils simples accessibles en ligne de commande
      5 publication: 2023-10-18
      6 sectionmd: main
      7 
      8 
      9 ## Avant propos, pour aller plus loin
     10 
     11 Pour voir des exemples plus avancés vous pouvez jeter un coup d'oeil à cette
     12 [tentative de reproduction] d'un [article analysant des données à propos d'un
     13 tournoi de starcraft].
     14 
     15 ## Introduction
     16 
     17 L'objectif de cet article est de démonter qu'il est possible de traiter
     18 des données scientifiques en ligne de commande, via des outils
     19 traditionnellement disponibles sur les distributions linux et/ou des outils
     20 relativement simples se manipulant bien dans un terminal. On ne fera rien
     21 de très complexe parce que
     22 
     23   1. Je ne sais pas le faire, je ne suis pas data-scientist
     24   2. Les outils que nous verrons sont volontairement assez simples
     25 
     26 Nous allons traiter des fichiers TSV[^1] parce que j'ai constaté que
     27 de très nombreux projets scientifiques font du traitement de données
     28 tabulaires. Si l'on parvient à faire des choses intéressantes avec
     29 on répond donc à de nombreux besoins. Il est fréquent que les chercheureuses
     30 dégainent des outils très complexes, capables de faire bien plus que ce que
     31 l'on va voir, pour faire de opérations très simples. L'idée est ici
     32 de rendre plus tangible ce qu'il est possible de faire sans déployer ces
     33 outils pour éviter de vider son bol de céréales avec une [cuillère de deux
     34 mètres - 524Ko].
     35 
     36 Je vais introduire beaucoup de commandes sans les expliquer en profondeur.
     37 Nombreuses d'entre elles ne seront utilisées que d'une seule façon ou dans un
     38 seul contexte alors qu'elles regorgent d'options. Je vous incite fortement à
     39 être curieux et curieuses et à lire les manuels des ces commandes en tapant
     40 `man commande`, `commande --help` ou encore `info commande` pour en apprendre
     41 plus. Quand j'écris "cette commande sert à/fait cela", il est très rare qu'elle
     42 ne serve *qu'à* cela.
     43 
     44 Presque tout ce que je vais faire peut ête reproduit d'autres manières,
     45 certaines possiblement meilleures. C'est le propre des interfaces en ligne de
     46 commande UNIX. Si vous voulez proposer des alternatives n'hésitez pas à
     47 contacter le collectif par mail ou en présentiel, je modifierai l'article avec
     48 plaisir.
     49 
     50 Finalement, beaucoup de tabulations seront utilisés dans le code présenté dans
     51 cet article. Malheureusement elles ressemblent à des espaces dans les blocs de
     52 code, je vais réfléchir à un moyen d'y remédier. En attendant vous pouvez
     53 lire cet article en markdown dans vim en faisant `curl
     54 http://katzele.netlib.re/articles/datascience-cli/index.md | vim -` et faire
     55 apparaître les tabulations avec la commande vim `set listchars=tab:\ \ \|`.
     56 Pour comprendre ce que cela fait vous pouvez consulter l'aide `:help listchars`.
     57 
     58 ## C'est parti
     59 
     60 ### CSV vs TSV
     61 
     62 Prenons ce fichier (de 500Ko) pour exemple de jeu de donnée :
     63 https://raw.githubusercontent.com/datasets/population/main/data/population.csv
     64 
     65 Je vais d'abord le convertir en TSV. Les raisons pour lesquelles je préfère le
     66 TSV sont résumées dans [cet article d'une équipe d'eBay].
     67 
     68 Un moyen de convertir notre fichier CSV en TSV serait de simplement remplacer
     69 toutes les occurrences de `,` par des tabulations. L'outil `tr`[^2] permet de
     70 traduire un ensemble de caractères en un autre. Le début de `population.csv`
     71 contient :
     72 
     73 	Country Name,Country Code,Year,Value
     74 	Aruba,ABW,1960,54608
     75 	Aruba,ABW,1961,55811
     76 	Aruba,ABW,1962,56682
     77 	Aruba,ABW,1963,57475
     78 	Aruba,ABW,1964,58178
     79 	Aruba,ABW,1965,58782
     80 	Aruba,ABW,1966,59291
     81 	Aruba,ABW,1967,59522
     82 	Aruba,ABW,1968,59471
     83 
     84 Si l'on fait `tr ',' '\t' < population.csv > population.tsv'[^3] :
     85 
     86 	Country Name	Country Code	Year	Value
     87 	Aruba	ABW	1960	54608
     88 	Aruba	ABW	1961	55811
     89 	Aruba	ABW	1962	56682
     90 	Aruba	ABW	1963	57475
     91 	Aruba	ABW	1964	58178
     92 	Aruba	ABW	1965	58782
     93 	Aruba	ABW	1966	59291
     94 	Aruba	ABW	1967	59522
     95 	Aruba	ABW	1968	59471
     96 
     97 on obtient bien un fichier TSV. Ici la tâche a été très simple puisque nos
     98 données ne contiennent jamais de virgules ni aucun échappement en particulier.
     99 Résoudre cette tâche à coup sûr est moins triviale qu'il n'y paraît. J'opte
    100 donc généralement d'utiliser [csv2tsv] des outils [tsv-utils].
    101 
    102 	csv2tsv population.csv > population.tsv
    103 
    104 ### Compter des entrées
    105 
    106 Première question, combien y a-t-il d'entrée dans notre fichier / base de
    107 donnée ? `wc` est un outil permettant de compter les lignes d'un fichier :
    108 
    109 	wc -l population.tsv
    110 	16401 population.tsv
    111 
    112 Si on ne passe pas le fichier en argument mais directement dans stdin `wc` n'affiche
    113 pas le nom du fichier en sortie
    114 
    115 	wc -l < population.tsv
    116 	16401
    117 
    118 Si vous avez un jeu de donnée contenant pleins de TSV et que vous voulez exécuter `wc` sur plusieurs de ces fichiers :
    119 
    120 	wc -l population.tsv population.tsv
    121 	16401 population.tsv
    122 	16401 population.tsv
    123 	32802 total
    124 
    125 `wc` vous fera gentiment un affichage avec le total et le sous total par fichier.
    126 
    127 Admettons que nous voulions maintenant regarder à l'intérieur du fichier. Il est souvent intéressant de n'afficher que
    128 le début ou la fin pour éviter d'être submergé·e par un mur de texte. `head` et `tail` servent à cela :
    129 
    130 	head population.tsv
    131 	Country Name	Country Code	Year	Value
    132 	Aruba	ABW	1960	54608
    133 	Aruba	ABW	1961	55811
    134 	Aruba	ABW	1962	56682
    135 	Aruba	ABW	1963	57475
    136 	Aruba	ABW	1964	58178
    137 	Aruba	ABW	1965	58782
    138 	Aruba	ABW	1966	59291
    139 	Aruba	ABW	1967	59522
    140 	Aruba	ABW	1968	59471
    141 
    142 Si jamais vous voulez un affichage où les colonnes sont alignées vous pouvez
    143 utiliser `column` en lui disant que le séparateur est la tabulation[^4] :
    144 
    145 	head population.tsv | column -ts '	'
    146 	Country Name  Country Code  Year  Value
    147 	Aruba         ABW           1960  54608
    148 	Aruba         ABW           1961  55811
    149 	Aruba         ABW           1962  56682
    150 	Aruba         ABW           1963  57475
    151 	Aruba         ABW           1964  58178
    152 	Aruba         ABW           1965  58782
    153 	Aruba         ABW           1966  59291
    154 	Aruba         ABW           1967  59522
    155 	Aruba         ABW           1968  59471
    156 
    157 Sinon [tsv-utils] fournit son propre outil [tsv-pretty] qui semble faire la
    158 même chose mais probablement plus/mieux (j'ai pas vérifié) :
    159 
    160 	head population.tsv | tsv-pretty
    161 	Country Name  Country Code  Year  Value
    162 	Aruba         ABW           1960  54608
    163 	Aruba         ABW           1961  55811
    164 	Aruba         ABW           1962  56682
    165 	Aruba         ABW           1963  57475
    166 	Aruba         ABW           1964  58178
    167 	Aruba         ABW           1965  58782
    168 	Aruba         ABW           1966  59291
    169 	Aruba         ABW           1967  59522
    170 	Aruba         ABW           1968  59471
    171 
    172 ### Lister des valeurs
    173 
    174 Disons que l'on souhaite obtenir la liste de tous les pays présents dans ce jeu
    175 de données. D'abord restreignons nous à la colonne qui nous intéresse, la
    176 première, avec `cut` :
    177 
    178 	head population.tsv | cut -f1
    179 	Country Name
    180 	Aruba
    181 	Aruba
    182 	Aruba
    183 	Aruba
    184 	Aruba
    185 	Aruba
    186 	Aruba
    187 	Aruba
    188 	Aruba
    189 
    190 Pas besoin de dire à `cut` quel est le délimiteur, la tabulation est celui par
    191 défaut. On lui dit à l'aide de l'argument `-f` quelle colonne on veut (la
    192 première). Notre jeux de donnée affiche l'évolution des populations des pays
    193 par années, on se retrouve donc avec pleins de duplicas. Pour résoudre ce souci
    194 on peut utiliser `uniq`. En le faisant sur la totalité du fichier cette fois -ci :
    195 
    196 	cut -f1 population.tsv | uniq
    197 	Country Name
    198 	Aruba
    199 	Africa Eastern and Southern
    200 	Afghanistan
    201 	Africa Western and Central
    202 	Angola
    203 	Albania
    204 	Andorra
    205 	Arab World
    206 	United Arab Emirates
    207 	Argentina
    208 	...
    209 
    210 On peut aussi savoir rapidement combien de pays sont concernés en combinant avec `wc` :
    211 
    212 	cut -f1 population.tsv | uniq | wc -l
    213 	266
    214 
    215 Ce qui semble vraiment beaucoup. Effectivement notre jeux de donnée contient
    216 des regroupements de pays tels que "Europe & Central Asia". Cette capacité à combiner
    217 des commandes est l'une des propriétés vraiment puissantes de l'utilisation du pipe (|)
    218 dans le traitement de données. D'ailleurs c'est tellement puissant que R l'a reproduit
    219 tel quel dans son langage. On remarque que le pays "United Arab Emirates" apparaît avant
    220 le pays "Argentina". C'est l'occasion de voir comment trier les données. On utilise la
    221 commande `sort` :
    222 
    223 	cut -f1 population.tsv | uniq | sort
    224 	Afghanistan
    225 	Africa Eastern and Southern
    226 	Africa Western and Central
    227 	Albania
    228 	Algeria
    229 	American Samoa
    230 	Andorra
    231 	Angola
    232 	Antigua and Barbuda
    233 	Arab World
    234 	Argentina
    235 	...
    236 
    237 On voit que l'entrée des Emirats Arabes Unis apparaît désormais plus loin.
    238 
    239 ### Filtrer le fichier
    240 
    241 Revenons à nos données. Admettons que l'on ne veuille retenir que l'années
    242 2002. On va filtrer le fichier avec `grep` :
    243 
    244 	grep "2002" population.tsv | tsv-pretty
    245 	Aruba                                     ABW  2002       91781
    246 	Africa Eastern and Southern               AFE  2002   422741118
    247 	Afghanistan                               AFG  2002    21000256
    248 	Africa Western and Central                AFW  1996   242200260
    249 	Africa Western and Central                AFW  2002   284952322
    250 	Angola                                    AGO  2002    17516139
    251 
    252 oups petit souci, on constate que pour le regroupement "Africa Western and
    253 Central" deux années on été retenues. Pourquoi ? Parce qu'en 1996 la population
    254 de cette région s'élevait à 242**2002**260 personnes. `grep` ne sait pas ce
    255 qu'est une colonne. Il y a plusieurs solutions à cela :
    256 
    257   1. En utilisant `grep`, on fait non plus une recherche sur `2002` mais `
    258 	 2002	` (tab2002tab) 
    259 
    260 Puisqu'il n'y a pas de tabulation dans les données :
    261 
    262 	grep "	2002	" population.tsv | tsv-pretty
    263 	Aruba                                     ABW  2002       91781
    264 	Africa Eastern and Southern               AFE  2002   422741118
    265 	Afghanistan                               AFG  2002    21000256
    266 	Africa Western and Central                AFW  2002   284952322
    267 	Angola                                    AGO  2002    17516139
    268 	Albania                                   ALB  2002     3051010
    269 
    270   2. On utilise un autre outil qui "comprend" les colonnes.
    271 
    272 Par exemple `awk` (je ne rentre pas dans les détails pour le moment) : `awk -F'
    273 ' '$3=="2002"' population.tsv`. `$3` désigne la troisième colonne, on vérifie
    274 son égalité avec "2002". Par défaut si le test est vrai la ligne est affichée
    275 dans `awk`. Un autre exemple serait, toujours dans la suite [tsv-utils], l'outil
    276 [tsv-filter], `tsv-filter -H --eq 3:2002 population.tsv`.
    277 
    278 Maintenant que l'on a les données pour l'année 2002, comment savoir, par exemple
    279 quel est le pays / région la plus peuplée ? On ressort `sort` de notre besace.
    280 
    281 	grep "	2002	" population.tsv | sort -t'	' -nk4
    282 	Tuvalu	TUV	2002	9609
    283 	Nauru	NRU	2002	10351
    284 	Palau	PLW	2002	19851
    285 	Turks and Caicos Islands	TCA	2002	20598
    286 	British Virgin Islands	VGB	2002	21288
    287 	Gibraltar	GIB	2002	27892
    288 	San Marino	SMR	2002	27969
    289 	Sint Maarten (Dutch part)	SXM	2002	30777
    290 
    291 Avec `-t` on dit à `sort` quel est le délimiteur (par défaut c'est n'importe
    292 quelle transition d'un caractère non "blanc" vers un caractère "blanc") puis
    293 avec `-n` que l'on veut trier des chiffres (par défaut du texte) puis avec
    294 `-k4` que l'on veut trier sur le 4ème champ (la population). Avec un `-r`
    295 on trie par ordre décroisant (par défaut par ordre croissant comme on le
    296 constate au dessus).
    297 
    298 	grep "	2002	" population.tsv | sort -t'	' -nrk4
    299 	World	WLD	2002	6308092739
    300 	IDA & IBRD total	IBT	2002	5243235123
    301 	Low & middle income	LMY	2002	5167898454
    302 	Middle income	MIC	2002	4744040472
    303 	...
    304 
    305 ### Afficher une courbe
    306 
    307 Nous souhaitons maintenant regarder l'évolution de la population d'un pays
    308 donné et tenter de la visualiser. Prenons la France pour exemple. Nous allons
    309 trier avec `grep` puis utiliser [gnuplot] pour le graph. J'aime bien
    310 gnuplot parce que malgré ses commandes et sa logique parfois un peu
    311 confuse[^5], c'est un logiciel qui s'utilise bien en ligne de commande.
    312 
    313 	grep "France" population.tsv | cut -f3,4 | gnuplot -p -e "plot '-'"
    314 
    315 On a utilisé la `cut` pour retenir cette fois plusieurs colonnes, celle des
    316 années et celle de la population en listant leurs numéros séparés par des
    317 virgules. Le résultat donne ceci :
    318 
    319 ![Evolution de la population de la France de 1960 à 2021](population-france.png)
    320 
    321 J'espère que vous appréciez la puissance de la combinaison d'outils simples en
    322 ligne de commande pour des traitements modestes comme celui-ci. En quelques
    323 caractères et en installant uniquement gnuplot[^6] on peut filtrer un gros
    324 fichier texte et grapher des valeurs numériques en cinq centièmes de secondes
    325 (sur ma machine).
    326 
    327 Les paramètres par défaut de gnuplot pour un script aussi simple (`plot '-'`)
    328 donnent des résultats insatisfaisant si l'on souhaitait publier quelque chose de
    329 sérieux puisqu'il n'y a pas de titre, la légende ne fait pas sens, les axes ne
    330 sont pas labellisés, la population est exprimée en notation scientifique.
    331 Gnuplot permet de modifier tout cela mais ce n'est pas l'objet de cet article.
    332 Je vous renvoie vers la documentation. Bonne chance. Pour l'anecdote les sorties
    333 de gnuplot sont nombreuses (png, svg, pdf, jpg, html etc) mais l'une d'entre
    334 elle est plus inattendue que les autres, c'est la sortie ascii. Il faut exécuter
    335 la commande `set term dumb`, on obtient alors :
    336 
    337 	grep "France" population.tsv | cut -f3,4 | gnuplot -p -e "set term dumb;plot '-'"
    338 		7e+07 +----------------------------------------------------------------+   
    339 			  |        +         +        +        +        +         +        |   
    340 			  |                                                    AAAAA  A    |   
    341 			  |                                               AAAAA            |   
    342 	  6.5e+07 |-+                                          AAA               +-|   
    343 			  |                                         AAA                    |   
    344 			  |                                       AA                       |   
    345 			  |                                    AAA                         |   
    346 		6e+07 |-+                              AAAA                          +-|   
    347 			  |                            AAAA                                |   
    348 			  |                        AAAA                                    |   
    349 			  |                   AAAAA                                        |   
    350 	  5.5e+07 |-+              AAAA                                          +-|   
    351 			  |            AAAA                                                |   
    352 			  |         AAA                                                    |   
    353 			  |      AAA                                                       |   
    354 		5e+07 |-+   AA                                                       +-|   
    355 			  |  AAA                                                           |   
    356 			  |AA                                                              |   
    357 			  |        +         +        +        +        +         +        |   
    358 	  4.5e+07 +----------------------------------------------------------------+   
    359 			 1960     1970      1980     1990     2000     2010      2020     2030 
    360 																				   
    361 
    362 Voilà, marrant.
    363 
    364 ### Faire un filtre un peu plus avancé
    365 
    366 Nous voulons dorénavant savoir combien il existe de pays (et de régions puisque
    367 notre jeu de donée ne fait pas la différence) de plus de 50M d'habitant·e·s en
    368 2020 :
    369 
    370 	grep "	2020	" population.tsv | cut -f4 | grep -E "^([5-9][0-9]{7}|[1-9][0-9]{8,})$" | wc -l
    371 	73
    372 
    373 On filtre sur 2020, on ne conserve que la colonne de la population et on
    374 filtre sur les nombre supérieurs ou égaux à 50M pour finalement compter le
    375 nombre de ligne en résultat. J'introduis ici quelque chose d'assez complexe
    376 mais incroyablement puissant, les expressions régulières. Je ne vais pas
    377 les expliquer en détail mais celle que nous avons utilisé se comprend comme
    378 ça :
    379 
    380  * `[5-9][0-9]{7}` = 5,6,7,8 ou 9 suivi de n'importe quels sept chiffres (autrement dit tous les nombres entre 50M et 99 999 999)
    381  * `[1-9][0-9]{8,}` = 1,2,3,4,5,6,7,8 ou 9 suivi de n'importe quels huit chiffres (autrement dit 100M et au delà)
    382 
    383 Mettre les deux entre parenthèses séparées par un pipe `|` veut dire l'un ou
    384 l'autre. Le `^` veut dire "début de la ligne", le `$` veut dire la fin, c'est
    385 pour garantir de parser le nombre en entier. Je vous recommande très fortement
    386 d'apprendre à utiliser les regex. Un bon bouquin serait [Mastering Regular
    387 Expressions] que je peux vous prêter en français (dans une vieille édition) ou
    388 que vous pouvez trouver sur internet en cherchant bien. Si on estime cela
    389 utile/nécessaire on peut aussi utiliser `tsv-filter --ge 4:50000000` qui
    390 est un peu moins ésotérique. Si l'on voulait conserver la colonne des pays,
    391 pour grapher leurs populations par exemple, il faudrait modifier la regex
    392 pour lui dire de ne s'intéresser qu'à la dernière colonne ce qui permet
    393 de sauter le `cut` qui retire l'info que l'on veut :
    394 
    395 	grep "	2020	" population.tsv |
    396 		grep -E "([5-9][0-9]{7}|[1-9][0-9]{8,})$" |
    397 		cut -f1,4 |
    398 		sort -t'	' -nk2 > data
    399 		gnuplot -p -e "set style fill solid;
    400 					   set xtics rotate by 90 right;
    401 					   set datafile separator '	';
    402 					   set xtics font ',5';
    403 					   plot 'data' using 2:xticlabels(1) with histogram notitle"
    404 
    405 Pour une raison qui m'est encore inconnue gnuplot ne veut pas générer la
    406 totalité de cet histogramme. Je vais chercher pourquoi, en attendant je vous
    407 met le résultat bugué.
    408 
    409 > Je crois avoir trouvé. Il se trouve que lorsque gnuplot lit dans stdin il
    410 > s'arrête lorsqu'il voit un `e` sur une nouvelle ligne. Ici le pays qui
    411 > suivait l'europe centrale était l'Egypte, commençant donc par un `e`. Je
    412 > trouve cela surprenant que le fonctionnement par défaut ne soit pas de
    413 > s'arrêter à un `e` *seul* comme pour un here-doc mais soit. Pour y remédier
    414 > je dépose donc les données dans un fichier. Le code au dessus a été corrigé.
    415 
    416 ![Histograme de la population des pays et régions avec plus de 50M d'habitant·e·s en 2020](histo-popu-2020.png)
    417 
    418 ### Des sommes, des moyennes, des écarts type etc
    419 
    420 Calculons maintenant des valeurs statistiques. Par exemple, quel serait la
    421 somme de la population de la France, l'Allemagne et l'Italie en 1973 ?
    422 
    423 	grep -E "(France|Germany|Italy).+	1973	" population.tsv
    424 	Germany	DEU	1973	78936666
    425 	France	FRA	1973	53053660
    426 	Italy	ITA	1973	54751406
    427 
    428 	grep -E "(France|Germany|Italy).+	1973	" population.tsv |
    429 		cut -f4 |
    430 		paste -s -d'+'
    431 	78936666+53053660+54751406
    432 
    433 	grep -E "(France|Germany|Italy).+	1973	" population.tsv |
    434 		cut -f4 |
    435 		paste -s -d'+' |
    436 		bc -l
    437 	186741732
    438 
    439 On utilise ici `paste` pour coller les lignes de nos données une à une avec le
    440 délimiteur `+` ce qui créé une expression que `bc` (une calculette en ligne de
    441 commande) peut comprendre. Si l'on voulait faire ce traitement pour toutes les
    442 années :
    443 
    444 	cut -f3 population.tsv | tail -n+2 | sort -u |
    445 		xargs -d'\n' -n1 sh -c 'printf "$1	";
    446 			grep -E "(France|Germany|Italy).+	$1" population.tsv |
    447 				cut -f4 |
    448 				paste -s -d"+" |
    449 				bc -l
    450 		' -- |
    451 		gnuplot -p -e "plot '-'"
    452 
    453 ![Somme des populations de l'Allemagne, l'Italie et la France avec le temps](sum-pop-fr-de-it.png)
    454 
    455 Je reconnais que ça commence à être un peu velu. Sur la première ligne on
    456 récupère la liste des années. Le `tail` permet d'enlever la première ligne
    457 (l'entête) et le `sort -u` de retirer les doublons. Ensuite on utilise `xargs`
    458 pour appeler la commande qui nous donnait la somme des populations pour une
    459 année donnée successivement sur toutes les années que la première ligne nous
    460 donne. Il faut bien sûr remplacer le `1973` par un `$1` qui est une variable
    461 qui sera remplacée par l'année. Il y a beaucoup plus à expliquer à propos
    462 d'`xargs` mais je vais en rester là.
    463 
    464 Il est à noter que cet exemple est relativement peu efficace. La commande
    465 termine en deux dixièmes de secondes sur ma machine, ce qui me semble tout à
    466 fait respectable, mais sur un gros fichier ce temps d'exécution aurait explosé.
    467 De plus l'écriture de la commande n'est pas aisée. Si vous rencontrez des limites
    468 rédactionnelles ou de performance dans ce genre de cas c'est peut-être que vous
    469 devriez utiliser d'autres outils. Par exemple, en utilisant la suite [tsv-utils] :
    470 
    471 	tsv-filter --regex 1:"France|Germany|Italy" population.tsv |
    472 		tsv-summarize --sum 4 --group-by 3 |
    473 		gnuplot -p -e "plot '-'"
    474 
    475 `tsv-filter` ne garde que les lignes dont la première colonne (**1**:"France...)
    476 match la regex (**--regex** 1:...) qui match soit la France soit l'Allemagne soit
    477 l'Italie (1:"**France|Germany|Italy**". Ensuite `tsv-summarize` fait une somme de
    478 la quatrième colonne (`--sum 4`) en groupant par années (`--group-by 3`).
    479 
    480 Il faut reconnaître que c'est un peu plus facile à écrire et à lire. C'est
    481 aussi dramatiquement plus efficace puisque la commande termine en 14 centièmes
    482 de secondes soit presque 20 fois moins que celle avec xargs. Cela pour deux
    483 raisons principales. Premièrement les [tsv-utils] ont été bien
    484 optimisés pour faire de la lecture de fichiers `tsv` et `tsv` uniquement
    485 là où les autres outils sont souvent plus généralistes et ne peuvent donc
    486 pas se permettre certaines optimisations. Deuxièmement l'algorithme interne
    487 de `tsv-summarize` permettant de faire une opération en groupant sur une
    488 autre variable est terriblement plus efficace que de bêtement refiltrer
    489 sur chacune des années avec un `grep` etc. Cette dernière technique
    490 ouvre un nouveau processus à chaque année ce qui constitue un overhead
    491 significatif. Si vous avec besoin de plus de performance et où s'il est
    492 vraiment important d'exprimer ce que vous voulez plus simplement alors
    493 [tsv-utils] pourrait vous être utile[^7].
    494 
    495 `tsv-summarize` sait aussi calculer des statistiques assez classiques telles que
    496 la moyenne, la médiane, l'écart type etc. Les tsv-utils savent aussi adresser
    497 les colonnes par leurs noms si un entête existe. A chaque fois que l'on a
    498 utilisé des chiffres on aurait pu utiliser le nom de la colonne.
    499 
    500 ### Et des jointures ?
    501 
    502 Et si l'on veut faire des jointures ? Admettons que notre fichier soit séparé
    503 en deux, d'un côté nous avons les codes des pays, les années et les valeurs, de
    504 l'autre nous avons la correspondance entre les codes des pays et leur noms
    505 complets.
    506 
    507 	head correspondance.tsv sans-pays.tsv
    508 	==> correspondance.tsv <==
    509 	Country Name	Country Code
    510 	Aruba	ABW
    511 	Africa Eastern and Southern	AFE
    512 	Afghanistan	AFG
    513 	Africa Western and Central	AFW
    514 	Angola	AGO
    515 	Albania	ALB
    516 	Andorra	AND
    517 	Arab World	ARB
    518 	United Arab Emirates	ARE
    519 
    520 	==> sans-pays.tsv <==
    521 	Country Code	Year	Value
    522 	ABW	1960	54608
    523 	ABW	1961	55811
    524 	ABW	1962	56682
    525 	ABW	1963	57475
    526 	ABW	1964	58178
    527 	ABW	1965	58782
    528 	ABW	1966	59291
    529 	ABW	1967	59522
    530 	ABW	1968	59471
    531 	...
    532 
    533 Alors on peut faire une jointure avec
    534 
    535 	join --header -t'	' -1 2 -2 1 correspondance.tsv sans-pays.tsv
    536 	Country Code	Country Name	Year	Value
    537 	ABW	Aruba	1960	54608
    538 	ABW	Aruba	1961	55811
    539 	...
    540 
    541 On saute les premières lignes puisque ce sont des header (`--header`), on
    542 choisit la tabulation comme délimiteur (`-t'	'`), dans le premier fichier on
    543 joint sur le second champ (le code, `-1 2`), dans le second sur le premier (`-2
    544 1`) et on passe les fichier en argument. tsv-utils a un équivalent un peu plus
    545 versatile qui pour le même besoin s'utiliserait de la sorte :
    546 
    547 	tsv-join -H --filter-file correspondance.tsv -k 1 -a 2 sans-pays.tsv
    548 	Country Code	Year	Value	Country Name
    549 	ABW	1960	54608	Aruba
    550 	ABW	1961	55811	Aruba
    551 	ABW	1962	56682	Aruba
    552 	ABW	1963	57475	Aruba
    553 	...
    554 
    555 Attention, je crois qu'il faut que la colonne sur laquelle faire la jointure
    556 soit la même dans les deux fichiers. Limitation un peu étrange mais soit.
    557 
    558 ### Compter les occurrences des valeurs d'une colonne
    559 
    560 Pour continuer les exmples prenons le jeu de donnée ici :
    561 https://github.com/datablist/sample-csv-files/raw/main/files/people/people-2000000.zip.
    562 Attention le fichier pèse 66Mo zippé, 255M dézippé. Je prends un gros fichier
    563 comme celui-ci pour vérifier que ce que l'on fait est au moins un peu
    564 performant. Vous pouvez télécharger une version plus petite sur la page du [dépôt github](https://github.com/datablist/sample-csv-files).
    565 
    566 Convertissons le en TSV et regardons ce qu'il y a dedans :
    567 
    568 	csv2tsv people.csv > people.tsv;head people.tsv | tsv-pretty
    569 	Index  User Id          First Name  Last Name  Sex     Email                    Phone                   Date of birth  Job Title
    570 		1  4defE49671cF860  Sydney      Shannon    Male    tvang@example.net        574-440-1423x9799       2020-07-09     Technical brewer
    571 		2  F89B87bCf8f210b  Regina      Lin        Male    helen14@example.net      001-273-664-2268x90121  1909-06-20     Teacher, adult education
    572 		3  Cad6052BDd5DEaf  Pamela      Blake      Female  brent05@example.org      927-880-5785x85266      1964-08-19     Armed forces operational officer
    573 		4  e83E46f80f629CD  Dave        Hoffman    Female  munozcraig@example.org   001-147-429-8340x608    2009-02-19     Ship broker
    574 		5  60AAc4DcaBcE3b6  Ian         Campos     Female  brownevelyn@example.net  166-126-4390            1997-10-02     Media planner
    575 		6  7ACb92d81A42fdf  Valerie     Patel      Male    muellerjoel@example.net  001-379-612-1298x853    2021-04-07     Engineer, materials
    576 		7  A00bacC18101d37  Dan         Castillo   Female  billmoody@example.net    (448)494-0852x63243     1975-04-09     Historic buildings inspector/conservation officer
    577 		8  B012698Cf31cfec  Clinton     Cochran    Male    glenn94@example.org      4425100065              1966-07-19     Engineer, mining
    578 		9  a5bd11BD7dA1a4B  Gabriella   Richard    Female  blane@example.org        352.362.4148x8344       2021-09-02     Wellsite geologist
    579 
    580 Bon c'est pas facile à lire parce que les lignes sont vraiment longues mais soit. Si vous n'utilisez pas tsv-utils mais que vous voulez voir rapidement quels sont les chiffres qui correspondent aux entêtes vous pouvez faire :
    581 
    582 	head -n1 people.tsv | tr '	' '\n' | nl
    583 		 1	Index
    584 		 2	User Id
    585 		 3	First Name
    586 		 4	Last Name
    587 		 5	Sex
    588 		 6	Email
    589 		 7	Phone
    590 		 8	Date of birth
    591 		 9	Job Title
    592 
    593 Imaginons que nous voulions compter le nombre de personnes identifiées comme "Male" dans ce fichier :
    594 
    595 	cut -f5 people.tsv | sort | uniq -c
    596 	1000505 Female
    597 	 999495 Male
    598 		  1 Sex
    599 
    600 On ne retient que la colonne pertinente avec `cut`, on la tri pour que tous les
    601 occurrences identiques soient les unes à côté des autres puis on les compte
    602 avec `uniq` en lui précisant d'afficher le nombre d'occurrence en utilisant
    603 `-c`. Il est nécessaire de trier les données avant de pouvoir les compter avec
    604 `uniq`. `uniq` est très bête, il ne sait pas tenir les comptes d'une valeur à
    605 une autre.
    606 
    607 On voit qu'il y a une occurrence de "Sex", c'est l'entête que se ballade. Si vous ne
    608 la voulez pas on peut initialement la supprimer avec `tail -n+2`.
    609 
    610 Cette commande s'exécute en 1,4s. Certes le fichier est très gros mais c'est
    611 aussi parce que cette nécessité de trier la colonne avant de compter est
    612 coûteuse. Deux solutions à ça. Premièrement, donner un plus gros buffer à
    613 `sort` avec `--buffer-size=NM` où `N` est un entier. Cela aide mais n'est pas
    614 magique non plus. Si les performances sont vraiment cruciales alors je
    615 suggère de dégainer à nouveau tsv-utils.
    616 
    617 	tsv-summarize --count --group-by 5 people.tsv
    618 	Sex	1
    619 	Male	999495
    620 	Female	1000505
    621 
    622 Cette commande s'exécute en 0,3s. Autre avantage de `tsv-summarize` dans ce cas
    623 est qu'il renvoie le résultat sous forme d'un TSV qu'on peut donc continuer à
    624 piper facilement. Le résultat d'`uniq` doit être remanié. J'en donne un exemple
    625 juste après. Pour un exemple similaire mais avec un peu plus de préparation,
    626 imaginons que nous souhaitons grapher la fréquence des années de naissance des
    627 personnes :
    628 
    629 	tail -n+2 people.tsv | cut -f8 | cut -d'-' -f1 |
    630 		sort -n | uniq -c |
    631 		sed -E 's/ +([0-9]+) ([0-9]+)/\2	\1/' |
    632 		gnuplot -p -e "plot '-'"
    633 
    634 ![Répartition des années de naissance dans ce jeu de données](repart-ann-naissance.png)
    635 
    636 Il semblerait que ce jeu de données ait été généré aléatoirement pour que la
    637 répartition soit aussi régulières. Avec tsv-summarize :
    638 
    639 	cut -f8 people.tsv | cut -d'-' -f1 | tail -n+2 |
    640 		tsv-summarize  --count -g 1 |
    641 		gnuplot -p -e "plot '-'"
    642 
    643 Cela évite l'appel à `sed` pour remanier la sortie d'`uniq` et va plus de deux
    644 fois plus vite.
    645 
    646 ## Reproductibilité
    647 
    648 J'espère avoir fait la démonstration, même courte, qu'il est très facile,
    649 rapide et performant d'effectuer des traitements simples sur des gros volumes
    650 de données à l'aide des commandes de bases du monde Unix. En plus de cette
    651 propriété je pense qu'évoluer aussi longtemps que possible dans ce monde
    652 logiciel là est bénéfique pour la reproductibilité de résultats scientifiques.
    653 
    654 Les commandes majoritairement utilisées ici sont toutes :
    655 
    656   1. Très anciennes
    657 
    658 Être un vieux logiciel n'est pas un avantage pour la repro en soit mais cela
    659 témoigne d'une certaine longévité *jusque là* qui permet d'être relativement
    660 optimiste sur la possibilité qu'il soit là *à l'avenir*. Les commandes que l'on
    661 a vu ont pour la plupart 20/30/40 ans et survivent avec relativement peu de
    662 maintenance. De plus, étant déjà fondamentales à l'époque et le temps ayant
    663 fait son affaires, elle se sont immiscées partout ce qui diminue les chances
    664 qu'elles changent ou pire disparaissent.
    665 
    666   2. Très stables
    667 
    668 Du fait que chaque commande ne sache faire qu'un ensemble relativement
    669 restreint de choses et soient isolées les une des autres, elles sont assez
    670 rarement mises à jour. Ainsi les chances pour qu'une mise à jour vienne
    671 modifier le comportement de votre code ou casser votre environnement sont
    672 presque nulles. Dans le milieu on dit que ce sont des logiciels "matures".
    673 
    674   3. Très peu buguées
    675 
    676 Des décennies de développement et d'usages ont fait de ces commandes des
    677 logiciels contenant peu de bugs. La présence de bugs n'est évidemment pas
    678 souhaitable dans un contexte où l'on cherche à obtenir des résultats fiables et
    679 à les reproduire.
    680 
    681   4. Généralement disponibles par défaut
    682 
    683 Étant historiquement[^9] le fondement de "l'espace utilisateurice" des systèmes
    684 Unix, ces commandes sont naturellement (presque ?) toujours disponibles sur une
    685 installation fraîche des systèmes héritier d'Unix. Rien besoin d'installer, pas
    686 besoin d'une connexion internet, pas besoin de configurer quoi que soit. En
    687 gros, dépendre de ces commandes revient plus ou moins à dépendre de la chaîne
    688 de développement et de de distribution qui vous permet d'avoir un système
    689 d'exploitation sur votre machine. Or vous ne feriez rien de ce genre sans OS de
    690 toute façon donc vous consentez à pratiquement aucune dépendance supplémentaire
    691 en utilisant ces logiciels. Je caricature mais la documentation pour permettre
    692 de reproduire l'environnement de développement nécessaire à l'obtention des
    693 mêmes résultats se limiterait alors presque à "Un OS Linux en état de
    694 fonctionnement". Entre ça et lister les versions précises des dizaines de paquets
    695 Python ou R installés mon choix est vite fait.
    696 
    697   5. Relativement simples dans leurs implémentations
    698 
    699 Ces commandes sont des logiciels assez simples **relativement** à d'autres
    700 outils pour manipuler des données. Cela rend leur maintenance plus simple ce
    701 qui favorise leur pérennité. De plus, puisqu'elles sont toutes indépendantes
    702 les une des autres, la disparition d'une n'implique pas forcément la
    703 disparition d'une autre. Je précise "relativement" puisqu'en réalité, avec
    704 le temps et pour des raisons plus ou moins légitimes, les variantes les plus
    705 répandues de ces commandes sont devenues assez complexes. Par exemple `sort`
    706 fait :
    707 
    708 	curl -Ls https://github.com/coreutils/coreutils/raw/master/src/sort.c | wc -l
    709 	4847
    710 
    711 4847 lignes de C[^10]. Trier n'est pas un problème trivial mais il est possible
    712 de faire moins. Par exemple le projet [busybox] a réimplémenté `sort` en :
    713 
    714 	curl -Ls https://git.busybox.net/busybox/plain/coreutils/sort.c | wc -l
    715 	684
    716 
    717 684 lignes. Cela dit, bien que je puisse imaginer que le projet GNU ait pris
    718 un peu trop de poids "inutile" avec le temps il est manifeste qu'une partie
    719 de tout ce code participe aux bonnes performances. Par exemple :
    720 
    721 	time -f '%e' cut -f5 people.tsv | sort | uniq -c
    722 	1.22
    723 	1000505 Female
    724 	 999495 Male
    725 		  1 Sex
    726 
    727 	time -f '%e' busybox cut -f5 people.tsv | busybox sort | busybox uniq -c
    728 	1.73
    729 	1000505 Female
    730 	 999495 Male
    731 		  1 Sex
    732 
    733 On voit que pour ce traitement les versions busybox sont environ 30% plus
    734 lentes.
    735 
    736   6. Relativement performantes et accessibles via des interfaces simples et
    737 	 performantes
    738 
    739 Dans l'ensemble ces commandes sont performantes prises individuellement. Cela
    740 permet de diminuer la charge sur le matériel et ainsi permettre de faire
    741 tourner les traitements sur un plus grand nombre de machine. Cela permet à plus
    742 de personnes y compris sur des machines peu puissantes et peu coûteuse de
    743 produire ou reproduire le traitement. Ces outils ont également l'élégance
    744 d'être accessible via des interfaces très simples, en ligne de commande, qui
    745 peuvent tourner sur des machines peu puissantes. De nombreux outils de
    746 traitement de données beaucoup plus complexes sont également accessibles en
    747 ligne de commande et je pense qu'ils devraient, au moins initialement, être
    748 enseignés via cette interface. Pour pouvoir tourner sur plus de machines mais
    749 aussi pour inciter à l'utilisation des commandes que l'on a vu dans cet
    750 article. On ne dégaine alors l'outil plus complexe que lorsqu'il le faut, au
    751 détour d'un pipe, exactement comme nous l'avons fait avec les tsv-utils ou
    752 gnuplot.
    753 
    754 Évidemment ce n'est pas magique et pour des besoins un peu complexes il est
    755 très facile d'écrire du code très peu performant avec nos commandes. J'ai
    756 montré l'un de ces exemples plus tôt dans l'article. Dans ces cas il est
    757 judicieux de revoir son code ou, si besoin, d'utiliser un nouvel outil.
    758 Un très bon exemple de cela est documenté dans notre article sur [sed et ses
    759 performances]. En somme, pour filtrer une fois sur une regex il est très
    760 probable que `grep` soit le plus rapide mais s'il s'agit de l'enchaîner avec
    761 deux ou trois autres opérations d'une manière un peu sophistiquée faites
    762 attention.
    763 
    764   7. Libres
    765 
    766 Ces logiciels sont tous libres. Je n'ai pas envie de prendre ici le temps
    767 d'expliquer pourquoi l'utilisation de logiciel et de formats libres devrait
    768 être la toute première étape lorsque l'on souhaite produire de la recherche
    769 reproductible.
    770 
    771 ## Pour aller plus loin
    772 
    773 Pour voir des exemples plus avancés vous pouvez jeter un coup d'oeil à cette
    774 [tentative de reproduction] d'un [article analysant des données à propos d'un
    775 tournoi de starcraft].
    776 
    777 ## Bêtisier
    778 
    779 Quelques graphs un peu jolis mais inutilisables :
    780 
    781 [oups - 40Ko](oups.png)\
    782 [oups le retour - 20Ko](oups2.png)
    783 
    784 [^1]: Tabulation Seperated Data, du CSV mais séparé par une tabulation plutôt qu'un autre délimiteur.
    785 [cuillère de deux mètres - 524Ko]: http://katzele.netlib.re/memes/visualstudio-pour-dix-lignes-dxml.png
    786 [cet article d'une équipe d'eBay]: https://raw.githubusercontent.com/eBay/tsv-utils/master/docs/comparing-tsv-and-csv.md
    787 [csv2tsv]: https://github.com/eBay/tsv-utils/tree/master/csv2tsv
    788 [tsv-utils]: https://github.com/eBay/tsv-utils
    789 [^2]: Pour translate
    790 [^3]: Ici \t veut dire une tabulation. La commande exprime donc "traduit toutes les virgules en tabulation du fichier population.csv et met le résultat dans population.tsv"
    791 [^4]: Pour insérer une tabulation plutôt que de provoquer l'autocomplétion vous pouvez faire ctrl+v puis appuyer sur la touche tabulation
    792 [tsv-pretty]: https://github.com/eBay/tsv-utils/tree/master/tsv-pretty
    793 [tsv-filter]: https://github.com/eBay/tsv-utils/tree/master/tsv-filter
    794 [gnuplot]: http://www.gnuplot.info/
    795 [^5]: pour être honnête je n'ai pas fait beaucoup de graphs dans ma vie mais du peu que j'ai vu, aucun outil de graph n'est vraiment facile d'utilisation.
    796 [^6]: A part les outils [tsv-utils] et [gnuplot], tous les outils que nous voyons ici sont installés par défaut sur (presque) toutes les distributions linux. Gnuplot ne pèse "que" 3,5Mo
    797 [Mastering Regular Expressions]: https://www.oreilly.com/library/view/mastering-regular-expressions/0596528124/
    798 [^7]: J'aurais tendance à penser que dans ce cas particulier, si c'était le seul traitement que nous avions à faire alors ça ne vaudrait pas forcément le coup
    799 [^8]: Pour le meilleur et pour le pire. Par exemple les variantes généralement les plus répandues sont celles de GNU c'est à dire souvent les plus lourdes et complexes.
    800 [POSIX]: https://en.wikipedia.org/wiki/POSIX
    801 [^9]: et c'est en réalité toujours le cas
    802 [busybox]: https://busybox.net
    803 [^10]: petite mise en pratique improptu hop
    804 [sed et ses performances]: ../substitutions
    805 [tentative de reproduction]: starcraft.html
    806 [article analysant des données à propos d'un tournoi de starcraft]: https://www.kaggle.com/code/fulviocapra/sc2-iem-katowice-data-analytics