#python-3.x #pandas #pyqt5
#python-3.x #pandas #pyqt5
Вопрос:
Мое приложение базы данных использует библиотеку Pandas. Я могу отобразить файл Excel в своем TableView, но в любое время я удаляю данные с мэйнфрейма и пытаюсь обновить TableView. Это дает мне ошибку ключа.
Я пытаюсь заставить его отображать обновленную таблицу. Я пытаюсь удалить строку, которую запрашивает пользователь. Он работает, когда он падает, потому что я вывел информацию, но сам TableView не обновляется и выдает ошибку.
df = pd.read_excel("filename")
model = PandasModel(df)
self.tableView.setModel(model)
self.tableView.resizeColumnsToContents()
def DeletePlayer(self):
global df
choose = self.removePlayerEdit.text()
if(choose == '0'):
df = df.drop([0])
print("Player deleted")
print(df)
class PandasModel(QtCore.QAbstractTableModel):
def __init__(self, df = pd.DataFrame(), parent=None):
QtCore.QAbstractTableModel.__init__(self, parent=parent)
self._df = df
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if role != QtCore.Qt.DisplayRole:
return QtCore.QVariant()
if orientation == QtCore.Qt.Horizontal:
try:
return self._df.columns.tolist()[section]
except (IndexError, ):
return QtCore.QVariant()
elif orientation == QtCore.Qt.Vertical:
try:
# return self.df.index.tolist()
return self._df.index.tolist()[section]
except (IndexError, ):
return QtCore.QVariant()
def data(self, index, role=QtCore.Qt.DisplayRole):
if role != QtCore.Qt.DisplayRole:
return QtCore.QVariant()
if not index.isValid():
return QtCore.QVariant()
return QtCore.QVariant(str(self._df.ix[index.row(), index.column()]))
def setData(self, index, value, role):
row = self._df.index[index.row()]
col = self._df.columns[index.column()]
if hasattr(value, 'toPyObject'):
# PyQt4 gets a QVariant
value = value.toPyObject()
else:
# PySide gets an unicode
dtype = self._df[col].dtype
if dtype != object:
value = None if value == '' else dtype.type(value)
self._df.set_value(row, col, value)
return True
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self._df.index)
def columnCount(self, parent=QtCore.QModelIndex()):
return len(self._df.columns)
def sort(self, column, order):
colname = self._df.columns.tolist()[column]
self.layoutAboutToBeChanged.emit()
self._df.sort_values(colname, ascending= order == QtCore.Qt.AscendingOrder, inplace=True)
self._df.reset_index(inplace=True, drop=True)
self.layoutChanged.emit()
Ответ №1:
При реализации модели вы не должны напрямую обращаться к элементу, в котором хранятся данные (dataframe), потому что, если вы измените его, модель не будет знать, что вызовет проблемы, вместо этого вы должны создать методы, которые изменяют внутренние данные, но используют такие методы, как beginRemoveRows и endRemoveColumns, которые будут уведомлять пользователямодель изменения.
def removeColumn(self, col):
if 0 <= col < self.columnCount():
self.beginRemoveRows(QtCore.QModelIndex(), col, col)
self._df.drop(
self._df.columns[[col]], axis=1, inplace=True
)
self._df.reset_index(inplace=True, drop=True)
self.endRemoveColumns()
Я улучшил свою первоначальную модель до следующей:
from PyQt5 import QtCore, QtGui, QtWidgets
import pandas as pd
import numpy as np
class FloatDelegate(QtWidgets.QStyledItemDelegate):
@property
def decimals(self):
if not hasattr(self, "_decimals"):
self._decimals = 2
return self._decimals
@decimals.setter
def decimals(self, decimals):
self._decimals = decimals
def createEditor(self, parent, option, index):
DBL_MAX = 1.7976931348623157e308
editor = QtWidgets.QDoubleSpinBox(
parent, minimum=-DBL_MAX, maximum=DBL_MAX, decimals=self.decimals
)
return editor
def setEditorData(self, editor, index):
editor.setValue(index.data())
def setModelData(self, editor, model, index):
model.setData(index, editor.value(), QtCore.Qt.DisplayRole)
def displayText(self, value, locale):
return "{}".format(value)
class DataFrameModel(QtCore.QAbstractTableModel):
DtypeRole = QtCore.Qt.UserRole 1000
ValueRole = QtCore.Qt.UserRole 1001
def __init__(self, df=pd.DataFrame(), parent=None):
super(DataFrameModel, self).__init__(parent)
self._dataframe = df
def setDataFrame(self, dataframe):
self.beginResetModel()
self._dataframe = dataframe.copy()
self.endResetModel()
def dataFrame(self):
return self._dataframe
dataFrame = QtCore.pyqtProperty(
pd.DataFrame, fget=dataFrame, fset=setDataFrame
)
@QtCore.pyqtSlot(int, QtCore.Qt.Orientation, result=str)
def headerData(
self,
section: int,
orientation: QtCore.Qt.Orientation,
role: int = QtCore.Qt.DisplayRole,
):
if role == QtCore.Qt.DisplayRole:
if orientation == QtCore.Qt.Horizontal:
return self._dataframe.columns[section]
else:
return str(self._dataframe.index[section])
return QtCore.QVariant()
def rowCount(self, parent=QtCore.QModelIndex()):
if parent.isValid():
return 0
return len(self._dataframe.index)
def columnCount(self, parent=QtCore.QModelIndex()):
if parent.isValid():
return 0
return self._dataframe.columns.size
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid() or not (
0 <= index.row() < self.rowCount()
and 0 <= index.column() < self.columnCount()
):
return QtCore.QVariant()
row = self._dataframe.index[index.row()]
col = self._dataframe.columns[index.column()]
dt = self._dataframe[col].dtype
val = self._dataframe.iloc[row][col]
if role == QtCore.Qt.DisplayRole:
return val
elif role == DataFrameModel.ValueRole:
return val
if role == DataFrameModel.DtypeRole:
return dt
return QtCore.QVariant()
def setData(self, index, value, role):
row = self._dataframe.index[index.row()]
col = self._dataframe.columns[index.column()]
if hasattr(value, "toPyObject"):
# PyQt4 gets a QVariant
value = value.toPyObject()
else:
# PySide gets an unicode
dtype = self._dataframe[col].dtype
if dtype != object:
value = None if value == "" else dtype.type(value)
self._dataframe.at[row, col] = value
return True
def flags(self, index):
flags = (
QtCore.Qt.ItemIsSelectable
| QtCore.Qt.ItemIsDragEnabled
| QtCore.Qt.ItemIsEditable
| QtCore.Qt.ItemIsEnabled
)
return flags
def roleNames(self):
roles = {
QtCore.Qt.DisplayRole: b"display",
DataFrameModel.DtypeRole: b"dtype",
DataFrameModel.ValueRole: b"value",
}
return roles
def removeRow(self, row):
if 0 <= row < self.rowCount():
self.beginRemoveRows(QtCore.QModelIndex(), row, row)
self._dataframe.drop([row], inplace=True)
self._dataframe.reset_index(inplace=True, drop=True)
self.endRemoveRows()
def removeColumn(self, col):
if 0 <= col < self.columnCount():
self.beginRemoveRows(QtCore.QModelIndex(), col, col)
self._dataframe.drop(
self._dataframe.columns[[col]], axis=1, inplace=True
)
self._dataframe.reset_index(inplace=True, drop=True)
self.endRemoveColumns()
def sort(self, column, order):
colname = self._dataframe.columns[column]
self.layoutAboutToBeChanged.emit()
self._dataframe.sort_values(
colname, ascending=order == QtCore.Qt.AscendingOrder, inplace=True
)
self._dataframe.reset_index(inplace=True, drop=True)
self.layoutChanged.emit()
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
tableview = QtWidgets.QTableView()
tableview.setSortingEnabled(True)
delegate = FloatDelegate(tableview)
tableview.setItemDelegate(delegate)
delegate.decimals = 4
self.spinbox_row = QtWidgets.QSpinBox()
self.button_row = QtWidgets.QPushButton(
"Delete Row", clicked=self.remove_row
)
self.spinbox_col = QtWidgets.QSpinBox()
self.button_col = QtWidgets.QPushButton(
"Delete Column", clicked=self.remove_col
)
df = pd.DataFrame(
np.random.uniform(0, 100, size=(100, 4)), columns=list("ABCD")
)
self._model = DataFrameModel(df)
tableview.setModel(self._model)
grid = QtWidgets.QGridLayout(self)
grid.addWidget(tableview, 0, 0, 1, 4)
grid.addWidget(self.spinbox_row, 1, 0)
grid.addWidget(self.button_row, 1, 1)
grid.addWidget(self.spinbox_col, 1, 2)
grid.addWidget(self.button_col, 1, 3)
self.on_rowChanged()
self.on_columnChanged()
self._model.rowsInserted.connect(self.on_rowChanged)
self._model.rowsRemoved.connect(self.on_rowChanged)
self._model.columnsInserted.connect(self.on_columnChanged)
self._model.columnsRemoved.connect(self.on_columnChanged)
@QtCore.pyqtSlot()
def on_rowChanged(self):
self.spinbox_row.setMaximum(self._model.rowCount() - 1)
@QtCore.pyqtSlot()
def on_columnChanged(self):
self.spinbox_col.setMaximum(self._model.columnCount() - 1)
@QtCore.pyqtSlot()
def remove_row(self):
row = self.spinbox_row.value()
self._model.removeRow(row)
@QtCore.pyqtSlot()
def remove_col(self):
col = self.spinbox_col.value()
self._model.removeColumn(col)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
Комментарии:
1. Я надеялся, что вы ответите, потому что я использовал вашу модель. Спасибо, это работает.