从 Pandas 数据框填充 QTableView 的最快方法

Fastest way to populate QTableView from Pandas data frame

我是 PyQt 的新手,我正在努力填充 QTableView 控件。

我的代码如下:

def data_frame_to_ui(self, data_frame):
        """
        Displays a pandas data frame into the GUI
        """
        list_model = QtGui.QStandardItemModel()
        i = 0
        for val in data_frame.columns:
            # for the list model
            if i > 0:
                item = QtGui.QStandardItem(val)
                #item.setCheckable(True)
                item.setEditable(False)
                list_model.appendRow(item)
            i += 1
        self.ui.profilesListView.setModel(list_model)

        # for the table model
        table_model = QtGui.QStandardItemModel()

        # set table headers
        table_model.setColumnCount(data_frame.columns.size)
        table_model.setHorizontalHeaderLabels(data_frame.columns.tolist())
        self.ui.profileTableView.horizontalHeader().setStretchLastSection(True)

        # fill table model data
        for row_idx in range(10): #len(data_frame.values)
            row = list()
            for col_idx in range(data_frame.columns.size):
                val = QtGui.QStandardItem(str(data_frame.values[row_idx][col_idx]))
                row.append(val)
            table_model.appendRow(row)

        # set table model to table object
        self.ui.profileTableView.setModel(table_model)

实际上在代码中我成功填充了一个QListView,但是我设置给QTableView的值没有显示,你也可以看到我将行截断为10因为它需要永远显示数百行数据框。

那么,从 pandas 数据框填充 table 模型的最快方法是什么?

提前致谢。

就我个人而言,我会创建自己的模型 class 以便于处理它。

例如:

import sys
from PyQt4 import QtCore, QtGui
Qt = QtCore.Qt

class PandasModel(QtCore.QAbstractTableModel):
    def __init__(self, data, parent=None):
        QtCore.QAbstractTableModel.__init__(self, parent)
        self._data = data

    def rowCount(self, parent=None):
        return len(self._data.values)

    def columnCount(self, parent=None):
        return self._data.columns.size

    def data(self, index, role=Qt.DisplayRole):
        if index.isValid():
            if role == Qt.DisplayRole:
                return QtCore.QVariant(str(
                    self._data.iloc[index.row()][index.column()]))
        return QtCore.QVariant()


if __name__ == '__main__':
    application = QtGui.QApplication(sys.argv)
    view = QtGui.QTableView()
    model = PandasModel(your_pandas_data)
    view.setModel(model)

    view.show()
    sys.exit(application.exec_())

这个有效:

class PandasModel(QtCore.QAbstractTableModel):
    """
    Class to populate a table view with a pandas dataframe
    """
    def __init__(self, data, parent=None):
        QtCore.QAbstractTableModel.__init__(self, parent)
        self._data = data

    def rowCount(self, parent=None):
        return len(self._data.values)

    def columnCount(self, parent=None):
        return self._data.columns.size

    def data(self, index, role=QtCore.Qt.DisplayRole):
        if index.isValid():
            if role == QtCore.Qt.DisplayRole:
                return str(self._data.iloc[index.row()][index.column()])
        return None

    def headerData(self, col, orientation, role):
        if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
            return self._data.columns[col]
        return None

这样使用:

model = PandasModel(your_pandas_data_frame)
your_tableview.setModel(model)

我阅读 here 以避免从 PyQT 4.6 开始 QVariant()

pandas中实际上有一些代码支持与 Qt 的集成。

在撰写此答案时,最新的 pandas 版本是 0.18.1,您可以:

from pandas.sandbox.qtpandas import DataFrameModel, DataFrameWidget

该代码似乎与 PySide 耦合,但使其与 PyQt 一起工作应该相对简单。此外,该代码已被弃用,警告说该模块将在未来被删除。

幸运的是,他们将其提取到 GitHub 中的一个单独项目中,名为 pandas-qt:

https://github.com/datalyze-solutions/pandas-qt

在尝试推出我自己的模型和视图实现之前,我会尝试使用它。

我发现所有建议的答案对于 1000 多行的 DataFrame 来说都非常慢。什么对我来说非常快:

class PandasModel(QtCore.QAbstractTableModel):
    """
    Class to populate a table view with a pandas dataframe
    """
    def __init__(self, data, parent=None):
        QtCore.QAbstractTableModel.__init__(self, parent)
        self._data = data

    def rowCount(self, parent=None):
        return self._data.shape[0]

    def columnCount(self, parent=None):
        return self._data.shape[1]

    def data(self, index, role=QtCore.Qt.DisplayRole):
        if index.isValid():
            if role == QtCore.Qt.DisplayRole:
                return str(self._data.iloc[index.row(), index.column()])
        return None

    def headerData(self, col, orientation, role):
        if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
            return self._data.columns[col]
        return None

除了使用QtCore.QAbstractTableModel,还可以继承QtGui.QStandardItemModel。我发现这种方式更容易支持从 QTableView 发出的 handleChanged 事件。

from PyQt5 import QtCore, QtGui

class PandasModel(QtGui.QStandardItemModel):
    def __init__(self, data, parent=None):
        QtGui.QStandardItemModel.__init__(self, parent)
        self._data = data
        for row in data.values.tolist():
            data_row = [ QtGui.QStandardItem("{0:.6f}".format(x)) for x in row ]
            self.appendRow(data_row)
        return

    def rowCount(self, parent=None):
        return len(self._data.values)

    def columnCount(self, parent=None):
        return self._data.columns.size

    def headerData(self, x, orientation, role):
        if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
            return self._data.columns[x]
        if orientation == QtCore.Qt.Vertical and role == QtCore.Qt.DisplayRole:
            return self._data.index[x]
        return None

将数据帧写入 QtableWidget 的简单且快速的方法

# Takes a df and writes it to a qtable provided. df headers become qtable headers
@staticmethod
def write_df_to_qtable(df,table):
    headers = list(df)
    table.setRowCount(df.shape[0])
    table.setColumnCount(df.shape[1])
    table.setHorizontalHeaderLabels(headers)        

    # getting data from df is computationally costly so convert it to array first
    df_array = df.values
    for row in range(df.shape[0]):
        for col in range(df.shape[1]):
            table.setItem(row, col, QtGui.QTableWidgetItem(str(df_array[row,col])))

这里是copy-paste PyQT5 的完整工作示例 基于@Frederick Li 的回答稍作修改。

from PyQt5 import QtGui, QtWidgets
from PyQt5.QtCore import Qt
import sys
import pandas as pd

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, *args, obj=None, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.centralwidget = QtWidgets.QWidget(self)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed)
        self.centralwidget.setSizePolicy(sizePolicy)

        self.pdtable = QtWidgets.QTableView(self.centralwidget)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed)
        self.pdtable.setSizePolicy(sizePolicy)

        dataPD = [['tom', 10.0, 180.3], ['nick', 15.0, 175.7], ['juli', 14.0, 160.6]]
        df = pd.DataFrame(dataPD, columns=['Name', 'Age', 'Height'])
        print(df.dtypes)
        self.model = PandasTableModel(df)
        self.pdtable.setModel(self.model)

        self.setCentralWidget(self.centralwidget)


class PandasTableModel(QtGui.QStandardItemModel):
    def __init__(self, data, parent=None):
        QtGui.QStandardItemModel.__init__(self, parent)
        self._data = data
        for col in data.columns:
            data_col = [QtGui.QStandardItem("{}".format(x)) for x in data[col].values]
            self.appendColumn(data_col)
        return

    def rowCount(self, parent=None):
        return len(self._data.values)

    def columnCount(self, parent=None):
        return self._data.columns.size

    def headerData(self, x, orientation, role):
        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
            return self._data.columns[x]
        if orientation == Qt.Vertical and role == Qt.DisplayRole:
            return self._data.index[x]
        return None


if __name__ == "__main__":
    app  = QtWidgets.QApplication(sys.argv)
    app.setStyle("Fusion")
    main = MainWindow()
    main.show()
    main.resize(600, 400)
    sys.exit(app.exec_())