python pandas pyqt pyqt5 qtableview

python - Cómo mostrar un marco de datos Pandas con PyQt5



qtableview (1)

En el caso de QTableView los datos deben proporcionarse a través de un modelo, ya que implementa el MVC ( Modelo-Vista-Controlador ), en el caso de los pandas no hay un modelo predeterminado, pero podemos crear un personalizado como se muestra en la siguiente parte:

class PandasModel(QtCore.QAbstractTableModel): def __init__(self, df = pd.DataFrame(), parent=None): QtCore.QAbstractTableModel.__init__(self, parent=parent) self._df = df def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole): if role != QtCore.Qt.DisplayRole: return QtCore.QVariant() if orientation == QtCore.Qt.Horizontal: try: return self._df.columns.tolist()[section] except (IndexError, ): return QtCore.QVariant() elif orientation == QtCore.Qt.Vertical: try: # return self.df.index.tolist() return self._df.index.tolist()[section] except (IndexError, ): return QtCore.QVariant() def data(self, index, role=QtCore.Qt.DisplayRole): if role != QtCore.Qt.DisplayRole: return QtCore.QVariant() if not index.isValid(): return QtCore.QVariant() return QtCore.QVariant(str(self._df.ix[index.row(), index.column()])) def setData(self, index, value, role): row = self._df.index[index.row()] col = self._df.columns[index.column()] if hasattr(value, ''toPyObject''): # PyQt4 gets a QVariant value = value.toPyObject() else: # PySide gets an unicode dtype = self._df[col].dtype if dtype != object: value = None if value == '''' else dtype.type(value) self._df.set_value(row, col, value) return True def rowCount(self, parent=QtCore.QModelIndex()): return len(self._df.index) def columnCount(self, parent=QtCore.QModelIndex()): return len(self._df.columns) def sort(self, column, order): colname = self._df.columns.tolist()[column] self.layoutAboutToBeChanged.emit() self._df.sort_values(colname, ascending= order == QtCore.Qt.AscendingOrder, inplace=True) self._df.reset_index(inplace=True, drop=True) self.layoutChanged.emit()

Y luego úsalo:

def btn_clk(self): path = self.lineEdit.text() df = pd.read_csv(path) model = PandasModel(df) self.tableView.setModel(model)

El código completo está here

Actualización 03-07-2019:

Algunos métodos de Pandas están en desuso, por lo que he implementado una nueva versión (que también se puede usar en QML, como muestra esta answer ):

class DataFrameModel(QtCore.QAbstractTableModel): DtypeRole = QtCore.Qt.UserRole + 1000 ValueRole = QtCore.Qt.UserRole + 1001 def __init__(self, df=pd.DataFrame(), parent=None): super(DataFrameModel, self).__init__(parent) self._dataframe = df def setDataFrame(self, dataframe): self.beginResetModel() self._dataframe = dataframe.copy() self.endResetModel() def dataFrame(self): return self._dataframe dataFrame = QtCore.pyqtProperty(pd.DataFrame, fget=dataFrame, fset=setDataFrame) @QtCore.pyqtSlot(int, QtCore.Qt.Orientation, result=str) def headerData(self, section: int, orientation: QtCore.Qt.Orientation, role: int = QtCore.Qt.DisplayRole): if role == QtCore.Qt.DisplayRole: if orientation == QtCore.Qt.Horizontal: return self._dataframe.columns[section] else: return str(self._dataframe.index[section]) return QtCore.QVariant() def rowCount(self, parent=QtCore.QModelIndex()): if parent.isValid(): return 0 return len(self._dataframe.index) def columnCount(self, parent=QtCore.QModelIndex()): if parent.isValid(): return 0 return self._dataframe.columns.size def data(self, index, role=QtCore.Qt.DisplayRole): if not index.isValid() or not (0 <= index.row() < self.rowCount() / and 0 <= index.column() < self.columnCount()): return QtCore.QVariant() row = self._dataframe.index[index.row()] col = self._dataframe.columns[index.column()] dt = self._dataframe[col].dtype val = self._dataframe.iloc[row][col] if role == QtCore.Qt.DisplayRole: return str(val) elif role == DataFrameModel.ValueRole: return val if role == DataFrameModel.DtypeRole: return dt return QtCore.QVariant() def roleNames(self): roles = { QtCore.Qt.DisplayRole: b''display'', DataFrameModel.DtypeRole: b''dtype'', DataFrameModel.ValueRole: b''value'' } return roles

Tengo un problema con la línea debajo de self.tableView.set??????????(df) que se supone que muestra el marco de datos en PyQt5. Pongo ??? allí donde me falta el código que necesito.

def btn_clk(self): path = self.lineEdit.text() df = pd.read_csv(path) self.tableView.set??????????(df)

El resto del código funciona, porque si uso print(df) en el código anterior, el marco de datos se imprime en la consola IPython. Entonces, Pandas lee el CSV y lo imprime.

Pero, intenté muchas cosas para que se muestre en PyQt5 y nada funciona. No estoy muy familiarizado con PyQt, recién comencé a jugar con él y estoy atrapado aquí.

Aquí está mi código:

from PyQt5 import QtCore, QtGui, QtWidgets import pandas as pd class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") MainWindow.resize(662, 512) self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget) self.horizontalLayout.setObjectName("horizontalLayout") self.verticalLayout = QtWidgets.QVBoxLayout() self.verticalLayout.setObjectName("verticalLayout") self.lineEdit = QtWidgets.QLineEdit(self.centralwidget) self.lineEdit.setObjectName("lineEdit") self.verticalLayout.addWidget(self.lineEdit) self.tableView = QtWidgets.QTableView(self.centralwidget) self.tableView.setObjectName("tableView") self.verticalLayout.addWidget(self.tableView) self.pushButton = QtWidgets.QPushButton(self.centralwidget) self.pushButton.setObjectName("pushButton") self.verticalLayout.addWidget(self.pushButton) self.horizontalLayout.addLayout(self.verticalLayout) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) self.menubar.setGeometry(QtCore.QRect(0, 0, 662, 21)) self.menubar.setObjectName("menubar") MainWindow.setMenuBar(self.menubar) self.statusbar = QtWidgets.QStatusBar(MainWindow) self.statusbar.setObjectName("statusbar") MainWindow.setStatusBar(self.statusbar) self.retranslateUi(MainWindow) QtCore.QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) self.pushButton.setText(_translate("MainWindow", "PushButton")) self.pushButton.clicked.connect(self.btn_clk) MainWindow.show() def btn_clk(self): path = self.lineEdit.text() df = pd.read_csv(path) self.tableView.set????????????(df) if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) MainWindow = QtWidgets.QMainWindow() ui = Ui_MainWindow() ui.setupUi(MainWindow) MainWindow.show() sys.exit(app.exec_())