├── __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 | ![](./screenshots/dfViewer_main.png)
34 | pandas describe function
35 | ![](./screenshots/Describe_Dialog.png)
36 | Graph dialog
37 | ![](./screenshots/Graph_Dialog.png)
38 | Group-by dialog
39 | ![](./screenshots/GroupBy_Dialog.png)
40 | Merge dialog
41 | ![](./screenshots/Merge_Dialog.png)
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_() --------------------------------------------------------------------------------