Matplotlib 嵌入";“图形类型”;PyQt中的Seaborn图(pyqtgraph)

Matplotlib 嵌入";“图形类型”;PyQt中的Seaborn图(pyqtgraph),matplotlib,pyqt,seaborn,pyqtgraph,matplotlib-widget,Matplotlib,Pyqt,Seaborn,Pyqtgraph,Matplotlib Widget,我正在使用PyQt()的包装器来构建GUI应用程序。 我希望在其中嵌入一个绘图使用。但是,我的问题是Seaborn包装器方法,如FaceGrid不接受外部图形句柄。此外,当我尝试用FacetGrid生成的图形更新MatplotlibWidget对象基础图形(.fig)时,它不起作用(在绘制之后没有绘图)。有任何解决方法的建议吗?Seaborn的FaceGrid提供了一个方便的功能,可以快速将pandas数据帧连接到matplotlib pyplot接口 但是,在GUI应用程序中,您很少希望使用p

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

Seaborn的
FaceGrid
提供了一个方便的功能,可以快速将pandas数据帧连接到matplotlib pyplot接口

但是,在GUI应用程序中,您很少希望使用pyplot,而是matplotlib API

这里面临的问题是,
Facetgrid
已经创建了自己的
matplotlib.figure.figure
对象(
Facetgrid.fig
)。此外,MatplotlibWidget 创建它自己的图形,因此最终得到两个图形

现在,让我们退一步: 原则上,可以在PyQt中使用seaborn
FaceGrid
绘图,方法是首先创建绘图,然后将生成的图形提供给图形画布(
matplotlib.backends.backend_qt4agg.FigureCanvasqtag
)。下面是一个如何做到这一点的示例

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中的绘图功能,这些功能在图形级别上工作,如
lmplot
factorplot
jointplot
FaceGrid
以及其他可能的功能。
其他函数,如
regplot
boxplot
kdeplot
在轴级别工作,并接受matplotlib
axes
对象作为参数(
sns.regplot(x,y,ax=ax1)


可能的解决方案是首先创建子地块轴,然后绘制这些轴,例如使用

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

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


关于pyqtgraph的最后一句话:我不会称pyqtgraph为PyQt的包装器,而是一个扩展。尽管pyqtgraph附带了自己的Qt(这使它具有可移植性和开箱即用性),但它也是一个可以在PyQt中使用的包。因此,您可以通过以下方法将
GraphicsLayoutWidget
添加到PyQt布局

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

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

有一个更好的方法来做到这一点,就是设置一个“小部件布局设置”文件,这样您就可以在实际的主应用程序代码中键入更少的代码

请看这里:


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

这里是公认答案的精确副本,但使用的是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_())


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

这个问题真的与PyQTraph的MatplotlibWidget有关吗?还是会以相同的方式出现在通常的matplotlib打印中?您能否指定“当我尝试用FacetGrid生成的图形更新MatplotlibWidget对象基础图形(.fig)时”的含义?如何用地物更新地物?你能展示尝试这样做的代码吗?这是一个彻底而好的答案,但你的第一句话不准确。@mwaskom如果我错了,请随时纠正我。如果不是一个方便的函数,你还会称之为什么呢?对于初学者来说,它是一个类,而不是函数。应该补充的是,在
PyQt5
中,它是
PyQt5.qtwidts.QSizePolicy。扩展
如果文章本身包含更多信息,这个答案会更有用。链接可能会随着时间的推移而中断,因此最好将信息放在此处。
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_())