从 Pandas 数据框填充 QTableView 的最快方法
Posted
技术标签:
【中文标题】从 Pandas 数据框填充 QTableView 的最快方法【英文标题】:Fastest way to populate QTableView from Pandas data frame 【发布时间】:2015-07-17 12:33:27 【问题描述】:我是 PyQt 的新手,我正在努力填充 QTableView 控件。
我的代码如下:
def data_frame_to_ui(self, data_frame):
"""
Displays a pandas data frame into the GUI
"""
list_model = QtGui.QStandardItemModel()
i = 0
for val in data_frame.columns:
# for the list model
if i > 0:
item = QtGui.QStandardItem(val)
#item.setCheckable(True)
item.setEditable(False)
list_model.appendRow(item)
i += 1
self.ui.profilesListView.setModel(list_model)
# for the table model
table_model = QtGui.QStandardItemModel()
# set table headers
table_model.setColumnCount(data_frame.columns.size)
table_model.setHorizontalHeaderLabels(data_frame.columns.tolist())
self.ui.profileTableView.horizontalHeader().setStretchLastSection(True)
# fill table model data
for row_idx in range(10): #len(data_frame.values)
row = list()
for col_idx in range(data_frame.columns.size):
val = QtGui.QStandardItem(str(data_frame.values[row_idx][col_idx]))
row.append(val)
table_model.appendRow(row)
# set table model to table object
self.ui.profileTableView.setModel(table_model)
实际上在代码中我成功填充了一个 QListView,但是我设置给 QTableView 的值没有显示,你也可以看到我将行截断为 10,因为它需要永远显示数百行数据框架。
那么,从 pandas 数据框填充表模型的最快方法是什么?
提前致谢。
【问题讨论】:
您是否测试过 Wolph 提供的解决方案,看看它是否提供了更好的性能? 还没有,我也不太明白,所以需要一些时间。 我做了一些测试。对于 25 列 10000 行的表,自定义模型大约快 40 倍(并且性能差异随着行/列数的增加呈几何级数增长)。这是对数据使用简单的列表列表,因此创建QStandardItem
的所有这些实例似乎是主要瓶颈。
不确定这是否有帮助,但熊猫曾经有一个 pyqt 模型。看起来它现在已经被拆分到另一个项目中,所以你可能想查看pandas-qt。不知道性能如何。
@ekhumoro,您介意发布您的代码吗?我得到的课程是:return QtCore.QVariant() TypeError: PyQt4.QtCore.QVariant represents a mapped type and cannot be instantiated
【参考方案1】:
就我个人而言,我只是创建自己的模型类以使其更容易处理。
例如:
import sys
from PyQt4 import QtCore, QtGui
Qt = QtCore.Qt
class PandasModel(QtCore.QAbstractTableModel):
def __init__(self, data, parent=None):
QtCore.QAbstractTableModel.__init__(self, parent)
self._data = data
def rowCount(self, parent=None):
return len(self._data.values)
def columnCount(self, parent=None):
return self._data.columns.size
def data(self, index, role=Qt.DisplayRole):
if index.isValid():
if role == Qt.DisplayRole:
return QtCore.QVariant(str(
self._data.iloc[index.row()][index.column()]))
return QtCore.QVariant()
if __name__ == '__main__':
application = QtGui.QApplication(sys.argv)
view = QtGui.QTableView()
model = PandasModel(your_pandas_data)
view.setModel(model)
view.show()
sys.exit(application.exec_())
【讨论】:
嗨,我收到以下错误:return QtCore.QVariant() TypeError: PyQt4.QtCore.QVariant represents a mapped type and cannot be instantiated
对于新的 pandas 版本,您必须将 self._data.iloc[index.row()][index.column()]))
替换为 self._data.iat[row, col]
【参考方案2】:
这行得通:
class PandasModel(QtCore.QAbstractTableModel):
"""
Class to populate a table view with a pandas dataframe
"""
def __init__(self, data, parent=None):
QtCore.QAbstractTableModel.__init__(self, parent)
self._data = data
def rowCount(self, parent=None):
return len(self._data.values)
def columnCount(self, parent=None):
return self._data.columns.size
def data(self, index, role=QtCore.Qt.DisplayRole):
if index.isValid():
if role == QtCore.Qt.DisplayRole:
return str(self._data.iloc[index.row()][index.column()])
return None
def headerData(self, col, orientation, role):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return self._data.columns[col]
return None
像这样使用它:
model = PandasModel(your_pandas_data_frame)
your_tableview.setModel(model)
我从 PyQT 4.6 开始阅读 here 以避免 QVariant()
。
【讨论】:
旧响应,但仍然很好。如果希望数据框的索引出现在行中,可以修改方法 headerData 如下:def headerData(self, rowcol, orientation, role): if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole: return self._data.columns[rowcol] if orientation == QtCore.Qt.Vertical and role == QtCore.Qt.DisplayRole: return self._data.index[rowcol] return None
这行得通,但我怎样才能使模型可编辑,然后将其移回数据框?目前它甚至无法编辑。【参考方案3】:
我发现对于 1000 多行的 DataFrame,所有建议的答案都非常缓慢。对我有用的东西非常快:
class PandasModel(QtCore.QAbstractTableModel):
"""
Class to populate a table view with a pandas dataframe
"""
def __init__(self, data, parent=None):
QtCore.QAbstractTableModel.__init__(self, parent)
self._data = data
def rowCount(self, parent=None):
return self._data.shape[0]
def columnCount(self, parent=None):
return self._data.shape[1]
def data(self, index, role=QtCore.Qt.DisplayRole):
if index.isValid():
if role == QtCore.Qt.DisplayRole:
return str(self._data.iloc[index.row(), index.column()])
return None
def headerData(self, col, orientation, role):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return self._data.columns[col]
return None
【讨论】:
【参考方案4】:除了使用QtCore.QAbstractTableModel,还可以继承QtGui.QStandardItemModel。我发现这种方式更容易支持从 QTableView 发出的 handleChanged 事件。
from PyQt5 import QtCore, QtGui
class PandasModel(QtGui.QStandardItemModel):
def __init__(self, data, parent=None):
QtGui.QStandardItemModel.__init__(self, parent)
self._data = data
for row in data.values.tolist():
data_row = [ QtGui.QStandardItem("0:.6f".format(x)) for x in row ]
self.appendRow(data_row)
return
def rowCount(self, parent=None):
return len(self._data.values)
def columnCount(self, parent=None):
return self._data.columns.size
def headerData(self, x, orientation, role):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return self._data.columns[x]
if orientation == QtCore.Qt.Vertical and role == QtCore.Qt.DisplayRole:
return self._data.index[x]
return None
【讨论】:
以上在 PyQt5 上完美运行。感谢@Frederick Li 的帖子-尽管我确实修改了data_row
行以仅将值作为字符串输入,但除此之外,它将我的加载时间从可能一分钟或更长时间缩短到几秒钟.【参考方案5】:
pandas
中其实有一些代码支持与Qt的集成。
在撰写此答案时,最新的 pandas 版本是 0.18.1
,您可以这样做:
from pandas.sandbox.qtpandas import DataFrameModel, DataFrameWidget
该代码似乎与 PySide 耦合,但使其与 PyQt 一起使用应该相对简单。此外,该代码已被弃用,警告称该模块将来会被删除。
幸运的是,他们将其提取到 GitHub 中名为 pandas-qt
的单独项目中:
https://github.com/datalyze-solutions/pandas-qt
在尝试推出我自己的模型和视图实现之前,我会尝试使用它。
【讨论】:
您好,我只想补充一下,pandas-qt 不支持 python3,而且看起来可能不支持。同时你可以使用qtpandaspip install qtpandas
【参考方案6】:
这是 PyQT5的完整复制粘贴示例 基于@Frederick Li 的回答,稍作修改。
from PyQt5 import QtGui, QtWidgets
from PyQt5.QtCore import Qt
import sys
import pandas as pd
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, obj=None, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.centralwidget = QtWidgets.QWidget(self)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed)
self.centralwidget.setSizePolicy(sizePolicy)
self.pdtable = QtWidgets.QTableView(self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed)
self.pdtable.setSizePolicy(sizePolicy)
dataPD = [['tom', 10.0, 180.3], ['nick', 15.0, 175.7], ['juli', 14.0, 160.6]]
df = pd.DataFrame(dataPD, columns=['Name', 'Age', 'Height'])
print(df.dtypes)
self.model = PandasTableModel(df)
self.pdtable.setModel(self.model)
self.setCentralWidget(self.centralwidget)
class PandasTableModel(QtGui.QStandardItemModel):
def __init__(self, data, parent=None):
QtGui.QStandardItemModel.__init__(self, parent)
self._data = data
for col in data.columns:
data_col = [QtGui.QStandardItem("".format(x)) for x in data[col].values]
self.appendColumn(data_col)
return
def rowCount(self, parent=None):
return len(self._data.values)
def columnCount(self, parent=None):
return self._data.columns.size
def headerData(self, x, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self._data.columns[x]
if orientation == Qt.Vertical and role == Qt.DisplayRole:
return self._data.index[x]
return None
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
app.setStyle("Fusion")
main = MainWindow()
main.show()
main.resize(600, 400)
sys.exit(app.exec_())
【讨论】:
QT 网站有一个 Pandas 简单示例:doc.qt.io/qtforpython/examples/example_external__pandas.html【参考方案7】:将数据框写入 QtableWidget 的简单快捷方式
# Takes a df and writes it to a qtable provided. df headers become qtable headers
@staticmethod
def write_df_to_qtable(df,table):
headers = list(df)
table.setRowCount(df.shape[0])
table.setColumnCount(df.shape[1])
table.setHorizontalHeaderLabels(headers)
# getting data from df is computationally costly so convert it to array first
df_array = df.values
for row in range(df.shape[0]):
for col in range(df.shape[1]):
table.setItem(row, col, QtGui.QTableWidgetItem(str(df_array[row,col])))
【讨论】:
以上是关于从 Pandas 数据框填充 QTableView 的最快方法的主要内容,如果未能解决你的问题,请参考以下文章
可编辑 QTableView 中的 Pandas df:删除复选框
python postgresql从pandas数据框创建数据库并填充表