【发布时间】:2019-09-13 06:50:34
【问题描述】:
我在下面的代码中定义了类来显示带有 MultiHeader(具有多个级别的标题)的 Pandas DataFrame(用于表示 2D 表的数据结构),如下所示:
我用两个 QTableView 来做这个,一个用于 DataFrame 本身的数据,一个用于 MultiHeader 标签。但是,我希望能够将 DataFrame 存储在单个模型中,并将这些多个 QTableView 连接到它。理想情况下,我可以从视图中向 data() 方法传递一个附加参数,指示视图是用于标题还是正文,但我认为这不可能吗?
我想将它们组合成一个模型的一些原因...
- DataFrame 不同步标头和正文。换句话说,
header.model().df is data.model().df开始时为 True,但一旦调用delete_first_column并且self.df被覆盖,则为 False - 在结构上更有意义,因为 DataFrame 是单个对象
- 当前的结构需要在两个模型之间进行代码复制和通信,例如 delete_first_column() 应该适用于正文和标题,但它只适用于它所在的模型。
如何重构此代码,以便视图仅连接到单个 DataFrame 的单个模型?
from PyQt5 import QtGui, QtCore, QtWidgets
import pandas as pd
import numpy as np
import sys
# DataTableModel and DataTableView show the data in the rows of the DataFrame
class DataTableModel(QtCore.QAbstractTableModel):
"""
Model for DataTableView to connect for DataFrame data
"""
def __init__(self, df, parent=None):
super().__init__(parent)
self.df = df
# Headers for DataTableView are hidden. Header data is shown in HeaderView
def headerData(self, section, orientation, role=None):
pass
def columnCount(self, parent=None):
return len(self.df.columns)
def rowCount(self, parent=None):
return len(self.df)
# Returns the data from the DataFrame
def data(self, index, role=None):
if role == QtCore.Qt.DisplayRole:
row = index.row()
col = index.column()
cell = self.df.iloc[row, col]
return str(cell)
class DataTableView(QtWidgets.QTableView):
def __init__(self, df):
super().__init__()
# Create and set model
model = DataTableModel(df)
self.setModel(model)
# Hide the headers. The DataFrame headers (index & columns) will be displayed in the DataFrameHeaderViews
self.horizontalHeader().hide()
self.verticalHeader().hide()
# HeaderModel and HeaderView show the header of the DataFrame, in this case a 3 level header
class HeaderModel(QtCore.QAbstractTableModel):
def __init__(self, df):
super().__init__()
self.df = df
def columnCount(self, parent=None):
return len(self.df.columns.values)
def rowCount(self, parent=None):
if type(self.df.columns) == pd.MultiIndex:
if type(self.df.columns.values[0]) == tuple:
return len(self.df.columns.values[0])
else:
return 1
def data(self, index, role):
if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.ToolTipRole:
if type(self.df.columns) == pd.MultiIndex:
row = index.row()
col = index.column()
return str(self.df.columns.values[col][row])
else: # Not MultiIndex
col = index.column()
return str(self.df.columns.values[col])
# A simple example of some way this model might modify its data
def delete_first_column(self):
self.beginResetModel()
self.df = self.df.drop(self.df.columns[0], axis=1)
self.endResetModel()
class HeaderView(QtWidgets.QTableView):
def __init__(self, df):
super().__init__()
self.setModel(HeaderModel(df))
self.clicked.connect(self.model().delete_first_column)
self.horizontalHeader().hide()
self.verticalHeader().hide()
self.setFixedHeight(115)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
app.setStyle('Windows XP')
tuples = [('A', 'one', 'x'), ('A', 'one', 'y'), ('A', 'two', 'x'), ('A', 'two', 'y'),
('B', 'one', 'x'), ('B', 'one', 'y'), ('B', 'two', 'x'), ('B', 'two', 'y')]
columns = pd.MultiIndex.from_tuples(tuples, names=['first', 'second', 'third'])
multidf = pd.DataFrame(np.arange(40).reshape(5,8), columns=columns[:8])
container = QtWidgets.QWidget()
layout = QtWidgets.QVBoxLayout()
container.setLayout(layout)
header = HeaderView(multidf)
data = DataTableView(multidf)
layout.addWidget(header)
layout.addWidget(data)
print(header.model().df is data.model().df)
container.show()
sys.exit(app.exec_())
【问题讨论】:
标签: python pandas pyqt pyqt5 qtableview