2017-01-16 2 views
2

Я использую оболочку PyQt (pyqtgraph) для создания графического приложения. Я хочу вставить в него участок Seaborn, используя MatplotlibWidget. Однако моя проблема заключается в том, что метод обертки Seaborn, такой как FacetGrid, не принимает внешнюю фигуру. Более того, когда я пытаюсь обновить объект MatplotlibWidget, лежащий в основе рисунка (.fig), с рисунком, созданным FacetGrid, он не работает (без графика после draw). Любое предложение об обходном пути?Вложение "Figure Type" Seaborn Plot in PyQt (pyqtgraph)

+0

Является ли эта проблема действительно связана с MatplotlibWidget от pyqtgraph или она будет происходить таким же образом в рамках обычного графика matplotlib? Можете ли вы указать, что вы подразумеваете под «когда я пытаюсь обновить объект MatplotlibWidget, лежащий в основе рисунка (.fig), с рисунком, созданным FacetGrid»? Как вы обновляете фигуру с фигурой? Можете ли вы показать код, который пытается это сделать? – ImportanceOfBeingErnest

ответ

3

Seaborn's Facetgrid предоставляет удобную функцию для быстрого соединения данных с данными pandas с интерфейсом pyplot matplotlib.

Однако в приложениях GUI вы редко хотите использовать pyplot, а скорее API matplotlib.

Проблема, с которой вы сталкиваетесь, заключается в том, что Facetgrid уже создает свой собственный объект matplotlib.figure.Figure (Facetgrid.fig). Кроме того, MatplotlibWidget создает свою собственную фигуру, поэтому вы получаете две цифры.

Теперь давайте вернемся немного назад: В принципе можно использовать Сиборн Facetgrid участок в PyQt, сначала создавая сюжет, а затем обеспечивая полученную фигуру на фигуру холста (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_()) 

Хотя это работает нормально, это немного сомнительно, если оно вообще полезно. Создание сюжета внутри графического интерфейса в большинстве случаев имеет целью обновить его в зависимости от взаимодействия пользователя. В примере выше, это довольно неэффективно, поскольку для создания нового экземпляра фигуры потребуется создать новый холст с этой фигурой и заменить старый экземпляр canvas новым, добавив его в макет.

Обратите внимание, что эта проблематика является специфической для этих функций рисования в Сиборне, которые работают на уровне фигуры, как lmplot, factorplot, jointplot, FacetGrid и, возможно, другие.
Другие функции, такие как regplot, boxplot, kdeplot работают на уровне осей и принимают matplotlib axes объект в качестве аргумента (sns.regplot(x, y, ax=ax1)).


possibile раствор должен сначала создать подзаговор осей, а затем и участок для этих осей, например, с помощью pandas plotting functionality.

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

, где ax должны быть установлены в ранее созданных оси.
Это позволяет обновить график в графическом интерфейсе. См. Пример ниже. Разумеется, нормальный график matplotlib (ax.plot(x,y)) или использование описанной выше функции уровня осей морского судна работают одинаково хорошо.

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) 

То же самое справедливо для MatplotlibWidget (mw = pg.MatplotlibWidget()). Хотя вы можете использовать этот вид виджетов, это всего лишь удобная оболочка, поскольку все, что он делает, это поиск правильного импорта matplotlib и создание Figure и FigureCanvas экземпляра. Если вы не используете другие функции pyqtgraph, импортирование полного пакета pyqtgraph просто для сохранения 5 строк кода кажется мне немного лишним.

+0

Это тщательный и хороший ответ, но ваше первое предложение неточно. – mwaskom

+0

@mwaskom Пожалуйста, не стесняйтесь исправлять меня, если я ошибаюсь. Что еще вы бы назвали «FacetGrid», если бы не функция удобства? – ImportanceOfBeingErnest

+0

Ну для начала, это класс, а не функция. – mwaskom