matplotlib 플롯에서 가장 가까운 데이터 포인트의 좌표 얻기


9

matplotlib와 함께 사용 하고 NavigationToolbar2QT있습니다. 툴바에 커서 위치가 표시됩니다. 그러나 커서가 가장 가까운 데이터 포인트에 스냅 되거나 ( 충분히 닫을 때) 가장 가까운 데이터 포인트의 좌표를 표시하고 싶습니다 . 어떻게 든 배열 될 수 있습니까?


아래 링크를 확인하여 문제가 해결되는지 확인하십시오. 이 링크는 찾고자하는 것과 유사한 기능 스냅 토커를 제공합니다. matplotlib.org/3.1.1/gallery/misc/cursor_demo_sgskip.html
Anupam Chaplot

@AnupamChaplot "Matlotlotlib를 사용하여 커서를 그리며 마우스를 움직일 때마다 그림을 다시 그려야하므로 속도가 느릴 수 있습니다." 그래프에 10000 점이있는 약 16 개의 플롯이 있으므로 다시 그리면 다소 느려집니다.
피그말리온

시각적으로 무언가를 다시 그리지 않으려면 (왜 그런가?) matplotlib.org/3.1.1/gallery/images_contours_and_fields/에
ImportanceOfBeingErnest

@ImportanceOfBeingErnest 나는 당신의 제안을 이해하지 못합니다. 그러나 이것을 상상해보십시오. 16 개의 선 그림이 있으며 각 그림에는 뚜렷한 피크가 있습니다. 데이터를 엿 보지 않고 한 플롯의 피크의 정확한 좌표를 알고 싶습니다. 커서를 정확히 포인트에 놓을 수 없으므로 매우 정확하지 않습니다. 따라서 Origin과 같은 프로그램에는 현재 커서 위치에 가장 가까운 점의 정확한 좌표를 표시하는 옵션이 있습니다.
피그말리온

1
예, 이것이 cursor_demo_sgskip의 기능 입니다. 그러나 커서를 그리지 않으려면 image_zcoord
ImportanceOfBeingErnest

답변:


6

큰 포인트 세트로 작업하는 경우 다음을 사용하는 것이 좋습니다 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's여기에 약간의 대답을 리팩토링했습니다 . 일단 ckdtree생성 되면 , 가장 가까운 지점과 그에 대한 다양한 정보를 약간의 노력으로 즉시 확인할 수 있습니다.

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

커서 위치를 대화식으로 표시합니다. 가장 가까운 점의 좌표를 탐색 도구 모음에 표시하려면 다음을 수행하십시오.

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()

이벤트 사용 다른 종류의 상호 작용을 원한다면 이벤트를 사용할 수 있습니다.

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()

이것은 물론 x : y 비주얼 스케일이 1 인 경우에만 완벽하게 작동합니다. points플롯이 확대 / 축소 될 때마다 크기를 재조정하는 것을 제외하고 문제의이 부분에 대한 아이디어 가 있습니까?
피그말리온

종횡비를 변경하려면 ckdtree에서 거리를 측정하는 방법에 대한 메트릭을 변경해야합니다. ckdtrees에서 맞춤 측정 항목을 사용하는 것은 지원되지 않는 것 같습니다. 따라서 ckdtree.datascale = 1의 현실적인 점으로 유지해야합니다. 크기를 조정할 points수 있으며 해당 인덱스에만 액세스해야하는 경우 문제가 없습니다.
mathfux

감사. 의 축에 대해 reuw 스케일 비율에 쉽게 액세스 할 수있는 방법이 있다면 우연히 아십니까 matplotlib? 웹에서 찾은 것은 매우 복잡했습니다.
피그말리온

내 문제에 대한 최상의 해결책은 matplotlib라이브러리에 옵션으로 포함시키는 것 입니다. 결국, 라이브러리는 어딘가에 포인트 위치를 다시 불렀습니다.
피그말리온

당신이 시도하는 것 같아서 set_aspect: matplotlib.org/3.1.3/api/_as_gen/...이
mathfux

0

다음 코드는 클릭 할 때 마우스에 가장 가까운 점의 좌표를 인쇄합니다.

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()

아마도 내 상황에 맞게 코드를 조정할 수있을 것입니다 (각 10000 점이있는 16 플롯). 점의 좌표가 탐색 도구 모음에 인쇄되어 있다는 아이디어가있었습니다. 가능합니까?
피그말리온

0

핸들러를 서브 클래 싱 NavigationToolbar2QT하고 재정의 할 수 있습니다 mouse_move. xdataydata속성은 플롯 좌표에서 현재 마우스 위치가 포함되어 있습니다. 이벤트를 기본 클래스 mouse_move핸들러 로 전달하기 전에 가장 가까운 데이터 포인트로 스냅 할 수 있습니다 .

플롯에서 가장 가까운 점을 보너스로 강조 표시 한 전체 예 :

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_()
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.