class OWItemsets(widget.OWWidget): name = 'Frequent Itemsets' description = 'Explore sets of items that frequently appear together.' icon = 'icons/FrequentItemsets.svg' priority = 10 inputs = [("Data", Table, 'set_data')] outputs = [(Output.DATA, Table)] minSupport = settings.Setting(30) maxItemsets = settings.Setting(10000) filterSearch = settings.Setting(True) autoFind = settings.Setting(False) autoSend = settings.Setting(True) filterKeywords = settings.Setting('') filterMinItems = settings.Setting(1) filterMaxItems = settings.Setting(10000) UserAdviceMessages = [ widget.Message('Itemset are listed in item-sorted order, i.e. ' 'an itemset containing A and B is only listed once, as ' 'A > B (and not also B > A).', 'itemsets-order', widget.Message.Warning), widget.Message('To select all the itemsets that are descendants of ' '(include) some item X (i.e. the whole subtree), you ' 'can fold the subtree at that item and then select it.', 'itemsets-order', widget.Message.Information) ] def __init__(self): self.tree = QTreeWidget(self.mainArea, columnCount=2, allColumnsShowFocus=True, alternatingRowColors=True, selectionMode=QTreeWidget.ExtendedSelection, uniformRowHeights=True) self.tree.setHeaderLabels(["Itemsets", "Support", "%"]) self.tree.header().setStretchLastSection(True) self.tree.itemSelectionChanged.connect(self.selectionChanged) self.mainArea.layout().addWidget(self.tree) box = gui.widgetBox(self.controlArea, "Info") self.nItemsets = self.nSelectedExamples = self.nSelectedItemsets = '' gui.label(box, self, "Number of itemsets: %(nItemsets)s") gui.label(box, self, "Selected itemsets: %(nSelectedItemsets)s") gui.label(box, self, "Selected examples: %(nSelectedExamples)s") hbox = gui.widgetBox(box, orientation='horizontal') gui.button(hbox, self, "Expand all", callback=self.tree.expandAll) gui.button(hbox, self, "Collapse all", callback=self.tree.collapseAll) box = gui.widgetBox(self.controlArea, 'Find itemsets') gui.hSlider(box, self, 'minSupport', minValue=1, maxValue=100, label='Minimal support:', labelFormat="%d%%", callback=lambda: self.find_itemsets()) gui.hSlider(box, self, 'maxItemsets', minValue=10000, maxValue=100000, step=10000, label='Max. number of itemsets:', labelFormat="%d", callback=lambda: self.find_itemsets()) gui.checkBox(box, self, 'filterSearch', label='Apply below filters in search', tooltip='If checked, the itemsets are filtered according ' 'to below filter conditions already in the search ' 'phase. \nIf unchecked, the only filters applied ' 'during search are the ones above, ' 'and the itemsets are \nfiltered afterwards only for ' 'display, i.e. only the matching itemsets are shown.') self.button = gui.auto_commit( box, self, 'autoFind', 'Find itemsets', commit=self.find_itemsets) box = gui.widgetBox(self.controlArea, 'Filter itemsets') gui.lineEdit(box, self, 'filterKeywords', 'Contains:', callback=self.filter_change, orientation='horizontal', tooltip='A comma or space-separated list of regular ' 'expressions.') hbox = gui.widgetBox(box, orientation='horizontal') gui.spin(hbox, self, 'filterMinItems', 1, 998, label='Min. items:', callback=self.filter_change) gui.spin(hbox, self, 'filterMaxItems', 2, 999, label='Max. items:', callback=self.filter_change) gui.rubber(hbox) gui.rubber(self.controlArea) gui.auto_commit(self.controlArea, self, 'autoSend', 'Send selection') self.filter_change() def sendReport(self): self.reportSettings("Itemset statistics", [("Number of itemsets", self.nItemsets), ("Selected itemsets", self.nSelectedItemsets), ("Covered examples", self.nSelectedExamples), ]) self.reportSection("Itemsets") self.reportRaw(OWReport.reportTree(self.tree)) ITEM_DATA_ROLE = Qt.UserRole + 1 def selectionChanged(self): X = self.data.X mapping = self.onehot_mapping instances = set() where = np.where def whole_subtree(node): yield node for i in range(node.childCount()): yield from whole_subtree(node.child(i)) def itemset(node): while node: yield node.data(0, self.ITEM_DATA_ROLE) node = node.parent() def selection_ranges(node): n_children = node.childCount() if n_children: yield (self.tree.indexFromItem(node.child(0)), self.tree.indexFromItem(node.child(n_children - 1))) for i in range(n_children): yield from selection_ranges(node.child(i)) nSelectedItemsets = 0 item_selection = QItemSelection() for node in self.tree.selectedItems(): nodes = (node,) if node.isExpanded() else whole_subtree(node) if not node.isExpanded(): for srange in selection_ranges(node): item_selection.select(*srange) for node in nodes: nSelectedItemsets += 1 cols, vals = zip(*(mapping[i] for i in itemset(node))) instances.update(where((X[:, cols] == vals).all(axis=1))[0]) self.tree.itemSelectionChanged.disconnect(self.selectionChanged) self.tree.selectionModel().select(item_selection, QItemSelectionModel.Select | QItemSelectionModel.Rows) self.tree.itemSelectionChanged.connect(self.selectionChanged) self.nSelectedExamples = len(instances) self.nSelectedItemsets = nSelectedItemsets self.output = self.data[sorted(instances)] or None self.commit() def commit(self): self.send(Output.DATA, self.output) def filter_change(self): isRegexMatch = self.isRegexMatch = re.compile( '|'.join(i.strip() for i in re.split('(,|\s)+', self.filterKeywords.strip()) if i.strip())).search def hide(node, depth, has_kw): if not has_kw: has_kw = isRegexMatch(node.text(0)) hidden = (sum(hide(node.child(i), depth + 1, has_kw) for i in range(node.childCount())) == node.childCount() if node.childCount() else (not has_kw or not self.filterMinItems <= depth <= self.filterMaxItems)) node.setHidden(hidden) return hidden hide(self.tree.invisibleRootItem(), 0, False) class TreeWidgetItem(QTreeWidgetItem): def data(self, column, role): """Construct lazy tooltips""" if role != Qt.ToolTipRole: return super().data(column, role) tooltip = [] while self: tooltip.append(self.text(0)) self = self.parent() return ' '.join(reversed(tooltip)) def find_itemsets(self): if self.data is None: return data = self.data self.tree.clear() self.tree.setUpdatesEnabled(False) self.tree.blockSignals(True) class ItemDict(dict): def __init__(self, item): self.item = item top = ItemDict(self.tree.invisibleRootItem()) X, mapping = OneHot.encode(data) self.onehot_mapping = mapping names = {item: '{}={}'.format(var.name, val) for item, var, val in OneHot.decode(mapping.keys(), data, mapping)} nItemsets = 0 filterSearch = self.filterSearch filterMinItems, filterMaxItems = self.filterMinItems, self.filterMaxItems isRegexMatch = self.isRegexMatch # Find itemsets and populate the TreeView progress = gui.ProgressBar(self, self.maxItemsets + 1) for itemset, support in frequent_itemsets(X, self.minSupport / 100): if filterSearch and not filterMinItems <= len(itemset) <= filterMaxItems: continue parent = top first_new_item = None itemset_matches_filter = False for item in sorted(itemset): name = names[item] if filterSearch and not itemset_matches_filter: itemset_matches_filter = isRegexMatch(name) child = parent.get(name) if child is None: wi = self.TreeWidgetItem(parent.item, [name, str(support), '{:.1f}'.format(100 * support / len(data))]) wi.setData(0, self.ITEM_DATA_ROLE, item) child = parent[name] = ItemDict(wi) if first_new_item is None: first_new_item = (parent, name) parent = child if filterSearch and not itemset_matches_filter: parent, name = first_new_item parent.item.removeChild(parent[name].item) del parent[name].item del parent[name] else: nItemsets += 1 progress.advance() if nItemsets >= self.maxItemsets: break if not filterSearch: self.filter_change() self.nItemsets = nItemsets self.nSelectedItemsets = 0 self.nSelectedExamples = 0 self.tree.expandAll() for i in range(self.tree.columnCount()): self.tree.resizeColumnToContents(i) self.tree.setUpdatesEnabled(True) self.tree.blockSignals(False) progress.finish() def set_data(self, data): self.data = data if data is not None: self.warning(0, 'Data has continuous attributes which will be skipped.' if data.domain.has_continuous_attributes() else None) self.error(1, 'Discrete features required but data has none.' if not data.domain.has_discrete_attributes() else None) self.button.setDisabled(not data.domain.has_discrete_attributes()) if self.autoFind: self.find_itemsets()
class OWItemsets(widget.OWWidget): name = 'Frequent Itemsets' description = 'Explore sets of items that frequently appear together.' icon = 'icons/FrequentItemsets.svg' priority = 10 inputs = [("Data", Table, 'set_data')] outputs = [(Output.DATA, Table)] minSupport = settings.Setting(30) maxItemsets = settings.Setting(10000) filterSearch = settings.Setting(True) autoFind = settings.Setting(False) autoSend = settings.Setting(True) filterKeywords = settings.Setting('') filterMinItems = settings.Setting(1) filterMaxItems = settings.Setting(10000) UserAdviceMessages = [ widget.Message( 'Itemset are listed in item-sorted order, i.e. ' 'an itemset containing A and B is only listed once, as ' 'A > B (and not also B > A).', 'itemsets-order', widget.Message.Warning), widget.Message( 'To select all the itemsets that are descendants of ' '(include) some item X (i.e. the whole subtree), you ' 'can fold the subtree at that item and then select it.', 'itemsets-order', widget.Message.Information) ] def __init__(self): self._is_running = False self.isRegexMatch = lambda x: True self.tree = QTreeWidget(self.mainArea, columnCount=2, allColumnsShowFocus=True, alternatingRowColors=True, selectionMode=QTreeWidget.ExtendedSelection, uniformRowHeights=True) self.tree.setHeaderLabels(["Itemsets", "Support", "%"]) self.tree.header().setStretchLastSection(True) self.tree.itemSelectionChanged.connect(self.selectionChanged) self.mainArea.layout().addWidget(self.tree) box = gui.widgetBox(self.controlArea, "Info") self.nItemsets = self.nSelectedExamples = self.nSelectedItemsets = '' gui.label(box, self, "Number of itemsets: %(nItemsets)s") gui.label(box, self, "Selected itemsets: %(nSelectedItemsets)s") gui.label(box, self, "Selected examples: %(nSelectedExamples)s") hbox = gui.widgetBox(box, orientation='horizontal') gui.button(hbox, self, "Expand all", callback=self.tree.expandAll) gui.button(hbox, self, "Collapse all", callback=self.tree.collapseAll) box = gui.widgetBox(self.controlArea, 'Find itemsets') gui.valueSlider(box, self, 'minSupport', values=[.0001, .0005, .001, .005, .01, .05, .1, .5] + list(range(1, 101)), label='Minimal support:', labelFormat="%g%%", callback=lambda: self.find_itemsets()) gui.hSlider(box, self, 'maxItemsets', minValue=10000, maxValue=100000, step=10000, label='Max. number of itemsets:', labelFormat="%d", callback=lambda: self.find_itemsets()) self.button = gui.auto_commit(box, self, 'autoFind', 'Find itemsets', commit=self.find_itemsets) box = gui.widgetBox(self.controlArea, 'Filter itemsets') gui.lineEdit(box, self, 'filterKeywords', 'Contains:', callback=self.filter_change, orientation='horizontal', tooltip='A comma or space-separated list of regular ' 'expressions.') hbox = gui.widgetBox(box, orientation='horizontal') gui.spin(hbox, self, 'filterMinItems', 1, 998, label='Min. items:', callback=self.filter_change) gui.spin(hbox, self, 'filterMaxItems', 2, 999, label='Max. items:', callback=self.filter_change) gui.checkBox(box, self, 'filterSearch', label='Apply these filters in search', tooltip='If checked, the itemsets are filtered according ' 'to these filter conditions already in the search ' 'phase. \nIf unchecked, the only filters applied ' 'during search are the ones above, ' 'and the itemsets are \nfiltered afterwards only for ' 'display, i.e. only the matching itemsets are shown.') gui.rubber(hbox) gui.rubber(self.controlArea) gui.auto_commit(self.controlArea, self, 'autoSend', 'Send selection') self.filter_change() ITEM_DATA_ROLE = Qt.UserRole + 1 def selectionChanged(self): X = self.X mapping = self.onehot_mapping instances = set() where = np.where def whole_subtree(node): yield node for i in range(node.childCount()): yield from whole_subtree(node.child(i)) def itemset(node): while node: yield node.data(0, self.ITEM_DATA_ROLE) node = node.parent() def selection_ranges(node): n_children = node.childCount() if n_children: yield (self.tree.indexFromItem(node.child(0)), self.tree.indexFromItem(node.child(n_children - 1))) for i in range(n_children): yield from selection_ranges(node.child(i)) nSelectedItemsets = 0 item_selection = QItemSelection() for node in self.tree.selectedItems(): nodes = (node, ) if node.isExpanded() else whole_subtree(node) if not node.isExpanded(): for srange in selection_ranges(node): item_selection.select(*srange) for node in nodes: nSelectedItemsets += 1 cols, vals = zip(*(mapping[i] for i in itemset(node))) if issparse(X): rows = (len(cols) == np.bincount( (X[:, cols] != 0).indices, minlength=X.shape[0])).nonzero()[0] else: rows = where((X[:, cols] == vals).all(axis=1))[0] instances.update(rows) self.tree.itemSelectionChanged.disconnect(self.selectionChanged) self.tree.selectionModel().select( item_selection, QItemSelectionModel.Select | QItemSelectionModel.Rows) self.tree.itemSelectionChanged.connect(self.selectionChanged) self.nSelectedExamples = len(instances) self.nSelectedItemsets = nSelectedItemsets self.output = self.data[sorted(instances)] or None self.commit() def commit(self): self.send(Output.DATA, self.output) def filter_change(self): self.warning(9) try: isRegexMatch = self.isRegexMatch = re.compile( '|'.join( i.strip() for i in re.split('(,|\s)+', self.filterKeywords.strip()) if i.strip()), re.IGNORECASE).search except Exception as e: self.warning(9, 'Error in regular expression: {}'.format(e.args[0])) isRegexMatch = self.isRegexMatch = lambda x: True def hide(node, depth, has_kw): if not has_kw: has_kw = isRegexMatch(node.text(0)) hidden = ( sum( hide(node.child(i), depth + 1, has_kw) for i in range(node.childCount())) == node.childCount() if node.childCount() else (not has_kw or not self.filterMinItems <= depth <= self.filterMaxItems)) node.setHidden(hidden) return hidden hide(self.tree.invisibleRootItem(), 0, False) class TreeWidgetItem(QTreeWidgetItem): def data(self, column, role): """Construct lazy tooltips""" if role != Qt.ToolTipRole: return super().data(column, role) tooltip = [] while self: tooltip.append(self.text(0)) self = self.parent() return ' '.join(reversed(tooltip)) def find_itemsets(self): if self.data is None: return if self._is_running: return self._is_running = True data = self.data self.tree.clear() self.tree.setUpdatesEnabled(False) self.tree.blockSignals(True) class ItemDict(dict): def __init__(self, item): self.item = item top = ItemDict(self.tree.invisibleRootItem()) X, mapping = OneHot.encode(data) self.onehot_mapping = mapping ITEM_FMT = '{}' if issparse(data.X) else '{}={}' names = { item: ITEM_FMT.format(var.name, val) for item, var, val in OneHot.decode(mapping.keys(), data, mapping) } nItemsets = 0 filterSearch = self.filterSearch filterMinItems, filterMaxItems = self.filterMinItems, self.filterMaxItems isRegexMatch = self.isRegexMatch # Find itemsets and populate the TreeView with self.progressBar(self.maxItemsets + 1) as progress: for itemset, support in frequent_itemsets(X, self.minSupport / 100): if filterSearch and not filterMinItems <= len( itemset) <= filterMaxItems: continue parent = top first_new_item = None itemset_matches_filter = False for item in sorted(itemset): name = names[item] if filterSearch and not itemset_matches_filter: itemset_matches_filter = isRegexMatch(name) child = parent.get(name) if child is None: try: wi = self.TreeWidgetItem(parent.item, [ name, str(support), '{:.4g}'.format( 100 * support / len(data)) ]) except RuntimeError: # FIXME: When autoFind was in effect and the support # slider was moved, this line excepted with: # RuntimeError: wrapped C/C++ object of type # TreeWidgetItem has been deleted return wi.setData(0, self.ITEM_DATA_ROLE, item) child = parent[name] = ItemDict(wi) if first_new_item is None: first_new_item = (parent, name) parent = child if filterSearch and not itemset_matches_filter: parent, name = first_new_item parent.item.removeChild(parent[name].item) del parent[name].item del parent[name] else: nItemsets += 1 progress.advance() if nItemsets >= self.maxItemsets: break if not filterSearch: self.filter_change() self.nItemsets = nItemsets self.nSelectedItemsets = 0 self.nSelectedExamples = 0 self.tree.expandAll() for i in range(self.tree.columnCount()): self.tree.resizeColumnToContents(i) self.tree.setUpdatesEnabled(True) self.tree.blockSignals(False) self._is_running = False def set_data(self, data): self.data = data is_error = False if data is not None: self.warning(0) self.error(1) self.button.setDisabled(False) self.X = data.X if issparse(data.X): self.X = data.X.tocsc() else: if not data.domain.has_discrete_attributes(): self.error( 1, 'Discrete features required but data has none.') is_error = True self.button.setDisabled(True) elif data.domain.has_continuous_attributes(): self.warning( 0, 'Data has continuous attributes which will be skipped.' ) else: self.output = None self.commit() if self.autoFind and not is_error: self.find_itemsets()
class XConsoleEdit(XLoggerWidget): __designer_icon__ = projexui.resources.find('img/ui/console.png') def __init__( self, parent ): super(XConsoleEdit, self).__init__(parent) self.setLogger(logging.getLogger()) # create custom properties self._completerTree = None self._commandStack = [] self._highlighter = XConsoleHighlighter(self.document()) # set properties font = QFont('Courier New') font.setPointSize(9) self.setFont(font) self.setReadOnly(False) self.waitForInput() metrics = QFontMetrics(font) self.setTabStopWidth(4 * metrics.width(' ')) # create connections XStream.stdout().messageWritten.connect( self.information ) XStream.stderr().messageWritten.connect( self.error ) def __del__( self ): XStream.stdout().messageWritten.disconnect( self.information ) XStream.stderr().messageWritten.disconnect( self.error ) def acceptCompletion( self ): """ Accepts the current completion and inserts the code into the edit. :return <bool> accepted """ tree = self._completerTree if not tree: return False tree.hide() item = tree.currentItem() if not item: return False # clear the previously typed code for the block cursor = self.textCursor() text = cursor.block().text() col = cursor.columnNumber() end = col while col: col -= 1 if text[col] == '.': col += 1 break # insert the current text cursor.setPosition(cursor.position() - (end-col), cursor.KeepAnchor) cursor.removeSelectedText() self.insertPlainText(item.text(0)) return True def applyCommand( self ): """ Applies the current line of code as an interactive python command. """ # grab the command from the current line block = self.textCursor().block().text() match = INPUT_EXPR.match(projex.text.toUtf8(block)) # if there is no command, then wait for the input if not match: self.waitForInput() return self.executeCommand(match.group(2).rstrip(), match.group(1).strip()) def cancelCompletion( self ): """ Cancels the current completion. """ if ( self._completerTree ): self._completerTree.hide() def clear( self ): """ Clears the current text and starts a new input line. """ super(XConsoleEdit, self).clear() self.waitForInput() def closeEvent( self, event ): """ Disconnects from the stderr/stdout on close. :param event | <QCloseEvent> """ if ( self.clearOnClose() ): XStream.stdout().messageWritten.disconnect( self.information ) XStream.stderr().messageWritten.disconnect( self.error ) super(XConsoleEdit, self).closeEvent(event) def completerTree( self ): """ Returns the completion tree for this instance. :return <QTreeWidget> """ if ( not self._completerTree ): self._completerTree = QTreeWidget(self) self._completerTree.setWindowFlags(Qt.Popup) self._completerTree.setAlternatingRowColors( True ) self._completerTree.installEventFilter(self) self._completerTree.itemClicked.connect( self.acceptCompletion ) self._completerTree.setRootIsDecorated(False) self._completerTree.header().hide() return self._completerTree def eventFilter( self, obj, event ): """ Filters particular events for a given QObject through this class. \ Will use this to intercept events to the completer tree widget while \ filtering. :param obj | <QObject> event | <QEvent> :return <bool> consumed """ if ( not obj == self._completerTree ): return False if ( event.type() != event.KeyPress ): return False if ( event.key() == Qt.Key_Escape ): QToolTip.hideText() self.cancelCompletion() return False elif ( event.key() in (Qt.Key_Enter, Qt.Key_Return, Qt.Key_Tab) ): self.acceptCompletion() return False elif ( event.key() in (Qt.Key_Up, Qt.Key_Down) ): return False else: self.keyPressEvent(event) # update the completer cursor = self.textCursor() text = projex.text.toUtf8(cursor.block().text()) text = text[:cursor.columnNumber()].split(' ')[-1] text = text.split('.')[-1] self._completerTree.blockSignals(True) self._completerTree.setUpdatesEnabled(False) self._completerTree.setCurrentItem(None) for i in range(self._completerTree.topLevelItemCount()): item = self._completerTree.topLevelItem(i) if projex.text.toUtf8(item.text(0)).startswith(text): self._completerTree.setCurrentItem(item) break self._completerTree.blockSignals(False) self._completerTree.setUpdatesEnabled(True) return True def executeCommand(self, command, mode='>>>'): """ Executes the inputed command in the global scope. :param command | <unicode> :return <variant> """ # check to see if we're starting a new line if mode == '...' and not command.strip(): command = '\n'.join(self._commandStack) elif command.endswith(':') or mode == '...': self._commandStack.append(command) self.insertPlainText('\n... ') return # if we're not at the end of the console, then add it to the end elif not self.textCursor().atEnd(): self.waitForInput() self.insertPlainText(command) return self._commandStack = [] # insert a new line self.insertPlainText('\n') cmdresult = None try: cmdresult = eval(command, __main__.__dict__, __main__.__dict__) except SyntaxError: exec(command) in __main__.__dict__, __main__.__dict__ # print the resulting commands if cmdresult != None: self.information(projex.text.toUtf8(cmdresult)) self.waitForInput() def highlighter(self): """ Returns the console highlighter for this widget. :return <XConsoleHighlighter> """ return self._highlighter def gotoHome( self ): """ Navigates to the home position for the edit. """ mode = QTextCursor.MoveAnchor # select the home if QApplication.instance().keyboardModifiers() == Qt.ShiftModifier: mode = QTextCursor.KeepAnchor cursor = self.textCursor() block = projex.text.toUtf8(cursor.block().text()) cursor.movePosition( QTextCursor.StartOfBlock, mode ) if ( block.startswith('>>> ') ): cursor.movePosition( QTextCursor.Right, mode, 4 ) self.setTextCursor(cursor) def keyPressEvent( self, event ): """ Overloads the key press event to control keystroke modifications for \ the console widget. :param event | <QKeyEvent> """ # enter || return keys will apply the command if event.key() in (Qt.Key_Return, Qt.Key_Enter): self.applyCommand() # home key will move the cursor to the home position elif event.key() == Qt.Key_Home: self.gotoHome() elif event.key() in (Qt.Key_Backspace, Qt.Key_Delete): super(XConsoleEdit, self).keyPressEvent(event) # update the completer cursor = self.textCursor() text = projex.text.toUtf8(cursor.block().text()) text = text[:cursor.columnNumber()].split(' ')[-1] if not '.' in text: self.cancelCompletion() # period key will trigger a completion popup elif event.key() == Qt.Key_Period: self.startCompletion() super(XConsoleEdit, self).keyPressEvent(event) # space, tab, backspace and delete will cancel the completion elif event.key() == Qt.Key_Space: self.cancelCompletion() super(XConsoleEdit, self).keyPressEvent(event) # left parenthesis will start method help elif event.key() == Qt.Key_ParenLeft: self.cancelCompletion() self.showMethodToolTip() super(XConsoleEdit, self).keyPressEvent(event) # otherwise, handle the event like normal else: super(XConsoleEdit, self).keyPressEvent(event) def objectAtCursor( self ): """ Returns the python object that the text is representing. :return <object> || None """ # determine the text block cursor = self.textCursor() text = projex.text.toUtf8(cursor.block().text()) col = cursor.columnNumber() end = col while ( col ): col -= 1 if ( re.match('[^a-zA-Z\._\(\)]', text[col]) ): break symbol = text[col:end].rstrip('.(') try: return eval(symbol, __main__.__dict__, __main__.__dict__) except: return None def showMethodToolTip( self ): """ Pops up a tooltip message with the help for the object under the \ cursor. :return <bool> success """ self.cancelCompletion() obj = self.objectAtCursor() if ( not obj ): return False docs = inspect.getdoc(obj) if ( not docs ): return False # determine the cursor position rect = self.cursorRect() cursor = self.textCursor() point = QPoint(rect.left(), rect.top() + 18) QToolTip.showText( self.mapToGlobal(point), docs, self ) return True def startCompletion( self ): """ Starts a new completion popup for the current object. :return <bool> success """ # add the top level items tree = self.completerTree() tree.clear() # make sure we have a valid object obj = self.objectAtCursor() if ( obj is None ): return False # determine the cursor position rect = self.cursorRect() cursor = self.textCursor() point = QPoint(rect.left(), rect.top() + 18) try: o_keys = obj.__dir__() except (AttributeError, TypeError): o_keys = dir(obj) keys = [key for key in sorted(o_keys) if not key.startswith('_')] if ( not keys ): return False for key in keys: tree.addTopLevelItem(QTreeWidgetItem([key])) tree.move(self.mapToGlobal(point)) tree.show() return True def waitForInput( self ): """ Inserts a new input command into the console editor. """ if ( self.isReadOnly() ): return self.moveCursor( QTextCursor.End ) if ( self.textCursor().block().text() == '>>> ' ): return # if there is already text on the line, then start a new line newln = '>>> ' if projex.text.toUtf8(self.textCursor().block().text()): newln = '\n' + newln # insert the text self.setCurrentMode('standard') self.insertPlainText(newln) self.scrollToEnd() self._blankCache = ''
class VersionSelectDialog( QDialog ): def __init__( self, parent ): # initialize the super class super( VersionSelectDialog, self ).__init__( parent ) # create the tree self.uiVersionsTREE = QTreeWidget(self) self.uiVersionsTREE.setAlternatingRowColors(True) self.uiVersionsTREE.setRootIsDecorated(False) self.uiVersionsTREE.setSelectionMode( self.uiVersionsTREE.NoSelection ) header = self.uiVersionsTREE.header() header.setVisible(False) # create the layout layout = QVBoxLayout() layout.addWidget(self.uiVersionsTREE) layout.setContentsMargins(0,0,0,0) # inherit the highlight palette palette = self.palette() palette.setColor(palette.Highlight,parent.palette().color(palette.Highlight)) self.setPalette(palette) # set dialog information self.setLayout(layout) self.setWindowFlags( Qt.Popup ) self.resize(500,250) # create connections self.uiVersionsTREE.itemClicked.connect( self.acceptItem ) def closeEvent( self, event ): # update all the items for this for i in range( self.uiVersionsTREE.topLevelItemCount() ): item = self.uiVersionsTREE.topLevelItem(i) widget = item.versionWidget() version = widget.version() # match the active state version.setActive(widget.isActive()) super(VersionSelectDialog,self).closeEvent(event) def acceptItem( self, item ): # handle version change information widget = item.versionWidget() widget.toggleActive() # accept the dialog self.close() def popup( self, versions ): self.uiVersionsTREE.setUpdatesEnabled(False) self.uiVersionsTREE.blockSignals(True) self.uiVersionsTREE.clear() for version in versions: item = VersionItem(self.uiVersionsTREE, version) self.uiVersionsTREE.addTopLevelItem( item ) self.uiVersionsTREE.setItemWidget( item, 0, item.versionWidget() ) # reset the scrolling self.uiVersionsTREE.verticalScrollBar().setValue(0) self.uiVersionsTREE.setUpdatesEnabled(True) self.uiVersionsTREE.blockSignals(False) return self.exec_()
class OWItemsets(widget.OWWidget): name = "Frequent Itemsets" description = "Explore sets of items that frequently appear together." icon = "icons/FrequentItemsets.svg" priority = 10 inputs = [("Data", Table, "set_data")] outputs = [(Output.DATA, Table)] minSupport = settings.Setting(30) maxItemsets = settings.Setting(10000) filterSearch = settings.Setting(True) autoFind = settings.Setting(False) autoSend = settings.Setting(True) filterKeywords = settings.Setting("") filterMinItems = settings.Setting(1) filterMaxItems = settings.Setting(10000) UserAdviceMessages = [ widget.Message( "Itemset are listed in item-sorted order, i.e. " "an itemset containing A and B is only listed once, as " "A > B (and not also B > A).", "itemsets-order", widget.Message.Warning, ), widget.Message( "To select all the itemsets that are descendants of " "(include) some item X (i.e. the whole subtree), you " "can fold the subtree at that item and then select it.", "itemsets-order", widget.Message.Information, ), ] def __init__(self): self._is_running = False self.isRegexMatch = lambda x: True self.tree = QTreeWidget( self.mainArea, columnCount=2, allColumnsShowFocus=True, alternatingRowColors=True, selectionMode=QTreeWidget.ExtendedSelection, uniformRowHeights=True, ) self.tree.setHeaderLabels(["Itemsets", "Support", "%"]) self.tree.header().setStretchLastSection(True) self.tree.itemSelectionChanged.connect(self.selectionChanged) self.mainArea.layout().addWidget(self.tree) box = gui.widgetBox(self.controlArea, "Info") self.nItemsets = self.nSelectedExamples = self.nSelectedItemsets = "" gui.label(box, self, "Number of itemsets: %(nItemsets)s") gui.label(box, self, "Selected itemsets: %(nSelectedItemsets)s") gui.label(box, self, "Selected examples: %(nSelectedExamples)s") hbox = gui.widgetBox(box, orientation="horizontal") gui.button(hbox, self, "Expand all", callback=self.tree.expandAll) gui.button(hbox, self, "Collapse all", callback=self.tree.collapseAll) box = gui.widgetBox(self.controlArea, "Find itemsets") gui.valueSlider( box, self, "minSupport", values=[0.0001, 0.0005, 0.001, 0.005, 0.01, 0.05, 0.1, 0.5] + list(range(1, 101)), label="Minimal support:", labelFormat="%g%%", callback=lambda: self.find_itemsets(), ) gui.hSlider( box, self, "maxItemsets", minValue=10000, maxValue=100000, step=10000, label="Max. number of itemsets:", labelFormat="%d", callback=lambda: self.find_itemsets(), ) self.button = gui.auto_commit(box, self, "autoFind", "Find itemsets", commit=self.find_itemsets) box = gui.widgetBox(self.controlArea, "Filter itemsets") gui.lineEdit( box, self, "filterKeywords", "Contains:", callback=self.filter_change, orientation="horizontal", tooltip="A comma or space-separated list of regular " "expressions.", ) hbox = gui.widgetBox(box, orientation="horizontal") gui.spin(hbox, self, "filterMinItems", 1, 998, label="Min. items:", callback=self.filter_change) gui.spin(hbox, self, "filterMaxItems", 2, 999, label="Max. items:", callback=self.filter_change) gui.checkBox( box, self, "filterSearch", label="Apply these filters in search", tooltip="If checked, the itemsets are filtered according " "to these filter conditions already in the search " "phase. \nIf unchecked, the only filters applied " "during search are the ones above, " "and the itemsets are \nfiltered afterwards only for " "display, i.e. only the matching itemsets are shown.", ) gui.rubber(hbox) gui.rubber(self.controlArea) gui.auto_commit(self.controlArea, self, "autoSend", "Send selection") self.filter_change() ITEM_DATA_ROLE = Qt.UserRole + 1 def selectionChanged(self): X = self.X mapping = self.onehot_mapping instances = set() where = np.where def whole_subtree(node): yield node for i in range(node.childCount()): yield from whole_subtree(node.child(i)) def itemset(node): while node: yield node.data(0, self.ITEM_DATA_ROLE) node = node.parent() def selection_ranges(node): n_children = node.childCount() if n_children: yield (self.tree.indexFromItem(node.child(0)), self.tree.indexFromItem(node.child(n_children - 1))) for i in range(n_children): yield from selection_ranges(node.child(i)) nSelectedItemsets = 0 item_selection = QItemSelection() for node in self.tree.selectedItems(): nodes = (node,) if node.isExpanded() else whole_subtree(node) if not node.isExpanded(): for srange in selection_ranges(node): item_selection.select(*srange) for node in nodes: nSelectedItemsets += 1 cols, vals = zip(*(mapping[i] for i in itemset(node))) if issparse(X): rows = (len(cols) == np.bincount((X[:, cols] != 0).indices, minlength=X.shape[0])).nonzero()[0] else: rows = where((X[:, cols] == vals).all(axis=1))[0] instances.update(rows) self.tree.itemSelectionChanged.disconnect(self.selectionChanged) self.tree.selectionModel().select(item_selection, QItemSelectionModel.Select | QItemSelectionModel.Rows) self.tree.itemSelectionChanged.connect(self.selectionChanged) self.nSelectedExamples = len(instances) self.nSelectedItemsets = nSelectedItemsets self.output = self.data[sorted(instances)] or None self.commit() def commit(self): self.send(Output.DATA, self.output) def filter_change(self): self.warning(9) try: isRegexMatch = self.isRegexMatch = re.compile( "|".join(i.strip() for i in re.split("(,|\s)+", self.filterKeywords.strip()) if i.strip()), re.IGNORECASE, ).search except Exception as e: self.warning(9, "Error in regular expression: {}".format(e.args[0])) isRegexMatch = self.isRegexMatch = lambda x: True def hide(node, depth, has_kw): if not has_kw: has_kw = isRegexMatch(node.text(0)) hidden = ( sum(hide(node.child(i), depth + 1, has_kw) for i in range(node.childCount())) == node.childCount() if node.childCount() else (not has_kw or not self.filterMinItems <= depth <= self.filterMaxItems) ) node.setHidden(hidden) return hidden hide(self.tree.invisibleRootItem(), 0, False) class TreeWidgetItem(QTreeWidgetItem): def data(self, column, role): """Construct lazy tooltips""" if role != Qt.ToolTipRole: return super().data(column, role) tooltip = [] while self: tooltip.append(self.text(0)) self = self.parent() return " ".join(reversed(tooltip)) def find_itemsets(self): if self.data is None: return if self._is_running: return self._is_running = True data = self.data self.tree.clear() self.tree.setUpdatesEnabled(False) self.tree.blockSignals(True) class ItemDict(dict): def __init__(self, item): self.item = item top = ItemDict(self.tree.invisibleRootItem()) X, mapping = OneHot.encode(data) self.onehot_mapping = mapping ITEM_FMT = "{}" if issparse(data.X) else "{}={}" names = { item: ITEM_FMT.format(var.name, val) for item, var, val in OneHot.decode(mapping.keys(), data, mapping) } nItemsets = 0 filterSearch = self.filterSearch filterMinItems, filterMaxItems = self.filterMinItems, self.filterMaxItems isRegexMatch = self.isRegexMatch # Find itemsets and populate the TreeView with self.progressBar(self.maxItemsets + 1) as progress: for itemset, support in frequent_itemsets(X, self.minSupport / 100): if filterSearch and not filterMinItems <= len(itemset) <= filterMaxItems: continue parent = top first_new_item = None itemset_matches_filter = False for item in sorted(itemset): name = names[item] if filterSearch and not itemset_matches_filter: itemset_matches_filter = isRegexMatch(name) child = parent.get(name) if child is None: try: wi = self.TreeWidgetItem( parent.item, [name, str(support), "{:.4g}".format(100 * support / len(data))] ) except RuntimeError: # FIXME: When autoFind was in effect and the support # slider was moved, this line excepted with: # RuntimeError: wrapped C/C++ object of type # TreeWidgetItem has been deleted return wi.setData(0, self.ITEM_DATA_ROLE, item) child = parent[name] = ItemDict(wi) if first_new_item is None: first_new_item = (parent, name) parent = child if filterSearch and not itemset_matches_filter: parent, name = first_new_item parent.item.removeChild(parent[name].item) del parent[name].item del parent[name] else: nItemsets += 1 progress.advance() if nItemsets >= self.maxItemsets: break if not filterSearch: self.filter_change() self.nItemsets = nItemsets self.nSelectedItemsets = 0 self.nSelectedExamples = 0 self.tree.expandAll() for i in range(self.tree.columnCount()): self.tree.resizeColumnToContents(i) self.tree.setUpdatesEnabled(True) self.tree.blockSignals(False) self._is_running = False def set_data(self, data): self.data = data is_error = False if data is not None: self.warning(0) self.error(1) self.button.setDisabled(False) self.X = data.X if issparse(data.X): self.X = data.X.tocsc() else: if not data.domain.has_discrete_attributes(): self.error(1, "Discrete features required but data has none.") is_error = True self.button.setDisabled(True) elif data.domain.has_continuous_attributes(): self.warning(0, "Data has continuous attributes which will be skipped.") else: self.output = None self.commit() if self.autoFind and not is_error: self.find_itemsets()
class VersionSelectDialog(QDialog): def __init__(self, parent): # initialize the super class super(VersionSelectDialog, self).__init__(parent) # create the tree self.uiVersionsTREE = QTreeWidget(self) self.uiVersionsTREE.setAlternatingRowColors(True) self.uiVersionsTREE.setRootIsDecorated(False) self.uiVersionsTREE.setSelectionMode(self.uiVersionsTREE.NoSelection) header = self.uiVersionsTREE.header() header.setVisible(False) # create the layout layout = QVBoxLayout() layout.addWidget(self.uiVersionsTREE) layout.setContentsMargins(0, 0, 0, 0) # inherit the highlight palette palette = self.palette() palette.setColor(palette.Highlight, parent.palette().color(palette.Highlight)) self.setPalette(palette) # set dialog information self.setLayout(layout) self.setWindowFlags(Qt.Popup) self.resize(500, 250) # create connections self.uiVersionsTREE.itemClicked.connect(self.acceptItem) def closeEvent(self, event): # update all the items for this for i in range(self.uiVersionsTREE.topLevelItemCount()): item = self.uiVersionsTREE.topLevelItem(i) widget = item.versionWidget() version = widget.version() # match the active state version.setActive(widget.isActive()) super(VersionSelectDialog, self).closeEvent(event) def acceptItem(self, item): # handle version change information widget = item.versionWidget() widget.toggleActive() # accept the dialog self.close() def popup(self, versions): self.uiVersionsTREE.setUpdatesEnabled(False) self.uiVersionsTREE.blockSignals(True) self.uiVersionsTREE.clear() for version in versions: item = VersionItem(self.uiVersionsTREE, version) self.uiVersionsTREE.addTopLevelItem(item) self.uiVersionsTREE.setItemWidget(item, 0, item.versionWidget()) # reset the scrolling self.uiVersionsTREE.verticalScrollBar().setValue(0) self.uiVersionsTREE.setUpdatesEnabled(True) self.uiVersionsTREE.blockSignals(False) return self.exec_()
class XConsoleEdit(XLoggerWidget): __designer_icon__ = projexui.resources.find('img/ui/console.png') def __init__(self, parent): super(XConsoleEdit, self).__init__(parent) self.setLogger(logging.getLogger()) # create custom properties self._completerTree = None self._commandStack = [] self._highlighter = XConsoleHighlighter(self.document()) # set properties font = QFont('Courier New') font.setPointSize(9) self.setFont(font) self.setReadOnly(False) self.waitForInput() metrics = QFontMetrics(font) self.setTabStopWidth(4 * metrics.width(' ')) # create connections XStream.stdout().messageWritten.connect(self.information) XStream.stderr().messageWritten.connect(self.error) def __del__(self): XStream.stdout().messageWritten.disconnect(self.information) XStream.stderr().messageWritten.disconnect(self.error) def acceptCompletion(self): """ Accepts the current completion and inserts the code into the edit. :return <bool> accepted """ tree = self._completerTree if not tree: return False tree.hide() item = tree.currentItem() if not item: return False # clear the previously typed code for the block cursor = self.textCursor() text = cursor.block().text() col = cursor.columnNumber() end = col while col: col -= 1 if text[col] == '.': col += 1 break # insert the current text cursor.setPosition(cursor.position() - (end - col), cursor.KeepAnchor) cursor.removeSelectedText() self.insertPlainText(item.text(0)) return True def applyCommand(self): """ Applies the current line of code as an interactive python command. """ # grab the command from the current line block = self.textCursor().block().text() match = INPUT_EXPR.match(projex.text.toUtf8(block)) # if there is no command, then wait for the input if not match: self.waitForInput() return self.executeCommand(match.group(2).rstrip(), match.group(1).strip()) def cancelCompletion(self): """ Cancels the current completion. """ if (self._completerTree): self._completerTree.hide() def clear(self): """ Clears the current text and starts a new input line. """ super(XConsoleEdit, self).clear() self.waitForInput() def closeEvent(self, event): """ Disconnects from the stderr/stdout on close. :param event | <QCloseEvent> """ if (self.clearOnClose()): XStream.stdout().messageWritten.disconnect(self.information) XStream.stderr().messageWritten.disconnect(self.error) super(XConsoleEdit, self).closeEvent(event) def completerTree(self): """ Returns the completion tree for this instance. :return <QTreeWidget> """ if (not self._completerTree): self._completerTree = QTreeWidget(self) self._completerTree.setWindowFlags(Qt.Popup) self._completerTree.setAlternatingRowColors(True) self._completerTree.installEventFilter(self) self._completerTree.itemClicked.connect(self.acceptCompletion) self._completerTree.setRootIsDecorated(False) self._completerTree.header().hide() return self._completerTree def eventFilter(self, obj, event): """ Filters particular events for a given QObject through this class. \ Will use this to intercept events to the completer tree widget while \ filtering. :param obj | <QObject> event | <QEvent> :return <bool> consumed """ if (not obj == self._completerTree): return False if (event.type() != event.KeyPress): return False if (event.key() == Qt.Key_Escape): QToolTip.hideText() self.cancelCompletion() return False elif (event.key() in (Qt.Key_Enter, Qt.Key_Return, Qt.Key_Tab)): self.acceptCompletion() return False elif (event.key() in (Qt.Key_Up, Qt.Key_Down)): return False else: self.keyPressEvent(event) # update the completer cursor = self.textCursor() text = projex.text.toUtf8(cursor.block().text()) text = text[:cursor.columnNumber()].split(' ')[-1] text = text.split('.')[-1] self._completerTree.blockSignals(True) self._completerTree.setUpdatesEnabled(False) self._completerTree.setCurrentItem(None) for i in range(self._completerTree.topLevelItemCount()): item = self._completerTree.topLevelItem(i) if projex.text.toUtf8(item.text(0)).startswith(text): self._completerTree.setCurrentItem(item) break self._completerTree.blockSignals(False) self._completerTree.setUpdatesEnabled(True) return True def executeCommand(self, command, mode='>>>'): """ Executes the inputed command in the global scope. :param command | <unicode> :return <variant> """ # check to see if we're starting a new line if mode == '...' and not command.strip(): command = '\n'.join(self._commandStack) elif command.endswith(':') or mode == '...': self._commandStack.append(command) self.insertPlainText('\n... ') return # if we're not at the end of the console, then add it to the end elif not self.textCursor().atEnd(): self.waitForInput() self.insertPlainText(command) return self._commandStack = [] # insert a new line self.insertPlainText('\n') cmdresult = None try: cmdresult = eval(command, __main__.__dict__, __main__.__dict__) except SyntaxError: exec(command) in __main__.__dict__, __main__.__dict__ # print the resulting commands if cmdresult != None: self.information(projex.text.toUtf8(cmdresult)) self.waitForInput() def highlighter(self): """ Returns the console highlighter for this widget. :return <XConsoleHighlighter> """ return self._highlighter def gotoHome(self): """ Navigates to the home position for the edit. """ mode = QTextCursor.MoveAnchor # select the home if QApplication.instance().keyboardModifiers() == Qt.ShiftModifier: mode = QTextCursor.KeepAnchor cursor = self.textCursor() block = projex.text.toUtf8(cursor.block().text()) cursor.movePosition(QTextCursor.StartOfBlock, mode) if (block.startswith('>>> ')): cursor.movePosition(QTextCursor.Right, mode, 4) self.setTextCursor(cursor) def keyPressEvent(self, event): """ Overloads the key press event to control keystroke modifications for \ the console widget. :param event | <QKeyEvent> """ # enter || return keys will apply the command if event.key() in (Qt.Key_Return, Qt.Key_Enter): self.applyCommand() # home key will move the cursor to the home position elif event.key() == Qt.Key_Home: self.gotoHome() elif event.key() in (Qt.Key_Backspace, Qt.Key_Delete): super(XConsoleEdit, self).keyPressEvent(event) # update the completer cursor = self.textCursor() text = projex.text.toUtf8(cursor.block().text()) text = text[:cursor.columnNumber()].split(' ')[-1] if not '.' in text: self.cancelCompletion() # period key will trigger a completion popup elif event.key() == Qt.Key_Period: self.startCompletion() super(XConsoleEdit, self).keyPressEvent(event) # space, tab, backspace and delete will cancel the completion elif event.key() == Qt.Key_Space: self.cancelCompletion() super(XConsoleEdit, self).keyPressEvent(event) # left parenthesis will start method help elif event.key() == Qt.Key_ParenLeft: self.cancelCompletion() self.showMethodToolTip() super(XConsoleEdit, self).keyPressEvent(event) # otherwise, handle the event like normal else: super(XConsoleEdit, self).keyPressEvent(event) def objectAtCursor(self): """ Returns the python object that the text is representing. :return <object> || None """ # determine the text block cursor = self.textCursor() text = projex.text.toUtf8(cursor.block().text()) col = cursor.columnNumber() end = col while (col): col -= 1 if (re.match('[^a-zA-Z\._\(\)]', text[col])): break symbol = text[col:end].rstrip('.(') try: return eval(symbol, __main__.__dict__, __main__.__dict__) except: return None def showMethodToolTip(self): """ Pops up a tooltip message with the help for the object under the \ cursor. :return <bool> success """ self.cancelCompletion() obj = self.objectAtCursor() if (not obj): return False docs = inspect.getdoc(obj) if (not docs): return False # determine the cursor position rect = self.cursorRect() cursor = self.textCursor() point = QPoint(rect.left(), rect.top() + 18) QToolTip.showText(self.mapToGlobal(point), docs, self) return True def startCompletion(self): """ Starts a new completion popup for the current object. :return <bool> success """ # add the top level items tree = self.completerTree() tree.clear() # make sure we have a valid object obj = self.objectAtCursor() if (obj is None): return False # determine the cursor position rect = self.cursorRect() cursor = self.textCursor() point = QPoint(rect.left(), rect.top() + 18) try: o_keys = obj.__dir__() except (AttributeError, TypeError): o_keys = dir(obj) keys = [key for key in sorted(o_keys) if not key.startswith('_')] if (not keys): return False for key in keys: tree.addTopLevelItem(QTreeWidgetItem([key])) tree.move(self.mapToGlobal(point)) tree.show() return True def waitForInput(self): """ Inserts a new input command into the console editor. """ if (self.isReadOnly()): return self.moveCursor(QTextCursor.End) if (self.textCursor().block().text() == '>>> '): return # if there is already text on the line, then start a new line newln = '>>> ' if projex.text.toUtf8(self.textCursor().block().text()): newln = '\n' + newln # insert the text self.setCurrentMode('standard') self.insertPlainText(newln) self.scrollToEnd() self._blankCache = ''