[pandas] Surligner des lignes d'une DataFrame
Résolumamiemando Messages postés 33346 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 8 novembre 2024 - 10 juin 2024 à 16:47
- [pandas] Surligner des lignes d'une DataFrame
- Aller à la ligne dans une cellule excel - Guide
- Excel trier par ordre alphabétique en gardant les lignes - Guide
- Partager des photos en ligne - Guide
- Afficher toutes les lignes masquées excel ✓ - Forum Excel
- Probleme d'affichage de ligne dans Excel ✓ - Forum Excel
2 réponses
Modifié le 1 oct. 2024 à 16:47
Bonjour,
Réponse brève
Voir la section "Réponse finale" en bas de message, pour les personnes pressées et/ou familières de pandas/numpy/python
Réponse détaillée
La question est double car il faut considérer deux sous-questions indépendantes:
- Comprendre comment appliquer un style en pandas
- Comprendre comment extraire le masque adéquat pour appliquer le style sur les cellules adéquates
Construire un masque avec pandas
Le concept de masque (qui en réalité est réalisé grâce à numpy) consiste à construire un tableau de même taille que l'itérable considéré (une pd.DataFrame, une pd.Series, un np.array, etc).
Dans cette optique, pandas propose un système permettant de faire une requête sur une pd.DataFrame en vue d'en dériver un masque qui ne conserve que les cellules satisfaites par la requête. Si tu as fait des bases de données, c'est un peu là même idée qu'une requête (SELECT ... WHERE en SQL).
Dans ton cas, on veut sélectionner toutes les rangées telle que la colonne "telephone" de la rangée courante d'une première pd.DataFrame (df1) apparaît n'importe dans la colonne "telephone" d'une autre pd.DataFrame (df2). Pandas propose un opérateur permettant de faire un test d'appartenance de manière vectorisée (c'est-à-dire, de manière efficace) : pd.DataFrame.isin. Cette opération retourne un masque. Il est possible d'appliquer sur un masque n'importe quelle opération "bitwise" (bit-à-bit) :
- renverser le masque (NOT) : ~
- faire l'intersection avec un autre masque (AND) : &
- faire l'union avec un autre masque (OR) : |
Plus de détails dans ce tutoriel.
Dans ton cas, le masque s'écrit :
~df2["telephone"].isin(df1["telephone"])
Dans ce qui suit, je vais utiliser la colonne 1 (donc la colonne qui contient des entiers) pour stocker mes "numéros de téléphone".
import pandas as pd df1 = pd.DataFrame([ ["toto", 2], ["tata", 3], ["titi", 4], ]) df2 = pd.DataFrame([ ["toto", 2], ["tutu", 5], ["titi", 3], ]) mask = df2[1].isin(df1[1]) print(mask) # Affiche : # 0 True # 1 False # 2 True # Name: 1, dtype: bool
Appliquer un style HTML à des cellules pandas
Pour commencer, démystifions comment marche l'application d'un style en pandas. Considérons l'un exemple jouet ci-dessous inspiré de cette discussion.
import numpy as np import pandas as pd from IPython.display import HTML df = pd.DataFrame(np.reshape(np.arange(6), (2, 3))) mask1 = np.array( [ [0, 1, 0], [0, 0, 1], ], dtype=bool ) mask2 = np.array( [ [0, 0, 1], [0, 1, 1], ], dtype=bool ) def styler(s): color = "background-color: {}".format m = np.select( [mask1, mask2], list(map(color, ["red", "blue"])), default="" ) print(m) # Affiche: # [['' 'background-color: red' 'background-color: blue'] # ['' 'background-color: blue' 'background-color: red']] return m df = df.style.apply(styler, axis=None) HTML(df.to_html())
Dans cet exemple on voit que le style à appliquer (styler) retourne un tableau dont
- les dimensions correspondent à la pd.DataFrame de départ
- chaque case contient le style HTML à appliquer à chaque cellule.
On voit que ce code peut être facilement étendu avec un nombre arbitraire de masques (pour le moment, 2 masques, respectivement colorés en rouge et bleu). Un exemple avec plus de paires (masque, couleur) est proposé ici.
Une case ne peut avoir qu'une couleur, alors quid dans notre exemple de la case en bas à droite, sélectionnée par les deux masques ? Eh bien, cet extrait de code sélectionne la première couleur qui correspond à un masque, donc si une case correspond au masque rouge et au masque bleu, alors elle sera colorée en rouge. Notons aussi que l'attribut default permet d'attribuer le style par défaut (dans cet exemple, aucun style, puisque default="")
Maintenant, ce code est un peu sale, car styler utilise mask en tant que variable globale. On ne peut pas l'ajouter en paramètre, alors comment faire. Ici, le plus simple est de s'en sortir avec un foncteur, c'est-à-dire en une classe capable de se comporter comme une fonction. En python, cela consiste à définir une classe, le foncteur, qui implémente la méthode __call__ dans ledit objet. Les paramètres de __call__ correspondent à ceux de l'ancienne fonction (il faut aussi passer self, puisqu'il s'agit d'une méthode d'instance).
Dans notre cas la fonction styler se réécrit avec le foncteur Styler suivant :
import numpy as np import pandas as pd from IPython.display import HTML class Styler: def __init__(self, masks, colors): self.masks = masks self.colors = colors def __call__(self, s): color = "background-color: {}".format return np.select( self.masks, list(map(color, self.colors)), default="" ) df = pd.DataFrame(np.reshape(np.arange(6), (2, 3))) mask1 = np.array( [ [0, 1, 0], [0, 0, 1], ], dtype=bool ) mask2 = np.array( [ [0, 0, 1], [0, 1, 1], ], dtype=bool ) styler = Styler([mask1, mask2], ["red", "blue"]) df = df.style.apply(styler, axis=None) HTML(df.to_html())
Réunir les deux réponses
Il reste un problème de taille à régler. Dans notre exemple, si df1 est de taille (m, n), alors mask est de taille m. Or nous avons vu que pour appliquer le style, il faut que le masque doit être de la même taille que df1, donc de taille (m, n). Il faut donc revoir comment on construit mask.
Pour régler ce problème, soit un nouveau masque mask2 de taille (m, n) dans lequel les cellules à de la ligne i valent 1 si et seulement si mask[i] == 1, 0 sinon. Ce nouveau masque peut se construire ainsi.
mask = ~df2[1].isin(df1[1]) mask2 = np.zeros(df1.shape, dtype=bool) mask2[mask, :] = 1 print(mask2)
Afin de garder le code concis, je vais maintenant me passer de mon masque intermédiaire mask et renommer mask2 en mask :
mask = np.zeros(df1.shape, dtype=bool) mask[~df2[1].isin(df1[1]), :] = 1 print(mask)
Réponse finale
import pandas as pd import numpy as np from IPython.display import HTML class Styler: def __init__(self, masks, colors): self.masks = masks self.colors = colors def __call__(self, s): color = "background-color: {}".format return np.select( self.masks, list(map(color, self.colors)), default="" ) def highlight(df, mask, color="red"): m = np.zeros(df.shape, dtype=bool) m[mask, :] = 1 styler = Styler([m], [color]) return df.style.apply(styler, axis=None) df1 = pd.DataFrame([ ["toto", 2], ["tata", 3], ["titi", 4], ]) df2 = pd.DataFrame([ ["toto", 2], ["tutu", 5], ["titi", 3], ]) df1 = highlight(df1, ~df2[1].isin(df1[1]), "red") HTML(df1.to_html())
Bonne chance
Modifié le 10 juin 2024 à 15:16
Merci pour ta réponse mais sans succès, je suis mauvais en python et ca m'agace.
Je mets mon code :
import pandas as pd df_clients = pd.read_excel(r"Fichier clients.xlsx", header=0) df_retour = pd.read_excel(r"Retour fichier 2.xlsx", header=0) L = len(df_clients) x = df_clients["telephone"] x = x.to_frame() tel_clients = [int(x.values[k]) for k in range(L)] l = len(df_retour) x = df_retour["telephone"] x = x.to_frame() tel_retour = [int(x.values[k]) for k in range(l)] def surlignage_ligne(x): if x.telephone not in tel_retour : return ["background-color: red"] * len(df_clients.values[0]) else: return ['background-color: ""'] * len(df_clients.values[0]) df_clients.style.apply(surlignage_ligne, axis=1)
Nouvelle version :
import pandas as pd df_clients = pd.read_excel(r"Fichier clients.xlsx", header=0) df_retour = pd.read_excel(r"Retour fichier 2.xlsx", header=0) L = len(df_clients) x = df_clients["telephone"] x = x.to_frame() tel_clients = [int(x.values[k]) for k in range(L)] l = len(df_retour) x = df_retour["telephone"] x = x.to_frame() tel_retour = [int(x.values[k]) for k in range(l)] ref_manquantes = [] for i in range(L): if tel_clients[i] not in tel_retour: ref_manquantes.append(i+1) def surlignage_ligne(x): if x.index in ref_manquantes : return["background-color: red"] * len(df_clients.values[0]) else: return['background-color: ""'] * len(df_clients.values[0]) df_clients.style.apply(surlignage_ligne, axis=1) writer = pd.ExcelWriter("Fichier clients modifié.xlsx") df_clients.to_excel(writer, sheet_name="Feuille1", index=False) writer.save()
Merci beaucoup de la réponse