Personnaliser un élément de tableau

Nous avons vu dans les articles précédents comment paramétrer l’aspect général d’un tableau, agrémenter les entêtes de colonnes et enfin personnaliser des regroupements de lignes. Cependant, il peut être nécessaire d’être encore plus pointilleux.

Pour cela, kableExtra possède aussi un groupe de fonctions *_spec() permettant de paramétrer le contenus des cellules en les spécifiant par ligne, colonne ou les deux.

cell_spec(), la personnalisation de cellule à l’ancienne

Paradoxalement nous allons commencer par la fonction qu’au final vous utiliserez probablement le moins : cell_spec() car à la différence de la plupart des autres fonctions de kableExtra, elle doit se positionner avant l’appel à kable() ou kbl().

Les paramètres de la fonction

Les options qu’on peut passer pour personnaliser une cellule sont listées ci-dessous. Elles portent des noms, pour certaines, assez courants que je ne détaillerai pas :

  • x = l’objet sur lequel cell_spec() va être appliqué. Utilisable avec un pipe.
  • format = soit « html » (défaut) , soit « latex ». Pour préciser le format de sortie. Il n’est pas possible de mixer les 2 dans le même tableau.
  • bold,italic, monospace, underline, strikeout = (défaut FALSE)
  • color = (défaut NULL), une façon adaptée au format de spécifier une couleur pour le texte. Il n’y a aucun contrôle de l’existence de cette couleur. Si vous passez une représentation erronée, il ne se passera au mieux rien au pire n’importe quoi…
  • background = (défaut NULL), cette fois-ci la spécification d’une couleur pour le fond de cellule
  • align = (défaut NULL), spécifier l’alignement du texte dans la cellule. Les valeurs possibles sont « l », « c », « r » pour LaTeX et HTML, auquelles HTML permet aussi « left », « center », « right », « justify » et « inherit ». Ce paramètre marche mal lorsqu’on applique des styles à toute la table après l’appel à kable()
  • font_size = (défaut NULL), permet de régler la taille de police soit numériquement soit pour le HTML en précisant des familles de tailles (« x-small », etc)
  • angle = (défaut NULL), règle la rotation du contenu de la cellule, s’il le permet.
  • tooltip = (défaut NULL), uniquement pour HTML, permet de spécifier un court texte qui se mettra dans une infobulle lors du survol à la souris
  • popover = (défaut NULL), similaire à tooltip mais pas natif HTML, on peut donc y mettre beaucoup plus de choses (y compris du html par exemple)
  • link = (défaut NULL), permet d’ajouter un lien hypertexte
  • new_tab = (défaut FALSE), inutile sans le paramètre ci-dessus, précise s’il faut ouvrir le-dit lien dans un nouvel onglet ou pas
  • extra_css = (défaut NULL), un moyen de préciser du contenu encore plus précis à ajouter dans l’attribut style= de la balise HTML que cell_spec() va construire. Là aussi, il n’y a aucun contrôle d’intégrité.
  • escape = (défaut TRUE), spécifie si les caractères spéciaux se trouvant dans x= doivent être protégés (on dit « échappé » en anglais) pour apparaitre dans le résultat de sortie
  • background_as_tile = (défaut TRUE), celui-ci est piégeux. Il s’agit juste de préciser si la cellule aura des coins arrondis s’il background= est fixé et le format HTML.
  • latex_background_in_cell = (défaut TRUE), spécifie si le fond doit être coloré en LaTeX (bien que réglé à TRUE, c’est franchement rare de colorer des tables en LaTeX, ce n’est que mon avis personnel…)

Typiquement cell_spec() retourne une chaine de caractères contenant mise en forme du contenu de la cellule source et non un objet compliqué. Techniquement, on pourrait donc lui passer n’importe quoi. comme par exemple ici une chaine de caractères arbitraire :

> cell_spec("Je suis un test", "html", bold = TRUE, underline = TRUE)
[1] "<span style=\" font-weight: bold;   text-decoration: underline; \" >Je suis un test</span>"

# ou

> cell_spec("Je suis un test","latex",italic = TRUE,color="red")
[1] "\\textcolor{red}{\\em{Je suis un test}}"
R

Vous pouvez voir que cell_spec() renvoie la chaine de caractères qu’elle a adaptée aux paramètres passés.

La mise en oeuvre

Reprenons l’exemple des traitements utilisés dans les exemples précédents.

temp <- RSA %>%
 mutate(CMD    = substr(GHMOUT, 1, 2),
        CATGHM = substr(GHMOUT, 3, 3),
        GHM    = substr(GHMOUT, 1, 5)) %>%
 filter(CMD == "23") %>%
 count(CMD, CATGHM, GHM, SEVOUT)
R

Une fois nos calculs réalisés, il nous faut préparer les données à afficher tout d’abord en les transformant en chaines de caractères si ce n’en sont pas déjà.

# Ceci est la méthode bourrin. Nous pouvons nous le permettre car le jeu de données est petit. Sinon, il faudrait adapter par exemple en ne traitant que les colonnes non chaines de caractères (je vous laisse chercher).

temp <-temp %>% mutate(across(all_of(names(.)),as.character))

# on prend temp 
# on applique (mutate()) la fonction as.character()
# à (across()) toutes colonnes (all_of()) portant le nom des colonnes (names()) de la table en entrée de mutate (.)
R

Supposons que nous voulions changer l’aspect de la cellule située sur la 2e ligne, colonne GHM en la mettant en gras. et la cellule de la 2ème ligne colonne « n » en italique. Il faut alors écrire :

temp[2,"GHM"] <- cell_spec(temp[2,"GHM"],"HTML", bold = TRUE)
temp[2,"n"] <- cell_spec(temp[2,"n"],"HTML", italique = TRUE)
R

Il nous faut ensuite adapter la suite du traitement. Cela consiste principalement à désactiver dans l’appel à kable() la protection des caractères spéciaux que cell_spec() a généré :

temp %>%
kable("html",escape=FALSE) %>%
pack_rows(group_label = "CMD = 23" ,start_row = 1,end_row = 16) %>%
pack_rows(group_label = "Type = M" ,start_row = 3,end_row = 15) %>%
pack_rows(group_label = "CMD = 23, Type = M, GHM = 10" ,start_row=7, end_row =11) %>%
kable_classic_2(lightable_options = "striped")
R

Notez cependant en passant que les colonnes ayant changé de type, les alignements sont tous « à gauche ».

Il est possible, en lieu et place de « coordonnées » dans le tableau de passer des colonnes : (en repartant de notre variable temp originale)

temp <-temp %>% mutate(across(all_of(names(.)),as.character))

temp$n <- cell_spec(temp$n, "html", bold=(as.numeric(temp$n)>100))

# pour des raisons de concision j'ai fait une double conversion car je repasse via as.numeric pour comparer les valeurs de n. C'est bien sûr inutile quand programmé directement et correctement car dans ce cas on conserverait la colonne initiale et on l'utiliserait directement.

temp %>%
kable("html",escape=FALSE) %>%
pack_rows(group_label = "CMD = 23" ,start_row = 1,end_row = 16) %>%
pack_rows(group_label = "Type = M" ,start_row = 3,end_row = 15) %>%
pack_rows(group_label = "CMD = 23, Type = M, GHM = 10" ,start_row=7, end_row =11) %>%
kable_classic_2(lightable_options = "striped")
R

Ce qui nous donne :

Où chaque valeur de ‘n’ supérieure à 100 est en gras.

Rien ne nous oblige d’ailleurs à respecter la cohérence des colonnes. Je vous laisse chercher comment obtenir cette sortie (chaque n supérieur à 50 est en gras, le GHM est en rouge si n<50 et en vert sinon) :

cell_spec() est une fonction parfois ardue à utiliser. L’auteur de kableExtra a donc plus récemment proposé de nouvelles alternatives.

row_spec(), agir sur tout une ligne

row_spec() se place après l’appel à kable()et idéalement après tout autre effet qui affecte toute la table (kable_styling() ou les styles par défaut).

il nécessite donc d’y passer un « kable » déjà produit et une spécification numérique des lignes à impacter dans row=.

Le reste des paramètres ressemble assez à un appel de cell_spec(), s’y ajoute hline_after= qui permet de rajouter une trait sous la ligne sélectionnée.

le paramètre row=

Ce paramètre doit être un vecteur d’entiers positifs représentant le(s) numéro(s) de lignes à modifier. Nous sommes en dessous de l’appel à kable() donc la structure de la table initiale n’est plus accessible mais cela apporte plusieurs avantages :

  • les types de données ont été correctement importés dans kable() et donc les alignements sont respectés
  • Il n’est pas nécessaire de passer le paramètre escape à TRUE ce qui permet de mieux garantir l’aspect des données initiales.
  • On agit sur l’aspect indépendamment des données ce qui est une bonne pratique.

Le « row » 0 (zéro)

En spécifiant row = 0, il est possible de paramétrer la ligne « 0 » (rappelez-vous les valeurs d’index en R commencent toutes à « 1 ») c’est à dire « la ligne avant les données » : les entêtes !

Les « row » supérieurs au égaux à 1

Pour appliquer un style à une ligne, il suffit qu’elle soit dans le vecteur passé à row=. Ainsi, si on met row = 10, cela veut dire qu’on va appliquer la mise à forme à la ligne reflétant le 10ème ensemble de données (indépendamment des lignes de regroupement, qui ne sont configurables que lors de leur création).

Et si on passe row = c(1, 5, 10), on va agir sur ces 3 lignes de données. Mais souvent, nous voulons faire de la mise en forme conditionnelle. Il faut alors faire appel à un petit contournement :

Supposons que nous voulions mettre sur fond vert les lignes dont le « GHM est en M ». Nous savons comment les repérer via un vecteur logique et ensuite il faut les traduire en numéros de lignes. Pour cela il existe une fonction, which() :

> temp$CATGHM == "M" # retourne TRUE les positions ou le test est vrai, FALSE pour les autres
 [1] FALSE FALSE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE FALSE
> which(temp$CATGHM == "M") # retourne les index numériques de valeurs TRUE uniquement
 [1]  3  4  5  6  7  8  9 10 11 12 13 14 15
R

Il « suffit » alors de passer ce résultat dans le paramètre row= :

> temp %>%
kable("html") %>%
pack_rows(group_label = "CMD = 23" ,start_row = 1,end_row = 16) %>%
pack_rows(group_label = "Type = M" ,start_row = 3,end_row = 15) %>%
pack_rows(group_label = "CMD = 23, Type = M, GHM = 10" ,start_row=7, end_row =11) %>%
kable_classic_2(lightable_options = "striped") %>%
row_spec(row = which(temp$CATGHM == "M"), background = "lightgreen")
R

Attention, rappelez vous que seul row= est vectorisé. Si vous voulez appliquer une couleur différente à chaque ligne, il faudra faire autant d’appels à row_spec() que nécessaire (au moins autant que de couleurs). Rappelez vous aussi qu’un appel remplace les réglages précédents si il porte sur des paramètres déjà fixés par une fonction *_spec() ou par un style global de kable (ci-dessous kable_classic_2()).

temp %>%
kable("html") %>%
pack_rows(group_label = "CMD = 23" ,start_row = 1,end_row = 16) %>%
pack_rows(group_label = "Type = M" ,start_row = 3,end_row = 15) %>%
pack_rows(group_label = "CMD = 23, Type = M, GHM = 10" ,start_row=7, end_row =11) %>%
kable_classic_2(lightable_options = "striped") %>%
row_spec(row=which(temp["CATGHM"]=="M"),background = "lightgreen") %>%
row_spec(row=which(temp["SEVOUT"]==1),bold = TRUE) %>%
row_spec(row=which(temp["n"]<50),background = "grey")
R

Comme vous pouvez le voir le dernier appel à row_spec() a remplacé le fond (background=) paramétré dans le premier ainsi que via kable_classic_2()) mais n’a pas impacté les mises en gras réalisées par le 2ème appel.

column_spec(), agir sur un ou plusieurs colonnes

column_spec() est la fonction la plus utile au quotidien. C’est avec elle que nous allons pouvoir faire de la mise en forme « conditionnelle ».

Tout comme row_spec(), elle est attendue après l’appel à kable() et se fait typiquement via un « pipe » %>%. Les 2 paramètres obligatoires sont l’objet kable (en première position) et un (vecteur de) numéro de colonne via le paramètre col= (ou en seconde position). col= est le pendant de row= pour row_spec().

Parmi les autres paramètres à passer, on en retrouve beaucoup de déjà vus plus tôt en parlant de cell_spec() : bold, italic, monospace, underline, strikeout, color, background, extra_css, link, new_tab, tooltip, popover, image… auxquels s’ajoutent d’autres, plus spécifiques d’une colonne :

  • width = (non vectorisé: il n’est attendu qu’une valeur, défaut = NULL), permet de définir la largeur de colonne. Il convient d’utiliser une valeur valide dans le format cible, en HTML il faut préciser l’unité (cm, pt, em,…). Aucun contrôle de cohérence n’est réalisé.
  • width_min (défaut = NULL), idem que ci-dessus mais pour fixer la largeur minimum…
  • width_max (défaut = NULL), … ou maximum
  • border_left (défaut = FALSE), la valeur de l’attribut CSS, fixant l’aspect du bord gauche
  • border_right (défaut = FALSE), … ou du bord droit
  • include_thead (défaut = FALSE), une valeur TRUE/FALSE pour spécifier si l’entête doit aussi être retouchée par la fonction
  • latex_column_spec = NULL,
  • latex_valign (défaut = « p »), le réglage spécifique pour une sortie LaTeX de l’alignement vertical dans la colonne. Les valeurs possibles sont « p » (en haut, c’est une LaTeXerie), « m » (middle = au milieu), « b » (bottom = en bas)

le paramètre « col »

Tout comme « row= » pour row_spec(), il s’agit d’un vecteur de numéros de colonnes. La notion de nom de données à disparu lors de l’appel à kable(). Il convient donc de spécifier correctement ce vecteur.

Cependant, la plupart du temps, les tableaux de sortie sont d’un format défini strictement et la position des colonnes est absolue. Il est donc souvent possible d’écrire les numéros de colonne « en dur » comme un vecteur d’index numériques.

La principale exception est lorsque la table est le résultat d’une transformation par exemple avec les fonctions de la famille pivot_* (et en particulier pivot_wider()) car dans ce cas, le nombre, les noms et positions de colonnes sont incertains. Il convient alors de repérer par calcul les colonnes qui nous intéressent, d’une façon assez similaire aux exemples plus haut. Dans les faits, nous allons donc réutiliser l’appel qui nous avions concocté pour cell_spec().

Pour repérer une colonne, il nous suffit de passer à nouveau par un which() qui va s’appliquer sur le test des noms de colonnes du jeu en cours (« .« ):

# Récupère les index des colonnes s'appelant "GHM" et "n" 
> temp %>% which(names(.) %in% c("GHM", "n"))
R

Comme précédemment, on peut enchainer autant de column_spec() que nécessaire.

La mise en forme conditionnelle

A la différence de row_spec() qui n’accepte pas de vecteur sur les paramètres d’aspect, column_spec(), oui. C’est comme cela que l’on peut faire de la mise en forme conditionnelle.

Le cas des paramètres à valeur discrète

C’est le cas de tous les paramètres on-off comme bold=, italic=, etc…

En effet, si nous passons, par exemple via bold=, un vecteur du même nombre de valeurs qu’il y a de lignes dans le tableau, chaque cellule de la colonne sélectionnée via col= se verra mise en gras si bold= TRUE et en « normal » si bold= FALSE. Il suffit donc de constituer ce vecteur en se basant sur les données originelles (que l’on a gardées dans temp) :

temp %>%
kable("html") %>%
pack_rows(group_label = "CMD = 23" ,start_row = 1,end_row = 16) %>%
pack_rows(group_label = "Type = M" ,start_row = 3,end_row = 15) %>%
pack_rows(group_label = "CMD = 23, Type = M, GHM = 10" ,start_row=7, end_row =11) %>%
kable_classic_2(lightable_options = "striped") %>%

# Ici la magie de column_spec:
column_spec(col=which(names(temp)=="SEVOUT"),bold = (temp$SEVOUT==1))
# qui se lit : change l'habillage des cellules de la colonne "SEVOUT" 
# en mettant en gras (bold) quand la valeur de "SEVOUT" dans la table
# "temp" est égale à 1
R

Bien sûr rien n’empêche que cela soit un traitement plus complexe, tant que le vecteur résultant est du type attendu et qu’il est la même longueur que le jeu de données initial : n’importe quelle fonction compatible (ou valeur littérale) peut être utilisée même une que vous écrivez vous même.

Faire varier les paramètres à valeurs continues

Une mise en forme conditionnelle classique consiste à modifier le fond de la cellule ou de l’avant plan (le texte) en fonction d’une échelle de couleur. C’est tout à fait possible en Rmarkdown/tableExtra via la définition d’une palette discrète ou continue. Cependant, l’intégrer dans le présent article le rendrait encore plus indigeste qu’il n’est déjà. Nous le verrons… LA PROCHAINE FOIS !

Conclusion

Un gros morceau cette fois-ci – R est un langage puissant mais qui se mérite, on va dire… – et en plus, on va y revenir la prochaine fois pour le plus gros de la mise en forme conditionnelle. A bientôt !

Remarque générale

Certes utiliser ces fonctions améliore la lisibilité des tables, mais il faut garder à l’esprit que leur usage est très consommateur de ressources et ralentit énormément le traitement. Je vous conseille d’éviter de traiter des tableaux de plusieurs centaines ou milliers de lignes, de vous limiter à la mise en forme des tableaux récapitulatifs, ciblés ou en limitant les colonnes à modifier.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *