在PyQt中嵌入“图类型”Seaborn图(pyqtgraph)

时间:2017-01-16 08:05:53

标签: matplotlib pyqt seaborn pyqtgraph matplotlib-widget

我正在使用PyQt(pyqtgraph)的包装器来构建GUI应用程序。 我希望使用Seaborn在其中嵌入MatplotlibWidget图。但是,我的问题是Seaborn包装器方法(如FacetGrid)不接受外部数字句柄。此外,当我尝试使用.fig生成的数字更新MatplotlibWidget对象底层图(FacetGrid)时,它不起作用(draw之后没有绘图)。有关解决方法的任何建议吗?

4 个答案:

答案 0 :(得分:4)

Seaborn的Facetgrid提供了一个便利功能,可以将pandas数据帧快速连接到matplotlib pyplot界面。

然而,在GUI应用程序中,您很少使用pyplot,而是使用matplotlib API。

您遇到的问题是Facetgrid已经创建了自己的matplotlib.figure.Figure对象(Facetgrid.fig)。另外,MatplotlibWidget 创造了自己的形象,所以最终得到两个数字。

现在,让我们退一步: 原则上可以在PyQt中使用seaborn Facetgrid绘图,首先创建绘图,然后将结果图形提供给图形画布(matplotlib.backends.backend_qt4agg.FigureCanvasQTAgg)。以下是如何执行此操作的示例。

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

虽然这种方法很好,但如果它有用的话,它有点可疑。在大多数情况下,在GUI内创建绘图的目的是根据用户交互更新。在上面的示例中,这是非常低效的,因为它需要创建一个新的图形实例,使用此图形创建一个新的画布并将旧的画布实例替换为新的画布实例,并将其添加到布局中。

请注意,此问题特定于seaborn中的绘制函数,这些函数在图形级别上工作,如lmplotfactorplotjointplotFacetGrid以及其他可能的其他函数。
其他函数如regplotboxplotkdeplot适用于轴级别,并接受matplotlib axes对象作为参数(sns.regplot(x, y, ax=ax1))。

可能的解决方案是首先创建子绘图轴,然后绘制到这些轴,例如使用pandas plotting functionality

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

其中ax应设置为先前创建的轴 这允许更新GUI中的绘图。请参阅下面的示例。当然,正常的matplotlib绘图(ax.plot(x,y))或使用上面讨论的seaborn轴水平函数同样有效。

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

enter image description here

<小时/> 关于 pyqtgraph 的最后一句话:我不会将pyqtgraph称为PyQt的包装器,而是更多的扩展。虽然pyqtgraph附带了自己的Qt(这使得它可以移植并开箱即用),但它也是一个可以在PyQt中使用的包。因此,您只需通过

GraphicsLayoutWidget添加到PyQt布局
self.pgcanvas = pg.GraphicsLayoutWidget()
self.layout().addWidget(self.pgcanvas) 

同样适用于MatplotlibWidgetmw = pg.MatplotlibWidget())。虽然您可以使用这种小部件,但它只是一个方便的包装器,因为它所做的只是找到正确的matplotlib导入并创建FigureFigureCanvas实例。除非你使用其他pyqtgraph功能,导入完整的pyqtgraph包只是为了保存5行代码对我来说似乎有点过头了。

答案 1 :(得分:0)

执行此操作的更好方法是设置“窗口小部件布局设置”文件,这样您可以用更少的代码键入实际的主应用程序代码。

请参阅此处: https://yapayzekalabs.blogspot.com/2018/11/pyqt5-gui-qt-designer-matplotlib.html

这对您应该遵循的图像和过程很有帮助。

答案 2 :(得分:0)

以下是接受的答案的准确副本,但使用的是 PYQT5

from PyQt5 import QtCore, QtGui, QtWidgets
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import sys
import seaborn as sns

tips = sns.load_dataset("tips")

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

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

        self.main_widget = QtWidgets.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(QtWidgets.QSizePolicy.Expanding, 
                                  QtWidgets.QSizePolicy.Expanding)
        self.canvas.updateGeometry()

        self.dropdown1 = QtWidgets.QComboBox()
        self.dropdown1.addItems(["sex", "time", "smoker"])
        self.dropdown2 = QtWidgets.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 = QtWidgets.QLabel("A plot:")

        self.layout = QtWidgets.QGridLayout(self.main_widget)
        self.layout.addWidget(QtWidgets.QLabel("Select category for subplots"))
        self.layout.addWidget(self.dropdown1)
        self.layout.addWidget(QtWidgets.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__':
    import sys
    app = QtWidgets.QApplication(sys.argv) 
    ex = MainWindow()
    sys.exit(app.exec_())

enter image description here

虽然任何matplotlib图都可以以相同的方式嵌入pyqt5中,但要注意的是,随着数据集大小的增加,UI可能会变慢。但是我发现使用正则表达式功能可以方便地解析和绘制日志文件。

答案 3 :(得分:0)

我无法提交 Zstack 的答案,这就是我#m 写一个新答案的原因。

对我来说,他的代码只在我删除方法“update”的for循环中的两部分“.get_values()”后才有效,如下所示:

定义更新(自我):

    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()):
        print ("value ", value)
        df = tips.loc[tips[cat1] == value]
        self.axes[i].set_title(cat1 + ": " + value)
        for j, value2 in enumerate(df[cat2].unique()):
            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()

感谢您的回答,这是一个很好的例子!