Xavier Nayrac

Rubyiste accro au TDD, serial blogger, apprenti data scientist, heureux utilisateur de Vim, accordéoniste.
Si vous vous sentez particulièrement généreux, suivez moi sur Twitter.

Extraction des tags de mon blog en ligne de commande

| Comments

Niveau : facile

J’aimerais savoir quels sont les tags que j’ai le plus employé sur ce blog. Pour cela il va d’abord falloir les extraire, si possible dans un fichier csv.

Comment ? En Ruby ? En R ? Non non, en Bash, ça va être beaucoup plus drôle.

Petite étude de cas pour découvrir la puissance de la ligne de commande, ou pour rafraichir ses connaissances ;)

Si vous voulez reproduire l’analyse en même temps que moi, les articles sont ici.

Voyons à quoi ressemble le début d’un article au hasard avec head. Octopress, le framework que j’utilise pour ce blog, appelle les tags des categories. Mais c’est pareil.

1
2
3
4
5
6
7
8
$ head 2013-10-20-les-algorithmes-genetiques-demystifies-35.markdown
---
layout: post
title: "Les algorithmes génétiques démystifiés 35"
date: 2013-10-20 21:21
comments: true
categories: [imagerie, algorithme génétique, intermédiaire, javascript]
#...

L’entête d’un article a toujours la même structure. Avec head *.markdown j’affiche le début de tous les articles, les uns à la suite des autres. Avec sed je peux extraire uniquement les lignes qui commencent par cat :

1
2
3
4
5
6
$ head *.markdown | sed -n '/^cat/p'
categories: [annonce, défi]
categories: [vim, conseil, débutant]
categories: [ruby, code propre, intermédiaire]
categories: [ruby, eigenclass, object, intermédiaire]
#...

Gardons seulement les tableaux. cut -f1 découpe le premier champ, -d' ' indique que le séparateur de champ est l’espace, et --complement indique qu’on veut conserver le complément. Autrement dit tout sauf la première colonne.

1
2
3
4
5
6
7
$ head *.markdown | sed -n '/^cat/p' |
> cut -f1 -d' ' --complement
[annonce, défi]
[vim, conseil, débutant]
[ruby, code propre, intermédiaire]
[ruby, eigenclass, object, intermédiaire]
#...

Supprimons les crochets à l’aide de sed et d’une regex rigolote.

1
2
3
4
5
6
7
$ head *.markdown | sed -n '/^cat/p' | cut -f1 -d' ' --complement |
> sed 's/[][]//g'
annonce, défi
vim, conseil, débutant
ruby, code propre, intermédiaire
ruby, eigenclass, object, intermédiaire
#...

Supprimons les espaces inutiles. Attention, certains tags contiennent des espaces.

1
2
3
4
5
6
7
8
$ head *.markdown | sed -n '/^cat/p' | cut -f1 -d' ' --complement |
> sed 's/[][]//g' |
> sed 's/, /,/g'
annonce,défi
vim,conseil,débutant
ruby,code propre,intermédiaire
ruby,eigenclass,object,intermédiaire
#...

Grâce à tr, je remplace chaque virgule par un retour à la ligne. Ça commence à prendre forme.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ head *.markdown | sed -n '/^cat/p' | cut -f1 -d' ' --complement |
> sed 's/[][]//g' | sed 's/, /,/g' |
> tr ',' '\n'
annonce
défi
vim
conseil
débutant
ruby
code propre
intermédiaire
ruby
eigenclass
#...

Trions par ordre alphabétique.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ head *.markdown | sed -n '/^cat/p' | cut -f1 -d' ' --complement |
> sed 's/[][]//g' | sed 's/, /,/g' | tr ',' '\n' |
> sort

ack
activerecord
activerecord
ag
airline
airline
airline
airline
algorithme génétique
#...

Réduisons les occurrences et comptons les avec uniq -c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ head *.markdown | sed -n '/^cat/p' | cut -f1 -d' ' --complement |
> sed 's/[][]//g' | sed 's/, /,/g' | tr ',' '\n' | sort |
> uniq -c
      1
      1 ack
      2 activerecord
      1 ag
      4 airline
     70 algorithme génétique
      1 alias
     16 annonce
      3 app
      1 application
#...

Trions à nouveau, cette fois sur le nombre et du plus grand au plus petit.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ head *.markdown | sed -n '/^cat/p' | cut -f1 -d' ' --complement |
> sed 's/[][]//g' | sed 's/, /,/g' | tr ',' '\n' | sort | uniq -c |
> sort -nr
    213 ruby
    176 intermédiaire
    171 débutant
     70 algorithme génétique
     55 vim
     26 tutoriel
     26 julia
     26 javascript
#...
      1 application
      1 alias
      1 ag
      1 ack
      1

Vous avez remarquez ? Un tag est vide. Ça pourrait poser problème pour la suite. Avec sed on peut facilement supprimer la dernière ligne.

1
2
3
4
5
6
7
8
9
10
$ head *.markdown | sed -n '/^cat/p' | cut -f1 -d' ' --complement |
> sed 's/[][]//g' | sed 's/, /,/g' | tr ',' '\n' | sort | uniq -c |
> sort -nr | sed '$d'
    213 ruby
    176 intermédiaire
    171 débutant
#...
      1 alias
      1 ag
      1 ack

À la réflexion je préfère faire comme si il y avait plusieurs lignes vides et les supprimer toutes. Ça pourrait être plus réutilisable.

1
2
3
4
$ head *.markdown | sed -n '/^cat/p' | cut -f1 -d' ' --complement |
> sed 's/[][]//g' | sed 's/, /,/g' | tr ',' '\n' |
> sed '/^$/d' |
> sort | uniq -c | sort -nr

Il est temps de sortir une regex un peu plus complexe pour inverser les deux champs et ajouter une virgule entre eux.

1
2
3
4
5
6
7
8
9
$ head *.markdown | sed -n '/^cat/p' | cut -f1 -d' ' --complement |
> sed 's/[][]//g' | sed 's/, /,/g' | tr ',' '\n' | sed '/^$/d' |
> sort | uniq -c | sort -nr |
> sed -r 's/\s+([0-9]+) (.*)/\2,\1/'
ruby,213
intermédiaire,176
débutant,171
algorithme génétique,70
#...

C’est quasiment terminé. Il reste à ajouter l’entête du fichier csv. Pour cela je vais utiliser une commande non standard mais bien pratique, header.

1
2
3
4
5
6
7
8
9
10
$ head *.markdown | sed -n '/^cat/p' | cut -f1 -d' ' --complement |
>  sed 's/[][]//g' | sed 's/, /,/g' | tr ',' '\n' | sed '/^$/d' |
> sort | uniq -c | sort -nr | sed -r 's/\s+([0-9]+) (.*)/\2,\1/' |
> header -a tag,frequency
tag,frequency
ruby,213
intermédiaire,176
débutant,171
algorithme génétique,70
#...

On a terminé. C’est un beau pipeline, non ? Enregistrons le résultat dans un fichier.

1
2
3
4
$ head *.markdown | sed -n '/^cat/p' | cut -f1 -d' ' --complement |
>  sed 's/[][]//g' | sed 's/, /,/g' | tr ',' '\n' | sed '/^$/d' |
> sort | uniq -c | sort -nr | sed -r 's/\s+([0-9]+) (.*)/\2,\1/' |
> header -a tag,frequency > tags.csv

En bonus, voici le code R qui produit l’image qui illustre cet article.

1
2
3
4
5
6
7
8
9
library(wordcloud)

d <- read.csv('tags.csv')

colors <- brewer.pal(12, 'Paired')
colors <- colors[seq(2, 10, by=2)]

wordcloud(d$tag, d$frequency, colors=colors, min.freq=1, scale=c(5, .6),
          rot.per=.25, random.order=FALSE, random.color=TRUE)

Articles connexes

Commentaires