Skip to content

Commit

Permalink
Merge pull request #1382 from janezd/sieve-rank
Browse files Browse the repository at this point in the history
[ENH] Ranking for Sieve, refactoring of Sieve, Mosaic and VizRank
  • Loading branch information
astaric authored Jul 1, 2016
2 parents 503fff2 + d29cf14 commit 9e4e5dd
Show file tree
Hide file tree
Showing 15 changed files with 1,025 additions and 708 deletions.
17 changes: 13 additions & 4 deletions Orange/preprocess/preprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,12 @@ class Discretize(Preprocess):
during discretization.
"""

def __init__(self, method=None, remove_const=True):
def __init__(self, method=None, remove_const=True,
discretize_classes=False, discretize_metas=False):
self.method = method
self.remove_const = remove_const
self.discretize_classes = discretize_classes
self.discretize_metas = discretize_metas

def __call__(self, data):
"""
Expand All @@ -106,11 +109,17 @@ def transform(var):
else:
return var

def discretized(vars, do_discretize):
if do_discretize:
vars = (transform(var) for var in vars)
vars = [var for var in vars if var is not None]
return vars

method = self.method or discretize.EqualFreq()
attributes = [transform(var) for var in data.domain.attributes]
attributes = [var for var in attributes if var is not None]
domain = Orange.data.Domain(
attributes, data.domain.class_vars, data.domain.metas)
discretized(data.domain.attributes, True),
discretized(data.domain.class_vars, self.discretize_classes),
discretized(data.domain.metas, self.discretize_metas))
return data.from_table(domain, data)


Expand Down
41 changes: 41 additions & 0 deletions Orange/tests/test_discretize.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,47 @@ def test_keep_constant(self):
self.assertEqual(len(table.domain.attributes),
len(new_table.domain.attributes))

def test_discretize_class(self):
table = data.Table('iris')
domain = table.domain
regr_domain = data.Domain(domain.attributes[:3],
[domain.attributes[3], domain.class_var])
table = data.Table.from_table(regr_domain, table)

discretize = Discretize(remove_const=False)
new_table = discretize(table)
self.assertIs(new_table.domain.class_vars[0],
new_table.domain.class_vars[0])
self.assertIs(new_table.domain.class_vars[1],
new_table.domain.class_vars[1])

discretize = Discretize(remove_const=False, discretize_classes=True)
new_table = discretize(table)
self.assertIsInstance(new_table.domain.class_vars[0], DiscreteVariable)
self.assertIs(new_table.domain.class_vars[1],
new_table.domain.class_vars[1])

def test_discretize_metas(self):
table = data.Table('iris')
domain = table.domain
regr_domain = data.Domain(domain.attributes[:3],
[],
[domain.attributes[3], domain.class_var])
table = data.Table.from_table(regr_domain, table)

discretize = Discretize(remove_const=False)
new_table = discretize(table)
self.assertIs(new_table.domain.metas[0],
new_table.domain.metas[0])
self.assertIs(new_table.domain.metas[1],
new_table.domain.metas[1])

discretize = Discretize(remove_const=False, discretize_metas=True)
new_table = discretize(table)
self.assertIsInstance(new_table.domain.metas[0], DiscreteVariable)
self.assertIs(new_table.domain.metas[1],
new_table.domain.metas[1])


# noinspection PyPep8Naming
class TestDiscretizeTable(TestCase):
Expand Down
23 changes: 5 additions & 18 deletions Orange/widgets/data/owcolor.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,18 @@
"""
Widget for assigning colors to variables
"""

from PyQt4.QtCore import Qt, QAbstractTableModel, QSize
from PyQt4.QtGui import QStyledItemDelegate, QColor, QHeaderView, QFont, \
QColorDialog, QTableView, qRgb, QImage, QBrush, QApplication
import numpy as np
from PyQt4.QtCore import Qt, QAbstractTableModel, QSize
from PyQt4.QtGui import (
QColor, QHeaderView, QFont, QColorDialog, QTableView, qRgb, QImage,
QBrush)

import Orange
from Orange.widgets import widget, settings, gui
from Orange.widgets.gui import HorizontalGridDelegate
from Orange.widgets.utils.colorpalette import \
ContinuousPaletteGenerator, ColorPaletteDlg

ColorRole = next(gui.OrangeUserRole)


class HorizontalGridDelegate(QStyledItemDelegate):
"""Delegate that draws a horizontal grid."""
def paint(self, painter, option, index):
# pylint: disable=missing-docstring
painter.save()
painter.setPen(QColor(212, 212, 212))
painter.drawLine(option.rect.bottomLeft(), option.rect.bottomRight())
painter.restore()
QStyledItemDelegate.paint(self, painter, option, index)


# noinspection PyMethodOverriding
class ColorTableModel(QAbstractTableModel):
"""Base color model for discrete and continuous attributes. The model
Expand Down
34 changes: 24 additions & 10 deletions Orange/widgets/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import Qt, pyqtSignal as Signal
from PyQt4.QtGui import QCursor, QApplication, QTableView, QHeaderView, \
QStyledItemDelegate
QStyledItemDelegate, QSizePolicy, QColor

# Some Orange widgets might expect this here
from Orange.widgets.webview import WebView as WebviewWidget # pylint: disable=unused-import
Expand Down Expand Up @@ -234,7 +234,10 @@ def miscellanea(control, box, parent,
:type sizePolicy: PyQt4.QtQui.QSizePolicy
"""
for prop, val in kwargs.items():
getattr(control, "set" + prop[0].upper() + prop[1:])(val)
if prop == "sizePolicy":
control.setSizePolicy(QSizePolicy(*val))
else:
getattr(control, "set" + prop[0].upper() + prop[1:])(val)
if disabled:
# if disabled==False, do nothing; it can be already disabled
control.setDisabled(disabled)
Expand All @@ -249,6 +252,8 @@ def miscellanea(control, box, parent,
box.layout().indexOf(control) == -1:
box.layout().addWidget(control)
if sizePolicy is not None:
if isinstance(sizePolicy, tuple):
sizePolicy = QSizePolicy(*sizePolicy)
(box or control).setSizePolicy(sizePolicy)
if addToLayout and parent and parent.layout() is not None:
parent.layout().addWidget(box or control, stretch)
Expand Down Expand Up @@ -1069,8 +1074,8 @@ def button(widget, master, label, callback=None, width=None, height=None,
activated on pressing Return.
:type autoDefault: bool
:param buttonType: the button type (default: `QPushButton`)
:type buttonType: PyQt4.QtGui.QAbstractButton
:rtype: PyQt4.QtGui.QAbstractButton
:type buttonType: PyQt4.QtGui.QPushButton
:rtype: PyQt4.QtGui.QPushButton
"""
button = buttonType(widget)
if label:
Expand Down Expand Up @@ -1350,6 +1355,8 @@ def appendRadioButton(group, label, insertInto=None,
if tooltip is not None:
w.setToolTip(tooltip)
if sizePolicy:
if isinstance(sizePolicy, tuple):
sizePolicy = QSizePolicy(*sizePolicy)
w.setSizePolicy(sizePolicy)
if addToLayout:
dest = insertInto or group
Expand Down Expand Up @@ -2218,17 +2225,16 @@ def do_commit():
orientation = Qt.Vertical if checkbox_label else Qt.Horizontal
b = widgetBox(widget, box=box, orientation=orientation,
addToLayout=False)
b.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Maximum)
b.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Maximum)

b.checkbox = cb = checkBox(b, master, value, checkbox_label,
callback=checkbox_toggled, tooltip=auto_label)
if _is_horizontal(orientation):
b.layout().addSpacing(10)
cb.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
cb.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
b.button = btn = button(b, master, label, callback=lambda: do_commit())
if not checkbox_label:
btn.setSizePolicy(QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Preferred)
btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
checkbox_toggled()
setattr(master, commit_name, unconditional_commit)
misc['addToLayout'] = misc.get('addToLayout', True) and \
Expand Down Expand Up @@ -3090,11 +3096,19 @@ def get_bar_brush(self, _, index):
return QtGui.QBrush(bar_brush)


class HorizontalGridDelegate(QStyledItemDelegate):
def paint(self, painter, option, index):
painter.save()
painter.setPen(QColor(212, 212, 212))
painter.drawLine(option.rect.bottomLeft(), option.rect.bottomRight())
painter.restore()
QStyledItemDelegate.paint(self, painter, option, index)


class VerticalLabel(QtGui.QLabel):
def __init__(self, text, parent=None):
super().__init__(text, parent)
self.setSizePolicy(QtGui.QSizePolicy.Preferred,
QtGui.QSizePolicy.MinimumExpanding)
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.MinimumExpanding)
self.setMaximumWidth(self.sizeHint().width() + 2)
self.setMargin(4)

Expand Down
8 changes: 6 additions & 2 deletions Orange/widgets/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,9 @@ def getdeepattr(obj, attr, *arg, **kwarg):
return kwarg["default"]
raise

def getHtmlCompatibleString(strVal):
return strVal.replace("<=", "&#8804;").replace(">=","&#8805;").replace("<", "&#60;").replace(">","&#62;").replace("=\\=", "&#8800;")

def to_html(str):
return str.replace("<=", "&#8804;").replace(">=", "&#8805;").\
replace("<", "&#60;").replace(">", "&#62;").replace("=\\=", "&#8800;")

getHtmlCompatibleString = to_html
2 changes: 1 addition & 1 deletion Orange/widgets/utils/domaineditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from Orange.data import DiscreteVariable, ContinuousVariable, StringVariable, \
TimeVariable
from Orange.widgets import gui
from Orange.widgets.data.owcolor import HorizontalGridDelegate
from Orange.widgets.gui import HorizontalGridDelegate
from Orange.widgets.utils.itemmodels import TableModel


Expand Down
159 changes: 159 additions & 0 deletions Orange/widgets/utils/progressbar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import contextlib
import time
import warnings

from PyQt4.QtCore import pyqtSignal as Signal, pyqtProperty, QEventLoop
from PyQt4.QtGui import qApp

from Orange.widgets import gui

class ProgressBarMixin:
# Set these here so we avoid having to call `__init__` fromm classes
# that use this mix-in
__progressBarValue = -1
__progressState = 0
startTime = time.time() # used in progressbar

def progressBarInit(self, processEvents=QEventLoop.AllEvents):
"""
Initialize the widget's progress (i.e show and set progress to 0%).
.. note::
This method will by default call `QApplication.processEvents`
with `processEvents`. To suppress this behavior pass
``processEvents=None``.
:param processEvents: Process events flag
:type processEvents: `QEventLoop.ProcessEventsFlags` or `None`
"""
self.startTime = time.time()
self.setWindowTitle(self.captionTitle + " (0% complete)")

if self.__progressState != 1:
self.__progressState = 1
self.processingStateChanged.emit(1)

self.progressBarSet(0, processEvents)

def progressBarSet(self, value, processEvents=QEventLoop.AllEvents):
"""
Set the current progress bar to `value`.
.. note::
This method will by default call `QApplication.processEvents`
with `processEvents`. To suppress this behavior pass
``processEvents=None``.
:param float value: Progress value
:param processEvents: Process events flag
:type processEvents: `QEventLoop.ProcessEventsFlags` or `None`
"""
old = self.__progressBarValue
self.__progressBarValue = value

if value > 0:
if self.__progressState != 1:
warnings.warn("progressBarSet() called without a "
"preceding progressBarInit()",
stacklevel=2)
self.__progressState = 1
self.processingStateChanged.emit(1)

usedTime = max(1, time.time() - self.startTime)
totalTime = 100.0 * usedTime / value
remainingTime = max(0, int(totalTime - usedTime))
hrs = remainingTime // 3600
mins = (remainingTime % 3600) // 60
secs = remainingTime % 60
if hrs > 0:
text = "{}:{:02}:{:02}".format(hrs, mins, secs)
else:
text = "{}:{}:{:02}".format(hrs, mins, secs)
self.setWindowTitle("{} ({:d}%, ETA: {})"
.format(self.captionTitle, value, text))
else:
self.setWindowTitle(self.captionTitle + " (0% complete)")

if old != value:
self.progressBarValueChanged.emit(value)

if processEvents is not None and processEvents is not False:
qApp.processEvents(processEvents)

def progressBarValue(self):
"""Return the state of the progress bar
"""
return self.__progressBarValue

progressBarValue = pyqtProperty(
float, fset=progressBarSet, fget=progressBarValue)
processingState = pyqtProperty(int, fget=lambda self: self.__progressState)

def progressBarAdvance(self, value, processEvents=QEventLoop.AllEvents):
"""
Advance the progress bar.
.. note::
This method will by default call `QApplication.processEvents`
with `processEvents`. To suppress this behavior pass
``processEvents=None``.
Args:
value (int): progress value
processEvents (`QEventLoop.ProcessEventsFlags` or `None`):
process events flag
"""
self.progressBarSet(self.progressBarValue + value, processEvents)

def progressBarFinished(self, processEvents=QEventLoop.AllEvents):
"""
Stop the widget's progress (i.e hide the progress bar).
.. note::
This method will by default call `QApplication.processEvents`
with `processEvents`. To suppress this behavior pass
``processEvents=None``.
:param processEvents: Process events flag
:type processEvents: `QEventLoop.ProcessEventsFlags` or `None`
"""
self.setWindowTitle(self.captionTitle)
if self.__progressState != 0:
self.__progressState = 0
self.processingStateChanged.emit(0)

if processEvents is not None and processEvents is not False:
qApp.processEvents(processEvents)

@contextlib.contextmanager
def progressBar(self, iterations=0):
"""
Context manager for progress bar.
Using it ensures that the progress bar is removed at the end without
needing the `finally` blocks.
Usage:
with self.progressBar(20) as progress:
...
progress.advance()
or
with self.progressBar() as progress:
...
progress.advance(0.15)
or
with self.progressBar():
...
self.progressBarSet(50)
:param iterations: the number of iterations (optional)
:type iterations: int
"""
progress_bar = gui.ProgressBar(self, iterations)
yield progress_bar
progress_bar.finish() # Let us not rely on garbage collector
Loading

0 comments on commit 9e4e5dd

Please sign in to comment.