PyQt5 TableView kills kernal - python

So, Im currently taking just limited data from a ZeroMQ Messaging system appending it all to a Dataframe and then turning the Dataframe into a pandas model with code I acquired from someone on stack. Then running that model through a PyQt5 Tableview.
Every time I run the tableview code, after converting my Dataframe to a model for Tableview the kernal just dies. I tried to at least handle a exception but it wont even raise a runtimeerror that is super general. It just dies every time....
For the purpose of the test im using a CSV with data to try to get this into a workable model. You can use any csv or data you have on your end to test this as theres no hard coded formatting.
from PyQt5 import QtCore, QtGui, QtWidgets
import pandas as pd
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()
def createview(title, model):
try:
view = QtWidgets.QTableView()
view.setWindowview
except:
raise RuntimeError("I know python!")
if __name__=="__main__":
df=pd.read_csv("C:\Excel Sheets\Test_CSV_6-18-18.csv")
model = PandasModel(df)
createview("Model", model)

Related

Slow PyQt5 QAbstractTableModel for Pandas Model with qTableview

i am using PandasModel(QAbstractTableModel) to show data in a qtableView. it's working fine with less data. but whenever i tried with large datas it became slow. for 5000 rows data it almost took 20-25 seconds .it just take a seconds in mssql mgmt studio to run the query. i am not getting where i did wrong with my codes.
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()[section]+1
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.iloc[index.row(), index.column()]))
def setData(self, index, value, role):
if not index.isValid():
return False
if role != QtCore.Qt.EditRole:
return False
row = index.row()
if row < 0 or row >= len(self._data.values):
return False
column = index.column()
if column < 0 or column >= self._data.columns.size:
return False
self._data.values[row][column] = value
self.dataChanged.emit(index, index)
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()
i tried many approaches like st the chunksize , but not working. please help me out
sql_conn = pyodbc.connect('DRIVER={ODBC Driver 17 for SQL Server};SERVER=' +
server+';DATABASE='+database+';UID='+username+';PWD=' + password)
cursor = sql_conn.cursor()
def loadTranForACC(AccountID):
query_string="Select * from user.dbo.Transaction(nolock) where accID =" + AccountID
data=pd.read_sql_query(query_string, sql_conn)
return data
Since you are working with the data from Database, I'd like to point out that PyQt has
QSqlTableModel and QSqlQueryModel to work with the data from database.
Looking at your model, to me, the model looks fine, try to see exactly what part of your program is eating up time.
You can look at these threads for more information and details regarding these:
qtablewidget becomes slow for large tables
pyqt qtablewidget extremely slow
The ultimate solution though would be to use pagination, show only 500,1000 rows at a time, it will run within a fraction of seconds.
You can visit these threads to implement pagination: QT Forum | Pagination in QTableWidget or Paginated Display of Table Data in PyQt

pyqt5 comboBox - get the associate value for selected item

i want to set a comboBox and show my table's column1 data as items and associated value column2 is for selected item id and want to setText in a qLabel.
i am using a model to view the items in comboBox and working fine. i can get the currentText value but how to get the associated value for the items.
in my case :
my sql table like:
type id
------------
DIV 2
TRANS 33
FEE 41
EXP 89
now , i can set the column1(type) values into comboBox successfully. now, if user selected value 'FEE' , than qlable should be updated as its associate id : 41. how to do that!
df=loadData()
model=PandasModel(df)
self.comboBox.setModel(model)
self.comboBox.setModelColumn(0)
content=self.comboBox.currentText()
self.label.setText(content) # content should be ID instead of currentText
pandasMode:
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()[section]+1
except (IndexError, ):
return QtCore.QVariant()
def data(self, index, role=QtCore.Qt.DisplayRole):
if index.isValid():
if role == QtCore.Qt.DisplayRole:
return str(self._df.iloc[index.row(), index.column()])
return None
def setData(self, index, value, role):
if not index.isValid():
return False
if role != QtCore.Qt.EditRole:
return False
row = index.row()
if row < 0 or row >= len(self._df.values):
return False
column = index.column()
if column < 0 or column >= self._df.columns.size:
return False
self._df.values[row][column] = value
self.dataChanged.emit(index, index)
return True
# def rowCount(self, parent=QtCore.QModelIndex()):
# return len(self._df.index)
def rowCount(self, parent=None):
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()
Assuming that the model stores the id in the second column then it has to obtain the associated index:
ID_COLUMN = 1
index = self.comboBox.model().index(
self.comboBox.currentIndex(), ID_COLUMN, self.comboBox.rootModelIndex()
)
id_ = index.data()
print(id_)
self.label.setText(id_)

background color of cells in QTableview, PySide2

Is it possible to conditionally change the background color of items in a QTableView, using PySide2?
I've read a lot on the model view framework . I cannot figure out if it is necessary to use a Delegate or not. Recently I was able to get a column of checkboxes without a Delegate. I believe that the virtual methods setItemData(index, roles) and itemData(index) could be what I need. However, there is no QMap in PySide2. My model must need somewhere to store the extra information to be used by QtCore.Qt.BackgroundRole (that enum, btw, says "the background brush used for items rendered with the default delegate") If I don't specify a delegate, is the "default delegate" used?. Should I be using QStandardItemModel instead?
In the example code below, how would I get a particular column's background color to be red based on some thresholds (the min and max column are the thresholds?
from PySide2.QtWidgets import (QWidget, QApplication, QTableView,QVBoxLayout)
import sys
from PandasModel2 import PandasModel2
import numpy as np
import pandas as pd
class Example(QWidget):
def __init__(self):
super().__init__()
self.setGeometry(300, 300, 700, 300)
self.setWindowTitle("QTableView")
self.initData()
self.initUI()
def initData(self):
data = pd.DataFrame(np.random.randint(1,10,size=(6,4)), columns=['Test#','MIN', 'MAX','MEASURED'])
data['Test#'] = [1,2,3,4,5,6]
#add the checkable column to the DataFrame
data['Check'] = True
self.model = PandasModel2(data)
def initUI(self):
self.tv = QTableView(self)
self.tv.setModel(self.model)
vbox = QVBoxLayout()
vbox.addWidget(self.tv)
self.setLayout(vbox)
app = QApplication([])
ex = Example()
ex.show()
sys.exit(app.exec_())
And I have a custom model using a pandas dataFrame:
import PySide2.QtCore as QtCore
class PandasModel2(QtCore.QAbstractTableModel):
"""
Class to populate a table view with a pandas dataframe.
This model is non-hierachical.
"""
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 role==QtCore.Qt.DisplayRole:
if index.column() != 4:
#don't want what determines check state to be shown as a string
if index.isValid():
if index.column() in [1,2,3]:
return '{:.3f}'.format(self._data.iloc[index.row(), index.column()])
if index.column() == 0:
return '{:.2f}'.format(self._data.iloc[index.row(), index.column()])
return str(self._data.iloc[index.row(), index.column()])
if role==QtCore.Qt.CheckStateRole:
if index.column()==4:#had to add this check to get the check boxes only in column 10
if self._data.iloc[index.row(), index.column()] == True:
return QtCore.Qt.Checked
else:
return QtCore.Qt.Unchecked
def getMinimum(self, row):
return self._data.iloc[row, self.getColumnNumber('MIN')]
def getMaximum(self, row):
return self._data.iloc[row, self.getColumnNumber('MAX')]
def getColumnNumber(self, string):
'''
Given a string that identifies a label/column,
return the location of that label/column.
This enables the config file columns to be moved around.
'''
return self._data.columns.get_loc(string)
def headerData(self, col, orientation, role):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return self._data.columns[col]
return None
def flags(self, index):
'''
The returned enums indicate which columns are editable, selectable,
checkable, etc.
The index is a QModelIndex.
'''
if index.column() == self.getColumnNumber('Check'):
#print(index.column())
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsUserCheckable
else:
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable
return QtCore.Qt.ItemIsEnabled
def setData(self, index, value, role=QtCore.Qt.DisplayRole):
"""Set the value to the index position depending on Qt::ItemDataRole and data type of the column
Args:
index (QtCore.QModelIndex): Index to define column and row.
value (object): new value.
role (Qt::ItemDataRole): Use this role to specify what you want to do.
Raises:
TypeError: If the value could not be converted to a known datatype.
Returns:
True if value is changed. Calls layoutChanged after update.
False if value is not different from original value.
"""
if not index.isValid():
return False
if role == QtCore.Qt.DisplayRole: #why not edit role?
self._data.iat[index.row(),index.column()]= value
self.layoutChanged.emit()
return True
elif role == (QtCore.Qt.CheckStateRole | QtCore.Qt.DisplayRole):
#this block does get executed when toggling the check boxes,
#verified with debugger. Although the action is the same
#as the block above!
self._data.iat[index.row(),index.column()]= value
self.layoutChanged.emit()
return True
else:
return False
The delegate by default uses the BackgroundRole information if it is available so the solution is just to return a QColor, QBrush or similar.
from PySide2 import QtCore, QtGui
class PandasModel2(QtCore.QAbstractTableModel):
# ...
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid():
return
if not (0 <= index.row() < self.rowCount() and 0 <= index.column() <= self.columnCount()):
return
value = self._data.iloc[index.row(), index.column()]
if role == QtCore.Qt.DisplayRole:
if index.column() != 4:
if index.column() in [1,2,3]:
return '{:.3f}'.format(value)
if index.column() == 0:
return '{:.2f}'.format(value)
return str(value)
elif role == QtCore.Qt.CheckStateRole:
if index.column() == 4:
return QtCore.Qt.Checked if value else QtCore.Qt.Unchecked
elif index.column() == self.getColumnNumber('MEASURED'):
if role == QtCore.Qt.BackgroundRole:
if self.getMinimum(index.row()) <= value <= self.getMaximum(index.row()):
return QtGui.QColor("red")

QTableView unexpectedly shifts left after inserting row to data using pandas

----How The Problem Looks Like----
after I clicked on the Record button, it becomes this
As you can see, the table just shifted to the left, hiding the vertical headers.
----Related Codes----
My tableivew is called tableViewTransaction, it uses a model to link to data in pandas' dataframe format. Here is how I connected them together inside the __init__ function of a QMainWindow.
self.data = pd.read_csv('transactions.txt', sep='\t', header=None)
self.data.columns = ["Name", "Price", "Action"]
self.model = PandasModel(self.data)
self.tableViewTransaction.setModel(self.model)
Here is my Model for the TableView
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.layoutChanged.emit()
self._df.sort_values(colname, ascending= order == QtCore.Qt.AscendingOrder, inplace=True)
self._df.reset_index(inplace=True, drop=True)
self.layoutChanged.emit()
When the Record button is pressed, the following function is called:
def recordTransaction(self):
name = self.comboBoxStock.currentText()
price = self.lineEditMoney.text()
action = self.comboBoxAction.currentText()
self.data.loc[len(self.data.index)] = [name, price, action]
self.tableViewTransaction.model().layoutChanged.emit()
I know that name, price, action here is storing the correct information i.e. "stock1", "2", "Buy" in my above example.
----Full Code----
https://drive.google.com/file/d/1rU66yLqlQu0bTdINkSWxJe2E_OdMtS0i/view?usp=sharing
How to use:
first unzip it, then use python3 to run StockSim.py.
On a mac, just run python3 StockSim.py when you have went to the Stock Sim directory using terminal. Make sure you have python3 and pyqt5 installed first.
----------------------
All I want is that the TableView not shift to the left, any of your help would be very much appreciated!

Editable QTableView and Pandas do not work properly

I am trying to get a self-contained piece of example code for using pandas and QTableView while getting editable cell views.
For this I am following up to an earlier discussion:
Pandas df in editable QTableView: remove check boxes
While the answer and proposed modifications in that other discussion help to get rid of the checkboxes, the code discussed there is still not working for me (python 2.7).
When I modify a cell using the code below, the content shown in the cell is: PtQt4.PtCore.QtVariant object at ...
The package versions I use are:
Pandas: 0.20.2
Pyside 1.2.4
Qt version: 4.8.4
SIP version: 4.14.4
PyQt version: 4.10
import sys
from PyQt4 import QtCore, QtGui
import pandas as pd
Qt = QtCore.Qt
class PandasModelEditable(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=QtCore.Qt.DisplayRole):
if index.isValid():
if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
return unicode(self._data.iloc[index.row(), index.column()])
return None
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if role != QtCore.Qt.DisplayRole:
return None
if orientation == QtCore.Qt.Horizontal:
try:
return '%s' % unicode(self._data.columns.tolist()[section])
except (IndexError,):
return unicode()
elif orientation == QtCore.Qt.Vertical:
try:
return '%s' % unicode(self._data.index.tolist()[section])
except (IndexError,):
return unicode()
def flags(self, index):
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | \
QtCore.Qt.ItemIsEditable
def setData(self, index, value, role=QtCore.Qt.EditRole):
if index.isValid():
self._data.iloc[index.row(), index.column()] = value
if self.data(index, QtCore.Qt.DisplayRole) == value:
self.dataChanged.emit(index, index)
return True
return False
if __name__ == '__main__':
application = QtGui.QApplication(sys.argv)
view = QtGui.QTableView()
df = pd.DataFrame([[1, 2, 3], [4, 5, 6]], columns=['a', 'b', 'c'], index=['x', 'y'])
model = PandasModelEditable(df)
view.setModel(model)
view.show()
sys.exit(application.exec_())
The immediate problem is caused by passing an unconverted QVariant object to the underlying database. The simplest fix is convert it to a python object, like this:
self._data.iloc[index.row(), index.column()] = value.toPyObject()
However, this doesn't really deal with the most fundamental problem with the code, which is that you are using such old versions of Python and PyQt. Qt does not officially support Qt4 any more, and it won't be long before the same is true for Python and Python2. Strictly speaking, PyQt4 is already obsolete legacy code - so you shouldn't be using it for new projects unless you have a really good reason for doing that (e.g. backwards compatibilty).
If you can, I would strongly recommend that you port your code to Python3/PyQt5 as soon as possible, as it will save you a lot of hassle in the medium to long term. However, if you cannot do this for some reason, and you want to continue using Python2/PyQt4, you can get the same behaviour as PySide by adding the following to the beginning of your program:
import sip
sip.setapi('QString', 2)
sip.setapi('QVariant', 2)
from PyQt4 import QtCore, QtGui
After doing this, PyQt will automatically convert all QString and QVariant objects to ordinary python data types, so you will never need to do any explicit conversions (i.e. you can remove all those unicode() and toPyObject() calls in your code).
Alternatively, you could also use Python3 with PyQt4, which has the same behaviour as PySide by default (so the setapi stuff would not be needed).
It seems to work when I switch to PySide instead of PyQt4:
import sys
from PySide import QtCore, QtGui
import pandas as pd
Qt = QtCore.Qt
class PandasModelEditable(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=QtCore.Qt.DisplayRole):
if index.isValid():
if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
return unicode(self._data.iloc[index.row(), index.column()])
return None
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if role != QtCore.Qt.DisplayRole:
return None
if orientation == QtCore.Qt.Horizontal:
try:
return '%s' % unicode(self._data.columns.tolist()[section])
except (IndexError,):
return unicode()
elif orientation == QtCore.Qt.Vertical:
try:
return '%s' % unicode(self._data.index.tolist()[section])
except (IndexError,):
return unicode()
def flags(self, index):
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | \
QtCore.Qt.ItemIsEditable
def setData(self, index, value, role=QtCore.Qt.EditRole):
if index.isValid():
self._data.iloc[index.row(), index.column()] = value
if self.data(index, QtCore.Qt.DisplayRole) == value:
self.dataChanged.emit(index, index)
return True
return False
if __name__ == '__main__':
application = QtGui.QApplication(sys.argv)
view = QtGui.QTableView()
df = pd.DataFrame([[1, 2, 3], [4, 5, 6]], columns=['a', 'b', 'c'], index=['x', 'y'])
model = PandasModelEditable(df)
view.setModel(model)
view.show()
sys.exit(application.exec_())

Categories