[PYTHON] Je veux bien comprendre les bases de Bokeh

Afin de comprendre le plus soigneusement possible l'intrigue interactive de bokeh, je vais essayer de le déchiffrer en utilisant [Échantillon abordable] 0 comme exemple. J'ai écrit les spécifications comme prévu à partir du code source, mais comme j'ai également vérifié la documentation, cela ne devrait faire aucun doute. Le code source complet est [ici] 1

Un tel mec ↓ bokeh.png

Génération de données

# create three normal population samples with different parameters
x1 = np.random.normal(loc=5.0, size=400) * 100
y1 = np.random.normal(loc=10.0, size=400) * 10

x2 = np.random.normal(loc=5.0, size=800) * 50
y2 = np.random.normal(loc=5.0, size=800) * 10

x3 = np.random.normal(loc=55.0, size=200) * 10
y3 = np.random.normal(loc=4.0, size=200) * 10

x = np.concatenate((x1, x2, x3))
y = np.concatenate((y1, y2, y3))

L'explication est omise car elle n'est pas liée au bokeh.

Tracé de groupe de points

TOOLS="pan,wheel_zoom,box_select,lasso_select,reset"

# create the scatter plot
p = figure(tools=TOOLS, plot_width=600, plot_height=600, min_border=10, min_border_left=50,
           toolbar_location="above", x_axis_location=None, y_axis_location=None,
           title="Linked Histograms")
p.background_fill_color = "#fafafa"

↑ Donnez à l'objet figure le nom de l'outil que vous souhaitez afficher et définissez la couleur d'arrière-plan. Jusqu'à ce point, le flux est le même qu'un graphique normal. figure est une fonction de bokeh.models.figure et sa valeur de retour est bokeh.models.Figure. Je vais y mettre divers paramètres.

p.select(BoxSelectTool).select_every_mousemove = False
p.select(LassoSelectTool).select_every_mousemove = False

↑ Faites attention d'ici. Premièrement, p.select () est une méthode de figure, et la principale est la méthode de source d'héritage bokeh.models.Model. Étant donné un objet de classe pour le sélecteur, renvoie le type de sélecteur approprié affecté à la figure. Par exemple, dans la première ligne, une instance de la classe BoxSelectTool est obtenue et en définissant select_every_mousemove sur False, la mise à jour ne se produit pas tant que la sélection de la souris n'est pas terminée.

Référence →

select(selector) Query this object and all of its references for objects that match the given selector.

r = p.scatter(x, y, size=3, color="#3A5785", alpha=0.6)

Voici un graphique des points! La valeur de retour r est utilisée en dernier. La valeur de retour lorsqu'elle est tracée est généralement renvoyée par une instance de la classe GlyphRenderer, qui peut être utilisée ultérieurement pour peaufiner le tracé.

histogramme

# create the horizontal histogram
hhist, hedges = np.histogram(x, bins=20)
hzeros = np.zeros(len(hedges)-1)
hmax = max(hhist)*1.1

↑ La valeur de retour de numpy.histrogram () est (liste des hauteurs de chaque histogramme, liste des valeurs limites de l'histogramme). le bokeh n'a pas d'importance.

LINE_ARGS = dict(color="#3A5785", line_color=None)

ph = figure(toolbar_location=None, plot_width=p.plot_width, plot_height=200, x_range=p.x_range,
            y_range=(-hmax, hmax), min_border=10, min_border_left=50, y_axis_location="right")
ph.xgrid.grid_line_color = None
ph.yaxis.major_label_orientation = np.pi/4
ph.background_fill_color = "#fafafa"

ph.quad(bottom=0, left=hedges[:-1], right=hedges[1:], top=hhist, color="white", line_color="#3A5785")

↑ Histogramme horizontal. Il existe de nombreux éléments de réglage et c'est compliqué, mais le flux de base reste le même. J'obtiens une figure avec figure () et dessine un quadrangle avec sa méthode quad (). (Il semble que ce soit une méthode qui trace un rectangle parallèle aux coordonnées, pas une méthode dédiée à l'histogramme). Les coordonnées des quatre côtés sont spécifiées par bas ~ haut. Chaque quadrangle semble être une instance de la classe bokeh.models.glyphs.Quad.

Les autres arguments qui semblent utiles sont les suivants.

toolbar_location=None #Masquer la barre de tâches
plot_width=p.plot_width #Partage de la largeur du tracé
x_range=p.x_range #x Partage de plage de coordonnées
min_border_left=50 #Marge minimale sur le côté gauche du tracé
ph.yaxis.major_label_orientation = np.pi/4 #Rotation des étiquettes de coordonnées

Histogramme au moment de la sélection

L'histogramme qui s'affiche lorsque vous sélectionnez un groupe de points,

hh1 = ph.quad(bottom=0, left=hedges[:-1], right=hedges[1:], top=hzeros, alpha=0.5, **LINE_ARGS)
hh2 = ph.quad(bottom=0, left=hedges[:-1], right=hedges[1:], top=hzeros, alpha=0.1, **LINE_ARGS)

Je l'ai dessiné en premier. À ce stade, top = hzeros, donc la hauteur est définie sur 0 et elle n'est pas visible. Il semble que la hauteur de ces valeurs de retour hh1 et hh2 soit mise à jour lorsqu'elle est sélectionnée (*).

Histogramme vertical

# create the vertical histogram
vhist, vedges = np.histogram(y, bins=20)
vzeros = np.zeros(len(vedges)-1)
vmax = max(vhist)*1.1

pv = figure(toolbar_location=None, plot_width=200, plot_height=p.plot_height, x_range=(-vmax, vmax),
            y_range=p.y_range, min_border=10, y_axis_location="right")
pv.ygrid.grid_line_color = None
pv.xaxis.major_label_orientation = np.pi/4
pv.background_fill_color = "#fafafa"

pv.quad(left=0, bottom=vedges[:-1], top=vedges[1:], right=vhist, color="white", line_color="#3A5785")
vh1 = pv.quad(left=0, bottom=vedges[:-1], top=vedges[1:], right=vzeros, alpha=0.5, **LINE_ARGS)
vh2 = pv.quad(left=0, bottom=vedges[:-1], top=vedges[1:], right=vzeros, alpha=0.1, **LINE_ARGS)

Il est réglé de la même manière que dans le sens horizontal.

terminer

Assemblez les anciens.

layout = gridplot([[p, pv], [ph, None]], merge_tools=False)

↑ Premièrement, les figures sont disposées en deux dimensions avec la fonction bokeh.layouts.girdplot (). À propos, si vous souhaitez simplement organiser verticalement ou horizontalement au lieu de deux dimensions, utilisez bokeh.layouts.column ou row.

curdoc().add_root(layout)
curdoc().title = "Selection Histogram"

↑ curdoc est une abréviation pour le document courant, qui récupère le Document par défaut (une classe qui résume la sortie de bokeh) et le lie à la grille avec la méthode add_root (). C'est le point, mais il semble que la spécification est que "lorsqu'une modification est apportée à la grille add_root, le rappel enregistré dans Document avec" on_change "est appelé." De la référence →

add_root(model, setter=None)

Add a model as a root of this Document. Any changes to this model (including to other models referred to by it) will trigger on_change callbacks registered on this document.

Donc, si vous regardez d'abord la dernière ligne,

r.data_source.selected.on_change('indices', update)

Il est vrai que le callback est enregistré dans on_change.

R ici est la valeur de retour de scatter plot () (une instance de la classe GlyphRenderer) r.data_source correspond à l'ensemble de données tracé et sa sélection correspond à la partie sélectionnée des données.

on_chage () est une méthode de bokeh.model.Model qui enregistre un rappel pour cet objet.

def on_change(self, attr, *callbacks):
        ''' Add a callback on this object to trigger when ``attr`` changes.

        Args:
            attr (str) : an attribute name on this object
            *callbacks (callable) : callback functions to register

Le premier argument attr est difficile à comprendre, mais ici nous décrivons "quels changements pour appeler le callback". Cette fois, puisqu'il s'agit d'une plage de sélection (= index de données = selection.indices), "indices" est spécifié.

Si vous souhaitez associer un rappel à un événement spécifique (appui sur un bouton, mouvement du curseur, etc.) au lieu de le modifier

on_event(event, callback)

utilisation.

(Mis à part ce qui suit) Ici, les fonctions Python sont enregistrées, mais si vous souhaitez enregistrer des fonctions écrites en javascript (une instance de bokeh.models.CustomJS)

m.js_on_change(attr, callback)

Devrait être utilisé. Cette zone est décrite dans le [Chapitre 6 du tutoriel officiel] 1. Il semble que vous deviez l'utiliser si vous voulez une seule sortie html. Avertissement à l'exécution →

WARNING:bokeh.embed.util:
You are generating standalone HTML/JS output, but trying to use real Python
callbacks (i.e. with on_change or on_event). This combination cannot work.

Only JavaScript callbacks may be used with standalone output. For more
information on JavaScript callbacks with Bokeh, see:

    https://docs.bokeh.org/en/latest/docs/user_guide/interaction/callbacks.html

Alternatively, to use real Python callbacks, a Bokeh server application may
be used. For more information on building and running Bokeh applications, see:

    https://docs.bokeh.org/en/latest/docs/user_guide/server.html

(À part d'ici)

Enfin, à propos du contenu de la fonction de rappel update ()

def update(attr, old, new):
    inds = new
    if len(inds) == 0 or len(inds) == len(x):
        hhist1, hhist2 = hzeros, hzeros
        vhist1, vhist2 = vzeros, vzeros
    else:
        neg_inds = np.ones_like(x, dtype=np.bool)
        neg_inds[inds] = False
        hhist1, _ = np.histogram(x[inds], bins=hedges)
        vhist1, _ = np.histogram(y[inds], bins=vedges)
        hhist2, _ = np.histogram(x[neg_inds], bins=hedges)
        vhist2, _ = np.histogram(y[neg_inds], bins=vedges)

    hh1.data_source.data["top"]   =  hhist1
    hh2.data_source.data["top"]   = -hhist2
    vh1.data_source.data["right"] =  vhist1
    vh2.data_source.data["right"] = -vhist2

Il semble que attr spécifié par on_change et les valeurs anciennes et nouvelles avant et après le changement de l'attribut soient données en arguments. Ici, nouveau est l'index du groupe de points nouvellement sélectionné. Comme prédit par (*), la valeur supérieure de l'histogramme horizontal est mise à jour en fonction de l'index sélectionné. La relation de référence est un peu loin, mais

Jeu de données d'origine ↑ nuage de points (= partager la source de données avec un histogramme horizontal) ↑ selection ↑ rappel on_change

On peut voir que l'index donné à l'argument on_change est le même que l'index du premier ensemble de données car il est connecté comme ceci.

Les références

[Échantillon officiel] 0 [Code source] 1

Recommended Posts

Je veux bien comprendre les bases de Bokeh
Je souhaite personnaliser l'apparence de zabbix
Même les débutants veulent dire "Je comprends parfaitement Python"
Je veux grep le résultat de l'exécution de strace
Je souhaite augmenter la sécurité de la connexion SSH
Je veux comprendre à peu près systemd
Je souhaite utiliser uniquement le traitement de normalisation SudachiPy
Je veux déterminer l'authenticité d'un élément du tableau numpy
Je veux connaître la nature de Python et pip
Keras Je veux obtenir la sortie de n'importe quelle couche !!
Je veux connaître la légende du monde des technologies informatiques
Je veux épingler Spyder à la barre des tâches
Je veux obtenir le nom de la fonction / méthode en cours d'exécution
Je veux sortir froidement sur la console
[Pytorch] Je souhaite attribuer manuellement les paramètres d'entraînement du modèle
[Python3] Comprendre les bases de Beautiful Soup
Je veux gérer la rime part1
Je veux lire la version html de la version "OpenCV-Python Tutorials" OpenCV 3.1
Je veux gérer la rime part3
Je ne connaissais pas les bases de Python
Je veux afficher la barre de progression
Je veux vérifier la position de mon visage avec OpenCV!
Je veux connaître la population de chaque pays du monde.
Je veux gérer la rime part2
Je veux gérer la rime part5
Je veux gérer la rime part4
[Python3] Comprendre les bases des opérations sur les fichiers
Je ne veux pas l'admettre ... Représentation dynamique du système de Neural Network
Je veux expliquer en détail la classe abstraite (ABCmeta) de Python
Je souhaite trier une liste dans l'ordre des autres listes
Je veux exprimer mes sentiments avec les paroles de Mr. Children
Je veux analyser les sentiments des gens qui veulent se rencontrer et trembler
Je veux prédire le succès des joueurs NBA utilisant le SDK Qore
Je souhaite laisser une commande arbitraire dans l'historique des commandes de Shell
Je souhaite arrêter la suppression automatique de la zone tmp dans RHEL7
Python: je souhaite mesurer proprement le temps de traitement d'une fonction
Je veux gérer la rime part7 (BOW)
Je veux obtenir les données de League of Legends ③
Je veux obtenir les données de League of Legends ②
Je veux obtenir les données de League of Legends ①
Je souhaite utiliser la fonction d'activation Mish
Je veux afficher la progression en Python!
J'ai essayé de vectoriser les paroles de Hinatazaka 46!
Je veux obtenir le chemin du répertoire où le fichier en cours d'exécution est stocké.
Je souhaite visualiser l'état des transferts de la J League 2020, que dois-je faire?
L'histoire de l'adresse IPv6 que je souhaite conserver au minimum
Je souhaite utiliser Python dans l'environnement de pyenv + pipenv sous Windows 10
Je veux utiliser PyTorch pour générer quelque chose comme les paroles de Japari Park
Je souhaite définir un cycle de vie dans la définition de tâche d'ECS
Je veux ajouter du silence pendant 1 seconde au début d'un fichier wav
Je souhaite voir une liste de fichiers WebDAV dans le module Requêtes
Je veux recadrer l'image le long du contour au lieu du rectangle [python OpenCV]
Je souhaite stocker les résultats de% time, %% time, etc. dans un objet (variable)
Je n'ai pas compris le redimensionnement de TensorFlow, alors je l'ai résumé visuellement.
Je veux voir le nom de fichier de DataLoader
Je veux comprendre (l'ingénierie) UMAP plus fort que t-SNE
Je veux détecter des images de chats d'Instagram