scikit-learn/sklearn/tests/test_docstring_parameters.py

259 lines
9.5 KiB
Python
Raw Normal View History

# Authors: Alexandre Gramfort <alexandre.gramfort@inria.fr>
# Raghav RV <rvraghav93@gmail.com>
# License: BSD 3 clause
import inspect
import warnings
import importlib
from pkgutil import walk_packages
from inspect import signature
import numpy as np
import sklearn
from sklearn.utils import IS_PYPY
from sklearn.utils._testing import check_docstring_parameters
from sklearn.utils._testing import _get_func_name
from sklearn.utils._testing import ignore_warnings
2020-05-21 04:03:54 +08:00
from sklearn.utils import all_estimators
from sklearn.utils.estimator_checks import _enforce_estimator_tags_y
from sklearn.utils.estimator_checks import _enforce_estimator_tags_x
from sklearn.utils.estimator_checks import _construct_instance
from sklearn.utils.deprecation import _is_deprecated
from sklearn.externals._pep562 import Pep562
from sklearn.datasets import make_classification
2018-09-19 05:09:07 +08:00
import pytest
# walk_packages() ignores DeprecationWarnings, now we need to ignore
# FutureWarnings
with warnings.catch_warnings():
warnings.simplefilter('ignore', FutureWarning)
PUBLIC_MODULES = set([
2020-03-30 21:41:05 +08:00
pckg[1] for pckg in walk_packages(
prefix='sklearn.',
# mypy error: Module has no attribute "__path__"
path=sklearn.__path__) # type: ignore # mypy issue #1422
if not ("._" in pckg[1] or ".tests." in pckg[1])
])
# functions to ignore args / docstring of
_DOCSTRING_IGNORES = [
'sklearn.utils.deprecation.load_mlcomp',
'sklearn.pipeline.make_pipeline',
'sklearn.pipeline.make_union',
'sklearn.utils.extmath.safe_sparse_dot',
'sklearn.utils._joblib'
]
# Methods where y param should be ignored if y=None by default
_METHODS_IGNORE_NONE_Y = [
'fit',
'score',
'fit_predict',
'fit_transform',
'partial_fit',
'predict'
]
# numpydoc 0.8.0's docscrape tool raises because of collections.abc under
# Python 3.7
@pytest.mark.filterwarnings('ignore::FutureWarning')
2018-09-19 05:09:07 +08:00
@pytest.mark.filterwarnings('ignore::DeprecationWarning')
@pytest.mark.skipif(IS_PYPY, reason='test segfaults on PyPy')
def test_docstring_parameters():
# Test module docstring formatting
# Skip test if numpydoc is not found
pytest.importorskip('numpydoc',
reason="numpydoc is required to test the docstrings")
# XXX unreached code as of v0.22
from numpydoc import docscrape
incorrect = []
for name in PUBLIC_MODULES:
if name == 'sklearn.utils.fixes':
# We cannot always control these docstrings
continue
with warnings.catch_warnings(record=True):
module = importlib.import_module(name)
classes = inspect.getmembers(module, inspect.isclass)
# Exclude imported classes
classes = [cls for cls in classes if cls[1].__module__ == name]
for cname, cls in classes:
this_incorrect = []
if cname in _DOCSTRING_IGNORES or cname.startswith('_'):
continue
if inspect.isabstract(cls):
continue
with warnings.catch_warnings(record=True) as w:
cdoc = docscrape.ClassDoc(cls)
if len(w):
raise RuntimeError('Error for __init__ of %s in %s:\n%s'
% (cls, name, w[0]))
cls_init = getattr(cls, '__init__', None)
if _is_deprecated(cls_init):
continue
elif cls_init is not None:
this_incorrect += check_docstring_parameters(
cls.__init__, cdoc)
for method_name in cdoc.methods:
method = getattr(cls, method_name)
if _is_deprecated(method):
continue
param_ignore = None
# Now skip docstring test for y when y is None
# by default for API reason
if method_name in _METHODS_IGNORE_NONE_Y:
sig = signature(method)
if ('y' in sig.parameters and
sig.parameters['y'].default is None):
param_ignore = ['y'] # ignore y for fit and score
result = check_docstring_parameters(
method, ignore=param_ignore)
this_incorrect += result
incorrect += this_incorrect
functions = inspect.getmembers(module, inspect.isfunction)
# Exclude imported functions
functions = [fn for fn in functions if fn[1].__module__ == name]
for fname, func in functions:
# Don't test private methods / functions
if fname.startswith('_'):
continue
if fname == "configuration" and name.endswith("setup"):
continue
name_ = _get_func_name(func)
if (not any(d in name_ for d in _DOCSTRING_IGNORES) and
not _is_deprecated(func)):
incorrect += check_docstring_parameters(func)
msg = '\n'.join(incorrect)
if len(incorrect) > 0:
raise AssertionError("Docstring Error:\n" + msg)
@ignore_warnings(category=FutureWarning)
def test_tabs():
# Test that there are no tabs in our source files
for importer, modname, ispkg in walk_packages(sklearn.__path__,
prefix='sklearn.'):
if IS_PYPY and ('_svmlight_format_io' in modname or
'feature_extraction._hashing_fast' in modname):
continue
# because we don't import
mod = importlib.import_module(modname)
# TODO: Remove when minimum python version is 3.7
# unwrap to get module because Pep562 backport wraps the original
# module
if isinstance(mod, Pep562):
mod = mod._module
try:
source = inspect.getsource(mod)
except IOError: # user probably should have run "make clean"
continue
assert '\t' not in source, ('"%s" has tabs, please remove them ',
'or add it to the ignore list'
% modname)
@pytest.mark.parametrize('name, Estimator',
all_estimators())
def test_fit_docstring_attributes(name, Estimator):
pytest.importorskip('numpydoc')
from numpydoc import docscrape
doc = docscrape.ClassDoc(Estimator)
attributes = doc['Attributes']
IGNORED = {'CCA', 'ClassifierChain', 'ColumnTransformer',
'CountVectorizer', 'DictVectorizer', 'FeatureUnion',
'GaussianRandomProjection', 'GridSearchCV',
'MultiOutputClassifier', 'MultiOutputRegressor',
'NoSampleWeightWrapper', 'OneVsOneClassifier',
'OutputCodeClassifier', 'Pipeline', 'PLSCanonical',
'PLSRegression', 'PLSSVD', 'RFE', 'RFECV',
'RandomizedSearchCV', 'RegressorChain', 'SelectFromModel',
'SparseCoder', 'SparseRandomProjection',
'SpectralBiclustering', 'StackingClassifier',
'StackingRegressor', 'TfidfVectorizer', 'VotingClassifier',
FEA Successive halving for faster parameter search (#13900) * More flexible grid search interface * added info dict parameter * Put back removed test * renamed info into more_results * Passed grroups as well since we need n_to use get_n_splits(X, y, groups) * port * pep8 * dabl -> sklearn * add _required_parameters * skipping check in rst file if pandas not installed * Update sklearn/model_selection/_search_successive_halving.py Co-Authored-By: Joel Nothman <joel.nothman@gmail.com> * renamed into GridHalvingSearchCV and RandomHalvingSearchCV * Addressed thomas' comments * repr * removed passing group as a parameter to evaluate_candidates * Joels comments * pep8 * reorganized user user guide * renaming * update user guide * remove groups support + pass fit_params * parameter renaming * pep8 * r_i -> resource_iter * fixed r_i issues * examples + removed use of word budget * Added inpute checking tests * added cv_resutlts_ user guide * minor title change * fixed doc layout * Addressed some comments * properly pass down fit_params * change default value of force_exhaust_resources and update doc * should fix doc * Used check_fit_params * Update section about min_resources and number of candidates * Clarified ratio section * Use ~ to refer to classes * fixed doc checks * Apply suggestions from code review Co-authored-by: Joel Nothman <joel.nothman@gmail.com> * Addressed easy comments from Joel * missed some * updated docstring of run_search * Used f strings instead of format * remove candidate duplication checks * fix example * Addressed easy comments * rotate ticks labels * Added discussion in the intro as suggested by Joel * Split examples into sections * minor changes * remove force_exhaust_budget and introduce min_resources=exhaust * some minor validation * Added a n_resources_ attribute * update examples * Addressed comments * passing CV instead of X,y * minor revert for handling fit_params * updated docs * fix len * whatsnew * Add test for sampling when all_list * minor change to top-k * Force CV splits to be consistent across calls * reorder parameters * reduced diff * added tests for top_k * put back doc for groups * not sure what went wrong * put import at its place * some comment * Addressed comments * Added tests for cv_results_ and base estimator inputs * pep8 * avoid monkeypatching * rename df * use Joel's suggestions for testing masks * Made it experimental * Should fix docs * whats new entry * Apply suggestions from code review Co-authored-by: Andreas Mueller <t3kcit@gmail.com> * Addressed comments to docs * Addressed comments in examples * minor doc update * minor renaming in UG * forgot some * some sad note about splitter statefulness :'( * Addressed comments * ratio -> factor Co-authored-by: Joel Nothman <joel.nothman@gmail.com> Co-authored-by: Andreas Mueller <t3kcit@gmail.com>
2020-09-09 23:12:35 +08:00
'VotingRegressor', 'SequentialFeatureSelector',
'HalvingGridSearchCV', 'HalvingRandomSearchCV'}
if Estimator.__name__ in IGNORED or Estimator.__name__.startswith('_'):
pytest.skip("Estimator cannot be fit easily to test fit attributes")
est = _construct_instance(Estimator)
if Estimator.__name__ == 'SelectKBest':
est.k = 2
if Estimator.__name__ == 'DummyClassifier':
est.strategy = "stratified"
if 'PLS' in Estimator.__name__ or 'CCA' in Estimator.__name__:
est.n_components = 1 # default = 2 is invalid for single target.
ENH Add random_state parameter to AffinityPropagation (#16801) * Added value checks and random state parameter to method * Changed default parameter to None instead of 0 * Added numpy RandomState to the check * Replaced inline validation with check_random_state from utils and pointed at glossery * Needed a different default parameter to pass the default way this has been working in the past * Updated to conform with flake8 stds * Add random_state to AffinityPropagation class. * Add test. * Add what's new entry and versionadded directive. * Add PR number. * Fix lint error due to this PR. * Use np.array_equal in test. * Update sklearn/cluster/_affinity_propagation.py Co-Authored-By: Adrin Jalali <adrin.jalali@gmail.com> * Homogenize parametre descriptions, default random_state to None. * Update sklearn/cluster/_affinity_propagation.py Co-Authored-By: Nicolas Hug <contact@nicolas-hug.com> * Update sklearn/cluster/_affinity_propagation.py Co-Authored-By: Nicolas Hug <contact@nicolas-hug.com> * Update sklearn/cluster/_affinity_propagation.py Co-Authored-By: Nicolas Hug <contact@nicolas-hug.com> * Update doc/whats_new/v0.23.rst Co-Authored-By: Nicolas Hug <contact@nicolas-hug.com> * Change test name. * Modify check in test. * Fix lint errors. * Address comment. * Address comment. * Add 'deprecation' and its correspondent test. * Fix lint errors. * Add random_state parameter to tests, to avoid FutureWarnings. * Move warning in fit. Modify tests. * Modify example. * Tentative fix for failures. * Document default value to 0. Revert docstring. * Explicit link to Glossary. * Fix default value. * Remove some warnings from tests. * Validate and test docstring. * Tentative fix. * Tentative fix. * Ignore FutureWarning in fit attribute test. * Set random_state to avoid FutureWarning in test_fit_docstring_attributes. * [doc build] Force documentation build. * Clarify warning message. Co-authored-by: rwoolston.admin <rwoolston.admin@LXO-DS-DEV.afcucorp.local> Co-authored-by: Adrin Jalali <adrin.jalali@gmail.com> Co-authored-by: Nicolas Hug <contact@nicolas-hug.com>
2020-05-05 22:40:38 +08:00
# TO BE REMOVED for v0.25 (avoid FutureWarning)
if Estimator.__name__ == 'AffinityPropagation':
est.random_state = 63
X, y = make_classification(n_samples=20, n_features=3,
n_redundant=0, n_classes=2,
random_state=2)
y = _enforce_estimator_tags_y(est, y)
X = _enforce_estimator_tags_x(est, X)
if '1dlabels' in est._get_tags()['X_types']:
est.fit(y)
elif '2dlabels' in est._get_tags()['X_types']:
est.fit(np.c_[y, y])
else:
est.fit(X, y)
skipped_attributes = {'n_features_in_',
'x_scores_', # For PLS, TODO remove in 0.26
'y_scores_'} # For PLS, TODO remove in 0.26
for attr in attributes:
if attr.name in skipped_attributes:
continue
desc = ' '.join(attr.desc).lower()
# As certain attributes are present "only" if a certain parameter is
# provided, this checks if the word "only" is present in the attribute
# description, and if not the attribute is required to be present.
if 'only ' in desc:
continue
# ignore deprecation warnings
with ignore_warnings(category=FutureWarning):
assert hasattr(est, attr.name)
IGNORED = {'BayesianRidge', 'Birch', 'CCA',
'LarsCV', 'Lasso',
'OrthogonalMatchingPursuit',
'PLSCanonical', 'PLSSVD'}
if Estimator.__name__ in IGNORED:
pytest.xfail(
reason="Estimator has too many undocumented attributes.")
fit_attr = [k for k in est.__dict__.keys() if k.endswith('_')
and not k.startswith('_')]
fit_attr_names = [attr.name for attr in attributes]
undocumented_attrs = set(fit_attr).difference(fit_attr_names)
undocumented_attrs = set(undocumented_attrs).difference(skipped_attributes)
assert not undocumented_attrs,\
"Undocumented attributes: {}".format(undocumented_attrs)