'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 1:[1]
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.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | HiFile the best file manager |