#python #pandas #pyqt #pyqt5 #qtableview
#питон #pandas #pyqt ( пикт ) #pyqt5 #qtableview ( просмотр таблицы ) #python #pyqt #qtableview
Вопрос:
У меня есть классы, определенные в приведенном ниже коде, для отображения фрейма данных Pandas (структура данных для представления 2D-таблиц) с мультизаголовком (заголовок с несколькими уровнями), подобный этому:
Как могут выглядеть данные в Excel:
Я делаю это с помощью двух QTableViews, одного для данных в самом фрейме данных и одного для меток с несколькими заголовками. Однако я хотел бы иметь возможность хранить фрейм данных в одной модели и подключать к нему эти несколько QTableViews. В идеале я мог бы передать дополнительный аргумент методу data() из представления, указывающий, предназначено ли представление для заголовка или тела, но я не думаю, что это возможно?
По некоторым причинам я хочу объединить их в единую модель…
- Фрейм данных не синхронизируется между заголовком и телом. Другими словами,
header.model().df is data.model().df
имеет значение True для запуска, но False послеdelete_first_column
вызова иself.df
перезаписи - Структурно это имеет больше смысла, поскольку фрейм данных — это единый объект
- Эта текущая структура требует дублирования кода и взаимодействия между двумя моделями, например, функция delete_first_column() должна применяться как к телу, так и к заголовку, но как есть она применима только к модели, в которой она находится.
Как я могу реорганизовать этот код, чтобы представления были подключены только к одной модели для одного фрейма данных?
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 amp; 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_())
Ответ №1:
Я не уверен, что могу однозначно ответить на этот вопрос, но я бы попытался сохранить две модели и два представления, которые у вас уже есть, затем создать новую модель и представление, которые действуют в унифицированных интерфейсах. На самом деле они были бы просто оболочками поверх ваших существующих моделей и представлений. Итак, если кто-то вызывает delete_first_column() в вашем классе-оболочке, он обрабатывает детали передачи этого как в базовое тело, так и в заголовок, поддерживая их синхронизацию.
Если вы любите приключения, вы можете использовать QTableView.setSpan(), чтобы создать вид объединенных столбцов в вашем представлении.