Search code examples
pythonqtmodeltableview

Speed differences between QStandardItemModel and QAbstractTableModel?


Can anyone explain the following: I have 2 scripts for loading a pandas dataframe in a tableview which has a filter field. The one with the standard model loads the data in the "init" section. With this one everyting is blazing fast , also the filtering. The second script works much slower but with this one i can set the background color of the cells which i need. These are the scripts:

import timeit

import pandas as pd
from PyQt5 import QtGui
from PyQt5 import QtWidgets
from PyQt5.QtCore import *
from PyQt5.QtCore import QAbstractTableModel
from PyQt5.QtCore import Qt, QSortFilterProxyModel
from PyQt5.QtGui import *
from PyQt5.uic import loadUi


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]

    def flags(self, index):
        if not index.isValid():
            return Qt.ItemIsEnabled

        return super().flags(index) | Qt.ItemIsEditable  # add editable flag.

    def setData(self, index, value, role):

        if role == Qt.EditRole:
            # Set the value into the frame.
            self._data.iloc[index.row(), index.column()] = value
            return True

        return False

class TableViewer(QtWidgets.QMainWindow):

    def __init__(self):
        super(TableViewer, self).__init__()

        self.ui = loadUi("QTableViewForm.ui", self)
        self.ui.cmdRun1.clicked.connect(self.RunFunction1)
        self.ui.cmdRun2.clicked.connect(self.RunFunction2)
        self.ui.inputFilter.textChanged.connect(self.SetFilteredView)

        self.showdata()

    def showdata(self):
        start = timeit.default_timer()
        print("Start LoadData")

        data = pd.read_pickle("productdata.pkl")

        self.model = PandasTableModel(data)
        self.ui.tableData.setModel(self.model)
        self.proxy_model = QSortFilterProxyModel()
        self.proxy_model.setFilterKeyColumn(-1)  # Search all columns.
        self.proxy_model.setSourceModel(self.model)
        self.proxy_model.sort(0, Qt.AscendingOrder)
        self.proxy_model.setFilterCaseSensitivity(False)
        self.ui.tableData.setModel(self.proxy_model)

        print("Stop LoadData")
        end = timeit.default_timer()
        print("Process Time: ", (end - start))

    def set_cell_color(self, row, column):
        self.model.change_color(row, column, QBrush(Qt.red))

    def RunFunction1(self):
        start = timeit.default_timer()
        print("Start RunFunction1")
        #Gans de rij in 't rood
        colums = self.proxy_model.columnCount()

        for c in range(colums):

            self.set_cell_color(3, c)

        print("Stop RunFunction1")
        end = timeit.default_timer()
        print("Process Time: ", (end - start))

    def RunFunction2(self):
        start = timeit.default_timer()
        print("Start RunFunction1")
        #Gans de rij in 't rood
        colums = self.proxy_model.columnCount()

        for c in range(colums):

            self.set_cell_color(3, c)

        print("Stop RunFunction1")
        end = timeit.default_timer()
        print("Process Time: ", (end - start))

    def SetFilteredView(self):

        # print("Start set_filter")
        filter_text = self.ui.inputFilter.text()
        self.proxy_model.setFilterFixedString(filter_text)
        filter_result = self.proxy_model.rowCount()
        self.ui.lblResult.setText("(" + str(filter_result) + " records)")


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    win = TableViewer()
    win.show()
    sys.exit(app.exec_())enter code here

And the slow one:

import timeit

import pandas as pd
from PyQt5 import QtGui
from PyQt5 import QtWidgets
from PyQt5.QtCore import *
from PyQt5.QtCore import QAbstractTableModel
from PyQt5.QtCore import Qt, QSortFilterProxyModel
from PyQt5.QtGui import *
from PyQt5.uic import loadUi


class PandasTableModel(QAbstractTableModel):
    def __init__(self, data, parent=None):
        QAbstractItemModel.__init__(self, parent)
        self._data = data
        self.colors = dict()

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

    def setData(self, index, value, role):

        if role == Qt.EditRole:
            # Set the value into the frame.
            self._data.iloc[index.row(), index.column()] = value
            return True

    def data(self, index, role=Qt.DisplayRole):
        if index.isValid():
            if role == Qt.DisplayRole:
                return str(self._data.iloc[index.row(), index.column()])
            if role == Qt.EditRole:
                return str(self._data.iloc[index.row(), index.column()])
            if role == Qt.BackgroundRole:
                color = self.colors.get((index.row(), index.column()))
                if color is not None:
                    return color
        return None

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

    def change_color(self, row, column, color):
        ix = self.index(row, column)
        self.colors[(row, column)] = color
        self.dataChanged.emit(ix, ix, (Qt.BackgroundRole,))


class TableViewer(QtWidgets.QMainWindow):

    def __init__(self):
        super(TableViewer, self).__init__()

        self.ui = loadUi("QTableViewForm.ui", self)
        self.ui.cmdRun1.clicked.connect(self.RunFunction1)
        self.ui.cmdRun2.clicked.connect(self.RunFunction2)
        self.ui.inputFilter.textChanged.connect(self.SetFilteredView)

        self.showdata()

    def showdata(self):
        start = timeit.default_timer()
        print("Start LoadData")

        data = pd.read_pickle("productdata.pkl")

        self.model = PandasTableModel(data)
        self.ui.tableData.setModel(self.model)
        self.proxy_model = QSortFilterProxyModel()
        self.proxy_model.setFilterKeyColumn(-1)  # Search all columns.
        self.proxy_model.setSourceModel(self.model)
        self.proxy_model.sort(0, Qt.AscendingOrder)
        self.proxy_model.setFilterCaseSensitivity(False)
        self.ui.tableData.setModel(self.proxy_model)

        print("Stop LoadData")
        end = timeit.default_timer()
        print("Process Time: ", (end - start))

    def set_cell_color(self, row, column):
        self.model.change_color(row, column, QBrush(Qt.red))

    def RunFunction1(self):
        start = timeit.default_timer()
        print("Start RunFunction1")
        #Gans de rij in 't rood
        colums = self.proxy_model.columnCount()

        for c in range(colums):

            self.set_cell_color(3, c)

        print("Stop RunFunction1")
        end = timeit.default_timer()
        print("Process Time: ", (end - start))

    def RunFunction2(self):
        start = timeit.default_timer()
        print("Start RunFunction1")
        #Gans de rij in 't rood
        colums = self.proxy_model.columnCount()

        for c in range(colums):

            self.set_cell_color(3, c)

        print("Stop RunFunction1")
        end = timeit.default_timer()
        print("Process Time: ", (end - start))

    def SetFilteredView(self):

        # print("Start set_filter")
        filter_text = self.ui.inputFilter.text()
        self.proxy_model.setFilterFixedString(filter_text)
        filter_result = self.proxy_model.rowCount()
        self.ui.lblResult.setText("(" + str(filter_result) + " records)")


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    win = TableViewer()
    win.show()
    sys.exit(app.exec_())

(i'm loading 2000 rows and 35 columns)

Can i have a fast one with the background color function in it ?

Cheers , Johnson


Solution

  • Instead of

    def set_cell_color(self, row, column):
            self.model.change_color(row, column, QBrush(Qt.red))
    

    use this

    def set_cell_color(self, row, column):
            self.model.item(row, column).setBackground(QBrush(Qt.red))
    

    Depending on your requirements, you may need to replace model for proxy_model, but it depends on whether row and column should refer to coordinates of the filtered or the underlying model. So it is up to you.