嵌入“地物类型".PyQt 中的 Seaborn 图 (pyqtgraph) [英] Embedding "Figure Type" Seaborn Plot in PyQt (pyqtgraph)

查看:214
本文介绍了嵌入“地物类型".PyQt 中的 Seaborn 图 (pyqtgraph)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 PyQt 的包装器(

<小时>关于 pyqtgraph 的最后一句话:我不会将 pyqtgraph 称为 PyQt 的包装器,而更像是一种扩展.尽管pyqtgraph附带了自己的Qt(这使它可移植并可以立即使用),但它也是一个可以从PyQt内部使用的软件包.因此,您可以通过

GraphicsLayoutWidget 添加到PyQt布局中

  self.pgcanvas = pg.GraphicsLayoutWidget()self.layout().addWidget(self.pgcanvas)

对于 MatplotlibWidget(mw = pg.MatplotlibWidget()).虽然您可以使用这种小部件,但它只是一个方便的包装器,因为它所做的只是找到正确的 matplotlib 导入并创建一个 Figure 和一个 FigureCanvas 实例.除非您正在使用其他 pyqtgraph 功能,否则仅仅为了节省 5 行代码而导入完整的 pyqtgraph 包对我来说似乎有点过分.

I am using a wrapper of PyQt (pyqtgraph) to build a GUI application. I wish to embed a Seaborn plot within it using the MatplotlibWidget. However, my problem is that the Seaborn wrapper method such as FacetGrid do not accept an external figure handle. Moreover, when I try to update the MatplotlibWidget object underlying figure (.fig) with the figure produced by the FacetGrid it doesn't work (no plot after draw). Any suggestion for a workaround?

解决方案

Seaborn's Facetgrid provides a convenience function to quickly connect pandas dataframes to the matplotlib pyplot interface.

However in GUI applications you rarely want to use pyplot, but rather the matplotlib API.

The problem you are facing here is that Facetgrid already creates its own matplotlib.figure.Figure object (Facetgrid.fig). Also, the MatplotlibWidget creates its own figure, so you end up with two figures.

Now, let's step back a bit: In principle it is possible to use a seaborn Facetgrid plot in PyQt, by first creating the plot and then providing the resulting figure to the figure canvas (matplotlib.backends.backend_qt4agg.FigureCanvasQTAgg). The following is an example of how to do that.

from PyQt4 import QtGui, QtCore
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
import sys
import seaborn as sns
import matplotlib.pyplot as plt

tips = sns.load_dataset("tips")


def seabornplot():
    g = sns.FacetGrid(tips, col="sex", hue="time", palette="Set1",
                                hue_order=["Dinner", "Lunch"])
    g.map(plt.scatter, "total_bill", "tip", edgecolor="w")
    return g.fig


class MainWindow(QtGui.QMainWindow):
    send_fig = QtCore.pyqtSignal(str)

    def __init__(self):
        super(MainWindow, self).__init__()

        self.main_widget = QtGui.QWidget(self)

        self.fig = seabornplot()
        self.canvas = FigureCanvas(self.fig)

        self.canvas.setSizePolicy(QtGui.QSizePolicy.Expanding,
                      QtGui.QSizePolicy.Expanding)
        self.canvas.updateGeometry()
        self.button = QtGui.QPushButton("Button")
        self.label = QtGui.QLabel("A plot:")

        self.layout = QtGui.QGridLayout(self.main_widget)
        self.layout.addWidget(self.button)
        self.layout.addWidget(self.label)
        self.layout.addWidget(self.canvas)

        self.setCentralWidget(self.main_widget)
        self.show()


if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    win = MainWindow()
    sys.exit(app.exec_())

While this works fine, it is a bit questionable, if it's useful at all. Creating a plot inside a GUI in most cases has the purpose of beeing updated depending on user interactions. In the example case from above, this is pretty inefficient, as it would require to create a new figure instance, create a new canvas with this figure and replace the old canvas instance with the new one, adding it to the layout.

Note that this problematics is specific to those plotting functions in seaborn, which work on a figure level, like lmplot, factorplot, jointplot, FacetGrid and possibly others.
Other functions like regplot, boxplot, kdeplot work on an axes level and accept a matplotlib axes object as argument (sns.regplot(x, y, ax=ax1)).


A possibile solution is to first create the subplot axes and later plot to those axes, for example using the pandas plotting functionality.

df.plot(kind="scatter", x=..., y=..., ax=...)

where ax should be set to the previously created axes.
This allows to update the plot within the GUI. See the example below. Of course normal matplotlib plotting (ax.plot(x,y)) or the use of the seaborn axes level function discussed above work equally well.

from PyQt4 import QtGui, QtCore
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import sys
import seaborn as sns

tips = sns.load_dataset("tips")

class MainWindow(QtGui.QMainWindow):
    send_fig = QtCore.pyqtSignal(str)

    def __init__(self):
        super(MainWindow, self).__init__()

        self.main_widget = QtGui.QWidget(self)

        self.fig = Figure()
        self.ax1 = self.fig.add_subplot(121)
        self.ax2 = self.fig.add_subplot(122, sharex=self.ax1, sharey=self.ax1)
        self.axes=[self.ax1, self.ax2]
        self.canvas = FigureCanvas(self.fig)

        self.canvas.setSizePolicy(QtGui.QSizePolicy.Expanding, 
                                  QtGui.QSizePolicy.Expanding)
        self.canvas.updateGeometry()

        self.dropdown1 = QtGui.QComboBox()
        self.dropdown1.addItems(["sex", "time", "smoker"])
        self.dropdown2 = QtGui.QComboBox()
        self.dropdown2.addItems(["sex", "time", "smoker", "day"])
        self.dropdown2.setCurrentIndex(2)

        self.dropdown1.currentIndexChanged.connect(self.update)
        self.dropdown2.currentIndexChanged.connect(self.update)
        self.label = QtGui.QLabel("A plot:")

        self.layout = QtGui.QGridLayout(self.main_widget)
        self.layout.addWidget(QtGui.QLabel("Select category for subplots"))
        self.layout.addWidget(self.dropdown1)
        self.layout.addWidget(QtGui.QLabel("Select category for markers"))
        self.layout.addWidget(self.dropdown2)

        self.layout.addWidget(self.canvas)

        self.setCentralWidget(self.main_widget)
        self.show()
        self.update()

    def update(self):

        colors=["b", "r", "g", "y", "k", "c"]
        self.ax1.clear()
        self.ax2.clear()
        cat1 = self.dropdown1.currentText()
        cat2 = self.dropdown2.currentText()
        print cat1, cat2

        for i, value in enumerate(tips[cat1].unique().get_values()):
            print "value ", value
            df = tips.loc[tips[cat1] == value]
            self.axes[i].set_title(cat1 + ": " + value)
            for j, value2 in enumerate(df[cat2].unique().get_values()):
                print "value2 ", value2
                df.loc[ tips[cat2] == value2 ].plot(kind="scatter", x="total_bill", y="tip", 
                                                ax=self.axes[i], c=colors[j], label=value2)
        self.axes[i].legend()   
        self.fig.canvas.draw_idle()


if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    win = MainWindow()
    sys.exit(app.exec_())


A final word about pyqtgraph: I wouldn't call pyqtgraph a wrapper for PyQt but more an extention. Although pyqtgraph ships with its own Qt (which makes it portable and work out of the box), it is also a package one can use from within PyQt. You can therefore add a GraphicsLayoutWidget to a PyQt layout simply by

self.pgcanvas = pg.GraphicsLayoutWidget()
self.layout().addWidget(self.pgcanvas) 

The same holds for a MatplotlibWidget (mw = pg.MatplotlibWidget()). While you can use this kind of widget, it's merely a convenience wrapper, since all it's doing is finding the correct matplotlib imports and creating a Figure and a FigureCanvas instance. Unless you are using other pyqtgraph functionality, importing the complete pyqtgraph package just to save 5 lines of code seems a bit overkill to me.

这篇关于嵌入“地物类型".PyQt 中的 Seaborn 图 (pyqtgraph)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆