Skip to content

Commit

Permalink
Context settings, radiobutton sparse_support check
Browse files Browse the repository at this point in the history
Orange/projection/{base.py, pca.py}: supports_sparse attribute
Orange/widgets/unsupervised/owpca.py:
  - normalize and radio buttons are now context settings.
  - enable/disable radio buttons selectively depending on sparse support
  - method is automatically re-selected to the first method that supports sparse decomposition (avoid hard-coding index).

test_owpca.py: Remove error message test (not needed anymore).
  • Loading branch information
acopar committed May 17, 2017
1 parent ffd8ec8 commit 26bef48
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 18 deletions.
2 changes: 2 additions & 0 deletions Orange/projection/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ class SklProjector(Projector, metaclass=WrapperMeta):
__wraps__ = None
_params = {}
name = 'skl projection'
supports_sparse = True

preprocessors = [Orange.preprocess.Continuize(),
Orange.preprocess.SklImpute()]

Expand Down
3 changes: 3 additions & 0 deletions Orange/projection/pca.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def score(self, data):
class PCA(SklProjector, _FeatureScorerMixin):
__wraps__ = skl_decomposition.PCA
name = 'pca'
supports_sparse = False

def __init__(self, n_components=None, copy=True, whiten=False,
svd_solver='auto', tol=0.0, iterated_power='auto',
Expand Down Expand Up @@ -61,6 +62,7 @@ def fit(self, X, Y=None):
class SparsePCA(SklProjector):
__wraps__ = skl_decomposition.SparsePCA
name = 'sparse pca'
supports_sparse = False

def __init__(self, n_components=None, alpha=1, ridge_alpha=0.01,
max_iter=1000, tol=1e-8, method='lars', n_jobs=1, U_init=None,
Expand Down Expand Up @@ -125,6 +127,7 @@ def pca_variable(i):
class IncrementalPCA(SklProjector):
__wraps__ = skl_decomposition.IncrementalPCA
name = 'incremental pca'
supports_sparse = False

def __init__(self, n_components=None, whiten=False, copy=True,
batch_size=None, preprocessors=None):
Expand Down
48 changes: 36 additions & 12 deletions Orange/widgets/unsupervised/owpca.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,15 @@ class OWPCA(widget.OWWidget):
("Components", Table),
("PCA", PCA)]

settingsHandler = settings.DomainContextHandler()

ncomponents = settings.Setting(2)
variance_covered = settings.Setting(100)
batch_size = settings.Setting(100)
address = settings.Setting('')
auto_update = settings.Setting(True)
auto_commit = settings.Setting(True)
normalize = settings.Setting(True)
normalize = settings.ContextSetting(True)
decomposition_idx = settings.ContextSetting(0)
maxp = settings.Setting(20)
axis_labels = settings.Setting(10)
Expand All @@ -72,7 +74,7 @@ def __init__(self):
self._cumulative = None
self._line = False
self._init_projector()

# Components Selection
box = gui.vBox(self.controlArea, "Components Selection")
form = QFormLayout()
Expand Down Expand Up @@ -124,15 +126,18 @@ def __init__(self):
self.sampling_box.setVisible(remotely)

# Decomposition
self.decomposition_box = gui.radioButtons(self.controlArea, self,
"decomposition_idx", [d.name for d in DECOMPOSITIONS],
box="Decomposition", callback=self._update_decomposition
self.decomposition_box = gui.radioButtons(
self.controlArea, self,
"decomposition_idx", [d.__name__ for d in DECOMPOSITIONS],
box="Decomposition", callback=self._update_decomposition
)

# Options
self.options_box = gui.vBox(self.controlArea, "Options")
self.normalize_box = gui.checkBox(self.options_box, self, "normalize",
"Normalize data", callback=self._update_normalize)
self.normalize_box = gui.checkBox(
self.options_box, self, "normalize",
"Normalize data", callback=self._update_normalize
)

self.maxp_spin = gui.spin(
self.options_box, self, "maxp", 1, MAX_COMPONENTS,
Expand Down Expand Up @@ -169,6 +174,23 @@ def update_model(self):
else:
self.__timer.stop()

def update_buttons_sparse(self):
update_selection = False
if DECOMPOSITIONS[self.decomposition_idx].supports_sparse == False:
update_selection = True

for i, cls in enumerate(DECOMPOSITIONS):
if cls.supports_sparse == False:
self.decomposition_box.group.box.buttons[i].setEnabled(False)
elif update_selection == True:
# Fallback to first method that supports sparse
self.decomposition_idx = i
update_selection = False

def update_buttons_dense(self):
for i in range(len(DECOMPOSITIONS)):
self.decomposition_box.group.box.buttons[i].setEnabled(True)

def start(self):
if 'Abort' in self.start_button.text():
self.rpca.abort()
Expand All @@ -184,6 +206,7 @@ def start(self):
self.start_button.setText("Abort remote computation")

def set_data(self, data):
self.closeContext()
self.clear_messages()
self.clear()
self.start_button.setEnabled(False)
Expand All @@ -203,18 +226,19 @@ def set_data(self, data):
self.start_button.setEnabled(True)
if not isinstance(data, SqlTable):
self.sampling_box.setVisible(False)

self.openContext(data)
if isinstance(data, Table):
if data.is_sparse():
# PCA does not support sparse data
# Falling back to TruncatedSVD aka LSA
self.decomposition_idx = 1
self.normalize = False
self.normalize_box.setEnabled(False)
self.decomposition_box.setEnabled(False)
self.update_buttons_sparse()
else:
self.normalize_box.setEnabled(True)
self.decomposition_box.setEnabled(True)
self.update_buttons_dense()

self._update_decomposition()

if len(data.domain.attributes) == 0:
Expand Down Expand Up @@ -398,7 +422,7 @@ def _init_projector(self):
self._pca_projector = cls(max_components=MAX_COMPONENTS)
self._pca_projector.component = self.ncomponents
self._pca_preprocessors = cls.preprocessors

def _update_decomposition(self):
self.clear_messages()
self._init_projector()
Expand Down
6 changes: 0 additions & 6 deletions Orange/widgets/unsupervised/tests/test_owpca.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,3 @@ def test_variance_shown(self):
self.widget._update_selection_component_spin()
var3 = self.widget.variance_covered
self.assertGreater(var3, var2)

def test_error_on_sparse_data(self):
data = Table('iris')
data.X = sp.csr_matrix(data.X)
self.widget.set_data(data)
self.assertTrue(self.widget.Error.sparse_data.is_shown())

0 comments on commit 26bef48

Please sign in to comment.