Skip to content

Commit 9e4e5dd

Browse files
authored
Merge pull request #1382 from janezd/sieve-rank
[ENH] Ranking for Sieve, refactoring of Sieve, Mosaic and VizRank
2 parents 503fff2 + d29cf14 commit 9e4e5dd

File tree

15 files changed

+1025
-708
lines changed

15 files changed

+1025
-708
lines changed

Orange/preprocess/preprocess.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,12 @@ class Discretize(Preprocess):
8080
during discretization.
8181
"""
8282

83-
def __init__(self, method=None, remove_const=True):
83+
def __init__(self, method=None, remove_const=True,
84+
discretize_classes=False, discretize_metas=False):
8485
self.method = method
8586
self.remove_const = remove_const
87+
self.discretize_classes = discretize_classes
88+
self.discretize_metas = discretize_metas
8689

8790
def __call__(self, data):
8891
"""
@@ -106,11 +109,17 @@ def transform(var):
106109
else:
107110
return var
108111

112+
def discretized(vars, do_discretize):
113+
if do_discretize:
114+
vars = (transform(var) for var in vars)
115+
vars = [var for var in vars if var is not None]
116+
return vars
117+
109118
method = self.method or discretize.EqualFreq()
110-
attributes = [transform(var) for var in data.domain.attributes]
111-
attributes = [var for var in attributes if var is not None]
112119
domain = Orange.data.Domain(
113-
attributes, data.domain.class_vars, data.domain.metas)
120+
discretized(data.domain.attributes, True),
121+
discretized(data.domain.class_vars, self.discretize_classes),
122+
discretized(data.domain.metas, self.discretize_metas))
114123
return data.from_table(domain, data)
115124

116125

Orange/tests/test_discretize.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,47 @@ def test_keep_constant(self):
174174
self.assertEqual(len(table.domain.attributes),
175175
len(new_table.domain.attributes))
176176

177+
def test_discretize_class(self):
178+
table = data.Table('iris')
179+
domain = table.domain
180+
regr_domain = data.Domain(domain.attributes[:3],
181+
[domain.attributes[3], domain.class_var])
182+
table = data.Table.from_table(regr_domain, table)
183+
184+
discretize = Discretize(remove_const=False)
185+
new_table = discretize(table)
186+
self.assertIs(new_table.domain.class_vars[0],
187+
new_table.domain.class_vars[0])
188+
self.assertIs(new_table.domain.class_vars[1],
189+
new_table.domain.class_vars[1])
190+
191+
discretize = Discretize(remove_const=False, discretize_classes=True)
192+
new_table = discretize(table)
193+
self.assertIsInstance(new_table.domain.class_vars[0], DiscreteVariable)
194+
self.assertIs(new_table.domain.class_vars[1],
195+
new_table.domain.class_vars[1])
196+
197+
def test_discretize_metas(self):
198+
table = data.Table('iris')
199+
domain = table.domain
200+
regr_domain = data.Domain(domain.attributes[:3],
201+
[],
202+
[domain.attributes[3], domain.class_var])
203+
table = data.Table.from_table(regr_domain, table)
204+
205+
discretize = Discretize(remove_const=False)
206+
new_table = discretize(table)
207+
self.assertIs(new_table.domain.metas[0],
208+
new_table.domain.metas[0])
209+
self.assertIs(new_table.domain.metas[1],
210+
new_table.domain.metas[1])
211+
212+
discretize = Discretize(remove_const=False, discretize_metas=True)
213+
new_table = discretize(table)
214+
self.assertIsInstance(new_table.domain.metas[0], DiscreteVariable)
215+
self.assertIs(new_table.domain.metas[1],
216+
new_table.domain.metas[1])
217+
177218

178219
# noinspection PyPep8Naming
179220
class TestDiscretizeTable(TestCase):

Orange/widgets/data/owcolor.py

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,18 @@
1-
"""
2-
Widget for assigning colors to variables
3-
"""
4-
5-
from PyQt4.QtCore import Qt, QAbstractTableModel, QSize
6-
from PyQt4.QtGui import QStyledItemDelegate, QColor, QHeaderView, QFont, \
7-
QColorDialog, QTableView, qRgb, QImage, QBrush, QApplication
81
import numpy as np
2+
from PyQt4.QtCore import Qt, QAbstractTableModel, QSize
3+
from PyQt4.QtGui import (
4+
QColor, QHeaderView, QFont, QColorDialog, QTableView, qRgb, QImage,
5+
QBrush)
96

107
import Orange
118
from Orange.widgets import widget, settings, gui
9+
from Orange.widgets.gui import HorizontalGridDelegate
1210
from Orange.widgets.utils.colorpalette import \
1311
ContinuousPaletteGenerator, ColorPaletteDlg
1412

1513
ColorRole = next(gui.OrangeUserRole)
1614

1715

18-
class HorizontalGridDelegate(QStyledItemDelegate):
19-
"""Delegate that draws a horizontal grid."""
20-
def paint(self, painter, option, index):
21-
# pylint: disable=missing-docstring
22-
painter.save()
23-
painter.setPen(QColor(212, 212, 212))
24-
painter.drawLine(option.rect.bottomLeft(), option.rect.bottomRight())
25-
painter.restore()
26-
QStyledItemDelegate.paint(self, painter, option, index)
27-
28-
2916
# noinspection PyMethodOverriding
3017
class ColorTableModel(QAbstractTableModel):
3118
"""Base color model for discrete and continuous attributes. The model

Orange/widgets/gui.py

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from PyQt4 import QtGui, QtCore
1515
from PyQt4.QtCore import Qt, pyqtSignal as Signal
1616
from PyQt4.QtGui import QCursor, QApplication, QTableView, QHeaderView, \
17-
QStyledItemDelegate
17+
QStyledItemDelegate, QSizePolicy, QColor
1818

1919
# Some Orange widgets might expect this here
2020
from Orange.widgets.webview import WebView as WebviewWidget # pylint: disable=unused-import
@@ -234,7 +234,10 @@ def miscellanea(control, box, parent,
234234
:type sizePolicy: PyQt4.QtQui.QSizePolicy
235235
"""
236236
for prop, val in kwargs.items():
237-
getattr(control, "set" + prop[0].upper() + prop[1:])(val)
237+
if prop == "sizePolicy":
238+
control.setSizePolicy(QSizePolicy(*val))
239+
else:
240+
getattr(control, "set" + prop[0].upper() + prop[1:])(val)
238241
if disabled:
239242
# if disabled==False, do nothing; it can be already disabled
240243
control.setDisabled(disabled)
@@ -249,6 +252,8 @@ def miscellanea(control, box, parent,
249252
box.layout().indexOf(control) == -1:
250253
box.layout().addWidget(control)
251254
if sizePolicy is not None:
255+
if isinstance(sizePolicy, tuple):
256+
sizePolicy = QSizePolicy(*sizePolicy)
252257
(box or control).setSizePolicy(sizePolicy)
253258
if addToLayout and parent and parent.layout() is not None:
254259
parent.layout().addWidget(box or control, stretch)
@@ -1069,8 +1074,8 @@ def button(widget, master, label, callback=None, width=None, height=None,
10691074
activated on pressing Return.
10701075
:type autoDefault: bool
10711076
:param buttonType: the button type (default: `QPushButton`)
1072-
:type buttonType: PyQt4.QtGui.QAbstractButton
1073-
:rtype: PyQt4.QtGui.QAbstractButton
1077+
:type buttonType: PyQt4.QtGui.QPushButton
1078+
:rtype: PyQt4.QtGui.QPushButton
10741079
"""
10751080
button = buttonType(widget)
10761081
if label:
@@ -1350,6 +1355,8 @@ def appendRadioButton(group, label, insertInto=None,
13501355
if tooltip is not None:
13511356
w.setToolTip(tooltip)
13521357
if sizePolicy:
1358+
if isinstance(sizePolicy, tuple):
1359+
sizePolicy = QSizePolicy(*sizePolicy)
13531360
w.setSizePolicy(sizePolicy)
13541361
if addToLayout:
13551362
dest = insertInto or group
@@ -2218,17 +2225,16 @@ def do_commit():
22182225
orientation = Qt.Vertical if checkbox_label else Qt.Horizontal
22192226
b = widgetBox(widget, box=box, orientation=orientation,
22202227
addToLayout=False)
2221-
b.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Maximum)
2228+
b.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Maximum)
22222229

22232230
b.checkbox = cb = checkBox(b, master, value, checkbox_label,
22242231
callback=checkbox_toggled, tooltip=auto_label)
22252232
if _is_horizontal(orientation):
22262233
b.layout().addSpacing(10)
2227-
cb.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
2234+
cb.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
22282235
b.button = btn = button(b, master, label, callback=lambda: do_commit())
22292236
if not checkbox_label:
2230-
btn.setSizePolicy(QtGui.QSizePolicy.Expanding,
2231-
QtGui.QSizePolicy.Preferred)
2237+
btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
22322238
checkbox_toggled()
22332239
setattr(master, commit_name, unconditional_commit)
22342240
misc['addToLayout'] = misc.get('addToLayout', True) and \
@@ -3090,11 +3096,19 @@ def get_bar_brush(self, _, index):
30903096
return QtGui.QBrush(bar_brush)
30913097

30923098

3099+
class HorizontalGridDelegate(QStyledItemDelegate):
3100+
def paint(self, painter, option, index):
3101+
painter.save()
3102+
painter.setPen(QColor(212, 212, 212))
3103+
painter.drawLine(option.rect.bottomLeft(), option.rect.bottomRight())
3104+
painter.restore()
3105+
QStyledItemDelegate.paint(self, painter, option, index)
3106+
3107+
30933108
class VerticalLabel(QtGui.QLabel):
30943109
def __init__(self, text, parent=None):
30953110
super().__init__(text, parent)
3096-
self.setSizePolicy(QtGui.QSizePolicy.Preferred,
3097-
QtGui.QSizePolicy.MinimumExpanding)
3111+
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.MinimumExpanding)
30983112
self.setMaximumWidth(self.sizeHint().width() + 2)
30993113
self.setMargin(4)
31003114

Orange/widgets/utils/__init__.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,9 @@ def getdeepattr(obj, attr, *arg, **kwarg):
3131
return kwarg["default"]
3232
raise
3333

34-
def getHtmlCompatibleString(strVal):
35-
return strVal.replace("<=", "&#8804;").replace(">=","&#8805;").replace("<", "&#60;").replace(">","&#62;").replace("=\\=", "&#8800;")
34+
35+
def to_html(str):
36+
return str.replace("<=", "&#8804;").replace(">=", "&#8805;").\
37+
replace("<", "&#60;").replace(">", "&#62;").replace("=\\=", "&#8800;")
38+
39+
getHtmlCompatibleString = to_html

Orange/widgets/utils/domaineditor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from Orange.data import DiscreteVariable, ContinuousVariable, StringVariable, \
55
TimeVariable
66
from Orange.widgets import gui
7-
from Orange.widgets.data.owcolor import HorizontalGridDelegate
7+
from Orange.widgets.gui import HorizontalGridDelegate
88
from Orange.widgets.utils.itemmodels import TableModel
99

1010

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import contextlib
2+
import time
3+
import warnings
4+
5+
from PyQt4.QtCore import pyqtSignal as Signal, pyqtProperty, QEventLoop
6+
from PyQt4.QtGui import qApp
7+
8+
from Orange.widgets import gui
9+
10+
class ProgressBarMixin:
11+
# Set these here so we avoid having to call `__init__` fromm classes
12+
# that use this mix-in
13+
__progressBarValue = -1
14+
__progressState = 0
15+
startTime = time.time() # used in progressbar
16+
17+
def progressBarInit(self, processEvents=QEventLoop.AllEvents):
18+
"""
19+
Initialize the widget's progress (i.e show and set progress to 0%).
20+
21+
.. note::
22+
This method will by default call `QApplication.processEvents`
23+
with `processEvents`. To suppress this behavior pass
24+
``processEvents=None``.
25+
26+
:param processEvents: Process events flag
27+
:type processEvents: `QEventLoop.ProcessEventsFlags` or `None`
28+
"""
29+
self.startTime = time.time()
30+
self.setWindowTitle(self.captionTitle + " (0% complete)")
31+
32+
if self.__progressState != 1:
33+
self.__progressState = 1
34+
self.processingStateChanged.emit(1)
35+
36+
self.progressBarSet(0, processEvents)
37+
38+
def progressBarSet(self, value, processEvents=QEventLoop.AllEvents):
39+
"""
40+
Set the current progress bar to `value`.
41+
42+
.. note::
43+
This method will by default call `QApplication.processEvents`
44+
with `processEvents`. To suppress this behavior pass
45+
``processEvents=None``.
46+
47+
:param float value: Progress value
48+
:param processEvents: Process events flag
49+
:type processEvents: `QEventLoop.ProcessEventsFlags` or `None`
50+
"""
51+
old = self.__progressBarValue
52+
self.__progressBarValue = value
53+
54+
if value > 0:
55+
if self.__progressState != 1:
56+
warnings.warn("progressBarSet() called without a "
57+
"preceding progressBarInit()",
58+
stacklevel=2)
59+
self.__progressState = 1
60+
self.processingStateChanged.emit(1)
61+
62+
usedTime = max(1, time.time() - self.startTime)
63+
totalTime = 100.0 * usedTime / value
64+
remainingTime = max(0, int(totalTime - usedTime))
65+
hrs = remainingTime // 3600
66+
mins = (remainingTime % 3600) // 60
67+
secs = remainingTime % 60
68+
if hrs > 0:
69+
text = "{}:{:02}:{:02}".format(hrs, mins, secs)
70+
else:
71+
text = "{}:{}:{:02}".format(hrs, mins, secs)
72+
self.setWindowTitle("{} ({:d}%, ETA: {})"
73+
.format(self.captionTitle, value, text))
74+
else:
75+
self.setWindowTitle(self.captionTitle + " (0% complete)")
76+
77+
if old != value:
78+
self.progressBarValueChanged.emit(value)
79+
80+
if processEvents is not None and processEvents is not False:
81+
qApp.processEvents(processEvents)
82+
83+
def progressBarValue(self):
84+
"""Return the state of the progress bar
85+
"""
86+
return self.__progressBarValue
87+
88+
progressBarValue = pyqtProperty(
89+
float, fset=progressBarSet, fget=progressBarValue)
90+
processingState = pyqtProperty(int, fget=lambda self: self.__progressState)
91+
92+
def progressBarAdvance(self, value, processEvents=QEventLoop.AllEvents):
93+
"""
94+
Advance the progress bar.
95+
96+
.. note::
97+
This method will by default call `QApplication.processEvents`
98+
with `processEvents`. To suppress this behavior pass
99+
``processEvents=None``.
100+
101+
Args:
102+
value (int): progress value
103+
processEvents (`QEventLoop.ProcessEventsFlags` or `None`):
104+
process events flag
105+
"""
106+
self.progressBarSet(self.progressBarValue + value, processEvents)
107+
108+
def progressBarFinished(self, processEvents=QEventLoop.AllEvents):
109+
"""
110+
Stop the widget's progress (i.e hide the progress bar).
111+
112+
.. note::
113+
This method will by default call `QApplication.processEvents`
114+
with `processEvents`. To suppress this behavior pass
115+
``processEvents=None``.
116+
117+
:param processEvents: Process events flag
118+
:type processEvents: `QEventLoop.ProcessEventsFlags` or `None`
119+
"""
120+
self.setWindowTitle(self.captionTitle)
121+
if self.__progressState != 0:
122+
self.__progressState = 0
123+
self.processingStateChanged.emit(0)
124+
125+
if processEvents is not None and processEvents is not False:
126+
qApp.processEvents(processEvents)
127+
128+
@contextlib.contextmanager
129+
def progressBar(self, iterations=0):
130+
"""
131+
Context manager for progress bar.
132+
133+
Using it ensures that the progress bar is removed at the end without
134+
needing the `finally` blocks.
135+
136+
Usage:
137+
138+
with self.progressBar(20) as progress:
139+
...
140+
progress.advance()
141+
142+
or
143+
144+
with self.progressBar() as progress:
145+
...
146+
progress.advance(0.15)
147+
148+
or
149+
150+
with self.progressBar():
151+
...
152+
self.progressBarSet(50)
153+
154+
:param iterations: the number of iterations (optional)
155+
:type iterations: int
156+
"""
157+
progress_bar = gui.ProgressBar(self, iterations)
158+
yield progress_bar
159+
progress_bar.finish() # Let us not rely on garbage collector

0 commit comments

Comments
 (0)