Matplotlib grafiğinde en yakın veri noktasının koordinatlarını alma


9

Ben kullanıyorum matplotlibile NavigationToolbar2QT. Araç çubuğu imlecin konumunu gösteriyor. Ancak imlecin en yakın veri noktasına (yeterince yakın olduğunda) geçmesini veya sadece en yakın veri noktasının koordinatını göstermesini istiyorum. Bu bir şekilde düzenlenebilir mi?


Lütfen aşağıdaki bağlantıyı kontrol edin ve sorununuzu çözüp çözmediğine bakın. Bağlantı, aradığınıza benzeyen bir işlev snaptocursor sağlar. matplotlib.org/3.1.1/gallery/misc/cursor_demo_sgskip.html
Anupam Chaplot

@AnupamChaplot "İmleci çizmek için Matplotlib kullanır ve bu yavaş olabilir çünkü bu, her fare hareketinde figürün yeniden çizilmesini gerektirir." Ben grafik üzerinde 10000 puan HER ile yaklaşık 16 parsel var, bu yüzden yeniden çizim ile bu oldukça yavaş olacaktır.
Pygmalion

Hiçbir şeyi görsel olarak yeniden çizmek istemiyorsanız (neden bunu istesiniz?), Araç çubuğunda gösterilenleri matplotlib.org/3.1.1/gallery/images_contours_and_fields/…
ImportanceOfBeingErnest

@ ImportanceOfBeingErnest Önerinizi anlamıyorum. Ama şunu hayal edin: 16 satır grafiğiniz var ve her birinin belirgin bir zirvesi var. Verilere bakmadan bir arsanın zirvesinin kesin koordinatlarını bilmek istersiniz. İmleci asla tam olarak noktaya koyamazsınız, bu yüzden bu son derece kesin değildir. Dolayısıyla, Origin gibi programlar, geçerli imleç konumuna en yakın noktanın kesin koordinatlarını gösterme seçeneğine sahiptir.
Pygmalion

1
Evet, cursor_demo_sgskip bunu yapar. Ancak imleci çizmek istemiyorsanız, o örnekteki hesaplamaları kullanabilir ve bunun yerine, sonuç çubuğunu image_zcoord
ImportanceOfBeingErnest

Yanıtlar:


6

Büyük puan kümeleriyle çalışıyorsanız, şunları kullanmanızı öneririm CKDtrees:

import matplotlib.pyplot as plt
import numpy as np
import scipy.spatial

points = np.column_stack([np.random.rand(50), np.random.rand(50)])
fig, ax = plt.subplots()
coll = ax.scatter(points[:,0], points[:,1])
ckdtree = scipy.spatial.cKDTree(points)

kpie'sBuraya biraz cevap verdim . Bir kez ckdtreeoluşturulduktan sonra , en yakın noktaları anında ve bunlarla ilgili çeşitli bilgileri biraz çaba ile tanımlayabilirsiniz:

def closest_point_distance(ckdtree, x, y):
    #returns distance to closest point
    return ckdtree.query([x, y])[0]

def closest_point_id(ckdtree, x, y):
    #returns index of closest point
    return ckdtree.query([x, y])[1]

def closest_point_coords(ckdtree, x, y):
    # returns coordinates of closest point
    return ckdtree.data[closest_point_id(ckdtree, x, y)]
    # ckdtree.data is the same as points

İmleç konumunun etkileşimli gösterimi. En yakın noktanın koordinatlarının Gezinme Araç Çubuğu'nda görüntülenmesini istiyorsanız:

def val_shower(ckdtree):
    #formatter of coordinates displayed on Navigation Bar
    return lambda x, y: '[x = {}, y = {}]'.format(*closest_point_coords(ckdtree, x, y))

plt.gca().format_coord = val_shower(ckdtree)
plt.show()

Olayları kullanma. Başka bir etkileşim türü istiyorsanız, etkinlikleri kullanabilirsiniz:

def onclick(event):
    if event.inaxes is not None:
        print(closest_point_coords(ckdtree, event.xdata, event.ydata))

fig.canvas.mpl_connect('motion_notify_event', onclick)
plt.show()

Ders çalışma Bu irade kusursuz yalnızca x ise: y görsel ölçek 1. yeniden ölçeklendirme hariç, sorunun bu kısmı ile ilgili herhangi bir fikri eşittir pointsher zaman arsa yakınlaştırıldığında?
Pygmalion

En boy oranının değiştirilmesi, ckdtrees'ta mesafenin nasıl ölçüldüğüne ilişkin metriklerin değiştirilmesini gerektirir. Ckdtrees'te özel metriklerin kullanılması desteklenmiyor gibi görünüyor. Bu nedenle ckdtree.datascale = 1 ile gerçekçi puanlar tutmalısınız points. Yeniden ölçeklendirilebilir ve yalnızca dizinlerine erişmeniz gerekiyorsa bir sorun yoktur.
mathfux

Teşekkürler. Herhangi bir şans eseri, içindeki eksenler için reuw ölçek oranına kolayca erişmenin bir yolu olup olmadığını biliyor musunuz matplotlib? Web'de bulduğum son derece karmaşıktı.
Pygmalion

IMHO benim sorunum için en iyi çözüm bunu matplotlibkütüphaneye bir seçenek olarak dahil etmek olacaktır . Sonuçta, kütüphane bir yerde nokta pozisyonlarını geri çağırdı - sonuçta, onları arsaya çiziyor!
Pygmalion

Bunu deneyebilirsiniz set_aspect: matplotlib.org/3.1.3/api/_as_gen/…
mathfux

0

Aşağıdaki kod, tıklattığınızda fareye en yakın noktanın koordinatlarını yazdırır.

import matplotlib.pyplot as plt
import numpy as np
np.random.seed(19680801)
N = 50
x = np.random.rand(N)
y = np.random.rand(N)
fig,ax = plt.subplots()
plt.scatter(x, y)
points = list(zip(x,y))
def distance(a,b):
    return(sum([(k[0]-k[1])**2 for k in zip(a,b)])**0.5)
def onclick(event):
    dists = [distance([event.xdata, event.ydata],k) for k in points]
    print(points[dists.index(min(dists))])
fig.canvas.mpl_connect('button_press_event', onclick)
plt.show()

Muhtemelen kodu durumuma uyarlayabileceğim (her biri 10000 puanlı 16 parsel), ancak fikir noktanın koordinatlarının, örneğin navigasyon araç çubuğuna basılmasıydı. Mümkün mü?
Pygmalion

0

İşleyiciyi alt sınıflandırabilir NavigationToolbar2QTve geçersiz kılabilirsiniz mouse_move. xdataVe ydatanitelikleri arsa koordinatlarında geçerli fare konumunu içerirler. Etkinliği temel sınıf mouse_moveişleyicisine geçirmeden önce bunu en yakın veri noktasına yapıştırabilirsiniz.

Bir örnek olarak, arsadaki en yakın noktanın vurgulanmasıyla:

import sys

import numpy as np

from matplotlib.backends.qt_compat import QtWidgets
from matplotlib.backends.backend_qt5agg import FigureCanvas, NavigationToolbar2QT
from matplotlib.figure import Figure


class Snapper:
    """Snaps to data points"""

    def __init__(self, data, callback):
        self.data = data
        self.callback = callback

    def snap(self, x, y):
        pos = np.array([x, y])
        distances = np.linalg.norm(self.data - pos, axis=1)
        dataidx = np.argmin(distances)
        datapos = self.data[dataidx,:]
        self.callback(datapos[0], datapos[1])
        return datapos


class SnappingNavigationToolbar(NavigationToolbar2QT):
    """Navigation toolbar with data snapping"""

    def __init__(self, canvas, parent, coordinates=True):
        super().__init__(canvas, parent, coordinates)
        self.snapper = None

    def set_snapper(self, snapper):
        self.snapper = snapper

    def mouse_move(self, event):
        if self.snapper and event.xdata and event.ydata:
            event.xdata, event.ydata = self.snapper.snap(event.xdata, event.ydata)
        super().mouse_move(event)


class Highlighter:
    def __init__(self, ax):
        self.ax = ax
        self.marker = None
        self.markerpos = None

    def draw(self, x, y):
        """draws a marker at plot position (x,y)"""
        if (x, y) != self.markerpos:
            if self.marker:
                self.marker.remove()
                del self.marker
            self.marker = self.ax.scatter(x, y, color='yellow')
            self.markerpos = (x, y)
            self.ax.figure.canvas.draw()


class ApplicationWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self._main = QtWidgets.QWidget()
        self.setCentralWidget(self._main)
        layout = QtWidgets.QVBoxLayout(self._main)
        canvas = FigureCanvas(Figure(figsize=(5,3)))
        layout.addWidget(canvas)
        toolbar = SnappingNavigationToolbar(canvas, self)
        self.addToolBar(toolbar)

        data = np.random.randn(100, 2)
        ax = canvas.figure.subplots()
        ax.scatter(data[:,0], data[:,1])

        self.highlighter = Highlighter(ax)
        snapper = Snapper(data, self.highlighter.draw)
        toolbar.set_snapper(snapper)


if __name__ == "__main__":
    qapp = QtWidgets.QApplication(sys.argv)
    app = ApplicationWindow()
    app.show()
    qapp.exec_()
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.