[PYTHON] Gérez le chevauchement lors du dessin d'un diagramme de dispersion avec une grande quantité de données (Matplotlib, Pandas, Datashader)

Si vous dessinez un diagramme de dispersion avec un grand nombre de points de données, il sera trop encombré et vous ne pourrez pas comprendre la quantité de données existant dans une certaine zone.

À titre d'exemple, considérons les données suivantes obtenues en compressant l'ensemble de données d'images numériques manuscrites (MNIST) en deux dimensions avec UMAP.

import pandas as pd

df = pd.read_csv('./mnist_embedding.csv', index_col=0)
display(df)
x y class
0 1.273394 1.008444 5
1 12.570375 0.472456 0
2 -2.197421 8.652475 4
3 -5.642218 -4.971571 1
4 -3.874749 5.150311 9
... ... ... ...
69995 -0.502520 -7.309745 2
69996 3.264405 -0.887491 3
69997 -4.995078 8.153721 4
69998 -0.226225 -0.188836 5
69999 8.405535 -2.277809 6

70000 rows × 3 columns

x est la coordonnée X, y est la coordonnée Y et la classe est l'étiquette (quel nombre de 0 à 9 est écrit).

Essayez de dessiner un diagramme de dispersion avec matplotlib comme d'habitude. À propos, bien que ce ne soit pas le point principal, la fonction récemment ajoutée `` legend_elements '' facilite la création d'une légende pour un diagramme de dispersion multi-catégorie sans tourner l'instruction for.

import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(12, 12))

sc = ax.scatter(df['x'], df['y'], c=df['class'], cmap='Paired', s=6, alpha=1.0)

ax.add_artist(ax.legend(*sc.legend_elements(), loc="upper right", title="Classes"))
plt.axis('off')
plt.show()

output_3_0.png

70 000 points sont tracés. C'est bien d'avoir des clusters séparés pour chaque nombre, mais avec une taille de données si grande, les points sont si denses qu'ils se chevauchent et se remplissent, rendant la structure de chaque classe presque invisible. Je veux faire quelque chose à ce sujet.

Solution 1: ajustez la taille et l'alpha et faites de votre mieux

Pour éviter les chevauchements, réduisez la taille des points ou ajustez la transparence des points pour rendre la densité plus facile à voir. Cela nécessite des essais et des erreurs, et ce n'est pas toujours facile à voir.

fig, ax = plt.subplots(figsize=(12, 12))

sc = ax.scatter(df['x'], df['y'], c=df['class'], cmap='Paired', s=3, alpha=0.1)

ax.add_artist(ax.legend(*sc.legend_elements(), loc="upper right", title="Classes"))
plt.axis('off')
plt.show()

output_7_0.png

Solution 2: Binning hexagonal

C'est aussi une bonne façon de le faire. Le canevas est disposé avec une grille hexagonale et le nombre de points de données dans chacun est agrégé et exprimé en profondeur de couleur. Fonction de tracé Pandas facile à utiliser.

fig, ax = plt.subplots(figsize=(12, 12))

df.plot.hexbin(x='x', y='y', gridsize=100, ax=ax)

plt.axis('off')
plt.show()

output_10_0.png

Solution 3: utiliser Datashader

Il est polyvalent et facile à utiliser. Tant que vous vous y habituez.

Datashader est une bibliothèque qui génère rapidement des "tracés rasterisés" pour les grands ensembles de données.

Après avoir décidé de la résolution (nombre de pixels) de la figure à sortir en premier, les données sont agrégées pour chaque pixel et sorties sous forme d'image, ce qui correspond aux trois étapes du dessin. Etant donné que chaque étape peut être finement ajustée, le degré de liberté est élevé.

Chaque étape sera décrite plus tard, mais si vous les écrivez toutes avec les paramètres par défaut, ce sera comme suit.

import datashader as ds
from datashader import transfer_functions as tf

tf.shade(ds.Canvas().points(df,'x','y'))

output_13_0.png

Réglage de chaque étape

Dans Datashader

  1. Définissez la toile

  2. Paramètres et calculs de la fonction d'agrégation

  3. Convertir en image

Faites un tracé en trois étapes. Chacun est expliqué ci-dessous.

1. Définissez la toile

datashader.Définissez diverses toiles avec Canvas. Résolution verticale et horizontale (pixels), axe logarithmique ou non, plage numérique (xlim dans matplotlib),ylim) etc.




```python
canvas = ds.Canvas(plot_width=600, plot_height=600, #600 pixels verticaux et horizontaux
                   x_axis_type='linear', y_axis_type='linear', # 'linear' or 'log'
                   x_range=(-10,15), y_range=(-15,10))

2. Paramètres et calculs de la fonction d'agrégation

J'ai fait une toile de (600 x 600) pixels ci-dessus. Ici, nous définissons comment agréger les données pour chacun de ces pixels. Par exemple, modifiez la densité de couleur en fonction du nombre de points de données qui entrent un pixel ou faites-en une valeur binaire, même si un seul point de données est inclus ou non.

Par exemple, pour la variable de canevas définie ci-dessus, entrez le bloc de données, les coordonnées de l'axe x (nom de la colonne), les coordonnées de l'axe y et la fonction d'agrégation comme indiqué ci-dessous, puis exécutez le calcul. La fonction `` datashader.reductions.count '' compte le nombre de points de données qui entrent dans un pixel.

canvas.points(df, 'x', 'y', agg=ds.count())
<xarray.DataArray (y: 600, x: 600)>
array([[0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       ...,
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0]], dtype=int32)
Coordinates:
  * x        (x) float64 -9.979 -9.938 -9.896 -9.854 ... 14.85 14.9 14.94 14.98
  * y        (y) float64 -14.98 -14.94 -14.9 -14.85 ... 9.854 9.896 9.938 9.979

De cette manière, les données de dessin ont été générées en comptant le nombre de points de données dans une matrice de taille (600 x 600).

Si vous souhaitez agréger par la valeur binaire de la saisie ou non des points de données au lieu de compter, utilisez la fonction datashader.reductions.any '' et procédez comme suit.

canvas.points(df, 'x', 'y', agg=ds.any())
<xarray.DataArray (y: 600, x: 600)>
array([[False, False, False, ..., False, False, False],
       [False, False, False, ..., False, False, False],
       [False, False, False, ..., False, False, False],
       ...,
       [False, False, False, ..., False, False, False],
       [False, False, False, ..., False, False, False],
       [False, False, False, ..., False, False, False]])
Coordinates:
  * x        (x) float64 -9.979 -9.938 -9.896 -9.854 ... 14.85 14.9 14.94 14.98
  * y        (y) float64 -14.98 -14.94 -14.9 -14.85 ... 9.854 9.896 9.938 9.979

3. Conversion en image

Pour convertir en image, utilisez la fonction shader de `` datashader.transfer_functions. Passez les données matricielles agrégées calculées ci-dessus à l'argument de la fonction ombre ''. De plus, diverses fonctions de transfert '' sont préparées et vous pouvez affiner la sortie de l'image. Ici, le résultat du comptage et du total est transformé en fond blanc avec la fonction `` set_background '' et mis en image.

tf.set_background(tf.shade(canvas.points(df,'x','y', agg=ds.count())), 'white')

output_26_0.png

L'ombrage est exprimé en fonction de la densité des points de données, ce qui rend la structure beaucoup plus facile à voir.

De la même manière, essayez le cas d'un total avec deux valeurs indiquant si les points de données sont inclus ou non.

tf.set_background(tf.shade(canvas.points(df,'x','y', agg=ds.any())), 'white')

output_28_0.png

Agréger avec d'autres données auxiliaires

Jusqu'à présent, seules les informations de coordonnées des données étaient utilisées pour l'agrégation, mais il arrive souvent que chaque point de données ait une étiquette d'une catégorie ou une valeur continue est attribuée.

Puisque ces informations ne sont pas reflétées en comptant simplement les points de données qui entrent dans le pixel, il existe une fonction d'agrégation spéciale pour chacun.

Agrégation lorsque les données auxiliaires sont des variables catégorielles

Dans le cas de MNIST, il y a une étiquette pour la classe de réponse correcte, donc je veux la coder correctement et la tracer. En tant que fonction d'agrégation pour cela, il y a datashader.reductions.count_cat```. Cette fonction compte le nombre de points de données qui entrent dans un pixel pour chaque étiquette. En d'autres termes, dans le cas de MNIST, 10 matrices agrégées (600 x 600) seront créées.

Pour utiliser count_cat, les données d'étiquette doivent être du type de catégorie Pandas (le type int n'est pas bon), donc convertissez d'abord la chaîne d'étiquette du bloc de données en type de catégorie.

df['class'] = df['class'].astype('category')

Agréger avec count_cat. Contrairement aux fonctions d'agrégation de `` count '' et `any '', vous devez spécifier le nom de colonne dont la colonne du bloc de données représente l'étiquette.

agg = canvas.points(df, 'x', 'y', ds.count_cat('class'))

La couleur de chaque étiquette est définie dans un dictionnaire en utilisant l'étiquette comme clé. Extrayez la couleur "Paired" de matplotlib pour qu'elle corresponde à la couleur de la figure lorsqu'elle est dessinée au début. Inclusion de liste de type dictionnaire facile à utiliser.

import matplotlib
color_key = {i:matplotlib.colors.rgb2hex(c[:3]) for i, c 
             in enumerate(matplotlib.cm.get_cmap('Paired', 10).colors)}
print(color_key)
{0: '#a6cee3', 1: '#1f78b4', 2: '#b2df8a', 3: '#fb9a99', 4: '#e31a1c', 5: '#fdbf6f', 6: '#cab2d6', 7: '#6a3d9a', 8: '#ffff99', 9: '#b15928'}

Essayez de l'imaginer. Il semble que la couleur de chaque pixel soit dessinée en mélangeant chaque couleur en fonction du nombre d'étiquettes de points de données qui entrent dans le pixel.

tf.set_background(tf.shade(agg, color_key=color_key), 'white')

output_39_0.png

Agrégation lorsque les données auxiliaires sont une valeur continue

Une sorte de valeur continue peut être associée à chaque point de données. Par exemple, dans l'analyse d'une cellule unique, lorsque des chiffres compressés dimensionnellement de dizaines de milliers de cellules sont utilisés, la profondeur de couleur est modifiée en fonction du niveau d'expression d'un gène pour chaque cellule.

Puisqu'un pixel contient plusieurs points de données, une valeur représentative doit être déterminée d'une manière ou d'une autre. En tant que fonction d'agrégation pour cela, des statistiques simples telles que max, moyenne, mode sont préparées.

MNIST n'a pas de données auxiliaires de valeur continue, essayez donc de le créer de manière appropriée. Comme une quantité facile à comprendre, calculons la luminosité moyenne de la zone centrale de l'image. Zéro doit être sombre (car la ligne passe rarement au milieu de l'image) et 1 doit être clair.

data = pd.read_csv('./mnist.csv').values[:, :784]
data.shape
(70000, 784)
#C'est une image de taille 28 x 28.
upper_left = 28 * 13 + 14
upper_right = 28 * 13 + 15
bottom_left = 28 * 14 + 14
bottom_right = 28 * 14 + 15

average_center_area = data[:, [upper_left, upper_right, 
                               bottom_left, bottom_right]].mean(axis=1)

Tout d'abord, essayez de dessiner normalement avec matplotlib.

fig, ax = plt.subplots(figsize=(12, 12))

sc = ax.scatter(df['x'], df['y'], c=average_center_area, cmap='viridis', 
                vmin=0, vmax=255, s=6, alpha=1.0)

plt.colorbar(sc)
plt.axis('off')
plt.show()

output_45_0.png

Après tout il est écrasé et je ne comprends pas bien.

Passez-le au Datashader et essayez de le peindre en fonction de la "valeur maximale" des points de données contenus dans chaque pixel. Il peut être agrégé avec la fonction `` datashader.reductions.max ''.

df['value'] = average_center_area
agg = canvas.points(df, 'x', 'y', agg=ds.max('value'))
tf.set_background(tf.shade(agg, cmap=matplotlib.cm.get_cmap('viridis')), 'white')

output_47_0.png

C'est plus facile à voir. Cela peut ne pas être très différent d'ajuster la taille à une taille plus petite avec dispersion de matplotlib, mais il est pratique de pouvoir dessiner magnifiquement sans essais et erreurs détaillés.

De plus, même si la taille des données est énorme, elle est rapide, il n'est donc pas stressant de faire divers ajustements tels que ce qui se passe lors d'un total avec des valeurs moyennes.

agg = canvas.points(df, 'x', 'y', agg=ds.mean('value'))
tf.set_background(tf.shade(agg, cmap=matplotlib.cm.get_cmap('viridis')), 'white')

output_49_0.png

Recommended Posts

Gérez le chevauchement lors du dessin d'un diagramme de dispersion avec une grande quantité de données (Matplotlib, Pandas, Datashader)
Une collection de méthodes utilisées lors de l'agrégation de données avec des pandas
Je souhaite résoudre le problème de fuite de mémoire lors de la sortie d'un grand nombre d'images avec Matplotlib
Un mémorandum de méthode souvent utilisé lors de l'analyse de données avec des pandas (pour les débutants)
Essayez de créer une table d'enregistrement de bataille avec matplotlib à partir des données de "Schedule-kun"
Lors de la lecture d'un fichier csv avec read_csv de pandas, la première colonne devient index
Un diagramme de réseau a été créé avec les données du COVID-19.
Remarques sur la gestion de grandes quantités de données avec python + pandas
Présentation du potentiel du nuage de points de Plotly avec des exemples pratiques
Reformatez l'axe des temps du graphique de la série chronologique des pandas avec matplotlib
Lors de la génération d'un grand nombre de graphiques avec matplotlib, je ne souhaite pas afficher le graphique à l'écran (environnement jupyter)
Comment créer une grande quantité de données de test dans MySQL? ??
Afficher l'étiquette de chaque élément lors du dessin d'un diagramme de dispersion dans Pandas
Traçage de données polyvalent avec pandas + matplotlib
Ne changez pas l'ordre des colonnes lors de la concaténation des trames de données pandas.
J'ai fait une erreur en récupérant la hiérarchie avec MultiIndex of pandas
Le résultat était meilleur lorsque les données d'apprentissage du mini-lot ont été faites un hybride de fixe et aléatoire avec un réseau de neurones.
Alignez la taille de la barre de couleurs avec matplotlib
Essayez de dessiner une distribution normale avec matplotlib
Exemple de traitement efficace des données avec PANDAS
Un mémorandum de problème lors du formatage des données
Introduction du code de dessin pour les figures avec un certain degré de perfection des données météorologiques
[Vérification] LevelDB prend-il du temps pour enregistrer les données lorsque la quantité de données augmente? ??
Lien vers les points de données du graphe créé par jupyterlab & matplotlib
Changer le bloc de données des données d'achat de pandas (produit ID X) en dictionnaire
Dessinez une ligne de pliage / diagramme de dispersion avec python matplotlib pour fichier CSV (2 colonnes)
[Introduction à Python] Comment obtenir l'index des données avec l'instruction for