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

Posted

技术标签:

【中文标题】从 Pandas 数据框填充 QTableView 的最快方法【英文标题】:Fastest way to populate QTableView from Pandas data frame 【发布时间】:2015-07-17 12:33:27 【问题描述】:

我是 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 数据框填充表模型的最快方法是什么?

提前致谢。

【问题讨论】:

您是否测试过 Wolph 提供的解决方案,看看它是否提供了更好的性能? 还没有,我也不太明白,所以需要一些时间。 我做了一些测试。对于 25 列 10000 行的表,自定义模型大约快 40 倍(并且性能差异随着行/列数的增加呈几何级数增长)。这是对数据使用简单的列表列表,因此创建QStandardItem 的所有这些实例似乎是主要瓶颈。 不确定这是否有帮助,但熊猫曾经有一个 pyqt 模型。看起来它现在已经被拆分到另一个项目中,所以你可能想查看pandas-qt。不知道性能如何。 @ekhumoro,您介意发布您的代码吗?我得到的课程是:return QtCore.QVariant() TypeError: PyQt4.QtCore.QVariant represents a mapped type and cannot be instantiated 【参考方案1】:

就我个人而言,我只是创建自己的模型类以使其更容易处理。

例如:

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

【讨论】:

嗨,我收到以下错误:return QtCore.QVariant() TypeError: PyQt4.QtCore.QVariant represents a mapped type and cannot be instantiated 对于新的 pandas 版本,您必须将 self._data.iloc[index.row()][index.column()])) 替换为 self._data.iat[row, col]【参考方案2】:

这行得通:

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)

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

【讨论】:

旧响应,但仍然很好。如果希望数据框的索引出现在行中,可以修改方法 headerData 如下:def headerData(self, rowcol, orientation, role): if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole: return self._data.columns[rowcol] if orientation == QtCore.Qt.Vertical and role == QtCore.Qt.DisplayRole: return self._data.index[rowcol] return None 这行得通,但我怎样才能使模型可编辑,然后将其移回数据框?目前它甚至无法编辑。【参考方案3】:

我发现对于 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

【讨论】:

【参考方案4】:

除了使用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

【讨论】:

以上在 PyQt5 上完美运行。感谢@Frederick Li 的帖子-尽管我确实修改了data_row 行以仅将值作为字符串输入,但除此之外,它将我的加载时间从可能一分钟或更长时间缩短到几秒钟.【参考方案5】:

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

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

【讨论】:

您好,我只想补充一下,pandas-qt 不支持 python3,而且看起来可能不支持。同时你可以使用qtpandaspip install qtpandas【参考方案6】:

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

【讨论】:

QT 网站有一个 Pandas 简单示例:doc.qt.io/qtforpython/examples/example_external__pandas.html【参考方案7】:

将数据框写入 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])))

【讨论】:

以上是关于从 Pandas 数据框填充 QTableView 的最快方法的主要内容,如果未能解决你的问题,请参考以下文章

可编辑 QTableView 中的 Pandas df:删除复选框

python postgresql从pandas数据框创建数据库并填充表

从具有字典列的csv构造pandas数据框

滚动大型数据集时,PyQt QTableView 速度非常慢

用 Pandas 数据框中的行填充嵌套字典

Pandas-根据开关用数据框填充字典