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

23

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

提前致谢。


我进行了一些测试。对于一个有25列和10000行的表格,自定义模型大约快了40倍(随着行/列数的增加,性能差异呈几何级数增长)。这是使用简单的列表嵌套列表来处理数据的,因此似乎创建所有那些QStandardItem实例是主要瓶颈。 - ekhumoro
不确定这是否有帮助,但是pandas曾经有一个pyqt模型。现在它似乎已经拆分成另一个项目了,所以您可能需要查看pandas-qt。不过我不知道性能如何。 - three_pineapples
1
@ekhumoro,你介意发一下你的代码吗?我使用当前的类会得到以下错误:return QtCore.QVariant() TypeError: PyQt4.QtCore.QVariant represents a mapped type and cannot be instantiated - Santi Peñate-Vera
@SantiPeñate-Vera。你是否正在使用Python 3?如果是的话,你可以摆脱所有的“QVariant”设备,返回普通的Python类型即可。具体导致错误的代码行可以完全忽略(即只允许该方法返回“None”)。 - ekhumoro
我正在使用Python 3.4。在Python 2.7中是否需要使用Qvariant() - Santi Peñate-Vera
显示剩余3条评论
7个回答

26

个人而言,我会创建自己的模型类来让处理变得更加容易。

例如:

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

1
嗨,我得到了以下错误:return QtCore.QVariant() TypeError: PyQt4.QtCore.QVariant表示一种映射类型,无法实例化 - Santi Peñate-Vera
对于新的pandas版本,您需要将self._data.iloc[index.row()][index.column()]))替换为self._data.iat[row, col] - Tuhin Mitra
1
如果有人在使用PandasModel时遇到了缓慢或迟钝的情况,那么问题可能出在如何计算rowCount上。self._data.values返回一个numpy数组,比len(df.index)慢得多。当在一个(1000,1000)的数据框上运行timeit时:In [5]: %timeit len(df.values) 6.18 µs ± 5.21 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) In [7]: %timeit len(df.index) 306 ns ± 0.88 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each) - ajoseps
.iat is also about 20% faster than .iloc - misantroop

22

这个有效:

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及以上版本中避免使用QVariant()


4
虽然这是一条旧回复,但仍然是一个好的回复。如果你想让数据框的索引出现在行中,你可以按照以下方式修改方法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 - hyamanieu
1
那个可行,但是我怎么能让这个模型可编辑并将其移回数据框呢?目前它甚至都无法被编辑。 - Nickpick

10

我发现所有提供的答案在数据框具有1000行或更多行时都非常缓慢。以下方法适用于我且速度惊人快:

我发现所有提供的答案在数据框具有1000行或更多行时都非常缓慢。以下方法适用于我且速度惊人快:

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

6
除了使用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行,只需将值作为字符串输入,但除此之外,它将我的加载时间从可能是一分钟或更长时间缩短到几秒钟。 - NL23codes

6
以下是基于@Frederick Li的回答进行微小修改的PyQt5完整工作剪切粘贴示例:
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简单示例,链接在这里:https://doc.qt.io/qtforpython/examples/example_external__pandas.html - zeroalpha

5

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

在撰写本答案时,最新版本的 pandas0.18.1,您可以执行以下操作:

from pandas.sandbox.qtpandas import DataFrameModel, DataFrameWidget

那段代码似乎与PySide耦合,但将其与PyQt配合使用应该相对容易。此外,该代码已被弃用,警告显示该模块将在未来删除。
幸运的是,他们将其提取到GitHub上的一个独立项目中,名为“pandas-qt”。

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

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

你好,我只想添加一点,pandas-qt不支持Python3,并且似乎以后也不会支持。在此期间,您可以使用qtpandas(https://github.com/draperjames/qtpandas)进行安装“pip install qtpandas”。 - James Draper

5

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接