Как ускорить заполнение и прокрутку таблицы с миллионами строк в pyqt tableview? - PullRequest
0 голосов
/ 26 апреля 2020

У меня возникли некоторые проблемы с моей табличной моделью, когда в нее подается миллион строк или более. Функция «data» в некоторых отношениях кажется виновной, однако я не знаю, как ее исправить. Будем весьма благодарны за любые предложения относительно новой модели таблицы или чего-то еще.

Примечания / FAQ:

Q: Зачем показывать пользователю миллионы строк?

A: Это Инструмент, который я делаю, предназначен для анализа, они смогут отфильтровать данные, которые они хотят использовать. Но мне сказали: «Мы хотим получить доступ ко всем данным».

В: Почему вы не используете какую-то базу данных для этого?

A: Мне сказали не делать , (Мой босс ненавидит базы данных ...)

В: Можете ли вы отказаться от этого для другого метода?

A: В некоторой степени. Наши данные хранятся в файлах .h5, и мы читаем их с Pandas. Эта модель сформулирована примерно на Pandas (что мне нравится, и может быть уже слишком поздно go назад, так как я убедил всех использовать Pandas), но если есть что-то, что значительно улучшит производительность, тогда я игра .

В: Почему вы сохраняете логический массив фильтров?

A: Модель напрямую связана с другим виджетом, который позволяет пользователям добавлять фильтры. Я пытался ограничить потребление памяти, сохраняя только 1 экземпляр данных за один раз.

Примечания:

  1. self.FilterIdx - это логический массив, который получает обновляется, когда пользователь определяет некоторые фильтры из другого виджета

  2. Функция GF () - это общая функция фильтра, которая возвращает логический массив, который присваивается self.FilterIdx.

Что я пробовал: я заметил, что фильтрация переменной self.arraydata в функции данных была основным источником замедления, поэтому теперь я проверяю, есть ли currentRowCount == self.arraydata. shape [0] и не фильтровать, если это так. Кроме того, я не уверен, что делать.

Я просто не уверен, что проблема с большими номерами строк, поскольку метод данных должен вызываться только для отображаемых ячеек.

Есть ли какая-то другая модель, из которой я мог бы добиться большей скорости?

Есть ли способ изменить метод данных, чтобы отображать целые порции DataFrame за раз вместо того, чтобы он, казалось бы, перебирал значения DataFrame? (Я предполагаю, что это где-то сделано в источнике QTableView)

У меня есть следующая модель абстрактной таблицы:

class MyTableModel(Core.QAbstractTableModel):
    DataChanged = Core.pyqtSignal()
    ColumnChanged = Core.pyqtSignal()
    RowChanged = Core.pyqtSignal()
    def __init__(self, datain=pd.DataFrame(), parent=None,**kwargs):
        Core.QAbstractTableModel.__init__(self, parent)
        self.parent = parent
        self.edit = kwargs.get('editable',False)
        self.useRowHeads = kwargs.get('rowheads',False)
        self.querystring = ''
        if not isinstance(self.edit,bool):
            self.edit = False
        if not isinstance(datain,pd.DataFrame):
            try:
                datain = pd.DataFrame(datain)
            except:
                datain = pd.DataFrame()
        if self.edit:
            self.editHeader = 'Table Edited'
            datain[self.editHeader] = pd.Series([0]*datain.shape[0])
        self.arraydata = copy.deepcopy(datain)
        self.reindexFilter(init=True)
        self.updateHeaders()

    def updateHeaders(self):
        self.headers = list(self.arraydata)

    def reindexFilter(self,init=False):
        if not init:
            self.FilterIdx = self.FilterIdx.reindex(self.arraydata.index)
        else:
            self.FilterIdx = pd.Series([True]*self.arraydata.shape[0],index=self.arraydata.index)

    def handleValue(self,header,value):
        if header in self.arraydata:
            dtype = self.arraydata[header].dtype.kind
            if dtype in ['u','i']:
                try:
                    value = int(value)
                except:
                    return None
            elif dtype in ['b']:
                try:
                    value = value == 'True'
                except:
                    return None
            elif dtype in ['f']:
                try:
                    value = float(value)
                except:
                    return None
        return value

    def rowCount(self, parent=None):
        self.currentRowCount = self.arraydata[self.FilterIdx].shape[0]
        return self.currentRowCount

    def columnCount(self, parent=None):
        return self.arraydata.shape[1]

    def headerData(self, section, orientation = Core.Qt.Horizontal, role=Core.Qt.DisplayRole):
        if role == Core.Qt.DisplayRole:
            if orientation == Core.Qt.Horizontal:
                return self.headers[section]
            elif orientation == Core.Qt.Vertical and self.useRowHeads:
                return str(self.arraydata.index[section])
            else:
                return section

    def flags(self, index):
        if self.headers[index.column()]:
                return Core.Qt.ItemIsEnabled | Core.Qt.ItemIsEditable | Core.Qt.ItemIsSelectable
        elif self.edit:
            if self.headers[index.column()] != self.editHeader:
                return Core.Qt.ItemIsEnabled | Core.Qt.ItemIsEditable | Core.Qt.ItemIsSelectable

        return Core.Qt.ItemIsEnabled | Core.Qt.ItemIsSelectable

    def data(self, index, role):
        start = time.time()
        if not index.isValid():
            return None
        if role == Core.Qt.DisplayRole:
            # if self.headers[index.column()] in self.arraydata:
                # colname = self.headers[index.column()]
            if self.currentRowCount == self.arraydata.shape[0]:
                value = self.arraydata.iloc[index.row(),index.column()]
            else:
                value = self.arraydata[self.FilterIdx].iloc[index.row(),index.column()]
            # else:
                # value = ''
            print(time.time()-start)
            return str(value)
### Data Changing
    def updateCell(self):
        self.DataChanged.emit()
        self.layoutChanged.emit()
        self.updateTableData()

    def updateColumns(self):
        self.updateHeaders()
        self.updateTableData()
        self.DataChanged.emit()
        self.ColumnChanged.emit()

    def updateRows(self):
        self.RowChanged.emit()
        self.updateTableData()
        self.DataChanged.emit()

    def updateTableData(self):
        self.reindexFilter()
        self.dataChanged.emit(self.createIndex(0, 0), self.createIndex(self.rowCount(0), self.columnCount(0)))
        self.layoutChanged.emit()


    def sort(self, colnum, order):
        try:
            print('DOING THE SORT')
            self.layoutAboutToBeChanged.emit()
            self.arraydata.sort_values(self.arraydata.columns[colnum], ascending=not order,inplace=True)
            self.reindexFilter()
            self.layoutChanged.emit()
        except Exception as e:
            print(e)

    def filterTable(self,procedures):
        self.layoutAboutToBeChanged.emit()
        if procedures:
            self.FilterIdx,self.querystring = GF(self.arraydata,procedures)
        else:
            self.reindexFilter(init=True)
        self.updateRows()
...