从熊猫数据框加载的QTableView中的多列过滤错误

时间:2019-03-20 10:59:40

标签: python python-3.x pyside2

我试图用QTableView中的1个QLineEdit框过滤多个列。我正在使用QAbstractTableModel进行过滤,并且我将QSortFilterProxyModel子类化以处理多个列。数据是从xlsx文件中提取的,并使用熊猫加载到数据框中。

因此,只要excel文件中的标头是数字(0、1、2..etc),我的代码就可以正常工作。在熊猫to_excel函数中,有一个标志可以防止熊猫分别添加其自己的“标头”和“索引”标签header = none和index = none。因此,当我将标题设置为none时,在填充QTableView时会出现此错误

TypeError: 'PySide2.QtCore.QRegExp.indexIn' called with wrong argument types:
  PySide2.QtCore.QRegExp.indexIn(int)
Supported signatures:
  PySide2.QtCore.QRegExp.indexIn(str, int = 0, PySide2.QtCore.QRegExp.CaretMode = PySide2.QtCore.QRegExp.CaretMode.CaretAtZero)

这是我自定义的“ filterAcceptsRow”函数:

def filterAcceptsRow(self, sourceRow, sourceParent):
    index0 = self.sourceModel().index(sourceRow, 0, sourceParent)
    index1 = self.sourceModel().index(sourceRow, 1, sourceParent)
    index2 = self.sourceModel().index(sourceRow, 2, sourceParent)
    # The following line causes the error
    return ((self.filterRegExp().indexIn(self.sourceModel().data(index0, self.role)) >= 0
             or self.filterRegExp().indexIn(self.sourceModel().data(index1, self.role)) >= 0
            or self.filterRegExp().indexIn(self.sourceModel().data(index2, self.role)) >= 0))

这是我的代码的有效示例

from pandas import DataFrame, ExcelFile, Series
from PySide2.QtWidgets import QApplication, QTabWidget, QVBoxLayout, QAbstractItemView, QSizePolicy, QAbstractScrollArea, QHeaderView, QTableView, QMainWindow, QGridLayout, QLineEdit, QWidget
from PySide2.QtCore import Qt, QAbstractTableModel, QSortFilterProxyModel, QModelIndex, QRegExp, QDate, QDateTime


class PandasModel(QAbstractTableModel):

    def __init__(self, data, parent=None):
        QAbstractTableModel.__init__(self, parent)
        self._filters = {}
        self._sortBy = []
        self._sortDirection = []
        self._dfSource = data
        self._dfDisplay = data
        self._dfDisplay = self._dfDisplay.fillna("")

    def rowCount(self, parent=QModelIndex()):
        if parent.isValid():
            return 0
        return self._dfDisplay.shape[0]

    def columnCount(self, parent=QModelIndex()):
        if parent.isValid():
            return 0
        return self._dfDisplay.shape[1]

    def data(self, index, role):
        if index.isValid() and role == Qt.DisplayRole:
            return self._dfDisplay.values[index.row()][index.column()]
        return None

    def headerData(self, col, orientation=Qt.Horizontal, role=Qt.DisplayRole):
        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
            return str(self._dfDisplay.columns[col])
        return None

    def setupModel(self, header, data):
        self._dfSource = DataFrame(data, columns=header)
        self._sortBy = []
        self._sortDirection = []
        self.setFilters({})

    def setFilters(self, filters):
        self.modelAboutToBeReset.emit()
        self._filters = filters
        print(filters)
        self.updateDisplay()
        self.modelReset.emit()

    def sort(self, col, order=Qt.AscendingOrder):
        # Storing persistent indexes
        self.layoutAboutToBeChanged.emit()
        oldIndexList = self.persistentIndexList()
        oldIds = self._dfDisplay.index.copy()
        # Sorting data
        column = self._dfDisplay.columns[col]
        ascending = (order == Qt.AscendingOrder)
        if column in self._sortBy:
            i = self._sortBy.index(column)
            self._sortBy.pop(i)
            self._sortDirection.pop(i)
        self._sortBy.insert(0, column)
        self._sortDirection.insert(0, ascending)
        self.updateDisplay()
        # Updating persistent indexes
        newIds = self._dfDisplay.index
        newIndexList = []
        for index in oldIndexList:
            id = oldIds[index.row()]
            newRow = newIds.get_loc(id)
            newIndexList.append(self.index(newRow, index.column(), index.parent()))
        self.changePersistentIndexList(oldIndexList, newIndexList)
        self.layoutChanged.emit()
        self.dataChanged.emit(QModelIndex(), QModelIndex())

    def updateDisplay(self):
        dfDisplay = self._dfSource.copy()
        # Filtering
        cond = Series(True, index=dfDisplay.index)
        for column, value in self._filters.items():
            cond = cond & \
                   (dfDisplay[column].str.lower().str.find(str(value).lower()) >= 0)
        dfDisplay = dfDisplay[cond]
        # Sorting
        if len(self._sortBy) != 0:
            dfDisplay.sort_values(by=self._sortBy, ascending=self._sortDirection, inplace=True)
        # Updating
        self._dfDisplay = dfDisplay

class SortFilterProxyModel(QSortFilterProxyModel):
    def __init__(self, data, parent=None):
        super(SortFilterProxyModel, self).__init__(parent)
        self.role = Qt.DisplayRole
        self.minDate = QDate()
        self.maxDate = QDate()
        self.__data = data

    def setFilterMinimumDate(self, date):
        self.minDate = date
        self.invalidateFilter()

    def filterMinimumDate(self):
        return self.minDate

    def setFilterMaximumDate(self, date):
        self.maxDate = date
        self.invalidateFilter()

    def filterMaximumDate(self):
        return self.maxDate

    def filterAcceptsRow(self, sourceRow, sourceParent):
        index0 = self.sourceModel().index(sourceRow, 0, sourceParent)
        index1 = self.sourceModel().index(sourceRow, 1, sourceParent)
        index2 = self.sourceModel().index(sourceRow, 2, sourceParent)
        return ((self.filterRegExp().indexIn(self.sourceModel().data(index0, self.role)) >= 0 or self.filterRegExp().indexIn(self.sourceModel().data(index1, self.role)) >= 0 or self.filterRegExp().indexIn(self.sourceModel().data(index2, self.role)) >= 0))

    def lessThan(self, left, right):
        leftData = self.sourceModel().data(left, self.role)
        rightData = self.sourceModel().data(right, self.role)

        if not isinstance(leftData, QDate):
            emailPattern = QRegExp("([\\w\\.]*@[\\w\\.]*)")

            if left.column() == 1 and emailPattern.indexIn(leftData) != -1:
                leftData = emailPattern.cap(1)

            if right.column() == 1 and emailPattern.indexIn(rightData) != -1:
                rightData = emailPattern.cap(1)

        return leftData < rightData

    def dateInRange(self, date):
        if isinstance(date, QDateTime):
            date = date.date()

        return ((not self.minDate.isValid() or date >= self.minDate)
                and (not self.maxDate.isValid() or date <= self.maxDate))

class PerfSearchTest(QMainWindow):
    def __init__(self):
        super().__init__()
        self.path_to_local_spreadsheet = "output.xlsx"
        self.spreadsheet_display = {}
        self.searches = {}
        self.current_tab_layout = {}
        self.tab_widget = {}
        self.central_widget = QWidget()
        self.tabs = QTabWidget()
        self.layout_grid = QGridLayout(self)
        self.layout_grid.setSpacing(10)
        self.populate_spreadsheet_table()
        self.layout_grid.addWidget(self.tabs, 0, 0)
        self.central_widget.setLayout(self.layout_grid)
        self.setCentralWidget(self.central_widget)

    def onTextChanged(self, text):
        self.proxyModelContact.setFilterRegExp(QRegExp(text, Qt.CaseInsensitive, QRegExp.FixedString))

    def populate_spreadsheet_table(self):
        opened_excel_flie_dataframe = ExcelFile(self.path_to_local_spreadsheet)
        opened_excel_file_dataframe_worksheets = []
        for y, sheet in enumerate(opened_excel_flie_dataframe.sheet_names):
            df = opened_excel_flie_dataframe.parse(y)
            self.tab_widget[y] = QWidget()
            self.searches[y] = QLineEdit(sheet)
            self.spreadsheet_display[y] = QTableView()
            self.spreadsheet_display[y].setSortingEnabled(True)
            self.spreadsheet_display[y].setEditTriggers(QAbstractItemView.NoEditTriggers)
            self.spreadsheet_display[y].setAlternatingRowColors(True)
            self.spreadsheet_display[y].horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
            self.spreadsheet_display[y].verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
            self.spreadsheet_display[y].setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents)
            self.spreadsheet_display[y].setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding)
            self.spreadsheet_display[y].resizeColumnsToContents()
            self.current_tab_layout[y] = QVBoxLayout()
            self.current_tab_layout[y].addWidget(self.searches[y])
            self.current_tab_layout[y].addWidget(self.spreadsheet_display[y])
            self.proxyModelContact = SortFilterProxyModel(df)
            self.proxyModelContact.setSourceModel(PandasModel(df))
            self.searches[y].textChanged.connect(self.onTextChanged)
            self.spreadsheet_display[y].setModel(self.proxyModelContact)
            self.tab_widget[y].setLayout(self.current_tab_layout[y])
            opened_excel_file_dataframe_worksheets.append(df)
            self.tabs.addTab(self.tab_widget[y], sheet)


if __name__ == "__main__":
    app = QApplication()
    w = PerfSearchTest()
    w.show()
    app.exec_()

Here's a 'broken' spreadsheet that will replicate the error

Here's a 'working' spreadsheet that will not replicate the error(or it shouldn't)

如果需要,我很乐意传递整个源代码,以防万一。我没有包括依赖pygsheets下载的内容-输入到pandas数据框-然后导出到xslx。

1 个答案:

答案 0 :(得分:1)

引起此问题的原因是indexIng需要一个字符串,并且如您所指出的,您正在传递一个整数,因此可能的解决方案是将其转换为字符串:

def filterAcceptsRow(self, sourceRow, sourceParent):
    index0 = self.sourceModel().index(sourceRow, 0, sourceParent)
    index1 = self.sourceModel().index(sourceRow, 1, sourceParent)
    index2 = self.sourceModel().index(sourceRow, 2, sourceParent)
    value = self.sourceModel().data(index0, self.role) 
    for ix in (index0, index1, index2):
        value = self.sourceModel().data(ix, self.role)
        if self.filterRegExp().indexIn(str(value)) >= 0:
            return True
    return False