Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Qt for Python
  4. Filtering QTableview with QSortFilterProxyModel very slow
Qt 6.11 is out! See what's new in the release blog

Filtering QTableview with QSortFilterProxyModel very slow

Scheduled Pinned Locked Moved Unsolved Qt for Python
7 Posts 2 Posters 2.1k Views 2 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • J Offline
    J Offline
    Johnson9070
    wrote on last edited by
    #1

    Hi All,

    I want to do some filtering on a pandas dataframe displayd in a QTableview. There about 4500 rows and 15 columns.
    When i type the results are filtered immediately. Pretty fast for 200 rows but very slow for large datasets.
    Any idea how to improve the results ?

    Here is the code:

    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 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)")
            print("Stop setFilteredView")
    if __name__ == "__main__":
        import sys
        app = QtWidgets.QApplication(sys.argv)
        win = TableViewer()
        win.show()
        sys.exit(app.exec_())
    
    1 Reply Last reply
    0
    • SGaistS Offline
      SGaistS Offline
      SGaist
      Lifetime Qt Champion
      wrote on last edited by
      #2

      Hi,

      Do you really need to filter on all columns ?

      That said, since you have a pandas data frame, did you consider using its filtering capabilities ? It might better suit your needs.

      Interested in AI ? www.idiap.ch
      Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

      1 Reply Last reply
      0
      • J Offline
        J Offline
        Johnson9070
        wrote on last edited by
        #3

        Hi SGaist , thx for your reply.

        The filter on all columns is exactly wat i need ;-)
        But not at this speed.

        The code below works great and filters really fast (>4500rows) but when i display the data using the "data" function of the model it becomes slow again. And i need the "data" function to do some formatting on the data in the table.
        I need to be able to filter semi-large tables (10000rows) at real time using the direct filter function.
        Here is the fast code:

        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 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_())
        
        1 Reply Last reply
        0
        • SGaistS Offline
          SGaistS Offline
          SGaist
          Lifetime Qt Champion
          wrote on last edited by
          #4

          Do you need to do a lot of data type conversions ?

          Interested in AI ? www.idiap.ch
          Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

          J 1 Reply Last reply
          0
          • SGaistS SGaist

            Do you need to do a lot of data type conversions ?

            J Offline
            J Offline
            Johnson9070
            wrote on last edited by
            #5

            @SGaist The idea is that i reed in a data file (csv , xls , ...) containing : url , string , date , email , phone numbers , .... so a bit of a mix. For the moment i have data showing 10.0 instead of 10 and 324758855252.0 instead of 0032.... or +32.....
            Also i would like to add some icons and so. I use face reco in this application so for example a user with a trained profile would have the faceId icon next to his name for example.

            1 Reply Last reply
            0
            • J Offline
              J Offline
              Johnson9070
              wrote on last edited by
              #6

              SGaist : Can i control the decorationrole through a function after a button click ?

              1 Reply Last reply
              0
              • SGaistS Offline
                SGaistS Offline
                SGaist
                Lifetime Qt Champion
                wrote on last edited by
                #7

                You can update the model as you wish, as long as you call the corresponding changed signal it will trigger the UI update.

                Interested in AI ? www.idiap.ch
                Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

                1 Reply Last reply
                1

                • Login

                • Login or register to search.
                • First post
                  Last post
                0
                • Categories
                • Recent
                • Tags
                • Popular
                • Users
                • Groups
                • Search
                • Get Qt Extensions
                • Unsolved