У меня есть классы, определенные в приведенном ниже коде для отображения Pandas DataFrame (структура данных для представления 2D-таблиц) с MultiHeader (заголовок с несколькими уровнями), например:
Как могут выглядеть данные в Excel:
Я делаю это с двумя QTableView, один для данных в самом DataFrame и один для меток MultiHeader. Однако я хотел бы иметь возможность хранить DataFrame в одной модели и подключать к нему эти несколько QTableView. В идеале я мог бы передать дополнительный аргумент методу data () из представления, указывающего, является ли представление заголовком или телом, но я не думаю, что это возможно?
Некоторые причины, по которым я хочу объединить их в одну модель ...
- DataFrame не синхронизируется между заголовком и телом. Другими словами,
header.model().df is data.model().df
имеет значение True для начала, но False после вызова delete_first_column
и self.df
перезаписывается
- Конструктивно это имеет больше смысла, поскольку 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_())