从 pandas 数据帧加载的 QTableView 中的多列过滤错误
Error with multi column filtering in a QTableView loaded from a pandas dataframe
我正在尝试使用 QTableView 中的 1 个 QLineEdit 框过滤多列。我正在使用 QAbstractTableModel 进行过滤,并且我已经将 QSortFilterProxyModel 子类化以处理多列。数据从 xlsx 文件中提取并使用 pandas.
加载到数据框中
所以只要 excel 文件中的 header 是数字(0、1、2..等),我的代码就可以工作。在 pandas to_excel 函数中有一个标志来防止 pandas 添加它自己的 'header' 和索引标签 header=none 和 index= none 分别。因此,当我将 headers 设置为 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。
问题是因为 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
我正在尝试使用 QTableView 中的 1 个 QLineEdit 框过滤多列。我正在使用 QAbstractTableModel 进行过滤,并且我已经将 QSortFilterProxyModel 子类化以处理多列。数据从 xlsx 文件中提取并使用 pandas.
加载到数据框中所以只要 excel 文件中的 header 是数字(0、1、2..等),我的代码就可以工作。在 pandas to_excel 函数中有一个标志来防止 pandas 添加它自己的 'header' 和索引标签 header=none 和 index= none 分别。因此,当我将 headers 设置为 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。
问题是因为 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