1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
|
from contextlib import closing
from io import StringIO
import pytest
from sklearn import config_context
from sklearn.linear_model import LogisticRegression
from sklearn.neural_network import MLPClassifier
from sklearn.impute import SimpleImputer
from sklearn.decomposition import PCA
from sklearn.decomposition import TruncatedSVD
from sklearn.pipeline import Pipeline
from sklearn.pipeline import FeatureUnion
from sklearn.compose import ColumnTransformer
from sklearn.ensemble import VotingClassifier
from sklearn.feature_selection import SelectPercentile
from sklearn.cluster import Birch
from sklearn.cluster import AgglomerativeClustering
from sklearn.preprocessing import OneHotEncoder
from sklearn.svm import LinearSVC
from sklearn.svm import LinearSVR
from sklearn.tree import DecisionTreeClassifier
from sklearn.multiclass import OneVsOneClassifier
from sklearn.ensemble import StackingClassifier
from sklearn.ensemble import StackingRegressor
from sklearn.gaussian_process import GaussianProcessRegressor
from sklearn.gaussian_process.kernels import RationalQuadratic
from sklearn.utils._estimator_html_repr import _write_label_html
from sklearn.utils._estimator_html_repr import _get_visual_block
from sklearn.utils._estimator_html_repr import estimator_html_repr
@pytest.mark.parametrize("checked", [True, False])
def test_write_label_html(checked):
# Test checking logic and labeling
name = "LogisticRegression"
tool_tip = "hello-world"
with closing(StringIO()) as out:
_write_label_html(out, name, tool_tip, checked=checked)
html_label = out.getvalue()
assert 'LogisticRegression</label>' in html_label
assert html_label.startswith('<div class="sk-label-container">')
assert '<pre>hello-world</pre>' in html_label
if checked:
assert 'checked>' in html_label
@pytest.mark.parametrize('est', ['passthrough', 'drop', None])
def test_get_visual_block_single_str_none(est):
# Test estimators that are represnted by strings
est_html_info = _get_visual_block(est)
assert est_html_info.kind == 'single'
assert est_html_info.estimators == est
assert est_html_info.names == str(est)
assert est_html_info.name_details == str(est)
def test_get_visual_block_single_estimator():
est = LogisticRegression(C=10.0)
est_html_info = _get_visual_block(est)
assert est_html_info.kind == 'single'
assert est_html_info.estimators == est
assert est_html_info.names == est.__class__.__name__
assert est_html_info.name_details == str(est)
def test_get_visual_block_pipeline():
pipe = Pipeline([
('imputer', SimpleImputer()),
('do_nothing', 'passthrough'),
('do_nothing_more', None),
('classifier', LogisticRegression())
])
est_html_info = _get_visual_block(pipe)
assert est_html_info.kind == 'serial'
assert est_html_info.estimators == tuple(step[1] for step in pipe.steps)
assert est_html_info.names == ['imputer: SimpleImputer',
'do_nothing: passthrough',
'do_nothing_more: passthrough',
'classifier: LogisticRegression']
assert est_html_info.name_details == [str(est) for _, est in pipe.steps]
def test_get_visual_block_feature_union():
f_union = FeatureUnion([
('pca', PCA()), ('svd', TruncatedSVD())
])
est_html_info = _get_visual_block(f_union)
assert est_html_info.kind == 'parallel'
assert est_html_info.names == ('pca', 'svd')
assert est_html_info.estimators == tuple(
trans[1] for trans in f_union.transformer_list)
assert est_html_info.name_details == (None, None)
def test_get_visual_block_voting():
clf = VotingClassifier([
('log_reg', LogisticRegression()),
('mlp', MLPClassifier())
])
est_html_info = _get_visual_block(clf)
assert est_html_info.kind == 'parallel'
assert est_html_info.estimators == tuple(trans[1]
for trans in clf.estimators)
assert est_html_info.names == ('log_reg', 'mlp')
assert est_html_info.name_details == (None, None)
def test_get_visual_block_column_transformer():
ct = ColumnTransformer([
('pca', PCA(), ['num1', 'num2']),
('svd', TruncatedSVD, [0, 3])
])
est_html_info = _get_visual_block(ct)
assert est_html_info.kind == 'parallel'
assert est_html_info.estimators == tuple(
trans[1] for trans in ct.transformers)
assert est_html_info.names == ('pca', 'svd')
assert est_html_info.name_details == (['num1', 'num2'], [0, 3])
def test_estimator_html_repr_pipeline():
num_trans = Pipeline(steps=[
('pass', 'passthrough'),
('imputer', SimpleImputer(strategy='median'))
])
cat_trans = Pipeline(steps=[
('imputer', SimpleImputer(strategy='constant',
missing_values='empty')),
('one-hot', OneHotEncoder(drop='first'))
])
preprocess = ColumnTransformer([
('num', num_trans, ['a', 'b', 'c', 'd', 'e']),
('cat', cat_trans, [0, 1, 2, 3])
])
feat_u = FeatureUnion([
('pca', PCA(n_components=1)),
('tsvd', Pipeline([('first', TruncatedSVD(n_components=3)),
('select', SelectPercentile())]))
])
clf = VotingClassifier([
('lr', LogisticRegression(solver='lbfgs', random_state=1)),
('mlp', MLPClassifier(alpha=0.001))
])
pipe = Pipeline([
('preprocessor', preprocess), ('feat_u', feat_u), ('classifier', clf)
])
html_output = estimator_html_repr(pipe)
# top level estimators show estimator with changes
assert str(pipe) in html_output
for _, est in pipe.steps:
assert (f"<div class=\"sk-toggleable__content\">"
f"<pre>{str(est)}") in html_output
# low level estimators do not show changes
with config_context(print_changed_only=True):
assert str(num_trans['pass']) in html_output
assert 'passthrough</label>' in html_output
assert str(num_trans['imputer']) in html_output
for _, _, cols in preprocess.transformers:
assert f"<pre>{cols}</pre>" in html_output
# feature union
for name, _ in feat_u.transformer_list:
assert f"<label>{name}</label>" in html_output
pca = feat_u.transformer_list[0][1]
assert f"<pre>{str(pca)}</pre>" in html_output
tsvd = feat_u.transformer_list[1][1]
first = tsvd['first']
select = tsvd['select']
assert f"<pre>{str(first)}</pre>" in html_output
assert f"<pre>{str(select)}</pre>" in html_output
# voting classifer
for name, est in clf.estimators:
assert f"<label>{name}</label>" in html_output
assert f"<pre>{str(est)}</pre>" in html_output
@pytest.mark.parametrize("final_estimator", [None, LinearSVC()])
def test_stacking_classsifer(final_estimator):
estimators = [('mlp', MLPClassifier(alpha=0.001)),
('tree', DecisionTreeClassifier())]
clf = StackingClassifier(
estimators=estimators, final_estimator=final_estimator)
html_output = estimator_html_repr(clf)
assert str(clf) in html_output
# If final_estimator's default changes from LogisticRegression
# this should be updated
if final_estimator is None:
assert "LogisticRegression(" in html_output
else:
assert final_estimator.__class__.__name__ in html_output
@pytest.mark.parametrize("final_estimator", [None, LinearSVR()])
def test_stacking_regressor(final_estimator):
reg = StackingRegressor(
estimators=[('svr', LinearSVR())], final_estimator=final_estimator)
html_output = estimator_html_repr(reg)
assert str(reg.estimators[0][0]) in html_output
assert "LinearSVR</label>" in html_output
if final_estimator is None:
assert "RidgeCV</label>" in html_output
else:
assert final_estimator.__class__.__name__ in html_output
def test_birch_duck_typing_meta():
# Test duck typing meta estimators with Birch
birch = Birch(n_clusters=AgglomerativeClustering(n_clusters=3))
html_output = estimator_html_repr(birch)
# inner estimators do not show changes
with config_context(print_changed_only=True):
assert f"<pre>{str(birch.n_clusters)}" in html_output
assert "AgglomerativeClustering</label>" in html_output
# outer estimator contains all changes
assert f"<pre>{str(birch)}" in html_output
def test_ovo_classifier_duck_typing_meta():
# Test duck typing metaestimators with OVO
ovo = OneVsOneClassifier(LinearSVC(penalty='l1'))
html_output = estimator_html_repr(ovo)
# inner estimators do not show changes
with config_context(print_changed_only=True):
assert f"<pre>{str(ovo.estimator)}" in html_output
assert "LinearSVC</label>" in html_output
# outter estimator
assert f"<pre>{str(ovo)}" in html_output
def test_duck_typing_nested_estimator():
# Test duck typing metaestimators with GP
kernel = RationalQuadratic(length_scale=1.0, alpha=0.1)
gp = GaussianProcessRegressor(kernel=kernel)
html_output = estimator_html_repr(gp)
assert f"<pre>{str(kernel)}" in html_output
assert f"<pre>{str(gp)}" in html_output
@pytest.mark.parametrize('print_changed_only', [True, False])
def test_one_estimator_print_change_only(print_changed_only):
pca = PCA(n_components=10)
with config_context(print_changed_only=print_changed_only):
pca_repr = str(pca)
html_output = estimator_html_repr(pca)
assert pca_repr in html_output
|