├── __init__.py
├── dialogs
├── __init__.py
├── DescribeDialog.py
├── ExcelFileDialog.py
├── GroupByDialog.py
├── MergeDataFrameDialog.py
├── EditDialogs.py
└── GraphFormatDialog.py
├── models
├── __init__.py
├── SupportedDtypes.py
└── DataFrameTableModel.py
├── images
├── icon.png
├── logo.png
├── next.png
├── note.png
├── up.png
├── folder.png
├── graph.png
├── group.png
├── merge.png
├── describe.png
├── editadd.png
├── editcopy.png
├── editcut.png
├── editedit.png
├── editpaste.png
├── filenew.png
├── fileopen.png
├── filequit.png
├── filesave.png
├── noteNew.png
├── noteOpen.png
├── previous.png
├── terminate.png
├── editdelete.png
├── editindent.png
├── filesaveas.png
├── dialog-cancel.png
├── document-edit.png
├── document-open.png
├── editunindent.png
├── view-refresh.png
├── dialog-ok-apply.png
├── document-save-as.png
├── edit-table-delete-row.png
├── edit-table-delete-column.png
├── edit-table-insert-row-below.png
└── edit-table-insert-column-right.png
├── screenshots
├── Graph_Dialog.png
├── GroupBy_Dialog.png
├── Merge_Dialog.png
├── dfViewer_main.png
└── Describe_Dialog.png
├── README.md
├── resources.qrc
└── dataFrameViewer.py
/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/dialogs/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/models/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/icon.png
--------------------------------------------------------------------------------
/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/logo.png
--------------------------------------------------------------------------------
/images/next.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/next.png
--------------------------------------------------------------------------------
/images/note.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/note.png
--------------------------------------------------------------------------------
/images/up.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/up.png
--------------------------------------------------------------------------------
/images/folder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/folder.png
--------------------------------------------------------------------------------
/images/graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/graph.png
--------------------------------------------------------------------------------
/images/group.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/group.png
--------------------------------------------------------------------------------
/images/merge.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/merge.png
--------------------------------------------------------------------------------
/images/describe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/describe.png
--------------------------------------------------------------------------------
/images/editadd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/editadd.png
--------------------------------------------------------------------------------
/images/editcopy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/editcopy.png
--------------------------------------------------------------------------------
/images/editcut.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/editcut.png
--------------------------------------------------------------------------------
/images/editedit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/editedit.png
--------------------------------------------------------------------------------
/images/editpaste.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/editpaste.png
--------------------------------------------------------------------------------
/images/filenew.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/filenew.png
--------------------------------------------------------------------------------
/images/fileopen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/fileopen.png
--------------------------------------------------------------------------------
/images/filequit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/filequit.png
--------------------------------------------------------------------------------
/images/filesave.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/filesave.png
--------------------------------------------------------------------------------
/images/noteNew.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/noteNew.png
--------------------------------------------------------------------------------
/images/noteOpen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/noteOpen.png
--------------------------------------------------------------------------------
/images/previous.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/previous.png
--------------------------------------------------------------------------------
/images/terminate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/terminate.png
--------------------------------------------------------------------------------
/images/editdelete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/editdelete.png
--------------------------------------------------------------------------------
/images/editindent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/editindent.png
--------------------------------------------------------------------------------
/images/filesaveas.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/filesaveas.png
--------------------------------------------------------------------------------
/images/dialog-cancel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/dialog-cancel.png
--------------------------------------------------------------------------------
/images/document-edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/document-edit.png
--------------------------------------------------------------------------------
/images/document-open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/document-open.png
--------------------------------------------------------------------------------
/images/editunindent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/editunindent.png
--------------------------------------------------------------------------------
/images/view-refresh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/view-refresh.png
--------------------------------------------------------------------------------
/images/dialog-ok-apply.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/dialog-ok-apply.png
--------------------------------------------------------------------------------
/images/document-save-as.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/document-save-as.png
--------------------------------------------------------------------------------
/screenshots/Graph_Dialog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/screenshots/Graph_Dialog.png
--------------------------------------------------------------------------------
/screenshots/GroupBy_Dialog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/screenshots/GroupBy_Dialog.png
--------------------------------------------------------------------------------
/screenshots/Merge_Dialog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/screenshots/Merge_Dialog.png
--------------------------------------------------------------------------------
/screenshots/dfViewer_main.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/screenshots/dfViewer_main.png
--------------------------------------------------------------------------------
/images/edit-table-delete-row.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/edit-table-delete-row.png
--------------------------------------------------------------------------------
/screenshots/Describe_Dialog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/screenshots/Describe_Dialog.png
--------------------------------------------------------------------------------
/images/edit-table-delete-column.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/edit-table-delete-column.png
--------------------------------------------------------------------------------
/images/edit-table-insert-row-below.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/edit-table-insert-row-below.png
--------------------------------------------------------------------------------
/images/edit-table-insert-column-right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sterry24/dfViewer/HEAD/images/edit-table-insert-column-right.png
--------------------------------------------------------------------------------
/dialogs/DescribeDialog.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on Wed Feb 03 14:53:28 2016
4 |
5 | @author: sterry
6 | """
7 |
8 | from PyQt4.QtCore import *
9 | try:
10 | from PyQt4.QtCore import QString
11 | except:
12 | QString = str
13 | from PyQt4.QtCore import pyqtSlot as Slot
14 | from PyQt4.QtCore import pyqtSignal as Signal
15 | from PyQt4.QtGui import *
16 | from models.DataFrameTableModel import DataFrameTableModel
17 |
18 | class DescribeDialog(QDialog):
19 |
20 | def __init__(self,data,parent=None):
21 | super(DescribeDialog,self).__init__(parent)
22 | self.setAttribute(Qt.WA_DeleteOnClose)
23 | self.setModal(True)
24 | self.data=data
25 | self.initUI()
26 |
27 | def initUI(self):
28 |
29 | self.gridLayout = QGridLayout()
30 | self.table=QTableView()
31 | self.model=DataFrameTableModel()
32 | self.table.setModel(self.model)
33 | self.model.setDataFrame(self.data)
34 | self.buttonBox=QDialogButtonBox(QDialogButtonBox.Ok)
35 | self.buttonBox.clicked.connect(self.accept)
36 |
37 | self.gridLayout.addWidget(self.table,0,0)
38 | self.gridLayout.addWidget(self.buttonBox,1,0)
39 |
40 | self.setLayout(self.gridLayout)
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # dfViewer
2 | A cross-platform PyQt GUI for visualizing pandas dataframes in table format. Implements some pandas operations,
3 | and includes a GUI for basic plotting with matplotlib.
4 |
5 | #### Features:
6 | Currently reads files of the following formats:
7 | * Excel
8 | * comma delimited or white-space delimited
9 |
10 | Built-in Graph Generator for plotting data
11 | Merge tool for merging multiple dataframes
12 | Group-by tool for selecting subsets of data
13 |
14 |
15 | #### Requirements:
16 | Python 3.x
17 | pandas
18 | numpy
19 | matplotlib
20 | PyQt4
21 | Or the Anaconda3 distribution
22 |
23 | #### Known Issues:
24 | Currently an issue with libpng and at least of of the images used in the tool bar.
25 | libpng warning: iCCP: known incorrect sRGB profile
26 | libpng warning: cHRM: inconsistent chromaticities
27 | libpng warning: iCCP: known incorrect sRGB profile
28 | libpng warning: cHRM: inconsistent chromaticities
29 |
30 |
31 | #### Screenshots
32 | Main Window
33 | 
34 | pandas describe function
35 | 
36 | Graph dialog
37 | 
38 | Group-by dialog
39 | 
40 | Merge dialog
41 | 
42 |
--------------------------------------------------------------------------------
/resources.qrc:
--------------------------------------------------------------------------------
1 |
2 |
3 | images/logo.png
4 | images/icon.png
5 | images/filenew.png
6 | images/fileopen.png
7 | images/filesave.png
8 | images/filesaveas.png
9 | images/filequit.png
10 | images/editcut.png
11 | images/editcopy.png
12 | images/editpaste.png
13 | images/editadd.png
14 | images/editedit.png
15 | images/editdelete.png
16 | images/editindent.png
17 | images/editunindent.png
18 | images/folder.png
19 | images/next.png
20 | images/previous.png
21 | images/up.png
22 | images/note.png
23 | images/dialog-cancel.png
24 | images/dialog-ok-apply.png
25 | images/document-edit.png
26 | images/document-open.png
27 | images/document-save-as.png
28 | images/edit-table-delete-column.png
29 | images/edit-table-delete-row.png
30 | images/edit-table-insert-column-right.png
31 | images/edit-table-insert-row-below.png
32 | images/view-refresh.png
33 | images/merge.png
34 | images/terminate.png
35 | images/noteNew.png
36 | images/noteOpen.png
37 | images/graph.png
38 | images/group.png
39 | images/describe.png
40 |
41 |
42 |
--------------------------------------------------------------------------------
/dialogs/ExcelFileDialog.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on Tue Feb 02 09:29:42 2016
4 |
5 | @author: sterry
6 | """
7 |
8 | from PyQt4.QtCore import *
9 | try:
10 | from PyQt4.QtCore import QString
11 | except:
12 | QString = str
13 | from PyQt4.QtCore import pyqtSlot as Slot
14 | from PyQt4.QtCore import pyqtSignal as Signal
15 | from PyQt4.QtGui import *
16 |
17 | class ExcelFileDialog(QDialog):
18 |
19 | accepted = Signal(dict)
20 |
21 | def __init__(self,filename,options,parent=None):
22 | super(ExcelFileDialog,self).__init__(parent)
23 | self.filename=filename
24 | self.sheets=options
25 | self.initUI()
26 |
27 | def initUI(self):
28 | self.setWindowTitle(self.tr("Open Excel File"))
29 | self.setModal(True)
30 | self.resize(366, 274)
31 | sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
32 | self.setSizePolicy(sizePolicy)
33 |
34 | self.gridLayout = QGridLayout(self)
35 |
36 | self.frameLabel=QLabel("Select Excel Sheet(s) to Parse")
37 |
38 | self.listView = QListView(self)
39 |
40 | model = QStandardItemModel()
41 | for sheet in self.sheets:
42 | item = QStandardItem(sheet)
43 | model.appendRow(item)
44 |
45 | self.listView.setModel(model)
46 | self.listView.setSelectionMode(QListView.MultiSelection)
47 |
48 | self.openEach=QCheckBox("Open sheets individually",self)
49 |
50 | self.buttonBox = QDialogButtonBox(self)
51 | self.buttonBox.setOrientation(Qt.Horizontal)
52 | self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok)
53 |
54 | self.frameComboBox=QComboBox()
55 | self.frameComboBox.addItems(self.sheets)
56 | selectButton=QPushButton("Ok")
57 | self.connect(selectButton,SIGNAL("clicked()"),self,SLOT("accept()"))
58 |
59 | self.gridLayout.addWidget(self.frameLabel, 0, 0, 1, 1)
60 | self.gridLayout.addWidget(self.listView, 1, 0, 1, 1)
61 | self.gridLayout.addWidget(self.openEach, 2, 0, 1, 1)
62 | self.gridLayout.addWidget(self.buttonBox, 3, 0, 1, 1)
63 |
64 | self.buttonBox.accepted.connect(self.accept)
65 | self.buttonBox.rejected.connect(self.reject)
66 |
67 | self.setLayout(self.gridLayout)
68 |
69 | def accept(self):
70 | selection = self.listView.selectedIndexes()
71 | names = []
72 | for index in selection:
73 | names.append(index.data(Qt.DisplayRole))
74 |
75 | if self.openEach.isChecked():
76 | openEach = True
77 | else:
78 | openEach = False
79 | options={"file":self.filename,"sheets":names,"openEach":openEach}
80 | super(ExcelFileDialog, self).accept()
81 | self.accepted.emit(options)
--------------------------------------------------------------------------------
/dialogs/GroupByDialog.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on Tue Feb 02 10:07:55 2016
4 |
5 | @author: sterry
6 | """
7 |
8 | from PyQt4.QtCore import *
9 | try:
10 | from PyQt4.QtCore import QString
11 | except:
12 | QString = str
13 | from PyQt4.QtCore import pyqtSlot as Slot
14 | from PyQt4.QtCore import pyqtSignal as Signal
15 | from PyQt4.QtGui import *
16 |
17 |
18 | class GroupByDialog(QDialog):
19 |
20 | changed = Signal(object)
21 |
22 | def __init__(self,options,parent=None):
23 | super(GroupByDialog,self).__init__(parent)
24 | self.setAttribute(Qt.WA_DeleteOnClose)
25 | self.callerModified = False
26 | self.data = options['data']
27 | self.callerIndex=options['idx']
28 | self.initUI()
29 |
30 | def initUI(self):
31 |
32 | self.title = QFileInfo(self.data.model()._filename).fileName()
33 | self.title = self.title + ' Group By Options'
34 | self.setWindowTitle(self.tr(self.title))
35 | sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
36 | self.setSizePolicy(sizePolicy)
37 |
38 | self.gridLayout=QGridLayout()
39 |
40 | self.groupByLabel=QLabel("Group by")
41 | self.groupByList=QListView(self)
42 | #self.groupCombo.addItems(self.df.columns)
43 |
44 | self.groupsLabel=QLabel("Groups")
45 | self.groupsList=QListView(self)
46 | self.addTabCheckBox=QCheckBox("Show Group in New Tab",self)
47 | self.addTabCheckBox.setChecked(True)
48 |
49 | groupByModel = QStandardItemModel()
50 | for col in self.data.model()._df.columns:
51 | item = QStandardItem(col)
52 | groupByModel.appendRow(item)
53 |
54 | self.groupByList.setModel(groupByModel)
55 | self.groupByList.clicked.connect(self.updateGroups)
56 |
57 | self.buttonBox = QDialogButtonBox(self)
58 | self.buttonBox.setOrientation(Qt.Horizontal)
59 | self.buttonBox.setStandardButtons(QDialogButtonBox.Apply | QDialogButtonBox.Close)
60 |
61 | self.buttonBox.button(QDialogButtonBox.Apply).clicked.connect(self.apply)
62 | self.buttonBox.button(QDialogButtonBox.Close).clicked.connect(self.reject)
63 |
64 | self.gridLayout.addWidget(self.groupByLabel,0,0)
65 | self.gridLayout.addWidget(self.groupsLabel,0,1)
66 | self.gridLayout.addWidget(self.groupByList,1,0)
67 | self.gridLayout.addWidget(self.groupsList,1,1)
68 | self.gridLayout.addWidget(self.addTabCheckBox,2,0)
69 | self.gridLayout.addWidget(self.buttonBox,2,1)
70 | self.setLayout(self.gridLayout)
71 |
72 |
73 | def updateGroups(self):
74 |
75 | selection = self.groupByList.selectedIndexes()
76 | for index in selection:
77 | self.field = index.data(Qt.DisplayRole)
78 | self.grouped = self.data.model()._df.groupby(self.field)
79 |
80 | l=[]
81 | for each in self.grouped.groups.keys():
82 | l.append(str(each))
83 |
84 | model = QStandardItemModel()
85 | for group in l:
86 | item = QStandardItem(group)
87 | model.appendRow(item)
88 | self.groupsList.setModel(model)
89 |
90 | def apply(self):
91 |
92 | opts = {}
93 | self.group = None
94 | selection = self.groupsList.selectedIndexes()
95 | for index in selection:
96 | self.group = index.data(Qt.DisplayRole)
97 |
98 | if self.group is None:
99 | QMessageBox.warning(self,"Group By Error","No Group Selected!!\n"
100 | "Please select an item in the Group By and Groups lists.")
101 | return
102 |
103 | if self.group.isdigit():
104 | self.selected=self.grouped.get_group(int(self.group)).copy()
105 |
106 | elif self.group.find('.') == 0:
107 | if self.group[1:].isdigit():
108 | self.selected=self.grouped.get_group(float(self.group)).copy()
109 |
110 | elif self.group.find('.') > 0:
111 | if self.group[:self.group.find('.')].isdigit():
112 | if self.group[self.group.find('.')+1:].isdigit():
113 | self.selected=self.grouped.get_group(float(self.group)).copy()
114 |
115 | else:
116 | self.selected=self.grouped.get_group(self.group)
117 |
118 | if self.addTabCheckBox.isChecked():
119 | opts['newTab']=True
120 | else:
121 | self.callerModified = True
122 | opts['newTab']=False
123 |
124 | name=(QFileInfo(self.data.model()._filename).fileName() + '_groupBy_' +
125 | str(self.field) + '_group_' + str(self.group))
126 |
127 | opts['key'] = "Accept"
128 | opts['name']=name
129 | opts['idx']=self.callerIndex
130 | opts['df']=self.selected
131 |
132 | self.changed.emit(opts)
133 |
134 | def reject(self):
135 |
136 | opts = {}
137 | if self.callerModified:
138 | opts['key'] = "Reject"
139 | opts['name'] = QFileInfo(self.data.model()._filename).fileName()
140 | opts['idx']=self.callerIndex
141 | opts['df']=self.data
142 | self.changed.emit(opts)
143 |
144 | else:
145 | opts['key'] = "Reject"
146 | opts['idx']=self.callerIndex
147 | self.changed.emit(opts)
148 |
149 | QDialog.reject(self)
150 |
--------------------------------------------------------------------------------
/models/SupportedDtypes.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | from PyQt4 import QtCore
3 |
4 | class SupportedDtypesTranslator(QtCore.QObject):
5 | """Represents all supported datatypes and the translations (i18n).
6 |
7 | """
8 | def __init__(self, parent=None):
9 | """Constructs the object with the given parent.
10 |
11 | Args:
12 | parent (QtCore.QObject, optional): Causes the objected to be owned
13 | by `parent` instead of Qt. Defaults to `None`.
14 |
15 | """
16 | super(SupportedDtypesTranslator, self).__init__(parent)
17 |
18 | # we are not supposed to use str objects (str/ dtype('S'))
19 | self._strs = [(np.dtype(object), self.tr('text'))]
20 |
21 | self._ints = [(np.dtype(np.int8), self.tr('small integer (8 bit)')),
22 | (np.dtype(np.int16), self.tr('small integer (16 bit)')),
23 | (np.dtype(np.int32), self.tr('integer (32 bit)')),
24 | (np.dtype(np.int64), self.tr('integer (64 bit)'))]
25 |
26 | self._uints = [(np.dtype(np.uint8), self.tr('unsigned small integer (8 bit)')),
27 | (np.dtype(np.uint16), self.tr('unsigned small integer (16 bit)')),
28 | (np.dtype(np.uint32), self.tr('unsigned integer (32 bit)')),
29 | (np.dtype(np.uint64), self.tr('unsigned integer (64 bit)'))]
30 |
31 | self._floats = [(np.dtype(np.float16), self.tr('floating point number (16 bit)')),
32 | (np.dtype(np.float32), self.tr('floating point number (32 bit)')),
33 | (np.dtype(np.float64), self.tr('floating point number (64 bit)'))]
34 |
35 | self._datetime = [(np.dtype(' Merge Conditions or pandas.DataFrame.merge\n"
153 | "instructions.")
154 | except MergeError as e:
155 | QMessageBox.warning(self,"Merge Error",unicode(e))
156 | return
157 | if self.leftName == self.rightName:
158 | QMessageBox.warning(self,"Merge Error","Left Frame and Right Frame cannot be the same!!\n")
159 | return
160 |
161 | if self.suffixLineEdit_x.text() != '':
162 | if not self.suffixLineEdit_x.text().startswith('_'):
163 | self.merge_options['suffix_x']='_'+self.suffixLineEdit_x.text()
164 | else:
165 | self.merge_options['suffix_x']=self.suffixLineEdit_x.text()
166 | if self.suffixLineEdit_y.text() != '':
167 | if not self.suffixLineEdit_y.text().startswith('_'):
168 | self.merge_options['suffix_y']='_'+self.suffixLineEdit_y.text()
169 | else:
170 | self.merge_options['suffix_y']=self.suffixLineEdit_y.text()
171 |
172 | if self.innerJoinButton.isChecked():
173 | self.merge_options['how']='inner'
174 | elif self.outerJoinButton.isChecked():
175 | self.merge_options['how']='outer'
176 | elif self.leftJoinButton.isChecked():
177 | self.merge_options['how']='left'
178 | elif self.rightJoinButton.isChecked():
179 | self.merge_options['how']='right'
180 | else:
181 | pass
182 | if self.frameCombo.currentText() != '':
183 | self.merge_options['on']=self.frameCombo.currentText()
184 | if self.leftCombo.currentText() != '':
185 | self.merge_options['left_on']=self.leftCombo.currentText()
186 | if self.rightCombo.currentText() != '':
187 | self.merge_options['right_on']=self.rightCombo.currentText()
188 | if self.leftIndexCheck.isChecked():
189 | self.merge_options['left_index']=True
190 | if self.rightIndexCheck.isChecked():
191 | self.merge_options['right_index']=True
192 |
193 | output={"merge_options":self.merge_options,"left":self.options[self.leftName],"right":self.options[self.rightName]}
194 |
195 | super(MergeDataFrameDialog, self).accept()
196 | self.accepted.emit(output)
--------------------------------------------------------------------------------
/dialogs/EditDialogs.py:
--------------------------------------------------------------------------------
1 | import re
2 | from pandas import Timestamp
3 |
4 | from PyQt4.QtCore import *
5 | from PyQt4.QtCore import pyqtSlot as Slot
6 | from PyQt4.QtCore import pyqtSignal as Signal
7 | from PyQt4.QtGui import *
8 |
9 | from models.SupportedDtypes import SupportedDtypes
10 |
11 |
12 | class AddAttributesDialog(QDialog):
13 |
14 | accepted = Signal(str, object, object)
15 |
16 | def __init__(self, parent=None):
17 | super(AddAttributesDialog, self).__init__(parent)
18 |
19 | self.initUi()
20 |
21 | def initUi(self):
22 | self.setModal(True)
23 | self.resize(303, 168)
24 | sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
25 | self.setSizePolicy(sizePolicy)
26 |
27 | self.verticalLayout = QVBoxLayout(self)
28 |
29 | self.dialogHeading = QLabel(self.tr('Add a new attribute column'), self)
30 |
31 | self.gridLayout = QGridLayout()
32 |
33 | self.columnNameLineEdit = QLineEdit(self)
34 | self.columnNameLabel = QLabel(self.tr('Name'), self)
35 | self.dataTypeComboBox = QComboBox(self)
36 |
37 | self.dataTypeComboBox.addItems(SupportedDtypes.names())
38 |
39 | self.columnTypeLabel = QLabel(self.tr('Type'), self)
40 | self.defaultValueLineEdit = QLineEdit(self)
41 | self.defaultValueLabel = QLabel(self.tr('Inital Value(s)'), self)
42 |
43 | self.gridLayout.addWidget(self.columnNameLabel, 0, 0, 1, 1)
44 | self.gridLayout.addWidget(self.columnNameLineEdit, 0, 1, 1, 1)
45 |
46 | self.gridLayout.addWidget(self.columnTypeLabel, 1, 0, 1, 1)
47 | self.gridLayout.addWidget(self.dataTypeComboBox, 1, 1, 1, 1)
48 |
49 | self.gridLayout.addWidget(self.defaultValueLabel, 2, 0, 1, 1)
50 | self.gridLayout.addWidget(self.defaultValueLineEdit, 2, 1, 1, 1)
51 |
52 | self.buttonBox = QDialogButtonBox(self)
53 | self.buttonBox.setOrientation(Qt.Horizontal)
54 | self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok)
55 |
56 | self.verticalLayout.addWidget(self.dialogHeading)
57 | self.verticalLayout.addLayout(self.gridLayout)
58 | self.verticalLayout.addWidget(self.buttonBox)
59 |
60 | self.buttonBox.accepted.connect(self.accept)
61 | self.buttonBox.rejected.connect(self.reject)
62 |
63 |
64 | def accept(self):
65 | super(AddAttributesDialog, self).accept()
66 |
67 | newColumn = self.columnNameLineEdit.text()
68 | dtype = SupportedDtypes.dtype(self.dataTypeComboBox.currentText())
69 |
70 | defaultValue = self.defaultValueLineEdit.text()
71 | try:
72 | if dtype in SupportedDtypes.intTypes() + SupportedDtypes.uintTypes():
73 | defaultValue = int(defaultValue)
74 | elif dtype in SupportedDtypes.floatTypes():
75 | defaultValue = float(defaultValue)
76 | elif dtype in SupportedDtypes.boolTypes():
77 | defaultValue = defaultValue.lower() in ['t', '1']
78 | elif dtype in SupportedDtypes.datetimeTypes():
79 | defaultValue = Timestamp(defaultValue)
80 | if isinstance(defaultValue, NaTType):
81 | defaultValue = Timestamp('')
82 | else:
83 | defaultValue = dtype.type()
84 | except ValueError as e:
85 | defaultValue = dtype.type()
86 |
87 | self.accepted.emit(newColumn, dtype, defaultValue)
88 |
89 | class FilterColDialog(QDialog):
90 |
91 | accepted = Signal(dict)
92 |
93 | def __init__(self,section,name,values,parent=None):
94 | super(FilterColDialog,self).__init__(parent)
95 | self.section = section
96 | self.name = name
97 | self.values = ['']
98 | for each in values:
99 | if str(each) not in self.values:
100 | self.values.append(str(each))
101 | self.filters=["","Equals","Does not equal", "Less than",
102 | "Less than or equal to","Greater than","Greater than or equal to"]
103 | self.initUI()
104 |
105 | def initUI(self):
106 | self.setWindowTitle(self.tr("Create Filter"))
107 | self.setModal(True)
108 | sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
109 | self.setSizePolicy(sizePolicy)
110 |
111 | self.gridLayout=QGridLayout()
112 |
113 | self.showLabel=QLabel(self.tr("Show rows where %s:" % self.name))
114 |
115 | self.filterOne = QComboBox()
116 | self.filterTwo = QComboBox()
117 | self.filterOne.addItems(self.filters)
118 | self.filterTwo.addItems(self.filters)
119 |
120 | self.valueOne = QComboBox()
121 | self.valueOne.setEditable(True)
122 | self.valueTwo = QComboBox()
123 | self.valueTwo.setEditable(True)
124 | self.valueOne.addItems(self.values)
125 | self.valueTwo.addItems(self.values)
126 |
127 | self.andButton = QRadioButton("and")
128 | self.andButton.setChecked(True)
129 | self.orButton = QRadioButton("or")
130 | self.andOrBox = QHBoxLayout()
131 | self.andOrBox.addWidget(self.andButton)
132 | self.andOrBox.addWidget(self.orButton)
133 |
134 | self.buttonBox = QDialogButtonBox(self)
135 | self.buttonBox.setOrientation(Qt.Horizontal)
136 | self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok)
137 |
138 | self.buttonBox.accepted.connect(self.accept)
139 | self.buttonBox.rejected.connect(self.reject)
140 |
141 | self.gridLayout.addWidget(self.showLabel,0,0)
142 | self.gridLayout.addWidget(self.filterOne,1,0,1,1)
143 | self.gridLayout.addWidget(self.valueOne,1,2,1,1)
144 | self.gridLayout.addLayout(self.andOrBox,2,0,1,1)
145 | self.gridLayout.addWidget(self.filterTwo,3,0,1,1)
146 | self.gridLayout.addWidget(self.valueTwo,3,2,1,1)
147 | self.gridLayout.addWidget(self.buttonBox,4,2)
148 |
149 | self.setLayout(self.gridLayout)
150 |
151 | def accept(self):
152 | selection={}
153 |
154 | filterOne = str(self.filterOne.currentText())
155 | if filterOne == '':
156 | QMessageBox.warning(self,"Filter Error","Must select at least one operation.\n")
157 | else:
158 | if filterOne == "Equals":
159 | filterOne = "=="
160 | elif filterOne == "Does not equal":
161 | filterOne = "!="
162 | elif filterOne == "Less than":
163 | filterOne = "<"
164 | elif filterOne == "Less than or equal to":
165 | filterOne = "<="
166 | elif filterOne == "Greater than":
167 | filterOne = ">"
168 | else:
169 | filterOne = ">="
170 | filterTwo = str(self.filterTwo.currentText())
171 | if filterTwo != '':
172 | if filterTwo == "Equals":
173 | filterTwo = "=="
174 | elif filterTwo == "Does not equal":
175 | filterTwo = "!="
176 | elif filterTwo == "Less than":
177 | filterTwo = "<"
178 | elif filterTwo == "Less than or equal to":
179 | filterTwo = "<="
180 | elif filterTwo == "Greater than":
181 | filterTwo = ">"
182 | else:
183 | filterTwo = ">="
184 | else:
185 | filterTwo = ''
186 |
187 |
188 | selection['name']=str(self.name)
189 | selection['filterOne']=filterOne
190 | selection['filterTwo']=filterTwo
191 | selection['valueOne']=str(self.valueOne.currentText())
192 | selection['valueTwo']=str(self.valueTwo.currentText())
193 | selection['operator']= '&' if self.andButton.isChecked else '|'
194 |
195 | super(FilterColDialog, self).accept()
196 | self.accepted.emit(selection)
197 |
198 | class FillNaNDialog(QDialog):
199 |
200 | accepted = Signal(list)
201 |
202 | def __init__(self,section,parent=None):
203 | super(FillNaNDialog,self).__init__(parent)
204 | self.section = section
205 | self.initUI()
206 |
207 | def initUI(self):
208 | self.setWindowTitle(self.tr("Fill NaN's"))
209 | self.setModal(True)
210 | self.resize(366, 274)
211 | sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
212 | self.setSizePolicy(sizePolicy)
213 |
214 | self.gridLayout=QGridLayout()
215 |
216 | self.fillWithLabel=QLabel(self.tr("Fill With"))
217 | self.fillWithEdit=QLineEdit()
218 | self.methodLabel=QLabel(self.tr("Fill Method"))
219 | self.fillMethodCombo=QComboBox()
220 | self.fillMethodCombo.addItems(['None','backfill','bfill','pad','ffill'])
221 |
222 | self.buttonBox = QDialogButtonBox(self)
223 | self.buttonBox.setOrientation(Qt.Horizontal)
224 | self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok)
225 |
226 | self.buttonBox.accepted.connect(self.accept)
227 | self.buttonBox.rejected.connect(self.reject)
228 |
229 | self.gridLayout.addWidget(self.fillWithLabel,0,0)
230 | self.gridLayout.addWidget(self.fillWithEdit,0,1)
231 | self.gridLayout.addWidget(self.methodLabel,1,0)
232 | self.gridLayout.addWidget(self.fillMethodCombo,1,1)
233 | self.gridLayout.addWidget(self.buttonBox,2,1)
234 | self.setLayout(self.gridLayout)
235 |
236 | def accept(self):
237 | selection = [self.fillMethodCombo.currentText(),self.fillWithEdit.text(),self.section]
238 | super(FillNaNDialog, self).accept()
239 | self.accepted.emit(selection)
240 |
241 | class ReplaceEntryDialog(QDialog):
242 |
243 | accepted = Signal(list)
244 |
245 | def __init__(self,section,parent=None):
246 | super(ReplaceEntryDialog,self).__init__(parent)
247 | self.section = section
248 | self.initUI()
249 |
250 | def initUI(self):
251 | self.setWindowTitle(self.tr('Replace Entry'))
252 | self.setModal(True)
253 | self.resize(366, 274)
254 | sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
255 | self.setSizePolicy(sizePolicy)
256 |
257 | self.gridLayout = QGridLayout()
258 |
259 | self.toReplaceLabel = QLabel(self.tr("Value to Replace"))
260 | self.toReplaceLineEdit = QLineEdit()
261 | self.replaceWithLabel = QLabel(self.tr("Replace With"))
262 | self.replaceWithLineEdit = QLineEdit()
263 |
264 | self.buttonBox = QDialogButtonBox(self)
265 | self.buttonBox.setOrientation(Qt.Horizontal)
266 | self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok)
267 |
268 | self.buttonBox.accepted.connect(self.accept)
269 | self.buttonBox.rejected.connect(self.reject)
270 |
271 | self.gridLayout.addWidget(self.toReplaceLabel,0,0)
272 | self.gridLayout.addWidget(self.toReplaceLineEdit,0,1)
273 | self.gridLayout.addWidget(self.replaceWithLabel,1,0)
274 | self.gridLayout.addWidget(self.replaceWithLineEdit,1,1)
275 | self.gridLayout.addWidget(self.buttonBox,2,1)
276 | self.setLayout(self.gridLayout)
277 |
278 | def accept(self):
279 | selection = [self.toReplaceLineEdit.text(),self.replaceWithLineEdit.text(),self.section]
280 | super(ReplaceEntryDialog, self).accept()
281 | self.accepted.emit(selection)
282 |
283 |
284 | class RemoveAttributesDialog(QDialog):
285 |
286 | accepted = Signal(list)
287 |
288 | def __init__(self, columns, parent=None):
289 | super(RemoveAttributesDialog, self).__init__(parent)
290 | self.columns = columns
291 | self.initUi()
292 |
293 | def initUi(self):
294 | self.setWindowTitle(self.tr('Remove Attributes'))
295 | self.setModal(True)
296 | self.resize(366, 274)
297 | sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
298 | self.setSizePolicy(sizePolicy)
299 |
300 | self.gridLayout = QGridLayout(self)
301 |
302 | self.dialogHeading = QLabel(self.tr('Select the attribute column(s) which shall be removed'), self)
303 |
304 | self.listView = QListView(self)
305 |
306 | model = QStandardItemModel()
307 | for column in self.columns:
308 | item = QStandardItem(column)
309 | model.appendRow(item)
310 |
311 | self.listView.setModel(model)
312 | self.listView.setSelectionMode(QListView.MultiSelection)
313 |
314 | self.buttonBox = QDialogButtonBox(self)
315 | self.buttonBox.setOrientation(Qt.Horizontal)
316 | self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok)
317 |
318 | self.gridLayout.addWidget(self.dialogHeading, 0, 0, 1, 1)
319 | self.gridLayout.addWidget(self.listView, 1, 0, 1, 1)
320 | self.gridLayout.addWidget(self.buttonBox, 2, 0, 1, 1)
321 |
322 | self.buttonBox.accepted.connect(self.accept)
323 | self.buttonBox.rejected.connect(self.reject)
324 |
325 |
326 |
327 | def accept(self):
328 | selection = self.listView.selectedIndexes()
329 | names = []
330 | for index in selection:
331 | position = index.row()
332 | names.append((position, index.data(Qt.DisplayRole)))
333 |
334 | super(RemoveAttributesDialog, self).accept()
335 | self.accepted.emit(names)
336 |
--------------------------------------------------------------------------------
/dialogs/GraphFormatDialog.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from PyQt4.QtCore import *
3 | from PyQt4.QtGui import *
4 | from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
5 | from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
6 | import matplotlib.pyplot as plt
7 | import pandas as pd
8 | import numpy as np
9 |
10 | __author__ = "Stephen Terry"
11 | __version__ = "1.0.0"
12 |
13 |
14 | class GraphFormatDialog(QDialog):
15 |
16 | def __init__(self,options,parent=None):
17 |
18 | super(GraphFormatDialog,self).__init__(parent)
19 | ## Set some variables
20 | self.colors={'blue':'b','green':'g','red':'r','cyan':'c','magenta':'m',
21 | 'yellow':'y','black':'k','white':'w'}
22 | self.line_options={'none':'','solid':'-','dashed':'--','dotted':':','dashdot':'-.'}
23 | self.markers={'none':'','point':'.','pixel':',','circle':'o','triangle_down':'v',
24 | 'triangle_up':'^','triangle_right':'>','triangle_left':'<',
25 | 'square':'s','pentagon':'p','star':'*','hex1':'h','hex2':'H',
26 | 'plus':'+','x':'x','diamond':'D','thin diamond':'d'}
27 | self.histDrawn=False
28 | self.tables = options
29 |
30 | self.initUI()
31 |
32 | def initUI(self):
33 | self.setWindowTitle(self.tr("DataFrame Graph Generator"))
34 | self.setModal(True)
35 | #self.resize(366, 274)
36 | sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
37 | self.setSizePolicy(sizePolicy)
38 |
39 | self.gridLayout=QGridLayout()
40 |
41 | self.xDataSourceLabel = QLabel(self.tr('Choose a DataFrame for the X-Data'),self)
42 | self.yDataSourceLabel = QLabel(self.tr('Choose a DataFrame for the Y-Data'),self)
43 |
44 | self.xSourceList = QListView(self)
45 | self.ySourceList = QListView(self)
46 |
47 | model = QStandardItemModel()
48 | for key in self.tables.keys():
49 | item = QStandardItem(key)
50 | model.appendRow(item)
51 |
52 | self.xSourceList.setModel(model)
53 | self.ySourceList.setModel(model)
54 | self.xSourceList.clicked.connect(self.updateXDataCombo)
55 | self.ySourceList.clicked.connect(self.updateYDataCombo)
56 |
57 | self.xDataComboLabel=QLabel(self.tr("X-Axis Data"))
58 | self.xDataCombo=QComboBox()
59 | self.yDataComboLabel=QLabel(self.tr("Y-Axis Data"))
60 | self.yDataCombo=QComboBox()
61 |
62 | self.graphTitleLabel=QLabel(self.tr("Graph Title"))
63 | self.graphTitleLineEdit=QLineEdit()
64 | self.graphXTitleLabel=QLabel(self.tr("X Title"))
65 | self.graphXTitleLineEdit=QLineEdit()
66 | self.graphYTitleLabel=QLabel(self.tr("Y Title"))
67 | self.graphYTitleLineEdit=QLineEdit()
68 |
69 | self.graphInputGroupBox=QGroupBox(self.tr("Graph Input"))
70 | self.graphInputBox=QGridLayout()
71 |
72 | self.dataGroupBox=QGroupBox(self.tr("Data Options"))
73 | self.dataBox=QGridLayout()
74 |
75 | self.dataBox.addWidget(self.xDataComboLabel,0,0)
76 | self.dataBox.addWidget(self.xDataCombo,0,1)
77 | self.dataBox.addWidget(self.yDataComboLabel,1,0)
78 | self.dataBox.addWidget(self.yDataCombo,1,1)
79 | self.dataGroupBox.setLayout(self.dataBox)
80 |
81 | self.titleGroupBox=QGroupBox(self.tr("Title Options"))
82 | self.titleBox=QGridLayout()
83 |
84 | self.titleBox.addWidget(self.graphTitleLabel,0,0)
85 | self.titleBox.addWidget(self.graphTitleLineEdit,0,1)
86 | self.titleBox.addWidget(self.graphXTitleLabel,1,0)
87 | self.titleBox.addWidget(self.graphXTitleLineEdit,1,1)
88 | self.titleBox.addWidget(self.graphYTitleLabel,2,0)
89 | self.titleBox.addWidget(self.graphYTitleLineEdit,2,1)
90 | self.titleGroupBox.setLayout(self.titleBox)
91 |
92 | self.tabWidget=QTabWidget()
93 | self.plotWidget=QWidget()
94 | self.scatterWidget=QWidget()
95 | self.histWidget=QWidget()
96 |
97 | self.plotGrid=QGridLayout()
98 | self.scatterGrid=QGridLayout()
99 | self.histGrid=QGridLayout()
100 |
101 | self.lineComboLabel=QLabel(self.tr('Line Style'))
102 | self.lineStyleCombo=QComboBox()
103 | self.lineStyleCombo.addItems(list(self.line_options.keys()))
104 | self.colorComboLabel=QLabel(self.tr('Color'))
105 | self.colorCombo=QComboBox()
106 | self.colorCombo.addItems(list(self.colors.keys()))
107 | self.markerComboLabel=QLabel(self.tr('Markers'))
108 | self.markerCombo=QComboBox()
109 | self.markerCombo.addItems(list(self.markers.keys()))
110 |
111 | self.plotGrid.addWidget(self.lineComboLabel,0,0)
112 | self.plotGrid.addWidget(self.lineStyleCombo,0,1)
113 | self.plotGrid.addWidget(self.colorComboLabel,1,0)
114 | self.plotGrid.addWidget(self.colorCombo,1,1)
115 | self.plotGrid.addWidget(self.markerComboLabel,2,0)
116 | self.plotGrid.addWidget(self.markerCombo,2,1)
117 | self.plotWidget.setLayout(self.plotGrid)
118 | self.tabWidget.addTab(self.plotWidget,"Plot")
119 |
120 | self.scattercolorComboLabel=QLabel(self.tr('Color'))
121 | self.scattercolorCombo=QComboBox()
122 | self.scattercolorCombo.addItems(list(self.colors.keys()))
123 | self.scattermarkerComboLabel=QLabel(self.tr('Markers'))
124 | self.scattermarkerCombo=QComboBox()
125 | self.scattermarkerCombo.addItems(list(self.markers.keys()))
126 | self.scatterXYlineCheckBox=QCheckBox(self.tr("Add XY Line"))
127 |
128 | self.scatterGrid.addWidget(self.scattercolorComboLabel,0,0)
129 | self.scatterGrid.addWidget(self.scattercolorCombo,0,1)
130 | self.scatterGrid.addWidget(self.scattermarkerComboLabel,1,0)
131 | self.scatterGrid.addWidget(self.scattermarkerCombo,1,1)
132 | self.scatterGrid.addWidget(self.scatterXYlineCheckBox,2,0)
133 | self.scatterWidget.setLayout(self.scatterGrid)
134 | self.tabWidget.addTab(self.scatterWidget,"Scatter")
135 |
136 | self.histLabel=QLabel(self.tr("Bins"))
137 | self.histMinRangeTextLabel=QLabel(self.tr('Range (min)'))
138 | self.histMaxRangeTextLabel=QLabel(self.tr('Range (max)'))
139 | self.histMinRangeText=QLineEdit()
140 | self.histMaxRangeText=QLineEdit()
141 | self.histSlider=QSlider(Qt.Horizontal)
142 | self.histSlider.setRange(1,100)
143 | self.histSlider.setValue(10)
144 | self.histValue=QLineEdit()
145 | self.histValue.setReadOnly(True)
146 | self.histValue.setText(str(self.histSlider.value()))
147 | self.connect(self.histSlider,SIGNAL("valueChanged(int)"),self.setBins)
148 |
149 | self.histGrid.addWidget(self.histLabel,0,0)
150 | self.histGrid.addWidget(self.histSlider,0,1)
151 | self.histGrid.addWidget(self.histValue,0,2)
152 | self.histGrid.addWidget(self.histMinRangeTextLabel,1,0)
153 | self.histGrid.addWidget(self.histMinRangeText,1,1)
154 | self.histGrid.addWidget(self.histMaxRangeTextLabel,2,0)
155 | self.histGrid.addWidget(self.histMaxRangeText,2,1)
156 | self.histWidget.setLayout(self.histGrid)
157 | self.tabWidget.addTab(self.histWidget,"Histogram")
158 |
159 | self.drawButton=QPushButton('Draw')
160 | self.clearButton=QPushButton('Clear')
161 | self.drawButton.clicked.connect(self.drawFigure)
162 | self.clearButton.clicked.connect(self.clearFigure)
163 | self.buttonBox = QHBoxLayout()
164 | self.buttonBox.addWidget(self.drawButton)
165 | self.buttonBox.addWidget(self.clearButton)
166 |
167 | self.graphInputBox.addWidget(self.dataGroupBox,0,0)
168 | self.graphInputBox.addWidget(self.titleGroupBox,1,0)
169 | self.graphInputBox.addWidget(self.tabWidget,2,0)
170 | self.graphInputBox.addLayout(self.buttonBox,3,0)
171 | self.graphInputGroupBox.setLayout(self.graphInputBox)
172 |
173 | self.vBox=QVBoxLayout()
174 | self.graphFrame=QWidget()
175 |
176 | self.figure=plt.figure()
177 | self.ax=self.figure.add_subplot(111)
178 | self.canvas=FigureCanvas(self.figure)
179 | self.toolbar=NavigationToolbar(self.canvas,self)
180 |
181 | self.vBox.addWidget(self.canvas)
182 | self.vBox.addWidget(self.toolbar)
183 | self.graphFrame.setLayout(self.vBox)
184 |
185 | self.gridLayout.addWidget(self.xDataSourceLabel,0,0,1,1)
186 | self.gridLayout.addWidget(self.yDataSourceLabel,0,1,1,1)
187 | self.gridLayout.addWidget(self.graphInputGroupBox,1,2,1,1)
188 | self.gridLayout.addWidget(self.xSourceList,1,0,1,1)
189 | self.gridLayout.addWidget(self.ySourceList,1,1,1,1)
190 | self.gridLayout.addWidget(self.graphFrame,2,0,3,3)
191 |
192 | self.setLayout(self.gridLayout)
193 |
194 | def doNothing(self):
195 | pass
196 |
197 | def updateXDataCombo(self):
198 | selection = self.xSourceList.selectedIndexes()
199 | for index in selection:
200 | self.xSource = index.data(Qt.DisplayRole)
201 | self.xDataCombo.clear()
202 | self.xDataCombo.addItems(self.tables[self.xSource].columns)
203 |
204 | def updateYDataCombo(self):
205 | selection = self.ySourceList.selectedIndexes()
206 | for index in selection:
207 | self.ySource = index.data(Qt.DisplayRole)
208 | self.yDataCombo.clear()
209 | self.yDataCombo.addItems(self.tables[self.ySource].columns)
210 |
211 | def drawFigure(self):
212 | plot = self.tabWidget.tabText(self.tabWidget.currentIndex())
213 | if plot == 'Plot':
214 | self.drawPlot()
215 | if plot == 'Scatter':
216 | self.drawScatter()
217 | if plot == 'Histogram':
218 | self.drawHist()
219 |
220 | def drawPlot(self):
221 | xdata=self.xDataCombo.currentText()
222 | ydata=self.yDataCombo.currentText()
223 | line=self.lineStyleCombo.currentText()
224 | color=self.colorCombo.currentText()
225 | markers=self.markerCombo.currentText()
226 |
227 | if self.graphTitleLineEdit.text() != '':
228 | self.ax.set_title(self.graphTitleLineEdit.text())
229 | if self.graphXTitleLineEdit.text() != '':
230 | self.ax.set_xlabel(self.graphXTitleLineEdit.text())
231 | if self.graphYTitleLineEdit.text() != '':
232 | self.ax.set_ylabel(self.graphYTitleLineEdit.text())
233 |
234 | if markers == 'none':
235 | self.ax.plot(pd.to_numeric(self.tables[self.xSource][xdata]),
236 | pd.to_numeric(self.tables[self.ySource][ydata]),
237 | color=color,linestyle=line)
238 | elif line == 'none':
239 | self.ax.plot(pd.to_numeric(self.tables[self.xSource][xdata]),
240 | pd.to_numeric(self.tables[self.ySource][ydata]),
241 | color=color,marker=self.markers[markers])
242 | else:
243 | self.ax.plot(pd.to_numeric(self.tables[self.xSource][xdata]),
244 | pd.to_numeric(self.tables[self.ySource][ydata]),
245 | color=color,linestyle=line,marker=self.markers[markers])
246 |
247 | self.update_figure()
248 |
249 | def drawScatter(self):
250 | xdata=self.xDataCombo.currentText()
251 | ydata=self.yDataCombo.currentText()
252 | color=self.scattercolorCombo.currentText()
253 | markers=self.scattermarkerCombo.currentText()
254 |
255 | if self.graphTitleLineEdit.text() != '':
256 | self.ax.set_title(self.graphTitleLineEdit.text())
257 | if self.graphXTitleLineEdit.text() != '':
258 | self.ax.set_xlabel(self.graphXTitleLineEdit.text())
259 | if self.graphYTitleLineEdit.text() != '':
260 | self.ax.set_ylabel(self.graphYTitleLineEdit.text())
261 |
262 | self.ax.scatter(pd.to_numeric(self.tables[self.xSource][xdata]),
263 | pd.to_numeric(self.tables[self.ySource][ydata]),
264 | color=color,marker=self.markers[markers])
265 |
266 | if self.scatterXYlineCheckBox.isChecked():
267 | max_x=0
268 | max_y=0
269 | if np.max(pd.to_numeric(self.tables[self.xSource][xdata])) > max_x:
270 | max_x=np.max(pd.to_numeric(self.tables[self.xSource][xdata]))
271 | if np.max(pd.to_numeric(self.tables[self.ySource][ydata])) > max_y:
272 | max_y=np.max(pd.to_numeric(self.tables[self.ySource][ydata]))
273 | max_val=np.max([max_x,max_y])
274 | self.ax.plot([0,max_val],[0,max_val],linewidth=2,linestyle='--',c='k',alpha=.4)
275 |
276 | self.update_figure()
277 |
278 | def setBins(self,value):
279 | if self.histDrawn:
280 | self.histValue.setText(str(self.histSlider.value()))
281 | self.clearFigure()
282 | self.drawHist()
283 | else:
284 | self.histValue.setText(str(self.histSlider.value()))
285 |
286 | def drawHist(self):
287 | self.histDrawn=True
288 | xdata=self.xDataCombo.currentText()
289 |
290 | if self.graphTitleLineEdit.text() != '':
291 | self.ax.set_title(self.graphTitleLineEdit.text())
292 | if self.graphXTitleLineEdit.text() != '':
293 | self.ax.set_xlabel(self.graphXTitleLineEdit.text())
294 | if self.graphYTitleLineEdit.text() != '':
295 | self.ax.set_ylabel(self.graphYTitleLineEdit.text())
296 |
297 | if self.histMinRangeText.text() != '':
298 | rMin=float(histMinRangeText.text())
299 | else:
300 | rMin=np.min(self.tables[self.xSource][xdata])
301 | if self.histMaxRangeText.text() != '':
302 | rMax=float(histMaxRangeText.text())
303 | else:
304 | rMax=np.max(self.tables[self.xSource][xdata])
305 |
306 | #if self.histDensityCheckBox.isChecked():
307 | # self.ax.histogram(self.df[xdata].convert_objects(convert_numeric=True),bins=int(self.histSlider.value()),
308 | # range=(rMin,rMax),density=True)
309 | #else:
310 | #self.ax.hist(self.tables[self.xSource][xdata].convert_objects(convert_numeric=True),
311 | # bins=int(self.histSlider.value()),range=(rMin,rMax))
312 | self.ax.hist(pd.to_numeric(self.tables[self.xSource][xdata]),
313 | bins=int(self.histSlider.value()),range=(rMin,rMax))
314 |
315 | self.update_figure()
316 |
317 | def clearFigure(self):
318 | self.histDrawn=False
319 | self.figure.clf()
320 | self.ax=self.figure.add_subplot(111)
321 | self.update_figure()
322 |
323 | def update_figure(self):
324 | self.canvas.draw()
325 |
326 | if __name__ == "__main__":
327 | app=QApplication(sys.argv)
328 | form=GraphFormatDialog(options={})
329 | form.show()
330 | app.exec_()
--------------------------------------------------------------------------------
/models/DataFrameTableModel.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 | import numpy as np
3 |
4 | from PyQt4.QtCore import *
5 | from PyQt4.QtCore import pyqtSlot as Slot
6 | from PyQt4.QtCore import pyqtSignal as Signal
7 | from PyQt4.QtGui import *
8 |
9 | from models.SupportedDtypes import SupportedDtypes
10 |
11 | __author__ = "Stephen Terry"
12 | __version__ = "1.0.0"
13 |
14 | class DataFrameTableModel(QAbstractTableModel):
15 |
16 | _float_precisions = {
17 | "float16": np.finfo(np.float16).precision - 2,
18 | "float32": np.finfo(np.float32).precision - 1,
19 | "float64": np.finfo(np.float64).precision - 1
20 | }
21 |
22 | """list of int datatypes for easy checking in data() and setData()"""
23 | _intDtypes = SupportedDtypes.intTypes() + SupportedDtypes.uintTypes()
24 | """list of float datatypes for easy checking in data() and setData()"""
25 | _floatDtypes = SupportedDtypes.floatTypes()
26 | """list of bool datatypes for easy checking in data() and setData()"""
27 | _boolDtypes = SupportedDtypes.boolTypes()
28 | """list of datetime datatypes for easy checking in data() and setData()"""
29 | _dateDtypes = SupportedDtypes.datetimeTypes()
30 |
31 | _timestampFormat = Qt.ISODate
32 |
33 | sortingStart = Signal()
34 | sortingFinish = Signal()
35 | dtypeChanged = Signal(int, object)
36 | changingDtypeFailed = Signal(object, QModelIndex, object)
37 | dataChanged = Signal()
38 | dataFrameChanged = Signal()
39 | trackDataChange = Signal()
40 |
41 | def __init__(self, filename="", dataFrame=None, copyDataFrame=False):
42 |
43 | super(DataFrameTableModel, self).__init__()
44 |
45 | self._filename = filename
46 | self._dirty = False
47 | self._df = pd.DataFrame()
48 | self._grouping = False
49 | self._changes = []
50 | self._currChange = 0
51 | if dataFrame is not None:
52 | self.setDataFrame(dataFrame, copyDataFrame=copyDataFrame)
53 | #self.dataChanged.emit()
54 |
55 | self.editable = False
56 |
57 | def dataFrame(self):
58 |
59 | return self._df
60 |
61 | def setDataFrame(self, dataFrame, copyDataFrame=False):
62 |
63 | if not isinstance(dataFrame, pd.core.frame.DataFrame):
64 | raise TypeError("not of type pandas.core.frame.DataFrame")
65 |
66 | self.layoutAboutToBeChanged.emit()
67 | if copyDataFrame:
68 | self._df = dataFrame.copy()
69 | else:
70 | self._df = dataFrame
71 |
72 | # self._columnDtypeModel = ColumnDtypeModel(dataFrame)
73 | # self._columnDtypeModel.dtypeChanged.connect(self.propagateDtypeChanges)
74 | # self._columnDtypeModel.changeFailed.connect(
75 | # lambda columnName, index, dtype: self.changingDtypeFailed.emit(columnName, index, dtype)
76 | # )
77 | self.layoutChanged.emit()
78 | self.dataChanged.emit()
79 | self.dataFrameChanged.emit()
80 | self._changes.insert(self._currChange,self._df.copy())
81 |
82 |
83 | @Slot(int, object)
84 | def propagateDtypeChanges(self, column, dtype):
85 | self.dtypeChanged.emit(column, dtype)
86 |
87 | @property
88 | def timestampFormat(self):
89 |
90 | return self._timestampFormat
91 |
92 | @timestampFormat.setter
93 | def timestampFormat(self, timestampFormat):
94 |
95 | if not isinstance(timestampFormat, (unicode, )):
96 | raise TypeError('not of type unicode')
97 |
98 | self._timestampFormat = timestampFormat
99 |
100 | ###### The following methods are for Read-Only models #########################
101 | def rowCount(self, index=QModelIndex()):
102 |
103 | return len(self._df.index)
104 |
105 | def columnCount(self, index=QModelIndex()):
106 |
107 | return len(self._df.columns)
108 |
109 | def data(self, index, role=Qt.DisplayRole):
110 |
111 | if role == Qt.DisplayRole:
112 | i=index.row()
113 | j=index.column()
114 | #return '{0}'.format(self._df.iget_value(i,j))
115 | return '{0}'.format(self._df.iat[i,j])
116 | else:
117 | return None
118 |
119 | def headerData(self, section, orientation, role=Qt.DisplayRole):
120 | if role != Qt.DisplayRole:
121 | return None
122 |
123 | if orientation == Qt.Horizontal:
124 | try:
125 | return self._df.columns.tolist()[section]
126 | except (IndexError, ):
127 | return None
128 |
129 | elif orientation == Qt.Vertical:
130 | try:
131 | if type(self._df.index.tolist()[section]) == str:
132 | return self._df.index.tolist()[section]
133 | else:
134 | return int(section)
135 | except (IndexError, ):
136 | return None
137 | ###############################################################################
138 |
139 | ###### The following methods are for editable models ##########################
140 | def flags(self, index):
141 |
142 | flags = super(DataFrameTableModel, self).flags(index)
143 |
144 | if not self.editable:
145 | return flags
146 |
147 | col = self._df.columns[index.column()]
148 | if self._df[col].dtype == np.bool:
149 | flags |= Qt.ItemIsUserCheckable
150 | else:
151 | # if you want to have a combobox for bool columns set this
152 | flags |= Qt.ItemIsEditable
153 |
154 | return flags
155 |
156 | def setData(self, index, value, role=Qt.DisplayRole):
157 |
158 | if not index.isValid() or not self.editable:
159 | return False
160 |
161 | if value != index.data(role):
162 |
163 | self.layoutAboutToBeChanged.emit()
164 |
165 | row = self._df.index[index.row()]
166 | col = self._df.columns[index.column()]
167 | columnDtype = self._df[col].dtype
168 |
169 |
170 | if columnDtype == object:
171 | pass
172 |
173 | elif columnDtype in self._intDtypes:
174 | dtypeInfo = np.iinfo(columnDtype)
175 | value = np.int64(value).astype(columnDtype)
176 | if value < dtypeInfo.min:
177 | value = dtypeInfo.min
178 | elif value > dtypeInfo.max:
179 | value = dtypeInfo.max
180 | else:
181 | pass
182 |
183 | elif columnDtype in self._floatDtypes:
184 | value = np.float64(value).astype(columnDtype)
185 |
186 | elif columnDtype in self._boolDtypes:
187 | value = np.bool_(value)
188 |
189 | elif columnDtype in self._dateDtypes:
190 | # convert the given value to a compatible datetime object.
191 | # if the conversation could not be done, keep the original
192 | # value.
193 | if isinstance(value, QDateTime):
194 | value = value.toString(self.timestampFormat)
195 | try:
196 | value = pd.Timestamp(value)
197 | except Exception:
198 | raise Exception(u"Can't convert '{0}' into a datetime".format(value))
199 | return False
200 | else:
201 | raise TypeError("try to set unhandled data type")
202 |
203 | self._df.set_value(row, col, value)
204 |
205 | #print 'after change: ', value, self._df.iloc[row][col]
206 | self._dirty = True
207 | self.layoutChanged.emit()
208 | self.dataChanged.emit()
209 | self.trackDataChange.emit()
210 | return True
211 | else:
212 | return False
213 |
214 | def addDataFrameColumn(self, columnName, dtype, defaultValue):
215 | if not self.editable or dtype not in SupportedDtypes.allTypes():
216 | return False
217 |
218 | elements = self.rowCount()
219 | columnPosition = self.columnCount()
220 |
221 | newColumn = pd.Series([defaultValue]*elements, index=self._df.index, dtype=dtype)
222 |
223 | self.beginInsertColumns(QModelIndex(), columnPosition - 1, columnPosition - 1)
224 | try:
225 | self._df.insert(columnPosition, columnName, newColumn, allow_duplicates=False)
226 | except ValueError as e:
227 | # columnName does already exist
228 | return False
229 |
230 | self.endInsertColumns()
231 | self._dirty = True
232 | self.layoutChanged.emit()
233 | self.dataChanged.emit()
234 | self.trackDataChange.emit()
235 | self.propagateDtypeChanges(columnPosition, newColumn.dtype)
236 |
237 | return True
238 |
239 | def addDataFrameRows(self, count=1):
240 | # don't allow any gaps in the data rows.
241 | # and always append at the end
242 |
243 | if not self.editable:
244 | return False
245 |
246 | position = self.rowCount()
247 |
248 | if count < 1:
249 | return False
250 |
251 | ## What is self.dataFrame()
252 | if len(self.dataFrame().columns) == 0:
253 | # log an error message or warning
254 | return False
255 |
256 | # Note: This function emits the rowsAboutToBeInserted() signal which
257 | # connected views (or proxies) must handle before the data is
258 | # inserted. Otherwise, the views may end up in an invalid state.
259 | self.beginInsertRows(QModelIndex(), position, position + count - 1)
260 |
261 | defaultValues = []
262 | for dtype in self._df.dtypes:
263 | if dtype.type == np.dtype('= 0:
353 | if options[1][:options[1].find('.')].isdigit():
354 | options[1]=float(options[1])
355 | else:
356 | pass
357 | self._df[col].replace(to_replace=options[0],
358 | value=options[1],inplace=True)
359 | self._dirty = True
360 | self.layoutChanged.emit()
361 | self.dataChanged.emit()
362 | self.trackDataChange.emit()
363 |
364 | def filterCol(self,options):
365 | err = False
366 | if not self.editable:
367 | return False
368 |
369 | name = options['name']
370 | operator = options['operator']
371 | filterOne = options['filterOne']
372 | if options['valueOne'].find('.') == 0:
373 | if options['valueOne'][options['valueOne'].find('.'):].isdigit():
374 | options['valueOne']=float(options['valueOne'])
375 | elif options['valueOne'].find('.') > 0:
376 | if ((options['valueOne'][:options['valueOne'].find('.')].isdigit()) and
377 | (options['valueOne'][options['valueOne'].find('.')+1:].isdigit())):
378 | options['valueOne']=float(options['valueOne'])
379 | else:
380 | try:
381 | options['valueOne']=int(options['valueOne'])
382 | except ValueError:
383 | err = True
384 |
385 | if options['filterTwo'] == '' and not err:
386 | if filterOne == "==":
387 | self._df=self._df[self._df[name] == options['valueOne']]
388 | elif filterOne == "!=":
389 | self._df=self._df[self._df[name] != options['valueOne']]
390 | elif filterOne == "<":
391 | self._df=self._df[self._df[name] < options['valueOne']]
392 | elif filterOne == "<=":
393 | self._df=self._df[self._df[name] <= options['valueOne']]
394 | elif filterOne == ">":
395 | self._df=self._df[self._df[name] > options['valueOne']]
396 | elif filterOne == ">=":
397 | self._df=self._df[self._df[name] >= options['valueOne']]
398 | else:
399 | pass
400 | elif options['filterTwo'] != '' and not err:
401 |
402 | filterTwo = options['filterTwo']
403 | if options['valueTwo'].find('.') == 0:
404 | if options['valueTwo'][options['valueTwo'].find('.'):].isdigit():
405 | options['valueTwo']=float(options['valueTwo'])
406 | elif options['valueTwo'].find('.') > 0:
407 | if ((options['valueTwo'][:options['valueTwo'].find('.')-1].isdigit()) and
408 | (options['valueTwo'][options['valueTwo'].find('.'):].isdigit())):
409 | options['valueTwo']=float(options['valueTwo'])
410 | else:
411 | try:
412 | options['valueTwo']=int(options['valueTwo'])
413 | except ValueError:
414 | err = True
415 | if not err:
416 | if filterOne == "==":
417 | if operator == '&':
418 | if filterTwo == "==":
419 | self._df=self._df[(self._df[name] == options['valueOne'])
420 | & (self._df[name] == options['valueTwo'])]
421 | elif filterTwo == "!=":
422 | self._df=self._df[(self._df[name] == options['valueOne'])
423 | & (self._df[name] != options['valueTwo'])]
424 | elif filterTwo == "<":
425 | self._df=self._df[(self._df[name] == options['valueOne'])
426 | & (self._df[name] < options['valueTwo'])]
427 | elif filterTwo == "<=":
428 | self._df=self._df[(self._df[name] == options['valueOne'])
429 | & (self._df[name] <= options['valueTwo'])]
430 | elif filterTwo == ">":
431 | self._df=self._df[(self._df[name] == options['valueOne'])
432 | & (self._df[name] > options['valueTwo'])]
433 | elif filterTwo == ">=":
434 | self._df=self._df[(self._df[name] == options['valueOne'])
435 | & (self._df[name] >= options['valueTwo'])]
436 | else:
437 | pass
438 | if operator == '|':
439 | if filterTwo == "==":
440 | self._df=self._df[(self._df[name] == options['valueOne'])
441 | | (self._df[name] == options['valueTwo'])]
442 | elif filterTwo == "!=":
443 | self._df=self._df[(self._df[name] == options['valueOne'])
444 | | (self._df[name] != options['valueTwo'])]
445 | elif filterTwo == "<":
446 | self._df=self._df[(self._df[name] == options['valueOne'])
447 | | (self._df[name] < options['valueTwo'])]
448 | elif filterTwo == "<=":
449 | self._df=self._df[(self._df[name] == options['valueOne'])
450 | | (self._df[name] <= options['valueTwo'])]
451 | elif filterTwo == ">":
452 | self._df=self._df[(self._df[name] == options['valueOne'])
453 | | (self._df[name] > options['valueTwo'])]
454 | elif filterTwo == ">=":
455 | self._df=self._df[(self._df[name] == options['valueOne'])
456 | | (self._df[name] >= options['valueTwo'])]
457 | else:
458 | pass
459 | elif filterOne == "!=":
460 | if operator == '&':
461 | if filterTwo == "==":
462 | self._df=self._df[(self._df[name] != options['valueOne'])
463 | & (self._df[name] == options['valueTwo'])]
464 | elif filterTwo == "!=":
465 | self._df=self._df[(self._df[name] != options['valueOne'])
466 | & (self._df[name] != options['valueTwo'])]
467 | elif filterTwo == "<":
468 | self._df=self._df[(self._df[name] != options['valueOne'])
469 | & (self._df[name] < options['valueTwo'])]
470 | elif filterTwo == "<=":
471 | self._df=self._df[(self._df[name] != options['valueOne'])
472 | & (self._df[name] <= options['valueTwo'])]
473 | elif filterTwo == ">":
474 | self._df=self._df[(self._df[name] != options['valueOne'])
475 | & (self._df[name] > options['valueTwo'])]
476 | elif filterTwo == ">=":
477 | self._df=self._df[(self._df[name] != options['valueOne'])
478 | & (self._df[name] >= options['valueTwo'])]
479 | else:
480 | pass
481 | if operator == '|':
482 | if filterTwo == "==":
483 | self._df=self._df[(self._df[name] != options['valueOne'])
484 | | (self._df[name] == options['valueTwo'])]
485 | elif filterTwo == "!=":
486 | self._df=self._df[(self._df[name] != options['valueOne'])
487 | | (self._df[name] != options['valueTwo'])]
488 | elif filterTwo == "<":
489 | self._df=self._df[(self._df[name] != options['valueOne'])
490 | | (self._df[name] < options['valueTwo'])]
491 | elif filterTwo == "<=":
492 | self._df=self._df[(self._df[name] != options['valueOne'])
493 | | (self._df[name] <= options['valueTwo'])]
494 | elif filterTwo == ">":
495 | self._df=self._df[(self._df[name] != options['valueOne'])
496 | | (self._df[name] > options['valueTwo'])]
497 | elif filterTwo == ">=":
498 | self._df=self._df[(self._df[name] != options['valueOne'])
499 | | (self._df[name] >= options['valueTwo'])]
500 | else:
501 | pass
502 | elif filterOne == "<":
503 | if operator == '&':
504 | if filterTwo == "==":
505 | self._df=self._df[(self._df[name] < options['valueOne'])
506 | & (self._df[name] == options['valueTwo'])]
507 | elif filterTwo == "!=":
508 | self._df=self._df[(self._df[name] < options['valueOne'])
509 | & (self._df[name] != options['valueTwo'])]
510 | elif filterTwo == "<":
511 | self._df=self._df[(self._df[name] < options['valueOne'])
512 | & (self._df[name] < options['valueTwo'])]
513 | elif filterTwo == "<=":
514 | self._df=self._df[(self._df[name] < options['valueOne'])
515 | & (self._df[name] <= options['valueTwo'])]
516 | elif filterTwo == ">":
517 | self._df=self._df[(self._df[name] < options['valueOne'])
518 | & (self._df[name] > options['valueTwo'])]
519 | elif filterTwo == ">=":
520 | self._df=self._df[(self._df[name] < options['valueOne'])
521 | & (self._df[name] >= options['valueTwo'])]
522 | else:
523 | pass
524 | if operator == '|':
525 | if filterTwo == "==":
526 | self._df=self._df[(self._df[name] < options['valueOne'])
527 | | (self._df[name] == options['valueTwo'])]
528 | elif filterTwo == "!=":
529 | self._df=self._df[(self._df[name] < options['valueOne'])
530 | | (self._df[name] != options['valueTwo'])]
531 | elif filterTwo == "<":
532 | self._df=self._df[(self._df[name] < options['valueOne'])
533 | | (self._df[name] < options['valueTwo'])]
534 | elif filterTwo == "<=":
535 | self._df=self._df[(self._df[name] < options['valueOne'])
536 | | (self._df[name] <= options['valueTwo'])]
537 | elif filterTwo == ">":
538 | self._df=self._df[(self._df[name] < options['valueOne'])
539 | | (self._df[name] > options['valueTwo'])]
540 | elif filterTwo == ">=":
541 | self._df=self._df[(self._df[name] < options['valueOne'])
542 | | (self._df[name] >= options['valueTwo'])]
543 | else:
544 | pass
545 | elif filterOne == "<=":
546 | if operator == '&':
547 | if filterTwo == "==":
548 | self._df=self._df[(self._df[name] <= options['valueOne'])
549 | & (self._df[name] == options['valueTwo'])]
550 | elif filterTwo == "!=":
551 | self._df=self._df[(self._df[name] <= options['valueOne'])
552 | & (self._df[name] != options['valueTwo'])]
553 | elif filterTwo == "<":
554 | self._df=self._df[(self._df[name] <= options['valueOne'])
555 | & (self._df[name] < options['valueTwo'])]
556 | elif filterTwo == "<=":
557 | self._df=self._df[(self._df[name] <= options['valueOne'])
558 | & (self._df[name] <= options['valueTwo'])]
559 | elif filterTwo == ">":
560 | self._df=self._df[(self._df[name] <= options['valueOne'])
561 | & (self._df[name] > options['valueTwo'])]
562 | elif filterTwo == ">=":
563 | self._df=self._df[(self._df[name] <= options['valueOne'])
564 | & (self._df[name] >= options['valueTwo'])]
565 | else:
566 | pass
567 | if operator == '|':
568 | if filterTwo == "==":
569 | self._df=self._df[(self._df[name] <= options['valueOne'])
570 | | (self._df[name] == options['valueTwo'])]
571 | elif filterTwo == "!=":
572 | self._df=self._df[(self._df[name] <= options['valueOne'])
573 | | (self._df[name] != options['valueTwo'])]
574 | elif filterTwo == "<":
575 | self._df=self._df[(self._df[name] <= options['valueOne'])
576 | | (self._df[name] < options['valueTwo'])]
577 | elif filterTwo == "<=":
578 | self._df=self._df[(self._df[name] <= options['valueOne'])
579 | | (self._df[name] <= options['valueTwo'])]
580 | elif filterTwo == ">":
581 | self._df=self._df[(self._df[name] <= options['valueOne'])
582 | | (self._df[name] > options['valueTwo'])]
583 | elif filterTwo == ">=":
584 | self._df=self._df[(self._df[name] <= options['valueOne'])
585 | | (self._df[name] >= options['valueTwo'])]
586 | else:
587 | pass
588 | elif filterOne == ">":
589 | if operator == '&':
590 | if filterTwo == "==":
591 | self._df=self._df[(self._df[name] > options['valueOne'])
592 | & (self._df[name] == options['valueTwo'])]
593 | elif filterTwo == "!=":
594 | self._df=self._df[(self._df[name] > options['valueOne'])
595 | & (self._df[name] != options['valueTwo'])]
596 | elif filterTwo == "<":
597 | self._df=self._df[(self._df[name] > options['valueOne']) &
598 | (self._df[name] < options['valueTwo'])]
599 | elif filterTwo == "<=":
600 | self._df=self._df[(self._df[name] > options['valueOne'])
601 | & (self._df[name] <= options['valueTwo'])]
602 | elif filterTwo == ">":
603 | self._df=self._df[(self._df[name] > options['valueOne'])
604 | & (self._df[name] > options['valueTwo'])]
605 | elif filterTwo == ">=":
606 | self._df=self._df[(self._df[name] > options['valueOne'])
607 | & (self._df[name] >= options['valueTwo'])]
608 | else:
609 | pass
610 | if operator == '|':
611 | if filterTwo == "==":
612 | self._df=self._df[(self._df[name] > options['valueOne'])
613 | | (self._df[name] == options['valueTwo'])]
614 | elif filterTwo == "!=":
615 | self._df=self._df[(self._df[name] > options['valueOne'])
616 | | (self._df[name] != options['valueTwo'])]
617 | elif filterTwo == "<":
618 | #print("Right one: > <")
619 | self._df=self._df[(self._df[name] > options['valueOne'])
620 | | (self._df[name] < options['valueTwo'])]
621 | elif filterTwo == "<=":
622 | self._df=self._df[(self._df[name] > options['valueOne'])
623 | | (self._df[name] <= options['valueTwo'])]
624 | elif filterTwo == ">":
625 | self._df=self._df[(self._df[name] > options['valueOne'])
626 | | (self._df[name] > options['valueTwo'])]
627 | elif filterTwo == ">=":
628 | self._df=self._df[(self._df[name] > options['valueOne'])
629 | | (self._df[name] >= options['valueTwo'])]
630 | else:
631 | pass
632 | elif filterOne == ">=":
633 | if operator == '&':
634 | if filterTwo == "==":
635 | self._df=self._df[(self._df[name] >= options['valueOne'])
636 | & (self._df[name] == options['valueTwo'])]
637 | elif filterTwo == "!=":
638 | self._df=self._df[(self._df[name] >= options['valueOne'])
639 | & (self._df[name] != options['valueTwo'])]
640 | elif filterTwo == "<":
641 | self._df=self._df[(self._df[name] >= options['valueOne'])
642 | & (self._df[name] < options['valueTwo'])]
643 | elif filterTwo == "<=":
644 | self._df=self._df[(self._df[name] >= options['valueOne'])
645 | & (self._df[name] <= options['valueTwo'])]
646 | elif filterTwo == ">":
647 | self._df=self._df[(self._df[name] >= options['valueOne'])
648 | & (self._df[name] > options['valueTwo'])]
649 | elif filterTwo == ">=":
650 | self._df=self._df[(self._df[name] >= options['valueOne'])
651 | & (self._df[name] >= options['valueTwo'])]
652 | else:
653 | pass
654 | if operator == '|':
655 | if filterTwo == "==":
656 | self._df=self._df[(self._df[name] >= options['valueOne'])
657 | | (self._df[name] == options['valueTwo'])]
658 | elif filterTwo == "!=":
659 | self._df=self._df[(self._df[name] >= options['valueOne'])
660 | | (self._df[name] != options['valueTwo'])]
661 | elif filterTwo == "<":
662 | self._df=self._df[(self._df[name] >= options['valueOne'])
663 | | (self._df[name] < options['valueTwo'])]
664 | elif filterTwo == "<=":
665 | self._df=self._df[(self._df[name] >= options['valueOne'])
666 | | (self._df[name] <= options['valueTwo'])]
667 | elif filterTwo == ">":
668 | self._df=self._df[(self._df[name] >= options['valueOne'])
669 | | (self._df[name] > options['valueTwo'])]
670 | elif filterTwo == ">=":
671 | self._df=self._df[(self._df[name] >= options['valueOne'])
672 | | (self._df[name] >= options['valueTwo'])]
673 | else:
674 | pass
675 | else:
676 | pass
677 |
678 |
679 | if err:
680 | QMessageBox.warning(self,"Filter Error","Error converting input to number.\n"
681 | "Please ensure that you entered a numeric value.")
682 | else:
683 | self._df=self._df.copy()
684 | self._dirty = True
685 | self.layoutChanged.emit()
686 | self.dataChanged.emit()
687 | self.trackDataChange.emit()
688 |
689 |
690 | def fillNaN(self,options):
691 | if not self.editable:
692 | return False
693 |
694 | col=self._df.columns[options[2]]
695 | if options[0] == 'None':
696 | if options[1].isdigit():
697 | options[1]=int(options[1])
698 | elif options[1].find('.') == 0:
699 | if options[1][1:].isdigit():
700 | options[1]=float(options[1])
701 | elif options[1].find('.') >= 0:
702 | if options[1][:options[1].find('.')].isdigit():
703 | options[1]=float(options[1])
704 | else:
705 | pass
706 | self._df[col].fillna(value=options[1],inplace=True)
707 | self._dirty = True
708 | self.layoutChanged.emit()
709 | self.dataChanged.emit()
710 | self.trackDataChange.emit()
711 | else:
712 | self._df[col].fillna(method=options[0],inplace=True)
713 | self._dirty = True
714 | self.layoutChanged.emit()
715 | self.dataChanged.emit()
716 | self.trackDataChange.emit()
717 |
718 | def undo(self):
719 | if not self.editable:
720 | return False
721 |
722 | if self._currChange > 0:
723 | self._df = self._changes[self._currChange-1].copy()
724 | self._currChange = self._currChange - 1
725 | self.layoutChanged.emit()
726 | self.dataChanged.emit()
727 | self.dataFrameChanged.emit()
728 |
729 | def redo(self):
730 | if not self.editable:
731 | return False
732 |
733 | if self._currChange + 1 <= len(self._changes) - 1:
734 | self._df = self._changes[self._currChange + 1].copy()
735 | self._currChange = self._currChange + 1
736 | self.layoutChanged.emit()
737 | self.dataChanged.emit()
738 | self.dataFrameChanged.emit()
739 |
740 | def convertColumnsToNumeric(self,section):
741 | col=self._df.columns[section]
742 | self._df[col]=pd.to_numeric(self._df[col])
743 | self._dirty = True
744 | self.dataChanged.emit()
745 |
746 | def convertColumnsToDate(self,section):
747 | col=self._df.columns[section]
748 | self._df[col]=pd.to_datetime(self._df[col])
749 | self._dirty = True
750 | self.dataChanged.emit()
751 |
752 | def convertColumnsToTimeDeltas(self,section):
753 | col=self._df.columns[section]
754 | self._df[col]=pd.to_timedelta(self._df[col])
755 | self._dirty = True
756 | self.dataChanged.emit()
757 |
758 | ###############################################################################
759 |
760 | def enableEditing(self, editable):
761 | self.editable = editable
762 |
763 | def dataFrameColumns(self):
764 | return self._df.columns.tolist()
765 |
766 | def columnDtypeModel(self):
767 |
768 | return self._columnDtypeModel
769 |
770 | def sort(self, columnId, order=Qt.AscendingOrder):
771 |
772 | self.layoutAboutToBeChanged.emit()
773 | self.sortingStart.emit()
774 | column = self._df.columns[columnId]
775 | self._df.sort(column, ascending=bool(order), kind='mergesort', inplace=True)
776 | self.layoutChanged.emit()
777 | self.sortingFinish.emit()
778 | self.trackDataChange.emit()
779 |
--------------------------------------------------------------------------------
/dataFrameViewer.py:
--------------------------------------------------------------------------------
1 | import csv
2 | import sys
3 | from collections import OrderedDict
4 | from PyQt4.QtCore import *
5 | from PyQt4.QtCore import pyqtSlot as Slot
6 | from PyQt4.QtGui import *
7 | import pandas as pd
8 | import numpy as np
9 | import qrc_resources
10 |
11 |
12 | from dialogs.EditDialogs import (AddAttributesDialog, RemoveAttributesDialog,
13 | ReplaceEntryDialog, FillNaNDialog,
14 | FilterColDialog)
15 | from dialogs.ExcelFileDialog import ExcelFileDialog
16 | from dialogs.MergeDataFrameDialog import MergeDataFrameDialog
17 | from dialogs.GroupByDialog import GroupByDialog
18 | from dialogs.DescribeDialog import DescribeDialog
19 | from dialogs.GraphFormatDialog import GraphFormatDialog
20 | from models.DataFrameTableModel import DataFrameTableModel
21 |
22 | __author__ = "Stephen Terry"
23 | __version__= "1.0.0"
24 |
25 | class DataFrameViewer(QMainWindow):
26 |
27 | NextID = 1
28 |
29 | def __init__(self,filename='',parent=None):
30 | super(DataFrameViewer,self).__init__(parent)
31 |
32 |
33 | ## Tab widget for DataFrames########################################
34 | self.tableTabWidget = QTabWidget()
35 | self.tableTabWidget.setTabPosition(1)
36 | self.tableTabWidget.setTabsClosable(True)
37 | self.tableTabWidget.currentChanged.connect(self.tabChanged)
38 | self.setCentralWidget(self.tableTabWidget)
39 |
40 |
41 | ## Actions and shortcuts for table(s)##################################
42 | fileNewAction = self.createAction("&New", self.fileNew,
43 | QKeySequence.New, "filenew", "Create a file")
44 | fileOpenAction = self.createAction("&Open...", self.fileOpen,
45 | QKeySequence.Open, "fileopen",
46 | "Open an existing file")
47 | fileSaveAction = self.createAction("&Save", self.fileSave,
48 | QKeySequence.Save, "filesave", "Save the file")
49 | fileSaveAsAction = self.createAction("Save &As...",
50 | self.fileSaveAs, icon="filesaveas",
51 | tip="Save the file using a new filename")
52 | fileSaveAllAction = self.createAction("Save A&ll",
53 | self.fileSaveAll, icon="filesave",
54 | tip="Save all the files")
55 | fileCloseTabAction = self.createAction("Close &Tab",
56 | self.fileCloseTab, QKeySequence.Close, "filequit",
57 | "Close the active tab")
58 | fileQuitAction = self.createAction("&Quit", self.close,
59 | "Ctrl+Q", "filequit", "Close the application")
60 |
61 | self.editEditableAction = self.createAction("E&ditable",
62 | self.enableEditing,icon="document-edit",checkable=True,
63 | signal="toggled(bool)")
64 | self.editAddRowAction = self.createAction("Add Row", self.addRow,
65 | icon='edit-table-insert-row-below', tip="Add a new row")
66 | self.editAddColAction = self.createAction("Add Column",
67 | self.showAddColumnDialog,icon='edit-table-insert-column-right',
68 | tip="Add a new column")
69 | self.editDelRowAction = self.createAction("Delete Row", self.removeRow,
70 | icon='edit-table-delete-row', tip="Delete a row")
71 | self.editDelColAction = self.createAction("Delete Column",
72 | self.showRemoveColumnDialog,icon='edit-table-delete-column',
73 | tip="Delete a column")
74 | self.editUndoAction = self.createAction("Undo", self.undoChange,
75 | shortcut = 'Ctrl+Z',icon='previous', tip="Undo last action")
76 | self.editRedoAction = self.createAction("Redo", self.redoChange,
77 | shortcut = 'Ctrl+Y',icon='next', tip="Redo last action")
78 | self.mergeAction = self.createAction("Merge DataFrames",
79 | self.showMergeDialog, icon='merge', tip="Merge Dataframes")
80 | self.groupByAction = self.createAction("GroupBy", self.showGroupDialog,
81 | icon="group",tip="Group-by")
82 | self.graphAction = self.createAction("Launch Graphing Tool",
83 | self.showGraphDialog, icon="graph", tip="Launch Graphing Tool")
84 | self.describeAction = self.createAction("Describe Data",
85 | self.showDescribeDialog, icon="describe", tip="Describe Data")
86 | self.editEditableAction.setChecked(False)
87 | self.editAddRowAction.setEnabled(False)
88 | self.editAddColAction.setEnabled(False)
89 | self.editDelRowAction.setEnabled(False)
90 | self.editDelColAction.setEnabled(False)
91 | self.editUndoAction.setEnabled(False)
92 | self.editRedoAction.setEnabled(False)
93 | QShortcut(QKeySequence.PreviousChild, self, self.prevTab)
94 | QShortcut(QKeySequence.NextChild, self, self.nextTab)
95 |
96 |
97 | fileMenu = self.menuBar().addMenu("&File")
98 | self.addActions(fileMenu, (fileNewAction, fileOpenAction,
99 | fileSaveAction, fileSaveAsAction, fileSaveAllAction,
100 | fileCloseTabAction, None, fileQuitAction))
101 |
102 | editMenu = self.menuBar().addMenu("&Edit")
103 | self.addActions(editMenu,(self.editEditableAction,self.editAddRowAction,
104 | self.editAddColAction,self.editDelRowAction,self.editDelColAction,
105 | None,self.editUndoAction,self.editRedoAction))
106 |
107 | dataMenu = self.menuBar().addMenu("&Data")
108 | self.addActions(dataMenu,(self.mergeAction,self.groupByAction,
109 | self.describeAction))
110 |
111 | graphMenu = self.menuBar().addMenu("&Graph")
112 | self.addActions(graphMenu,(self.graphAction,))
113 |
114 | fileToolbar = self.addToolBar("File")
115 | fileToolbar.setObjectName("FileToolbar")
116 | self.addActions(fileToolbar, (fileNewAction, fileOpenAction,
117 | fileSaveAction))
118 | editToolbar = self.addToolBar("Edit")
119 | editToolbar.setObjectName("EditToolbar")
120 | self.addActions(editToolbar,(self.editEditableAction,self.editAddRowAction,
121 | self.editAddColAction,self.editDelRowAction,self.editDelColAction,
122 | None,self.editUndoAction,self.editRedoAction))
123 |
124 | miscToolbar = self.addToolBar("Misc")
125 | miscToolbar.setObjectName("MiscToolbar")
126 | self.addActions(miscToolbar,(self.mergeAction,self.groupByAction,self.graphAction,
127 | self.describeAction))
128 |
129 | self.connect(self.tableTabWidget,SIGNAL("tabCloseRequested(int)"),self.fileCloseTab)
130 |
131 | status = self.statusBar()
132 | status.setSizeGripEnabled(False)
133 | status.showMessage("Ready", 5000)
134 |
135 | self.setWindowTitle("DataFrameViewer")
136 |
137 | self.filename = filename
138 | if self.filename != '':
139 | self.loadFile(filename)
140 |
141 | def trackChanges(self):
142 | tableEdit = self.tableTabWidget.currentWidget()
143 | if isinstance(tableEdit,QTableView):
144 | tableEdit.model()._currChange += 1
145 | tableEdit.model()._changes.insert(tableEdit.model()._currChange,tableEdit.model()._df.copy())
146 | if tableEdit.model()._currChange > 0:
147 | self.editUndoAction.setEnabled(True)
148 | else:
149 | self.editUndoAction.setEnabled(False)
150 | if tableEdit.model()._currChange < len(tableEdit.model()._changes) - 1:
151 | tableEdit.model()._changes=tableEdit.model()._changes[:tableEdit.model()._currChange + 1]
152 | self.editRedoAction.setEnabled(False)
153 |
154 | if isinstance(tableEdit,QTabWidget):
155 | table = tableEdit.currentWidget()
156 | table.model()._currChange += 1
157 | table.model()._changes.insert(table.model()._currChange,table.model()._df.copy())
158 | if table.model()._currChange > 0:
159 | self.editUndoAction.setEnabled(True)
160 | else:
161 | self.editUndoAction.setEnabled(False)
162 | if table.model()._currChange < len(table.model()._changes) - 1:
163 | table.model()._changes=table.model()._changes[:table.model()._currChange + 1]
164 | self.editRedoAction.setEnabled(False)
165 |
166 |
167 | def undoChange(self):
168 | tableEdit = self.tableTabWidget.currentWidget()
169 | if isinstance(tableEdit,QTableView):
170 | if tableEdit.model()._currChange > 0:
171 | tableEdit.model().undo()
172 | self.editRedoAction.setEnabled(True)
173 | if tableEdit.model()._currChange == 0:
174 | self.editUndoAction.setEnabled(False)
175 |
176 | if isinstance(tableEdit,QTabWidget):
177 | table = tableEdit.currentWidget()
178 | if table.model()._currChange > 0:
179 | table.model().undo()
180 | self.editRedoAction.setEnabled(True)
181 | if table.model()._currChange == 0:
182 | self.editUndoAction.setEnabled(False)
183 |
184 |
185 | def redoChange(self):
186 | tableEdit = self.tableTabWidget.currentWidget()
187 | if isinstance(tableEdit,QTableView):
188 | if tableEdit.model()._currChange < len(tableEdit.model()._changes) - 1:
189 | tableEdit.model().redo()
190 | self.editUndoAction.setEnabled(True)
191 | if tableEdit.model()._currChange == len(tableEdit.model()._changes) - 1:
192 | self.editRedoAction.setEnabled(False)
193 |
194 | if isinstance(tableEdit,QTabWidget):
195 | table = tableEdit.currentWidget()
196 | if table.model()._currChange < len(table.model()._changes) - 1:
197 | table.model().redo()
198 | self.editUndoAction.setEnabled(True)
199 | if table.model()._currChange == len(table.model().changes) - 1:
200 | self.editRedoAction.setEnabled(False)
201 |
202 |
203 | def closeEvent(self, event):
204 |
205 | self.fileSaveAll()
206 |
207 |
208 | def tabChanged(self):
209 |
210 | tableEdit = self.tableTabWidget.currentWidget()
211 | if isinstance(tableEdit,QTableView):
212 | if tableEdit.model().editable:
213 | self.editEditableAction.setChecked(True)
214 | else:
215 | self.editEditableAction.setChecked(False)
216 | if isinstance(tableEdit,QTabWidget):
217 | table = tableEdit.currentWidget()
218 | if table.model().editable:
219 | self.editEditableAction.setChecked(True)
220 | else:
221 | self.editEditableAction.setChecked(False)
222 |
223 |
224 | def prevTab(self):
225 | last = self.tableTabWidget.count()
226 | current = self.tableTabWidget.currentIndex()
227 | if last:
228 | last -= 1
229 | current = last if current == 0 else current - 1
230 | self.tableTabWidget.setCurrentIndex(current)
231 |
232 |
233 | def nextTab(self):
234 | last = self.tableTabWidget.count()
235 | current = self.tableTabWidget.currentIndex()
236 | if last:
237 | last -= 1
238 | current = 0 if current == last else current + 1
239 | self.tableTabWidget.setCurrentIndex(current)
240 |
241 | def doNothing(self):
242 | pass
243 |
244 | def sortColumn(self,column,who):
245 | tableEdit = self.tableTabWidget.currentWidget()
246 | if isinstance(tableEdit,QTableView):
247 | model = tableEdit.model()
248 | elif isinstance(tableEdit,QTabWidget):
249 | tableEdit=tableEdit.currentWidget()
250 | model = tableEdit.model()
251 | else:
252 | return
253 |
254 | if model is not None:
255 | if self.okToEdit(model):
256 | if who:
257 | model.sort(column,True)
258 | if not who:
259 | model.sort(column,False)
260 |
261 | ############## CONTEXT MENU FUNCTIONS #########################################
262 | def headerMenu(self,position):
263 | tableEdit = self.tableTabWidget.currentWidget()
264 | if isinstance(tableEdit, QTableView):
265 | column = tableEdit.headers.logicalIndexAt(position)
266 | elif isinstance(tableEdit, QTabWidget):
267 | tableEdit = tableEdit.currentWidget()
268 | column = tableEdit.headers.logicalIndexAt(position)
269 | else:
270 | return
271 |
272 | menu = QMenu()
273 | submenu=QMenu("Sort")
274 | sortAscendAction=QAction("Ascending",self)
275 | sortAscendAction.triggered.connect(lambda: self.sortColumn(column,True))
276 | sortDescendAction=QAction("Descending",self)
277 | sortDescendAction.triggered.connect(lambda: self.sortColumn(column,False))
278 | submenu.addAction(sortAscendAction)
279 | submenu.addAction(sortDescendAction)
280 | convertMenu=QMenu("Convert Objects")
281 | convertNumericAction=QAction("Numeric",self)
282 | convertNumericAction.triggered.connect(lambda: tableEdit.model().convertColumnsToNumeric(column))
283 | convertDateAction=QAction("Date",self)
284 | convertDateAction.triggered.connect(lambda: tableEdit.model().convertColumnsToDate(column))
285 | convertDeltaAction=QAction("TimeDeltas",self)
286 | convertDeltaAction.triggered.connect(lambda: tableEdit.model().convertColumnsToTimeDeltas(column))
287 | convertMenu.addAction(convertNumericAction)
288 | convertMenu.addAction(convertDateAction)
289 | convertMenu.addAction(convertDeltaAction)
290 | fillNaNAction=QAction("Fill NaN's",self)
291 | fillNaNAction.triggered.connect(lambda: self.showFillNaNDialog(column))
292 | menu.addMenu(convertMenu)
293 | menu.addMenu(submenu)
294 | menu.addAction(fillNaNAction)
295 | replaceEntryAction=QAction("Replace",self)
296 | replaceEntryAction.triggered.connect(lambda: self.showReplaceValueDialog(column))
297 | filterColAction=QAction("Filter",self)
298 | filterColAction.triggered.connect(lambda: self.showFilterColDialog(column))
299 | menu.addAction(replaceEntryAction)
300 | menu.addAction(filterColAction)
301 | menu.exec_(tableEdit.viewport().mapToGlobal(position))
302 |
303 | def vHeaderMenu(self,position):
304 | tableEdit = self.tableTabWidget.currentWidget()
305 | if isinstance(tableEdit, QTableView):
306 | column = tableEdit.headers.logicalIndexAt(position)
307 | elif isinstance(tableEdit, QTabWidget):
308 | tableEdit = tableEdit.currentWidget()
309 | column = tableEdit.headers.logicalIndexAt(position)
310 | else:
311 | return
312 | row = tableEdit.vHeaders.logicalIndexAt(position)
313 | menu = QMenu("Drop Row")
314 | dropRowAction=QAction("Drop",self)
315 | dropRowAction.triggered.connect(lambda: self.removeRow(val=row))
316 | menu.addAction(dropRowAction)
317 | menu.exec_(tableEdit.viewport().mapToGlobal(position))
318 | ###############################################################################
319 |
320 | def fileNew(self):
321 |
322 | filename = "Unnamed-%d" % self.NextID
323 | self.NextID += 1
324 | table = QTableView()
325 | table.setAlternatingRowColors(True)
326 | model=DataFrameTableModel(filename=filename)
327 | model.trackDataChange.connect(self.trackChanges)
328 | table.setModel(model)
329 | ### Set some variables ###
330 | table.headers = table.horizontalHeader()
331 | table.vHeaders=table.verticalHeader()
332 | #### Set context menu for table headers ####
333 | table.headers.setContextMenuPolicy(Qt.CustomContextMenu)
334 | table.headers.customContextMenuRequested.connect(self.headerMenu)
335 | table.vHeaders.setContextMenuPolicy(Qt.CustomContextMenu)
336 | table.vHeaders.customContextMenuRequested.connect(self.vHeaderMenu)
337 | d={'':{'':''}}
338 | df=pd.DataFrame(d)
339 | model.setDataFrame(df)
340 | self.tableTabWidget.addTab(table,
341 | QFileInfo(model._filename).fileName())
342 | self.tableTabWidget.setCurrentWidget(table)
343 | self._currentTab = self.tableTabWidget.currentIndex()
344 |
345 |
346 |
347 | def fileOpen(self):
348 | filename = str(QFileDialog.getOpenFileName(self,
349 | "DataFrameViewer -- Open File"))
350 | if not filename == '':
351 | for i in range(self.tableTabWidget.count()):
352 | tableEdit = self.tableTabWidget.widget(i)
353 | if tableEdit is None:
354 | return
355 | elif isinstance(tableEdit,QTabWidget):
356 | for j in range(tableEdit.count()):
357 | table = tableEdit.widget(i)
358 | if isinstance(table,QTableView):
359 | if table.model()._filename == filename:
360 | tableEdit.setCurrentWidget(table)
361 | self.tableTabWidget.setCurrentWidget(tableEdit)
362 |
363 | elif tableEdit.model()._filename == filename:
364 | self.tableTabWidget.setCurrentWidget(tableEdit)
365 | break
366 | else:
367 | pass
368 | else:
369 | self.loadFile(filename)
370 |
371 |
372 | def loadFile(self, filename):
373 | if filename.endswith('.xls') or filename.endswith('.xlsx'):
374 | df=pd.ExcelFile(filename)
375 | sheetnames=df.sheet_names
376 | dialog=ExcelFileDialog(filename,sheetnames,self)
377 | dialog.accepted.connect(self.loadExcel)
378 | dialog.show()
379 | else:
380 | table = QTableView()
381 | table.setAlternatingRowColors(True)
382 | model=DataFrameTableModel(filename=filename)
383 | model.trackDataChange.connect(self.trackChanges)
384 | table.setModel(model)
385 | ### Set some variables ###
386 | table.headers = table.horizontalHeader()
387 | table.vHeaders=table.verticalHeader()
388 | #### Set context menu for table headers ####
389 | table.headers.setContextMenuPolicy(Qt.CustomContextMenu)
390 | table.headers.customContextMenuRequested.connect(self.headerMenu)
391 | table.vHeaders.setContextMenuPolicy(Qt.CustomContextMenu)
392 | table.vHeaders.customContextMenuRequested.connect(self.vHeaderMenu)
393 | if model._filename.endswith('.csv'):
394 | df=pd.read_csv(model._filename)
395 | model.setDataFrame(df)
396 | self.tableTabWidget.addTab(table, QFileInfo(model._filename).fileName())
397 | self.tableTabWidget.setCurrentWidget(table)
398 | if model._filename.endswith('.txt'):
399 | delim = str(self.parseDelimiter(model._filename))
400 | if delim == ' ':
401 | df=pd.read_csv(model._filename,delim_whitespace = True)
402 | else:
403 | df=pd.read_csv(model._filename,sep=delim)
404 | model.setDataFrame(df)
405 | self.tableTabWidget.addTab(table, QFileInfo(model._filename).fileName())
406 | self.tableTabWidget.setCurrentWidget(table)
407 |
408 |
409 | def loadExcel(self,options):
410 | names = options['sheets']
411 | filename = options['file']
412 | openEach = options['openEach']
413 | df=pd.ExcelFile(filename)
414 | if not openEach:
415 | newTab = QTabWidget()
416 | newTab.setTabsClosable(True)
417 | newTab.currentChanged.connect(self.tabChanged)
418 | self.connect(newTab,SIGNAL("tabCloseRequested(int)"),
419 | self.fileCloseInternalTab)
420 | for i in range(len(names)):
421 | table = QTableView()
422 | table.setAlternatingRowColors(True)
423 | model=DataFrameTableModel(filename=filename)
424 | model.trackDataChange.connect(self.trackChanges)
425 | table.setModel(model)
426 | ### Set some variables ###
427 | table.headers = table.horizontalHeader()
428 | table.vHeaders=table.verticalHeader()
429 | #### Set context menu for table headers ####
430 | table.headers.setContextMenuPolicy(Qt.CustomContextMenu)
431 | table.headers.customContextMenuRequested.connect(self.headerMenu)
432 | table.vHeaders.setContextMenuPolicy(Qt.CustomContextMenu)
433 | table.vHeaders.customContextMenuRequested.connect(self.vHeaderMenu)
434 |
435 | df2=df.parse(sheetname=names[i])
436 | model.setDataFrame(df2)
437 | newTab.addTab(table,names[i])
438 | newTab.setCurrentIndex(0)
439 | self.tableTabWidget.addTab(newTab,QFileInfo(filename).fileName())
440 | self.tableTabWidget.setCurrentWidget(newTab)
441 | else:
442 | for i in range(len(names)):
443 | table = QTableView()
444 | table.setAlternatingRowColors(True)
445 | model=DataFrameTableModel(filename=names[i])
446 | model.trackDataChange.connect(self.trackChanges)
447 | table.setModel(model)
448 | ### Set some variables ###
449 | table.headers = table.horizontalHeader()
450 | table.vHeaders=table.verticalHeader()
451 | #### Set context menu for table headers ####
452 | table.headers.setContextMenuPolicy(Qt.CustomContextMenu)
453 | table.headers.customContextMenuRequested.connect(self.headerMenu)
454 | table.vHeaders.setContextMenuPolicy(Qt.CustomContextMenu)
455 | table.vHeaders.customContextMenuRequested.connect(self.vHeaderMenu)
456 | df2=df.parse(sheetname=names[i])
457 | model.setDataFrame(df2)
458 | self.tableTabWidget.addTab(table,names[i])
459 | self.tableTabWidget.setCurrentWidget(table)
460 |
461 |
462 | def writeTextOutput(self,table):
463 | outfile=open(table.model()._filename)
464 | table.model()._df.to_string(outfile,index=False)
465 | table.model()._dirty = False
466 | outfile.close()
467 |
468 | def writeCSVOutput(self,table):
469 | table.model()._df.to_csv(table.model()._filename,index=False)
470 | table.model()._dirty = False
471 |
472 | def writePickleOutput(self,table):
473 | pass
474 | #table.model()._dirty = False
475 |
476 | def writeExcelOutput(self,table):
477 | if isinstance(table,QTabWidget):
478 | filename = table.currentWidget().model()._filename
479 | writer = pd.ExcelWriter(filename, engine='xlsxwriter')
480 |
481 | for i in range(table.count()):
482 | data=table.widget(i)
483 | sheetname=table.tabText(i)
484 | data.model()._df.to_excel(writer, sheet_name=sheetname,index=False)
485 | data.model()._dirty = False
486 | writer.save()
487 | if isinstance(table,QTableView):
488 | filename = table.model()._filename
489 | writer = pd.ExcelWriter(filename, engine='xlsxwriter')
490 | table.model()._df.to_excel(writer, sheet_name='Sheet 1',index=False)
491 | table.model()._dirty = False
492 | writer.save()
493 |
494 | def fileSave(self):
495 | tableEdit = self.tableTabWidget.currentWidget()
496 | if isinstance(tableEdit, QTableView):
497 | if tableEdit.model()._filename.endswith('.txt'):
498 | self.writeTextOutput(tableEdit)
499 | elif tableEdit.model()._filename.endswith('.csv'):
500 | self.writeCSVOutput(tableEdit)
501 | elif tableEdit.model()._filename.endswith('.pkl'):
502 | self.writePickleOutput(tableEdit)
503 | else:
504 | self.fileSaveAs()
505 |
506 | if (self.tableTabWidget.tabText(
507 | self.tableTabWidget.currentIndex()).startswith("Unnamed-")):
508 | self.tableTabWidget.setTabText(self.tableTabWidget.currentIndex(),
509 | QFileInfo(tableEdit.model()._filename).fileName())
510 |
511 | if isinstance(tableEdit,QTabWidget):
512 | files = []
513 | msg = "Select YES to save as Excel File\nSelect NO to save as csv's."
514 | reply = QMessageBox.question(self,"Excel File Detected",msg,
515 | QMessageBox.Yes,QMessageBox.No)
516 | if reply == QMessageBox.Yes:
517 | self.writeExcelOutput(tableEdit)
518 | else:
519 |
520 | for i in range(tableEdit.count()):
521 | newName=tableEdit.tabText(i) + '.csv'
522 | table=tableEdit.widget(i)
523 | table.model()._filename = os.path.join(QFileInfo(table.model()._filename).absolutePath(),newName)
524 | files.append(table.model()._filename)
525 | self.writeCSVOutput(table)
526 |
527 | self.tableTabWidget.removeTab(self.tableTabWidget.currentIndex())
528 | for f in files:
529 | self.loadFile(f)
530 |
531 |
532 | def fileSaveAs(self):
533 | files_types = "Excel (*.xls);;Pickle (*.pkl);;CSV (*.csv);;Text (*.txt)"
534 | tableEdit = self.tableTabWidget.currentWidget()
535 | if isinstance(tableEdit, QTableView):
536 | filename = QFileDialog.getSaveFileNameAndFilter(self,
537 | "DataFrameViewer -- Save File As",
538 | tableEdit.model()._filename, files_types)
539 | if not filename[0] == '':
540 | tableEdit.model()._filename = filename[0]
541 | self.tableTabWidget.setTabText(self.tableTabWidget.currentIndex(),
542 | QFileInfo(filename[0]).fileName())
543 | if filename[1].find('.csv') >= 0:
544 | self.writeCSVOutput(tableEdit)
545 | if filename[1].find('.xls') >= 0:
546 | self.writeExcelOutput(tableEdit)
547 | if filename[1].find('.txt') >= 0:
548 | self.writeTextOutput(tableEdit)
549 | if filename[1].find('.pkl') >= 0:
550 | self.writePickleOutput(tableEdit)
551 | if isinstance(tableEdit,QTabWidget):
552 | msg = "Select YES to save the Excel File\nSelect NO to save single sheet."
553 | reply = QMessageBox.question(self,"Excel File Detected",msg,QMessageBox.Yes,QMessageBox.No)
554 | if reply == QMessageBox.Yes:
555 | filename = QFileDialog.getSaveFileName(self,
556 | "DataFrameViewer -- Save File As",
557 | tableEdit.currentWidget().model()._filename, "Excel (*.xls)")
558 | if not filename == '':
559 | for i in range(tableEdit.count()):
560 | tableEdit.widget(i).model()._filename = filename
561 | self.tableTabWidget.setTabText(self.tableTabWidget.currentIndex(),
562 | QFileInfo(filename).fileName())
563 | self.writeExcelOutput(tableEdit)
564 | else:
565 | table=tableEdit.currentWidget()
566 | filename = QFileDialog.getSaveFileNameAndFilter(self,
567 | "DataFrameViewer -- Save File As",
568 | table.model()._filename, files_types)
569 | if not filename[0] == '':
570 | table.model()._filename = filename[0]
571 | tableEdit.setTabText(tableEdit.currentIndex(),
572 | QFileInfo(filename[0]).fileName())
573 | if filename[1].find('.csv') >= 0:
574 | self.writeCSVOutput(tableEdit)
575 | if filename[1].find('.xls') >= 0:
576 | self.writeExcelOutput(tableEdit)
577 | if filename[1].find('.txt') >= 0:
578 | self.writeTextOutput(tableEdit)
579 | if filename[1].find('.pkl') >= 0:
580 | self.writePickleOutput(tableEdit)
581 |
582 |
583 | def fileSaveAll(self):
584 | for i in range(self.tableTabWidget.count()):
585 | tableEdit = self.tableTabWidget.widget(i)
586 | if isinstance(tableEdit,QTableView):
587 | if tableEdit.model()._dirty:
588 | msg = "Save Changes to %s" % self.tableTabWidget.tabText(i)
589 | reply = QMessageBox.question(self,"Save File",msg,
590 | QMessageBox.Yes,QMessageBox.No)
591 | if reply == QMessageBox.Yes:
592 | self.fileSaveAs()
593 | if isinstance(tableEdit,QTabWidget):
594 | save=False
595 | for j in range(tableEdit.count()):
596 | if tableEdit.widget(j).model()._dirty:
597 | msg = "Save Changes to %s" % self.tableTabWidget.tabText(i)
598 | reply = QMessageBox.question(self,"Save File",msg,
599 | QMessageBox.Yes,QMessageBox.No)
600 | if reply == QMessageBox.Yes:
601 | msg = "Select YES to save as Excel File.\nSelect NO to save as csv."
602 | reply = QMessageBox.question(self,"Excel File Detected",
603 | msg,QMessageBox.Yes,QMessageBox.No)
604 | if reply == QMessageBox.Yes:
605 | save=True
606 | break
607 | else:
608 | table=tableEdit.widget(j)
609 | table.model()._filename = os.path.join(QFileInfo(table.model()._filename).absolutePath(),newName)
610 | self.writeCSVOutput(table)
611 |
612 | if save:
613 | self.writeExcelOutput(tableEdit)
614 |
615 |
616 | def fileCloseTab(self):
617 | tableEdit = self.tableTabWidget.currentWidget()
618 | if isinstance(tableEdit,QTableView):
619 | tableEdit.close()
620 | self.tableTabWidget.removeTab(self.tableTabWidget.currentIndex())
621 | if isinstance(tableEdit,QTabWidget):
622 | self.tableTabWidget.removeTab(self.tableTabWidget.currentIndex())
623 |
624 | def fileCloseInternalTab(self):
625 | tableEdit = self.tableTabWidget.currentWidget()
626 | table = tableEdit.currentWidget()
627 | if isinstance(table,QTableView):
628 | table.close()
629 | tableEdit.removeTab(tableEdit.currentIndex())
630 |
631 |
632 | @Slot(bool)
633 | def enableEditing(self, enabled):
634 |
635 | tableEdit = self.tableTabWidget.currentWidget()
636 | if isinstance(tableEdit, QTableView):
637 | model = tableEdit.model()
638 | elif isinstance(tableEdit, QTabWidget):
639 | table = tableEdit.currentWidget()
640 | model = table.model()
641 | else:
642 | return
643 |
644 |
645 | if model is not None:
646 | model.enableEditing(enabled)
647 | if self.editEditableAction.isChecked():
648 | self.editAddRowAction.setEnabled(True)
649 | self.editAddColAction.setEnabled(True)
650 | self.editDelRowAction.setEnabled(True)
651 | self.editDelColAction.setEnabled(True)
652 | else:
653 | self.editAddRowAction.setEnabled(False)
654 | self.editAddColAction.setEnabled(False)
655 | self.editDelRowAction.setEnabled(False)
656 | self.editDelColAction.setEnabled(False)
657 |
658 | def okToEdit(self,model):
659 | if not model.editable:
660 | reply = QMessageBox.question(self,"Make Edits",
661 | "Make this DataFrame editable?",
662 | QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel)
663 | if reply == QMessageBox.Cancel or reply == QMessageBox.No:
664 | return False
665 | elif reply == QMessageBox.Yes:
666 | model.editable=True
667 | self.editEditableAction.setChecked(True)
668 | self.enableEditing(True)
669 | return True
670 | else:
671 | return False
672 | return True
673 |
674 |
675 |
676 | @Slot(str, object, object)
677 | def addColumn(self, columnName, dtype, defaultValue):
678 | """Adds a column with the given parameters to the underlying model
679 |
680 | This method is also a slot.
681 | If no model is set, nothing happens.
682 |
683 | Args:
684 | columnName (str): The name of the new column.
685 | dtype (numpy.dtype): The datatype of the new column.
686 | defaultValue (object): Fill the column with this value.
687 |
688 | """
689 | tableEdit = self.tableTabWidget.currentWidget()
690 | if isinstance(tableEdit, QTableView):
691 | model = tableEdit.model()
692 | elif isinstance(tableEdit, QTabWidget):
693 | tableEdit = tableEdit.currentWidget()
694 | model = tableEdit.model()
695 | else:
696 | return
697 |
698 | if model is not None:
699 | if self.okToEdit(model):
700 | model.addDataFrameColumn(columnName, dtype, defaultValue)
701 |
702 | self.editAddColAction.setChecked(False)
703 |
704 | @Slot(bool)
705 | def showAddColumnDialog(self):
706 | """Display the dialog to add a column to the model.
707 |
708 | This method is also a slot.
709 |
710 | Args:
711 | triggered (bool): If the corresponding button was
712 | activated, the dialog will be created and shown.
713 |
714 | """
715 | tableEdit = self.tableTabWidget.currentWidget()
716 | if isinstance(tableEdit, QTableView):
717 | model = tableEdit.model()
718 | elif isinstance(tableEdit, QTabWidget):
719 | tableEdit = tableEdit.currentWidget()
720 | model = tableEdit.model()
721 | else:
722 | return
723 |
724 | dialog = AddAttributesDialog(self)
725 | dialog.accepted.connect(self.addColumn)
726 | dialog.show()
727 |
728 | def addRow(self):
729 |
730 | tableEdit = self.tableTabWidget.currentWidget()
731 | if isinstance(tableEdit, QTableView):
732 | model = tableEdit.model()
733 | elif isinstance(tableEdit, QTabWidget):
734 | tableEdit = tableEdit.currentWidget()
735 | model = tableEdit.model()
736 | else:
737 | return
738 |
739 | if model is not None:
740 | if self.okToEdit(model):
741 | model.addDataFrameRows()
742 | self.sender().setChecked(False)
743 |
744 |
745 | def removeRow(self,val=''):
746 |
747 | tableEdit = self.tableTabWidget.currentWidget()
748 | if isinstance(tableEdit, QTableView):
749 | model = tableEdit.model()
750 | elif isinstance(tableEdit, QTabWidget):
751 | tableEdit = tableEdit.currentWidget()
752 | model = tableEdit.model()
753 | else:
754 | return
755 |
756 | selection = tableEdit.selectedIndexes()
757 |
758 | if val == '':
759 | rows = [index.row() for index in selection]
760 | else:
761 | rows = [val]
762 |
763 | if model is not None:
764 | if self.okToEdit(model):
765 | model.removeDataFrameRows(set(rows))
766 | self.sender().setChecked(False)
767 |
768 |
769 | def removeColumns(self, columnNames):
770 | """Removes one or multiple columns from the model.
771 |
772 | This method is also a slot.
773 |
774 | Args:
775 | columnNames (list): A list of columns, which shall
776 | be removed from the model.
777 |
778 | """
779 | tableEdit = self.tableTabWidget.currentWidget()
780 | if isinstance(tableEdit, QTableView):
781 | model = tableEdit.model()
782 | elif isinstance(tableEdit, QTabWidget):
783 | model = tableEdit.currentWidget().model()
784 | else:
785 | return
786 |
787 |
788 | if model is not None:
789 | if self.okToEdit(model):
790 | model.removeDataFrameColumns(columnNames)
791 |
792 | self.editDelColAction.setChecked(False)
793 |
794 | @Slot(bool)
795 | def showRemoveColumnDialog(self):
796 | """Display the dialog to remove column(s) from the model.
797 |
798 | This method is also a slot.
799 |
800 | Args:
801 | triggered (bool): If the corresponding button was
802 | activated, the dialog will be created and shown.
803 |
804 | """
805 | tableEdit = self.tableTabWidget.currentWidget()
806 | if isinstance(tableEdit, QTableView):
807 | model = tableEdit.model()
808 | elif isinstance(tableEdit, QTabWidget):
809 | model = tableEdit.currentWidget().model()
810 | else:
811 | return
812 |
813 | if model is not None:
814 | columns = model.dataFrameColumns()
815 | dialog = RemoveAttributesDialog(columns, self)
816 | dialog.accepted.connect(self.removeColumns)
817 | dialog.show()
818 |
819 | def showFilterColDialog(self,section):
820 |
821 | tableEdit = self.tableTabWidget.currentWidget()
822 | if isinstance(tableEdit, QTableView):
823 | model = tableEdit.model()
824 | elif isinstance(tableEdit, QTabWidget):
825 | model = tableEdit.currentWidget().model()
826 | else:
827 | return
828 |
829 | name = model._df.columns[section]
830 | values = model._df[name].values.tolist()
831 |
832 | dialog = FilterColDialog(section,name,values,self)
833 | dialog.accepted.connect(self.filterCol)
834 | dialog.show()
835 |
836 | def filterCol(self,selection):
837 |
838 | tableEdit = self.tableTabWidget.currentWidget()
839 | if isinstance(tableEdit, QTableView):
840 | model = tableEdit.model()
841 | elif isinstance(tableEdit, QTabWidget):
842 | model = tableEdit.currentWidget().model()
843 | else:
844 | return
845 |
846 | if model is not None:
847 | if self.okToEdit(model):
848 | model.filterCol(selection)
849 |
850 |
851 | def showReplaceValueDialog(self,section):
852 |
853 | tableEdit = self.tableTabWidget.currentWidget()
854 | if isinstance(tableEdit, QTableView):
855 | model = tableEdit.model()
856 | elif isinstance(tableEdit, QTabWidget):
857 | model = tableEdit.currentWidget().model()
858 | else:
859 | return
860 |
861 | dialog = ReplaceEntryDialog(section,self)
862 | dialog.accepted.connect(self.replaceValue)
863 | dialog.show()
864 |
865 | def replaceValue(self,selection):
866 |
867 | tableEdit = self.tableTabWidget.currentWidget()
868 | if isinstance(tableEdit, QTableView):
869 | model = tableEdit.model()
870 | elif isinstance(tableEdit, QTabWidget):
871 | model = tableEdit.currentWidget().model()
872 | else:
873 | return
874 |
875 | if model is not None:
876 | if self.okToEdit(model):
877 | model.replaceValue(selection)
878 |
879 | def showFillNaNDialog(self,section):
880 |
881 | tableEdit = self.tableTabWidget.currentWidget()
882 | if isinstance(tableEdit, QTableView):
883 | model = tableEdit.model()
884 | elif isinstance(tableEdit, QTabWidget):
885 | model = tableEdit.currentWidget().model()
886 | else:
887 | return
888 |
889 | dialog = FillNaNDialog(section,self)
890 | dialog.accepted.connect(self.fillNaN)
891 | dialog.show()
892 |
893 | def fillNaN(self,selection):
894 |
895 | tableEdit = self.tableTabWidget.currentWidget()
896 | if isinstance(tableEdit, QTableView):
897 | model = tableEdit.model()
898 | elif isinstance(tableEdit, QTabWidget):
899 | model = tableEdit.currentWidget().model()
900 | else:
901 | return
902 |
903 | if model is not None:
904 | if self.okToEdit(model):
905 | model.fillNaN(selection)
906 |
907 | def showGroupDialog(self):
908 | if self.tableTabWidget.count() == 0:
909 | QMessageBox.warning(self,"DataFrameViewer Error",
910 | "No initial DataFrame opened.\nPlease open a DataFrame.")
911 | else:
912 | tableEdit = self.tableTabWidget.currentWidget()
913 | if isinstance(tableEdit, QTableView):
914 | data = tableEdit
915 | idx = self.tableTabWidget.currentIndex()
916 | elif isinstance(tableEdit,QTabWidget):
917 | data = tableEdit.currentWidget()
918 | idx = [self.tableTabWidget.currentIndex(),tableEdit.currentIndex()]
919 | else:
920 | return
921 | if not data.model()._grouping:
922 | options={'data':data,'idx':idx}
923 | tableEdit.model()._grouping=True
924 | dialog = GroupByDialog(options,self)
925 | dialog.changed.connect(self.doGroupBy)
926 | dialog.show()
927 |
928 | def doGroupBy(self,opts):
929 |
930 | if opts['key'] == "Accept":
931 | if opts['newTab'] == True:
932 | for i in range(self.tableTabWidget.count()):
933 | tableEdit = self.tableTabWidget.widget(i)
934 | if isinstance(tableEdit,QTableView):
935 | if tableEdit.model()._filename == opts['name']:
936 | self.tableTabWidget.setCurrentWidget(tableEdit)
937 | break
938 |
939 | else:
940 | table = QTableView()
941 | model=DataFrameTableModel(filename=opts['name'])
942 | model.trackDataChange.connect(self.trackChanges)
943 | table.setModel(model)
944 | ### Set some variables ###
945 | table.headers = table.horizontalHeader()
946 | table.vHeaders=table.verticalHeader()
947 | #### Set context menu for table headers ####
948 | table.headers.setContextMenuPolicy(Qt.CustomContextMenu)
949 | table.headers.customContextMenuRequested.connect(self.headerMenu)
950 | table.vHeaders.setContextMenuPolicy(Qt.CustomContextMenu)
951 | table.vHeaders.customContextMenuRequested.connect(self.vHeaderMenu)
952 |
953 | model.setDataFrame(opts['df'])
954 | self.tableTabWidget.addTab(table, opts['name'])
955 | self.tableTabWidget.setCurrentWidget(table)
956 | else:
957 | if type(opts['idx']) == int:
958 | self.tableTabWidget.setCurrentIndex(opts['idx'])
959 | self.tableTabWidget.widget(opts['idx']).model().setDataFrame(opts['df'])
960 | self.tableTabWidget.setTabText(opts['idx'],opts['name'])
961 | if type(opts['idx']) == list:
962 | self.tableTabWidget.setCurrentIndex(opts['idx'][0])
963 | self.tableTabWidget.widget(opts['idx'][0]).setCurrentIndex(opts['idx'][1])
964 | self.tableTabWidget.widget(opts['idx'][0]).widget(opts['idx'][1]).model().setDataFrame(opts['df'])
965 | self.tableTabWidget.widget(opts['idx'][0]).setTabText(opts['idx'][1],opts['name'])
966 |
967 | elif opts['key'] == "Reject":
968 | if len(opts) > 2:
969 | if type(opts['idx']) == int:
970 | self.tableTabWidget.setCurrentIndex(opts['idx'])
971 | self.tableTabWidget.widget(opts['idx']).model().setDataFrame(opts['df'])
972 | self.tableTabWidget.setTabText(opts['idx'],opts['name'])
973 | if type(opts['idx']) == list:
974 | self.tableTabWidget.setCurrentIndex(opts['idx'][0])
975 | self.tableTabWidget.widget(opts['idx'][0]).setCurrentIndex(opts['idx'][1])
976 | self.tableTabWidget.widget(opts['idx'][0]).widget(opts['idx'][1]).model().setDataFrame(opts['df'])
977 | self.tableTabWidget.widget(opts['idx'][0]).setTabText(opts['idx'][1],opts['name'])
978 | if type(opts['idx']) == int:
979 | self.tableTabWidget.setCurrentIndex(opts['idx'])
980 | self.tableTabWidget.widget(opts['idx']).model()._grouping = False
981 | if type(opts['idx']) == list:
982 | self.tableTabWidget.setCurrentIndex(opts['idx'][0])
983 | self.tableTabWidget.widget(opts['idx'][0]).setCurrentIndex(opts['idx'][1])
984 | self.tableTabWidget.widget(opts['idx'][0]).widget(opts['idx'][1]).model()._grouping = False
985 | else:
986 | pass
987 |
988 | def showMergeDialog(self):
989 | if self.tableTabWidget.count() == 0:
990 | QMessageBox.warning(self,"DataFrameViewer Error",
991 | "No initial DataFrame opened.\nPlease open a DataFrame.")
992 | elif self.tableTabWidget.count() < 2:
993 | QMessageBox.warning(self,"DataFrameViewer Error",
994 | "Only one DataFrame opened.\nPlease open another DataFrame to merge with.")
995 | else:
996 | available = OrderedDict()
997 | status = {}
998 | for i in range(self.tableTabWidget.count()):
999 | tableEdit = self.tableTabWidget.widget(i)
1000 | if isinstance(tableEdit,QTableView):
1001 | status['cols']=tableEdit.model()._df.columns
1002 | status['view']=True
1003 | status['idx']=i
1004 | status['name']=self.tableTabWidget.tabText(i)
1005 | available[self.tableTabWidget.tabText(i)]=status.copy()
1006 | if isinstance(tableEdit,QTabWidget):
1007 | for j in range(tableEdit.count()):
1008 | table = tableEdit.widget(j)
1009 | if isinstance(table,QTableView):
1010 | status['cols']=table.model()._df.columns
1011 | status['view']=False
1012 | status['idx']=[i,j]
1013 | status['name']=tableEdit.tabText(j)
1014 | available[self.tableTabWidget.tabText(i)+'_'+tableEdit.tabText(j)]=status.copy()
1015 |
1016 | if len(available) > 0:
1017 | dialog = MergeDataFrameDialog(available,self)
1018 | dialog.accepted.connect(self.doMerge)
1019 | dialog.show()
1020 |
1021 | def doMerge(self,options):
1022 | merge_options = options['merge_options']
1023 | left = options['left']
1024 | right = options['right']
1025 | title = 'Merged_' + left['name'] + '_' + right['name']
1026 | if merge_options.has_key('how'):
1027 | how=merge_options['how']
1028 | if merge_options.has_key('on'):
1029 | on=merge_options['on']
1030 | else:
1031 | on=None
1032 | if merge_options.has_key('left_on'):
1033 | left_on=merge_options['left_on']
1034 | else:
1035 | left_on=None
1036 | if merge_options.has_key('right_on'):
1037 | right_on=merge_options['right_on']
1038 | else:
1039 | right_on=None
1040 | if merge_options.has_key('left_index'):
1041 | left_index=merge_options['left_index']
1042 | else:
1043 | left_index=False
1044 | if merge_options.has_key('right_index'):
1045 | right_index=merge_options['right_index']
1046 | else:
1047 | right_index=None
1048 | if merge_options.has_key('suffix_x'):
1049 | suffix_x=merge_options['suffix_x']
1050 | else:
1051 | suffix_x = '_x'
1052 | if merge_options.has_key('suffix_y'):
1053 | suffix_y=merge_options['suffix_y']
1054 | else:
1055 | suffix_y = '_y'
1056 | suffixes = (suffix_x,suffix_y)
1057 |
1058 | if type(left['idx']) == int:
1059 | left_df=self.tableTabWidget.widget(left['idx']).model()._df
1060 | if type(left['idx']) == list:
1061 | tab=self.tableTabWidget.widget(left['idx'][0])
1062 | left_df=tab.widget(left['idx'][1]).model()._df
1063 | if type(right['idx']) == int:
1064 | right_df=self.tableTabWidget.widget(right['idx']).model()._df
1065 | if type(right['idx']) == list:
1066 | tab=self.tableTabWidget.widget(right['idx'][0])
1067 | right_df=tab.widget(right['idx'][1]).model()._df
1068 |
1069 | df=pd.merge(left_df,right_df,how=how,on=on,left_on=left_on,right_on=right_on,
1070 | left_index=left_index,right_index=right_index,suffixes=suffixes)
1071 |
1072 | table = QTableView()
1073 | model=DataFrameTableModel(filename=title)
1074 | model.trackDataChange.connect(self.trackChanges)
1075 | table.setModel(model)
1076 | ### Set some variables ###
1077 | table.headers = table.horizontalHeader()
1078 | table.vHeaders=table.verticalHeader()
1079 | #### Set context menu for table headers ####
1080 | table.headers.setContextMenuPolicy(Qt.CustomContextMenu)
1081 | table.headers.customContextMenuRequested.connect(self.headerMenu)
1082 | table.vHeaders.setContextMenuPolicy(Qt.CustomContextMenu)
1083 | table.vHeaders.customContextMenuRequested.connect(self.vHeaderMenu)
1084 |
1085 | model.setDataFrame(df)
1086 | model._dirty = True
1087 | self.tableTabWidget.addTab(table, title)
1088 | self.tableTabWidget.setCurrentWidget(table)
1089 |
1090 | def showDescribeDialog(self):
1091 |
1092 | if self.tableTabWidget.count() == 0:
1093 | QMessageBox.warning(self,"DataFrameViewer Error",
1094 | "No DataFrame to Describe.\nPlease open a DataFrame.")
1095 | else:
1096 | tableEdit = self.tableTabWidget.currentWidget()
1097 | if isinstance(tableEdit,QTableView):
1098 | x = tableEdit.model()._df.describe()
1099 | if isinstance(tableEdit,QTabWidget):
1100 | table=tableEdit.currentWidget()
1101 | x = table.model()._df.describe()
1102 |
1103 | dialog = DescribeDialog(x,self)
1104 | dialog.show()
1105 |
1106 | def showGraphDialog(self):
1107 |
1108 | data = {}
1109 | if self.tableTabWidget.count() == 0:
1110 | QMessageBox.warning(self,"DataFrameViewer Error",
1111 | "No DataFrame to Graph.\nPlease open a DataFrame.")
1112 | else:
1113 | for i in range(self.tableTabWidget.count()):
1114 | tableEdit = self.tableTabWidget.widget(i)
1115 | if isinstance(tableEdit, QTableView):
1116 | data[self.tableTabWidget.tabText(i)]=tableEdit.model()._df
1117 | if isinstance(tableEdit, QTabWidget):
1118 | for j in range(tableEdit.count()):
1119 | table=tableEdit.widget(j)
1120 | name=QFileInfo(table.model()._filename).fileName()+'_'+tableEdit.tabText(j)
1121 | data[name]=table.model()._df
1122 |
1123 | dialog = GraphFormatDialog(data,self)
1124 | dialog.show()
1125 |
1126 | def parseDelimiter(self,f):
1127 |
1128 | infile = open(f)
1129 | lines = infile.readlines()
1130 | infile.close()
1131 |
1132 | sniffer = csv.Sniffer()
1133 | text = sniffer.sniff(lines[0])
1134 | return text.delimiter
1135 |
1136 |
1137 |
1138 | ###############################################################################
1139 | # The following are GUI shortcut tools
1140 | ###############################################################################
1141 | ## Create an action for GUIs
1142 | def createAction(self, text, slot = None, shortcut = None, icon = None,
1143 | tip = None, checkable = False, signal = "triggered()"):
1144 | action = QAction(text,self)
1145 | if icon is not None:
1146 | action.setIcon(QIcon(":/%s.png" % icon))
1147 | if shortcut is not None:
1148 | action.setShortcut(shortcut)
1149 | if tip is not None:
1150 | action.setToolTip(tip)
1151 | action.setStatusTip(tip)
1152 | if slot is not None:
1153 | self.connect(action, SIGNAL(signal),slot)
1154 | if checkable:
1155 | action.setCheckable(True)
1156 | return action
1157 |
1158 | def addActions(self, target, actions):
1159 | for action in actions:
1160 | if action is None:
1161 | target.addSeparator()
1162 | else:
1163 | target.addAction(action)
1164 |
1165 | ## Create a button
1166 | def createButton(self,text, kind= "push", checkable = False):
1167 |
1168 | if kind == "push":
1169 | button = QPushButton(text)
1170 | if kind == "radio":
1171 | button = QRadioButton(text)
1172 | if kind == "check":
1173 | button = QCheckBox(text)
1174 | if checkable:
1175 | button.setEnabled(True)
1176 | return button
1177 | ###############################################################################
1178 | ###############################################################################
1179 |
1180 | if __name__ == "__main__":
1181 | app = QApplication(sys.argv)
1182 | form = DataFrameViewer()
1183 | form.show()
1184 | app.exec_()
--------------------------------------------------------------------------------