Skip to content

Commit bb1c1c7

Browse files
committed
Merge branch 'master' into fixes/owhierarchicalclustering-colors-index-error
2 parents ab83660 + 876acb2 commit bb1c1c7

File tree

140 files changed

+7257
-584
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

140 files changed

+7257
-584
lines changed

.coveragerc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ omit =
1010
*/tests/*
1111
*/setup.py
1212
*/*/setup.py
13+
Orange/classification/utils/fasterrisk/*
1314

1415
[report]
1516
exclude_lines =

.github/workflows/build-wheels.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ jobs:
7676

7777
- name: Build sdist (pep517)
7878
run: |
79-
python -m pip install pep517 numpy cython
80-
python -m pep517.build -s .
79+
python -m pip install build numpy cython
80+
python -m build -s
8181
8282
- name: Upload sdist
8383
uses: actions/upload-artifact@v4

CHANGELOG.md

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,43 @@ Change Log
55
------------
66

77

8+
[3.38.1] - 2024-12-23
9+
--------------------
10+
##### Enhancements
11+
* Scatter Plot: Error Bars ([#6934](../../pull/6934))
12+
13+
##### Bugfixes
14+
* 1 line headers: only allow single-letter types and flags ([#6959](../../pull/6959))
15+
* Group By: fix categorical aggregations ([#6958](../../pull/6958))
16+
* Orange can be imported without Qt (fixed importing localization) ([#6955](../../pull/6955))
17+
* Fix documentation in packages: fix sphinx build command ([#6950](../../pull/6950))
18+
* httpx 0.28 support: use mounts instead of proxies ([#6943](../../pull/6943))
19+
* owpredicttions: Remove special magic value 2 ([#6933](../../pull/6933))
20+
21+
22+
[3.38.0] - 2024-11-15
23+
--------------------
24+
##### Enhancements
25+
* Parameter Fitter: Basic implementation ([#6921](../../pull/6921))
26+
* Datasets: Add domain field; respect "Unlisted" ([#6920](../../pull/6920))
27+
* Datasets: Let the filter override domain and language ([#6930](../../pull/6930))
28+
* ScoringSheet and ScoringSheetViewer widgets ([#6817](../../pull/6817))
29+
* Multilingual package and installation ([#6828](../../pull/6828))
30+
31+
##### Bugfixes
32+
* Calibrated Learner: Prevent in place modification of base learner ([#6917](../../pull/6917))
33+
* ListView: Fix selection ([#6823](../../pull/6823))
34+
* plotutils: Fix scene layout tracking in AnchorItem ([#6859](../../pull/6859))
35+
* oweditdomain: Hide "categories mapping" warning on change ([#6865](../../pull/6865))
36+
* Logistic regression: fix penalty argument for no regularization ([#6886](../../pull/6886))
37+
* Avoid slowdowns by caching Domain.__eq__ ([#6764](../../pull/6764))
38+
* SVM: degree has to be an integer ([#6866](../../pull/6866))
39+
* Only deepcopy the .attributes for the outermost Table transformation ([#6849](../../pull/6849))
40+
* Line plot: compatibility with scipy 1.14 ([#6845](../../pull/6845))
41+
* PCA: ensure tests pass on sklearn 1.4 and 1.5, which can return different results ([#6821](../../pull/6821))
42+
* Remove Orange implementation of randomized PCA for sparse data ([#6815](../../pull/6815))
43+
44+
845
[3.37.0] - 2024-05-27
946
--------------------
1047
##### Enhancements
@@ -1874,7 +1911,9 @@ Change Log
18741911
* Initial version based on Python 1.5.2 and Qt 2.3
18751912

18761913

1877-
[next]: https://github.com/biolab/orange3/compare/3.37.0..HEAD
1914+
[next]: https://github.com/biolab/orange3/compare/3.38.1..HEAD
1915+
[3.38.1]: https://github.com/biolab/orange3/compare/3.38.0...3.38.1
1916+
[3.38.0]: https://github.com/biolab/orange3/compare/3.37.0...3.38.0
18781917
[3.37.0]: https://github.com/biolab/orange3/compare/3.36.2...3.37.0
18791918
[3.36.2]: https://github.com/biolab/orange3/compare/3.36.1...3.36.2
18801919
[3.36.1]: https://github.com/biolab/orange3/compare/3.36.0...3.36.1

MANIFEST.in

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ recursive-include distribute *.svg *.desktop
1616

1717
include requirements*.txt
1818

19+
recursive-include i18n *.jaml *.yaml
20+
21+
recursive-include doc/visual-programming/build/htmlhelp *
22+
1923
include README.md
2024
include README.pypi
2125
include CONTRIBUTING.md

Orange/base.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from collections.abc import Iterable
44
import re
55
import warnings
6-
from typing import Callable, Dict, Optional
6+
from typing import Callable, Optional, NamedTuple, Type
77

88
import numpy as np
99
import scipy
@@ -88,6 +88,13 @@ class Learner(ReprableWithPreprocessors):
8888
#: fitting the model
8989
preprocessors = ()
9090

91+
class FittedParameter(NamedTuple):
92+
name: str
93+
label: str
94+
type: Type
95+
min: Optional[int] = None
96+
max: Optional[int] = None
97+
9198
# Note: Do not use this class attribute.
9299
# It remains here for compatibility reasons.
93100
learner_adequacy_err_msg = ''
@@ -179,6 +186,10 @@ def active_preprocessors(self):
179186
self.preprocessors is not type(self).preprocessors):
180187
yield from type(self).preprocessors
181188

189+
@property
190+
def fitted_parameters(self) -> list:
191+
return []
192+
182193
# pylint: disable=no-self-use
183194
def incompatibility_reason(self, _: Domain) -> Optional[str]:
184195
"""Return None if a learner can fit domain or string explaining why it can not."""
@@ -883,5 +894,5 @@ def __init__(self, preprocessors=None, **kwargs):
883894
self.params = kwargs
884895

885896
@SklLearner.params.setter
886-
def params(self, values: Dict):
897+
def params(self, values: dict):
887898
self._params = values

Orange/classification/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from .sgd import *
2121
from .neural_network import *
2222
from .calibration import *
23+
from .scoringsheet import *
2324
try:
2425
from .catgb import *
2526
except ModuleNotFoundError:

Orange/classification/logistic_regression.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
import warnings
2+
13
import numpy as np
24
import sklearn.linear_model as skl_linear_model
35

46
from Orange.classification import SklLearner, SklModel
57
from Orange.preprocess import Normalize
68
from Orange.preprocess.score import LearnerScorer
79
from Orange.data import Variable, DiscreteVariable
10+
from Orange.util import OrangeDeprecationWarning
11+
812

913
__all__ = ["LogisticRegressionLearner"]
1014

@@ -38,18 +42,29 @@ class LogisticRegressionLearner(SklLearner, _FeatureScorerMixin):
3842
def __init__(self, penalty="l2", dual=False, tol=0.0001, C=1.0,
3943
fit_intercept=True, intercept_scaling=1, class_weight=None,
4044
random_state=None, solver="auto", max_iter=100,
41-
multi_class="auto", verbose=0, n_jobs=1, preprocessors=None):
45+
multi_class="deprecated", verbose=0, n_jobs=1, preprocessors=None):
46+
if multi_class != "deprecated":
47+
warnings.warn("The multi_class parameter was "
48+
"deprecated in scikit-learn 1.5. Using it with "
49+
"scikit-learn 1.7 will lead to a crash.",
50+
OrangeDeprecationWarning,
51+
stacklevel=2)
4252
super().__init__(preprocessors=preprocessors)
4353
self.params = vars()
4454

4555
def _initialize_wrapped(self):
4656
params = self.params.copy()
57+
58+
multi_class = params.pop("multi_class")
59+
if multi_class != "deprecated":
60+
params["multi_class"] = multi_class
61+
4762
# The default scikit-learn solver `lbfgs` (v0.22) does not support the
4863
# l1 penalty.
4964
solver, penalty = params.pop("solver"), params.get("penalty")
5065
if solver == "auto":
5166
if penalty == "l1":
52-
solver = "liblinear"
67+
solver = "saga"
5368
else:
5469
solver = "lbfgs"
5570
params["solver"] = solver
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import numpy as np
2+
from Orange.classification.utils.fasterrisk.fasterrisk import (
3+
RiskScoreOptimizer,
4+
RiskScoreClassifier,
5+
)
6+
7+
from Orange.classification import Learner, Model
8+
from Orange.data import Table, Storage
9+
from Orange.data.filter import HasClass
10+
from Orange.preprocess import Discretize, Impute, Continuize, SelectBestFeatures
11+
from Orange.preprocess.discretize import Binning
12+
from Orange.preprocess.score import ReliefF
13+
14+
15+
def _change_class_var_values(y):
16+
"""
17+
Changes the class variable values from 0 and 1 to -1 and 1 or vice versa.
18+
"""
19+
return np.where(y == 0, -1, np.where(y == -1, 0, y))
20+
21+
22+
class ScoringSheetModel(Model):
23+
def __init__(self, model):
24+
self.model = model
25+
super().__init__()
26+
27+
def predict_storage(self, table):
28+
if not isinstance(table, Storage):
29+
raise TypeError("Data is not a subclass of Orange.data.Storage.")
30+
31+
y_pred = _change_class_var_values(self.model.predict(table.X))
32+
y_prob = self.model.predict_prob(table.X)
33+
34+
scores = np.hstack(((1 - y_prob).reshape(-1, 1), y_prob.reshape(-1, 1)))
35+
return y_pred, scores
36+
37+
38+
class ScoringSheetLearner(Learner):
39+
__returns__ = ScoringSheetModel
40+
preprocessors = [HasClass(), Discretize(method=Binning()), Impute(), Continuize()]
41+
42+
def __init__(
43+
self,
44+
num_attr_after_selection=20,
45+
num_decision_params=5,
46+
max_points_per_param=5,
47+
num_input_features=None,
48+
preprocessors=None,
49+
):
50+
# Set the num_decision_params, max_points_per_param, and num_input_features normally
51+
self.num_decision_params = num_decision_params
52+
self.max_points_per_param = max_points_per_param
53+
self.num_input_features = num_input_features
54+
self.feature_to_group = None
55+
56+
if preprocessors is None:
57+
self.preprocessors = [
58+
*self.preprocessors,
59+
SelectBestFeatures(method=ReliefF(), k=num_attr_after_selection),
60+
]
61+
62+
super().__init__(preprocessors=preprocessors)
63+
64+
def incompatibility_reason(self, domain):
65+
reason = None
66+
if len(domain.class_vars) > 1 and not self.supports_multiclass:
67+
reason = "Too many target variables."
68+
elif not domain.has_discrete_class:
69+
reason = "Categorical class variable expected."
70+
elif len(domain.class_vars[0].values) > 2:
71+
reason = "Too many target variable values."
72+
return reason
73+
74+
def fit_storage(self, table):
75+
if not isinstance(table, Storage):
76+
raise TypeError("Data is not a subclass of Orange.data.Storage.")
77+
elif table.get_nan_count_class() > 0:
78+
raise ValueError("Class variable contains missing values.")
79+
80+
if self.num_input_features is not None:
81+
self._generate_feature_group_index(table)
82+
83+
X, y, _ = table.X, table.Y, table.W if table.has_weights() else None
84+
learner = RiskScoreOptimizer(
85+
X=X,
86+
y=_change_class_var_values(y),
87+
k=self.num_decision_params,
88+
select_top_m=1,
89+
lb=-self.max_points_per_param,
90+
ub=self.max_points_per_param,
91+
group_sparsity=self.num_input_features,
92+
featureIndex_to_groupIndex=self.feature_to_group,
93+
)
94+
95+
self._optimize_decision_params_adjustment(learner)
96+
97+
multipliers, intercepts, coefficients = learner.get_models()
98+
99+
model = RiskScoreClassifier(
100+
multiplier=multipliers[0],
101+
intercept=intercepts[0],
102+
coefficients=coefficients[0],
103+
featureNames=[attribute.name for attribute in table.domain.attributes],
104+
X_train=X if self.num_decision_params > 10 else None,
105+
)
106+
107+
return ScoringSheetModel(model)
108+
109+
def _optimize_decision_params_adjustment(self, learner):
110+
"""
111+
This function attempts to optimize (fit) the learner, reducing the number of decision
112+
parameters ('k')if optimization fails due to being too high.
113+
114+
Sometimes, the number of decision parameters is too high for the
115+
number of input features. Which results in a ValueError.
116+
Continues until successful or 'k' cannot be reduced further.
117+
"""
118+
while True:
119+
try:
120+
learner.optimize()
121+
return True
122+
except ValueError as e:
123+
learner.k -= 1
124+
if learner.k < 1:
125+
# Raise a custom error when k falls below 1
126+
raise ValueError(
127+
"The number of input features is too low for the current settings."
128+
) from e
129+
130+
def _generate_feature_group_index(self, table):
131+
"""
132+
Returns a feature index to group index mapping. The group index is used to group
133+
binarized features that belong to the same original feature.
134+
"""
135+
original_feature_names = [
136+
attribute.compute_value.variable.name
137+
for attribute in table.domain.attributes
138+
]
139+
feature_to_group_index = {
140+
feature: idx for idx, feature in enumerate(set(original_feature_names))
141+
}
142+
feature_to_group = [
143+
feature_to_group_index[feature] for feature in original_feature_names
144+
]
145+
self.feature_to_group = np.asarray(feature_to_group)
146+
147+
148+
if __name__ == "__main__":
149+
mock_learner = ScoringSheetLearner(20, 5, 10, None)
150+
mock_table = Table("https://datasets.biolab.si/core/heart_disease.tab")
151+
mock_model = mock_learner(mock_table)
152+
mock_model(mock_table)

Orange/classification/utils/__init__.py

Whitespace-only changes.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
2+
3+
BSD 3-Clause License
4+
5+
Copyright (c) 2022, Jiachang Liu
6+
All rights reserved.
7+
8+
Redistribution and use in source and binary forms, with or without
9+
modification, are permitted provided that the following conditions are met:
10+
11+
* Redistributions of source code must retain the above copyright notice, this
12+
list of conditions and the following disclaimer.
13+
14+
* Redistributions in binary form must reproduce the above copyright notice,
15+
this list of conditions and the following disclaimer in the documentation
16+
and/or other materials provided with the distribution.
17+
18+
* Neither the name of the copyright holder nor the names of its
19+
contributors may be used to endorse or promote products derived from
20+
this software without specific prior written permission.
21+
22+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
26+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
28+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32+

0 commit comments

Comments
 (0)