Я использую оболочку PyQt (pyqtgraph) для создания приложения с графическим интерфейсом. Я хочу встроить в него график Seaborn, используя MatplotlibWidget. Однако моя проблема заключается в том, что метод оболочки Seaborn, такой как FacetGrid
, не принимает внешний дескриптор фигуры. Более того, когда я пытаюсь обновить объект MatplotlibWidget, лежащий в основе рисунка (.fig
), с помощью рисунка, созданного FacetGrid
, он не работает (нет графика после draw
). Любое предложение для обходного пути?
Встраивание графика Seaborn типа Figure в PyQt (pyqtgraph)
Ответы (3)
Facetgrid
от Seaborn предоставляет удобную функцию для быстрого подключения фреймов данных pandas к интерфейсу pyplot matplotlib.
Однако в приложениях с графическим интерфейсом вы редко хотите использовать 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_())
Хотя это работает нормально, это немного сомнительно, если это вообще полезно. Создание графика внутри графического интерфейса в большинстве случаев имеет целью обновление в зависимости от взаимодействия с пользователем. В приведенном выше примере это довольно неэффективно, так как потребуется создать новый экземпляр фигуры, создать новый холст с этой фигурой и заменить старый экземпляр холста новым, добавив его в макет.
Обратите внимание, что эта проблематика специфична для тех функций построения графиков в Seaborn, которые работают на уровне фигур, таких как lmplot
, factorplot
, jointplot
, FacetGrid
и, возможно, других.
Другие функции, такие как regplot
, boxplot
, kdeplot
, работают на уровне осей и принимают объект matplotlib axes
в качестве аргумента (sns.regplot(x, y, ax=ax1)
).
Возможное решение состоит в том, чтобы сначала создать оси подграфика, а затем построить график по этим осям, например, используя функции построения графиков pandas.
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_())
Последнее слово о 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 строк кода кажется мне немного излишним.
FacetGrid
, если не функцию удобства?
- person ImportanceOfBeingErnest; 16.01.2017
PyQt5
это PyQt5.QtWidgets.QSizePolicy.Expanding
- person Ilonpilaaja; 18.07.2019
Вот точная копия принятого ответа, но с использованием 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 таким же образом, важно отметить, что пользовательский интерфейс может работать медленнее по мере роста размера набора данных. Но я нашел такие подходы удобными для анализа и построения файлов журнала с использованием функций регулярных выражений.
Есть лучший способ сделать это — настроить файл «Настройки макета виджета», чтобы у вас было меньше кода для ввода фактического кода основного приложения.
См. здесь: https://yapayzekalabs.blogspot.com/2018/11/pyqt5-gui-qt-designer-matplotlib.html
Полезно только изображение и процесс, которому вы должны следовать.