diff --git a/.circleci/config.yml b/.circleci/config.yml
index 90098519eee0f..91f0ce0a92d8e 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1,133 +1,51 @@
version: 2.1
+# Parameters required to trigger the execution
+# of the "doc-min-dependencies" and "doc" jobs
+parameters:
+ GITHUB_RUN_URL:
+ type: string
+ default: "none"
+
jobs:
doc-min-dependencies:
docker:
- image: cimg/python:3.8.12
environment:
- - OMP_NUM_THREADS: 2
- - MKL_NUM_THREADS: 2
- - CONDA_ENV_NAME: testenv
- - PYTHON_VERSION: 3.8
- - NUMPY_VERSION: 'min'
- - SCIPY_VERSION: 'min'
- - MATPLOTLIB_VERSION: 'min'
- - CYTHON_VERSION: 'min'
- - SCIKIT_IMAGE_VERSION: 'min'
- - SPHINX_VERSION: 'min'
- - PANDAS_VERSION: 'min'
- - SPHINX_GALLERY_VERSION: 'min'
- - NUMPYDOC_VERSION: 'min'
- - SPHINX_PROMPT_VERSION: 'min'
- - SPHINXEXT_OPENGRAPH_VERSION: 'min'
+ - GITHUB_ARTIFACT_URL: << pipeline.parameters.GITHUB_RUN_URL >>/doc-min-dependencies.zip
steps:
- checkout
- - run: ./build_tools/circle/checkout_merge_commit.sh
- - restore_cache:
- key: v1-doc-min-deps-datasets-{{ .Branch }}
- - restore_cache:
- keys:
- - doc-min-deps-ccache-{{ .Branch }}
- - doc-min-deps-ccache
- - run: ./build_tools/circle/build_doc.sh
- - save_cache:
- key: doc-min-deps-ccache-{{ .Branch }}-{{ .BuildNum }}
- paths:
- - ~/.ccache
- - ~/.cache/pip
- - save_cache:
- key: v1-doc-min-deps-datasets-{{ .Branch }}
- paths:
- - ~/scikit_learn_data
+ - run: bash build_tools/circle/download_documentation.sh
- store_artifacts:
path: doc/_build/html/stable
destination: doc
- - store_artifacts:
- path: ~/log.txt
- destination: log.txt
doc:
docker:
- image: cimg/python:3.8.12
environment:
- - OMP_NUM_THREADS: 2
- - MKL_NUM_THREADS: 2
- - CONDA_ENV_NAME: testenv
- - PYTHON_VERSION: '3.9'
- - NUMPY_VERSION: 'latest'
- - SCIPY_VERSION: 'latest'
- - MATPLOTLIB_VERSION: 'latest'
- - CYTHON_VERSION: 'latest'
- - SCIKIT_IMAGE_VERSION: 'latest'
- # Bump the sphinx version from time to time. Avoid latest sphinx version
- # that tends to break things slightly too often
- - SPHINX_VERSION: 4.2.0
- - PANDAS_VERSION: 'latest'
- - SPHINX_GALLERY_VERSION: 'latest'
- - NUMPYDOC_VERSION: 'latest'
- - SPHINX_PROMPT_VERSION: 'latest'
- - SPHINXEXT_OPENGRAPH_VERSION: 'latest'
+ - GITHUB_ARTIFACT_URL: << pipeline.parameters.GITHUB_RUN_URL >>/doc.zip
steps:
- checkout
- - run: ./build_tools/circle/checkout_merge_commit.sh
- - restore_cache:
- key: v1-doc-datasets-{{ .Branch }}
- - restore_cache:
- keys:
- - doc-ccache-{{ .Branch }}
- - doc-ccache
- - run: ./build_tools/circle/build_doc.sh
- - save_cache:
- key: doc-ccache-{{ .Branch }}-{{ .BuildNum }}
- paths:
- - ~/.ccache
- - ~/.cache/pip
- - save_cache:
- key: v1-doc-datasets-{{ .Branch }}
- paths:
- - ~/scikit_learn_data
+ - run: bash build_tools/circle/download_documentation.sh
- store_artifacts:
path: doc/_build/html/stable
destination: doc
- - store_artifacts:
- path: ~/log.txt
- destination: log.txt
- # Persists generated documentation so that it can be attached and deployed
- # in the 'deploy' step.
+ # Persists the generated documentation, so that it
+ # can be attached and deployed in the "deploy" job
- persist_to_workspace:
root: doc/_build/html
paths: .
- lint:
- docker:
- - image: cimg/python:3.8.12
- steps:
- - checkout
- - run: ./build_tools/circle/checkout_merge_commit.sh
- - run:
- name: dependencies
- command: pip install flake8
- - run:
- name: linting
- command: ./build_tools/circle/linting.sh
-
linux-arm64:
machine:
image: ubuntu-2004:202101-01
resource_class: arm.medium
environment:
- # Use the latest supported version of python
- - PYTHON_VERSION: '3.9'
- OMP_NUM_THREADS: 2
- OPENBLAS_NUM_THREADS: 2
- - NUMPY_VERSION: 'latest'
- - SCIPY_VERSION: 'latest'
- - CYTHON_VERSION: 'latest'
- - JOBLIB_VERSION: 'latest'
- - THREADPOOLCTL_VERSION: 'latest'
- - PYTEST_VERSION: 'latest'
- - PYTEST_XDIST_VERSION: 'latest'
- - TEST_DOCSTRINGS: 'true'
+ - CONDA_ENV_NAME: testenv
+ - LOCK_FILE: build_tools/circle/py39_conda_forge_linux-aarch64_conda.lock
steps:
- checkout
- run: ./build_tools/circle/checkout_merge_commit.sh
@@ -159,18 +77,23 @@ jobs:
workflows:
version: 2
+
build-doc-and-deploy:
+ when:
+ not:
+ equal: [ "none", << pipeline.parameters.GITHUB_RUN_URL >> ]
+ # The jobs should run only when triggered by the workflow
jobs:
- - lint
- - doc:
- requires:
- - lint
- - doc-min-dependencies:
- requires:
- - lint
+ - doc-min-dependencies
+ - doc
- deploy:
requires:
- doc
+
linux-arm64:
+ when:
+ equal: [ "none", << pipeline.parameters.GITHUB_RUN_URL >> ]
+ # Prevent double execution of this job: on push
+ # by default and when triggered by the workflow
jobs:
- linux-arm64
diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml
new file mode 100644
index 0000000000000..bd6135f4bc2c0
--- /dev/null
+++ b/.github/workflows/build-docs.yml
@@ -0,0 +1,78 @@
+# Workflow to build the documentation
+name: Documentation builder
+
+on:
+ push:
+ branches:
+ - main
+ # Release branches
+ - "[0-9]+.[0-9]+.X"
+ pull_request:
+ branches:
+ - main
+ - "[0-9]+.[0-9]+.X"
+
+jobs:
+ # Build the documentation against the minimum version of the dependencies
+ doc-min-dependencies:
+ # This prevents this workflow from running on a fork.
+ # To test this workflow on a fork, uncomment the following line.
+ if: github.repository == 'scikit-learn/scikit-learn'
+
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout scikit-learn
+ uses: actions/checkout@v2
+ with:
+ # needed by build_doc.sh to compute the list of changed doc files:
+ fetch-depth: 0
+ ref: ${{ github.event.pull_request.head.sha }}
+
+ - name: Setup Python
+ uses: actions/setup-python@v2
+
+ - name: Build documentation
+ run: bash build_tools/github/build_doc.sh
+ env:
+ OMP_NUM_THREADS: 2
+ MKL_NUM_THREADS: 2
+ CONDA_ENV_NAME: testenv
+ LOCK_FILE: build_tools/github/doc_min_dependencies_linux-64_conda.lock
+
+ - name: Upload documentation
+ uses: actions/upload-artifact@v2
+ with:
+ name: doc-min-dependencies
+ path: doc/_build/html/stable
+
+ # Build the documentation against the latest version of the dependencies
+ doc:
+ # This prevents this workflow from running on a fork.
+ # To test this workflow on a fork, uncomment the following line.
+ if: github.repository == 'scikit-learn/scikit-learn'
+
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout scikit-learn
+ uses: actions/checkout@v2
+ with:
+ # needed by build_doc.sh to compute the list of changed doc files:
+ fetch-depth: 0
+ ref: ${{ github.event.pull_request.head.sha }}
+
+ - name: Setup Python
+ uses: actions/setup-python@v2
+
+ - name: Build documentation
+ run: bash build_tools/github/build_doc.sh
+ env:
+ OMP_NUM_THREADS: 2
+ MKL_NUM_THREADS: 2
+ CONDA_ENV_NAME: testenv
+ LOCK_FILE: build_tools/github/doc_linux-64_conda.lock
+
+ - name: Upload documentation
+ uses: actions/upload-artifact@v2
+ with:
+ name: doc
+ path: doc/_build/html/stable
diff --git a/.github/workflows/trigger-hosting.yml b/.github/workflows/trigger-hosting.yml
new file mode 100644
index 0000000000000..63f4def8e0814
--- /dev/null
+++ b/.github/workflows/trigger-hosting.yml
@@ -0,0 +1,30 @@
+# Workflow to trigger the jobs that will host the documentation
+name: Documentation push trigger
+on:
+ workflow_run:
+ # Run the workflow after the separate "Documentation builder" workflow completes
+ workflows: [Documentation builder]
+ types:
+ - completed
+
+jobs:
+ push:
+ runs-on: ubuntu-latest
+ # Run the job only if the "Documentation builder" workflow succeeded
+ # Prevents this workflow from running on a fork.
+ # To test this workflow on a fork remove the `github.repository == scikit-learn/scikit-learn` condition
+ if: github.repository == 'scikit-learn/scikit-learn' && github.event.workflow_run.conclusion == 'success'
+ steps:
+ - name: Checkout scikit-learn
+ uses: actions/checkout@v2
+
+ - name: Trigger hosting jobs
+ run: bash build_tools/github/trigger_hosting.sh
+ env:
+ CIRCLE_CI_TOKEN: ${{ secrets.CIRCLE_CI_TOKEN }}
+ EVENT: ${{ github.event.workflow_run.event }}
+ RUN_ID: ${{ github.event.workflow_run.id }}
+ HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
+ COMMIT_SHA: ${{ github.event.workflow_run.head_sha }}
+ REPO_NAME: ${{ github.event.workflow_run.head_repository.full_name }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
index d6ae51ec333f2..cea651cfda314 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
-*.pyc
+*.pyc*
*.so
*.pyd
*~
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 6519a849852fc..e5a6018df4473 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v2.3.0
+ rev: v4.3.0
hooks:
- id: check-yaml
- id: end-of-file-fixer
@@ -9,13 +9,13 @@ repos:
rev: 22.3.0
hooks:
- id: black
-- repo: https://gitlab.com/pycqa/flake8
- rev: 3.9.2
+- repo: https://github.com/pycqa/flake8
+ rev: 4.0.1
hooks:
- id: flake8
types: [file, python]
- repo: https://github.com/pre-commit/mirrors-mypy
- rev: v0.782
+ rev: v0.961
hooks:
- id: mypy
files: sklearn/
diff --git a/Makefile b/Makefile
index 112b1e68188a0..5ea64dc0d6cac 100644
--- a/Makefile
+++ b/Makefile
@@ -63,6 +63,3 @@ doc-noplot: inplace
code-analysis:
flake8 sklearn | grep -v __init__ | grep -v external
pylint -E -i y sklearn/ -d E1103,E0611,E1101
-
-flake8-diff:
- git diff upstream/main -u -- "*.py" | flake8 --diff
diff --git a/README.rst b/README.rst
index ad101cec6c673..089929278c75d 100644
--- a/README.rst
+++ b/README.rst
@@ -38,7 +38,7 @@
.. |JoblibMinVersion| replace:: 1.0.0
.. |ThreadpoolctlMinVersion| replace:: 2.0.0
.. |MatplotlibMinVersion| replace:: 3.1.2
-.. |Scikit-ImageMinVersion| replace:: 0.14.5
+.. |Scikit-ImageMinVersion| replace:: 0.16.2
.. |PandasMinVersion| replace:: 1.0.5
.. |SeabornMinVersion| replace:: 0.9.0
.. |PytestMinVersion| replace:: 5.0.1
@@ -144,7 +144,7 @@ directory (you will need to have ``pytest`` >= |PyTestMinVersion| installed)::
pytest sklearn
-See the web page https://scikit-learn.org/dev/developers/advanced_installation.html#testing
+See the web page https://scikit-learn.org/dev/developers/contributing.html#testing-and-improving-test-coverage
for more information.
Random number generation can be controlled during testing by setting
@@ -184,6 +184,7 @@ Communication
- Mailing list: https://mail.python.org/mailman/listinfo/scikit-learn
- Gitter: https://gitter.im/scikit-learn/scikit-learn
+- Logos & Branding: https://github.com/scikit-learn/scikit-learn/tree/main/doc/logos
- Blog: https://blog.scikit-learn.org
- Calendar: https://blog.scikit-learn.org/calendar/
- Twitter: https://twitter.com/scikit_learn
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 2a44674ef8610..1c9339308a45a 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -34,13 +34,13 @@ jobs:
versionSpec: '3.9'
- bash: |
# Include pytest compatibility with mypy
- pip install pytest flake8 mypy==0.782 black==22.3.0
+ pip install pytest flake8 mypy==0.961 black==22.3.0
displayName: Install linters
- bash: |
black --check --diff .
displayName: Run black
- bash: |
- ./build_tools/circle/linting.sh
+ ./build_tools/azure/linting.sh
displayName: Run linting
- bash: |
mypy sklearn/
@@ -63,7 +63,7 @@ jobs:
matrix:
pylatest_pip_scipy_dev:
DISTRIB: 'conda-pip-scipy-dev'
- PYTHON_VERSION: '*'
+ LOCK_FILE: './build_tools/azure/pylatest_pip_scipy_dev_linux-64_conda.lock'
CHECK_WARNINGS: 'true'
CHECK_PYTEST_SOFT_DEPENDENCY: 'true'
TEST_DOCSTRINGS: 'true'
@@ -71,6 +71,41 @@ jobs:
# Here we make sure, that they are still run on a regular basis.
SKLEARN_SKIP_NETWORK_TESTS: '0'
+- template: build_tools/azure/posix.yml
+ # Experimental CPython branch without the Global Interpreter Lock:
+ # https://github.com/colesbury/nogil/
+ #
+ # The nogil build relies on a dedicated PyPI-style index to install patched
+ # versions of NumPy, SciPy and Cython maintained by @colesbury and that
+ # include specifc fixes to make them run correctly without relying on the GIL.
+ #
+ # The goal of this CI entry is to make sure that we do not introduce any
+ # dependency on the GIL in scikit-learn itself. An auxiliary goal is to early
+ # detect any regression in the patched build dependencies to report them
+ # upstream. The long-term goal is to be able to stop having to maintain
+ # multiprocessing based workaround / hacks in joblib / loky to make multi-CPU
+ # computing in scikit-learn efficient by default using regular threads.
+ #
+ # If this experimental entry becomes too unstable, feel free to disable it.
+ parameters:
+ name: Linux_nogil
+ vmImage: ubuntu-20.04
+ dependsOn: [git_commit, linting]
+ condition: |
+ and(
+ succeeded(),
+ not(contains(dependencies['git_commit']['outputs']['commit.message'], '[ci skip]')),
+ or(eq(variables['Build.Reason'], 'Schedule'),
+ contains(dependencies['git_commit']['outputs']['commit.message'], '[nogil]'
+ )
+ )
+ )
+ matrix:
+ pylatest_pip_nogil:
+ DISTRIB: 'pip-nogil'
+ LOCK_FILE: './build_tools/azure/python_nogil_lock.txt'
+ COVERAGE: 'false'
+
# Check compilation with intel C++ compiler (ICC)
- template: build_tools/azure/posix.yml
parameters:
@@ -88,9 +123,7 @@ jobs:
matrix:
pylatest_conda_forge_mkl:
DISTRIB: 'conda'
- CONDA_CHANNEL: 'conda-forge'
- PYTHON_VERSION: '*'
- BLAS: 'mkl'
+ LOCK_FILE: 'build_tools/azure/pylatest_conda_forge_mkl_no_coverage_linux-64_conda.lock'
COVERAGE: 'false'
BUILD_WITH_ICC: 'true'
@@ -110,10 +143,9 @@ jobs:
)
matrix:
pypy3:
- DISTRIB: 'conda-mamba-pypy3'
- DOCKER_CONTAINER: 'condaforge/mambaforge-pypy3:4.10.3-5'
- PILLOW_VERSION: 'none'
- PANDAS_VERSION: 'none'
+ DOCKER_CONTAINER: 'condaforge/miniforge3:4.10.3-5'
+ DISTRIB: 'conda-pypy3'
+ LOCK_FILE: './build_tools/azure/pypy3_linux-64_conda.lock'
# Will run all the time regardless of linting outcome.
- template: build_tools/azure/posix.yml
@@ -129,9 +161,7 @@ jobs:
matrix:
pylatest_conda_forge_mkl:
DISTRIB: 'conda'
- CONDA_CHANNEL: 'conda-forge'
- PYTHON_VERSION: '*'
- BLAS: 'mkl'
+ LOCK_FILE: './build_tools/azure/pylatest_conda_forge_mkl_linux-64_conda.lock'
COVERAGE: 'true'
SHOW_SHORT_SUMMARY: 'true'
SKLEARN_TESTS_GLOBAL_RANDOM_SEED: '42' # default global random seed
@@ -150,9 +180,7 @@ jobs:
matrix:
py38_conda_forge_openblas_ubuntu_1804:
DISTRIB: 'conda'
- CONDA_CHANNEL: 'conda-forge'
- PYTHON_VERSION: '3.8'
- BLAS: 'openblas'
+ LOCK_FILE: './build_tools/azure/py38_conda_forge_openblas_ubuntu_1804_linux-64_conda.lock'
COVERAGE: 'false'
BUILD_WITH_ICC: 'false'
SKLEARN_TESTS_GLOBAL_RANDOM_SEED: '0' # non-default seed
@@ -173,21 +201,13 @@ jobs:
# i.e. numpy 1.17.4 and scipy 1.3.3
ubuntu_atlas:
DISTRIB: 'ubuntu'
- JOBLIB_VERSION: 'min'
- PANDAS_VERSION: 'none'
- THREADPOOLCTL_VERSION: 'min'
+ LOCK_FILE: './build_tools/azure/ubuntu_atlas_lock.txt'
COVERAGE: 'false'
SKLEARN_TESTS_GLOBAL_RANDOM_SEED: '1' # non-default seed
# Linux + Python 3.8 build with OpenBLAS
py38_conda_defaults_openblas:
DISTRIB: 'conda'
- CONDA_CHANNEL: 'defaults' # Anaconda main channel
- PYTHON_VERSION: '3.8'
- BLAS: 'openblas'
- NUMPY_VERSION: 'min'
- SCIPY_VERSION: 'min'
- MATPLOTLIB_VERSION: 'min'
- THREADPOOLCTL_VERSION: '2.2.0'
+ LOCK_FILE: './build_tools/azure/py38_conda_defaults_openblas_linux-64_conda.lock'
SKLEARN_ENABLE_DEBUG_CYTHON_DIRECTIVES: '1'
SKLEARN_RUN_FLOAT32_TESTS: '1'
SKLEARN_TESTS_GLOBAL_RANDOM_SEED: '2' # non-default seed
@@ -195,8 +215,7 @@ jobs:
# It runs tests requiring lightgbm, pandas and PyAMG.
pylatest_pip_openblas_pandas:
DISTRIB: 'conda-pip-latest'
- PYTHON_VERSION: '3.9'
- PYTEST_VERSION: '6.2.5'
+ LOCK_FILE: './build_tools/azure/pylatest_pip_openblas_pandas_linux-64_conda.lock'
CHECK_PYTEST_SOFT_DEPENDENCY: 'true'
TEST_DOCSTRINGS: 'true'
CHECK_WARNINGS: 'true'
@@ -214,13 +233,11 @@ jobs:
)
matrix:
debian_atlas_32bit:
- DISTRIB: 'debian-32'
DOCKER_CONTAINER: 'i386/debian:11.2'
- JOBLIB_VERSION: 'min'
+ DISTRIB: 'debian-32'
+ LOCK_FILE: './build_tools/azure/debian_atlas_32bit_lock.txt'
# disable pytest xdist due to unknown bug with 32-bit container
PYTEST_XDIST_VERSION: 'none'
- PYTEST_VERSION: 'min'
- THREADPOOLCTL_VERSION: '2.2.0'
SKLEARN_TESTS_GLOBAL_RANDOM_SEED: '4' # non-default seed
- template: build_tools/azure/posix.yml
@@ -236,12 +253,11 @@ jobs:
matrix:
pylatest_conda_forge_mkl:
DISTRIB: 'conda'
- BLAS: 'mkl'
- CONDA_CHANNEL: 'conda-forge'
+ LOCK_FILE: './build_tools/azure/pylatest_conda_forge_mkl_osx-64_conda.lock'
SKLEARN_TESTS_GLOBAL_RANDOM_SEED: '5' # non-default seed
pylatest_conda_mkl_no_openmp:
DISTRIB: 'conda'
- BLAS: 'mkl'
+ LOCK_FILE: './build_tools/azure/pylatest_conda_mkl_no_openmp_osx-64_conda.lock'
SKLEARN_TEST_NO_OPENMP: 'true'
SKLEARN_SKIP_OPENMP_TEST: 'true'
SKLEARN_TESTS_GLOBAL_RANDOM_SEED: '6' # non-default seed
@@ -259,15 +275,13 @@ jobs:
matrix:
py38_conda_forge_mkl:
DISTRIB: 'conda'
- CONDA_CHANNEL: 'conda-forge'
- PYTHON_VERSION: '3.8'
+ LOCK_FILE: ./build_tools/azure/py38_conda_forge_mkl_win-64_conda.lock
CHECK_WARNINGS: 'true'
- PYTHON_ARCH: '64'
- # Unpin when pytest stalling issue is fixed
- PYTEST_VERSION: '6.2.5'
COVERAGE: 'true'
SKLEARN_TESTS_GLOBAL_RANDOM_SEED: '7' # non-default seed
py38_pip_openblas_32bit:
+ DISTRIB: 'pip-windows'
PYTHON_VERSION: '3.8'
PYTHON_ARCH: '32'
+ LOCK_FILE: ./build_tools/azure/py38_pip_openblas_32bit_lock.txt
SKLEARN_TESTS_GLOBAL_RANDOM_SEED: '8' # non-default seed
diff --git a/benchmarks/bench_multilabel_metrics.py b/benchmarks/bench_multilabel_metrics.py
index 9981184a4af78..2a87b388e91a2 100755
--- a/benchmarks/bench_multilabel_metrics.py
+++ b/benchmarks/bench_multilabel_metrics.py
@@ -34,8 +34,8 @@
FORMATS = {
"sequences": lambda y: [list(np.flatnonzero(s)) for s in y],
"dense": lambda y: y,
- "csr": lambda y: sp.csr_matrix(y),
- "csc": lambda y: sp.csc_matrix(y),
+ "csr": sp.csr_matrix,
+ "csc": sp.csc_matrix,
}
diff --git a/benchmarks/bench_plot_randomized_svd.py b/benchmarks/bench_plot_randomized_svd.py
index c16e21c3e0568..cb9c208e1161d 100644
--- a/benchmarks/bench_plot_randomized_svd.py
+++ b/benchmarks/bench_plot_randomized_svd.py
@@ -107,7 +107,7 @@
# Determine when to switch to batch computation for matrix norms,
# in case the reconstructed (dense) matrix is too large
-MAX_MEMORY = int(2e9)
+MAX_MEMORY = int(4e9)
# The following datasets can be downloaded manually from:
# CIFAR 10: https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
@@ -153,7 +153,7 @@ def get_data(dataset_name):
elif dataset_name == "rcv1":
X = fetch_rcv1().data
elif dataset_name == "CIFAR":
- if handle_missing_dataset(CIFAR_FOLDER) == "skip":
+ if handle_missing_dataset(CIFAR_FOLDER) == 0:
return
X1 = [unpickle("%sdata_batch_%d" % (CIFAR_FOLDER, i + 1)) for i in range(5)]
X = np.vstack(X1)
@@ -323,8 +323,11 @@ def norm_diff(A, norm=2, msg=True, random_state=None):
def scalable_frobenius_norm_discrepancy(X, U, s, V):
- # if the input is not too big, just call scipy
- if X.shape[0] * X.shape[1] < MAX_MEMORY:
+ if not sp.sparse.issparse(X) or (
+ X.shape[0] * X.shape[1] * X.dtype.itemsize < MAX_MEMORY
+ ):
+ # if the input is not sparse or sparse but not too big,
+ # U.dot(np.diag(s).dot(V)) will fit in RAM
A = X - U.dot(np.diag(s).dot(V))
return norm_diff(A, norm="fro")
@@ -498,7 +501,7 @@ def bench_c(datasets, n_comps):
if __name__ == "__main__":
random_state = check_random_state(1234)
- power_iter = np.linspace(0, 6, 7, dtype=int)
+ power_iter = np.arange(0, 6)
n_comps = 50
for dataset_name in datasets:
diff --git a/build_tools/azure/debian_atlas_32bit_lock.txt b/build_tools/azure/debian_atlas_32bit_lock.txt
new file mode 100644
index 0000000000000..0fca5979d8b06
--- /dev/null
+++ b/build_tools/azure/debian_atlas_32bit_lock.txt
@@ -0,0 +1,34 @@
+#
+# This file is autogenerated by pip-compile with python 3.9
+# To update, run:
+#
+# pip-compile --output-file=build_tools/azure/debian_atlas_32bit_lock.txt build_tools/azure/debian_atlas_32bit_requirements.txt
+#
+atomicwrites==1.4.1
+ # via pytest
+attrs==21.4.0
+ # via pytest
+cython==0.29.30
+ # via -r build_tools/azure/debian_atlas_32bit_requirements.txt
+importlib-metadata==4.12.0
+ # via pytest
+joblib==1.0.0
+ # via -r build_tools/azure/debian_atlas_32bit_requirements.txt
+more-itertools==8.13.0
+ # via pytest
+packaging==21.3
+ # via pytest
+pluggy==0.13.1
+ # via pytest
+py==1.11.0
+ # via pytest
+pyparsing==3.0.9
+ # via packaging
+pytest==5.0.1
+ # via -r build_tools/azure/debian_atlas_32bit_requirements.txt
+threadpoolctl==2.2.0
+ # via -r build_tools/azure/debian_atlas_32bit_requirements.txt
+wcwidth==0.2.5
+ # via pytest
+zipp==3.8.1
+ # via importlib-metadata
diff --git a/build_tools/azure/debian_atlas_32bit_requirements.txt b/build_tools/azure/debian_atlas_32bit_requirements.txt
new file mode 100644
index 0000000000000..d856832d474dc
--- /dev/null
+++ b/build_tools/azure/debian_atlas_32bit_requirements.txt
@@ -0,0 +1,7 @@
+# DO NOT EDIT: this file is generated from the specification found in the
+# following script to centralize the configuration for CI builds:
+# build_tools/update_environments_and_lock_files.py
+cython
+joblib==1.0.0 # min
+threadpoolctl==2.2.0
+pytest==5.0.1 # min
diff --git a/build_tools/azure/install.sh b/build_tools/azure/install.sh
index ff89358c0c1f6..e240acadc1ec5 100755
--- a/build_tools/azure/install.sh
+++ b/build_tools/azure/install.sh
@@ -7,27 +7,24 @@ set -x
source build_tools/shared.sh
UNAMESTR=`uname`
+CCACHE_LINKS_DIR="/tmp/ccache"
-make_conda() {
- TO_INSTALL="$@"
- if [[ "$DISTRIB" == *"mamba"* ]]; then
- mamba create -n $VIRTUALENV --yes $TO_INSTALL
+setup_ccache() {
+ CCACHE_BIN=`which ccache || echo ""`
+ if [[ "${CCACHE_BIN}" == "" ]]; then
+ echo "ccache not found, skipping..."
+ elif [[ -d "${CCACHE_LINKS_DIR}" ]]; then
+ echo "ccache already configured, skipping..."
else
- conda config --show
- conda create -n $VIRTUALENV --yes $TO_INSTALL
+ echo "Setting up ccache with CCACHE_DIR=${CCACHE_DIR}"
+ mkdir ${CCACHE_LINKS_DIR}
+ which ccache
+ for name in gcc g++ cc c++ clang clang++ i686-linux-gnu-gcc i686-linux-gnu-c++ x86_64-linux-gnu-gcc x86_64-linux-gnu-c++ x86_64-apple-darwin13.4.0-clang x86_64-apple-darwin13.4.0-clang++; do
+ ln -s ${CCACHE_BIN} "${CCACHE_LINKS_DIR}/${name}"
+ done
+ export PATH="${CCACHE_LINKS_DIR}:${PATH}"
+ ccache -M 256M
fi
- source activate $VIRTUALENV
-}
-
-setup_ccache() {
- echo "Setting up ccache with CCACHE_DIR=${CCACHE_DIR}"
- mkdir /tmp/ccache/
- which ccache
- for name in gcc g++ cc c++ clang clang++ i686-linux-gnu-gcc i686-linux-gnu-c++ x86_64-linux-gnu-gcc x86_64-linux-gnu-c++ x86_64-apple-darwin13.4.0-clang x86_64-apple-darwin13.4.0-clang++; do
- ln -s $(which ccache) "/tmp/ccache/${name}"
- done
- export PATH="/tmp/ccache/:${PATH}"
- ccache -M 256M
}
pre_python_environment_install() {
@@ -43,11 +40,25 @@ pre_python_environment_install() {
python3-matplotlib libatlas3-base libatlas-base-dev \
python3-virtualenv python3-pandas ccache
- elif [[ "$DISTRIB" == "conda-mamba-pypy3" ]]; then
- # condaforge/mambaforge-pypy3 needs compilers
+ elif [[ "$DISTRIB" == "conda-pypy3" ]]; then
+ # need compilers
apt-get -yq update
apt-get -yq install build-essential
+ elif [[ "$DISTRIB" == "pip-nogil" ]]; then
+ echo "deb-src http://archive.ubuntu.com/ubuntu/ focal main" | sudo tee -a /etc/apt/sources.list
+ sudo apt-get -yq update
+ sudo apt-get install -yq ccache
+ sudo apt-get build-dep -yq python3 python3-dev
+ setup_ccache # speed-up the build of CPython itself
+ # build Python nogil
+ PYTHON_NOGIL_CLONE_PATH=../nogil
+ git clone --depth 1 https://github.com/colesbury/nogil $PYTHON_NOGIL_CLONE_PATH
+ cd $PYTHON_NOGIL_CLONE_PATH
+ ./configure && make -j 2
+ export PYTHON_NOGIL_PATH="${PYTHON_NOGIL_CLONE_PATH}/python"
+ cd $OLDPWD
+
elif [[ "$BUILD_WITH_ICC" == "true" ]]; then
wget https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB
sudo apt-key add GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB
@@ -56,87 +67,39 @@ pre_python_environment_install() {
sudo apt-get update
sudo apt-get install intel-oneapi-compiler-dpcpp-cpp-and-cpp-classic
source /opt/intel/oneapi/setvars.sh
+
fi
}
-python_environment_install() {
- if [[ "$DISTRIB" == "conda" || "$DISTRIB" == *"mamba"* ]]; then
-
- if [[ "$CONDA_CHANNEL" != "" ]]; then
- TO_INSTALL="--override-channels -c $CONDA_CHANNEL"
- else
- TO_INSTALL=""
- fi
-
- if [[ "$DISTRIB" == *"pypy"* ]]; then
- TO_INSTALL="$TO_INSTALL pypy"
- else
- TO_INSTALL="$TO_INSTALL python=$PYTHON_VERSION"
- fi
-
- TO_INSTALL="$TO_INSTALL ccache pip blas[build=$BLAS]"
-
- TO_INSTALL="$TO_INSTALL $(get_dep numpy $NUMPY_VERSION)"
- TO_INSTALL="$TO_INSTALL $(get_dep scipy $SCIPY_VERSION)"
- TO_INSTALL="$TO_INSTALL $(get_dep cython $CYTHON_VERSION)"
- TO_INSTALL="$TO_INSTALL $(get_dep joblib $JOBLIB_VERSION)"
- TO_INSTALL="$TO_INSTALL $(get_dep pandas $PANDAS_VERSION)"
- TO_INSTALL="$TO_INSTALL $(get_dep pyamg $PYAMG_VERSION)"
- TO_INSTALL="$TO_INSTALL $(get_dep Pillow $PILLOW_VERSION)"
- TO_INSTALL="$TO_INSTALL $(get_dep matplotlib $MATPLOTLIB_VERSION)"
-
- if [[ "$UNAMESTR" == "Darwin" ]] && [[ "$SKLEARN_TEST_NO_OPENMP" != "true" ]]; then
- TO_INSTALL="$TO_INSTALL compilers llvm-openmp"
- fi
+python_environment_install_and_activate() {
+ if [[ "$DISTRIB" == "conda"* ]]; then
+ conda update -n base conda -y
+ conda install -c conda-forge "$(get_dep conda-lock min)" -y
+ conda-lock install --name $VIRTUALENV $LOCK_FILE
+ source activate $VIRTUALENV
- make_conda $TO_INSTALL
-
- elif [[ "$DISTRIB" == "ubuntu" ]] || [[ "$DISTRIB" == "debian-32" ]]; then
+ elif [[ "$DISTRIB" == "ubuntu" || "$DISTRIB" == "debian-32" ]]; then
python3 -m virtualenv --system-site-packages --python=python3 $VIRTUALENV
source $VIRTUALENV/bin/activate
+ pip install -r "${LOCK_FILE}"
- python -m pip install $(get_dep cython $CYTHON_VERSION) \
- $(get_dep joblib $JOBLIB_VERSION)
-
- elif [[ "$DISTRIB" == "conda-pip-latest" ]]; then
- # Since conda main channel usually lacks behind on the latest releases,
- # we use pypi to test against the latest releases of the dependencies.
- # conda is still used as a convenient way to install Python and pip.
- make_conda "ccache python=$PYTHON_VERSION"
- python -m pip install -U pip
-
- python -m pip install pandas matplotlib scikit-image pyamg
- # do not install dependencies for lightgbm since it requires scikit-learn.
- python -m pip install "lightgbm>=3.0.0" --no-deps
+ elif [[ "$DISTRIB" == "pip-nogil" ]]; then
+ ${PYTHON_NOGIL_PATH} -m venv $VIRTUALENV
+ source $VIRTUALENV/bin/activate
+ pip install -r "${LOCK_FILE}"
+ fi
- elif [[ "$DISTRIB" == "conda-pip-scipy-dev" ]]; then
- make_conda "ccache python=$PYTHON_VERSION"
- python -m pip install -U pip
- echo "Installing numpy and scipy master wheels"
+ if [[ "$DISTRIB" == "conda-pip-scipy-dev" ]]; then
+ echo "Installing development dependency wheels"
dev_anaconda_url=https://pypi.anaconda.org/scipy-wheels-nightly/simple
pip install --pre --upgrade --timeout=60 --extra-index $dev_anaconda_url numpy pandas scipy
+ echo "Installing Cython from PyPI enabling pre-releases"
pip install --pre cython
echo "Installing joblib master"
pip install https://github.com/joblib/joblib/archive/master.zip
echo "Installing pillow master"
pip install https://github.com/python-pillow/Pillow/archive/main.zip
fi
-
- python -m pip install $(get_dep threadpoolctl $THREADPOOLCTL_VERSION) \
- $(get_dep pytest $PYTEST_VERSION) \
- $(get_dep pytest-xdist $PYTEST_XDIST_VERSION)
-
- if [[ "$COVERAGE" == "true" ]]; then
- # XXX: coverage is temporary pinned to 6.2 because 6.3 is not fork-safe
- # cf. https://github.com/nedbat/coveragepy/issues/1310
- python -m pip install codecov pytest-cov coverage==6.2
- fi
-
- if [[ "$TEST_DOCSTRINGS" == "true" ]]; then
- # numpydoc requires sphinx
- python -m pip install sphinx
- python -m pip install numpydoc
- fi
}
scikit_learn_install() {
@@ -147,7 +110,7 @@ scikit_learn_install() {
# workers with 2 cores when building the compiled extensions of scikit-learn.
export SKLEARN_BUILD_PARALLEL=3
- if [[ "$UNAMESTR" == "Darwin" ]] && [[ "$SKLEARN_TEST_NO_OPENMP" == "true" ]]; then
+ if [[ "$UNAMESTR" == "Darwin" && "$SKLEARN_TEST_NO_OPENMP" == "true" ]]; then
# Without openmp, we use the system clang. Here we use /usr/bin/ar
# instead because llvm-ar errors
export AR=/usr/bin/ar
@@ -183,7 +146,7 @@ scikit_learn_install() {
main() {
pre_python_environment_install
- python_environment_install
+ python_environment_install_and_activate
scikit_learn_install
}
diff --git a/build_tools/azure/install_win.sh b/build_tools/azure/install_win.sh
index e2f9a0c2f25e9..b28bc86270925 100755
--- a/build_tools/azure/install_win.sh
+++ b/build_tools/azure/install_win.sh
@@ -3,34 +3,22 @@
set -e
set -x
-if [[ "$PYTHON_ARCH" == "64" ]]; then
- conda create -n $VIRTUALENV -q -y python=$PYTHON_VERSION numpy scipy cython matplotlib wheel pillow joblib
-
+# defines the get_dep and show_installed_libraries functions
+source build_tools/shared.sh
+
+if [[ "$DISTRIB" == "conda" ]]; then
+ conda update -n base conda -y
+ conda install pip -y
+ pip install "$(get_dep conda-lock min)"
+ conda-lock install --name $VIRTUALENV $LOCK_FILE
source activate $VIRTUALENV
-
- pip install threadpoolctl
-
- if [[ "$PYTEST_VERSION" == "*" ]]; then
- pip install pytest
- else
- pip install pytest==$PYTEST_VERSION
- fi
else
- pip install numpy scipy cython pytest wheel pillow joblib threadpoolctl
-fi
-
-if [[ "$PYTEST_XDIST_VERSION" != "none" ]]; then
- pip install pytest-xdist
-fi
-
-if [[ "$COVERAGE" == "true" ]]; then
- # XXX: coverage is temporary pinned to 6.2 because 6.3 is not fork-safe
- # cf. https://github.com/nedbat/coveragepy/issues/1310
- pip install coverage codecov pytest-cov coverage==6.2
+ python -m venv $VIRTUALENV
+ source $VIRTUALENV/Scripts/activate
+ pip install -r $LOCK_FILE
fi
-python --version
-pip --version
+show_installed_libraries
# Build scikit-learn
python setup.py bdist_wheel
diff --git a/build_tools/azure/linting.sh b/build_tools/azure/linting.sh
new file mode 100755
index 0000000000000..21ef53c8012dc
--- /dev/null
+++ b/build_tools/azure/linting.sh
@@ -0,0 +1,43 @@
+#!/bin/bash
+
+set -e
+# pipefail is necessary to propagate exit codes
+set -o pipefail
+
+flake8 --show-source .
+echo -e "No problem detected by flake8\n"
+
+# For docstrings and warnings of deprecated attributes to be rendered
+# properly, the property decorator must come before the deprecated decorator
+# (else they are treated as functions)
+
+# do not error when grep -B1 "@property" finds nothing
+set +e
+bad_deprecation_property_order=`git grep -A 10 "@property" -- "*.py" | awk '/@property/,/def /' | grep -B1 "@deprecated"`
+
+if [ ! -z "$bad_deprecation_property_order" ]
+then
+ echo "property decorator should come before deprecated decorator"
+ echo "found the following occurrencies:"
+ echo $bad_deprecation_property_order
+ exit 1
+fi
+
+# Check for default doctest directives ELLIPSIS and NORMALIZE_WHITESPACE
+
+doctest_directive="$(git grep -nw -E "# doctest\: \+(ELLIPSIS|NORMALIZE_WHITESPACE)")"
+
+if [ ! -z "$doctest_directive" ]
+then
+ echo "ELLIPSIS and NORMALIZE_WHITESPACE doctest directives are enabled by default, but were found in:"
+ echo "$doctest_directive"
+ exit 1
+fi
+
+joblib_import="$(git grep -l -A 10 -E "joblib import.+delayed" -- "*.py" ":!sklearn/utils/_joblib.py" ":!sklearn/utils/fixes.py")"
+
+if [ ! -z "$joblib_import" ]; then
+ echo "Use from sklearn.utils.fixes import delayed instead of joblib delayed. The following files contains imports to joblib.delayed:"
+ echo "$joblib_import"
+ exit 1
+fi
diff --git a/build_tools/azure/posix-docker.yml b/build_tools/azure/posix-docker.yml
index 890731ffb5b0d..b27b1d0747e60 100644
--- a/build_tools/azure/posix-docker.yml
+++ b/build_tools/azure/posix-docker.yml
@@ -19,20 +19,9 @@ jobs:
OPENBLAS_NUM_THREADS: '2'
CPU_COUNT: '2'
SKLEARN_SKIP_NETWORK_TESTS: '1'
- NUMPY_VERSION: 'latest'
- SCIPY_VERSION: 'latest'
- CYTHON_VERSION: 'latest'
- JOBLIB_VERSION: 'latest'
- PANDAS_VERSION: 'latest'
- PYAMG_VERSION: 'latest'
- PILLOW_VERSION: 'latest'
- MATPLOTLIB_VERSION: 'latest'
- PYTEST_VERSION: 'latest'
PYTEST_XDIST_VERSION: 'latest'
- THREADPOOLCTL_VERSION: 'latest'
COVERAGE: 'false'
TEST_DOCSTRINGS: 'false'
- BLAS: 'openblas'
# Set in azure-pipelines.yml
DISTRIB: ''
DOCKER_CONTAINER: ''
@@ -76,24 +65,14 @@ jobs:
--detach
--name skcontainer
-e DISTRIB=$DISTRIB
+ -e LOCK_FILE=$LOCK_FILE
-e TEST_DIR=/temp_dir
-e JUNITXML=$JUNITXML
-e VIRTUALENV=testvenv
- -e NUMPY_VERSION=$NUMPY_VERSION
- -e SCIPY_VERSION=$SCIPY_VERSION
- -e CYTHON_VERSION=$CYTHON_VERSION
- -e JOBLIB_VERSION=$JOBLIB_VERSION
- -e PANDAS_VERSION=$PANDAS_VERSION
- -e PYAMG_VERSION=$PYAMG_VERSION
- -e PILLOW_VERSION=$PILLOW_VERSION
- -e MATPLOTLIB_VERSION=$MATPLOTLIB_VERSION
- -e PYTEST_VERSION=$PYTEST_VERSION
-e PYTEST_XDIST_VERSION=$PYTEST_XDIST_VERSION
- -e THREADPOOLCTL_VERSION=$THREADPOOLCTL_VERSION
-e OMP_NUM_THREADS=$OMP_NUM_THREADS
-e OPENBLAS_NUM_THREADS=$OPENBLAS_NUM_THREADS
-e SKLEARN_SKIP_NETWORK_TESTS=$SKLEARN_SKIP_NETWORK_TESTS
- -e BLAS=$BLAS
-e SELECTED_TESTS="$SELECTED_TESTS"
-e CPU_COUNT=$CPU_COUNT
-e CCACHE_DIR=/ccache
diff --git a/build_tools/azure/posix.yml b/build_tools/azure/posix.yml
index 169b61ce4859b..f93cd6e211231 100644
--- a/build_tools/azure/posix.yml
+++ b/build_tools/azure/posix.yml
@@ -22,17 +22,7 @@ jobs:
SKLEARN_SKIP_NETWORK_TESTS: '1'
CCACHE_DIR: $(Pipeline.Workspace)/ccache
CCACHE_COMPRESS: '1'
- NUMPY_VERSION: 'latest'
- SCIPY_VERSION: 'latest'
- CYTHON_VERSION: 'latest'
- JOBLIB_VERSION: 'latest'
- PANDAS_VERSION: 'latest'
- PYAMG_VERSION: 'latest'
- PILLOW_VERSION: 'latest'
- MATPLOTLIB_VERSION: 'latest'
- PYTEST_VERSION: '6.2.5'
PYTEST_XDIST_VERSION: 'latest'
- THREADPOOLCTL_VERSION: 'latest'
COVERAGE: 'true'
TEST_DOCSTRINGS: 'false'
CREATE_ISSUE_ON_TRACKER: 'true'
diff --git a/build_tools/azure/py38_conda_defaults_openblas_environment.yml b/build_tools/azure/py38_conda_defaults_openblas_environment.yml
new file mode 100644
index 0000000000000..53a6a8384d0de
--- /dev/null
+++ b/build_tools/azure/py38_conda_defaults_openblas_environment.yml
@@ -0,0 +1,23 @@
+# DO NOT EDIT: this file is generated from the specification found in the
+# following script to centralize the configuration for CI builds:
+# build_tools/update_environments_and_lock_files.py
+channels:
+ - defaults
+dependencies:
+ - python=3.8
+ - numpy=1.17.3 # min
+ - blas[build=openblas]
+ - scipy=1.3.2 # min
+ - cython
+ - joblib
+ - threadpoolctl=2.2.0
+ - matplotlib=3.1.2 # min
+ - pandas
+ - pyamg
+ - pytest
+ - pytest-xdist
+ - pillow
+ - codecov
+ - pytest-cov
+ - coverage=6.2
+ - ccache
diff --git a/build_tools/azure/py38_conda_defaults_openblas_linux-64_conda.lock b/build_tools/azure/py38_conda_defaults_openblas_linux-64_conda.lock
new file mode 100644
index 0000000000000..ace701d2b0eb5
--- /dev/null
+++ b/build_tools/azure/py38_conda_defaults_openblas_linux-64_conda.lock
@@ -0,0 +1,93 @@
+# Generated by conda-lock.
+# platform: linux-64
+# input_hash: bc3205329e5f838f2018cbcf919b18b3b16ead68f2ae45e3f1b4518b73dc95e2
+@EXPLICIT
+https://repo.anaconda.com/pkgs/main/linux-64/_libgcc_mutex-0.1-main.conda#c3473ff8bdb3d124ed5ff11ec380d6f9
+https://repo.anaconda.com/pkgs/main/linux-64/blas-1.0-openblas.conda#9ddfcaef10d79366c90128f5dc444be8
+https://repo.anaconda.com/pkgs/main/linux-64/ca-certificates-2022.4.26-h06a4308_0.conda#fc9c0bf2e7893f5407ff74289dbcf295
+https://repo.anaconda.com/pkgs/main/linux-64/ld_impl_linux-64-2.38-h1181459_1.conda#68eedfd9c06f2b0e6888d8db345b7f5b
+https://repo.anaconda.com/pkgs/main/linux-64/libgfortran4-7.5.0-ha8ba4b0_17.conda#e3883581cbf0a98672250c3e80d292bf
+https://repo.anaconda.com/pkgs/main/linux-64/libstdcxx-ng-11.2.0-h1234567_1.conda#57623d10a70e09e1d048c2b2b6f4e2dd
+https://repo.anaconda.com/pkgs/main/linux-64/libgfortran-ng-7.5.0-ha8ba4b0_17.conda#ecb35c8952579d5c8dc56c6e076ba948
+https://repo.anaconda.com/pkgs/main/linux-64/libgomp-11.2.0-h1234567_1.conda#b372c0eea9b60732fdae4b817a63c8cd
+https://repo.anaconda.com/pkgs/main/linux-64/_openmp_mutex-5.1-1_gnu.conda#71d281e9c2192cb3fa425655a8defb85
+https://repo.anaconda.com/pkgs/main/linux-64/libgcc-ng-11.2.0-h1234567_1.conda#a87728dabf3151fb9cfa990bd2eb0464
+https://repo.anaconda.com/pkgs/main/linux-64/expat-2.4.4-h295c915_0.conda#f9930c60940181cf06d0bd0b8095063c
+https://repo.anaconda.com/pkgs/main/linux-64/giflib-5.2.1-h7b6447c_0.conda#c2583ad8de5051f19479580c58336f15
+https://repo.anaconda.com/pkgs/main/linux-64/icu-58.2-he6710b0_3.conda#48cc14d5ad1a9bcd8dac17211a8deb8b
+https://repo.anaconda.com/pkgs/main/linux-64/jpeg-9e-h7f8727e_0.conda#a0571bd2254b360aef526307a17f3526
+https://repo.anaconda.com/pkgs/main/linux-64/libffi-3.3-he6710b0_2.conda#88a54b8f50e351c650e16f4ee781440c
+https://repo.anaconda.com/pkgs/main/linux-64/libopenblas-0.3.18-hf726d26_0.conda#10422bb3b9b022e27798fc368cda69ba
+https://repo.anaconda.com/pkgs/main/linux-64/libuuid-1.0.3-h7f8727e_2.conda#6c4c9e96bfa4744d4839b9ed128e1114
+https://repo.anaconda.com/pkgs/main/linux-64/libwebp-base-1.2.2-h7f8727e_0.conda#162451b4884cfc7db8400580c711e83a
+https://repo.anaconda.com/pkgs/main/linux-64/libxcb-1.15-h7f8727e_0.conda#ada518dcadd6aaee9aae47ba9a671553
+https://repo.anaconda.com/pkgs/main/linux-64/lz4-c-1.9.3-h295c915_1.conda#d9bd18f73ff566e08add10a54a3463cf
+https://repo.anaconda.com/pkgs/main/linux-64/ncurses-6.3-h5eee18b_3.conda#0c616f387885c1bbb57ec0bd1e779ced
+https://repo.anaconda.com/pkgs/main/linux-64/openssl-1.1.1q-h7f8727e_0.conda#2ac47797afee2ece8d339c18b095b8d8
+https://repo.anaconda.com/pkgs/main/linux-64/pcre-8.45-h295c915_0.conda#b32ccc24d1d9808618c1e898da60f68d
+https://repo.anaconda.com/pkgs/main/linux-64/xz-5.2.5-h7f8727e_1.conda#5d01fcf310bf465237f6b95a019d73bc
+https://repo.anaconda.com/pkgs/main/linux-64/zlib-1.2.12-h7f8727e_2.conda#4f4080e9939f082332cd8be7fedad087
+https://repo.anaconda.com/pkgs/main/linux-64/ccache-3.7.9-hfe4627d_0.conda#bef6fc681c273bb7bd0c67d1a591365e
+https://repo.anaconda.com/pkgs/main/linux-64/glib-2.69.1-h4ff587b_1.conda#4c3eae7c0b8b1c8fb3046a0740313bbf
+https://repo.anaconda.com/pkgs/main/linux-64/libpng-1.6.37-hbc83047_0.conda#689f903925dcf6c5ab7bc1de0f58b67b
+https://repo.anaconda.com/pkgs/main/linux-64/libxml2-2.9.14-h74e7548_0.conda#2eafeb1cb5f00b034d150f3d70436e52
+https://repo.anaconda.com/pkgs/main/linux-64/readline-8.1.2-h7f8727e_1.conda#ea33f478fea12406f394944e7e4f3d20
+https://repo.anaconda.com/pkgs/main/linux-64/tk-8.6.12-h1ccaba5_0.conda#fa10ff4aa631fa4aa090a6234d7770b9
+https://repo.anaconda.com/pkgs/main/linux-64/zstd-1.5.2-ha4553b6_0.conda#0e926a5f2e02fe4a9376ece4b732ce36
+https://repo.anaconda.com/pkgs/main/linux-64/dbus-1.13.18-hb2f20db_0.conda#6a6a6f1391f807847404344489ef6cf4
+https://repo.anaconda.com/pkgs/main/linux-64/freetype-2.11.0-h70c0345_0.conda#b767874a6273e1058027cb2e300d00ac
+https://repo.anaconda.com/pkgs/main/linux-64/gstreamer-1.14.0-h28cd5cc_2.conda#6af5d0cbd7310e1cd8a6a5c1c99649b2
+https://repo.anaconda.com/pkgs/main/linux-64/libtiff-4.2.0-h2818925_1.conda#4197d70794ffb5386cf9d4b59233c481
+https://repo.anaconda.com/pkgs/main/linux-64/sqlite-3.38.5-hc218d9a_0.conda#ed2668e84d5e2730827ad737bc5231a3
+https://repo.anaconda.com/pkgs/main/linux-64/fontconfig-2.13.1-h6c09931_0.conda#fa04e89166d4b44326c6d76e2f708715
+https://repo.anaconda.com/pkgs/main/linux-64/gst-plugins-base-1.14.0-h8213a91_2.conda#838648422452405b86699e780e293c1d
+https://repo.anaconda.com/pkgs/main/linux-64/lcms2-2.12-h3be6417_0.conda#719db47afba9f6586eecb5eacac70bff
+https://repo.anaconda.com/pkgs/main/linux-64/libwebp-1.2.2-h55f646e_0.conda#c9ed6bddefc09dbfc246301c3ce3ca14
+https://repo.anaconda.com/pkgs/main/linux-64/python-3.8.13-h12debd9_0.conda#edc17980bae484b711e090f0a0cbbaef
+https://repo.anaconda.com/pkgs/main/noarch/attrs-21.4.0-pyhd3eb1b0_0.conda#3bc977a57587a7964921e3e1e2e31f9e
+https://repo.anaconda.com/pkgs/main/linux-64/certifi-2022.6.15-py38h06a4308_0.conda#ebd13bbcc4bd93d8e743be775cc9b865
+https://repo.anaconda.com/pkgs/main/noarch/charset-normalizer-2.0.4-pyhd3eb1b0_0.conda#e7a441d94234b2b5fafee06e25dbf076
+https://repo.anaconda.com/pkgs/main/linux-64/coverage-6.2-py38h7f8727e_0.conda#34a3006ca7d8d286b63593b31b845ace
+https://repo.anaconda.com/pkgs/main/noarch/cycler-0.11.0-pyhd3eb1b0_0.conda#f5e365d2cdb66d547eb8c3ab93843aab
+https://repo.anaconda.com/pkgs/main/linux-64/cython-0.29.30-py38h6a678d5_0.conda#46c524f271afb80dfa5ef984e72c6ff4
+https://repo.anaconda.com/pkgs/main/noarch/execnet-1.9.0-pyhd3eb1b0_0.conda#f895937671af67cebb8af617494b3513
+https://repo.anaconda.com/pkgs/main/noarch/idna-3.3-pyhd3eb1b0_0.conda#8f43a528cf83b43af38a4d142fa38b8a
+https://repo.anaconda.com/pkgs/main/noarch/iniconfig-1.1.1-pyhd3eb1b0_0.tar.bz2#e40edff2c5708f342cef43c7f280c507
+https://repo.anaconda.com/pkgs/main/noarch/joblib-1.1.0-pyhd3eb1b0_0.conda#cae25b839f3b24686e683addde01b742
+https://repo.anaconda.com/pkgs/main/linux-64/kiwisolver-1.4.2-py38h295c915_0.conda#00e5f5a50b547c8c31d1a559828f3251
+https://repo.anaconda.com/pkgs/main/linux-64/numpy-base-1.17.3-py38h2f8d375_0.conda#40edbb76ecacefb1e6ab639b514822b1
+https://repo.anaconda.com/pkgs/main/linux-64/pillow-9.2.0-py38hace64e9_1.conda#a6b7baf62d6399704dfdeab8c0ec55f6
+https://repo.anaconda.com/pkgs/main/linux-64/pluggy-1.0.0-py38h06a4308_1.conda#87bb1d3f6cf3e409a1dac38cee99918e
+https://repo.anaconda.com/pkgs/main/noarch/py-1.11.0-pyhd3eb1b0_0.conda#7205a898ed2abbf6e9b903dff6abe08e
+https://repo.anaconda.com/pkgs/main/noarch/pycparser-2.21-pyhd3eb1b0_0.conda#135a72ff2a31150a3a3ff0b1edd41ca9
+https://repo.anaconda.com/pkgs/main/noarch/pyparsing-3.0.4-pyhd3eb1b0_0.conda#6bca2ae9c9aae9ccdebcb8cf2aa87cb3
+https://repo.anaconda.com/pkgs/main/linux-64/pysocks-1.7.1-py38h06a4308_0.conda#21c67581f3a81ffbb02728eb2178d693
+https://repo.anaconda.com/pkgs/main/linux-64/pytz-2022.1-py38h06a4308_0.conda#d9e022584b586338e235e41a76ccc657
+https://repo.anaconda.com/pkgs/main/linux-64/qt-5.9.7-h5867ecd_1.conda#05507dbc35c46ac5a7066fc860a62341
+https://repo.anaconda.com/pkgs/main/linux-64/sip-4.19.13-py38h295c915_0.conda#2046e66b7d12f7c0cda5687e4c27b692
+https://repo.anaconda.com/pkgs/main/noarch/six-1.16.0-pyhd3eb1b0_1.conda#34586824d411d36af2fa40e799c172d0
+https://repo.anaconda.com/pkgs/main/noarch/threadpoolctl-2.2.0-pyh0d69192_0.conda#bbfdbae4934150b902f97daaf287efe2
+https://repo.anaconda.com/pkgs/main/noarch/toml-0.10.2-pyhd3eb1b0_0.conda#cda05f5f6d8509529d1a2743288d197a
+https://repo.anaconda.com/pkgs/main/linux-64/tomli-2.0.1-py38h06a4308_0.conda#791cce9de9913e9587b0a85cd8419123
+https://repo.anaconda.com/pkgs/main/linux-64/tornado-6.1-py38h27cfd23_0.conda#d2d3043f631807af72b0fde504baf625
+https://repo.anaconda.com/pkgs/main/linux-64/cffi-1.15.0-py38hd667e15_1.conda#7b12fe728b28de7b8851af1eb1ba1d38
+https://repo.anaconda.com/pkgs/main/linux-64/numpy-1.17.3-py38h7e8d029_0.conda#5f2b196b515f8fe6b37e3d224650577d
+https://repo.anaconda.com/pkgs/main/noarch/packaging-21.3-pyhd3eb1b0_0.conda#07bbfbb961db7fa329cc42716943ea62
+https://repo.anaconda.com/pkgs/main/linux-64/pyqt-5.9.2-py38h05f1152_4.conda#d3e6b8e1a634125414f87f231c755dae
+https://repo.anaconda.com/pkgs/main/noarch/python-dateutil-2.8.2-pyhd3eb1b0_0.conda#211ee00320b08a1ac9fea6677649f6c9
+https://repo.anaconda.com/pkgs/main/linux-64/setuptools-61.2.0-py38h06a4308_0.conda#c5e94bc1e8e443ae6d53b191a351591e
+https://repo.anaconda.com/pkgs/main/linux-64/brotlipy-0.7.0-py38h27cfd23_1003.conda#e881c8ee8a4048f29da5d20f0330fe37
+https://repo.anaconda.com/pkgs/main/linux-64/cryptography-37.0.1-py38h9ce1e76_0.conda#16d301ed789096eb9881a25ed7a1155e
+https://repo.anaconda.com/pkgs/main/linux-64/matplotlib-base-3.1.2-py38hef1b27d_1.conda#5e99f974f4c2757791aa10a27596230a
+https://repo.anaconda.com/pkgs/main/linux-64/pandas-1.2.4-py38ha9443f7_0.conda#5bd3fd807a294f387feabc65821b75d0
+https://repo.anaconda.com/pkgs/main/linux-64/pytest-7.1.2-py38h06a4308_0.conda#8d7f526a3d29273e06957d302f515755
+https://repo.anaconda.com/pkgs/main/linux-64/scipy-1.3.2-py38he2b7bc3_0.conda#a9df91d5a41c1f39524fc8a53c56bc29
+https://repo.anaconda.com/pkgs/main/linux-64/matplotlib-3.1.2-py38_1.conda#1781036a02c5def820ea2923074d158a
+https://repo.anaconda.com/pkgs/main/linux-64/pyamg-4.1.0-py38h9a67853_0.conda#9b0bffd5f67e0c5ee3c226e5518991fb
+https://repo.anaconda.com/pkgs/main/noarch/pyopenssl-22.0.0-pyhd3eb1b0_0.conda#1dbbf9422269cd62c7094960d9b43f36
+https://repo.anaconda.com/pkgs/main/noarch/pytest-cov-3.0.0-pyhd3eb1b0_0.conda#bbdaac2947f507399816d509107945c2
+https://repo.anaconda.com/pkgs/main/noarch/pytest-forked-1.3.0-pyhd3eb1b0_0.tar.bz2#07970bffdc78f417d7f8f1c7e620f5c4
+https://repo.anaconda.com/pkgs/main/noarch/pytest-xdist-2.5.0-pyhd3eb1b0_0.conda#d15cdc4207bcf8ca920822597f1d138d
+https://repo.anaconda.com/pkgs/main/linux-64/urllib3-1.26.9-py38h06a4308_0.conda#40c1c6f5e634ec77344a822ab3aa84cc
+https://repo.anaconda.com/pkgs/main/linux-64/requests-2.28.1-py38h06a4308_0.conda#04d482ea4a1e190d688dee2e4048e49f
+https://repo.anaconda.com/pkgs/main/noarch/codecov-2.1.11-pyhd3eb1b0_0.conda#83a743cc928162d53d4066c43468b2c7
diff --git a/build_tools/azure/py38_conda_forge_mkl_environment.yml b/build_tools/azure/py38_conda_forge_mkl_environment.yml
new file mode 100644
index 0000000000000..ff6f3479b770c
--- /dev/null
+++ b/build_tools/azure/py38_conda_forge_mkl_environment.yml
@@ -0,0 +1,22 @@
+# DO NOT EDIT: this file is generated from the specification found in the
+# following script to centralize the configuration for CI builds:
+# build_tools/update_environments_and_lock_files.py
+channels:
+ - conda-forge
+dependencies:
+ - python=3.8
+ - numpy
+ - blas[build=mkl]
+ - scipy
+ - cython
+ - joblib
+ - threadpoolctl
+ - matplotlib
+ - pytest
+ - pytest-xdist
+ - pillow
+ - codecov
+ - pytest-cov
+ - coverage
+ - wheel
+ - pip
diff --git a/build_tools/azure/py38_conda_forge_mkl_win-64_conda.lock b/build_tools/azure/py38_conda_forge_mkl_win-64_conda.lock
new file mode 100644
index 0000000000000..f06c1398fdba6
--- /dev/null
+++ b/build_tools/azure/py38_conda_forge_mkl_win-64_conda.lock
@@ -0,0 +1,123 @@
+# Generated by conda-lock.
+# platform: win-64
+# input_hash: 058abecb942d0c68bd56879a3f802fea24ccdc77688a02b38c81b110d8d809e7
+@EXPLICIT
+https://conda.anaconda.org/conda-forge/win-64/ca-certificates-2022.6.15-h5b45459_0.tar.bz2#b84069692c33afe59f31c7117c80696e
+https://conda.anaconda.org/conda-forge/win-64/intel-openmp-2022.1.0-h57928b3_3787.tar.bz2#35dff2b6e944ce136a574c4c006cec28
+https://conda.anaconda.org/conda-forge/win-64/mkl-include-2022.1.0-h6a75c08_874.tar.bz2#414f6ab96ad71e7a95bd00d990fa3473
+https://conda.anaconda.org/conda-forge/win-64/msys2-conda-epoch-20160418-1.tar.bz2#b0309b72560df66f71a9d5e34a5efdfa
+https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.20348.0-h57928b3_0.tar.bz2#6d666b6ea8251231ff508062d1e41f9c
+https://conda.anaconda.org/conda-forge/win-64/m2w64-gmp-6.1.0-2.tar.bz2#53a1c73e1e3d185516d7e3af177596d9
+https://conda.anaconda.org/conda-forge/win-64/m2w64-libwinpthread-git-5.0.0.4634.697f757-2.tar.bz2#774130a326dee16f1ceb05cc687ee4f0
+https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.29.30037-h902a5da_6.tar.bz2#33d07ebe91062743eabc9e53a60d18e1
+https://conda.anaconda.org/conda-forge/win-64/m2w64-gcc-libs-core-5.3.0-7.tar.bz2#4289d80fb4d272f1f3b56cfe87ac90bd
+https://conda.anaconda.org/conda-forge/win-64/vc-14.2-hb210afc_6.tar.bz2#c2aecbc9b00ba6f352e27d3d61fd31fb
+https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h8ffe710_4.tar.bz2#7c03c66026944073040cb19a4f3ec3c9
+https://conda.anaconda.org/conda-forge/win-64/icu-70.1-h0e60522_0.tar.bz2#64073396a905b6df895ab2489fae3847
+https://conda.anaconda.org/conda-forge/win-64/jpeg-9e-h8ffe710_2.tar.bz2#733066523147548ce368a9bd0c8395af
+https://conda.anaconda.org/conda-forge/win-64/lerc-3.0-h0e60522_0.tar.bz2#756c8b51a32758df2ed6cddcc7b7ed58
+https://conda.anaconda.org/conda-forge/win-64/libbrotlicommon-1.0.9-h8ffe710_7.tar.bz2#e7b12a6cf353395dda7ed36b9041048b
+https://conda.anaconda.org/conda-forge/win-64/libdeflate-1.12-h8ffe710_0.tar.bz2#81735cd9af2e06a4eef556ed84fb4640
+https://conda.anaconda.org/conda-forge/win-64/libffi-3.4.2-h8ffe710_5.tar.bz2#2c96d1b6915b408893f9472569dee135
+https://conda.anaconda.org/conda-forge/win-64/libiconv-1.16-he774522_0.tar.bz2#bdfeadc9348e4d9fbe4821e81bf8f221
+https://conda.anaconda.org/conda-forge/win-64/libogg-1.3.4-h8ffe710_1.tar.bz2#04286d905a0dcb7f7d4a12bdfe02516d
+https://conda.anaconda.org/conda-forge/win-64/libwebp-base-1.2.3-h8ffe710_0.tar.bz2#41d06387666b77df69d0de7125f820d1
+https://conda.anaconda.org/conda-forge/win-64/libzlib-1.2.12-h8ffe710_2.tar.bz2#74585107cf2f18b31aa84375fcb24215
+https://conda.anaconda.org/conda-forge/win-64/lz4-c-1.9.3-h8ffe710_1.tar.bz2#d12763533276560a931c1bd3df1adf63
+https://conda.anaconda.org/conda-forge/win-64/m2w64-gcc-libgfortran-5.3.0-6.tar.bz2#066552ac6b907ec6d72c0ddab29050dc
+https://conda.anaconda.org/conda-forge/win-64/openssl-1.1.1q-h8ffe710_0.tar.bz2#2ee16f406ee12fe4dcd08b513e9bd0c6
+https://conda.anaconda.org/conda-forge/win-64/pcre-8.45-h0e60522_0.tar.bz2#3cd3948bb5de74ebef93b6be6d8cf0d5
+https://conda.anaconda.org/conda-forge/win-64/sqlite-3.39.1-h8ffe710_0.tar.bz2#c78517cc232400e9901399a387b8210c
+https://conda.anaconda.org/conda-forge/win-64/tbb-2021.5.0-h2d74725_1.tar.bz2#8f00f39dbd7deaba11410b0b6e7b2cb4
+https://conda.anaconda.org/conda-forge/win-64/tk-8.6.12-h8ffe710_0.tar.bz2#c69a5047cc9291ae40afd4a1ad6f0c0f
+https://conda.anaconda.org/conda-forge/win-64/xz-5.2.5-h62dcd97_1.tar.bz2#eabcbfedd14d7c18a514afca09ea0ebb
+https://conda.anaconda.org/conda-forge/win-64/gettext-0.19.8.1-ha2e2712_1008.tar.bz2#7097f745c7b794ce3e2a1ffe3195580f
+https://conda.anaconda.org/conda-forge/win-64/krb5-1.19.3-h1176d77_0.tar.bz2#2e0d447ab95d58d3ea1222121ec57f9f
+https://conda.anaconda.org/conda-forge/win-64/libbrotlidec-1.0.9-h8ffe710_7.tar.bz2#ca57bf17ba92eed4ca2667a4c5df9343
+https://conda.anaconda.org/conda-forge/win-64/libbrotlienc-1.0.9-h8ffe710_7.tar.bz2#75c0a84c7f22d57cda6aaf7205b4a27c
+https://conda.anaconda.org/conda-forge/win-64/libclang13-14.0.6-default_h77d9078_0.tar.bz2#4d9ea47241ea0851081dfe223c76e3b3
+https://conda.anaconda.org/conda-forge/win-64/libvorbis-1.3.7-h0e60522_0.tar.bz2#e1a22282de0169c93e4ffe6ce6acc212
+https://conda.anaconda.org/conda-forge/win-64/libwebp-1.2.3-h8ffe710_0.tar.bz2#c6ea145b03a43940a6d7e4f7dd300ad9
+https://conda.anaconda.org/conda-forge/win-64/m2w64-gcc-libs-5.3.0-7.tar.bz2#fe759119b8b3bfa720b8762c6fdc35de
+https://conda.anaconda.org/conda-forge/win-64/mkl-2022.1.0-h6a75c08_874.tar.bz2#2ff89a7337a9636029b4db9466e9f8e3
+https://conda.anaconda.org/conda-forge/win-64/python-3.8.13-h9a09f29_0_cpython.tar.bz2#41de0ad5db009f4f829f9d415a6a4200
+https://conda.anaconda.org/conda-forge/win-64/zlib-1.2.12-h8ffe710_2.tar.bz2#d6f4eeb62da2ad90c31082cc7178776a
+https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.2-h6255e5f_2.tar.bz2#e1c202295678d31c9feb9afded45c16a
+https://conda.anaconda.org/conda-forge/noarch/atomicwrites-1.4.1-pyhd8ed1ab_0.tar.bz2#1714887104aab113bde64001ceb17262
+https://conda.anaconda.org/conda-forge/noarch/attrs-21.4.0-pyhd8ed1ab_0.tar.bz2#f70280205d7044c8b8358c8de3190e5d
+https://conda.anaconda.org/conda-forge/win-64/brotli-bin-1.0.9-h8ffe710_7.tar.bz2#fd458f76be2068973734834202ac0352
+https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.0-pyhd8ed1ab_0.tar.bz2#abc0453b6e7bfbb87d275d58e333fc98
+https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.5-pyhd8ed1ab_0.tar.bz2#c267da48ce208905d7d976d49dfd9433
+https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb
+https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2
+https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd
+https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905
+https://conda.anaconda.org/conda-forge/win-64/libblas-3.9.0-15_win64_mkl.tar.bz2#661187f959fed1821cdd6921a738336d
+https://conda.anaconda.org/conda-forge/win-64/libclang-14.0.6-default_h77d9078_0.tar.bz2#39e84500879d1638f1cdca7a289bcf41
+https://conda.anaconda.org/conda-forge/win-64/libglib-2.72.1-h3be07f2_0.tar.bz2#b5560fb371f0876122fed12c6669d521
+https://conda.anaconda.org/conda-forge/win-64/libpng-1.6.37-h1d00b33_3.tar.bz2#29258e769c274f48a1d70210a1fc8896
+https://conda.anaconda.org/conda-forge/win-64/libtiff-4.4.0-h2ed3b44_1.tar.bz2#2c0c44be2cd4d32b677d311a27130724
+https://conda.anaconda.org/conda-forge/win-64/mkl-devel-2022.1.0-h57928b3_875.tar.bz2#6319a06307af296c1dfae93687c283b2
+https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19
+https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727
+https://conda.anaconda.org/conda-forge/win-64/pthread-stubs-0.4-hcd874cb_1001.tar.bz2#a1f820480193ea83582b13249a7e7bd9
+https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054
+https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff
+https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc
+https://conda.anaconda.org/conda-forge/win-64/python_abi-3.8-2_cp38.tar.bz2#80b95487563108d4cf92ff6384050751
+https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2
+https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.1.0-pyh8a188c0_0.tar.bz2#a2995ee828f65687ac5b1e71a2ab1e0c
+https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f832c45a477c78bebd107098db465095
+https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96
+https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022
+https://conda.anaconda.org/conda-forge/win-64/xorg-libxau-1.0.9-hcd874cb_0.tar.bz2#9cef622e75683c17d05ae62d66e69e6c
+https://conda.anaconda.org/conda-forge/win-64/xorg-libxdmcp-1.1.3-hcd874cb_0.tar.bz2#46878ebb6b9cbd8afcf8088d7ef00ece
+https://conda.anaconda.org/conda-forge/win-64/brotli-1.0.9-h8ffe710_7.tar.bz2#bdd3236d1f6962e8e6953276d12b7e5b
+https://conda.anaconda.org/conda-forge/win-64/certifi-2022.6.15-py38haa244fe_0.tar.bz2#88eb5b7ef2bb7238573273a6ff04e461
+https://conda.anaconda.org/conda-forge/win-64/cffi-1.15.1-py38hd8c33c5_0.tar.bz2#6039ace67b5ab57a65d5335e62e14656
+https://conda.anaconda.org/conda-forge/win-64/coverage-6.4.2-py38h294d835_0.tar.bz2#a5660e6122558d387eb6b8a8dadb7441
+https://conda.anaconda.org/conda-forge/win-64/cython-0.29.30-py38h885f38d_0.tar.bz2#7cdd65a187c9c04207b394b67c6cf64b
+https://conda.anaconda.org/conda-forge/win-64/freetype-2.10.4-h546665d_1.tar.bz2#1215a2e49d23da91c28d97cff8de35ea
+https://conda.anaconda.org/conda-forge/win-64/glib-tools-2.72.1-h7755175_0.tar.bz2#bd10245a526669540f748c94c94e2808
+https://conda.anaconda.org/conda-forge/win-64/kiwisolver-1.4.4-py38hbd9d945_0.tar.bz2#c53bd7d9390901f396a0473255d3f39f
+https://conda.anaconda.org/conda-forge/win-64/lcms2-2.12-h2a16943_0.tar.bz2#fee639c27301c4165b4d1f7e442de8a5
+https://conda.anaconda.org/conda-forge/win-64/libcblas-3.9.0-15_win64_mkl.tar.bz2#c6012601cd7dde1b4108128fb847de9f
+https://conda.anaconda.org/conda-forge/win-64/liblapack-3.9.0-15_win64_mkl.tar.bz2#7915daedfea4cc94ffcaa71cde184f50
+https://conda.anaconda.org/conda-forge/win-64/libxcb-1.13-hcd874cb_1004.tar.bz2#a6d7fd030532378ecb6ba435cd9f8234
+https://conda.anaconda.org/conda-forge/win-64/openjpeg-2.4.0-hb211442_1.tar.bz2#0991d2e943e5ba7ec9b7b32eec14e2e3
+https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85
+https://conda.anaconda.org/conda-forge/win-64/pluggy-1.0.0-py38haa244fe_3.tar.bz2#bd23d4e34ce9647a448d8048be89b2dd
+https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984
+https://conda.anaconda.org/conda-forge/win-64/setuptools-63.2.0-py38haa244fe_0.tar.bz2#33e8335b39ea25034dfd3328d5c3f3c5
+https://conda.anaconda.org/conda-forge/win-64/tornado-6.2-py38h294d835_0.tar.bz2#f69edffe253eed9f5f18877a2f93eff8
+https://conda.anaconda.org/conda-forge/win-64/unicodedata2-14.0.0-py38h294d835_1.tar.bz2#957e1074c481cbeba55ac1a5e4c07637
+https://conda.anaconda.org/conda-forge/win-64/win_inet_pton-1.1.0-py38haa244fe_4.tar.bz2#8adadd81dc9c22710b69628ec6e6d41a
+https://conda.anaconda.org/conda-forge/win-64/brotlipy-0.7.0-py38h294d835_1004.tar.bz2#f12a527d29a252cef0abbfd752d3ab01
+https://conda.anaconda.org/conda-forge/win-64/cryptography-37.0.4-py38hb7941b4_0.tar.bz2#9498b6b13423971d96da00e5e98b88d2
+https://conda.anaconda.org/conda-forge/win-64/fonttools-4.34.4-py38h294d835_0.tar.bz2#4fc5caf5ad862f88992406d02d14437c
+https://conda.anaconda.org/conda-forge/win-64/glib-2.72.1-h7755175_0.tar.bz2#eabbf16f405ae95d123e4ec90ee552b3
+https://conda.anaconda.org/conda-forge/noarch/joblib-1.1.0-pyhd8ed1ab_0.tar.bz2#07d1b5c8cde14d95998fd4767e1e62d2
+https://conda.anaconda.org/conda-forge/win-64/liblapacke-3.9.0-15_win64_mkl.tar.bz2#383596ab975e586b0964b5fd959bea78
+https://conda.anaconda.org/conda-forge/win-64/numpy-1.23.1-py38h223ccf5_0.tar.bz2#cc2580ede01216335dece5e24e7382fe
+https://conda.anaconda.org/conda-forge/win-64/pillow-9.2.0-py38hd8e0db4_0.tar.bz2#f2be20ed3bc095db50c05fe3bba50166
+https://conda.anaconda.org/conda-forge/noarch/pip-22.1.2-pyhd8ed1ab_0.tar.bz2#d29185c662a424f8bea1103270b85c96
+https://conda.anaconda.org/conda-forge/win-64/pysocks-1.7.1-py38haa244fe_5.tar.bz2#81fd9157802c3d99efc4a24563cfe885
+https://conda.anaconda.org/conda-forge/win-64/pytest-7.1.2-py38haa244fe_0.tar.bz2#523814dad1e91a414e8e449aae2202ec
+https://conda.anaconda.org/conda-forge/win-64/sip-6.6.2-py38h885f38d_0.tar.bz2#e7ded925411fbd38c929e869adc341b6
+https://conda.anaconda.org/conda-forge/win-64/blas-devel-3.9.0-15_win64_mkl.tar.bz2#9504c3464d7df789d8bcb98cba64bd33
+https://conda.anaconda.org/conda-forge/win-64/gstreamer-1.20.3-hdff456e_0.tar.bz2#e0d08ff2d3539aec413c14ad00f95c05
+https://conda.anaconda.org/conda-forge/win-64/matplotlib-base-3.5.2-py38he529843_0.tar.bz2#437b6a636997b284e69d33360d88e634
+https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44
+https://conda.anaconda.org/conda-forge/win-64/pyqt5-sip-12.11.0-py38h885f38d_0.tar.bz2#fcee1a2fdbb5a879fc7f80be5dd9351c
+https://conda.anaconda.org/conda-forge/noarch/pytest-cov-3.0.0-pyhd8ed1ab_0.tar.bz2#0f7cac11bb696b62d378bde725bfc3eb
+https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f
+https://conda.anaconda.org/conda-forge/win-64/scipy-1.8.1-py38h9bf8e03_0.tar.bz2#cd06e46ce3f0140becc4c0aed56938d0
+https://conda.anaconda.org/conda-forge/win-64/blas-2.115-mkl.tar.bz2#7a7efb718a4da3f8f4e4268c5828efe1
+https://conda.anaconda.org/conda-forge/win-64/gst-plugins-base-1.20.3-he07aa86_0.tar.bz2#85bce748dddf6ecc690f8d29f464b1cf
+https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e
+https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.10-pyhd8ed1ab_0.tar.bz2#14f22c5b9cfd0d93c2806faaa3fe6dec
+https://conda.anaconda.org/conda-forge/win-64/qt-main-5.15.4-h467ea89_2.tar.bz2#2471d740deef9ff39f309a872ad1e2bd
+https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_0.tar.bz2#70d6e72856de9551f83ae0f2de689a7a
+https://conda.anaconda.org/conda-forge/noarch/codecov-2.1.11-pyhd3deb0d_0.tar.bz2#9c661c2c14b4667827218402e6624ad5
+https://conda.anaconda.org/conda-forge/win-64/pyqt-5.15.7-py38h75e37d8_0.tar.bz2#f0e72b93887a28d4c406c2ac393b924d
+https://conda.anaconda.org/conda-forge/win-64/matplotlib-3.5.2-py38haa244fe_0.tar.bz2#3f67b7e43451da103b28f5fab2fe8feb
diff --git a/build_tools/azure/py38_conda_forge_openblas_ubuntu_1804_environment.yml b/build_tools/azure/py38_conda_forge_openblas_ubuntu_1804_environment.yml
new file mode 100644
index 0000000000000..64bb669224ef1
--- /dev/null
+++ b/build_tools/azure/py38_conda_forge_openblas_ubuntu_1804_environment.yml
@@ -0,0 +1,20 @@
+# DO NOT EDIT: this file is generated from the specification found in the
+# following script to centralize the configuration for CI builds:
+# build_tools/update_environments_and_lock_files.py
+channels:
+ - conda-forge
+dependencies:
+ - python=3.8
+ - numpy
+ - blas[build=openblas]
+ - scipy
+ - cython
+ - joblib
+ - threadpoolctl
+ - matplotlib
+ - pandas
+ - pyamg
+ - pytest
+ - pytest-xdist
+ - pillow
+ - ccache
diff --git a/build_tools/azure/py38_conda_forge_openblas_ubuntu_1804_linux-64_conda.lock b/build_tools/azure/py38_conda_forge_openblas_ubuntu_1804_linux-64_conda.lock
new file mode 100644
index 0000000000000..bfb506b345f64
--- /dev/null
+++ b/build_tools/azure/py38_conda_forge_openblas_ubuntu_1804_linux-64_conda.lock
@@ -0,0 +1,132 @@
+# Generated by conda-lock.
+# platform: linux-64
+# input_hash: 4a8f7bb9d2356ef6924ecbaa2de77c00a72130606a77d03cba997fdc114cbcb7
+@EXPLICIT
+https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81
+https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2
+https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45
+https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6
+https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb
+https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-hab24e00_0.tar.bz2#19410c3df09dfb12d1206132a1d357c5
+https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.36.1-hea4e1c9_2.tar.bz2#bd4f2e711b39af170e7ff15163fe87ee
+https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.1.0-hdcd56e2_16.tar.bz2#b02605b875559ff99f04351fd5040760
+https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.1.0-ha89aaad_16.tar.bz2#6f5ba041a41eb102a1027d9e68731be7
+https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29
+https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.1.0-h69a702a_16.tar.bz2#6bf15e29a20f614b18ae89368260d0a2
+https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab
+https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_kmp_llvm.tar.bz2#562b26ba2e19059551a811e72ab7f793
+https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-12.1.0-h8d9b700_16.tar.bz2#4f05bc9844f7c101e6e147dab3c88d5c
+https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.3.2-h166bdaf_0.tar.bz2#b7607b7b62dce55c194ad84f99464e5f
+https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54
+https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.8-h27087fc_0.tar.bz2#e1b07832504eeba765d648389cc387a9
+https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d
+https://conda.anaconda.org/conda-forge/linux-64/icu-69.1-h9c3ff4c_0.tar.bz2#e0773c9556d588b062a4e1424a6a02fa
+https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_2.tar.bz2#ee8b844357a0946870901c7c6f418268
+https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3
+https://conda.anaconda.org/conda-forge/linux-64/lerc-3.0-h9c3ff4c_0.tar.bz2#7fcefde484980d23f0ec24c11e314d2e
+https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_7.tar.bz2#f82dc1c78bcf73583f2656433ce2933c
+https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.12-h166bdaf_0.tar.bz2#d56e3db8fa642fb383f18f5be35eeef2
+https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3
+https://conda.anaconda.org/conda-forge/linux-64/libhiredis-1.0.2-h2cc385e_0.tar.bz2#b34907d3a81a3cd8095ee83d174c074a
+https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2#5c0f338a513a2943c659ae619fca9211
+https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206
+https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680
+https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.20-pthreads_h78a6416_0.tar.bz2#9b6d0781953c9e353faee494336cc229
+https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f
+https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d
+https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.3-h166bdaf_0.tar.bz2#3d6168ac3560d473e52a7cb836400135
+https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_2.tar.bz2#8302381297332ea50532cf2c67961080
+https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6
+https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238
+https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e
+https://conda.anaconda.org/conda-forge/linux-64/openssl-3.0.5-h166bdaf_0.tar.bz2#f158304d1e469c37e9ffdbe796305571
+https://conda.anaconda.org/conda-forge/linux-64/pcre-8.45-h9c3ff4c_0.tar.bz2#c05d1820a6d34ff07aaaab7a9b7eddaa
+https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036
+https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.9-h7f98852_0.tar.bz2#bf6f803a544f26ebbdc3bfff272eb179
+https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.3-h7f98852_0.tar.bz2#be93aabceefa2fac576e971aef407908
+https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.5-h516909a_1.tar.bz2#33f601066901f3e1a85af3522a8113f9
+https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h73d1719_1008.tar.bz2#af49250eca8e139378f8ff0ae9e57251
+https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-15_linux64_openblas.tar.bz2#04eb983975a1be3e57d6d667414cd774
+https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_7.tar.bz2#37a460703214d0d1b421e2a47eb5e6d0
+https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_7.tar.bz2#785a9296ea478eb78c47593c4da6550f
+https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1
+https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h28343ad_4.tar.bz2#4a049fc560e00e43151dc51368915fdd
+https://conda.anaconda.org/conda-forge/linux-64/libllvm13-13.0.1-hf817b99_2.tar.bz2#47da3ce0d8b2e65ccb226c186dd91eba
+https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0
+https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904
+https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-14.0.4-he0ac6c6_0.tar.bz2#cecc6e3cb66570ffcfb820c637890f54
+https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.29-h26416b9_1.tar.bz2#eb0ab80f8c0e15febcd644c43d1386ba
+https://conda.anaconda.org/conda-forge/linux-64/openblas-0.3.20-pthreads_h320a7e8_0.tar.bz2#4cc467036ee23a4e7dac2d2c53cc7c21
+https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa
+https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168
+https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_2.tar.bz2#4533821485cde83ab12ff3d8bda83768
+https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_2.tar.bz2#78c26dbb6e07d95ccc0eab8d4540aa0c
+https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e
+https://conda.anaconda.org/conda-forge/linux-64/ccache-4.5.1-haef5404_0.tar.bz2#8458e509920a0bb14bb6fedd248bed57
+https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h08a2579_0.tar.bz2#d25e05e7ee0e302b52d24491db4891eb
+https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-15_linux64_openblas.tar.bz2#f45968428e445fd0c6472b561145812a
+https://conda.anaconda.org/conda-forge/linux-64/libclang-13.0.1-default_hc23dcda_0.tar.bz2#8cebb0736cba83485b13dc10d242d96d
+https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz2#ebeadbb5fbc44052eeb6f96a2136e3c2
+https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-15_linux64_openblas.tar.bz2#b7078220384b8bf8db1a45e66412ac4f
+https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_3.tar.bz2#3e868978a04de8bf65a97bb86760f47a
+https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-hc85c160_1.tar.bz2#151f9fae3ab50f039c8735e47770aa2d
+https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.12-h885dcf4_1.tar.bz2#d1355eaa48f465782f228275a0a69771
+https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.29-hbc51c84_1.tar.bz2#823fca0470a61bb35c89c605adc96af5
+https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.1-h4ff8645_0.tar.bz2#6acda9d2a3ea84b58637b8f880bbf29b
+https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1
+https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d
+https://conda.anaconda.org/conda-forge/linux-64/freetype-2.10.4-h0708190_1.tar.bz2#4a06f2ac2e5bfae7b6b245171c3f07aa
+https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar.bz2#a3a99cc33279091262bbc4f5ee7c4571
+https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f
+https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-15_linux64_openblas.tar.bz2#31f21773a784f59962ea542a3d71ad87
+https://conda.anaconda.org/conda-forge/linux-64/libpq-14.4-he2d8382_0.tar.bz2#73038d5278e922bb655235ee18545ced
+https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.3-h522a892_0.tar.bz2#96e218d06394a4e4d77028cf70162a09
+https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b
+https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc
+https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.4.0-hb52868f_1.tar.bz2#b7ad78ad2e9ee155f59e6428406ee824
+https://conda.anaconda.org/conda-forge/linux-64/python-3.8.13-ha86cf86_0_cpython.tar.bz2#39183fc3fc91579b466dfa767d2ef4b1
+https://conda.anaconda.org/conda-forge/noarch/attrs-21.4.0-pyhd8ed1ab_0.tar.bz2#f70280205d7044c8b8358c8de3190e5d
+https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-15_linux64_openblas.tar.bz2#2b5095be485bdb407ff3134358c3ca9c
+https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb
+https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2
+https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89
+https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5
+https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905
+https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19
+https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054
+https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc
+https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.8-2_cp38.tar.bz2#bfbb29d517281e78ac53e48d21e6e860
+https://conda.anaconda.org/conda-forge/noarch/pytz-2022.1-pyhd8ed1ab_0.tar.bz2#b87d66d6d3991d988fb31510c95a9267
+https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2
+https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.1.0-pyh8a188c0_0.tar.bz2#a2995ee828f65687ac5b1e71a2ab1e0c
+https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96
+https://conda.anaconda.org/conda-forge/linux-64/blas-2.115-openblas.tar.bz2#ca9e177657aa07ab306bd1bbcdf80e69
+https://conda.anaconda.org/conda-forge/linux-64/certifi-2022.6.15-py38h578d9bd_0.tar.bz2#1f4339b25d1030cfbf4ee0b06690bbce
+https://conda.anaconda.org/conda-forge/linux-64/cython-0.29.30-py38hfa26641_0.tar.bz2#189de973189a5550f34a6c1131dcd15d
+https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_0.tar.bz2#94cb81ffdce328f80c87ac9b01244632
+https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py38h43d8883_0.tar.bz2#ae54c61918e1cbd280b8587ed6219258
+https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.1-py38h3a7f9d9_0.tar.bz2#90cf44c14b2bfe19ce7b875979b90cb9
+https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85
+https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py38h0ee0e06_0.tar.bz2#7ef61f9084bda76b2f7a668ec5d1499a
+https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py38h578d9bd_3.tar.bz2#6ce4ce3d4490a56eb33b52c179609193
+https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-4.19.18-py38h709712a_8.tar.bz2#11b72f5b1cc15427c89232321172a0bc
+https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984
+https://conda.anaconda.org/conda-forge/linux-64/setuptools-63.2.0-py38h578d9bd_0.tar.bz2#e69405e267e41bb8409e5ec307a6cd3d
+https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py38h0a891b7_0.tar.bz2#acd276486a0067bee3098590f0952a0f
+https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h0a891b7_1.tar.bz2#83df0e9e3faffc295f12607438691465
+https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.34.4-py38h0a891b7_0.tar.bz2#7ac14fa19454e00f50d9a39506bcc3c6
+https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.2-hcf0ee16_0.tar.bz2#79d7fca692d224dc29a72bda90f78a7b
+https://conda.anaconda.org/conda-forge/noarch/joblib-1.1.0-pyhd8ed1ab_0.tar.bz2#07d1b5c8cde14d95998fd4767e1e62d2
+https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.3-py38h47df419_0.tar.bz2#91c5ac3f8f0e55a946be7b9ce489abfe
+https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py38h578d9bd_0.tar.bz2#626d2b8f96c8c3d20198e6bd84d1cfb7
+https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.1-py38h1ee437e_0.tar.bz2#a0a8bc19d491ec659a534c9a11cf74a0
+https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py38h826bfd8_0.tar.bz2#107af20136422bcabf9f1195f6262117
+https://conda.anaconda.org/conda-forge/linux-64/pyamg-4.2.3-py38h514daf8_0.tar.bz2#b668a53168c9e66fd5948cf9a456ae50
+https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f
+https://conda.anaconda.org/conda-forge/linux-64/qt-5.12.9-h1304e3e_6.tar.bz2#f2985d160b8c43dd427923c04cd732fe
+https://conda.anaconda.org/conda-forge/linux-64/pyqt-impl-5.12.3-py38h0ffb2e6_8.tar.bz2#acfc7625a212c27f7decdca86fdb2aba
+https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e
+https://conda.anaconda.org/conda-forge/linux-64/pyqtchart-5.12-py38h7400c14_8.tar.bz2#78a2a6cb4ef31f997c1bee8223a9e579
+https://conda.anaconda.org/conda-forge/linux-64/pyqtwebengine-5.12.1-py38h7400c14_8.tar.bz2#857894ea9c5e53c962c3a0932efa71ea
+https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.12.3-py38h578d9bd_8.tar.bz2#88368a5889f31dff922a2d57bbfc3f5b
+https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.2-py38h578d9bd_0.tar.bz2#b15039e7f67b5f91c35f9b6d27c2775c
diff --git a/build_tools/azure/py38_pip_openblas_32bit_lock.txt b/build_tools/azure/py38_pip_openblas_32bit_lock.txt
new file mode 100644
index 0000000000000..17742a9720212
--- /dev/null
+++ b/build_tools/azure/py38_pip_openblas_32bit_lock.txt
@@ -0,0 +1,49 @@
+#
+# This file is autogenerated by pip-compile with python 3.8
+# To update, run:
+#
+# pip-compile --output-file=build_tools/azure/py38_pip_openblas_32bit_lock.txt build_tools/azure/py38_pip_openblas_32bit_requirements.txt
+#
+attrs==21.4.0
+ # via pytest
+cython==0.29.30
+ # via -r build_tools/azure/py38_pip_openblas_32bit_requirements.txt
+execnet==1.9.0
+ # via pytest-xdist
+iniconfig==1.1.1
+ # via pytest
+joblib==1.1.0
+ # via -r build_tools/azure/py38_pip_openblas_32bit_requirements.txt
+numpy==1.23.1
+ # via
+ # -r build_tools/azure/py38_pip_openblas_32bit_requirements.txt
+ # scipy
+packaging==21.3
+ # via pytest
+pillow==9.2.0
+ # via -r build_tools/azure/py38_pip_openblas_32bit_requirements.txt
+pluggy==1.0.0
+ # via pytest
+py==1.11.0
+ # via
+ # pytest
+ # pytest-forked
+pyparsing==3.0.9
+ # via packaging
+pytest==7.1.2
+ # via
+ # -r build_tools/azure/py38_pip_openblas_32bit_requirements.txt
+ # pytest-forked
+ # pytest-xdist
+pytest-forked==1.4.0
+ # via pytest-xdist
+pytest-xdist==2.5.0
+ # via -r build_tools/azure/py38_pip_openblas_32bit_requirements.txt
+scipy==1.8.1
+ # via -r build_tools/azure/py38_pip_openblas_32bit_requirements.txt
+threadpoolctl==3.1.0
+ # via -r build_tools/azure/py38_pip_openblas_32bit_requirements.txt
+tomli==2.0.1
+ # via pytest
+wheel==0.37.1
+ # via -r build_tools/azure/py38_pip_openblas_32bit_requirements.txt
diff --git a/build_tools/azure/py38_pip_openblas_32bit_requirements.txt b/build_tools/azure/py38_pip_openblas_32bit_requirements.txt
new file mode 100644
index 0000000000000..fb1b4ef7b1c63
--- /dev/null
+++ b/build_tools/azure/py38_pip_openblas_32bit_requirements.txt
@@ -0,0 +1,12 @@
+# DO NOT EDIT: this file is generated from the specification found in the
+# following script to centralize the configuration for CI builds:
+# build_tools/update_environments_and_lock_files.py
+numpy
+scipy
+cython
+joblib
+threadpoolctl
+pytest
+pytest-xdist
+pillow
+wheel
diff --git a/build_tools/azure/pylatest_conda_forge_mkl_linux-64_conda.lock b/build_tools/azure/pylatest_conda_forge_mkl_linux-64_conda.lock
new file mode 100644
index 0000000000000..e5a12a826209a
--- /dev/null
+++ b/build_tools/azure/pylatest_conda_forge_mkl_linux-64_conda.lock
@@ -0,0 +1,166 @@
+# Generated by conda-lock.
+# platform: linux-64
+# input_hash: cb096f7f420f09924e2a2d782533eefe4fbfbca3f94fe41424ac460dc2740aba
+@EXPLICIT
+https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81
+https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2
+https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45
+https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6
+https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb
+https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-hab24e00_0.tar.bz2#19410c3df09dfb12d1206132a1d357c5
+https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.36.1-hea4e1c9_2.tar.bz2#bd4f2e711b39af170e7ff15163fe87ee
+https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.1.0-hdcd56e2_16.tar.bz2#b02605b875559ff99f04351fd5040760
+https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.1.0-ha89aaad_16.tar.bz2#6f5ba041a41eb102a1027d9e68731be7
+https://conda.anaconda.org/conda-forge/linux-64/mkl-include-2022.1.0-h84fe81f_915.tar.bz2#2dcd1acca05c11410d4494d7fc7dfa2a
+https://conda.anaconda.org/conda-forge/noarch/tzdata-2022a-h191b570_0.tar.bz2#84be5301069417a2221187d2f435e0f7
+https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29
+https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.1.0-h69a702a_16.tar.bz2#6bf15e29a20f614b18ae89368260d0a2
+https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab
+https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_kmp_llvm.tar.bz2#562b26ba2e19059551a811e72ab7f793
+https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-12.1.0-h8d9b700_16.tar.bz2#4f05bc9844f7c101e6e147dab3c88d5c
+https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.6.1-h7f98852_0.tar.bz2#0347ce6a34f8b55b544b141432c6d4c7
+https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_0.tar.bz2#ec47e97c8e0b27dcadbebc4d17764548
+https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54
+https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.8-h27087fc_0.tar.bz2#e1b07832504eeba765d648389cc387a9
+https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_h77c792f_102.tar.bz2#208f18b1d596b50c6a92a12b30ebe31f
+https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d
+https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2#87473a15119779e021c314249d4b4aed
+https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_2.tar.bz2#ee8b844357a0946870901c7c6f418268
+https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3
+https://conda.anaconda.org/conda-forge/linux-64/lerc-3.0-h9c3ff4c_0.tar.bz2#7fcefde484980d23f0ec24c11e314d2e
+https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_7.tar.bz2#f82dc1c78bcf73583f2656433ce2933c
+https://conda.anaconda.org/conda-forge/linux-64/libdb-6.2.32-h9c3ff4c_0.tar.bz2#3f3258d8f841fbac63b36b75bdac1afd
+https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.12-h166bdaf_0.tar.bz2#d56e3db8fa642fb383f18f5be35eeef2
+https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3
+https://conda.anaconda.org/conda-forge/linux-64/libhiredis-1.0.2-h2cc385e_0.tar.bz2#b34907d3a81a3cd8095ee83d174c074a
+https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2#5c0f338a513a2943c659ae619fca9211
+https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206
+https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680
+https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f
+https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee
+https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334
+https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d
+https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.3-h166bdaf_0.tar.bz2#3d6168ac3560d473e52a7cb836400135
+https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_2.tar.bz2#8302381297332ea50532cf2c67961080
+https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6
+https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238
+https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e
+https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1q-h166bdaf_0.tar.bz2#07acc367c7fc8b716770cd5b36d31717
+https://conda.anaconda.org/conda-forge/linux-64/pcre-8.45-h9c3ff4c_0.tar.bz2#c05d1820a6d34ff07aaaab7a9b7eddaa
+https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036
+https://conda.anaconda.org/conda-forge/linux-64/tbb-2021.5.0-h924138e_1.tar.bz2#6d0aabe2be9d714b1f4ce57514d05b4d
+https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.9-h7f98852_0.tar.bz2#bf6f803a544f26ebbdc3bfff272eb179
+https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.3-h7f98852_0.tar.bz2#be93aabceefa2fac576e971aef407908
+https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.5-h516909a_1.tar.bz2#33f601066901f3e1a85af3522a8113f9
+https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h73d1719_1008.tar.bz2#af49250eca8e139378f8ff0ae9e57251
+https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_7.tar.bz2#37a460703214d0d1b421e2a47eb5e6d0
+https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_7.tar.bz2#785a9296ea478eb78c47593c4da6550f
+https://conda.anaconda.org/conda-forge/linux-64/libcap-2.64-ha37c62d_0.tar.bz2#5896fbd58d0376df8556a4aba1ce4f71
+https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1
+https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336
+https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe
+https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0
+https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904
+https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-14.0.4-he0ac6c6_0.tar.bz2#cecc6e3cb66570ffcfb820c637890f54
+https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.29-haf5c9bc_1.tar.bz2#c01640c8bad562720d6caff0402dbd96
+https://conda.anaconda.org/conda-forge/linux-64/portaudio-19.6.0-h57a0ea0_5.tar.bz2#5469312a373f481c05c380897fd7c923
+https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa
+https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168
+https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_2.tar.bz2#4533821485cde83ab12ff3d8bda83768
+https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_2.tar.bz2#78c26dbb6e07d95ccc0eab8d4540aa0c
+https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e
+https://conda.anaconda.org/conda-forge/linux-64/ccache-4.5.1-haef5404_0.tar.bz2#8458e509920a0bb14bb6fedd248bed57
+https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8
+https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d3e_0.tar.bz2#cdbd49e0ab5c5a6c522acb8271977d4c
+https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef
+https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz2#ebeadbb5fbc44052eeb6f96a2136e3c2
+https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_3.tar.bz2#3e868978a04de8bf65a97bb86760f47a
+https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-hc85c160_1.tar.bz2#151f9fae3ab50f039c8735e47770aa2d
+https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_3.tar.bz2#b6f4a0850ba620030a48b88c25497aaa
+https://conda.anaconda.org/conda-forge/linux-64/mkl-2022.1.0-h84fe81f_915.tar.bz2#b9c8f925797a93dbff45e1626b025a6b
+https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.29-h28c427c_1.tar.bz2#36dbdbf505b131c7e79a3857f3537185
+https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.1-h4ff8645_0.tar.bz2#6acda9d2a3ea84b58637b8f880bbf29b
+https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566
+https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879
+https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7
+https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.1-h166bdaf_0.tar.bz2#0a8e20a8aef954390b9481a527421a8c
+https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1
+https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d
+https://conda.anaconda.org/conda-forge/linux-64/freetype-2.10.4-h0708190_1.tar.bz2#4a06f2ac2e5bfae7b6b245171c3f07aa
+https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar.bz2#a3a99cc33279091262bbc4f5ee7c4571
+https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f
+https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-15_linux64_mkl.tar.bz2#1ffa5033a4fa691d679dabca44bb60f4
+https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.6-default_h2e3cab8_0.tar.bz2#eb70548da697e50cefa7ba939d57d001
+https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-hf5a7f15_1.tar.bz2#005557d6df00af70e438bcd532ce2304
+https://conda.anaconda.org/conda-forge/linux-64/libpq-14.4-hd77ab85_0.tar.bz2#7024df220bd8680192d4bad4024122d1
+https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a
+https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.3-h522a892_0.tar.bz2#96e218d06394a4e4d77028cf70162a09
+https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b
+https://conda.anaconda.org/conda-forge/linux-64/mkl-devel-2022.1.0-ha770c72_916.tar.bz2#69ba49e445f87aea2cba343a71a35ca2
+https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc
+https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.4.0-hb52868f_1.tar.bz2#b7ad78ad2e9ee155f59e6428406ee824
+https://conda.anaconda.org/conda-forge/linux-64/python-3.10.5-h582c2e5_0_cpython.tar.bz2#ccbed83043b9b7b5693164591317f327
+https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0.tar.bz2#c9b568bd804cb2903c6be6f5f68182e4
+https://conda.anaconda.org/conda-forge/noarch/attrs-21.4.0-pyhd8ed1ab_0.tar.bz2#f70280205d7044c8b8358c8de3190e5d
+https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.0-pyhd8ed1ab_0.tar.bz2#abc0453b6e7bfbb87d275d58e333fc98
+https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb
+https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2
+https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89
+https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5
+https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd
+https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905
+https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1002.tar.bz2#7b3f287fcb7683f67b3d953b79f412ea
+https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-15_linux64_mkl.tar.bz2#98a0dc4760b53d7d7ae24e232b4a1e44
+https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-15_linux64_mkl.tar.bz2#8803915f72ba0742c4f4c86ab02e67d1
+https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19
+https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727
+https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054
+https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff
+https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc
+https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.10-2_cp310.tar.bz2#9e7160cd0d865e98f6803f1fe15c8b61
+https://conda.anaconda.org/conda-forge/noarch/pytz-2022.1-pyhd8ed1ab_0.tar.bz2#b87d66d6d3991d988fb31510c95a9267
+https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2
+https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.1.0-pyh8a188c0_0.tar.bz2#a2995ee828f65687ac5b1e71a2ab1e0c
+https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f832c45a477c78bebd107098db465095
+https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96
+https://conda.anaconda.org/conda-forge/linux-64/certifi-2022.6.15-py310hff52083_0.tar.bz2#a5087d46181f812a662fbe20352961ee
+https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py310h255011f_0.tar.bz2#3e4b55b02998782f8ca9ceaaa4f5ada9
+https://conda.anaconda.org/conda-forge/linux-64/coverage-6.4.2-py310h5764c6d_0.tar.bz2#5f94ce84b98c6eb37a0207de551fbb39
+https://conda.anaconda.org/conda-forge/linux-64/cython-0.29.30-py310hd8f1fbe_0.tar.bz2#1b9afbc3fabae40f4664d5390681da0a
+https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_0.tar.bz2#94cb81ffdce328f80c87ac9b01244632
+https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py310hbf28c38_0.tar.bz2#8dc3e2dce8fa122f8df4f3739d1f771b
+https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-15_linux64_mkl.tar.bz2#ed59083832eb21f831ad847419a53855
+https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.1-py310h53a5b5f_0.tar.bz2#9b86a46d908354fe7f91da24f5ea3f36
+https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85
+https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py310he619898_0.tar.bz2#5808b13c720854aaa3307c6e04cc1505
+https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py310hff52083_3.tar.bz2#97f9a22577338f91a94dfac5c1a65a50
+https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h7f54b18_8.tar.bz2#f9dbcfbb942ec9a3c0249cb71da5c7d1
+https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py310hff52083_5.tar.bz2#378f2260e871f3ea46c6fa58d9f05277
+https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984
+https://conda.anaconda.org/conda-forge/linux-64/setuptools-63.2.0-py310hff52083_0.tar.bz2#9a7c1d5f93e2b153d5b0b4930f8b6b66
+https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py310h5764c6d_0.tar.bz2#c42dcb37acd84b3ca197f03f57ef927d
+https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py310h5764c6d_1.tar.bz2#791689ce9e578e2e83b635974af61743
+https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-15_linux64_mkl.tar.bz2#bf390b2b17bf22decf8bd7c1ad905fac
+https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py310h5764c6d_1004.tar.bz2#6499bb11b7feffb63b26847fc9181319
+https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py310h597c629_0.tar.bz2#f285746449d16d92884f4ce0cfe26679
+https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.34.4-py310h5764c6d_0.tar.bz2#c965e9e47e42b19d26994e848d2a40e6
+https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08
+https://conda.anaconda.org/conda-forge/noarch/joblib-1.1.0-pyhd8ed1ab_0.tar.bz2#07d1b5c8cde14d95998fd4767e1e62d2
+https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.3-py310h769672d_0.tar.bz2#e48c810453df0f03bb8fcdff5e1d9e9d
+https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py310hff52083_0.tar.bz2#5d44c6ab93d445b6c433914753390e86
+https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.1-py310h7612f91_0.tar.bz2#14a7ea0620e4c0801bee756171f4dc03
+https://conda.anaconda.org/conda-forge/linux-64/sip-6.6.2-py310hd8f1fbe_0.tar.bz2#3d311837eadeb8137fca02bdb5a9751f
+https://conda.anaconda.org/conda-forge/linux-64/blas-2.115-mkl.tar.bz2#415a1741a10128d016b685ce5a9b8ac4
+https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py310h5701ce4_0.tar.bz2#b038b2e97ae14fea11159dcb2c5abf0a
+https://conda.anaconda.org/conda-forge/linux-64/pyamg-4.2.3-py310hc4a4660_0.tar.bz2#4fa45d2d66864888df6f1ca29f516156
+https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44
+https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py310hd8f1fbe_0.tar.bz2#9e3db99607d6f9285b7348c2af28a095
+https://conda.anaconda.org/conda-forge/noarch/pytest-cov-3.0.0-pyhd8ed1ab_0.tar.bz2#0f7cac11bb696b62d378bde725bfc3eb
+https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f
+https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.4-ha5833f6_2.tar.bz2#dd3aa6715b9e9efaf842febf18ce4261
+https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py310h29803b5_0.tar.bz2#b5fb5328cae86d0b1591fc4894e68238
+https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e
+https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.10-pyhd8ed1ab_0.tar.bz2#14f22c5b9cfd0d93c2806faaa3fe6dec
+https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.2-py310hff52083_0.tar.bz2#0b90a9f77544f943264d47677d63ac9e
+https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_0.tar.bz2#70d6e72856de9551f83ae0f2de689a7a
+https://conda.anaconda.org/conda-forge/noarch/codecov-2.1.11-pyhd3deb0d_0.tar.bz2#9c661c2c14b4667827218402e6624ad5
diff --git a/build_tools/azure/pylatest_conda_forge_mkl_linux-64_environment.yml b/build_tools/azure/pylatest_conda_forge_mkl_linux-64_environment.yml
new file mode 100644
index 0000000000000..99fc3ad9e8f2a
--- /dev/null
+++ b/build_tools/azure/pylatest_conda_forge_mkl_linux-64_environment.yml
@@ -0,0 +1,23 @@
+# DO NOT EDIT: this file is generated from the specification found in the
+# following script to centralize the configuration for CI builds:
+# build_tools/update_environments_and_lock_files.py
+channels:
+ - conda-forge
+dependencies:
+ - python
+ - numpy
+ - blas[build=mkl]
+ - scipy
+ - cython
+ - joblib
+ - threadpoolctl
+ - matplotlib
+ - pandas
+ - pyamg
+ - pytest
+ - pytest-xdist
+ - pillow
+ - codecov
+ - pytest-cov
+ - coverage
+ - ccache
diff --git a/build_tools/azure/pylatest_conda_forge_mkl_no_coverage_environment.yml b/build_tools/azure/pylatest_conda_forge_mkl_no_coverage_environment.yml
new file mode 100644
index 0000000000000..65916f127a4e6
--- /dev/null
+++ b/build_tools/azure/pylatest_conda_forge_mkl_no_coverage_environment.yml
@@ -0,0 +1,20 @@
+# DO NOT EDIT: this file is generated from the specification found in the
+# following script to centralize the configuration for CI builds:
+# build_tools/update_environments_and_lock_files.py
+channels:
+ - conda-forge
+dependencies:
+ - python
+ - numpy
+ - blas[build=mkl]
+ - scipy
+ - cython
+ - joblib
+ - threadpoolctl
+ - matplotlib
+ - pandas
+ - pyamg
+ - pytest
+ - pytest-xdist
+ - pillow
+ - ccache
diff --git a/build_tools/azure/pylatest_conda_forge_mkl_no_coverage_linux-64_conda.lock b/build_tools/azure/pylatest_conda_forge_mkl_no_coverage_linux-64_conda.lock
new file mode 100644
index 0000000000000..b104b7ab02a16
--- /dev/null
+++ b/build_tools/azure/pylatest_conda_forge_mkl_no_coverage_linux-64_conda.lock
@@ -0,0 +1,135 @@
+# Generated by conda-lock.
+# platform: linux-64
+# input_hash: 7f42fec6989da8219b1c3ea2edc884a39f31df1acf891ad7307070c2d02e86c0
+@EXPLICIT
+https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81
+https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2
+https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45
+https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6
+https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb
+https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-hab24e00_0.tar.bz2#19410c3df09dfb12d1206132a1d357c5
+https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.36.1-hea4e1c9_2.tar.bz2#bd4f2e711b39af170e7ff15163fe87ee
+https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.1.0-hdcd56e2_16.tar.bz2#b02605b875559ff99f04351fd5040760
+https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.1.0-ha89aaad_16.tar.bz2#6f5ba041a41eb102a1027d9e68731be7
+https://conda.anaconda.org/conda-forge/linux-64/mkl-include-2022.1.0-h84fe81f_915.tar.bz2#2dcd1acca05c11410d4494d7fc7dfa2a
+https://conda.anaconda.org/conda-forge/noarch/tzdata-2022a-h191b570_0.tar.bz2#84be5301069417a2221187d2f435e0f7
+https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29
+https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.1.0-h69a702a_16.tar.bz2#6bf15e29a20f614b18ae89368260d0a2
+https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab
+https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_kmp_llvm.tar.bz2#562b26ba2e19059551a811e72ab7f793
+https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-12.1.0-h8d9b700_16.tar.bz2#4f05bc9844f7c101e6e147dab3c88d5c
+https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.3.2-h166bdaf_0.tar.bz2#b7607b7b62dce55c194ad84f99464e5f
+https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54
+https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.8-h27087fc_0.tar.bz2#e1b07832504eeba765d648389cc387a9
+https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d
+https://conda.anaconda.org/conda-forge/linux-64/icu-69.1-h9c3ff4c_0.tar.bz2#e0773c9556d588b062a4e1424a6a02fa
+https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_2.tar.bz2#ee8b844357a0946870901c7c6f418268
+https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3
+https://conda.anaconda.org/conda-forge/linux-64/lerc-3.0-h9c3ff4c_0.tar.bz2#7fcefde484980d23f0ec24c11e314d2e
+https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_7.tar.bz2#f82dc1c78bcf73583f2656433ce2933c
+https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.12-h166bdaf_0.tar.bz2#d56e3db8fa642fb383f18f5be35eeef2
+https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3
+https://conda.anaconda.org/conda-forge/linux-64/libhiredis-1.0.2-h2cc385e_0.tar.bz2#b34907d3a81a3cd8095ee83d174c074a
+https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2#5c0f338a513a2943c659ae619fca9211
+https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206
+https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680
+https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f
+https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d
+https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.3-h166bdaf_0.tar.bz2#3d6168ac3560d473e52a7cb836400135
+https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_2.tar.bz2#8302381297332ea50532cf2c67961080
+https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6
+https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238
+https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e
+https://conda.anaconda.org/conda-forge/linux-64/openssl-3.0.5-h166bdaf_0.tar.bz2#f158304d1e469c37e9ffdbe796305571
+https://conda.anaconda.org/conda-forge/linux-64/pcre-8.45-h9c3ff4c_0.tar.bz2#c05d1820a6d34ff07aaaab7a9b7eddaa
+https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036
+https://conda.anaconda.org/conda-forge/linux-64/tbb-2021.5.0-h924138e_1.tar.bz2#6d0aabe2be9d714b1f4ce57514d05b4d
+https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.9-h7f98852_0.tar.bz2#bf6f803a544f26ebbdc3bfff272eb179
+https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.3-h7f98852_0.tar.bz2#be93aabceefa2fac576e971aef407908
+https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.5-h516909a_1.tar.bz2#33f601066901f3e1a85af3522a8113f9
+https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h73d1719_1008.tar.bz2#af49250eca8e139378f8ff0ae9e57251
+https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_7.tar.bz2#37a460703214d0d1b421e2a47eb5e6d0
+https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_7.tar.bz2#785a9296ea478eb78c47593c4da6550f
+https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1
+https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h28343ad_4.tar.bz2#4a049fc560e00e43151dc51368915fdd
+https://conda.anaconda.org/conda-forge/linux-64/libllvm13-13.0.1-hf817b99_2.tar.bz2#47da3ce0d8b2e65ccb226c186dd91eba
+https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0
+https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904
+https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-14.0.4-he0ac6c6_0.tar.bz2#cecc6e3cb66570ffcfb820c637890f54
+https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.29-h26416b9_1.tar.bz2#eb0ab80f8c0e15febcd644c43d1386ba
+https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa
+https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168
+https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_2.tar.bz2#4533821485cde83ab12ff3d8bda83768
+https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_2.tar.bz2#78c26dbb6e07d95ccc0eab8d4540aa0c
+https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e
+https://conda.anaconda.org/conda-forge/linux-64/ccache-4.5.1-haef5404_0.tar.bz2#8458e509920a0bb14bb6fedd248bed57
+https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h08a2579_0.tar.bz2#d25e05e7ee0e302b52d24491db4891eb
+https://conda.anaconda.org/conda-forge/linux-64/libclang-13.0.1-default_hc23dcda_0.tar.bz2#8cebb0736cba83485b13dc10d242d96d
+https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz2#ebeadbb5fbc44052eeb6f96a2136e3c2
+https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_3.tar.bz2#3e868978a04de8bf65a97bb86760f47a
+https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-hc85c160_1.tar.bz2#151f9fae3ab50f039c8735e47770aa2d
+https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.12-h885dcf4_1.tar.bz2#d1355eaa48f465782f228275a0a69771
+https://conda.anaconda.org/conda-forge/linux-64/mkl-2022.1.0-h84fe81f_915.tar.bz2#b9c8f925797a93dbff45e1626b025a6b
+https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.29-hbc51c84_1.tar.bz2#823fca0470a61bb35c89c605adc96af5
+https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.1-h4ff8645_0.tar.bz2#6acda9d2a3ea84b58637b8f880bbf29b
+https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1
+https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d
+https://conda.anaconda.org/conda-forge/linux-64/freetype-2.10.4-h0708190_1.tar.bz2#4a06f2ac2e5bfae7b6b245171c3f07aa
+https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar.bz2#a3a99cc33279091262bbc4f5ee7c4571
+https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f
+https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-15_linux64_mkl.tar.bz2#1ffa5033a4fa691d679dabca44bb60f4
+https://conda.anaconda.org/conda-forge/linux-64/libpq-14.4-he2d8382_0.tar.bz2#73038d5278e922bb655235ee18545ced
+https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.3-h522a892_0.tar.bz2#96e218d06394a4e4d77028cf70162a09
+https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b
+https://conda.anaconda.org/conda-forge/linux-64/mkl-devel-2022.1.0-ha770c72_916.tar.bz2#69ba49e445f87aea2cba343a71a35ca2
+https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc
+https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.4.0-hb52868f_1.tar.bz2#b7ad78ad2e9ee155f59e6428406ee824
+https://conda.anaconda.org/conda-forge/linux-64/python-3.10.5-ha86cf86_0_cpython.tar.bz2#55a9dbd539c8af4e1a0ff5d87c1f3a14
+https://conda.anaconda.org/conda-forge/noarch/attrs-21.4.0-pyhd8ed1ab_0.tar.bz2#f70280205d7044c8b8358c8de3190e5d
+https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb
+https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2
+https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89
+https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5
+https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905
+https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-15_linux64_mkl.tar.bz2#98a0dc4760b53d7d7ae24e232b4a1e44
+https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-15_linux64_mkl.tar.bz2#8803915f72ba0742c4f4c86ab02e67d1
+https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19
+https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054
+https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc
+https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.10-2_cp310.tar.bz2#9e7160cd0d865e98f6803f1fe15c8b61
+https://conda.anaconda.org/conda-forge/noarch/pytz-2022.1-pyhd8ed1ab_0.tar.bz2#b87d66d6d3991d988fb31510c95a9267
+https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2
+https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.1.0-pyh8a188c0_0.tar.bz2#a2995ee828f65687ac5b1e71a2ab1e0c
+https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96
+https://conda.anaconda.org/conda-forge/linux-64/certifi-2022.6.15-py310hff52083_0.tar.bz2#a5087d46181f812a662fbe20352961ee
+https://conda.anaconda.org/conda-forge/linux-64/cython-0.29.30-py310hd8f1fbe_0.tar.bz2#1b9afbc3fabae40f4664d5390681da0a
+https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_0.tar.bz2#94cb81ffdce328f80c87ac9b01244632
+https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py310hbf28c38_0.tar.bz2#8dc3e2dce8fa122f8df4f3739d1f771b
+https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-15_linux64_mkl.tar.bz2#ed59083832eb21f831ad847419a53855
+https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.1-py310h53a5b5f_0.tar.bz2#9b86a46d908354fe7f91da24f5ea3f36
+https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85
+https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py310he619898_0.tar.bz2#5808b13c720854aaa3307c6e04cc1505
+https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py310hff52083_3.tar.bz2#97f9a22577338f91a94dfac5c1a65a50
+https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-4.19.18-py310h122e73d_8.tar.bz2#376d9895377aca761cdcded975211d39
+https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984
+https://conda.anaconda.org/conda-forge/linux-64/setuptools-63.2.0-py310hff52083_0.tar.bz2#9a7c1d5f93e2b153d5b0b4930f8b6b66
+https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py310h5764c6d_0.tar.bz2#c42dcb37acd84b3ca197f03f57ef927d
+https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py310h5764c6d_1.tar.bz2#791689ce9e578e2e83b635974af61743
+https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-15_linux64_mkl.tar.bz2#bf390b2b17bf22decf8bd7c1ad905fac
+https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.34.4-py310h5764c6d_0.tar.bz2#c965e9e47e42b19d26994e848d2a40e6
+https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.2-hcf0ee16_0.tar.bz2#79d7fca692d224dc29a72bda90f78a7b
+https://conda.anaconda.org/conda-forge/noarch/joblib-1.1.0-pyhd8ed1ab_0.tar.bz2#07d1b5c8cde14d95998fd4767e1e62d2
+https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.3-py310h769672d_0.tar.bz2#e48c810453df0f03bb8fcdff5e1d9e9d
+https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py310hff52083_0.tar.bz2#5d44c6ab93d445b6c433914753390e86
+https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.1-py310h7612f91_0.tar.bz2#14a7ea0620e4c0801bee756171f4dc03
+https://conda.anaconda.org/conda-forge/linux-64/blas-2.115-mkl.tar.bz2#415a1741a10128d016b685ce5a9b8ac4
+https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py310h5701ce4_0.tar.bz2#b038b2e97ae14fea11159dcb2c5abf0a
+https://conda.anaconda.org/conda-forge/linux-64/pyamg-4.2.3-py310hc4a4660_0.tar.bz2#4fa45d2d66864888df6f1ca29f516156
+https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f
+https://conda.anaconda.org/conda-forge/linux-64/qt-5.12.9-h1304e3e_6.tar.bz2#f2985d160b8c43dd427923c04cd732fe
+https://conda.anaconda.org/conda-forge/linux-64/pyqt-impl-5.12.3-py310h1f8e252_8.tar.bz2#248a59258a0a6d4d8c0f66c39a150781
+https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e
+https://conda.anaconda.org/conda-forge/linux-64/pyqtchart-5.12-py310hfcd6d55_8.tar.bz2#e7daa09dba0d95a874c3d0a678665e77
+https://conda.anaconda.org/conda-forge/linux-64/pyqtwebengine-5.12.1-py310hfcd6d55_8.tar.bz2#f78e8d66e92e73e8fda274664888b2a2
+https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.12.3-py310hff52083_8.tar.bz2#f75c4f1fe3bd1a15ae0d2e5f33bc75e6
+https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.2-py310hff52083_0.tar.bz2#0b90a9f77544f943264d47677d63ac9e
diff --git a/build_tools/azure/pylatest_conda_forge_mkl_osx-64_conda.lock b/build_tools/azure/pylatest_conda_forge_mkl_osx-64_conda.lock
new file mode 100644
index 0000000000000..94ad3f57a74fc
--- /dev/null
+++ b/build_tools/azure/pylatest_conda_forge_mkl_osx-64_conda.lock
@@ -0,0 +1,131 @@
+# Generated by conda-lock.
+# platform: osx-64
+# input_hash: d828c3b0c630cc7d9041a8cd41cfe47a33914b1928ecdd4e440ee35fbc286c94
+@EXPLICIT
+https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h0d85af4_4.tar.bz2#37edc4e6304ca87316e160f5ca0bd1b5
+https://conda.anaconda.org/conda-forge/osx-64/ca-certificates-2022.6.15-h033912b_0.tar.bz2#d16674f96e47de0d08af3fc97803134e
+https://conda.anaconda.org/conda-forge/osx-64/giflib-5.2.1-hbcb3906_2.tar.bz2#be8f747c37e4d7e346c133e641966750
+https://conda.anaconda.org/conda-forge/osx-64/jpeg-9e-hac89ed1_2.tar.bz2#60d90a3f5803660c5c2a2e9d883df0a6
+https://conda.anaconda.org/conda-forge/osx-64/libbrotlicommon-1.0.9-h5eb16cf_7.tar.bz2#898a296a93b36e42a065963bb4504f2b
+https://conda.anaconda.org/conda-forge/osx-64/libcxx-14.0.6-hce7ea42_0.tar.bz2#ac504a8074ae8add8b837a93d7bc33ca
+https://conda.anaconda.org/conda-forge/osx-64/libdeflate-1.12-hac89ed1_0.tar.bz2#3e2e03ff8bcdd49315541a80e4e9023d
+https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.2-h0d85af4_5.tar.bz2#ccb34fb14960ad8b125962d3d79b31a9
+https://conda.anaconda.org/conda-forge/noarch/libgfortran-devel_osx-64-9.3.0-h6c81a4c_23.tar.bz2#d66ac79367c179bf53520066430565e8
+https://conda.anaconda.org/conda-forge/osx-64/libiconv-1.16-haf1e3a3_0.tar.bz2#c5fab167412a52e491c8e11453ae016f
+https://conda.anaconda.org/conda-forge/osx-64/libwebp-base-1.2.2-h0d85af4_1.tar.bz2#052369eda6e13f44b308407c99857f84
+https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.2.12-hfe4f2af_2.tar.bz2#1d676bbaa2599f22121dd5bbea455c2e
+https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-14.0.4-ha654fa7_0.tar.bz2#5d5ab9ab83ce21422be84ecfd3142201
+https://conda.anaconda.org/conda-forge/osx-64/mkl-include-2022.1.0-h6bab518_928.tar.bz2#67f8511a5eaf693a202486f74035b3f7
+https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.3-h96cf925_1.tar.bz2#76217ebfbb163ff2770a261f955a5861
+https://conda.anaconda.org/conda-forge/osx-64/pthread-stubs-0.4-hc929b4f_1001.tar.bz2#addd19059de62181cd11ae8f4ef26084
+https://conda.anaconda.org/conda-forge/noarch/tzdata-2022a-h191b570_0.tar.bz2#84be5301069417a2221187d2f435e0f7
+https://conda.anaconda.org/conda-forge/osx-64/xorg-libxau-1.0.9-h35c211d_0.tar.bz2#c5049997b2e98edfbcdd294582f66281
+https://conda.anaconda.org/conda-forge/osx-64/xorg-libxdmcp-1.1.3-h35c211d_0.tar.bz2#86ac76d6bf1cbb9621943eb3bd9ae36e
+https://conda.anaconda.org/conda-forge/osx-64/xz-5.2.5-haf1e3a3_1.tar.bz2#41116deb499e9bc58048c297d6403ce6
+https://conda.anaconda.org/conda-forge/osx-64/gmp-6.2.1-h2e338ed_0.tar.bz2#dedc96914428dae572a39e69ee2a392f
+https://conda.anaconda.org/conda-forge/osx-64/isl-0.22.1-hb1e8313_2.tar.bz2#d875acf04fcf1e4d5f3e179e0799363d
+https://conda.anaconda.org/conda-forge/osx-64/lerc-3.0-he49afe7_0.tar.bz2#a1d21f85e4b0d5e27a88ecab5e3961da
+https://conda.anaconda.org/conda-forge/osx-64/libbrotlidec-1.0.9-h5eb16cf_7.tar.bz2#2f53da3dfe9d14ef4666ad2558df8615
+https://conda.anaconda.org/conda-forge/osx-64/libbrotlienc-1.0.9-h5eb16cf_7.tar.bz2#16993c3cd38b5ad4980b0934ac308ef6
+https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-9.3.0-h6c81a4c_23.tar.bz2#a6956ceb628b14594613cefee5127a7a
+https://conda.anaconda.org/conda-forge/osx-64/libllvm13-13.0.1-h64f94b2_2.tar.bz2#bac1c6f12f44f40e19274e6e04e9bad5
+https://conda.anaconda.org/conda-forge/osx-64/libxcb-1.13-h0d85af4_1004.tar.bz2#eb7860935e14aec936065cbc21a1a962
+https://conda.anaconda.org/conda-forge/osx-64/lz4-c-1.9.3-he49afe7_1.tar.bz2#05c08241b66631c00ca4f9e0b75320bc
+https://conda.anaconda.org/conda-forge/osx-64/openssl-1.1.1q-hfe4f2af_0.tar.bz2#ce822517fb00e8bafea6fe77d07f20bd
+https://conda.anaconda.org/conda-forge/osx-64/readline-8.1.2-h3899abd_0.tar.bz2#89fa404901fa8fb7d4f4e07083b8d635
+https://conda.anaconda.org/conda-forge/osx-64/tapi-1100.0.11-h9ce4665_0.tar.bz2#f9ff42ccf809a21ba6f8607f8de36108
+https://conda.anaconda.org/conda-forge/osx-64/tbb-2021.5.0-hbb4e6a2_1.tar.bz2#b83feb61bdbc393ba05a1b7626c4fcf0
+https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.12-h5dbffcc_0.tar.bz2#8e9480d9c47061db2ed1b4ecce519a7f
+https://conda.anaconda.org/conda-forge/osx-64/zlib-1.2.12-hfe4f2af_2.tar.bz2#fe7ebd3b35b8f26f6a7d0af481a21721
+https://conda.anaconda.org/conda-forge/osx-64/brotli-bin-1.0.9-h5eb16cf_7.tar.bz2#6ae0a8419a03d0d9675f319bef66d3aa
+https://conda.anaconda.org/conda-forge/osx-64/libclang-cpp13-13.0.1-default_he082bbe_0.tar.bz2#88cccadb3e9824f6859e605ad64c526c
+https://conda.anaconda.org/conda-forge/osx-64/libgfortran-5.0.0-9_3_0_h6c81a4c_23.tar.bz2#60f48cef2d50674e0428c5579b6c3f66
+https://conda.anaconda.org/conda-forge/osx-64/libpng-1.6.37-h5a3d3bf_3.tar.bz2#bb43320880baa655d8837477fd18807b
+https://conda.anaconda.org/conda-forge/osx-64/llvm-tools-13.0.1-h64f94b2_2.tar.bz2#38b9526e6cc69b7e0a358771ddb02874
+https://conda.anaconda.org/conda-forge/osx-64/mkl-2022.1.0-h860c996_928.tar.bz2#98a4d58de0ba6e61ce46620b775c19ce
+https://conda.anaconda.org/conda-forge/osx-64/mpfr-4.1.0-h0f52abe_1.tar.bz2#afe26b08c2d2265b4d663d199000e5da
+https://conda.anaconda.org/conda-forge/osx-64/sigtool-0.1.3-h57ddcff_0.tar.bz2#e0dcb354df4ad4f79539d57e4f8ec1bd
+https://conda.anaconda.org/conda-forge/osx-64/sqlite-3.39.1-hd9f0692_0.tar.bz2#add14b880512697bba36cb71a3d26c8b
+https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.2-ha9df2e0_2.tar.bz2#6e5b0ab35f8e4c407c3d2f25b51bc274
+https://conda.anaconda.org/conda-forge/osx-64/brotli-1.0.9-h5eb16cf_7.tar.bz2#d3320319f06d6adb52ed01e1839475a1
+https://conda.anaconda.org/conda-forge/osx-64/clang-13-13.0.1-default_he082bbe_0.tar.bz2#ffb3d690ddae0b4f601fb937187e38e1
+https://conda.anaconda.org/conda-forge/osx-64/freetype-2.10.4-h4cff582_1.tar.bz2#5a136a432c6062362cd7990c514bd8d6
+https://conda.anaconda.org/conda-forge/osx-64/ld64_osx-64-609-h6fbe7a8_10.tar.bz2#576f44dff0debeac8806eecdf0f301a4
+https://conda.anaconda.org/conda-forge/osx-64/libblas-3.9.0-15_osx64_mkl.tar.bz2#c998ae608542192771aadad79ec1a149
+https://conda.anaconda.org/conda-forge/osx-64/libhiredis-1.0.2-h2beb688_0.tar.bz2#524282b2c46c9dedf051b3bc2ae05494
+https://conda.anaconda.org/conda-forge/osx-64/libtiff-4.4.0-h9847915_1.tar.bz2#18372b55ad60d8f0ed1ca4bca9eb2d0d
+https://conda.anaconda.org/conda-forge/osx-64/mkl-devel-2022.1.0-h694c41f_929.tar.bz2#041ceef009fe6d29cbd2555907c23ab3
+https://conda.anaconda.org/conda-forge/osx-64/mpc-1.2.1-hbb51d92_0.tar.bz2#9f46d6ad4c460679ee997abc10da3bac
+https://conda.anaconda.org/conda-forge/osx-64/python-3.10.5-hdaaf3db_0_cpython.tar.bz2#2c27a532578a3e3b1574e0a50d9b36b9
+https://conda.anaconda.org/conda-forge/noarch/attrs-21.4.0-pyhd8ed1ab_0.tar.bz2#f70280205d7044c8b8358c8de3190e5d
+https://conda.anaconda.org/conda-forge/osx-64/ccache-4.6-h8580cb5_0.tar.bz2#fa11b3db27aec2910b317f8c06f5eed0
+https://conda.anaconda.org/conda-forge/osx-64/cctools_osx-64-973.0.1-h3eff9a4_10.tar.bz2#a7552b2de2f52bbf8b726d982507b1ef
+https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.0-pyhd8ed1ab_0.tar.bz2#abc0453b6e7bfbb87d275d58e333fc98
+https://conda.anaconda.org/conda-forge/osx-64/clang-13.0.1-h694c41f_0.tar.bz2#d11f9dc9b90ae4b55f501bdf2784d3c0
+https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb
+https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2
+https://conda.anaconda.org/conda-forge/osx-64/gfortran_impl_osx-64-9.3.0-h9cc0e5e_23.tar.bz2#e906ccf40e156c6aa1e7804a1db4b849
+https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd
+https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905
+https://conda.anaconda.org/conda-forge/osx-64/lcms2-2.12-h577c468_0.tar.bz2#abce77b852b73670e85e104746b0ea1b
+https://conda.anaconda.org/conda-forge/osx-64/ld64-609-ha328185_10.tar.bz2#bb5eb6726c4b1bbad865673edb48ba27
+https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.9.0-15_osx64_mkl.tar.bz2#5688696c64566ea2b138d80fe8c21078
+https://conda.anaconda.org/conda-forge/osx-64/liblapack-3.9.0-15_osx64_mkl.tar.bz2#0983e2d3d70eecb92f5be56bbf23091c
+https://conda.anaconda.org/conda-forge/osx-64/libwebp-1.2.2-h28dabe5_0.tar.bz2#6aa31e81854e3172dae89300ae9a5444
+https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19
+https://conda.anaconda.org/conda-forge/osx-64/openjpeg-2.4.0-h6e7aa92_1.tar.bz2#c8cbb6d99f6467246ac26a139256e50f
+https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054
+https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff
+https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc
+https://conda.anaconda.org/conda-forge/osx-64/python_abi-3.10-2_cp310.tar.bz2#502ca4a8b43b424316f380d864832877
+https://conda.anaconda.org/conda-forge/noarch/pytz-2022.1-pyhd8ed1ab_0.tar.bz2#b87d66d6d3991d988fb31510c95a9267
+https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2
+https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.1.0-pyh8a188c0_0.tar.bz2#a2995ee828f65687ac5b1e71a2ab1e0c
+https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f832c45a477c78bebd107098db465095
+https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96
+https://conda.anaconda.org/conda-forge/osx-64/cctools-973.0.1-h351d84c_10.tar.bz2#4c238f9fccbac5beaf219d59a59b7f10
+https://conda.anaconda.org/conda-forge/osx-64/certifi-2022.6.15-py310h2ec42d9_0.tar.bz2#7f60b1d9d5844975ca9794ce9bde7999
+https://conda.anaconda.org/conda-forge/osx-64/cffi-1.15.1-py310h96bbf6e_0.tar.bz2#7247d0e5dc3dc1adb94de8353478a015
+https://conda.anaconda.org/conda-forge/osx-64/clangxx-13.0.1-default_he082bbe_0.tar.bz2#2c7017821fa3d741405ab4c4da24ff54
+https://conda.anaconda.org/conda-forge/osx-64/coverage-6.4.2-py310h6c45266_0.tar.bz2#7d837797dea29c9e6a479a98d81e9543
+https://conda.anaconda.org/conda-forge/osx-64/cython-0.29.30-py310hd4537e4_0.tar.bz2#1dbaaf42d2bb8289b3b03f4d20a189ba
+https://conda.anaconda.org/conda-forge/osx-64/kiwisolver-1.4.4-py310habb735a_0.tar.bz2#33747e51e7dd3a9423b66c2a4012fe7c
+https://conda.anaconda.org/conda-forge/osx-64/liblapacke-3.9.0-15_osx64_mkl.tar.bz2#6af691dd2f3d7a45a84a5ff43c33e9d3
+https://conda.anaconda.org/conda-forge/osx-64/numpy-1.23.1-py310ha3f357c_0.tar.bz2#68fbd55712a1abb9cb416d38db848df6
+https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85
+https://conda.anaconda.org/conda-forge/osx-64/pillow-9.2.0-py310hb3240ae_0.tar.bz2#19342596ed0e3d4d6d604e8819e9392e
+https://conda.anaconda.org/conda-forge/osx-64/pluggy-1.0.0-py310h2ec42d9_3.tar.bz2#b2349ab9b4c83ae573a6985f728e5f37
+https://conda.anaconda.org/conda-forge/osx-64/pysocks-1.7.1-py310h2ec42d9_5.tar.bz2#8b7a82347d1ed70878126333339f4969
+https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984
+https://conda.anaconda.org/conda-forge/osx-64/setuptools-63.2.0-py310h2ec42d9_0.tar.bz2#bd56557297d0dc79853846eed9a69117
+https://conda.anaconda.org/conda-forge/osx-64/tornado-6.2-py310h6c45266_0.tar.bz2#854dc3c0c268f571429b0ca93d318880
+https://conda.anaconda.org/conda-forge/osx-64/unicodedata2-14.0.0-py310h1961e1f_1.tar.bz2#3fe48797c02b3b467efd4d9bc53d1624
+https://conda.anaconda.org/conda-forge/osx-64/blas-devel-3.9.0-15_osx64_mkl.tar.bz2#6b4ce97e5cd12e603ee4e8ec1ac23742
+https://conda.anaconda.org/conda-forge/osx-64/brotlipy-0.7.0-py310h1961e1f_1004.tar.bz2#e84bd2f66966aa51356dfe14ef887e42
+https://conda.anaconda.org/conda-forge/noarch/compiler-rt_osx-64-13.0.1-hd3f61c9_0.tar.bz2#d744aae4a8e003c1fd2a1d3d757bd628
+https://conda.anaconda.org/conda-forge/osx-64/cryptography-37.0.4-py310h52c3658_0.tar.bz2#0dbc94ce0eedf8eae3aded71c5761ad3
+https://conda.anaconda.org/conda-forge/osx-64/fonttools-4.34.4-py310h6c45266_0.tar.bz2#f7a44f9ec53d42a5d1f185d7e0522357
+https://conda.anaconda.org/conda-forge/noarch/joblib-1.1.0-pyhd8ed1ab_0.tar.bz2#07d1b5c8cde14d95998fd4767e1e62d2
+https://conda.anaconda.org/conda-forge/osx-64/pandas-1.4.3-py310h3099161_0.tar.bz2#872f475fc6ccf72790b8c076fade532b
+https://conda.anaconda.org/conda-forge/osx-64/pytest-7.1.2-py310h2ec42d9_0.tar.bz2#213fafaad4bdda317efc58119135876b
+https://conda.anaconda.org/conda-forge/osx-64/scipy-1.8.1-py310h1f9c157_0.tar.bz2#9301a094c53ed53bbbc168597c39719e
+https://conda.anaconda.org/conda-forge/osx-64/blas-2.115-mkl.tar.bz2#9e39ef5ea0a61aa52e69fb11103839df
+https://conda.anaconda.org/conda-forge/osx-64/compiler-rt-13.0.1-he01351e_0.tar.bz2#e30fcb14e68727908484468c4fd35ede
+https://conda.anaconda.org/conda-forge/osx-64/matplotlib-base-3.5.2-py310h4510841_0.tar.bz2#fd973aaf25d1e82d67ce4d87f7aad060
+https://conda.anaconda.org/conda-forge/osx-64/pyamg-4.2.3-py310h84c6d00_0.tar.bz2#2e0bb5228d595052b8a67d5baa643c67
+https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44
+https://conda.anaconda.org/conda-forge/noarch/pytest-cov-3.0.0-pyhd8ed1ab_0.tar.bz2#0f7cac11bb696b62d378bde725bfc3eb
+https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f
+https://conda.anaconda.org/conda-forge/osx-64/clang_osx-64-13.0.1-h71a8856_2.tar.bz2#420caff571651bd620a1bae84ee99cdf
+https://conda.anaconda.org/conda-forge/osx-64/matplotlib-3.5.2-py310h2ec42d9_0.tar.bz2#55c15bc829a216804a0fe2aabe9294e6
+https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e
+https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.10-pyhd8ed1ab_0.tar.bz2#14f22c5b9cfd0d93c2806faaa3fe6dec
+https://conda.anaconda.org/conda-forge/osx-64/c-compiler-1.4.2-had99412_0.tar.bz2#2e89dc235b304e25307378efec943f50
+https://conda.anaconda.org/conda-forge/osx-64/clangxx_osx-64-13.0.1-heae0f87_2.tar.bz2#169caaf4aaab4e07dc697421329b7a73
+https://conda.anaconda.org/conda-forge/osx-64/gfortran_osx-64-9.3.0-h18f7dce_15.tar.bz2#48f985e599ff223cd8acea3595d2cbe5
+https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_0.tar.bz2#70d6e72856de9551f83ae0f2de689a7a
+https://conda.anaconda.org/conda-forge/noarch/codecov-2.1.11-pyhd3deb0d_0.tar.bz2#9c661c2c14b4667827218402e6624ad5
+https://conda.anaconda.org/conda-forge/osx-64/cxx-compiler-1.4.2-h1b54a9f_0.tar.bz2#7a72e1003218550251de458140635999
+https://conda.anaconda.org/conda-forge/osx-64/gfortran-9.3.0-h768ea0c_15.tar.bz2#de56b2cd45db24c02bcc0463643e03a6
+https://conda.anaconda.org/conda-forge/osx-64/fortran-compiler-1.4.2-h373f3de_0.tar.bz2#d14368828e3766dfc3b072e275c50c7d
+https://conda.anaconda.org/conda-forge/osx-64/compilers-1.4.2-h694c41f_0.tar.bz2#40ad9bf1a937232adba976dab26c65a6
diff --git a/build_tools/azure/pylatest_conda_forge_mkl_osx-64_environment.yml b/build_tools/azure/pylatest_conda_forge_mkl_osx-64_environment.yml
new file mode 100644
index 0000000000000..07f09e1e8e5cf
--- /dev/null
+++ b/build_tools/azure/pylatest_conda_forge_mkl_osx-64_environment.yml
@@ -0,0 +1,25 @@
+# DO NOT EDIT: this file is generated from the specification found in the
+# following script to centralize the configuration for CI builds:
+# build_tools/update_environments_and_lock_files.py
+channels:
+ - conda-forge
+dependencies:
+ - python
+ - numpy
+ - blas[build=mkl]
+ - scipy
+ - cython
+ - joblib
+ - threadpoolctl
+ - matplotlib
+ - pandas
+ - pyamg
+ - pytest
+ - pytest-xdist
+ - pillow
+ - codecov
+ - pytest-cov
+ - coverage
+ - ccache
+ - compilers
+ - llvm-openmp
diff --git a/build_tools/azure/pylatest_conda_mkl_no_openmp_environment.yml b/build_tools/azure/pylatest_conda_mkl_no_openmp_environment.yml
new file mode 100644
index 0000000000000..415ad45bfa06d
--- /dev/null
+++ b/build_tools/azure/pylatest_conda_mkl_no_openmp_environment.yml
@@ -0,0 +1,23 @@
+# DO NOT EDIT: this file is generated from the specification found in the
+# following script to centralize the configuration for CI builds:
+# build_tools/update_environments_and_lock_files.py
+channels:
+ - defaults
+dependencies:
+ - python
+ - numpy
+ - blas[build=mkl]
+ - scipy
+ - cython
+ - joblib
+ - threadpoolctl
+ - matplotlib
+ - pandas
+ - pyamg
+ - pytest
+ - pytest-xdist
+ - pillow
+ - codecov
+ - pytest-cov
+ - coverage=6.2
+ - ccache
diff --git a/build_tools/azure/pylatest_conda_mkl_no_openmp_osx-64_conda.lock b/build_tools/azure/pylatest_conda_mkl_no_openmp_osx-64_conda.lock
new file mode 100644
index 0000000000000..35e4321bb6f4d
--- /dev/null
+++ b/build_tools/azure/pylatest_conda_mkl_no_openmp_osx-64_conda.lock
@@ -0,0 +1,83 @@
+# Generated by conda-lock.
+# platform: osx-64
+# input_hash: 1ab4cb5d3ea271222804b485b5288a9edc204e385d109d5acc8fb1d32e0ef6a4
+@EXPLICIT
+https://repo.anaconda.com/pkgs/main/osx-64/blas-1.0-mkl.conda#cb2c87e85ac8e0ceae776d26d4214c8a
+https://repo.anaconda.com/pkgs/main/osx-64/ca-certificates-2022.4.26-hecd8cb5_0.conda#dd4c1cfc3606b56486f7af0a99e80fa3
+https://repo.anaconda.com/pkgs/main/osx-64/giflib-5.2.1-haf1e3a3_0.conda#0c36d6800a1a0f0ae244699a09d3f982
+https://repo.anaconda.com/pkgs/main/osx-64/intel-openmp-2021.4.0-hecd8cb5_3538.conda#65e79d0ffef79cbb8ebd3c71e74eb50a
+https://repo.anaconda.com/pkgs/main/osx-64/jpeg-9e-hca72f7f_0.conda#99b7d820514a0c07818d58c320ab21fc
+https://repo.anaconda.com/pkgs/main/osx-64/libcxx-12.0.0-h2f01273_0.conda#fa697ecaca74bdf72bd0a10e42a2287a
+https://repo.anaconda.com/pkgs/main/osx-64/libgfortran-3.0.1-h93005f0_2.conda#2f6d6d3c7a46ff214a5a1a8991af9bef
+https://repo.anaconda.com/pkgs/main/osx-64/libwebp-base-1.2.2-hca72f7f_0.conda#029b8fce196d53c93af17512f6f606d8
+https://repo.anaconda.com/pkgs/main/osx-64/ncurses-6.3-hca72f7f_3.conda#dba236b91a8c0ef6ddecc56e387e92d2
+https://repo.anaconda.com/pkgs/main/noarch/tzdata-2022a-hda174b7_0.conda#e8fd073330b1083fcd3bc2634722f1a6
+https://repo.anaconda.com/pkgs/main/osx-64/xz-5.2.5-hca72f7f_1.conda#bb093b4af8f53670468795e5f12676e5
+https://repo.anaconda.com/pkgs/main/osx-64/zlib-1.2.12-h4dc903c_2.conda#4264c14bdd0bd302b0232cb65f3ee275
+https://repo.anaconda.com/pkgs/main/osx-64/brotli-1.0.9-hb1e8313_2.conda#47c6f0f0789dc3b0c350e2f6caac3ebc
+https://repo.anaconda.com/pkgs/main/osx-64/ccache-3.7.9-hf120daa_0.conda#a01515a32e721c51d631283f991bc8ea
+https://repo.anaconda.com/pkgs/main/osx-64/libffi-3.3-hb1e8313_2.conda#0c959d444ac65555cb836cdbd3e9a2d9
+https://repo.anaconda.com/pkgs/main/osx-64/libpng-1.6.37-ha441bb4_0.conda#d69245a20ec59d8dc534c65308607129
+https://repo.anaconda.com/pkgs/main/osx-64/lz4-c-1.9.3-h23ab428_1.conda#dc70fec3978d3189741886cc05fcb145
+https://repo.anaconda.com/pkgs/main/osx-64/mkl-2021.4.0-hecd8cb5_637.conda#07d14ece4a852cefa17c1c156db8134e
+https://repo.anaconda.com/pkgs/main/osx-64/openssl-1.1.1q-hca72f7f_0.conda#7103facc03bce257ae92b9c0981906fd
+https://repo.anaconda.com/pkgs/main/osx-64/readline-8.1.2-hca72f7f_1.conda#c54a6153e7ef82f55e7a0ae2f6749592
+https://repo.anaconda.com/pkgs/main/osx-64/tk-8.6.12-h5d9f67b_0.conda#047f0af5486d19163e37fd7f8ae3d29f
+https://repo.anaconda.com/pkgs/main/osx-64/freetype-2.11.0-hd8bbffd_0.conda#a06dcb72dc6961d37f280b4b97d74f43
+https://repo.anaconda.com/pkgs/main/osx-64/sqlite-3.38.5-h707629a_0.conda#658dd68c10414ce5630bc1f274a186a9
+https://repo.anaconda.com/pkgs/main/osx-64/zstd-1.5.2-hcb37349_0.conda#d3ba225e3bc4285d8efd8cdfd7aa6112
+https://repo.anaconda.com/pkgs/main/osx-64/libtiff-4.2.0-hdb42f99_1.conda#be71b575ef057665407d8a298499a669
+https://repo.anaconda.com/pkgs/main/osx-64/python-3.9.12-hdfd78df_1.conda#744dfacb4aa313dd39a24eacc8d195aa
+https://repo.anaconda.com/pkgs/main/noarch/attrs-21.4.0-pyhd3eb1b0_0.conda#3bc977a57587a7964921e3e1e2e31f9e
+https://repo.anaconda.com/pkgs/main/osx-64/certifi-2022.6.15-py39hecd8cb5_0.conda#30db92d8d0532828b651a5bcca55d70d
+https://repo.anaconda.com/pkgs/main/noarch/charset-normalizer-2.0.4-pyhd3eb1b0_0.conda#e7a441d94234b2b5fafee06e25dbf076
+https://repo.anaconda.com/pkgs/main/osx-64/coverage-6.2-py39hca72f7f_0.conda#55962a70ebebc8de15c4e1d745b20cdd
+https://repo.anaconda.com/pkgs/main/noarch/cycler-0.11.0-pyhd3eb1b0_0.conda#f5e365d2cdb66d547eb8c3ab93843aab
+https://repo.anaconda.com/pkgs/main/osx-64/cython-0.29.30-py39he9d5cce_0.conda#86142f3909fa8984ade817f086d45493
+https://repo.anaconda.com/pkgs/main/noarch/execnet-1.9.0-pyhd3eb1b0_0.conda#f895937671af67cebb8af617494b3513
+https://repo.anaconda.com/pkgs/main/noarch/idna-3.3-pyhd3eb1b0_0.conda#8f43a528cf83b43af38a4d142fa38b8a
+https://repo.anaconda.com/pkgs/main/noarch/iniconfig-1.1.1-pyhd3eb1b0_0.tar.bz2#e40edff2c5708f342cef43c7f280c507
+https://repo.anaconda.com/pkgs/main/noarch/joblib-1.1.0-pyhd3eb1b0_0.conda#cae25b839f3b24686e683addde01b742
+https://repo.anaconda.com/pkgs/main/osx-64/kiwisolver-1.4.2-py39he9d5cce_0.conda#6db2c99a6633b0cbd82faa1a36cd29d7
+https://repo.anaconda.com/pkgs/main/osx-64/lcms2-2.12-hf1fd2bf_0.conda#697aba7a3308226df7a93ccfeae16ffa
+https://repo.anaconda.com/pkgs/main/osx-64/libwebp-1.2.2-h56c3ce4_0.conda#027d2450b64e251b8169798f6121b47a
+https://repo.anaconda.com/pkgs/main/noarch/munkres-1.1.4-py_0.conda#148362ba07f92abab76999a680c80084
+https://repo.anaconda.com/pkgs/main/osx-64/pluggy-1.0.0-py39hecd8cb5_1.conda#c5507133514846cc5f54dc4de9ba1563
+https://repo.anaconda.com/pkgs/main/noarch/py-1.11.0-pyhd3eb1b0_0.conda#7205a898ed2abbf6e9b903dff6abe08e
+https://repo.anaconda.com/pkgs/main/noarch/pycparser-2.21-pyhd3eb1b0_0.conda#135a72ff2a31150a3a3ff0b1edd41ca9
+https://repo.anaconda.com/pkgs/main/noarch/pyparsing-3.0.4-pyhd3eb1b0_0.conda#6bca2ae9c9aae9ccdebcb8cf2aa87cb3
+https://repo.anaconda.com/pkgs/main/osx-64/pysocks-1.7.1-py39hecd8cb5_0.conda#4765ca1a39ea5287cbe170734ac83e37
+https://repo.anaconda.com/pkgs/main/osx-64/pytz-2022.1-py39hecd8cb5_0.conda#a4ca27633e16749c7688884f842053c8
+https://repo.anaconda.com/pkgs/main/noarch/six-1.16.0-pyhd3eb1b0_1.conda#34586824d411d36af2fa40e799c172d0
+https://repo.anaconda.com/pkgs/main/noarch/threadpoolctl-2.2.0-pyh0d69192_0.conda#bbfdbae4934150b902f97daaf287efe2
+https://repo.anaconda.com/pkgs/main/noarch/toml-0.10.2-pyhd3eb1b0_0.conda#cda05f5f6d8509529d1a2743288d197a
+https://repo.anaconda.com/pkgs/main/osx-64/tomli-2.0.1-py39hecd8cb5_0.conda#49318006e63c8628ce0a1e2e1433d30d
+https://repo.anaconda.com/pkgs/main/osx-64/tornado-6.1-py39h9ed2024_0.conda#3d060362ebceec33851e3b9369d5d502
+https://repo.anaconda.com/pkgs/main/osx-64/cffi-1.15.0-py39hc55c11b_1.conda#eecfc04a444eaefdf128199499b2c2e0
+https://repo.anaconda.com/pkgs/main/noarch/fonttools-4.25.0-pyhd3eb1b0_0.conda#bb9c5b5a6d892fca5efe4bf0203b6a48
+https://repo.anaconda.com/pkgs/main/osx-64/mkl-service-2.4.0-py39h9ed2024_0.conda#68ed4da109042256b78f9c46537bd2a3
+https://repo.anaconda.com/pkgs/main/noarch/packaging-21.3-pyhd3eb1b0_0.conda#07bbfbb961db7fa329cc42716943ea62
+https://repo.anaconda.com/pkgs/main/osx-64/pillow-9.2.0-py39hde71d04_1.conda#ecd1fdbc77659c3bf4c056e0f8e703c7
+https://repo.anaconda.com/pkgs/main/noarch/python-dateutil-2.8.2-pyhd3eb1b0_0.conda#211ee00320b08a1ac9fea6677649f6c9
+https://repo.anaconda.com/pkgs/main/osx-64/setuptools-61.2.0-py39hecd8cb5_0.conda#e262d518e990f236ada779f23d58ed18
+https://repo.anaconda.com/pkgs/main/osx-64/brotlipy-0.7.0-py39h9ed2024_1003.conda#a08f6f5f899aff4a07351217b36fae41
+https://repo.anaconda.com/pkgs/main/osx-64/cryptography-37.0.1-py39hf6deb26_0.conda#5f4c90fdfd8a45bc7060dbc3b65f025a
+https://repo.anaconda.com/pkgs/main/osx-64/numpy-base-1.22.3-py39h3b1a694_0.conda#f68019d1d839b40739b64b6feae2b436
+https://repo.anaconda.com/pkgs/main/osx-64/pytest-7.1.2-py39hecd8cb5_0.conda#8239bdb679b675ab8aac1bdc0756d383
+https://repo.anaconda.com/pkgs/main/noarch/pyopenssl-22.0.0-pyhd3eb1b0_0.conda#1dbbf9422269cd62c7094960d9b43f36
+https://repo.anaconda.com/pkgs/main/noarch/pytest-cov-3.0.0-pyhd3eb1b0_0.conda#bbdaac2947f507399816d509107945c2
+https://repo.anaconda.com/pkgs/main/noarch/pytest-forked-1.3.0-pyhd3eb1b0_0.tar.bz2#07970bffdc78f417d7f8f1c7e620f5c4
+https://repo.anaconda.com/pkgs/main/noarch/pytest-xdist-2.5.0-pyhd3eb1b0_0.conda#d15cdc4207bcf8ca920822597f1d138d
+https://repo.anaconda.com/pkgs/main/osx-64/urllib3-1.26.9-py39hecd8cb5_0.conda#7dab8b6edc90f7dc6e83e0c3d9c69432
+https://repo.anaconda.com/pkgs/main/osx-64/requests-2.28.1-py39hecd8cb5_0.conda#c2a59bb72db0abd039ce447be18c139d
+https://repo.anaconda.com/pkgs/main/noarch/codecov-2.1.11-pyhd3eb1b0_0.conda#83a743cc928162d53d4066c43468b2c7
+https://repo.anaconda.com/pkgs/main/osx-64/bottleneck-1.3.5-py39h67323c0_0.conda#312133560b81ec1a2aaf95835e90b5e9
+https://repo.anaconda.com/pkgs/main/osx-64/matplotlib-3.5.1-py39hecd8cb5_1.conda#7a58b76a491c78d0be87c83f63a36d02
+https://repo.anaconda.com/pkgs/main/osx-64/matplotlib-base-3.5.1-py39hfb0c5b7_1.conda#999c6f2f8542a0dd322f97c94de45a63
+https://repo.anaconda.com/pkgs/main/osx-64/mkl_fft-1.3.1-py39h4ab4a9b_0.conda#f947c9a1c65da729963b3035c219ba10
+https://repo.anaconda.com/pkgs/main/osx-64/mkl_random-1.2.2-py39hb2f4e1b_0.conda#1bc33de45069ad534182ca92e616ec7e
+https://repo.anaconda.com/pkgs/main/osx-64/numpy-1.22.3-py39h2e5f0a9_0.conda#16892a18dae1fb1522845e4b6005b436
+https://repo.anaconda.com/pkgs/main/osx-64/numexpr-2.8.3-py39h2e5f0a9_0.conda#db012a622b75c38fe4c5c5bd54cc7f40
+https://repo.anaconda.com/pkgs/main/osx-64/scipy-1.7.3-py39h8c7af03_0.conda#de2900b6122e1417d2f79f0266f700e9
+https://repo.anaconda.com/pkgs/main/osx-64/pandas-1.4.3-py39he9d5cce_0.conda#ac483cb22652d8a5f49b6c071fdc1105
+https://repo.anaconda.com/pkgs/main/osx-64/pyamg-4.1.0-py39h1341a74_0.conda#9c560e676ee6f9f26b05f94ffda599d8
diff --git a/build_tools/azure/pylatest_pip_openblas_pandas_environment.yml b/build_tools/azure/pylatest_pip_openblas_pandas_environment.yml
new file mode 100644
index 0000000000000..6f60df122f055
--- /dev/null
+++ b/build_tools/azure/pylatest_pip_openblas_pandas_environment.yml
@@ -0,0 +1,28 @@
+# DO NOT EDIT: this file is generated from the specification found in the
+# following script to centralize the configuration for CI builds:
+# build_tools/update_environments_and_lock_files.py
+channels:
+ - defaults
+dependencies:
+ - python=3.9
+ - ccache
+ - pip
+ - pip:
+ - numpy
+ - scipy
+ - cython
+ - joblib
+ - threadpoolctl
+ - matplotlib
+ - pandas
+ - pyamg
+ - pytest
+ - pytest-xdist
+ - pillow
+ - codecov
+ - pytest-cov
+ - coverage
+ - sphinx
+ - numpydoc
+ - lightgbm
+ - scikit-image
diff --git a/build_tools/azure/pylatest_pip_openblas_pandas_linux-64_conda.lock b/build_tools/azure/pylatest_pip_openblas_pandas_linux-64_conda.lock
new file mode 100644
index 0000000000000..71faa90614e0b
--- /dev/null
+++ b/build_tools/azure/pylatest_pip_openblas_pandas_linux-64_conda.lock
@@ -0,0 +1,86 @@
+# Generated by conda-lock.
+# platform: linux-64
+# input_hash: 80859cd05957ccd2ad01ce780429934030ee920cb6fbea7c58d2aecd044682c7
+@EXPLICIT
+https://repo.anaconda.com/pkgs/main/linux-64/_libgcc_mutex-0.1-main.conda#c3473ff8bdb3d124ed5ff11ec380d6f9
+https://repo.anaconda.com/pkgs/main/linux-64/ca-certificates-2022.4.26-h06a4308_0.conda#fc9c0bf2e7893f5407ff74289dbcf295
+https://repo.anaconda.com/pkgs/main/linux-64/ld_impl_linux-64-2.38-h1181459_1.conda#68eedfd9c06f2b0e6888d8db345b7f5b
+https://repo.anaconda.com/pkgs/main/linux-64/libstdcxx-ng-11.2.0-h1234567_1.conda#57623d10a70e09e1d048c2b2b6f4e2dd
+https://repo.anaconda.com/pkgs/main/noarch/tzdata-2022a-hda174b7_0.conda#e8fd073330b1083fcd3bc2634722f1a6
+https://repo.anaconda.com/pkgs/main/linux-64/libgomp-11.2.0-h1234567_1.conda#b372c0eea9b60732fdae4b817a63c8cd
+https://repo.anaconda.com/pkgs/main/linux-64/_openmp_mutex-5.1-1_gnu.conda#71d281e9c2192cb3fa425655a8defb85
+https://repo.anaconda.com/pkgs/main/linux-64/libgcc-ng-11.2.0-h1234567_1.conda#a87728dabf3151fb9cfa990bd2eb0464
+https://repo.anaconda.com/pkgs/main/linux-64/libffi-3.3-he6710b0_2.conda#88a54b8f50e351c650e16f4ee781440c
+https://repo.anaconda.com/pkgs/main/linux-64/ncurses-6.3-h5eee18b_3.conda#0c616f387885c1bbb57ec0bd1e779ced
+https://repo.anaconda.com/pkgs/main/linux-64/openssl-1.1.1q-h7f8727e_0.conda#2ac47797afee2ece8d339c18b095b8d8
+https://repo.anaconda.com/pkgs/main/linux-64/xz-5.2.5-h7f8727e_1.conda#5d01fcf310bf465237f6b95a019d73bc
+https://repo.anaconda.com/pkgs/main/linux-64/zlib-1.2.12-h7f8727e_2.conda#4f4080e9939f082332cd8be7fedad087
+https://repo.anaconda.com/pkgs/main/linux-64/ccache-3.7.9-hfe4627d_0.conda#bef6fc681c273bb7bd0c67d1a591365e
+https://repo.anaconda.com/pkgs/main/linux-64/readline-8.1.2-h7f8727e_1.conda#ea33f478fea12406f394944e7e4f3d20
+https://repo.anaconda.com/pkgs/main/linux-64/tk-8.6.12-h1ccaba5_0.conda#fa10ff4aa631fa4aa090a6234d7770b9
+https://repo.anaconda.com/pkgs/main/linux-64/sqlite-3.38.5-hc218d9a_0.conda#ed2668e84d5e2730827ad737bc5231a3
+https://repo.anaconda.com/pkgs/main/linux-64/python-3.9.12-h12debd9_1.conda#8fdbad0e044d2b7057698d7f14643e21
+https://repo.anaconda.com/pkgs/main/linux-64/certifi-2022.6.15-py39h06a4308_0.conda#2f715a68f1be9125f5c8f0425ea6eb30
+https://repo.anaconda.com/pkgs/main/noarch/wheel-0.37.1-pyhd3eb1b0_0.conda#ab85e96e26da8d5797c2458232338b86
+https://repo.anaconda.com/pkgs/main/linux-64/setuptools-61.2.0-py39h06a4308_0.conda#720869dc83cf20f2167fb12e7a54ebaa
+https://repo.anaconda.com/pkgs/main/linux-64/pip-22.1.2-py39h06a4308_0.conda#4485e29fb8b9be5ca1a7690c1dcec9e3
+# pip alabaster @ https://files.pythonhosted.org/packages/10/ad/00b090d23a222943eb0eda509720a404f531a439e803f6538f35136cae9e/alabaster-0.7.12-py2.py3-none-any.whl#md5=None
+# pip attrs @ https://files.pythonhosted.org/packages/be/be/7abce643bfdf8ca01c48afa2ddf8308c2308b0c3b239a44e57d020afa0ef/attrs-21.4.0-py2.py3-none-any.whl#md5=None
+# pip charset-normalizer @ https://files.pythonhosted.org/packages/94/69/64b11e8c2fb21f08634468caef885112e682b0ebe2908e74d3616eb1c113/charset_normalizer-2.1.0-py3-none-any.whl#md5=None
+# pip cycler @ https://files.pythonhosted.org/packages/5c/f9/695d6bedebd747e5eb0fe8fad57b72fdf25411273a39791cde838d5a8f51/cycler-0.11.0-py3-none-any.whl#md5=None
+# pip cython @ https://files.pythonhosted.org/packages/a7/c6/3af0df983ba8500831fdae19a515be6e532da7683ab98e031d803e6a8d03/Cython-0.29.30-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl#md5=None
+# pip docutils @ https://files.pythonhosted.org/packages/8d/14/69b4bad34e3f250afe29a854da03acb6747711f3df06c359fa053fae4e76/docutils-0.18.1-py2.py3-none-any.whl#md5=None
+# pip execnet @ https://files.pythonhosted.org/packages/81/c0/3072ecc23f4c5e0a1af35e3a222855cfd9c80a1a105ca67be3b6172637dd/execnet-1.9.0-py2.py3-none-any.whl#md5=None
+# pip fonttools @ https://files.pythonhosted.org/packages/ad/27/094dd5d09d3a57f7a5f27414ae5c1405bae1164922f1bb61fd8a748e8f65/fonttools-4.34.4-py3-none-any.whl#md5=None
+# pip idna @ https://files.pythonhosted.org/packages/04/a2/d918dcd22354d8958fe113e1a3630137e0fc8b44859ade3063982eacd2a4/idna-3.3-py3-none-any.whl#md5=None
+# pip imagesize @ https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl#md5=None
+# pip iniconfig @ https://files.pythonhosted.org/packages/9b/dd/b3c12c6d707058fa947864b67f0c4e0c39ef8610988d7baea9578f3c48f3/iniconfig-1.1.1-py2.py3-none-any.whl#md5=None
+# pip joblib @ https://files.pythonhosted.org/packages/3e/d5/0163eb0cfa0b673aa4fe1cd3ea9d8a81ea0f32e50807b0c295871e4aab2e/joblib-1.1.0-py2.py3-none-any.whl#md5=None
+# pip kiwisolver @ https://files.pythonhosted.org/packages/a4/36/c414d75be311ce97ef7248edcc4fc05afae2998641bf6b592d43a9dee581/kiwisolver-1.4.4-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl#md5=None
+# pip markupsafe @ https://files.pythonhosted.org/packages/df/06/c515c5bc43b90462e753bc768e6798193c6520c9c7eb2054c7466779a9db/MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#md5=None
+# pip networkx @ https://files.pythonhosted.org/packages/95/1b/14b5b17c52f7329b875e4ad2dcad23c808778b42ef6d250a7223d4dc378a/networkx-2.8.5-py3-none-any.whl#md5=None
+# pip numpy @ https://files.pythonhosted.org/packages/8d/d6/cc2330e512936a904a4db1629b71d697fb309115f6d2ede94d183cdfe185/numpy-1.23.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#md5=None
+# pip pillow @ https://files.pythonhosted.org/packages/c1/d2/169e77ffa99a04f6837ff860b022fa1ea925e698e1c544c58268c8fd2afe/Pillow-9.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#md5=None
+# pip pluggy @ https://files.pythonhosted.org/packages/9e/01/f38e2ff29715251cf25532b9082a1589ab7e4f571ced434f98d0139336dc/pluggy-1.0.0-py2.py3-none-any.whl#md5=None
+# pip py @ https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl#md5=None
+# pip pygments @ https://files.pythonhosted.org/packages/5c/8e/1d9017950034297fffa336c72e693a5b51bbf85141b24a763882cf1977b5/Pygments-2.12.0-py3-none-any.whl#md5=None
+# pip pyparsing @ https://files.pythonhosted.org/packages/6c/10/a7d0fa5baea8fe7b50f448ab742f26f52b80bfca85ac2be9d35cdd9a3246/pyparsing-3.0.9-py3-none-any.whl#md5=None
+# pip pytz @ https://files.pythonhosted.org/packages/60/2e/dec1cc18c51b8df33c7c4d0a321b084cf38e1733b98f9d15018880fb4970/pytz-2022.1-py2.py3-none-any.whl#md5=None
+# pip six @ https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl#md5=None
+# pip snowballstemmer @ https://files.pythonhosted.org/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl#md5=None
+# pip sphinxcontrib-applehelp @ https://files.pythonhosted.org/packages/dc/47/86022665a9433d89a66f5911b558ddff69861766807ba685de2e324bd6ed/sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl#md5=None
+# pip sphinxcontrib-devhelp @ https://files.pythonhosted.org/packages/c5/09/5de5ed43a521387f18bdf5f5af31d099605c992fd25372b2b9b825ce48ee/sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl#md5=None
+# pip sphinxcontrib-htmlhelp @ https://files.pythonhosted.org/packages/63/40/c854ef09500e25f6432dcbad0f37df87fd7046d376272292d8654cc71c95/sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl#md5=None
+# pip sphinxcontrib-jsmath @ https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl#md5=None
+# pip sphinxcontrib-qthelp @ https://files.pythonhosted.org/packages/2b/14/05f9206cf4e9cfca1afb5fd224c7cd434dcc3a433d6d9e4e0264d29c6cdb/sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl#md5=None
+# pip sphinxcontrib-serializinghtml @ https://files.pythonhosted.org/packages/c6/77/5464ec50dd0f1c1037e3c93249b040c8fc8078fdda97530eeb02424b6eea/sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl#md5=None
+# pip threadpoolctl @ https://files.pythonhosted.org/packages/61/cf/6e354304bcb9c6413c4e02a747b600061c21d38ba51e7e544ac7bc66aecc/threadpoolctl-3.1.0-py3-none-any.whl#md5=None
+# pip tomli @ https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl#md5=None
+# pip typing-extensions @ https://files.pythonhosted.org/packages/ed/d6/2afc375a8d55b8be879d6b4986d4f69f01115e795e36827fd3a40166028b/typing_extensions-4.3.0-py3-none-any.whl#md5=None
+# pip urllib3 @ https://files.pythonhosted.org/packages/68/47/93d3d28e97c7577f563903907912f4b3804054e4877a5ba6651f7182c53b/urllib3-1.26.10-py2.py3-none-any.whl#md5=None
+# pip zipp @ https://files.pythonhosted.org/packages/f0/36/639d6742bcc3ffdce8b85c31d79fcfae7bb04b95f0e5c4c6f8b206a038cc/zipp-3.8.1-py3-none-any.whl#md5=None
+# pip babel @ https://files.pythonhosted.org/packages/2e/57/a4177e24f8ed700c037e1eca7620097fdfbb1c9b358601e40169adf6d364/Babel-2.10.3-py3-none-any.whl#md5=None
+# pip coverage @ https://files.pythonhosted.org/packages/f6/73/cb9a3c2d8de315bb9f5fbbcaecd1cea2cacaf530885159159ec2d9c7757e/coverage-6.4.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl#md5=None
+# pip imageio @ https://files.pythonhosted.org/packages/9a/65/e566d02fffd9dec9a111cc0fb5c74e9a29bc50029db57b88b82fbd900d3a/imageio-2.19.5-py3-none-any.whl#md5=None
+# pip importlib-metadata @ https://files.pythonhosted.org/packages/d2/a2/8c239dc898138f208dd14b441b196e7b3032b94d3137d9d8453e186967fc/importlib_metadata-4.12.0-py3-none-any.whl#md5=None
+# pip jinja2 @ https://files.pythonhosted.org/packages/bc/c3/f068337a370801f372f2f8f6bad74a5c140f6fda3d9de154052708dd3c65/Jinja2-3.1.2-py3-none-any.whl#md5=None
+# pip packaging @ https://files.pythonhosted.org/packages/05/8e/8de486cbd03baba4deef4142bd643a3e7bbe954a784dc1bb17142572d127/packaging-21.3-py3-none-any.whl#md5=None
+# pip python-dateutil @ https://files.pythonhosted.org/packages/36/7a/87837f39d0296e723bb9b62bbb257d0355c7f6128853c78955f57342a56d/python_dateutil-2.8.2-py2.py3-none-any.whl#md5=None
+# pip pywavelets @ https://files.pythonhosted.org/packages/45/fd/1ad6a2c2b9f16d684c8a21e92455885891b38c703b39f13916971e9ee8ff/PyWavelets-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#md5=None
+# pip requests @ https://files.pythonhosted.org/packages/ca/91/6d9b8ccacd0412c08820f72cebaa4f0c0441b5cda699c90f618b6f8a1b42/requests-2.28.1-py3-none-any.whl#md5=None
+# pip scipy @ https://files.pythonhosted.org/packages/25/82/da07cc3bb40554f1f82d7e24bfa7ffbfb05b50c16eb8d738ebb74b68af8f/scipy-1.8.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#md5=None
+# pip tifffile @ https://files.pythonhosted.org/packages/19/b7/30d7af4c25985be3852dccd99f15a2003a81bc8f287d57704619fed006ec/tifffile-2022.5.4-py3-none-any.whl#md5=None
+# pip codecov @ https://files.pythonhosted.org/packages/dc/e2/964d0881eff5a67bf5ddaea79a13c7b34a74bc4efe917b368830b475a0b9/codecov-2.1.12-py2.py3-none-any.whl#md5=None
+# pip pandas @ https://files.pythonhosted.org/packages/a5/ac/6c04be2c26e8c839659b7bcdc7a4bcd4e5366867bff8027ffd1cae58b806/pandas-1.4.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#md5=None
+# pip pyamg @ https://files.pythonhosted.org/packages/8e/08/d512b6e34d502152723b5a4ad9d962a6141dfe83cd8bcd01af4cb6e84f28/pyamg-4.2.3-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl#md5=None
+# pip pytest @ https://files.pythonhosted.org/packages/fb/d0/bae533985f2338c5d02184b4a7083b819f6b3fc101da792e0d96e6e5299d/pytest-7.1.2-py3-none-any.whl#md5=None
+# pip scikit-image @ https://files.pythonhosted.org/packages/0f/29/d157cd648b87212e498189c183a32f0f48e24fe22e9673dacd97594f39fa/scikit_image-0.19.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#md5=None
+# pip scikit-learn @ https://files.pythonhosted.org/packages/62/cb/49d4c9d3505b0dd062f49c4f573995977876cc556c658caffcfcd9043ea8/scikit_learn-1.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#md5=None
+# pip setuptools-scm @ https://files.pythonhosted.org/packages/01/ed/75a20e7b075e8ecb1f84e8debf833917905d8790b78008915bd68dddd5c4/setuptools_scm-7.0.5-py3-none-any.whl#md5=None
+# pip sphinx @ https://files.pythonhosted.org/packages/fd/a2/3139e82a7caa2fb6954d0e63db206cc60e0ad6c67ae61ef9cf87dc70ade1/Sphinx-5.0.2-py3-none-any.whl#md5=None
+# pip lightgbm @ https://files.pythonhosted.org/packages/a1/00/84c572ff02b27dd828d6095158f4bda576c124c4c863be7bf14f58101e53/lightgbm-3.3.2-py3-none-manylinux1_x86_64.whl#md5=None
+# pip matplotlib @ https://files.pythonhosted.org/packages/e1/81/0a73fe71098683a1f73243f18f419464ec109acae16811bf29c5d0dc173e/matplotlib-3.5.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl#md5=None
+# pip numpydoc @ https://files.pythonhosted.org/packages/e7/1a/9e3c2a34aae5bd1ab8988b238aafeb4c8d3ab312b8aa5a8c37be6c6d869d/numpydoc-1.4.0-py3-none-any.whl#md5=None
+# pip pytest-cov @ https://files.pythonhosted.org/packages/20/49/b3e0edec68d81846f519c602ac38af9db86e1e71275528b3e814ae236063/pytest_cov-3.0.0-py3-none-any.whl#md5=None
+# pip pytest-forked @ https://files.pythonhosted.org/packages/0c/36/c56ef2aea73912190cdbcc39aaa860db8c07c1a5ce8566994ec9425453db/pytest_forked-1.4.0-py3-none-any.whl#md5=None
+# pip pytest-xdist @ https://files.pythonhosted.org/packages/21/08/b1945d4b4986eb1aa10cf84efc5293bba39da80a2f95db3573dd90678408/pytest_xdist-2.5.0-py3-none-any.whl#md5=None
diff --git a/build_tools/azure/pylatest_pip_scipy_dev_environment.yml b/build_tools/azure/pylatest_pip_scipy_dev_environment.yml
new file mode 100644
index 0000000000000..ea0f11980471b
--- /dev/null
+++ b/build_tools/azure/pylatest_pip_scipy_dev_environment.yml
@@ -0,0 +1,19 @@
+# DO NOT EDIT: this file is generated from the specification found in the
+# following script to centralize the configuration for CI builds:
+# build_tools/update_environments_and_lock_files.py
+channels:
+ - defaults
+dependencies:
+ - python
+ - ccache
+ - pip
+ - pip:
+ - threadpoolctl
+ - pytest
+ - pytest-xdist
+ - codecov
+ - pytest-cov
+ - coverage
+ - sphinx
+ - numpydoc
+ - python-dateutil
diff --git a/build_tools/azure/pylatest_pip_scipy_dev_linux-64_conda.lock b/build_tools/azure/pylatest_pip_scipy_dev_linux-64_conda.lock
new file mode 100644
index 0000000000000..b3186df55b542
--- /dev/null
+++ b/build_tools/azure/pylatest_pip_scipy_dev_linux-64_conda.lock
@@ -0,0 +1,66 @@
+# Generated by conda-lock.
+# platform: linux-64
+# input_hash: db3913f3589e63f4d927051206e496fa0d3ee21746928b690876fb60eeef8b64
+@EXPLICIT
+https://repo.anaconda.com/pkgs/main/linux-64/_libgcc_mutex-0.1-main.conda#c3473ff8bdb3d124ed5ff11ec380d6f9
+https://repo.anaconda.com/pkgs/main/linux-64/ca-certificates-2022.4.26-h06a4308_0.conda#fc9c0bf2e7893f5407ff74289dbcf295
+https://repo.anaconda.com/pkgs/main/linux-64/ld_impl_linux-64-2.38-h1181459_1.conda#68eedfd9c06f2b0e6888d8db345b7f5b
+https://repo.anaconda.com/pkgs/main/linux-64/libstdcxx-ng-11.2.0-h1234567_1.conda#57623d10a70e09e1d048c2b2b6f4e2dd
+https://repo.anaconda.com/pkgs/main/noarch/tzdata-2022a-hda174b7_0.conda#e8fd073330b1083fcd3bc2634722f1a6
+https://repo.anaconda.com/pkgs/main/linux-64/libgomp-11.2.0-h1234567_1.conda#b372c0eea9b60732fdae4b817a63c8cd
+https://repo.anaconda.com/pkgs/main/linux-64/_openmp_mutex-5.1-1_gnu.conda#71d281e9c2192cb3fa425655a8defb85
+https://repo.anaconda.com/pkgs/main/linux-64/libgcc-ng-11.2.0-h1234567_1.conda#a87728dabf3151fb9cfa990bd2eb0464
+https://repo.anaconda.com/pkgs/main/linux-64/bzip2-1.0.8-h7b6447c_0.conda#9303f4af7c004e069bae22bde8d800ee
+https://repo.anaconda.com/pkgs/main/linux-64/libffi-3.3-he6710b0_2.conda#88a54b8f50e351c650e16f4ee781440c
+https://repo.anaconda.com/pkgs/main/linux-64/libuuid-1.0.3-h7f8727e_2.conda#6c4c9e96bfa4744d4839b9ed128e1114
+https://repo.anaconda.com/pkgs/main/linux-64/ncurses-6.3-h5eee18b_3.conda#0c616f387885c1bbb57ec0bd1e779ced
+https://repo.anaconda.com/pkgs/main/linux-64/openssl-1.1.1q-h7f8727e_0.conda#2ac47797afee2ece8d339c18b095b8d8
+https://repo.anaconda.com/pkgs/main/linux-64/xz-5.2.5-h7f8727e_1.conda#5d01fcf310bf465237f6b95a019d73bc
+https://repo.anaconda.com/pkgs/main/linux-64/zlib-1.2.12-h7f8727e_2.conda#4f4080e9939f082332cd8be7fedad087
+https://repo.anaconda.com/pkgs/main/linux-64/ccache-3.7.9-hfe4627d_0.conda#bef6fc681c273bb7bd0c67d1a591365e
+https://repo.anaconda.com/pkgs/main/linux-64/readline-8.1.2-h7f8727e_1.conda#ea33f478fea12406f394944e7e4f3d20
+https://repo.anaconda.com/pkgs/main/linux-64/tk-8.6.12-h1ccaba5_0.conda#fa10ff4aa631fa4aa090a6234d7770b9
+https://repo.anaconda.com/pkgs/main/linux-64/sqlite-3.38.5-hc218d9a_0.conda#ed2668e84d5e2730827ad737bc5231a3
+https://repo.anaconda.com/pkgs/main/linux-64/python-3.10.4-h12debd9_0.tar.bz2#f931504bb2eeaf18f20388fd0ad44be4
+https://repo.anaconda.com/pkgs/main/linux-64/certifi-2022.6.15-py310h06a4308_0.conda#36486307238f598fbbbd575aeb741752
+https://repo.anaconda.com/pkgs/main/noarch/wheel-0.37.1-pyhd3eb1b0_0.conda#ab85e96e26da8d5797c2458232338b86
+https://repo.anaconda.com/pkgs/main/linux-64/setuptools-61.2.0-py310h06a4308_0.conda#1f43427d7c045e63786e0bb79084cf70
+https://repo.anaconda.com/pkgs/main/linux-64/pip-22.1.2-py310h06a4308_0.conda#44c964a18eaff5bd61d2ddd0a7a89a80
+# pip alabaster @ https://files.pythonhosted.org/packages/10/ad/00b090d23a222943eb0eda509720a404f531a439e803f6538f35136cae9e/alabaster-0.7.12-py2.py3-none-any.whl#md5=None
+# pip attrs @ https://files.pythonhosted.org/packages/be/be/7abce643bfdf8ca01c48afa2ddf8308c2308b0c3b239a44e57d020afa0ef/attrs-21.4.0-py2.py3-none-any.whl#md5=None
+# pip charset-normalizer @ https://files.pythonhosted.org/packages/94/69/64b11e8c2fb21f08634468caef885112e682b0ebe2908e74d3616eb1c113/charset_normalizer-2.1.0-py3-none-any.whl#md5=None
+# pip docutils @ https://files.pythonhosted.org/packages/8d/14/69b4bad34e3f250afe29a854da03acb6747711f3df06c359fa053fae4e76/docutils-0.18.1-py2.py3-none-any.whl#md5=None
+# pip execnet @ https://files.pythonhosted.org/packages/81/c0/3072ecc23f4c5e0a1af35e3a222855cfd9c80a1a105ca67be3b6172637dd/execnet-1.9.0-py2.py3-none-any.whl#md5=None
+# pip idna @ https://files.pythonhosted.org/packages/04/a2/d918dcd22354d8958fe113e1a3630137e0fc8b44859ade3063982eacd2a4/idna-3.3-py3-none-any.whl#md5=None
+# pip imagesize @ https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl#md5=None
+# pip iniconfig @ https://files.pythonhosted.org/packages/9b/dd/b3c12c6d707058fa947864b67f0c4e0c39ef8610988d7baea9578f3c48f3/iniconfig-1.1.1-py2.py3-none-any.whl#md5=None
+# pip markupsafe @ https://files.pythonhosted.org/packages/9e/82/2e089c6f34e77c073aa5a67040d368aac0dfb9b8ccbb46d381452c26fc33/MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#md5=None
+# pip pluggy @ https://files.pythonhosted.org/packages/9e/01/f38e2ff29715251cf25532b9082a1589ab7e4f571ced434f98d0139336dc/pluggy-1.0.0-py2.py3-none-any.whl#md5=None
+# pip py @ https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl#md5=None
+# pip pygments @ https://files.pythonhosted.org/packages/5c/8e/1d9017950034297fffa336c72e693a5b51bbf85141b24a763882cf1977b5/Pygments-2.12.0-py3-none-any.whl#md5=None
+# pip pyparsing @ https://files.pythonhosted.org/packages/6c/10/a7d0fa5baea8fe7b50f448ab742f26f52b80bfca85ac2be9d35cdd9a3246/pyparsing-3.0.9-py3-none-any.whl#md5=None
+# pip pytz @ https://files.pythonhosted.org/packages/60/2e/dec1cc18c51b8df33c7c4d0a321b084cf38e1733b98f9d15018880fb4970/pytz-2022.1-py2.py3-none-any.whl#md5=None
+# pip six @ https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl#md5=None
+# pip snowballstemmer @ https://files.pythonhosted.org/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl#md5=None
+# pip sphinxcontrib-applehelp @ https://files.pythonhosted.org/packages/dc/47/86022665a9433d89a66f5911b558ddff69861766807ba685de2e324bd6ed/sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl#md5=None
+# pip sphinxcontrib-devhelp @ https://files.pythonhosted.org/packages/c5/09/5de5ed43a521387f18bdf5f5af31d099605c992fd25372b2b9b825ce48ee/sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl#md5=None
+# pip sphinxcontrib-htmlhelp @ https://files.pythonhosted.org/packages/63/40/c854ef09500e25f6432dcbad0f37df87fd7046d376272292d8654cc71c95/sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl#md5=None
+# pip sphinxcontrib-jsmath @ https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl#md5=None
+# pip sphinxcontrib-qthelp @ https://files.pythonhosted.org/packages/2b/14/05f9206cf4e9cfca1afb5fd224c7cd434dcc3a433d6d9e4e0264d29c6cdb/sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl#md5=None
+# pip sphinxcontrib-serializinghtml @ https://files.pythonhosted.org/packages/c6/77/5464ec50dd0f1c1037e3c93249b040c8fc8078fdda97530eeb02424b6eea/sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl#md5=None
+# pip threadpoolctl @ https://files.pythonhosted.org/packages/61/cf/6e354304bcb9c6413c4e02a747b600061c21d38ba51e7e544ac7bc66aecc/threadpoolctl-3.1.0-py3-none-any.whl#md5=None
+# pip tomli @ https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl#md5=None
+# pip urllib3 @ https://files.pythonhosted.org/packages/68/47/93d3d28e97c7577f563903907912f4b3804054e4877a5ba6651f7182c53b/urllib3-1.26.10-py2.py3-none-any.whl#md5=None
+# pip babel @ https://files.pythonhosted.org/packages/2e/57/a4177e24f8ed700c037e1eca7620097fdfbb1c9b358601e40169adf6d364/Babel-2.10.3-py3-none-any.whl#md5=None
+# pip coverage @ https://files.pythonhosted.org/packages/96/1d/0b615e00ab0f78474112b9ef63605d7b0053900746a5c2592f011e850b93/coverage-6.4.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl#md5=None
+# pip jinja2 @ https://files.pythonhosted.org/packages/bc/c3/f068337a370801f372f2f8f6bad74a5c140f6fda3d9de154052708dd3c65/Jinja2-3.1.2-py3-none-any.whl#md5=None
+# pip packaging @ https://files.pythonhosted.org/packages/05/8e/8de486cbd03baba4deef4142bd643a3e7bbe954a784dc1bb17142572d127/packaging-21.3-py3-none-any.whl#md5=None
+# pip python-dateutil @ https://files.pythonhosted.org/packages/36/7a/87837f39d0296e723bb9b62bbb257d0355c7f6128853c78955f57342a56d/python_dateutil-2.8.2-py2.py3-none-any.whl#md5=None
+# pip requests @ https://files.pythonhosted.org/packages/ca/91/6d9b8ccacd0412c08820f72cebaa4f0c0441b5cda699c90f618b6f8a1b42/requests-2.28.1-py3-none-any.whl#md5=None
+# pip codecov @ https://files.pythonhosted.org/packages/dc/e2/964d0881eff5a67bf5ddaea79a13c7b34a74bc4efe917b368830b475a0b9/codecov-2.1.12-py2.py3-none-any.whl#md5=None
+# pip pytest @ https://files.pythonhosted.org/packages/fb/d0/bae533985f2338c5d02184b4a7083b819f6b3fc101da792e0d96e6e5299d/pytest-7.1.2-py3-none-any.whl#md5=None
+# pip sphinx @ https://files.pythonhosted.org/packages/fd/a2/3139e82a7caa2fb6954d0e63db206cc60e0ad6c67ae61ef9cf87dc70ade1/Sphinx-5.0.2-py3-none-any.whl#md5=None
+# pip numpydoc @ https://files.pythonhosted.org/packages/e7/1a/9e3c2a34aae5bd1ab8988b238aafeb4c8d3ab312b8aa5a8c37be6c6d869d/numpydoc-1.4.0-py3-none-any.whl#md5=None
+# pip pytest-cov @ https://files.pythonhosted.org/packages/20/49/b3e0edec68d81846f519c602ac38af9db86e1e71275528b3e814ae236063/pytest_cov-3.0.0-py3-none-any.whl#md5=None
+# pip pytest-forked @ https://files.pythonhosted.org/packages/0c/36/c56ef2aea73912190cdbcc39aaa860db8c07c1a5ce8566994ec9425453db/pytest_forked-1.4.0-py3-none-any.whl#md5=None
+# pip pytest-xdist @ https://files.pythonhosted.org/packages/21/08/b1945d4b4986eb1aa10cf84efc5293bba39da80a2f95db3573dd90678408/pytest_xdist-2.5.0-py3-none-any.whl#md5=None
diff --git a/build_tools/azure/pypy3_environment.yml b/build_tools/azure/pypy3_environment.yml
new file mode 100644
index 0000000000000..d947c30f8d414
--- /dev/null
+++ b/build_tools/azure/pypy3_environment.yml
@@ -0,0 +1,18 @@
+# DO NOT EDIT: this file is generated from the specification found in the
+# following script to centralize the configuration for CI builds:
+# build_tools/update_environments_and_lock_files.py
+channels:
+ - conda-forge
+dependencies:
+ - pypy
+ - numpy
+ - blas[build=openblas]
+ - scipy
+ - cython
+ - joblib
+ - threadpoolctl
+ - matplotlib
+ - pyamg
+ - pytest
+ - pytest-xdist
+ - ccache
diff --git a/build_tools/azure/pypy3_linux-64_conda.lock b/build_tools/azure/pypy3_linux-64_conda.lock
new file mode 100644
index 0000000000000..2d40a0c057536
--- /dev/null
+++ b/build_tools/azure/pypy3_linux-64_conda.lock
@@ -0,0 +1,95 @@
+# Generated by conda-lock.
+# platform: linux-64
+# input_hash: cf874320f6e578af129bfb6d9ba92149c20c91291a86dbc35043439181333dc8
+@EXPLICIT
+https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81
+https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2
+https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.1.0-hdcd56e2_16.tar.bz2#b02605b875559ff99f04351fd5040760
+https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.1.0-ha89aaad_16.tar.bz2#6f5ba041a41eb102a1027d9e68731be7
+https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.1.0-h69a702a_16.tar.bz2#6bf15e29a20f614b18ae89368260d0a2
+https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_kmp_llvm.tar.bz2#562b26ba2e19059551a811e72ab7f793
+https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-12.1.0-h8d9b700_16.tar.bz2#4f05bc9844f7c101e6e147dab3c88d5c
+https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54
+https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.8-h27087fc_0.tar.bz2#e1b07832504eeba765d648389cc387a9
+https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d
+https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_2.tar.bz2#ee8b844357a0946870901c7c6f418268
+https://conda.anaconda.org/conda-forge/linux-64/lerc-3.0-h9c3ff4c_0.tar.bz2#7fcefde484980d23f0ec24c11e314d2e
+https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_7.tar.bz2#f82dc1c78bcf73583f2656433ce2933c
+https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.10-h7f98852_0.tar.bz2#ffa3a757a97e851293909b49f49f28fb
+https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3
+https://conda.anaconda.org/conda-forge/linux-64/libhiredis-1.0.2-h2cc385e_0.tar.bz2#b34907d3a81a3cd8095ee83d174c074a
+https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.20-pthreads_h78a6416_0.tar.bz2#9b6d0781953c9e353faee494336cc229
+https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.2-h7f98852_1.tar.bz2#46cf26ecc8775a0aab300ea1821aaa3c
+https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_2.tar.bz2#8302381297332ea50532cf2c67961080
+https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6
+https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238
+https://conda.anaconda.org/conda-forge/linux-64/openssl-3.0.5-h166bdaf_0.tar.bz2#f158304d1e469c37e9ffdbe796305571
+https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036
+https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.9-h7f98852_0.tar.bz2#bf6f803a544f26ebbdc3bfff272eb179
+https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.3-h7f98852_0.tar.bz2#be93aabceefa2fac576e971aef407908
+https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.5-h516909a_1.tar.bz2#33f601066901f3e1a85af3522a8113f9
+https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-15_linux64_openblas.tar.bz2#04eb983975a1be3e57d6d667414cd774
+https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_7.tar.bz2#37a460703214d0d1b421e2a47eb5e6d0
+https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_7.tar.bz2#785a9296ea478eb78c47593c4da6550f
+https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904
+https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-14.0.4-he0ac6c6_0.tar.bz2#cecc6e3cb66570ffcfb820c637890f54
+https://conda.anaconda.org/conda-forge/linux-64/openblas-0.3.20-pthreads_h320a7e8_0.tar.bz2#4cc467036ee23a4e7dac2d2c53cc7c21
+https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa
+https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168
+https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_2.tar.bz2#4533821485cde83ab12ff3d8bda83768
+https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_2.tar.bz2#78c26dbb6e07d95ccc0eab8d4540aa0c
+https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e
+https://conda.anaconda.org/conda-forge/linux-64/ccache-4.5.1-haef5404_0.tar.bz2#8458e509920a0bb14bb6fedd248bed57
+https://conda.anaconda.org/conda-forge/linux-64/gdbm-1.18-h0a1914f_2.tar.bz2#b77bc399b07a19c00fe12fdc95ee0297
+https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-15_linux64_openblas.tar.bz2#f45968428e445fd0c6472b561145812a
+https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-15_linux64_openblas.tar.bz2#b7078220384b8bf8db1a45e66412ac4f
+https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_3.tar.bz2#3e868978a04de8bf65a97bb86760f47a
+https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.3.0-h0fcbabc_4.tar.bz2#2cdb3944029c2cb99cbe981b182a2e9a
+https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.1-h4ff8645_0.tar.bz2#6acda9d2a3ea84b58637b8f880bbf29b
+https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1
+https://conda.anaconda.org/conda-forge/linux-64/freetype-2.10.4-h0708190_1.tar.bz2#4a06f2ac2e5bfae7b6b245171c3f07aa
+https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f
+https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-15_linux64_openblas.tar.bz2#31f21773a784f59962ea542a3d71ad87
+https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.2-h3452ae3_0.tar.bz2#c363665b4aabe56aae4f8981cff5b153
+https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.4.0-hb52868f_1.tar.bz2#b7ad78ad2e9ee155f59e6428406ee824
+https://conda.anaconda.org/conda-forge/linux-64/pypy3.7-7.3.7-h24b73cb_3.tar.bz2#b48b2037293953ac936b2a7dd61f111a
+https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-15_linux64_openblas.tar.bz2#2b5095be485bdb407ff3134358c3ca9c
+https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.7-2_pypy37_pp73.tar.bz2#768e358541ec4abb2a3f4300e6caf4a9
+https://conda.anaconda.org/conda-forge/linux-64/blas-2.115-openblas.tar.bz2#ca9e177657aa07ab306bd1bbcdf80e69
+https://conda.anaconda.org/conda-forge/linux-64/python-3.7.12-0_73_pypy.tar.bz2#0718ea16c32e9f500f471713a4b3d5a7
+https://conda.anaconda.org/conda-forge/noarch/attrs-21.4.0-pyhd8ed1ab_0.tar.bz2#f70280205d7044c8b8358c8de3190e5d
+https://conda.anaconda.org/conda-forge/linux-64/certifi-2021.10.8-py37h9c2f6ca_1.tar.bz2#c34a43aefed7c093e459890232dbb58b
+https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb
+https://conda.anaconda.org/conda-forge/linux-64/cython-0.29.28-py37h2ca511a_1.tar.bz2#d04698a9e465c7f6d63d3bef13493a85
+https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2
+https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905
+https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19
+https://conda.anaconda.org/conda-forge/linux-64/numpy-1.21.5-py37h18e8e3d_0.tar.bz2#f9c1534d421ac008ccdeec21b3c6f825
+https://conda.anaconda.org/conda-forge/linux-64/pillow-9.1.0-py37h5350b5b_0.tar.bz2#9d0446d35e6668166abdd1123a7c8881
+https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054
+https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc
+https://conda.anaconda.org/conda-forge/noarch/pypy-7.3.7-0_pypy37.tar.bz2#bea1f6f8d946aafd6ed814c84db3ef99
+https://conda.anaconda.org/conda-forge/linux-64/setuptools-61.2.0-py37h9c2f6ca_0.tar.bz2#628b5ca5eb44eda9028ad710bae81e33
+https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2
+https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.1.0-pyh8a188c0_0.tar.bz2#a2995ee828f65687ac5b1e71a2ab1e0c
+https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96
+https://conda.anaconda.org/conda-forge/linux-64/tornado-6.1-py37h6b43d8f_2.tar.bz2#2027d768b9cd13e9738e0856aba431ca
+https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.3.0-pyha770c72_0.tar.bz2#a9d85960bc62d53cc4ea0d1d27f73c98
+https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py37h6b43d8f_0.tar.bz2#0abe97867cd8e0bd2ca5092f5d7ada85
+https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.0-pyhd8ed1ab_0.tar.bz2#050b94cf4a8c760656e51d2d44e4632c
+https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.31.2-py37h0313132_0.tar.bz2#97ce82afb9d0ccfcad3bd775ce9b8eac
+https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.3-py37h9c2f6ca_0.tar.bz2#2ecfd05cf475affe93c3abb820d5a353
+https://conda.anaconda.org/conda-forge/noarch/joblib-1.1.0-pyhd8ed1ab_0.tar.bz2#07d1b5c8cde14d95998fd4767e1e62d2
+https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85
+https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984
+https://conda.anaconda.org/conda-forge/linux-64/scipy-1.7.3-py37ha768fb6_0.tar.bz2#02658efbe18de3c92c6a18e568fae544
+https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6
+https://conda.anaconda.org/conda-forge/noarch/importlib_metadata-4.11.3-hd8ed1ab_1.tar.bz2#bd6b6ae37c03e68061574d5e32fe5bd1
+https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.2-py37h506a98e_0.tar.bz2#6e29b82c7ac67717d9e6c834e0760efe
+https://conda.anaconda.org/conda-forge/linux-64/pyamg-4.2.3-py37h1903001_0.tar.bz2#fa07775fa3ba272a9edaf38bca8581b8
+https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py37he341ac4_0.tar.bz2#1b13b927abe70400d6b9c71ac69fed15
+https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py37h9c2f6ca_2.tar.bz2#01f62860cfd125f2d103146495ae6312
+https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.2-py37h9c2f6ca_0.tar.bz2#a9050f7b5a866766f69fac8d19b39bc3
+https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.1-py37h9c2f6ca_0.tar.bz2#0886b5fe236c5d16a9b669f52923d981
+https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f
+https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e
diff --git a/build_tools/azure/python_nogil_lock.txt b/build_tools/azure/python_nogil_lock.txt
new file mode 100644
index 0000000000000..3be35727f930f
--- /dev/null
+++ b/build_tools/azure/python_nogil_lock.txt
@@ -0,0 +1,62 @@
+# This file is autogenerated by pip-compile with python 3.9
+# To update, run:
+#
+# pip-compile --output-file=/scikit-learn/build_tools/azure/python_nogil_lock.txt /scikit-learn/build_tools/azure/python_nogil_requirements.txt
+#
+--index-url https://d1yxz45j0ypngg.cloudfront.net/
+--extra-index-url https://pypi.org/simple
+
+attrs==21.4.0
+ # via pytest
+cycler==0.11.0
+ # via matplotlib
+cython==0.29.26
+ # via -r /scikit-learn/build_tools/azure/python_nogil_requirements.txt
+execnet==1.9.0
+ # via pytest-xdist
+iniconfig==1.1.1
+ # via pytest
+joblib==1.1.0
+ # via -r /scikit-learn/build_tools/azure/python_nogil_requirements.txt
+kiwisolver==1.3.2
+ # via matplotlib
+matplotlib==3.4.3
+ # via -r /scikit-learn/build_tools/azure/python_nogil_requirements.txt
+numpy==1.22.3
+ # via
+ # -r /scikit-learn/build_tools/azure/python_nogil_requirements.txt
+ # matplotlib
+ # scipy
+packaging==21.3
+ # via pytest
+pillow==8.4.0
+ # via matplotlib
+pluggy==1.0.0
+ # via pytest
+py==1.11.0
+ # via
+ # pytest
+ # pytest-forked
+pyparsing==3.0.8
+ # via
+ # matplotlib
+ # packaging
+pytest==6.2.5
+ # via
+ # -r /scikit-learn/build_tools/azure/python_nogil_requirements.txt
+ # pytest-forked
+ # pytest-xdist
+pytest-forked==1.4.0
+ # via pytest-xdist
+pytest-xdist==2.5.0
+ # via -r /scikit-learn/build_tools/azure/python_nogil_requirements.txt
+python-dateutil==2.8.2
+ # via matplotlib
+scipy==1.8.0
+ # via -r /scikit-learn/build_tools/azure/python_nogil_requirements.txt
+six==1.16.0
+ # via python-dateutil
+threadpoolctl==3.1.0
+ # via -r /scikit-learn/build_tools/azure/python_nogil_requirements.txt
+toml==0.10.2
+ # via pytest
diff --git a/build_tools/azure/python_nogil_requirements.txt b/build_tools/azure/python_nogil_requirements.txt
new file mode 100644
index 0000000000000..466ceb35d382e
--- /dev/null
+++ b/build_tools/azure/python_nogil_requirements.txt
@@ -0,0 +1,15 @@
+# To generate python_nogil_lock.txt, use the following command:
+# docker run -v $PWD:/scikit-learn -it nogil/python bash -c 'pip install pip-tools; pip-compile --upgrade /scikit-learn/build_tools/azure/python_nogil_requirements.txt -o /scikit-learn/build_tools/azure/python_nogil_lock.txt'
+#
+# The reason behind it is that you need python-nogil to generate the pip lock
+# file. Using pip-compile --index and --extra-index will not work, for example
+# the latest cython will be picked up from PyPI, rather than the one from the
+# python-nogil index
+matplotlib
+numpy
+scipy
+cython
+joblib
+threadpoolctl
+pytest==6.2.5
+pytest-xdist
diff --git a/build_tools/azure/test_docs.sh b/build_tools/azure/test_docs.sh
index 18b3ccb148b5e..1d28f64a036cd 100755
--- a/build_tools/azure/test_docs.sh
+++ b/build_tools/azure/test_docs.sh
@@ -4,7 +4,7 @@ set -e
if [[ "$DISTRIB" =~ ^conda.* ]]; then
source activate $VIRTUALENV
-elif [[ "$DISTRIB" == "ubuntu" ]]; then
+elif [[ "$DISTRIB" == "ubuntu" || "$DISTRIB" == "pip-nogil" ]]; then
source $VIRTUALENV/bin/activate
fi
diff --git a/build_tools/azure/test_script.sh b/build_tools/azure/test_script.sh
index c083114df60a4..3749434b1eb4a 100755
--- a/build_tools/azure/test_script.sh
+++ b/build_tools/azure/test_script.sh
@@ -7,8 +7,10 @@ source build_tools/shared.sh
if [[ "$DISTRIB" =~ ^conda.* ]]; then
source activate $VIRTUALENV
-elif [[ "$DISTRIB" == "ubuntu" ]] || [[ "$DISTRIB" == "debian-32" ]]; then
+elif [[ "$DISTRIB" == "ubuntu" || "$DISTRIB" == "debian-32" || "$DISTRIB" == "pip-nogil" ]]; then
source $VIRTUALENV/bin/activate
+elif [[ "$DISTRIB" == "pip-windows" ]]; then
+ source $VIRTUALENV/Scripts/activate
fi
if [[ "$BUILD_WITH_ICC" == "true" ]]; then
@@ -49,7 +51,7 @@ if [[ "$COVERAGE" == "true" ]]; then
fi
if [[ -n "$CHECK_WARNINGS" ]]; then
- TEST_CMD="$TEST_CMD -Werror::DeprecationWarning -Werror::FutureWarning"
+ TEST_CMD="$TEST_CMD -Werror::DeprecationWarning -Werror::FutureWarning -Werror::numpy.VisibleDeprecationWarning"
# numpy's 1.19.0's tostring() deprecation is ignored until scipy and joblib
# removes its usage
diff --git a/build_tools/azure/ubuntu_atlas_lock.txt b/build_tools/azure/ubuntu_atlas_lock.txt
new file mode 100644
index 0000000000000..bedcf365f8eb9
--- /dev/null
+++ b/build_tools/azure/ubuntu_atlas_lock.txt
@@ -0,0 +1,39 @@
+#
+# This file is autogenerated by pip-compile with python 3.8
+# To update, run:
+#
+# pip-compile --output-file=build_tools/azure/ubuntu_atlas_lock.txt build_tools/azure/ubuntu_atlas_requirements.txt
+#
+attrs==21.4.0
+ # via pytest
+cython==0.29.30
+ # via -r build_tools/azure/ubuntu_atlas_requirements.txt
+execnet==1.9.0
+ # via pytest-xdist
+iniconfig==1.1.1
+ # via pytest
+joblib==1.0.0
+ # via -r build_tools/azure/ubuntu_atlas_requirements.txt
+packaging==21.3
+ # via pytest
+pluggy==1.0.0
+ # via pytest
+py==1.11.0
+ # via
+ # pytest
+ # pytest-forked
+pyparsing==3.0.9
+ # via packaging
+pytest==7.1.2
+ # via
+ # -r build_tools/azure/ubuntu_atlas_requirements.txt
+ # pytest-forked
+ # pytest-xdist
+pytest-forked==1.4.0
+ # via pytest-xdist
+pytest-xdist==2.5.0
+ # via -r build_tools/azure/ubuntu_atlas_requirements.txt
+threadpoolctl==2.0.0
+ # via -r build_tools/azure/ubuntu_atlas_requirements.txt
+tomli==2.0.1
+ # via pytest
diff --git a/build_tools/azure/ubuntu_atlas_requirements.txt b/build_tools/azure/ubuntu_atlas_requirements.txt
new file mode 100644
index 0000000000000..b160f16a10ad4
--- /dev/null
+++ b/build_tools/azure/ubuntu_atlas_requirements.txt
@@ -0,0 +1,8 @@
+# DO NOT EDIT: this file is generated from the specification found in the
+# following script to centralize the configuration for CI builds:
+# build_tools/update_environments_and_lock_files.py
+cython
+joblib==1.0.0 # min
+threadpoolctl==2.0.0 # min
+pytest
+pytest-xdist
diff --git a/build_tools/azure/windows.yml b/build_tools/azure/windows.yml
index 3e1d282f3d79a..ea97b7eb5eaf0 100644
--- a/build_tools/azure/windows.yml
+++ b/build_tools/azure/windows.yml
@@ -16,7 +16,6 @@ jobs:
VIRTUALENV: 'testvenv'
JUNITXML: 'test-data.xml'
SKLEARN_SKIP_NETWORK_TESTS: '1'
- PYTEST_VERSION: '5.2.1'
PYTEST_XDIST_VERSION: 'latest'
TEST_DIR: '$(Agent.WorkFolder)/tmp_folder'
SHOW_SHORT_SUMMARY: 'false'
@@ -30,8 +29,8 @@ jobs:
displayName: Check selected tests for all random seeds
condition: eq(variables['Build.Reason'], 'PullRequest')
- bash: echo "##vso[task.prependpath]$CONDA/Scripts"
- displayName: Add conda to PATH for 64 bit Python
- condition: eq(variables['PYTHON_ARCH'], '64')
+ displayName: Add conda to PATH
+ condition: startsWith(variables['DISTRIB'], 'conda')
- task: UsePythonVersion@0
inputs:
versionSpec: '$(PYTHON_VERSION)'
@@ -55,3 +54,27 @@ jobs:
testRunTitle: ${{ format('{0}-$(Agent.JobName)', parameters.name) }}
displayName: 'Publish Test Results'
condition: succeededOrFailed()
+ - bash: |
+ set -ex
+ if [[ $(BOT_GITHUB_TOKEN) == "" ]]; then
+ echo "GitHub Token is not set. Issue tracker will not be updated."
+ exit
+ fi
+
+ LINK_TO_RUN="https://dev.azure.com/$BUILD_REPOSITORY_NAME/_build/results?buildId=$BUILD_BUILDID&view=logs&j=$SYSTEM_JOBID"
+ CI_NAME="$SYSTEM_JOBIDENTIFIER"
+ ISSUE_REPO="$BUILD_REPOSITORY_NAME"
+
+ $(pyTools.pythonLocation)/bin/pip install defusedxml PyGithub
+ $(pyTools.pythonLocation)/bin/python maint_tools/update_tracking_issue.py \
+ $(BOT_GITHUB_TOKEN) \
+ $CI_NAME \
+ $ISSUE_REPO \
+ $LINK_TO_RUN \
+ --junit-file $JUNIT_FILE \
+ --auto-close false
+ displayName: 'Update issue tracker'
+ env:
+ JUNIT_FILE: $(TEST_DIR)/$(JUNITXML)
+ condition: and(succeededOrFailed(), eq(variables['CREATE_ISSUE_ON_TRACKER'], 'true'),
+ eq(variables['Build.Reason'], 'Schedule'))
diff --git a/build_tools/circle/build_test_arm.sh b/build_tools/circle/build_test_arm.sh
index c086f3c7813a4..3b1979793f853 100755
--- a/build_tools/circle/build_test_arm.sh
+++ b/build_tools/circle/build_test_arm.sh
@@ -6,6 +6,8 @@ set -x
UNAMESTR=`uname`
N_CORES=`nproc --all`
+# defines the get_dep and show_installed_libraries functions
+source build_tools/shared.sh
setup_ccache() {
echo "Setting up ccache"
@@ -20,13 +22,6 @@ setup_ccache() {
ccache -M 0
}
-# imports get_dep
-source build_tools/shared.sh
-
-sudo add-apt-repository --remove ppa:ubuntu-toolchain-r/test
-sudo apt-get update
-
-# Setup conda environment
MINICONDA_URL="https://github.com/conda-forge/miniforge/releases/latest/download/Mambaforge-Linux-aarch64.sh"
# Install Mambaforge
@@ -35,41 +30,14 @@ MINICONDA_PATH=$HOME/miniconda
chmod +x mambaforge.sh && ./mambaforge.sh -b -p $MINICONDA_PATH
export PATH=$MINICONDA_PATH/bin:$PATH
mamba init --all --verbose
+mamba update --yes mamba
mamba update --yes conda
+mamba install "$(get_dep conda-lock min)" -y
+conda-lock install --name $CONDA_ENV_NAME $LOCK_FILE
+source activate $CONDA_ENV_NAME
-# Create environment and install dependencies
-mamba create -n testenv --yes $(get_dep python $PYTHON_VERSION)
-source activate testenv
-
-# pin pip to 22.0.4 because pip 22.1 validates build dependencies in
-# pyproject.toml. oldest-supported-numpy is part of the build dependencies in
-# pyproject.toml so using pip 22.1 will cause an error since
-# oldest-supported-numpy is not really meant to be installed in the
-# environment. See https://github.com/scikit-learn/scikit-learn/pull/23336 for
-# more details.
-mamba install --verbose -y ccache \
- pip==22.0.4 \
- $(get_dep numpy $NUMPY_VERSION) \
- $(get_dep scipy $SCIPY_VERSION) \
- $(get_dep cython $CYTHON_VERSION) \
- $(get_dep joblib $JOBLIB_VERSION) \
- $(get_dep threadpoolctl $THREADPOOLCTL_VERSION) \
- $(get_dep pytest $PYTEST_VERSION) \
- $(get_dep pytest-xdist $PYTEST_XDIST_VERSION)
setup_ccache
-if [[ "$COVERAGE" == "true" ]]; then
- # XXX: coverage is temporary pinned to 6.2 because 6.3 is not fork-safe
- # cf. https://github.com/nedbat/coveragepy/issues/1310
- mamba install --verbose -y codecov pytest-cov coverage=6.2
-fi
-
-if [[ "$TEST_DOCSTRINGS" == "true" ]]; then
- # numpydoc requires sphinx
- mamba install --verbose -y sphinx
- mamba install --verbose -y numpydoc
-fi
-
python --version
# Set parallelism to $N_CORES + 1 to overlap IO bound tasks with CPU bound tasks on CI
diff --git a/build_tools/circle/download_documentation.sh b/build_tools/circle/download_documentation.sh
new file mode 100755
index 0000000000000..c2d6d09d0abb9
--- /dev/null
+++ b/build_tools/circle/download_documentation.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+set -e
+set -x
+
+wget $GITHUB_ARTIFACT_URL
+mkdir -p doc/_build/html/stable
+unzip doc*.zip -d doc/_build/html/stable
diff --git a/build_tools/circle/linting.sh b/build_tools/circle/linting.sh
deleted file mode 100755
index aebe42dfecc70..0000000000000
--- a/build_tools/circle/linting.sh
+++ /dev/null
@@ -1,179 +0,0 @@
-#!/bin/bash
-
-# This script is used in CircleCI to check that PRs do not add obvious
-# flake8 violations. It relies on two things:
-# - find common ancestor between branch and
-# scikit-learn/scikit-learn remote
-# - run flake8 --diff on the diff between the branch and the common
-# ancestor
-#
-# Additional features:
-# - the line numbers in Travis match the local branch on the PR
-# author machine.
-# - ./build_tools/circle/flake8_diff.sh can be run locally for quick
-# turn-around
-
-set -e
-# pipefail is necessary to propagate exit codes
-set -o pipefail
-
-PROJECT=scikit-learn/scikit-learn
-PROJECT_URL=https://github.com/$PROJECT.git
-
-# Find the remote with the project name (upstream in most cases)
-REMOTE=$(git remote -v | grep $PROJECT | cut -f1 | head -1 || echo '')
-
-# Add a temporary remote if needed. For example this is necessary when
-# Travis is configured to run in a fork. In this case 'origin' is the
-# fork and not the reference repo we want to diff against.
-if [[ -z "$REMOTE" ]]; then
- TMP_REMOTE=tmp_reference_upstream
- REMOTE=$TMP_REMOTE
- git remote add $REMOTE $PROJECT_URL
-fi
-
-echo "Remotes:"
-echo '--------------------------------------------------------------------------------'
-git remote --verbose
-
-# Travis does the git clone with a limited depth (50 at the time of
-# writing). This may not be enough to find the common ancestor with
-# $REMOTE/main so we unshallow the git checkout
-if [[ -a .git/shallow ]]; then
- echo -e '\nTrying to unshallow the repo:'
- echo '--------------------------------------------------------------------------------'
- git fetch --unshallow
-fi
-
-if [[ "$TRAVIS" == "true" ]]; then
- if [[ "$TRAVIS_PULL_REQUEST" == "false" ]]
- then
- # In main repo, using TRAVIS_COMMIT_RANGE to test the commits
- # that were pushed into a branch
- if [[ "$PROJECT" == "$TRAVIS_REPO_SLUG" ]]; then
- if [[ -z "$TRAVIS_COMMIT_RANGE" ]]; then
- echo "New branch, no commit range from Travis so passing this test by convention"
- exit 0
- fi
- COMMIT_RANGE=$TRAVIS_COMMIT_RANGE
- fi
- else
- # We want to fetch the code as it is in the PR branch and not
- # the result of the merge into main. This way line numbers
- # reported by Travis will match with the local code.
- LOCAL_BRANCH_REF=travis_pr_$TRAVIS_PULL_REQUEST
- # In Travis the PR target is always origin
- git fetch origin pull/$TRAVIS_PULL_REQUEST/head:refs/$LOCAL_BRANCH_REF
- fi
-fi
-
-# If not using the commit range from Travis we need to find the common
-# ancestor between $LOCAL_BRANCH_REF and $REMOTE/main
-if [[ -z "$COMMIT_RANGE" ]]; then
- if [[ -z "$LOCAL_BRANCH_REF" ]]; then
- LOCAL_BRANCH_REF=$(git rev-parse --abbrev-ref HEAD)
- fi
- echo -e "\nLast 2 commits in $LOCAL_BRANCH_REF:"
- echo '--------------------------------------------------------------------------------'
- git --no-pager log -2 $LOCAL_BRANCH_REF
-
- REMOTE_MAIN_REF="$REMOTE/main"
- # Make sure that $REMOTE_MAIN_REF is a valid reference
- echo -e "\nFetching $REMOTE_MAIN_REF"
- echo '--------------------------------------------------------------------------------'
- git fetch $REMOTE main:refs/remotes/$REMOTE_MAIN_REF
- LOCAL_BRANCH_SHORT_HASH=$(git rev-parse --short $LOCAL_BRANCH_REF)
- REMOTE_MAIN_SHORT_HASH=$(git rev-parse --short $REMOTE_MAIN_REF)
-
- COMMIT=$(git merge-base $LOCAL_BRANCH_REF $REMOTE_MAIN_REF) || \
- echo "No common ancestor found for $(git show $LOCAL_BRANCH_REF -q) and $(git show $REMOTE_MAIN_REF -q)"
-
- if [ -z "$COMMIT" ]; then
- exit 1
- fi
-
- COMMIT_SHORT_HASH=$(git rev-parse --short $COMMIT)
-
- echo -e "\nCommon ancestor between $LOCAL_BRANCH_REF ($LOCAL_BRANCH_SHORT_HASH)"\
- "and $REMOTE_MAIN_REF ($REMOTE_MAIN_SHORT_HASH) is $COMMIT_SHORT_HASH:"
- echo '--------------------------------------------------------------------------------'
- git --no-pager show --no-patch $COMMIT_SHORT_HASH
-
- COMMIT_RANGE="$COMMIT_SHORT_HASH..$LOCAL_BRANCH_SHORT_HASH"
-
- if [[ -n "$TMP_REMOTE" ]]; then
- git remote remove $TMP_REMOTE
- fi
-
-else
- echo "Got the commit range from Travis: $COMMIT_RANGE"
-fi
-
-echo -e '\nRunning flake8 on the diff in the range' "$COMMIT_RANGE" \
- "($(git rev-list $COMMIT_RANGE | wc -l) commit(s)):"
-echo '--------------------------------------------------------------------------------'
-
-# We ignore files from sklearn/externals. Unfortunately there is no
-# way to do it with flake8 directly (the --exclude does not seem to
-# work with --diff). We could use the exclude magic in the git pathspec
-# ':!sklearn/externals' but it is only available on git 1.9 and Travis
-# uses git 1.8.
-# We need the following command to exit with 0 hence the echo in case
-# there is no match
-MODIFIED_FILES="$(git diff --name-only $COMMIT_RANGE | grep -v 'sklearn/externals' | \
- grep -v 'doc/sphinxext' || echo "no_match")"
-
-check_files() {
- files="$1"
- shift
- options="$*"
- if [ -n "$files" ]; then
- # Conservative approach: diff without context (--unified=0) so that code
- # that was not changed does not create failures
- git diff --unified=0 $COMMIT_RANGE -- $files | flake8 --diff --show-source $options
- fi
-}
-
-if [[ "$MODIFIED_FILES" == "no_match" ]]; then
- echo "No file outside sklearn/externals and doc/sphinxext has been modified"
-else
- check_files "$MODIFIED_FILES"
- # check code for unused imports
- flake8 --exclude=sklearn/externals/ --select=F401 sklearn/ examples/
-fi
-echo -e "No problem detected by flake8\n"
-
-# For docstrings and warnings of deprecated attributes to be rendered
-# properly, the property decorator must come before the deprecated decorator
-# (else they are treated as functions)
-
-# do not error when grep -B1 "@property" finds nothing
-set +e
-bad_deprecation_property_order=`git grep -A 10 "@property" -- "*.py" | awk '/@property/,/def /' | grep -B1 "@deprecated"`
-
-if [ ! -z "$bad_deprecation_property_order" ]
-then
- echo "property decorator should come before deprecated decorator"
- echo "found the following occurrencies:"
- echo $bad_deprecation_property_order
- exit 1
-fi
-
-# Check for default doctest directives ELLIPSIS and NORMALIZE_WHITESPACE
-
-doctest_directive="$(git grep -nw -E "# doctest\: \+(ELLIPSIS|NORMALIZE_WHITESPACE)")"
-
-if [ ! -z "$doctest_directive" ]
-then
- echo "ELLIPSIS and NORMALIZE_WHITESPACE doctest directives are enabled by default, but were found in:"
- echo "$doctest_directive"
- exit 1
-fi
-
-joblib_import="$(git grep -l -A 10 -E "joblib import.+delayed" -- "*.py" ":!sklearn/utils/_joblib.py" ":!sklearn/utils/fixes.py")"
-
-if [ ! -z "$joblib_import" ]; then
- echo "Use from sklearn.utils.fixes import delayed instead of joblib delayed. The following files contains imports to joblib.delayed:"
- echo "$joblib_import"
- exit 1
-fi
diff --git a/build_tools/circle/py39_conda_forge_environment.yml b/build_tools/circle/py39_conda_forge_environment.yml
new file mode 100644
index 0000000000000..16c5b0a0144ca
--- /dev/null
+++ b/build_tools/circle/py39_conda_forge_environment.yml
@@ -0,0 +1,19 @@
+# DO NOT EDIT: this file is generated from the specification found in the
+# following script to centralize the configuration for CI builds:
+# build_tools/update_environments_and_lock_files.py
+channels:
+ - conda-forge
+dependencies:
+ - python=3.9
+ - numpy
+ - blas
+ - scipy
+ - cython
+ - joblib
+ - threadpoolctl
+ - matplotlib
+ - pytest
+ - pytest-xdist
+ - pillow
+ - pip
+ - ccache
diff --git a/build_tools/circle/py39_conda_forge_linux-aarch64_conda.lock b/build_tools/circle/py39_conda_forge_linux-aarch64_conda.lock
new file mode 100644
index 0000000000000..95171f79f9cad
--- /dev/null
+++ b/build_tools/circle/py39_conda_forge_linux-aarch64_conda.lock
@@ -0,0 +1,90 @@
+# Generated by conda-lock.
+# platform: linux-aarch64
+# input_hash: e57a9b17b5add91c70ac3ba0cae1f9da4dcf5bc2b1bb65dbddac08a13e13945a
+@EXPLICIT
+https://conda.anaconda.org/conda-forge/linux-aarch64/ca-certificates-2022.6.15-h4fd8a4c_0.tar.bz2#49e3859b4b7fe9d28407e0749f5b4cc2
+https://conda.anaconda.org/conda-forge/linux-aarch64/ld_impl_linux-aarch64-2.36.1-h02ad14f_2.tar.bz2#3ca1a8e406eab04ffc3bfa6e8ac0a724
+https://conda.anaconda.org/conda-forge/linux-aarch64/libgfortran5-12.1.0-h41d5c85_16.tar.bz2#f053ad62fdac14fb8e73cfed4e8d2676
+https://conda.anaconda.org/conda-forge/linux-aarch64/libstdcxx-ng-12.1.0-hd01590b_16.tar.bz2#b64391bb81cc2f914d57c0927ec8a26b
+https://conda.anaconda.org/conda-forge/noarch/tzdata-2022a-h191b570_0.tar.bz2#84be5301069417a2221187d2f435e0f7
+https://conda.anaconda.org/conda-forge/linux-aarch64/libgfortran-ng-12.1.0-he9431aa_16.tar.bz2#69e5a58bbd94c934277f715160c1f0b5
+https://conda.anaconda.org/conda-forge/linux-aarch64/_openmp_mutex-4.5-2_kmp_llvm.tar.bz2#98a1185182fec3c434069fa74e6473d6
+https://conda.anaconda.org/conda-forge/linux-aarch64/libgcc-ng-12.1.0-h3242a24_16.tar.bz2#70e9f0947c17f3faf1a1974be0c110bf
+https://conda.anaconda.org/conda-forge/linux-aarch64/bzip2-1.0.8-hf897c2e_4.tar.bz2#2d787570a729e273a4e75775ddf3348a
+https://conda.anaconda.org/conda-forge/linux-aarch64/giflib-5.2.1-hb9de7d4_2.tar.bz2#08575a68724ff44a0cb24c6158511c0a
+https://conda.anaconda.org/conda-forge/linux-aarch64/jpeg-9e-h9cdd2b7_2.tar.bz2#8fd15daa7515a0fea9b3b68495118238
+https://conda.anaconda.org/conda-forge/linux-aarch64/lerc-3.0-h01db608_0.tar.bz2#13505433bc073878ec237f8ff8ba1ea5
+https://conda.anaconda.org/conda-forge/linux-aarch64/libbrotlicommon-1.0.9-h4e544f5_7.tar.bz2#6ee071311281942e2fec227751e7efff
+https://conda.anaconda.org/conda-forge/linux-aarch64/libdeflate-1.12-h4e544f5_0.tar.bz2#8867038c37cac9127964fa455585b67a
+https://conda.anaconda.org/conda-forge/linux-aarch64/libffi-3.4.2-h3557bc0_5.tar.bz2#dddd85f4d52121fab0a8b099c5e06501
+https://conda.anaconda.org/conda-forge/linux-aarch64/libhiredis-1.0.2-h05efe27_0.tar.bz2#a87f068744fd20334cd41489eb163bee
+https://conda.anaconda.org/conda-forge/linux-aarch64/libnsl-2.0.0-hf897c2e_0.tar.bz2#36fdbc05c9d9145ece86f5a63c3f352e
+https://conda.anaconda.org/conda-forge/linux-aarch64/libopenblas-0.3.20-pthreads_h6cb6f83_0.tar.bz2#1110034f2f90ca3c7ea35bf0d2eea15e
+https://conda.anaconda.org/conda-forge/linux-aarch64/libuuid-2.32.1-hf897c2e_1000.tar.bz2#e038da5ef9095b0d79aac14a311394e7
+https://conda.anaconda.org/conda-forge/linux-aarch64/libwebp-base-1.2.3-h4e544f5_0.tar.bz2#2fc8598982720195d976cb435c2dd778
+https://conda.anaconda.org/conda-forge/linux-aarch64/libzlib-1.2.12-h4e544f5_2.tar.bz2#72a5a5f407e894d738b0542adacc7d9a
+https://conda.anaconda.org/conda-forge/linux-aarch64/lz4-c-1.9.3-h01db608_1.tar.bz2#25b5ec27b49b04a997a87b0f00f5e205
+https://conda.anaconda.org/conda-forge/linux-aarch64/ncurses-6.3-headf329_1.tar.bz2#486b68148e121bc8bbadc3cefae4c04f
+https://conda.anaconda.org/conda-forge/linux-aarch64/openssl-3.0.5-h4e544f5_0.tar.bz2#4d3667d957345ac403c68803885a0f9a
+https://conda.anaconda.org/conda-forge/linux-aarch64/pthread-stubs-0.4-hb9de7d4_1001.tar.bz2#d0183ec6ce0b5aaa3486df25fa5f0ded
+https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxau-1.0.9-h3557bc0_0.tar.bz2#e0c187f5ce240897762bbb89a8a407cc
+https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxdmcp-1.1.3-h3557bc0_0.tar.bz2#a6c9016ae1ca5c47a3603ed4cd65fedd
+https://conda.anaconda.org/conda-forge/linux-aarch64/xz-5.2.5-h6dd45c4_1.tar.bz2#9da6f161d0f47f8a29fa708cda8deda2
+https://conda.anaconda.org/conda-forge/linux-aarch64/libblas-3.9.0-15_linuxaarch64_openblas.tar.bz2#045400d8613d7c22896082834ea58fb7
+https://conda.anaconda.org/conda-forge/linux-aarch64/libbrotlidec-1.0.9-h4e544f5_7.tar.bz2#6839e1d20fce65ff5f0f0bcf32841993
+https://conda.anaconda.org/conda-forge/linux-aarch64/libbrotlienc-1.0.9-h4e544f5_7.tar.bz2#353cfe995ca6183994d5eb3e674ab42c
+https://conda.anaconda.org/conda-forge/linux-aarch64/libxcb-1.13-h3557bc0_1004.tar.bz2#cc973f5f452272c397546eac588cddb3
+https://conda.anaconda.org/conda-forge/linux-aarch64/llvm-openmp-14.0.4-hb2805f8_0.tar.bz2#7d0aa14e0444d2d803591ca1a61d29f4
+https://conda.anaconda.org/conda-forge/linux-aarch64/openblas-0.3.20-pthreads_h2d9dd7e_0.tar.bz2#db1b9433e8d04b2591111d75be683d5e
+https://conda.anaconda.org/conda-forge/linux-aarch64/readline-8.1.2-h38e3740_0.tar.bz2#3cdbfb7d7b63ae2c2d35bb167d257ecd
+https://conda.anaconda.org/conda-forge/linux-aarch64/tk-8.6.12-hd8af866_0.tar.bz2#7894e82ff743bd96c76585ddebe28e2a
+https://conda.anaconda.org/conda-forge/linux-aarch64/zlib-1.2.12-h4e544f5_2.tar.bz2#cddc4fb1dc1e3ee8d7d8bb078543d656
+https://conda.anaconda.org/conda-forge/linux-aarch64/zstd-1.5.2-haad177d_2.tar.bz2#16fa59d4390a66da86eaee91317212bb
+https://conda.anaconda.org/conda-forge/linux-aarch64/brotli-bin-1.0.9-h4e544f5_7.tar.bz2#4014ebf8c97d8cb219bfc0a12344ceb6
+https://conda.anaconda.org/conda-forge/linux-aarch64/ccache-4.5.1-h637f6b2_0.tar.bz2#0981c793a35b1e72d75d3a40e8dd69a4
+https://conda.anaconda.org/conda-forge/linux-aarch64/libcblas-3.9.0-15_linuxaarch64_openblas.tar.bz2#9baa695b860e4abba076a0ad8fb24627
+https://conda.anaconda.org/conda-forge/linux-aarch64/liblapack-3.9.0-15_linuxaarch64_openblas.tar.bz2#f3b79286dda2ca36a05bc6922432903b
+https://conda.anaconda.org/conda-forge/linux-aarch64/libpng-1.6.37-hf9034f9_3.tar.bz2#421c814654877cdc1004c097253cd68a
+https://conda.anaconda.org/conda-forge/linux-aarch64/libtiff-4.4.0-hacb60eb_1.tar.bz2#52fbcabda33cbd901267ccd4d9205f9c
+https://conda.anaconda.org/conda-forge/linux-aarch64/sqlite-3.39.1-hc74f5b8_0.tar.bz2#2a5b4045b4cfd715a853d486297a4d4b
+https://conda.anaconda.org/conda-forge/linux-aarch64/brotli-1.0.9-h4e544f5_7.tar.bz2#4c345a372cf4936c8e959b8d266c4396
+https://conda.anaconda.org/conda-forge/linux-aarch64/freetype-2.10.4-hdf53a3c_1.tar.bz2#c0b393690f63cdfb986b430971f09cc2
+https://conda.anaconda.org/conda-forge/linux-aarch64/lcms2-2.12-h012adcb_0.tar.bz2#d112a00b1ba7dda1f79735d2a4661ea3
+https://conda.anaconda.org/conda-forge/linux-aarch64/liblapacke-3.9.0-15_linuxaarch64_openblas.tar.bz2#b3919151cfad6ccef7be182ccc295127
+https://conda.anaconda.org/conda-forge/linux-aarch64/libwebp-1.2.3-hef792ef_0.tar.bz2#f75e25750873d0b1098480269b2427f8
+https://conda.anaconda.org/conda-forge/linux-aarch64/openjpeg-2.4.0-h45037e0_1.tar.bz2#c888c9aa7f6e60f2514f88c070ec6f45
+https://conda.anaconda.org/conda-forge/linux-aarch64/python-3.9.13-h5016f1d_0_cpython.tar.bz2#55fbf16ae0237bd720c5231734d778dc
+https://conda.anaconda.org/conda-forge/noarch/attrs-21.4.0-pyhd8ed1ab_0.tar.bz2#f70280205d7044c8b8358c8de3190e5d
+https://conda.anaconda.org/conda-forge/linux-aarch64/blas-devel-3.9.0-15_linuxaarch64_openblas.tar.bz2#474836f44a3e1721fdf04d2b9e6fc614
+https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb
+https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2
+https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905
+https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19
+https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054
+https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc
+https://conda.anaconda.org/conda-forge/linux-aarch64/python_abi-3.9-2_cp39.tar.bz2#c74e493d773fa544a312b0904abcfbfb
+https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2
+https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.1.0-pyh8a188c0_0.tar.bz2#a2995ee828f65687ac5b1e71a2ab1e0c
+https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96
+https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022
+https://conda.anaconda.org/conda-forge/linux-aarch64/blas-2.115-openblas.tar.bz2#53bbf4da5d1548f107e3374bbf466dba
+https://conda.anaconda.org/conda-forge/linux-aarch64/certifi-2022.6.15-py39h4420490_0.tar.bz2#2f8b8e2cba70a1c78e20d00415f616f0
+https://conda.anaconda.org/conda-forge/linux-aarch64/cython-0.29.30-py39h3d8bfb9_0.tar.bz2#1928a6be375c7b0197b8bc7b64a3bf40
+https://conda.anaconda.org/conda-forge/linux-aarch64/kiwisolver-1.4.4-py39h110580c_0.tar.bz2#208abbe5753c4dc2124bc9e9991fe68c
+https://conda.anaconda.org/conda-forge/linux-aarch64/numpy-1.23.1-py39h7190128_0.tar.bz2#736b2e925faecaaf22467b3ada0307e4
+https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85
+https://conda.anaconda.org/conda-forge/linux-aarch64/pillow-9.2.0-py39h2a8e185_0.tar.bz2#7fb3b17c124d60e6a931dce32d194fef
+https://conda.anaconda.org/conda-forge/linux-aarch64/pluggy-1.0.0-py39ha65689a_3.tar.bz2#3ccbd600f5d9921e928da3d69a9720a9
+https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984
+https://conda.anaconda.org/conda-forge/linux-aarch64/setuptools-63.2.0-py39ha65689a_0.tar.bz2#50c735fb7e05c4615bb4455082c5325c
+https://conda.anaconda.org/conda-forge/linux-aarch64/tornado-6.2-py39hb9a1dbb_0.tar.bz2#70f97b74fb9ce68addca4652dc54c170
+https://conda.anaconda.org/conda-forge/linux-aarch64/unicodedata2-14.0.0-py39h0fd3b05_1.tar.bz2#7182266a1f86f367d88c86a9ab560cca
+https://conda.anaconda.org/conda-forge/linux-aarch64/fonttools-4.34.4-py39h0fd3b05_0.tar.bz2#48ddbfe19fc8630c73cdadff02792a87
+https://conda.anaconda.org/conda-forge/noarch/joblib-1.1.0-pyhd8ed1ab_0.tar.bz2#07d1b5c8cde14d95998fd4767e1e62d2
+https://conda.anaconda.org/conda-forge/noarch/pip-22.1.2-pyhd8ed1ab_0.tar.bz2#d29185c662a424f8bea1103270b85c96
+https://conda.anaconda.org/conda-forge/linux-aarch64/pytest-7.1.2-py39ha65689a_0.tar.bz2#6de5b49b9d9e7628e0666ccf2b7df40b
+https://conda.anaconda.org/conda-forge/linux-aarch64/scipy-1.8.1-py39h43b6dad_0.tar.bz2#372f005c9ca6dc0db0adae0f78dbc4d4
+https://conda.anaconda.org/conda-forge/linux-aarch64/matplotlib-base-3.5.2-py39hfed42d8_0.tar.bz2#9f90790067684c9f0ab1b07e6e82070a
+https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f
+https://conda.anaconda.org/conda-forge/linux-aarch64/matplotlib-3.5.2-py39ha65689a_0.tar.bz2#e5738e0c03863f94223c19aaf0f2564d
+https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e
diff --git a/build_tools/circle/build_doc.sh b/build_tools/github/build_doc.sh
similarity index 82%
rename from build_tools/circle/build_doc.sh
rename to build_tools/github/build_doc.sh
index c0bbb4350cc0a..249dd82e798b6 100755
--- a/build_tools/circle/build_doc.sh
+++ b/build_tools/github/build_doc.sh
@@ -1,5 +1,4 @@
#!/usr/bin/env bash
-set -x
set -e
# Decide what kind of documentation build to run, and run it.
@@ -17,6 +16,22 @@ set -e
# If the inspection of the current commit fails for any reason, the default
# behavior is to quick build the documentation.
+if [ -n "$GITHUB_ACTION" ]
+then
+ # Map the variables for the new documentation builder to the old one
+ CIRCLE_SHA1=$(git log -1 --pretty=format:%H)
+
+ CIRCLE_JOB=$GITHUB_JOB
+
+ if [ "$GITHUB_EVENT_NAME" == "pull_request" ]
+ then
+ CIRCLE_BRANCH=$GITHUB_HEAD_REF
+ CI_PULL_REQUEST=true
+ else
+ CIRCLE_BRANCH=$GITHUB_REF_NAME
+ fi
+fi
+
get_build_type() {
if [ -z "$CIRCLE_SHA1" ]
then
@@ -138,55 +153,35 @@ sudo -E apt-get -yq update --allow-releaseinfo-change
sudo -E apt-get -yq --no-install-suggests --no-install-recommends \
install dvipng gsfonts ccache zip optipng
-# deactivate circleci virtualenv and setup a miniconda env instead
+# deactivate circleci virtualenv and setup a conda env instead
if [[ `type -t deactivate` ]]; then
deactivate
fi
-MINICONDA_PATH=$HOME/miniconda
-# Install dependencies with miniconda
-wget https://github.com/conda-forge/miniforge/releases/latest/download/Mambaforge-Linux-x86_64.sh \
- -O miniconda.sh
-chmod +x miniconda.sh && ./miniconda.sh -b -p $MINICONDA_PATH
-export PATH="/usr/lib/ccache:$MINICONDA_PATH/bin:$PATH"
+MAMBAFORGE_PATH=$HOME/mambaforge
+# Install dependencies with mamba
+wget -q https://github.com/conda-forge/miniforge/releases/latest/download/Mambaforge-Linux-x86_64.sh \
+ -O mambaforge.sh
+chmod +x mambaforge.sh && ./mambaforge.sh -b -p $MAMBAFORGE_PATH
+export PATH="/usr/lib/ccache:$MAMBAFORGE_PATH/bin:$PATH"
ccache -M 512M
export CCACHE_COMPRESS=1
-# Old packages coming from the 'free' conda channel have been removed but we
-# are using them for our min-dependencies doc generation. See
-# https://www.anaconda.com/why-we-removed-the-free-channel-in-conda-4-7/ for
-# more details.
-if [[ "$CIRCLE_JOB" == "doc-min-dependencies" ]]; then
- conda config --set restore_free_channel true
-fi
+# pin conda-lock to latest released version (needs manual update from time to time)
+mamba install conda-lock==1.0.5 -y
+conda-lock install --log-level WARNING --name $CONDA_ENV_NAME $LOCK_FILE
+source activate $CONDA_ENV_NAME
-# imports get_dep
-source build_tools/shared.sh
-
-# packaging won't be needed once setuptools starts shipping packaging>=17.0
-mamba create -n $CONDA_ENV_NAME --yes --quiet \
- python="${PYTHON_VERSION:-*}" \
- "$(get_dep numpy $NUMPY_VERSION)" \
- "$(get_dep scipy $SCIPY_VERSION)" \
- "$(get_dep cython $CYTHON_VERSION)" \
- "$(get_dep matplotlib $MATPLOTLIB_VERSION)" \
- "$(get_dep sphinx $SPHINX_VERSION)" \
- "$(get_dep pandas $PANDAS_VERSION)" \
- joblib memory_profiler packaging seaborn pillow pytest coverage \
- compilers
-
-source activate testenv
-pip install "$(get_dep scikit-image $SCIKIT_IMAGE_VERSION)"
-pip install "$(get_dep sphinx-gallery $SPHINX_GALLERY_VERSION)"
-pip install "$(get_dep numpydoc $NUMPYDOC_VERSION)"
-pip install "$(get_dep sphinx-prompt $SPHINX_PROMPT_VERSION)"
-pip install "$(get_dep sphinxext-opengraph $SPHINXEXT_OPENGRAPH_VERSION)"
+mamba list
# Set parallelism to 3 to overlap IO bound tasks with CPU bound tasks on CI
# workers with 2 cores when building the compiled extensions of scikit-learn.
export SKLEARN_BUILD_PARALLEL=3
-python setup.py develop
+pip install -e . --no-build-isolation
+
+echo "ccache build summary:"
+ccache -s
export OMP_NUM_THREADS=1
diff --git a/build_tools/github/build_wheels.sh b/build_tools/github/build_wheels.sh
index 5d379bf155146..647b47492774b 100755
--- a/build_tools/github/build_wheels.sh
+++ b/build_tools/github/build_wheels.sh
@@ -18,9 +18,7 @@ if [[ "$RUNNER_OS" == "macOS" ]]; then
export MACOSX_DEPLOYMENT_TARGET=12.0
OPENMP_URL="https://anaconda.org/conda-forge/llvm-openmp/11.1.0/download/osx-arm64/llvm-openmp-11.1.0-hf3c4609_1.tar.bz2"
else
- # Currently, the oldest supported macos version is: High Sierra / 10.13.
- # Note that Darwin_17 == High Sierra / 10.13.
- export MACOSX_DEPLOYMENT_TARGET=10.13
+ export MACOSX_DEPLOYMENT_TARGET=10.9
OPENMP_URL="https://anaconda.org/conda-forge/llvm-openmp/11.1.0/download/osx-64/llvm-openmp-11.1.0-hda6cdc1_1.tar.bz2"
fi
diff --git a/build_tools/github/doc_environment.yml b/build_tools/github/doc_environment.yml
new file mode 100644
index 0000000000000..f0a933e31552a
--- /dev/null
+++ b/build_tools/github/doc_environment.yml
@@ -0,0 +1,30 @@
+# DO NOT EDIT: this file is generated from the specification found in the
+# following script to centralize the configuration for CI builds:
+# build_tools/update_environments_and_lock_files.py
+channels:
+ - conda-forge
+dependencies:
+ - python=3.9
+ - numpy
+ - blas
+ - scipy
+ - cython
+ - joblib
+ - threadpoolctl
+ - matplotlib
+ - pandas
+ - pyamg
+ - pytest
+ - pytest-xdist
+ - pillow
+ - scikit-image
+ - seaborn
+ - memory_profiler
+ - compilers
+ - sphinx
+ - sphinx-gallery
+ - numpydoc
+ - sphinx-prompt
+ - pip
+ - pip:
+ - sphinxext-opengraph
diff --git a/build_tools/github/doc_linux-64_conda.lock b/build_tools/github/doc_linux-64_conda.lock
new file mode 100644
index 0000000000000..c1ccf6437f624
--- /dev/null
+++ b/build_tools/github/doc_linux-64_conda.lock
@@ -0,0 +1,244 @@
+# Generated by conda-lock.
+# platform: linux-64
+# input_hash: 1b1e977a1b5dcedea55d0a8b53d501d1c1211b70a4419ec48dafe1b2658b4ef8
+@EXPLICIT
+https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81
+https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2
+https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45
+https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6
+https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb
+https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-hab24e00_0.tar.bz2#19410c3df09dfb12d1206132a1d357c5
+https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-2.6.32-he073ed8_15.tar.bz2#5dd5127afd710f91f6a75821bac0a4f0
+https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.36.1-hea4e1c9_2.tar.bz2#bd4f2e711b39af170e7ff15163fe87ee
+https://conda.anaconda.org/conda-forge/linux-64/libgcc-devel_linux-64-10.3.0-he6cfe16_16.tar.bz2#878a30aba0574e69bd920c55f243aa06
+https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.1.0-hdcd56e2_16.tar.bz2#b02605b875559ff99f04351fd5040760
+https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-devel_linux-64-10.3.0-he6cfe16_16.tar.bz2#baae55f62968547a3731cb668736f611
+https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.1.0-ha89aaad_16.tar.bz2#6f5ba041a41eb102a1027d9e68731be7
+https://conda.anaconda.org/conda-forge/noarch/tzdata-2022a-h191b570_0.tar.bz2#84be5301069417a2221187d2f435e0f7
+https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29
+https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.1.0-h69a702a_16.tar.bz2#6bf15e29a20f614b18ae89368260d0a2
+https://conda.anaconda.org/conda-forge/linux-64/libgomp-12.1.0-h8d9b700_16.tar.bz2#f013cf7749536ce43d82afbffdf499ab
+https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.12-he073ed8_15.tar.bz2#66c192522eacf5bb763568b4e415d133
+https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.36.1-h193b22a_2.tar.bz2#32aae4265554a47ea77f7c09f86aeb3b
+https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab
+https://conda.anaconda.org/conda-forge/linux-64/binutils-2.36.1-hdd6e379_2.tar.bz2#3111f86041b5b6863545ca49130cca95
+https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.36-hf3e587d_10.tar.bz2#9d5cdbfe24b182d4c749b86d500ac9d2
+https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_kmp_llvm.tar.bz2#562b26ba2e19059551a811e72ab7f793
+https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-12.1.0-h8d9b700_16.tar.bz2#4f05bc9844f7c101e6e147dab3c88d5c
+https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.6.1-h7f98852_0.tar.bz2#0347ce6a34f8b55b544b141432c6d4c7
+https://conda.anaconda.org/conda-forge/linux-64/aom-3.3.0-h27087fc_1.tar.bz2#fe863d1e92331e69c8f231df5eaf5e16
+https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_0.tar.bz2#ec47e97c8e0b27dcadbebc4d17764548
+https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54
+https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a
+https://conda.anaconda.org/conda-forge/linux-64/charls-2.3.4-h9c3ff4c_0.tar.bz2#c3f85a96a52befc5e41cab1145c8d3c2
+https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.8-h27087fc_0.tar.bz2#e1b07832504eeba765d648389cc387a9
+https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_h77c792f_102.tar.bz2#208f18b1d596b50c6a92a12b30ebe31f
+https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d
+https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2#87473a15119779e021c314249d4b4aed
+https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_2.tar.bz2#ee8b844357a0946870901c7c6f418268
+https://conda.anaconda.org/conda-forge/linux-64/jxrlib-1.1-h7f98852_2.tar.bz2#8e787b08fe19986d99d034b839df2961
+https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3
+https://conda.anaconda.org/conda-forge/linux-64/lerc-3.0-h9c3ff4c_0.tar.bz2#7fcefde484980d23f0ec24c11e314d2e
+https://conda.anaconda.org/conda-forge/linux-64/libaec-1.0.6-h9c3ff4c_0.tar.bz2#c77f5e4e418fa47d699d6afa54c5d444
+https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_7.tar.bz2#f82dc1c78bcf73583f2656433ce2933c
+https://conda.anaconda.org/conda-forge/linux-64/libdb-6.2.32-h9c3ff4c_0.tar.bz2#3f3258d8f841fbac63b36b75bdac1afd
+https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.12-h166bdaf_0.tar.bz2#d56e3db8fa642fb383f18f5be35eeef2
+https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2#6f8720dff19e17ce5d48cfe7f3d2f0a3
+https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3
+https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2#5c0f338a513a2943c659ae619fca9211
+https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206
+https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680
+https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.20-pthreads_h78a6416_0.tar.bz2#9b6d0781953c9e353faee494336cc229
+https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f
+https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-10.3.0-h26c7422_16.tar.bz2#4076c395c7fa53cd708949e4be48154e
+https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee
+https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334
+https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d
+https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.3-h166bdaf_0.tar.bz2#3d6168ac3560d473e52a7cb836400135
+https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_2.tar.bz2#8302381297332ea50532cf2c67961080
+https://conda.anaconda.org/conda-forge/linux-64/libzopfli-1.0.3-h9c3ff4c_0.tar.bz2#c66fe2d123249af7651ebde8984c51c2
+https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6
+https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238
+https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e
+https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1q-h166bdaf_0.tar.bz2#07acc367c7fc8b716770cd5b36d31717
+https://conda.anaconda.org/conda-forge/linux-64/pcre-8.45-h9c3ff4c_0.tar.bz2#c05d1820a6d34ff07aaaab7a9b7eddaa
+https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036
+https://conda.anaconda.org/conda-forge/linux-64/snappy-1.1.9-hbd366e4_1.tar.bz2#418adb239781d9690afc6b1a05514c37
+https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.9-h7f98852_0.tar.bz2#bf6f803a544f26ebbdc3bfff272eb179
+https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.3-h7f98852_0.tar.bz2#be93aabceefa2fac576e971aef407908
+https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.5-h516909a_1.tar.bz2#33f601066901f3e1a85af3522a8113f9
+https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae
+https://conda.anaconda.org/conda-forge/linux-64/zfp-0.5.5-h9c3ff4c_8.tar.bz2#f12900b1e1e0527c0e9a4e922a5de2bf
+https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.0.6-h166bdaf_0.tar.bz2#8650e4fb44c4a618e5ab3e1e19607e32
+https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-10.3.0-hf2f2afa_16.tar.bz2#a340c8ce9e702836e999984fcabd1b6e
+https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h73d1719_1008.tar.bz2#af49250eca8e139378f8ff0ae9e57251
+https://conda.anaconda.org/conda-forge/linux-64/libavif-0.10.1-h166bdaf_0.tar.bz2#eb9a725cb66435aaf81f13ce585a64eb
+https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-15_linux64_openblas.tar.bz2#04eb983975a1be3e57d6d667414cd774
+https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_7.tar.bz2#37a460703214d0d1b421e2a47eb5e6d0
+https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_7.tar.bz2#785a9296ea478eb78c47593c4da6550f
+https://conda.anaconda.org/conda-forge/linux-64/libcap-2.64-ha37c62d_0.tar.bz2#5896fbd58d0376df8556a4aba1ce4f71
+https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1
+https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336
+https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe
+https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0
+https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904
+https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-14.0.4-he0ac6c6_0.tar.bz2#cecc6e3cb66570ffcfb820c637890f54
+https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.29-haf5c9bc_1.tar.bz2#c01640c8bad562720d6caff0402dbd96
+https://conda.anaconda.org/conda-forge/linux-64/openblas-0.3.20-pthreads_h320a7e8_0.tar.bz2#4cc467036ee23a4e7dac2d2c53cc7c21
+https://conda.anaconda.org/conda-forge/linux-64/portaudio-19.6.0-h57a0ea0_5.tar.bz2#5469312a373f481c05c380897fd7c923
+https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa
+https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168
+https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_2.tar.bz2#4533821485cde83ab12ff3d8bda83768
+https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_2.tar.bz2#78c26dbb6e07d95ccc0eab8d4540aa0c
+https://conda.anaconda.org/conda-forge/linux-64/blosc-1.21.1-h83bc5f7_3.tar.bz2#37baca23e60af4130cfc03e8ab9f8e22
+https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e
+https://conda.anaconda.org/conda-forge/linux-64/c-blosc2-2.2.0-h7a311fb_0.tar.bz2#5bdd579ae21231f8971eab74bfb18f07
+https://conda.anaconda.org/conda-forge/linux-64/gcc-10.3.0-he2824d0_10.tar.bz2#b1f092f4fdbfff91e1df27aa46efa2bb
+https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-10.3.0-hc39de41_10.tar.bz2#6e52c54509389c06f88ea74c137f75be
+https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-10.3.0-h73f4979_16.tar.bz2#0407b066da49b4562ce054c06b40558c
+https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-10.3.0-hf2f2afa_16.tar.bz2#f825336ce3d4dba77ffd1edc78e7abf9
+https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8
+https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-15_linux64_openblas.tar.bz2#f45968428e445fd0c6472b561145812a
+https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d3e_0.tar.bz2#cdbd49e0ab5c5a6c522acb8271977d4c
+https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef
+https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz2#ebeadbb5fbc44052eeb6f96a2136e3c2
+https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-15_linux64_openblas.tar.bz2#b7078220384b8bf8db1a45e66412ac4f
+https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-h727a467_0.tar.bz2#a22567abfea169ff8048506b1ca9b230
+https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_3.tar.bz2#3e868978a04de8bf65a97bb86760f47a
+https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz2#6ab4eaa11ff01801cffca0a27489dc04
+https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-hc85c160_1.tar.bz2#151f9fae3ab50f039c8735e47770aa2d
+https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_3.tar.bz2#b6f4a0850ba620030a48b88c25497aaa
+https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.29-h28c427c_1.tar.bz2#36dbdbf505b131c7e79a3857f3537185
+https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.1-h4ff8645_0.tar.bz2#6acda9d2a3ea84b58637b8f880bbf29b
+https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566
+https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879
+https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7
+https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.1-h166bdaf_0.tar.bz2#0a8e20a8aef954390b9481a527421a8c
+https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1
+https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.4.2-h166bdaf_0.tar.bz2#d3a922efc75c5f2534372eead96d46be
+https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d
+https://conda.anaconda.org/conda-forge/linux-64/freetype-2.10.4-h0708190_1.tar.bz2#4a06f2ac2e5bfae7b6b245171c3f07aa
+https://conda.anaconda.org/conda-forge/linux-64/gfortran-10.3.0-h18518b4_10.tar.bz2#794676d0d7dd69998cb12654cf37f43d
+https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-10.3.0-hb09a455_10.tar.bz2#dd9058b625a6edce185db90cf99e3590
+https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar.bz2#a3a99cc33279091262bbc4f5ee7c4571
+https://conda.anaconda.org/conda-forge/linux-64/gxx-10.3.0-he2824d0_10.tar.bz2#121c9d19ecb6cf6e587c2ab8dfb5c73c
+https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-10.3.0-h2593f52_10.tar.bz2#075de70ba0493c56ed9e9cda930978ce
+https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f
+https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.6-default_h2e3cab8_0.tar.bz2#eb70548da697e50cefa7ba939d57d001
+https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-hf5a7f15_1.tar.bz2#005557d6df00af70e438bcd532ce2304
+https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.83.1-h7bff187_0.tar.bz2#d0c278476dba3b29ee13203784672ab1
+https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-15_linux64_openblas.tar.bz2#31f21773a784f59962ea542a3d71ad87
+https://conda.anaconda.org/conda-forge/linux-64/libpq-14.4-hd77ab85_0.tar.bz2#7024df220bd8680192d4bad4024122d1
+https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a
+https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.3-h522a892_0.tar.bz2#96e218d06394a4e4d77028cf70162a09
+https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b
+https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc
+https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.4.0-hb52868f_1.tar.bz2#b7ad78ad2e9ee155f59e6428406ee824
+https://conda.anaconda.org/conda-forge/linux-64/python-3.9.13-h9a8a25e_0_cpython.tar.bz2#69bc307cc4d7396c5fccb26bbcc9c379
+https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0.tar.bz2#c9b568bd804cb2903c6be6f5f68182e4
+https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0
+https://conda.anaconda.org/conda-forge/noarch/attrs-21.4.0-pyhd8ed1ab_0.tar.bz2#f70280205d7044c8b8358c8de3190e5d
+https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-15_linux64_openblas.tar.bz2#2b5095be485bdb407ff3134358c3ca9c
+https://conda.anaconda.org/conda-forge/linux-64/brunsli-0.1-h9c3ff4c_0.tar.bz2#c1ac6229d0bfd14f8354ff9ad2a26cad
+https://conda.anaconda.org/conda-forge/linux-64/cfitsio-4.1.0-hd9d235c_0.tar.bz2#ebc04a148d7204bb428f8633b89fd3dd
+https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.0-pyhd8ed1ab_0.tar.bz2#abc0453b6e7bfbb87d275d58e333fc98
+https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.1.0-pyhd8ed1ab_0.tar.bz2#f7551a8a008dfad2b7ac9662dd124614
+https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.5-pyhd8ed1ab_0.tar.bz2#c267da48ce208905d7d976d49dfd9433
+https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.4.2-h924138e_0.tar.bz2#40b38afc72b4f12be8a2ca29853b03ac
+https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb
+https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2
+https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89
+https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.4.2-h2a4ca65_0.tar.bz2#a39c981deb5b50e2ea1464ab9d5c0b10
+https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.5.0-pyhd8ed1ab_0.tar.bz2#db4ffc615663c66a9cc0869ce4d1092b
+https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5
+https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd
+https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352
+https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905
+https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1002.tar.bz2#7b3f287fcb7683f67b3d953b79f412ea
+https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4
+https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19
+https://conda.anaconda.org/conda-forge/noarch/networkx-2.8.5-pyhd8ed1ab_0.tar.bz2#4359cc8df0609a2b88aec55d6d2fedaf
+https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727
+https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054
+https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff
+https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc
+https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.9-2_cp39.tar.bz2#39adde4247484de2bb4000122fdcf665
+https://conda.anaconda.org/conda-forge/noarch/pytz-2022.1-pyhd8ed1ab_0.tar.bz2#b87d66d6d3991d988fb31510c95a9267
+https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2
+https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e
+https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-1.0.2-py_0.tar.bz2#20b2eaeaeea4ef9a9a0d99770620fd09
+https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-devhelp-1.0.2-py_0.tar.bz2#68e01cac9d38d0e717cd5c87bc3d2cc9
+https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.0.0-pyhd8ed1ab_0.tar.bz2#77dad82eb9c8c1525ff7953e0756d708
+https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jsmath-1.0.1-py_0.tar.bz2#67cd9d9c0382d37479b4d306c369a2d4
+https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-1.0.3-py_0.tar.bz2#d01180388e6d1838c3e1ad029590aa7a
+https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1.5-pyhd8ed1ab_2.tar.bz2#9ff55a0901cf952f05c654394de76bf7
+https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.1.0-pyh8a188c0_0.tar.bz2#a2995ee828f65687ac5b1e71a2ab1e0c
+https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f832c45a477c78bebd107098db465095
+https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96
+https://conda.anaconda.org/conda-forge/noarch/toolz-0.12.0-pyhd8ed1ab_0.tar.bz2#92facfec94bc02d6ccf42e7173831a36
+https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022
+https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.0-pyhd8ed1ab_0.tar.bz2#050b94cf4a8c760656e51d2d44e4632c
+https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2#72f1c6d03109d7a70087bc1d029a8eda
+https://conda.anaconda.org/conda-forge/linux-64/blas-2.115-openblas.tar.bz2#ca9e177657aa07ab306bd1bbcdf80e69
+https://conda.anaconda.org/conda-forge/linux-64/certifi-2022.6.15-py39hf3d152e_0.tar.bz2#cf0efee4ef53a6d3ea4dce06ac360f14
+https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py39he91dace_0.tar.bz2#61e961a94c8fd535e4496b17e7452dfe
+https://conda.anaconda.org/conda-forge/linux-64/compilers-1.4.2-ha770c72_0.tar.bz2#b353fa1271e1a82d37a7d35f4785de13
+https://conda.anaconda.org/conda-forge/linux-64/cython-0.29.30-py39h5a03fae_0.tar.bz2#78d64530b059de26a60f979e02c9fa3c
+https://conda.anaconda.org/conda-forge/linux-64/cytoolz-0.12.0-py39hb9d737c_0.tar.bz2#26740ffa0bfc09bfee7dbe9d226577c9
+https://conda.anaconda.org/conda-forge/linux-64/docutils-0.18.1-py39hf3d152e_1.tar.bz2#9851752658704495f8adf28f6d2b3cb3
+https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_0.tar.bz2#94cb81ffdce328f80c87ac9b01244632
+https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py39hf3d152e_0.tar.bz2#4c2a0eabf0b8980b2c755646a6f750eb
+https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py39hf939315_0.tar.bz2#e8d1310648c189d6d11a2e13f73da1fe
+https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py39hb9d737c_1.tar.bz2#7cda413e43b252044a270c2477031c5c
+https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.1-py39hba7629e_0.tar.bz2#ee8dff1fb28e0e2c458e845aae9d915a
+https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85
+https://conda.anaconda.org/conda-forge/noarch/partd-1.2.0-pyhd8ed1ab_0.tar.bz2#0c32f563d7f22e3a34c95cad8cc95651
+https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py39hae2aec6_0.tar.bz2#ea9760ab3700e63809de6ae71a11664b
+https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py39hf3d152e_3.tar.bz2#c375c89340e563053f3656c7f134d265
+https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.1-py39hb9d737c_0.tar.bz2#5852c69cad74811dc3c95f9ab6a184ef
+https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h7f54b18_8.tar.bz2#f9dbcfbb942ec9a3c0249cb71da5c7d1
+https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py39hf3d152e_5.tar.bz2#d34b97a2386932b97c7cb80916a673e7
+https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984
+https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py39hb9d737c_4.tar.bz2#dcc47a3b751508507183d17e569805e5
+https://conda.anaconda.org/conda-forge/linux-64/setuptools-63.2.0-py39hf3d152e_0.tar.bz2#0a487a44f996e39d13cdf2899855c406
+https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py39hb9d737c_0.tar.bz2#a3c57360af28c0d9956622af99a521cd
+https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py39hb9d737c_1.tar.bz2#ef84376736d1e8a814ccb06d1d814e6f
+https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py39hb9d737c_1004.tar.bz2#05a99367d885ec9990f25e74128a8a08
+https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py39hd97740a_0.tar.bz2#edc3668e7b71657237f94cf25e286478
+https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.7.0-pyhd8ed1ab_0.tar.bz2#3b533fc35efb54900c9e4ab06242f8b5
+https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.34.4-py39hb9d737c_0.tar.bz2#7980ace37ccb3399672c3a9840e039ed
+https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08
+https://conda.anaconda.org/conda-forge/linux-64/imagecodecs-2022.2.22-py39hfcd4fa2_6.tar.bz2#fb284401002dea344eada09798cd07ec
+https://conda.anaconda.org/conda-forge/noarch/imageio-2.19.3-pyhcf75d05_0.tar.bz2#9a5e536d761271c400310ec5dff8c5e1
+https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37
+https://conda.anaconda.org/conda-forge/noarch/joblib-1.1.0-pyhd8ed1ab_0.tar.bz2#07d1b5c8cde14d95998fd4767e1e62d2
+https://conda.anaconda.org/conda-forge/noarch/memory_profiler-0.60.0-pyhd8ed1ab_0.tar.bz2#f769ad93cd67c6eb7e932c255ed7d642
+https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.3-py39h1832856_0.tar.bz2#74e00961703972cf33b44a6fca7c3d51
+https://conda.anaconda.org/conda-forge/noarch/pip-22.1.2-pyhd8ed1ab_0.tar.bz2#d29185c662a424f8bea1103270b85c96
+https://conda.anaconda.org/conda-forge/noarch/pygments-2.12.0-pyhd8ed1ab_0.tar.bz2#cb27e2ded147e5bcc7eafc1c6d343cb3
+https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py39hf3d152e_0.tar.bz2#a6bcf633d12aabdfc4cb32a09ebc0f31
+https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py39hd257fcd_1.tar.bz2#c4b698994b2d8d2e659ae02202e6abe4
+https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.1-py39he49c0e8_0.tar.bz2#b1b4cc4216e555168e88d6a2b1914af1
+https://conda.anaconda.org/conda-forge/linux-64/sip-6.6.2-py39h5a03fae_0.tar.bz2#e37704c6be07b8b14ffc1ce912802ce0
+https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py39h700656a_0.tar.bz2#ab1bcd0fd24e375f16d662e4cc783cab
+https://conda.anaconda.org/conda-forge/noarch/patsy-0.5.2-pyhd8ed1ab_0.tar.bz2#2e4e8be763551f60bbfcc22b650e5d49
+https://conda.anaconda.org/conda-forge/linux-64/pyamg-4.2.3-py39hac2352c_0.tar.bz2#9df63dea0e4d2163be63b8e40c17ceaf
+https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44
+https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py39h5a03fae_0.tar.bz2#1fd9112714d50ee5be3dbf4fd23964dc
+https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f
+https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.4-ha5833f6_2.tar.bz2#dd3aa6715b9e9efaf842febf18ce4261
+https://conda.anaconda.org/conda-forge/noarch/tifffile-2022.5.4-pyhd8ed1ab_0.tar.bz2#8c8c68b9d466b8804584abe11a75c396
+https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py39h18e9c17_0.tar.bz2#5ed8f83afff3b64fa91f7a6af8d7ff04
+https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e
+https://conda.anaconda.org/conda-forge/linux-64/scikit-image-0.19.3-py39h1832856_0.tar.bz2#5d638481c48c57fdb986490a4a6983fc
+https://conda.anaconda.org/conda-forge/noarch/seaborn-base-0.11.2-pyhd8ed1ab_0.tar.bz2#fe2303dc8f1febeb82d927ce8ad153ed
+https://conda.anaconda.org/conda-forge/linux-64/statsmodels-0.13.2-py39hd257fcd_0.tar.bz2#bd7cdadf70e34a19333c3aacc40206e8
+https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.10-pyhd8ed1ab_0.tar.bz2#14f22c5b9cfd0d93c2806faaa3fe6dec
+https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.2-py39hf3d152e_0.tar.bz2#d65d073d186977a2a9a9d5a68b2b77ef
+https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_0.tar.bz2#70d6e72856de9551f83ae0f2de689a7a
+https://conda.anaconda.org/conda-forge/noarch/seaborn-0.11.2-hd8ed1ab_0.tar.bz2#e56b6a19f4b717eca7c68ad78196b075
+https://conda.anaconda.org/conda-forge/noarch/sphinx-5.0.2-pyh6c4a22f_0.tar.bz2#d4eaa1f50733a377480ce1d5aac556c7
+https://conda.anaconda.org/conda-forge/noarch/numpydoc-1.4.0-pyhd8ed1ab_1.tar.bz2#0aac89c61a466b0f9c4fd0ec44d81f1d
+https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.10.1-pyhd8ed1ab_0.tar.bz2#4918585fe5e5341740f7e63c61743efb
+https://conda.anaconda.org/conda-forge/noarch/sphinx-prompt-1.4.0-pyhd8ed1ab_0.tar.bz2#88ee91e8679603f2a5bd036d52919cc2
+# pip sphinxext-opengraph @ https://files.pythonhosted.org/packages/58/ed/59df64b8400caf736f38bd3725ab9b1d9e50874f629980973aea090c1a8b/sphinxext_opengraph-0.6.3-py3-none-any.whl#md5=None
diff --git a/build_tools/github/doc_min_dependencies_environment.yml b/build_tools/github/doc_min_dependencies_environment.yml
new file mode 100644
index 0000000000000..9f20b9e900336
--- /dev/null
+++ b/build_tools/github/doc_min_dependencies_environment.yml
@@ -0,0 +1,30 @@
+# DO NOT EDIT: this file is generated from the specification found in the
+# following script to centralize the configuration for CI builds:
+# build_tools/update_environments_and_lock_files.py
+channels:
+ - conda-forge
+dependencies:
+ - python=3.8
+ - numpy=1.17.3 # min
+ - blas
+ - scipy=1.3.2 # min
+ - cython=0.29.24 # min
+ - joblib
+ - threadpoolctl
+ - matplotlib=3.1.2 # min
+ - pandas=1.0.5 # min
+ - pyamg
+ - pytest
+ - pytest-xdist
+ - pillow
+ - scikit-image=0.16.2 # min
+ - seaborn
+ - memory_profiler
+ - compilers
+ - sphinx=4.0.1 # min
+ - sphinx-gallery=0.7.0 # min
+ - numpydoc=1.2.0 # min
+ - sphinx-prompt=1.3.0 # min
+ - pip
+ - pip:
+ - sphinxext-opengraph==0.4.2 # min
diff --git a/build_tools/github/doc_min_dependencies_linux-64_conda.lock b/build_tools/github/doc_min_dependencies_linux-64_conda.lock
new file mode 100644
index 0000000000000..10d531bc9005d
--- /dev/null
+++ b/build_tools/github/doc_min_dependencies_linux-64_conda.lock
@@ -0,0 +1,165 @@
+# Generated by conda-lock.
+# platform: linux-64
+# input_hash: a6aaffac15d19e8ed6fc40eddb398e18e9252fb737bf8c60f99f93e7d41ac5ce
+@EXPLICIT
+https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81
+https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2
+https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-2.6.32-he073ed8_15.tar.bz2#5dd5127afd710f91f6a75821bac0a4f0
+https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.36.1-hea4e1c9_2.tar.bz2#bd4f2e711b39af170e7ff15163fe87ee
+https://conda.anaconda.org/conda-forge/linux-64/libgcc-devel_linux-64-7.5.0-hda03d7c_20.tar.bz2#2146b25eb2a762a44fab709338a7b6d9
+https://conda.anaconda.org/conda-forge/linux-64/libgfortran4-7.5.0-h14aa051_20.tar.bz2#a072eab836c3a9578ce72b5640ce592d
+https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-devel_linux-64-7.5.0-hb016644_20.tar.bz2#31d5500f621954679ee41d7f5d1089fb
+https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.1.0-ha89aaad_16.tar.bz2#6f5ba041a41eb102a1027d9e68731be7
+https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-7.5.0-h14aa051_20.tar.bz2#c3b2ad091c043c08689e64b10741484b
+https://conda.anaconda.org/conda-forge/linux-64/libgomp-12.1.0-h8d9b700_16.tar.bz2#f013cf7749536ce43d82afbffdf499ab
+https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.12-he073ed8_15.tar.bz2#66c192522eacf5bb763568b4e415d133
+https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.36.1-h193b22a_2.tar.bz2#32aae4265554a47ea77f7c09f86aeb3b
+https://conda.anaconda.org/conda-forge/linux-64/binutils-2.36.1-hdd6e379_2.tar.bz2#3111f86041b5b6863545ca49130cca95
+https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.36-hf3e587d_33.tar.bz2#72b245322c589284f1b92a5c971e5cb6
+https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_kmp_llvm.tar.bz2#562b26ba2e19059551a811e72ab7f793
+https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-12.1.0-h8d9b700_16.tar.bz2#4f05bc9844f7c101e6e147dab3c88d5c
+https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.8-h27087fc_0.tar.bz2#e1b07832504eeba765d648389cc387a9
+https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-7.5.0-habd7529_20.tar.bz2#42140612518a7ce78f571d64b6a50ba3
+https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d
+https://conda.anaconda.org/conda-forge/linux-64/icu-64.2-he1b5a44_1.tar.bz2#8e881214a23508f1541eb7a3135d6fcb
+https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_2.tar.bz2#ee8b844357a0946870901c7c6f418268
+https://conda.anaconda.org/conda-forge/linux-64/lerc-3.0-h9c3ff4c_0.tar.bz2#7fcefde484980d23f0ec24c11e314d2e
+https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.12-h166bdaf_0.tar.bz2#d56e3db8fa642fb383f18f5be35eeef2
+https://conda.anaconda.org/conda-forge/linux-64/libffi-3.2.1-he1b5a44_1007.tar.bz2#11389072d7d6036fd811c3d9460475cd
+https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2#5c0f338a513a2943c659ae619fca9211
+https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d
+https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.3-h166bdaf_0.tar.bz2#3d6168ac3560d473e52a7cb836400135
+https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-0.10.0-he1b5a44_0.tar.bz2#78ccac2098edcd3673af2ceb3e95f932
+https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_2.tar.bz2#8302381297332ea50532cf2c67961080
+https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6
+https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238
+https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e
+https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1q-h166bdaf_0.tar.bz2#07acc367c7fc8b716770cd5b36d31717
+https://conda.anaconda.org/conda-forge/linux-64/pcre-8.45-h9c3ff4c_0.tar.bz2#c05d1820a6d34ff07aaaab7a9b7eddaa
+https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036
+https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.9-h7f98852_0.tar.bz2#bf6f803a544f26ebbdc3bfff272eb179
+https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.3-h7f98852_0.tar.bz2#be93aabceefa2fac576e971aef407908
+https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.5-h516909a_1.tar.bz2#33f601066901f3e1a85af3522a8113f9
+https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae
+https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-7.5.0-h47867f9_33.tar.bz2#3a31c3f430a31184a5d07e67d3b24e2c
+https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-hf34092f_1004.tar.bz2#5582e1349bee4a25705adca745bf6845
+https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-7.5.0-h56cb351_20.tar.bz2#8f897b30195bd3a2251b4c51c3cc91cf
+https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-7.5.0-hd0bb8aa_20.tar.bz2#dbe78fc5fb9c339f8e55426559e12f7b
+https://conda.anaconda.org/conda-forge/linux-64/libllvm9-9.0.1-default_hc23dcda_7.tar.bz2#9f4686a2c319355fe8636ca13783c3b4
+https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904
+https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-14.0.4-he0ac6c6_0.tar.bz2#cecc6e3cb66570ffcfb820c637890f54
+https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa
+https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168
+https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_2.tar.bz2#4533821485cde83ab12ff3d8bda83768
+https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_2.tar.bz2#78c26dbb6e07d95ccc0eab8d4540aa0c
+https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.1.1-h516909a_0.tar.bz2#d98aa4948ec35f52907e2d6152e2b255
+https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-7.5.0-h78c8a43_33.tar.bz2#b2879010fb369f4012040f7a27657cd8
+https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-7.5.0-h555fc39_33.tar.bz2#5cf979793d2c5130a012cb6480867adc
+https://conda.anaconda.org/conda-forge/linux-64/libclang-9.0.1-default_hb4e5071_5.tar.bz2#9dde69aa2a8ecd575a16e44987bdc9f7
+https://conda.anaconda.org/conda-forge/linux-64/libglib-2.66.3-hbe7bbb4_0.tar.bz2#d5a09a9e981849b751cb75656b7302a0
+https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_3.tar.bz2#3e868978a04de8bf65a97bb86760f47a
+https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-hc85c160_1.tar.bz2#151f9fae3ab50f039c8735e47770aa2d
+https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.10-hee79883_0.tar.bz2#0217b0926808b1adf93247bba489d733
+https://conda.anaconda.org/conda-forge/linux-64/mkl-2020.4-h726a3e6_304.tar.bz2#b9b35a50e5377b19da6ec0709ae77fc3
+https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.1-h4ff8645_0.tar.bz2#6acda9d2a3ea84b58637b8f880bbf29b
+https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.1.1-hc9558a2_0.tar.bz2#1eb7c67eb11eab0c98a87f84174fdde1
+https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.1.1-he991be0_0.tar.bz2#e38ac82cc517b9e245c1ae99f9f140da
+https://conda.anaconda.org/conda-forge/linux-64/freetype-2.10.4-h0708190_1.tar.bz2#4a06f2ac2e5bfae7b6b245171c3f07aa
+https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f
+https://conda.anaconda.org/conda-forge/linux-64/libblas-3.8.0-20_mkl.tar.bz2#8fbce60932c01d0e193a1a814f2002be
+https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.3-h522a892_0.tar.bz2#96e218d06394a4e4d77028cf70162a09
+https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc
+https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.4.0-hb52868f_1.tar.bz2#b7ad78ad2e9ee155f59e6428406ee824
+https://conda.anaconda.org/conda-forge/linux-64/python-3.8.6-h852b56e_0_cpython.tar.bz2#dd65401dfb61ac030edc0dc4d15c2c51
+https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0
+https://conda.anaconda.org/conda-forge/noarch/attrs-21.4.0-pyhd8ed1ab_0.tar.bz2#f70280205d7044c8b8358c8de3190e5d
+https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.0-pyhd8ed1ab_0.tar.bz2#abc0453b6e7bfbb87d275d58e333fc98
+https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.1.0-pyhd8ed1ab_0.tar.bz2#f7551a8a008dfad2b7ac9662dd124614
+https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.5-pyhd8ed1ab_0.tar.bz2#c267da48ce208905d7d976d49dfd9433
+https://conda.anaconda.org/conda-forge/linux-64/compilers-1.1.1-0.tar.bz2#1ba267e19dbaf3db9dd0404e6fb9cdb9
+https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb
+https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2
+https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89
+https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.5.0-pyhd8ed1ab_0.tar.bz2#db4ffc615663c66a9cc0869ce4d1092b
+https://conda.anaconda.org/conda-forge/linux-64/glib-2.66.3-h58526e2_0.tar.bz2#62c2e5c84f6cdc7ded2307ef9c30dc8c
+https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd
+https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352
+https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905
+https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.8.0-20_mkl.tar.bz2#14b25490fdcc44e879ac6c10fe764f68
+https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.8.0-20_mkl.tar.bz2#52c0ae3606eeae7e1d493f37f336f4f5
+https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4
+https://conda.anaconda.org/conda-forge/noarch/networkx-2.8.5-pyhd8ed1ab_0.tar.bz2#4359cc8df0609a2b88aec55d6d2fedaf
+https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054
+https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff
+https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc
+https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.8-2_cp38.tar.bz2#bfbb29d517281e78ac53e48d21e6e860
+https://conda.anaconda.org/conda-forge/noarch/pytz-2022.1-pyhd8ed1ab_0.tar.bz2#b87d66d6d3991d988fb31510c95a9267
+https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2
+https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e
+https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-1.0.2-py_0.tar.bz2#20b2eaeaeea4ef9a9a0d99770620fd09
+https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-devhelp-1.0.2-py_0.tar.bz2#68e01cac9d38d0e717cd5c87bc3d2cc9
+https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.0.0-pyhd8ed1ab_0.tar.bz2#77dad82eb9c8c1525ff7953e0756d708
+https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jsmath-1.0.1-py_0.tar.bz2#67cd9d9c0382d37479b4d306c369a2d4
+https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-1.0.3-py_0.tar.bz2#d01180388e6d1838c3e1ad029590aa7a
+https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1.5-pyhd8ed1ab_2.tar.bz2#9ff55a0901cf952f05c654394de76bf7
+https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.1.0-pyh8a188c0_0.tar.bz2#a2995ee828f65687ac5b1e71a2ab1e0c
+https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96
+https://conda.anaconda.org/conda-forge/noarch/toolz-0.12.0-pyhd8ed1ab_0.tar.bz2#92facfec94bc02d6ccf42e7173831a36
+https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022
+https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2#72f1c6d03109d7a70087bc1d029a8eda
+https://conda.anaconda.org/conda-forge/linux-64/certifi-2022.6.15-py38h578d9bd_0.tar.bz2#1f4339b25d1030cfbf4ee0b06690bbce
+https://conda.anaconda.org/conda-forge/linux-64/cffi-1.14.4-py38ha312104_0.tar.bz2#8f82b87522fbb1d4b24e8b5e2b1d0501
+https://conda.anaconda.org/conda-forge/linux-64/cython-0.29.24-py38h709712a_1.tar.bz2#9e5fe389471a13ae523ae980de4ad1f4
+https://conda.anaconda.org/conda-forge/linux-64/cytoolz-0.12.0-py38h0a891b7_0.tar.bz2#37285e493f65bd5dbde90d6dbfe39bee
+https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-hfdff14a_1.tar.bz2#4caaca6356992ee545080c7d7193b5a3
+https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py38h578d9bd_2.tar.bz2#affd6b87adb2b0c98da0e3ad274349be
+https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.14.5-h36ae1b5_2.tar.bz2#00084ab2657be5bf0ba0757ccde797ef
+https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py38h43d8883_0.tar.bz2#ae54c61918e1cbd280b8587ed6219258
+https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.8.0-20_mkl.tar.bz2#8274dc30518af9df1de47f5d9e73165c
+https://conda.anaconda.org/conda-forge/linux-64/markupsafe-1.1.1-py38h0a891b7_4.tar.bz2#d182e0c60439427453ed4a7abd28ef0d
+https://conda.anaconda.org/conda-forge/linux-64/numpy-1.17.3-py38h95a1406_0.tar.bz2#bc0cbf611fe2f86eab29b98e51404f5e
+https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85
+https://conda.anaconda.org/conda-forge/noarch/partd-1.2.0-pyhd8ed1ab_0.tar.bz2#0c32f563d7f22e3a34c95cad8cc95651
+https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py38h0ee0e06_0.tar.bz2#7ef61f9084bda76b2f7a668ec5d1499a
+https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py38h578d9bd_3.tar.bz2#6ce4ce3d4490a56eb33b52c179609193
+https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.1-py38h0a891b7_0.tar.bz2#e3908bd184030e7f4a3d837959ebf6d7
+https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py38h578d9bd_5.tar.bz2#11113c7e50bb81f30762fe8325f305e1
+https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984
+https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h0a891b7_4.tar.bz2#ba24ff01bb38c5cd5be54b45ef685db3
+https://conda.anaconda.org/conda-forge/linux-64/setuptools-59.8.0-py38h578d9bd_1.tar.bz2#da023e4a9c777abc28434d7a6473dcc2
+https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py38h0a891b7_0.tar.bz2#acd276486a0067bee3098590f0952a0f
+https://conda.anaconda.org/conda-forge/linux-64/blas-2.20-mkl.tar.bz2#e7d09a07f5413e53dca5282b8fa50bed
+https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1004.tar.bz2#9fcaaca218dcfeb8da806d4fd4824aa0
+https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py38h2b5fc30_0.tar.bz2#28e9acd6f13ed29f27d5550a1cf0554b
+https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.7.0-pyhd8ed1ab_0.tar.bz2#3b533fc35efb54900c9e4ab06242f8b5
+https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.14.5-h0935bb2_2.tar.bz2#eb125ee86480e00a4a1ed45a577c3311
+https://conda.anaconda.org/conda-forge/noarch/imageio-2.19.3-pyhcf75d05_0.tar.bz2#9a5e536d761271c400310ec5dff8c5e1
+https://conda.anaconda.org/conda-forge/noarch/jinja2-2.11.3-pyhd8ed1ab_2.tar.bz2#bdedf6199eec03402a0c5db1f25e891e
+https://conda.anaconda.org/conda-forge/noarch/joblib-1.1.0-pyhd8ed1ab_0.tar.bz2#07d1b5c8cde14d95998fd4767e1e62d2
+https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.1.2-py38h250f245_1.tar.bz2#0ae46309d21c964547792bac48162fc8
+https://conda.anaconda.org/conda-forge/noarch/memory_profiler-0.60.0-pyhd8ed1ab_0.tar.bz2#f769ad93cd67c6eb7e932c255ed7d642
+https://conda.anaconda.org/conda-forge/linux-64/pandas-1.0.5-py38hcb8c335_0.tar.bz2#1e1b4382170fd26cf722ef008ffb651e
+https://conda.anaconda.org/conda-forge/noarch/pip-22.1.2-pyhd8ed1ab_0.tar.bz2#d29185c662a424f8bea1103270b85c96
+https://conda.anaconda.org/conda-forge/noarch/pygments-2.12.0-pyhd8ed1ab_0.tar.bz2#cb27e2ded147e5bcc7eafc1c6d343cb3
+https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py38h578d9bd_0.tar.bz2#626d2b8f96c8c3d20198e6bd84d1cfb7
+https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.1.1-py38h5c078b8_3.tar.bz2#dafeef887e68bd18ec84681747ca0fd5
+https://conda.anaconda.org/conda-forge/linux-64/scipy-1.3.2-py38h921218d_0.tar.bz2#278670dc2fef5a6309d1635f047bd456
+https://conda.anaconda.org/conda-forge/noarch/patsy-0.5.2-pyhd8ed1ab_0.tar.bz2#2e4e8be763551f60bbfcc22b650e5d49
+https://conda.anaconda.org/conda-forge/linux-64/pyamg-4.0.0-py38hf6732f7_1003.tar.bz2#44e00bf7a4b6a564e9313181aaea2615
+https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44
+https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f
+https://conda.anaconda.org/conda-forge/linux-64/qt-5.12.5-hd8c4c69_1.tar.bz2#0e105d4afe0c3c81c4fbd9937ec4f359
+https://conda.anaconda.org/conda-forge/linux-64/scikit-image-0.16.2-py38hb3f55d8_0.tar.bz2#468b398fefac8884cd6e6513af66549b
+https://conda.anaconda.org/conda-forge/noarch/seaborn-base-0.11.2-pyhd8ed1ab_0.tar.bz2#fe2303dc8f1febeb82d927ce8ad153ed
+https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.12.3-py38ha8c2ead_3.tar.bz2#242c206b0c30fdc4c18aea16f04c4262
+https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e
+https://conda.anaconda.org/conda-forge/linux-64/statsmodels-0.12.2-py38h5c078b8_0.tar.bz2#33787719ad03d33cffc4e2e3ea82bc9e
+https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.10-pyhd8ed1ab_0.tar.bz2#14f22c5b9cfd0d93c2806faaa3fe6dec
+https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.1.2-py38_1.tar.bz2#c2b9671a19c01716c37fe0a0e18b0aec
+https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_0.tar.bz2#70d6e72856de9551f83ae0f2de689a7a
+https://conda.anaconda.org/conda-forge/noarch/seaborn-0.11.2-hd8ed1ab_0.tar.bz2#e56b6a19f4b717eca7c68ad78196b075
+https://conda.anaconda.org/conda-forge/noarch/sphinx-4.0.1-pyh6c4a22f_2.tar.bz2#c203dcc46f262853ecbb9552c50d664e
+https://conda.anaconda.org/conda-forge/noarch/numpydoc-1.2-pyhd8ed1ab_0.tar.bz2#025ad7ca2c7f65007ab6b6f5d93a56eb
+https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.7.0-py_0.tar.bz2#80bad3f857ecc86a4ab73f3e57addd13
+https://conda.anaconda.org/conda-forge/noarch/sphinx-prompt-1.3.0-py_0.tar.bz2#9363002e2a134a287af4e32ff0f26cdc
+# pip sphinxext-opengraph @ https://files.pythonhosted.org/packages/50/ac/c105ed3e0a00b14b28c0aa630935af858fd8a32affeff19574b16e2c6ae8/sphinxext_opengraph-0.4.2-py3-none-any.whl#md5=None
diff --git a/build_tools/github/trigger_hosting.sh b/build_tools/github/trigger_hosting.sh
new file mode 100755
index 0000000000000..2a8e28ff164ff
--- /dev/null
+++ b/build_tools/github/trigger_hosting.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+
+set -e
+set -x
+
+GITHUB_RUN_URL=https://nightly.link/$GITHUB_REPOSITORY/actions/runs/$RUN_ID
+
+if [ "$EVENT" == pull_request ]
+then
+ PULL_REQUEST_NUMBER=$(curl \
+ -H "Accept: application/vnd.github.v3+json" \
+ -H "Authorization: token $GITHUB_TOKEN" \
+ https://api.github.com/repos/$REPO_NAME/commits/$COMMIT_SHA/pulls 2>/dev/null \
+ | jq '.[0].number')
+
+ if [[ "$PULL_REQUEST_NUMBER" == "null" ]]; then
+ # The pull request is on the main (default) branch of the fork. The above API
+ # call is unable to get the PR number associated with the commit:
+ # https://docs.github.com/en/rest/commits/commits#list-pull-requests-associated-with-a-commit
+ # We fallback to the search API here. The search API is not used everytime
+ # because it has a lower rate limit.
+ PULL_REQUEST_NUMBER=$(curl \
+ -H "Accept: application/vnd.github+json" \
+ -H "Authorization: token $GITHUB_TOKEN" \
+ "https://api.github.com/search/issues?q=$COMMIT_SHA+repo:$GITHUB_REPOSITORY" 2>/dev/null \
+ | jq '.items[0].number')
+ fi
+
+ BRANCH=pull/$PULL_REQUEST_NUMBER/head
+else
+ BRANCH=$HEAD_BRANCH
+fi
+
+curl --request POST \
+ --url https://circleci.com/api/v2/project/gh/$GITHUB_REPOSITORY/pipeline \
+ --header "Circle-Token: $CIRCLE_CI_TOKEN" \
+ --header "content-type: application/json" \
+ --header "x-attribution-actor-id: github_actions" \
+ --header "x-attribution-login: github_actions" \
+ --data \{\"branch\":\"$BRANCH\",\"parameters\":\{\"GITHUB_RUN_URL\":\"$GITHUB_RUN_URL\"\}\}
diff --git a/build_tools/travis/test_script.sh b/build_tools/travis/test_script.sh
index cb5a3dbfeed33..1551ed858d1a1 100755
--- a/build_tools/travis/test_script.sh
+++ b/build_tools/travis/test_script.sh
@@ -33,7 +33,7 @@ if [[ $TRAVIS_CPU_ARCH == arm64 ]]; then
fi
if [[ -n $CHECK_WARNINGS ]]; then
- TEST_CMD="$TEST_CMD -Werror::DeprecationWarning -Werror::FutureWarning"
+ TEST_CMD="$TEST_CMD -Werror::DeprecationWarning -Werror::FutureWarning -Werror::numpy.VisibleDeprecationWarning"
fi
$TEST_CMD sklearn
diff --git a/build_tools/update_environments_and_lock_files.py b/build_tools/update_environments_and_lock_files.py
new file mode 100644
index 0000000000000..be44ac6983309
--- /dev/null
+++ b/build_tools/update_environments_and_lock_files.py
@@ -0,0 +1,551 @@
+"""Script to update CI environment files and associated lock files.
+
+To run it you need to be in the root folder of the scikit-learn repo:
+python build_tools/update_environments_and_lock_files.py
+
+Two scenarios where this script can be useful:
+- make sure that the latest versions of all the dependencies are used in the CI.
+ We can run this script regularly and open a PR with the changes to the lock
+ files. This workflow will eventually be automated with a bot in the future.
+- bump minimum dependencies in sklearn/_min_dependencies.py. Running this
+ script will update both the CI environment files and associated lock files.
+ You can then open a PR with the changes.
+- pin some packages to an older version by adding them to the
+ default_package_constraints variable. This is useful when regressions are
+ introduced in our dependencies, this has happened for example with pytest 7
+ and coverage 6.3.
+
+Environments are conda environment.yml or pip requirements.txt. Lock files are
+conda-lock lock files or pip-compile requirements.txt.
+
+pip requirements.txt are used when we install some dependencies (e.g. numpy and
+scipy) with apt-get and the rest of the dependencies (e.g. pytest and joblib)
+with pip.
+
+To run this script you need:
+- conda-lock. The version should match the one used in the CI in
+ sklearn/_min_dependencies.py
+- pip-tools
+
+"""
+
+import re
+import subprocess
+import sys
+from pathlib import Path
+import shlex
+import json
+import logging
+
+import click
+
+from jinja2 import Environment
+
+logger = logging.getLogger(__name__)
+logger.setLevel(logging.INFO)
+handler = logging.StreamHandler()
+logger.addHandler(handler)
+
+
+common_dependencies_without_coverage = [
+ "python",
+ "numpy",
+ "blas",
+ "scipy",
+ "cython",
+ "joblib",
+ "threadpoolctl",
+ "matplotlib",
+ "pandas",
+ "pyamg",
+ "pytest",
+ "pytest-xdist",
+ "pillow",
+]
+
+common_dependencies = common_dependencies_without_coverage + [
+ "codecov",
+ "pytest-cov",
+ "coverage",
+]
+
+docstring_test_dependencies = ["sphinx", "numpydoc"]
+
+default_package_constraints = {}
+
+
+def remove_from(alist, to_remove):
+ return [each for each in alist if each not in to_remove]
+
+
+conda_build_metadata_list = [
+ {
+ "build_name": "pylatest_conda_forge_mkl_linux-64",
+ "folder": "build_tools/azure",
+ "platform": "linux-64",
+ "channel": "conda-forge",
+ "conda_dependencies": common_dependencies + ["ccache"],
+ "package_constraints": {
+ "blas": "[build=mkl]",
+ },
+ },
+ {
+ "build_name": "pylatest_conda_forge_mkl_osx-64",
+ "folder": "build_tools/azure",
+ "platform": "osx-64",
+ "channel": "conda-forge",
+ "conda_dependencies": common_dependencies
+ + ["ccache", "compilers", "llvm-openmp"],
+ "package_constraints": {
+ "blas": "[build=mkl]",
+ },
+ },
+ {
+ "build_name": "pylatest_conda_mkl_no_openmp",
+ "folder": "build_tools/azure",
+ "platform": "osx-64",
+ "channel": "defaults",
+ "conda_dependencies": common_dependencies + ["ccache"],
+ "package_constraints": {
+ "blas": "[build=mkl]",
+ # XXX: coverage is temporary pinned to 6.2 because 6.3 is not
+ # fork-safe and 6.4 is not available yet (July 2022) in conda
+ # defaults channel. For more details, see:
+ # https://github.com/nedbat/coveragepy/issues/1310
+ "coverage": "6.2",
+ },
+ },
+ {
+ "build_name": "pylatest_conda_forge_mkl_no_coverage",
+ "folder": "build_tools/azure",
+ "platform": "linux-64",
+ "channel": "conda-forge",
+ "conda_dependencies": common_dependencies_without_coverage + ["ccache"],
+ "package_constraints": {
+ "blas": "[build=mkl]",
+ },
+ },
+ {
+ "build_name": "py38_conda_defaults_openblas",
+ "folder": "build_tools/azure",
+ "platform": "linux-64",
+ "channel": "defaults",
+ "conda_dependencies": common_dependencies + ["ccache"],
+ "package_constraints": {
+ "python": "3.8",
+ "blas": "[build=openblas]",
+ "numpy": "min",
+ "scipy": "min",
+ "matplotlib": "min",
+ "threadpoolctl": "2.2.0",
+ # XXX: coverage is temporary pinned to 6.2 because 6.3 is not
+ # fork-safe and 6.4 is not available yet (July 2022) in conda
+ # defaults channel. For more details, see:
+ # https://github.com/nedbat/coveragepy/issues/1310
+ "coverage": "6.2",
+ },
+ },
+ {
+ "build_name": "py38_conda_forge_openblas_ubuntu_1804",
+ "folder": "build_tools/azure",
+ "platform": "linux-64",
+ "channel": "conda-forge",
+ "conda_dependencies": common_dependencies_without_coverage + ["ccache"],
+ "package_constraints": {"python": "3.8", "blas": "[build=openblas]"},
+ },
+ {
+ "build_name": "pylatest_pip_openblas_pandas",
+ "folder": "build_tools/azure",
+ "platform": "linux-64",
+ "channel": "defaults",
+ "conda_dependencies": ["python", "ccache"],
+ "pip_dependencies": remove_from(common_dependencies, ["python", "blas"])
+ + docstring_test_dependencies
+ + ["lightgbm", "scikit-image"],
+ "package_constraints": {"python": "3.9"},
+ },
+ {
+ "build_name": "pylatest_pip_scipy_dev",
+ "folder": "build_tools/azure",
+ "platform": "linux-64",
+ "channel": "defaults",
+ "conda_dependencies": ["python", "ccache"],
+ "pip_dependencies": remove_from(
+ common_dependencies,
+ [
+ "python",
+ "blas",
+ "matplotlib",
+ "pyamg",
+ # all the dependencies below have a development version
+ # installed in the CI, so they can be removed from the
+ # environment.yml
+ "numpy",
+ "scipy",
+ "pandas",
+ "cython",
+ "joblib",
+ "pillow",
+ ],
+ )
+ + docstring_test_dependencies
+ # python-dateutil is a dependency of pandas and pandas is removed from
+ # the environment.yml. Adding python-dateutil so it is pinned
+ + ["python-dateutil"],
+ },
+ {
+ "build_name": "pypy3",
+ "folder": "build_tools/azure",
+ "platform": "linux-64",
+ "channel": "conda-forge",
+ "conda_dependencies": ["pypy"]
+ + remove_from(
+ common_dependencies_without_coverage, ["python", "pandas", "pillow"]
+ )
+ + ["ccache"],
+ "package_constraints": {"blas": "[build=openblas]"},
+ },
+ {
+ "build_name": "py38_conda_forge_mkl",
+ "folder": "build_tools/azure",
+ "platform": "win-64",
+ "channel": "conda-forge",
+ "conda_dependencies": remove_from(common_dependencies, ["pandas", "pyamg"])
+ + ["wheel", "pip"],
+ "package_constraints": {"python": "3.8", "blas": "[build=mkl]"},
+ },
+ {
+ "build_name": "doc_min_dependencies",
+ "folder": "build_tools/github",
+ "platform": "linux-64",
+ "channel": "conda-forge",
+ "conda_dependencies": common_dependencies_without_coverage
+ + [
+ "scikit-image",
+ "seaborn",
+ "memory_profiler",
+ "compilers",
+ "sphinx",
+ "sphinx-gallery",
+ "numpydoc",
+ "sphinx-prompt",
+ ],
+ "pip_dependencies": ["sphinxext-opengraph"],
+ "package_constraints": {
+ "python": "3.8",
+ "numpy": "min",
+ "scipy": "min",
+ "matplotlib": "min",
+ "cython": "min",
+ "scikit-image": "min",
+ "sphinx": "min",
+ "pandas": "min",
+ "sphinx-gallery": "min",
+ "numpydoc": "min",
+ "sphinx-prompt": "min",
+ "sphinxext-opengraph": "min",
+ },
+ },
+ {
+ "build_name": "doc",
+ "folder": "build_tools/github",
+ "platform": "linux-64",
+ "channel": "conda-forge",
+ "conda_dependencies": common_dependencies_without_coverage
+ + [
+ "scikit-image",
+ "seaborn",
+ "memory_profiler",
+ "compilers",
+ "sphinx",
+ "sphinx-gallery",
+ "numpydoc",
+ "sphinx-prompt",
+ ],
+ "pip_dependencies": ["sphinxext-opengraph"],
+ "package_constraints": {
+ "python": "3.9",
+ },
+ },
+ {
+ "build_name": "py39_conda_forge",
+ "folder": "build_tools/circle",
+ "platform": "linux-aarch64",
+ "channel": "conda-forge",
+ "conda_dependencies": remove_from(
+ common_dependencies_without_coverage, ["pandas", "pyamg"]
+ )
+ + ["pip", "ccache"],
+ "package_constraints": {
+ "python": "3.9",
+ },
+ },
+]
+
+
+pip_build_metadata_list = [
+ {
+ "build_name": "debian_atlas_32bit",
+ "folder": "build_tools/azure",
+ "pip_dependencies": ["cython", "joblib", "threadpoolctl", "pytest"],
+ "package_constraints": {
+ "joblib": "min",
+ "threadpoolctl": "2.2.0",
+ "pytest": "min",
+ # no pytest-xdist because it causes issue on 32bit
+ },
+ # same Python version as in debian-32 build
+ "python_version": "3.9.2",
+ },
+ {
+ "build_name": "ubuntu_atlas",
+ "folder": "build_tools/azure",
+ "pip_dependencies": [
+ "cython",
+ "joblib",
+ "threadpoolctl",
+ "pytest",
+ "pytest-xdist",
+ ],
+ "package_constraints": {"joblib": "min", "threadpoolctl": "min"},
+ # Ubuntu 20.04 has 3.8.2 but only 3.8.5 is available for osx-arm64 on
+ # conda-forge. Chosing 3.8.5 so that this script can be run locally on
+ # osx-arm64 machines. This should not matter for pining versions with
+ # pip-compile
+ "python_version": "3.8.5",
+ },
+ {
+ "build_name": "py38_pip_openblas_32bit",
+ "folder": "build_tools/azure",
+ "pip_dependencies": [
+ "numpy",
+ "scipy",
+ "cython",
+ "joblib",
+ "threadpoolctl",
+ "pytest",
+ "pytest-xdist",
+ "pillow",
+ "wheel",
+ ],
+ # The Windows 32bit build use 3.8.10. No cross-compilation support for
+ # pip-compile, we are going to assume the pip lock file on a Linux
+ # 64bit machine gives appropriate versions
+ "python_version": "3.8.10",
+ },
+]
+
+
+def execute_command(command_list):
+ proc = subprocess.Popen(
+ command_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE
+ )
+
+ out, err = proc.communicate()
+ out, err = out.decode(), err.decode()
+
+ if proc.returncode != 0:
+ command_str = " ".join(command_list)
+ raise RuntimeError(
+ "Command exited with non-zero exit code.\n"
+ "Exit code: {}\n"
+ "Command:\n{}\n"
+ "stdout:\n{}\n"
+ "stderr:\n{}\n".format(proc.returncode, command_str, out, err)
+ )
+ return out
+
+
+def get_package_with_constraint(package_name, build_metadata, uses_pip=False):
+ build_package_constraints = build_metadata.get("package_constraints")
+ if build_package_constraints is None:
+ constraint = None
+ else:
+ constraint = build_package_constraints.get(package_name)
+
+ constraint = constraint or default_package_constraints.get(package_name)
+
+ if constraint is None:
+ return package_name
+
+ comment = ""
+ if constraint == "min":
+ constraint = execute_command(
+ [sys.executable, "sklearn/_min_dependencies.py", package_name]
+ ).strip()
+ comment = " # min"
+
+ if re.match(r"\d[.\d]*", constraint):
+ equality = "==" if uses_pip else "="
+ constraint = equality + constraint
+
+ return f"{package_name}{constraint}{comment}"
+
+
+environment = Environment(trim_blocks=True, lstrip_blocks=True)
+environment.filters["get_package_with_constraint"] = get_package_with_constraint
+
+
+def get_conda_environment_content(build_metadata):
+ template = environment.from_string(
+ """
+# DO NOT EDIT: this file is generated from the specification found in the
+# following script to centralize the configuration for CI builds:
+# build_tools/update_environments_and_lock_files.py
+channels:
+ - {{ build_metadata['channel'] }}
+dependencies:
+ {% for conda_dep in build_metadata['conda_dependencies'] %}
+ - {{ conda_dep | get_package_with_constraint(build_metadata) }}
+ {% endfor %}
+ {% if build_metadata['pip_dependencies'] %}
+ - pip
+ - pip:
+ {% for pip_dep in build_metadata.get('pip_dependencies', []) %}
+ - {{ pip_dep | get_package_with_constraint(build_metadata, uses_pip=True) }}
+ {% endfor %}
+ {% endif %}""".strip()
+ )
+ return template.render(build_metadata=build_metadata)
+
+
+def write_conda_environment(build_metadata):
+ content = get_conda_environment_content(build_metadata)
+ build_name = build_metadata["build_name"]
+ folder_path = Path(build_metadata["folder"])
+ output_path = folder_path / f"{build_name}_environment.yml"
+ output_path.write_text(content)
+
+
+def write_all_conda_environments(build_metadata_list):
+ for build_metadata in build_metadata_list:
+ write_conda_environment(build_metadata)
+
+
+def conda_lock(environment_path, lock_file_path, platform):
+ command = (
+ f"conda-lock lock --mamba --kind explicit --platform {platform} "
+ f"--file {environment_path} --filename-template {lock_file_path}"
+ )
+
+ logger.debug("conda-lock command: %s", command)
+ execute_command(shlex.split(command))
+
+
+def create_conda_lock_file(build_metadata):
+ build_name = build_metadata["build_name"]
+ folder_path = Path(build_metadata["folder"])
+ environment_path = folder_path / f"{build_name}_environment.yml"
+ platform = build_metadata["platform"]
+ lock_file_basename = build_name
+ if not lock_file_basename.endswith(platform):
+ lock_file_basename = f"{lock_file_basename}_{platform}"
+
+ lock_file_path = folder_path / f"{lock_file_basename}_conda.lock"
+ conda_lock(environment_path, lock_file_path, platform)
+
+
+def write_all_conda_lock_files(build_metadata_list):
+ for build_metadata in build_metadata_list:
+ logger.info(build_metadata["build_name"])
+ create_conda_lock_file(build_metadata)
+
+
+def get_pip_requirements_content(build_metadata):
+ template = environment.from_string(
+ """
+# DO NOT EDIT: this file is generated from the specification found in the
+# following script to centralize the configuration for CI builds:
+# build_tools/update_environments_and_lock_files.py
+{% for pip_dep in build_metadata['pip_dependencies'] %}
+{{ pip_dep | get_package_with_constraint(build_metadata, uses_pip=True) }}
+{% endfor %}""".strip()
+ )
+ return template.render(build_metadata=build_metadata)
+
+
+def write_pip_requirements(build_metadata):
+ build_name = build_metadata["build_name"]
+ content = get_pip_requirements_content(build_metadata)
+ folder_path = Path(build_metadata["folder"])
+ output_path = folder_path / f"{build_name}_requirements.txt"
+ output_path.write_text(content)
+
+
+def write_all_pip_requirements(build_metadata_list):
+ for build_metadata in build_metadata_list:
+ logger.info(build_metadata["build_name"])
+ write_pip_requirements(build_metadata)
+
+
+def pip_compile(pip_compile_path, requirements_path, lock_file_path):
+ command = f"{pip_compile_path} --upgrade {requirements_path} -o {lock_file_path}"
+
+ logger.debug("pip-compile command: %s", command)
+ execute_command(shlex.split(command))
+
+
+def write_pip_lock_file(build_metadata):
+ build_name = build_metadata["build_name"]
+ python_version = build_metadata["python_version"]
+ environment_name = f"pip-tools-python{python_version}"
+ # To make sure that the Python used to create the pip lock file is the same
+ # as the one used during the CI build where the lock file is used, we first
+ # create a conda environment with the correct Python version and
+ # pip-compile and run pip-compile in this environment
+
+ command = (
+ "conda create -c conda-forge -n"
+ f" pip-tools-python{python_version} python={python_version} pip-tools -y"
+ )
+ execute_command(shlex.split(command))
+
+ json_output = execute_command(shlex.split("conda info --json"))
+ conda_info = json.loads(json_output)
+ environment_folder = [
+ each for each in conda_info["envs"] if each.endswith(environment_name)
+ ][0]
+ environment_path = Path(environment_folder)
+ pip_compile_path = environment_path / "bin" / "pip-compile"
+
+ folder_path = Path(build_metadata["folder"])
+ requirement_path = folder_path / f"{build_name}_requirements.txt"
+ lock_file_path = folder_path / f"{build_name}_lock.txt"
+ pip_compile(pip_compile_path, requirement_path, lock_file_path)
+
+
+def write_all_pip_lock_files(build_metadata_list):
+ for build_metadata in build_metadata_list:
+ write_pip_lock_file(build_metadata)
+
+
+@click.command()
+@click.option(
+ "--select-build",
+ default="",
+ help="Regex to restrict the builds we want to update environment and lock files",
+)
+def main(select_build):
+ filtered_conda_build_metadata_list = [
+ each
+ for each in conda_build_metadata_list
+ if re.search(select_build, each["build_name"])
+ ]
+ logger.info("Writing conda environments")
+ write_all_conda_environments(filtered_conda_build_metadata_list)
+ logger.info("Writing conda lock files")
+ write_all_conda_lock_files(filtered_conda_build_metadata_list)
+
+ filtered_pip_build_metadata_list = [
+ each
+ for each in pip_build_metadata_list
+ if re.search(select_build, each["build_name"])
+ ]
+ logger.info("Writing pip requirements")
+ write_all_pip_requirements(filtered_pip_build_metadata_list)
+ logger.info("Writing pip lock files")
+ write_all_pip_lock_files(filtered_pip_build_metadata_list)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/doc/Makefile b/doc/Makefile
index 6146d11123017..4fdec654ca23f 100644
--- a/doc/Makefile
+++ b/doc/Makefile
@@ -2,10 +2,18 @@
#
# You can set these variables from the command line.
-SPHINXOPTS = -j auto
+SPHINXOPTS =
SPHINXBUILD ?= sphinx-build
PAPER =
BUILDDIR = _build
+
+# Disable multiple jobs on OSX
+ifeq ($(shell uname), Darwin)
+ SPHINX_NUMJOBS = 1
+else
+ SPHINX_NUMJOBS = auto
+endif
+
ifneq ($(EXAMPLES_PATTERN),)
EXAMPLES_PATTERN_OPTS := -D sphinx_gallery_conf.filename_pattern="$(EXAMPLES_PATTERN)"
endif
@@ -14,7 +22,7 @@ endif
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -T -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS)\
- $(EXAMPLES_PATTERN_OPTS) .
+ -j$(SPHINX_NUMJOBS) $(EXAMPLES_PATTERN_OPTS) .
.PHONY: help clean html dirhtml ziphtml pickle json latex latexpdf changes linkcheck doctest optipng
diff --git a/doc/about.rst b/doc/about.rst
index fe10db4939f16..2496c1afa0ed3 100644
--- a/doc/about.rst
+++ b/doc/about.rst
@@ -410,7 +410,7 @@ full-time. It also hosts coding sprints and other events.
`Paris-Saclay Center for Data Science
-`_
+`_
funded one year for a developer to work on the project full-time
(2014-2015), 50% of the time of Guillaume Lemaitre (2016-2017) and 50% of the
time of Joris van den Bossche (2017-2018).
@@ -423,7 +423,7 @@ time of Joris van den Bossche (2017-2018).
.. image:: images/cds-logo.png
:width: 100pt
:align: center
- :target: https://www.datascience-paris-saclay.fr/
+ :target: http://www.datascience-paris-saclay.fr/
.. raw:: html
@@ -574,7 +574,7 @@ The 2013 International Paris Sprint was made possible thanks to the support of
`Télécom Paristech `_, `tinyclues
`_, the `French Python Association
`_ and the `Fonds de la Recherche Scientifique
-`_.
+`_.
..............
@@ -586,9 +586,15 @@ Donating to the project
.......................
If you are interested in donating to the project or to one of our code-sprints,
-you can use the *Paypal* button below or the `NumFOCUS Donations Page
-`_ (if you use the latter,
-please indicate that you are donating for the scikit-learn project).
+please donate via the `NumFOCUS Donations Page
+`_.
+
+.. raw :: html
+
+
+
All donations will be handled by `NumFOCUS
`_, a non-profit-organization which is
@@ -602,13 +608,6 @@ The received donations for the scikit-learn project mostly will go towards
covering travel-expenses for code sprints, as well as towards the organization
budget of the project [#f1]_.
-.. raw :: html
-
-
-
-
.. rubric:: Notes
diff --git a/doc/computing/computational_performance.rst b/doc/computing/computational_performance.rst
index ceb0a0af2e66c..a7fc692fbaaa7 100644
--- a/doc/computing/computational_performance.rst
+++ b/doc/computing/computational_performance.rst
@@ -290,7 +290,7 @@ Optimized BLAS / LAPACK implementations include:
- MKL
- Apple Accelerate and vecLib frameworks (OSX only)
-More information can be found on the `Scipy install page `_
+More information can be found on the `NumPy install page `_
and in this
`blog post `_
from Daniel Nouri which has some nice step by step install instructions for
diff --git a/doc/computing/parallelism.rst b/doc/computing/parallelism.rst
index e3fe6ac386897..382fa8938b5ca 100644
--- a/doc/computing/parallelism.rst
+++ b/doc/computing/parallelism.rst
@@ -152,7 +152,7 @@ Note that:
You will find additional details about joblib mitigation of oversubscription
in `joblib documentation
-`_.
+`_.
Configuration switches
diff --git a/doc/conf.py b/doc/conf.py
index 8276e8522f133..799ce4b74dd5c 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -15,7 +15,7 @@
import warnings
import re
from datetime import datetime
-from packaging.version import parse
+from sklearn.externals._packaging.version import parse
from pathlib import Path
from io import StringIO
@@ -525,9 +525,18 @@ def generate_min_dependency_substitutions(app):
issues_github_path = "scikit-learn/scikit-learn"
+def disable_plot_gallery_for_linkcheck(app):
+ if app.builder.name == "linkcheck":
+ sphinx_gallery_conf["plot_gallery"] = "False"
+
+
def setup(app):
+ # do not run the examples when using linkcheck by using a small priority
+ # (default priority is 500 and sphinx-gallery using builder-inited event too)
+ app.connect("builder-inited", disable_plot_gallery_for_linkcheck, priority=50)
app.connect("builder-inited", generate_min_dependency_table)
app.connect("builder-inited", generate_min_dependency_substitutions)
+
# to hide/show the prompt in code examples:
app.connect("build-finished", make_carousel_thumbs)
app.connect("build-finished", filter_search_index)
@@ -566,3 +575,70 @@ def setup(app):
ogp_image = "https://scikit-learn.org/stable/_static/scikit-learn-logo-small.png"
ogp_use_first_image = True
ogp_site_name = "scikit-learn"
+
+# Config for linkcheck that checks the documentation for broken links
+
+# ignore all links in 'whats_new' to avoid doing many github requests and
+# hitting the github rate threshold that makes linkcheck take a lot of time
+linkcheck_exclude_documents = [r"whats_new/.*"]
+
+# default timeout to make some sites links fail faster
+linkcheck_timeout = 10
+
+# Allow redirects from doi.org
+linkcheck_allowed_redirects = {r"https://doi.org/.+": r".*"}
+linkcheck_ignore = [
+ # ignore links to local html files e.g. in image directive :target: field
+ r"^..?/",
+ # ignore links to specific pdf pages because linkcheck does not handle them
+ # ('utf-8' codec can't decode byte error)
+ r"http://www.utstat.toronto.edu/~rsalakhu/sta4273/notes/Lecture2.pdf#page=.*",
+ "https://www.fordfoundation.org/media/2976/"
+ "roads-and-bridges-the-unseen-labor-behind-our-digital-infrastructure.pdf#page=.*",
+ # links falsely flagged as broken
+ "https://www.researchgate.net/publication/"
+ "233096619_A_Dendrite_Method_for_Cluster_Analysis",
+ "https://www.researchgate.net/publication/221114584_Random_Fourier_Approximations_"
+ "for_Skewed_Multiplicative_Histogram_Kernels",
+ "https://www.researchgate.net/publication/4974606_"
+ "Hedonic_housing_prices_and_the_demand_for_clean_air",
+ "https://www.researchgate.net/profile/Anh-Huy-Phan/publication/220241471_Fast_"
+ "Local_Algorithms_for_Large_Scale_Nonnegative_Matrix_and_Tensor_Factorizations",
+ "https://doi.org/10.13140/RG.2.2.35280.02565",
+ "https://www.microsoft.com/en-us/research/uploads/prod/2006/01/"
+ "Bishop-Pattern-Recognition-and-Machine-Learning-2006.pdf",
+ "https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/tr-99-87.pdf",
+ "https://microsoft.com/",
+ "https://www.jstor.org/stable/2984099",
+ "https://stat.uw.edu/sites/default/files/files/reports/2000/tr371.pdf",
+ # Broken links from testimonials
+ "http://www.bestofmedia.com",
+ "http://www.data-publica.com/",
+ "https://livelovely.com",
+ "https://www.mars.com/global",
+ "https://www.yhat.com",
+ # Ignore some dynamically created anchors. See
+ # https://github.com/sphinx-doc/sphinx/issues/9016 for more details about
+ # the github example
+ r"https://github.com/conda-forge/miniforge#miniforge",
+ r"https://stackoverflow.com/questions/5836335/"
+ "consistently-create-same-random-numpy-array/5837352#comment6712034_5837352",
+]
+
+# Use a browser-like user agent to avoid some "403 Client Error: Forbidden for
+# url" errors. This is taken from the variable navigator.userAgent inside a
+# browser console.
+user_agent = (
+ "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:100.0) Gecko/20100101 Firefox/100.0"
+)
+
+# Use Github token from environment variable to avoid Github rate limits when
+# checking Github links
+github_token = os.getenv("GITHUB_TOKEN")
+
+if github_token is None:
+ linkcheck_request_headers = {}
+else:
+ linkcheck_request_headers = {
+ "https://github.com/": {"Authorization": f"token {github_token}"},
+ }
diff --git a/doc/contributor_experience_team.rst b/doc/contributor_experience_team.rst
index 3308d74563350..0461deb762f11 100644
--- a/doc/contributor_experience_team.rst
+++ b/doc/contributor_experience_team.rst
@@ -37,4 +37,8 @@
Albert Thomas
-
\ No newline at end of file
+
+
+
Meekail Zain
+
+
diff --git a/doc/datasets/loading_other_datasets.rst b/doc/datasets/loading_other_datasets.rst
index b77bbd120aeb2..50997f7b44cd3 100644
--- a/doc/datasets/loading_other_datasets.rst
+++ b/doc/datasets/loading_other_datasets.rst
@@ -257,10 +257,10 @@ For some miscellaneous data such as images, videos, and audio, you may wish to
refer to:
* `skimage.io `_ or
- `Imageio `_
+ `Imageio `_
for loading images and videos into numpy arrays
* `scipy.io.wavfile.read
- `_
+ `_
for reading WAV files into a numpy array
Categorical (or nominal) features stored as strings (common in pandas DataFrames)
diff --git a/doc/developers/advanced_installation.rst b/doc/developers/advanced_installation.rst
index 89dc6e5267ded..658c1ff4c945d 100644
--- a/doc/developers/advanced_installation.rst
+++ b/doc/developers/advanced_installation.rst
@@ -177,11 +177,11 @@ each time you update the sources. Therefore it is recommended that you install
in with the ``pip install --no-build-isolation --editable .`` command, which
allows you to edit the code in-place. This builds the extension in place and
creates a link to the development directory (see `the pip docs
-`_).
+`_).
-This is fundamentally similar to using the command ``python setup.py develop``
-(see `the setuptool docs
-`_).
+As the doc aboves explains, this is fundamentally similar to using the command
+``python setup.py develop``. (see `the setuptool docs
+`_).
It is however preferred to use pip.
On Unix-like systems, you can equivalently type ``make in`` from the top-level
@@ -296,6 +296,12 @@ forge using the following command:
which should include ``compilers`` and ``llvm-openmp``.
+.. note::
+
+ If you installed these packages after creating and activating a new conda
+ environment, you will need to first deactivate and then reactivate the
+ environment for these changes to take effect.
+
The compilers meta-package will automatically set custom environment
variables:
diff --git a/doc/developers/bug_triaging.rst b/doc/developers/bug_triaging.rst
index 53333b8163ae4..80a0a74c1f3e5 100644
--- a/doc/developers/bug_triaging.rst
+++ b/doc/developers/bug_triaging.rst
@@ -101,7 +101,7 @@ The following workflow [1]_ is a good way to approach issue triaging:
#. Thank the reporter for opening an issue
- The issue tracker is many people’s first interaction with the
+ The issue tracker is many people's first interaction with the
scikit-learn project itself, beyond just using the library. As such,
we want it to be a welcoming, pleasant experience.
@@ -156,4 +156,4 @@ The following workflow [1]_ is a good way to approach issue triaging:
#. Remove the "Needs Triage" label from the issue if the label exists.
.. [1] Adapted from the pandas project `maintainers guide
- `_
+ `_
diff --git a/doc/developers/contributing.rst b/doc/developers/contributing.rst
index fbcec1871f2dd..c0598c60683cc 100644
--- a/doc/developers/contributing.rst
+++ b/doc/developers/contributing.rst
@@ -435,15 +435,11 @@ complies with the following rules before marking a PR as ``[MRG]``. The
`editor integration documentation `_
to configure your editor to run `black`.
-6. **Make sure that your PR does not add PEP8 violations**. To check the
- code that you changed, you can run the following command (see
- :ref:`above ` to set up the ``upstream`` remote):
+6. Run `flake8` to make sure you followed the project coding conventions.
.. prompt:: bash $
- git diff upstream/main -u -- "*.py" | flake8 --diff
-
- or `make flake8-diff` which should work on unix-like system.
+ flake8 .
7. Follow the :ref:`coding-guidelines`.
@@ -528,7 +524,7 @@ profiling and Cython optimizations.
For two very well documented and more detailed guides on development
workflow, please pay a visit to the `Scipy Development Workflow
- `_ -
+ `_ -
and the `Astropy Workflow for Developers
`_
sections.
@@ -552,6 +548,7 @@ message, the following actions are taken.
[cd build gh] CD is run only for GitHub Actions
[lint skip] Azure pipeline skips linting
[scipy-dev] Build & test with our dependencies (numpy, scipy, etc ...) development builds
+ [nogil] Build & test with the nogil experimental branches of CPython, Cython, NumPy, SciPy...
[icc-build] Build & test with the Intel C compiler (ICC)
[pypy] Build & test with PyPy
[doc skip] Docs are not built
@@ -857,12 +854,15 @@ Finally, follow the formatting rules below to make it consistently good:
.. _generated_doc_CI:
-Generated documentation on CircleCI
------------------------------------
+Generated documentation on GitHub Actions
+-----------------------------------------
+
+When you change the documentation in a pull request, GitHub Actions automatically
+builds it. To view the documentation generated by GitHub Actions, simply go at the
+bottom of your PR page and look for the "Check the rendered docs here!" link:
-When you change the documentation in a pull request, CircleCI automatically
-builds it. To view the documentation generated by CircleCI, simply go at the
-bottom of your PR page and look for the "ci/circleci: doc artifact" link.
+.. image:: ../images/generated-doc-ci.png
+ :align: center
.. _testing_coverage:
@@ -923,7 +923,7 @@ Monitoring performance
======================
*This section is heavily inspired from the* `pandas documentation
-`_.
+`_.
When proposing changes to the existing code base, it's important to make sure
that they don't introduce performance regressions. Scikit-learn uses
@@ -1306,7 +1306,7 @@ contributor to keep involved in the project. [1]_
- Every PR, good or bad, is an act of generosity. Opening with a positive
comment will help the author feel rewarded, and your subsequent remarks may
be heard more clearly. You may feel good also.
-- Begin if possible with the large issues, so the author knows they’ve been
+- Begin if possible with the large issues, so the author knows they've been
understood. Resist the temptation to immediately go line by line, or to open
with small pervasive issues.
- Do not let perfect be the enemy of the good. If you find yourself making
diff --git a/doc/developers/develop.rst b/doc/developers/develop.rst
index a60d60260b485..0e4b8258476da 100644
--- a/doc/developers/develop.rst
+++ b/doc/developers/develop.rst
@@ -370,9 +370,10 @@ While when `deep=False`, the output will be::
my_extra_param -> random
subestimator -> LogisticRegression()
-The ``set_params`` on the other hand takes as input a dict of the form
-``'parameter': value`` and sets the parameter of the estimator using this dict.
-Return value must be estimator itself.
+On the other hand, ``set_params`` takes the parameters of ``__init__``
+as keyword arguments, unpacks them into a dict of the form
+``'parameter': value`` and sets the parameters of the estimator using this dict.
+Return value must be the estimator itself.
While the ``get_params`` mechanism is not essential (see :ref:`cloning` below),
the ``set_params`` function is necessary as it is used to set parameters during
@@ -677,7 +678,8 @@ In addition, we add the following guidelines:
find bugs in scikit-learn.
* Use the `numpy docstring standard
- `_ in all your docstrings.
+ `_
+ in all your docstrings.
A good example of code that we like can be found `here
diff --git a/doc/developers/performance.rst b/doc/developers/performance.rst
index 3e8bdbc4b857a..36419894eafd6 100644
--- a/doc/developers/performance.rst
+++ b/doc/developers/performance.rst
@@ -268,7 +268,7 @@ Then, setup the magics in a manner similar to ``line_profiler``.
- **Under IPython 0.11+**, first create a configuration profile:
.. prompt:: bash $
-
+
ipython profile create
@@ -425,4 +425,4 @@ See `joblib documentation `_
A simple algorithmic trick: warm restarts
=========================================
-See the glossary entry for `warm_start `_
+See the glossary entry for :term:`warm_start`
diff --git a/doc/glossary.rst b/doc/glossary.rst
index b52dcde382246..ff3a3da0cbfb7 100644
--- a/doc/glossary.rst
+++ b/doc/glossary.rst
@@ -694,7 +694,7 @@ General Concepts
decision-making process outlined in :ref:`governance`.
For all votes, a proposal must have been made public and discussed before the
vote. Such a proposal must be a consolidated document, in the form of a
- ‘Scikit-Learn Enhancement Proposal’ (SLEP), rather than a long discussion on an
+ "Scikit-Learn Enhancement Proposal" (SLEP), rather than a long discussion on an
issue. A SLEP must be submitted as a pull-request to
`enhancement proposals `_ using the
`SLEP template `_.
@@ -835,7 +835,13 @@ Class APIs and Estimator Types
* :term:`predict` if :term:`inductive`
density estimator
- TODO
+ An :term:`unsupervised` estimation of input probability density
+ function. Commonly used techniques are:
+
+ * :ref:`kernel_density` - uses a kernel function, controlled by the
+ bandwidth parameter to represent density;
+ * :ref:`Gaussian mixture ` - uses mixture of Gaussian models
+ to represent density.
estimator
estimators
@@ -1385,7 +1391,11 @@ Methods
often the likelihood of the data under the model.
``score_samples``
- TODO
+ A method that returns a score for each given sample. The exact
+ definition of *score* varies from one class to another. In the case of
+ density estimation, it can be the log density model on the data, and in
+ the case of outlier detection, it can be the opposite of the outlier
+ factor of the data.
If the estimator was not already :term:`fitted`, calling this method
should raise a :class:`exceptions.NotFittedError`.
@@ -1492,7 +1502,13 @@ functions or non-estimator constructors.
``cv`` values are validated and interpreted with :func:`utils.check_cv`.
``kernel``
- TODO
+ Specifies the kernel function to be used by Kernel Method algorithms.
+ For example, the estimators :class:`SVC` and
+ :class:`GaussianProcessClassifier` both have a ``kernel`` parameter
+ that takes the name of the kernel to use as string or a callable
+ kernel function used to compute the kernel matrix. For more reference,
+ see the :ref:`kernel_approximation` and the :ref:`gaussian_process`
+ user guides.
``max_iter``
For estimators involving iterative optimization, this determines the
diff --git a/doc/governance.rst b/doc/governance.rst
index bdabf82378425..224ad21ce6e6f 100644
--- a/doc/governance.rst
+++ b/doc/governance.rst
@@ -72,7 +72,7 @@ the continued development of the project through ongoing engagement with the
community. They have shown they can be trusted to maintain scikit-learn with
care. Being a core developer allows contributors to more easily carry on
with their project related activities by giving them direct access to the
-project’s repository and is represented as being an organization member on the
+project's repository and is represented as being an organization member on the
scikit-learn `GitHub organization `_.
Core developers are expected to review code
contributions, can merge approved pull requests, can cast votes for and against
@@ -120,7 +120,7 @@ Decision Making Process
=======================
Decisions about the future of the project are made through discussion with all
members of the community. All non-sensitive project management discussion takes
-place on the project contributors’ `mailing list `_
+place on the project contributors' `mailing list `_
and the `issue tracker `_.
Occasionally, sensitive discussion occurs on a private list.
@@ -142,7 +142,7 @@ are made according to the following rules:
page: Requires +1 by a core developer, no -1 by a core developer (lazy
consensus), happens on the issue or pull request page. Core developers are
expected to give “reasonable time” to others to give their opinion on the pull
- request if they’re not confident others would agree.
+ request if they're not confident others would agree.
* **Code changes and major documentation changes**
require +1 by two core developers, no -1 by a core developer (lazy
@@ -164,7 +164,7 @@ Enhancement proposals (SLEPs)
==============================
For all votes, a proposal must have been made public and discussed before the
vote. Such proposal must be a consolidated document, in the form of a
-‘Scikit-Learn Enhancement Proposal’ (SLEP), rather than a long discussion on an
+"Scikit-Learn Enhancement Proposal" (SLEP), rather than a long discussion on an
issue. A SLEP must be submitted as a pull-request to
`enhancement proposals `_
using the `SLEP template `_.
diff --git a/doc/images/generated-doc-ci.png b/doc/images/generated-doc-ci.png
new file mode 100644
index 0000000000000..3e980bdf35293
Binary files /dev/null and b/doc/images/generated-doc-ci.png differ
diff --git a/doc/install.rst b/doc/install.rst
index 9710e98b2a555..faae9fccb60f3 100644
--- a/doc/install.rst
+++ b/doc/install.rst
@@ -188,6 +188,19 @@ dependencies (numpy, scipy) that scikit-learn requires.
The following is an incomplete list of OS and python distributions
that provide their own version of scikit-learn.
+Alpine Linux
+------------
+
+Alpine Linux's package is provided through the `official repositories
+`__ as
+``py3-scikit-learn`` for Python.
+It can be installed by typing the following command:
+
+.. prompt:: bash $
+
+ sudo apk add py3-scikit-learn
+
+
Arch Linux
----------
diff --git a/doc/logos/1280px-scikit-learn-logo.png b/doc/logos/1280px-scikit-learn-logo.png
new file mode 100644
index 0000000000000..f02c510019d87
Binary files /dev/null and b/doc/logos/1280px-scikit-learn-logo.png differ
diff --git a/doc/logos/README.md b/doc/logos/README.md
new file mode 100644
index 0000000000000..b2a238ec75a4d
--- /dev/null
+++ b/doc/logos/README.md
@@ -0,0 +1,57 @@
+# scikit-learn Branding
+
+This section contains scikit-learn's branding standards.
+File types:
+- `PNG` is a higher-quality compression format; size of file is generally larger
+- `ICO` file format refers to an image file format that contains small size computer icon images
+- `SVG` Scalable Vector Graphics (SVG) are an XML-based markup language for describing two-dimensional based vector graphics. They can be created and edited with any text editor or with drawing software.
+
+---
+
+# scikit-learn: Font, Colors & Logos
+
+## scikit-learn Font
+- "scikit": Helvetica Neue
+- "learn": Script MT
+
+## scikit-learn Color Palette
+ `RGB 41/171/226 | HEX #29ABE2 | scikit-learn Cyan` | More info: [#29ABE2](https://www.color-hex.com/color/29abe2)
+
+ `RGB 247/147/30 | HEX #F7931E | scikit-learn Orange` | More info: [#F7931E](https://www.color-hex.com/color/f7931e)
+
+ `RGB 155/70/0| HEX #9B4600 | scikit-learn Brown` | More info: [#9B4600](https://www.color-hex.com/color/9b4600)
+
+## scikit-learn Logos
+
+### Logo 1
+- File type: PNG
+- File size: 49 KB (1280 x 689 px)
+- File name: [1280px-scikit-learn-logo.png](https://github.com/scikit-learn/scikit-learn/blob/main/doc/logos/1280px-scikit-learn-logo.png)
+
+
+
+
+
+### Logo 2
+- File type: ICO
+- File size: 2 KB (32 x 32 px)
+- File name: [favicon.ico](https://github.com/scikit-learn/scikit-learn/blob/main/doc/logos/favicon.ico)
+
+
+
+
+
+### Logo 3
+- File type: SVG
+- File size: 5 KB
+- File name: [scikit-learn-logo-without-subtitle.svg](https://github.com/scikit-learn/scikit-learn/blob/main/doc/logos/scikit-learn-logo-without-subtitle.svg)
+
+
+
+---
+
+## Reference
+- [color-hex](https://www.color-hex.com): Glossary of Color Palettes
+
+## Other
+You can find more variations of the logos here: https://github.com/scikit-learn/blog/tree/main/assets/images
diff --git a/doc/logos/brand_colors/colorswatch_29ABE2_cyan.png b/doc/logos/brand_colors/colorswatch_29ABE2_cyan.png
new file mode 100644
index 0000000000000..b014a859dd4b9
Binary files /dev/null and b/doc/logos/brand_colors/colorswatch_29ABE2_cyan.png differ
diff --git a/doc/logos/brand_colors/colorswatch_9B4600_brown.png b/doc/logos/brand_colors/colorswatch_9B4600_brown.png
new file mode 100644
index 0000000000000..379400786ef56
Binary files /dev/null and b/doc/logos/brand_colors/colorswatch_9B4600_brown.png differ
diff --git a/doc/logos/brand_colors/colorswatch_F7931E_orange.png b/doc/logos/brand_colors/colorswatch_F7931E_orange.png
new file mode 100644
index 0000000000000..5b22b575ac411
Binary files /dev/null and b/doc/logos/brand_colors/colorswatch_F7931E_orange.png differ
diff --git a/doc/logos/scikit-learn-logo.svg b/doc/logos/scikit-learn-logo.svg
index 523a656943772..362542602e0ae 100644
--- a/doc/logos/scikit-learn-logo.svg
+++ b/doc/logos/scikit-learn-logo.svg
@@ -1,110 +1 @@
-
-
-
-
\ No newline at end of file
+
diff --git a/doc/make.bat b/doc/make.bat
index fa8e7171ea7e6..b7e269a6a7836 100644
--- a/doc/make.bat
+++ b/doc/make.bat
@@ -9,7 +9,7 @@ if NOT "%PAPER%" == "" (
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
)
-if "%1" == "" goto help
+if "%1" == "" goto html-noplot
if "%1" == "help" (
:help
@@ -42,6 +42,7 @@ if "%1" == "html" (
)
if "%1" == "html-noplot" (
+ :html-noplot
%SPHINXBUILD% -D plot_gallery=0 -b html %ALLSPHINXOPTS% %BUILDDIR%/html
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/html
diff --git a/doc/modules/clustering.rst b/doc/modules/clustering.rst
index 881f884453fc1..13b2004a41223 100644
--- a/doc/modules/clustering.rst
+++ b/doc/modules/clustering.rst
@@ -141,7 +141,7 @@ K-means
The :class:`KMeans` algorithm clusters data by trying to separate samples in n
groups of equal variance, minimizing a criterion known as the *inertia* or
within-cluster sum-of-squares (see below). This algorithm requires the number
-of clusters to be specified. It scales well to large number of samples and has
+of clusters to be specified. It scales well to large numbers of samples and has
been used across a large range of application areas in many different fields.
The k-means algorithm divides a set of :math:`N` samples :math:`X` into
diff --git a/doc/modules/compose.rst b/doc/modules/compose.rst
index 2a2b007783f27..c797eb288c6e5 100644
--- a/doc/modules/compose.rst
+++ b/doc/modules/compose.rst
@@ -202,7 +202,7 @@ each configuration.
The parameter ``memory`` is needed in order to cache the transformers.
``memory`` can be either a string containing the directory where to cache the
-transformers or a `joblib.Memory `_
+transformers or a `joblib.Memory `_
object::
>>> from tempfile import mkdtemp
diff --git a/doc/modules/cross_decomposition.rst b/doc/modules/cross_decomposition.rst
index 5c9aed46e66ea..caaec18c6c6d2 100644
--- a/doc/modules/cross_decomposition.rst
+++ b/doc/modules/cross_decomposition.rst
@@ -185,7 +185,7 @@ targets is greater than the number of samples.
.. [1] `A survey of Partial Least Squares (PLS) methods, with emphasis on
the two-block case
- `_
+ `_
JA Wegelin
.. topic:: Examples:
diff --git a/doc/modules/cross_validation.rst b/doc/modules/cross_validation.rst
index 3ecab9bd8eb04..2f2c28eef6cea 100644
--- a/doc/modules/cross_validation.rst
+++ b/doc/modules/cross_validation.rst
@@ -592,8 +592,8 @@ Here is a visualization of the cross-validation behavior.
.. _group_cv:
-Cross-validation iterators for grouped data.
---------------------------------------------
+Cross-validation iterators for grouped data
+-------------------------------------------
The i.i.d. assumption is broken if the underlying generative process yield
groups of dependent samples.
@@ -641,7 +641,8 @@ Imagine you have three subjects, each with an associated number from 1 to 3::
Each subject is in a different testing fold, and the same subject is never in
both testing and training. Notice that the folds do not have exactly the same
-size due to the imbalance in the data.
+size due to the imbalance in the data. If class proportions must be balanced
+across folds, :class:`StratifiedGroupKFold` is a better option.
Here is a visualization of the cross-validation behavior.
@@ -650,6 +651,11 @@ Here is a visualization of the cross-validation behavior.
:align: center
:scale: 75%
+Similar to :class:`KFold`, the test sets from :class:`GroupKFold` will form a
+complete partition of all the data. Unlike :class:`KFold`, :class:`GroupKFold`
+is not randomized at all, whereas :class:`KFold` is randomized when
+``shuffle=True``.
+
.. _stratified_group_k_fold:
StratifiedGroupKFold
@@ -713,7 +719,8 @@ group information can be used to encode arbitrary domain specific pre-defined
cross-validation folds.
Each training set is thus constituted by all the samples except the ones
-related to a specific group.
+related to a specific group. This is basically the same as
+:class:`LeavePGroupsOut` with ``n_groups=1``.
For example, in the cases of multiple experiments, :class:`LeaveOneGroupOut`
can be used to create a cross-validation based on the different experiments:
@@ -741,7 +748,9 @@ Leave P Groups Out
^^^^^^^^^^^^^^^^^^
:class:`LeavePGroupsOut` is similar as :class:`LeaveOneGroupOut`, but removes
-samples related to :math:`P` groups for each training/test set.
+samples related to :math:`P` groups for each training/test set. All possible
+combinations of :math:`P` groups are left out, meaning test sets will overlap
+for :math:`P>1`.
Example of Leave-2-Group Out::
@@ -765,7 +774,8 @@ Group Shuffle Split
The :class:`GroupShuffleSplit` iterator behaves as a combination of
:class:`ShuffleSplit` and :class:`LeavePGroupsOut`, and generates a
sequence of randomized partitions in which a subset of groups are held
-out for each split.
+out for each split. Each train/test split is performed independently meaning
+there is no guaranteed relationship between successive test sets.
Here is a usage example::
diff --git a/doc/modules/decomposition.rst b/doc/modules/decomposition.rst
index 4f6a889473f13..293f31dacd091 100644
--- a/doc/modules/decomposition.rst
+++ b/doc/modules/decomposition.rst
@@ -283,7 +283,7 @@ prediction (kernel dependency estimation). :class:`KernelPCA` supports both
.. note::
:meth:`KernelPCA.inverse_transform` relies on a kernel ridge to learn the
function mapping samples from the PCA basis into the original feature
- space [Bakir2004]_. Thus, the reconstruction obtained with
+ space [Bakir2003]_. Thus, the reconstruction obtained with
:meth:`KernelPCA.inverse_transform` is an approximation. See the example
linked below for more details.
@@ -299,10 +299,10 @@ prediction (kernel dependency estimation). :class:`KernelPCA` supports both
International conference on artificial neural networks.
Springer, Berlin, Heidelberg, 1997.
- .. [Bakir2004] Bakır, Gökhan H., Jason Weston, and Bernhard Schölkopf.
+ .. [Bakir2003] Bakır, Gökhan H., Jason Weston, and Bernhard Schölkopf.
`"Learning to find pre-images."
- `_
- Advances in neural information processing systems 16 (2004): 449-456.
+ `_
+ Advances in neural information processing systems 16 (2003): 449-456.
.. _kPCA_Solvers:
@@ -956,12 +956,12 @@ is not readily available from the start, or when the data does not fit into memo
.. [4] `"SVD based initialization: A head start for nonnegative
matrix factorization"
- `_
+ `_
C. Boutsidis, E. Gallopoulos, 2008
.. [5] `"Fast local algorithms for large scale nonnegative matrix and tensor
factorizations."
- `_
+ `_
A. Cichocki, A. Phan, 2009
.. [6] :arxiv:`"Algorithms for nonnegative matrix factorization with
@@ -1077,7 +1077,7 @@ when data can be fetched sequentially.
M. Hoffman, D. Blei, F. Bach, 2010
* `"Stochastic Variational Inference"
- `_
+ `_
M. Hoffman, D. Blei, C. Wang, J. Paisley, 2013
* `"The varimax criterion for analytic rotation in factor analysis"
diff --git a/doc/modules/ensemble.rst b/doc/modules/ensemble.rst
index e93a6201bd43c..7d64a0e91181c 100644
--- a/doc/modules/ensemble.rst
+++ b/doc/modules/ensemble.rst
@@ -562,7 +562,7 @@ for regression which can be specified via the argument
The figure below shows the results of applying :class:`GradientBoostingRegressor`
with least squares loss and 500 base learners to the diabetes dataset
(:func:`sklearn.datasets.load_diabetes`).
-The plot on the left shows the train and test error at each iteration.
+The plot shows the train and test error at each iteration.
The train error at each iteration is stored in the
:attr:`~GradientBoostingRegressor.train_score_` attribute
of the gradient boosting model. The test error at each iterations can be obtained
@@ -826,7 +826,7 @@ does poorly.
:scale: 75
Another strategy to reduce the variance is by subsampling the features
-analogous to the random splits in :class:`RandomForestClassifier` .
+analogous to the random splits in :class:`RandomForestClassifier`.
The number of subsampled features can be controlled via the ``max_features``
parameter.
@@ -979,7 +979,7 @@ corresponds to :math:`\lambda` in equation (2) of [XGBoost]_.
Note that **early-stopping is enabled by default if the number of samples is
larger than 10,000**. The early-stopping behaviour is controlled via the
-``early-stopping``, ``scoring``, ``validation_fraction``,
+``early_stopping``, ``scoring``, ``validation_fraction``,
``n_iter_no_change``, and ``tol`` parameters. It is possible to early-stop
using an arbitrary :term:`scorer`, or just the training or validation loss.
Note that for technical reasons, using a scorer is significantly slower than
@@ -1241,7 +1241,7 @@ Voting Classifier
The idea behind the :class:`VotingClassifier` is to combine
conceptually different machine learning classifiers and use a majority vote
or the average predicted probabilities (soft vote) to predict the class labels.
-Such a classifier can be useful for a set of equally well performing model
+Such a classifier can be useful for a set of equally well performing models
in order to balance out their individual weaknesses.
@@ -1333,7 +1333,7 @@ Here, the predicted class label is 2, since it has the
highest average probability.
The following example illustrates how the decision regions may change
-when a soft :class:`VotingClassifier` is used based on an linear Support
+when a soft :class:`VotingClassifier` is used based on a linear Support
Vector Machine, a Decision Tree, and a K-nearest neighbor classifier::
>>> from sklearn import datasets
diff --git a/doc/modules/feature_extraction.rst b/doc/modules/feature_extraction.rst
index a07d722defb9b..962784fb236bc 100644
--- a/doc/modules/feature_extraction.rst
+++ b/doc/modules/feature_extraction.rst
@@ -33,7 +33,7 @@ need not be stored) and storing feature names in addition to values.
:class:`DictVectorizer` implements what is called one-of-K or "one-hot"
coding for categorical (aka nominal, discrete) features. Categorical
features are "attribute-value" pairs where the value is restricted
-to a list of discrete of possibilities without ordering (e.g. topic
+to a list of discrete possibilities without ordering (e.g. topic
identifiers, types of objects, tags, names...).
In the following, "city" is a categorical attribute while "temperature"
@@ -995,7 +995,7 @@ Patch extraction
The :func:`extract_patches_2d` function extracts patches from an image stored
as a two-dimensional array, or three-dimensional with color information along
the third axis. For rebuilding an image from all its patches, use
-:func:`reconstruct_from_patches_2d`. For example let use generate a 4x4 pixel
+:func:`reconstruct_from_patches_2d`. For example let us generate a 4x4 pixel
picture with 3 color channels (e.g. in RGB format)::
>>> import numpy as np
diff --git a/doc/modules/feature_selection.rst b/doc/modules/feature_selection.rst
index c27a334c2ed4b..baa24d86a25b8 100644
--- a/doc/modules/feature_selection.rst
+++ b/doc/modules/feature_selection.rst
@@ -196,9 +196,7 @@ alpha parameter, the fewer features selected.
.. topic:: Examples:
- * :ref:`sphx_glr_auto_examples_text_plot_document_classification_20newsgroups.py`: Comparison
- of different algorithms for document classification including L1-based
- feature selection.
+ * :ref:`sphx_glr_auto_examples_linear_model_plot_lasso_dense_vs_sparse_data.py`.
.. _compressive_sensing:
@@ -271,14 +269,14 @@ SFS can be either forward or backward:
Forward-SFS is a greedy procedure that iteratively finds the best new feature
to add to the set of selected features. Concretely, we initially start with
-zero feature and find the one feature that maximizes a cross-validated score
+zero features and find the one feature that maximizes a cross-validated score
when an estimator is trained on this single feature. Once that first feature
is selected, we repeat the procedure by adding a new feature to the set of
selected features. The procedure stops when the desired number of selected
features is reached, as determined by the `n_features_to_select` parameter.
Backward-SFS follows the same idea but works in the opposite direction:
-instead of starting with no feature and greedily adding features, we start
+instead of starting with no features and greedily adding features, we start
with *all* the features and greedily *remove* features from the set. The
`direction` parameter controls whether forward or backward SFS is used.
diff --git a/doc/modules/gaussian_process.rst b/doc/modules/gaussian_process.rst
index 94fc69305cf6d..d6f7008c271ba 100644
--- a/doc/modules/gaussian_process.rst
+++ b/doc/modules/gaussian_process.rst
@@ -484,7 +484,7 @@ Note that magic methods ``__add__``, ``__mul___`` and ``__pow__`` are
overridden on the Kernel objects, so one can use e.g. ``RBF() + RBF()`` as
a shortcut for ``Sum(RBF(), RBF())``.
-Radial-basis function (RBF) kernel
+Radial basis function (RBF) kernel
----------------------------------
The :class:`RBF` kernel is a stationary kernel. It is also known as the "squared
exponential" kernel. It is parameterized by a length-scale parameter :math:`l>0`, which
diff --git a/doc/modules/grid_search.rst b/doc/modules/grid_search.rst
index 9128c7d3c9841..0404ad3242268 100644
--- a/doc/modules/grid_search.rst
+++ b/doc/modules/grid_search.rst
@@ -152,8 +152,8 @@ A continuous log-uniform random variable is available through
:class:`~sklearn.utils.fixes.loguniform`. This is a continuous version of
log-spaced parameters. For example to specify ``C`` above, ``loguniform(1,
100)`` can be used instead of ``[1, 10, 100]`` or ``np.logspace(0, 2,
-num=1000)``. This is an alias to SciPy's `stats.reciprocal
-`_.
+num=1000)``. This is an alias to `scipy.stats.loguniform
+`_.
Mirroring the example above in grid search, we can specify a continuous random
variable that is log-uniformly distributed between ``1e0`` and ``1e3``::
diff --git a/doc/modules/impute.rst b/doc/modules/impute.rst
index 2df6e0a76bd73..c71d71b7c8ee9 100644
--- a/doc/modules/impute.rst
+++ b/doc/modules/impute.rst
@@ -14,7 +14,7 @@ use incomplete datasets is to discard entire rows and/or columns containing
missing values. However, this comes at the price of losing data which may be
valuable (even though incomplete). A better strategy is to impute the missing
values, i.e., to infer them from the known part of the data. See the
-:ref:`glossary` entry on imputation.
+glossary entry on :term:`imputation`.
Univariate vs. Multivariate Imputation
diff --git a/doc/modules/kernel_approximation.rst b/doc/modules/kernel_approximation.rst
index 7b46d19cb52ad..2a192d5f4273a 100644
--- a/doc/modules/kernel_approximation.rst
+++ b/doc/modules/kernel_approximation.rst
@@ -237,9 +237,9 @@ or store training examples.
`_
Rahimi, A. and Recht, B. - Advances in neural information processing 2007,
.. [LS2010] `"Random Fourier approximations for skewed multiplicative histogram kernels"
- `_
- Random Fourier approximations for skewed multiplicative histogram kernels
- - Lecture Notes for Computer Sciencd (DAGM)
+ `_
+ Li, F., Ionescu, C., and Sminchisescu, C.
+ - Pattern Recognition, DAGM 2010, Lecture Notes in Computer Science.
.. [VZ2010] `"Efficient additive kernels via explicit feature maps"
`_
Vedaldi, A. and Zisserman, A. - Computer Vision and Pattern Recognition 2010
diff --git a/doc/modules/linear_model.rst b/doc/modules/linear_model.rst
index 125c78a5043b7..c2f18b53f758d 100644
--- a/doc/modules/linear_model.rst
+++ b/doc/modules/linear_model.rst
@@ -389,10 +389,10 @@ formula is valid only when `n_samples > n_features`.
The Annals of Statistics 35.5 (2007): 2173-2192.
<0712.0881.pdf>`
- .. [13] `Cherkassky, Vladimir, and Yunqian Ma.
+ .. [13] :doi:`Cherkassky, Vladimir, and Yunqian Ma.
"Comparison of model selection for regression."
Neural computation 15.7 (2003): 1691-1714.
- `_
+ <10.1162/089976603321891864>`
Comparison with the regularization parameter of SVM
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -571,7 +571,7 @@ The disadvantages of the LARS method include:
in the discussion section of the Efron et al. (2004) Annals of
Statistics article.
-The LARS model can be used using via the estimator :class:`Lars`, or its
+The LARS model can be used via the estimator :class:`Lars`, or its
low-level implementation :func:`lars_path` or :func:`lars_path_gram`.
@@ -631,7 +631,7 @@ column is always zero.
Orthogonal Matching Pursuit (OMP)
=================================
-:class:`OrthogonalMatchingPursuit` and :func:`orthogonal_mp` implements the OMP
+:class:`OrthogonalMatchingPursuit` and :func:`orthogonal_mp` implement the OMP
algorithm for approximating the fit of a linear model with constraints imposed
on the number of non-zero coefficients (ie. the :math:`\ell_0` pseudo-norm).
@@ -850,28 +850,91 @@ regularization.
that it improves numerical stability. No regularization amounts to
setting C to a very high value.
-As an optimization problem, binary class :math:`\ell_2` penalized logistic
-regression minimizes the following cost function:
+Binary Case
+-----------
-.. math:: \min_{w, c} \frac{1}{2}w^T w + C \sum_{i=1}^n \log(\exp(- y_i (X_i^T w + c)) + 1) .
+For notational ease, we assume that the target :math:`y_i` takes values in the
+set :math:`\{0, 1\}` for data point :math:`i`.
+Once fitted, the :meth:`~sklearn.linear_model.LogisticRegression.predict_proba`
+method of :class:`~sklearn.linear_model.LogisticRegression` predicts
+the probability of the positive class :math:`P(y_i=1|X_i)` as
-Similarly, :math:`\ell_1` regularized logistic regression solves the following
-optimization problem:
+.. math:: \hat{p}(X_i) = \operatorname{expit}(X_i w + w_0) = \frac{1}{1 + \exp(-X_i w - w_0)}.
-.. math:: \min_{w, c} \|w\|_1 + C \sum_{i=1}^n \log(\exp(- y_i (X_i^T w + c)) + 1).
+As an optimization problem, binary
+class logistic regression with regularization term :math:`r(w)` minimizes the
+following cost function:
-Elastic-Net regularization is a combination of :math:`\ell_1` and
-:math:`\ell_2`, and minimizes the following cost function:
+.. math:: \min_{w} C \sum_{i=1}^n \left(-y_i \log(\hat{p}(X_i)) - (1 - y_i) \log(1 - \hat{p}(X_i))\right) + r(w).
-.. math:: \min_{w, c} \frac{1 - \rho}{2}w^T w + \rho \|w\|_1 + C \sum_{i=1}^n \log(\exp(- y_i (X_i^T w + c)) + 1),
-where :math:`\rho` controls the strength of :math:`\ell_1` regularization vs.
-:math:`\ell_2` regularization (it corresponds to the `l1_ratio` parameter).
+We currently provide four choices for the regularization term :math:`r(w)` via
+the `penalty` argument:
-Note that, in this notation, it's assumed that the target :math:`y_i` takes
-values in the set :math:`{-1, 1}` at trial :math:`i`. We can also see that
-Elastic-Net is equivalent to :math:`\ell_1` when :math:`\rho = 1` and equivalent
-to :math:`\ell_2` when :math:`\rho=0`.
++----------------+-------------------------------------------------+
+| penalty | :math:`r(w)` |
++================+=================================================+
+| `None` | :math:`0` |
++----------------+-------------------------------------------------+
+| :math:`\ell_1` | :math:`\|w\|_1` |
++----------------+-------------------------------------------------+
+| :math:`\ell_2` | :math:`\frac{1}{2}\|w\|_2^2 = \frac{1}{2}w^T w` |
++----------------+-------------------------------------------------+
+| `ElasticNet` | :math:`\frac{1 - \rho}{2}w^T w + \rho \|w\|_1` |
++----------------+-------------------------------------------------+
+
+For ElasticNet, :math:`\rho` (which corresponds to the `l1_ratio` parameter)
+controls the strength of :math:`\ell_1` regularization vs. :math:`\ell_2`
+regularization. Elastic-Net is equivalent to :math:`\ell_1` when
+:math:`\rho = 1` and equivalent to :math:`\ell_2` when :math:`\rho=0`.
+
+Multinomial Case
+----------------
+
+The binary case can be extended to :math:`K` classes leading to the multinomial
+logistic regression, see also `log-linear model
+`_.
+
+.. note::
+ It is possible to parameterize a :math:`K`-class classification model
+ using only :math:`K-1` weight vectors, leaving one class probability fully
+ determined by the other class probabilities by leveraging the fact that all
+ class probabilities must sum to one. We deliberately choose to overparameterize the model
+ using :math:`K` weight vectors for ease of implementation and to preserve the
+ symmetrical inductive bias regarding ordering of classes, see [16]_. This effect becomes
+ especially important when using regularization. The choice of overparameterization can be
+ detrimental for unpenalized models since then the solution may not be unique, as shown in [16]_.
+
+Let :math:`y_i \in {1, \ldots, K}` be the label (ordinal) encoded target variable for observation :math:`i`.
+Instead of a single coefficient vector, we now have
+a matrix of coefficients :math:`W` where each row vector :math:`W_k` corresponds to class
+:math:`k`. We aim at predicting the class probabilities :math:`P(y_i=k|X_i)` via
+:meth:`~sklearn.linear_model.LogisticRegression.predict_proba` as:
+
+.. math:: \hat{p}_k(X_i) = \frac{\exp(X_i W_k + W_{0, k})}{\sum_{l=0}^{K-1} \exp(X_i W_l + W_{0, l})}.
+
+The objective for the optimization becomes
+
+.. math:: \min_W -C \sum_{i=1}^n \sum_{k=0}^{K-1} [y_i = k] \log(\hat{p}_k(X_i)) + r(W).
+
+Where :math:`[P]` represents the Iverson bracket which evaluates to :math:`0`
+if :math:`P` is false, otherwise it evaluates to :math:`1`. We currently provide four choices
+for the regularization term :math:`r(W)` via the `penalty` argument:
+
++----------------+----------------------------------------------------------------------------------+
+| penalty | :math:`r(W)` |
++================+==================================================================================+
+| `None` | :math:`0` |
++----------------+----------------------------------------------------------------------------------+
+| :math:`\ell_1` | :math:`\|W\|_{1,1} = \sum_{i=1}^n\sum_{j=1}^{K}|W_{i,j}|` |
++----------------+----------------------------------------------------------------------------------+
+| :math:`\ell_2` | :math:`\frac{1}{2}\|W\|_F^2 = \frac{1}{2}\sum_{i=1}^n\sum_{j=1}^{K} W_{i,j}^2` |
++----------------+----------------------------------------------------------------------------------+
+| `ElasticNet` | :math:`\frac{1 - \rho}{2}\|W\|_F^2 + \rho \|W\|_{1,1}` |
++----------------+----------------------------------------------------------------------------------+
+
+Solvers
+-------
The solvers implemented in the class :class:`LogisticRegression`
are "liblinear", "newton-cg", "lbfgs", "sag" and "saga":
@@ -1004,6 +1067,10 @@ to warm-starting (see :term:`Glossary `).
.. [9] `"Performance Evaluation of Lbfgs vs other solvers"
`_
+ .. [16] :arxiv:`Simon, Noah, J. Friedman and T. Hastie.
+ "A Blockwise Descent Algorithm for Group-penalized Multiresponse and
+ Multinomial Regression." <1311.6529>`
+
.. _Generalized_linear_regression:
Generalized Linear Regression
@@ -1482,7 +1549,7 @@ in the following ways.
* Peter J. Huber, Elvezio M. Ronchetti: Robust Statistics, Concomitant scale estimates, pg 172
Note that this estimator is different from the R implementation of Robust Regression
-(http://www.ats.ucla.edu/stat/r/dae/rreg.htm) because the R implementation does a weighted least
+(https://stats.oarc.ucla.edu/r/dae/robust-regression/) because the R implementation does a weighted least
squares implementation with weights given to each sample on the basis of how much the residual is
greater than a certain threshold.
@@ -1512,7 +1579,7 @@ see also :class:`~sklearn.metrics.mean_pinball_loss`,
\begin{cases}
q t, & t > 0, \\
0, & t = 0, \\
- (1-q) t, & t < 0
+ (q-1) t, & t < 0
\end{cases}
and the L1 penalty controlled by parameter ``alpha``, similar to
diff --git a/doc/modules/manifold.rst b/doc/modules/manifold.rst
index cb616267f2d06..22987eb61e674 100644
--- a/doc/modules/manifold.rst
+++ b/doc/modules/manifold.rst
@@ -427,7 +427,7 @@ distances in a geometric spaces. The data can be ratings of similarity between
objects, interaction frequencies of molecules, or trade indices between
countries.
-There exists two types of MDS algorithm: metric and non metric. In the
+There exists two types of MDS algorithm: metric and non metric. In
scikit-learn, the class :class:`MDS` implements both. In Metric MDS, the input
similarity matrix arises from a metric (and thus respects the triangular
inequality), the distances between output two points are then set to be as
diff --git a/doc/modules/metrics.rst b/doc/modules/metrics.rst
index 0926980aaaf8a..71e914afad192 100644
--- a/doc/modules/metrics.rst
+++ b/doc/modules/metrics.rst
@@ -228,5 +228,5 @@ The chi squared kernel is most commonly used on histograms (bags) of visual word
Local features and kernels for classification of texture and object
categories: A comprehensive study
International Journal of Computer Vision 2007
- https://research.microsoft.com/en-us/um/people/manik/projects/trade-off/papers/ZhangIJCV06.pdf
+ https://hal.archives-ouvertes.fr/hal-00171412/document
diff --git a/doc/modules/mixture.rst b/doc/modules/mixture.rst
index 2037f15fe3ee8..e1918d305bf3c 100644
--- a/doc/modules/mixture.rst
+++ b/doc/modules/mixture.rst
@@ -43,7 +43,7 @@ confidence ellipsoids for multivariate models, and compute the
Bayesian Information Criterion to assess the number of clusters in the
data. A :meth:`GaussianMixture.fit` method is provided that learns a Gaussian
Mixture Model from train data. Given test data, it can assign to each
-sample the Gaussian it mostly probably belong to using
+sample the Gaussian it mostly probably belongs to using
the :meth:`GaussianMixture.predict` method.
..
@@ -120,7 +120,7 @@ Estimation algorithm Expectation-maximization
-----------------------------------------------
The main difficulty in learning Gaussian mixture models from unlabeled
-data is that it is one usually doesn't know which points came from
+data is that one usually doesn't know which points came from
which latent component (if one has access to this information it gets
very easy to fit a separate Gaussian distribution to each set of
points). `Expectation-maximization
@@ -179,7 +179,7 @@ Variational Bayesian Gaussian Mixture
The :class:`BayesianGaussianMixture` object implements a variant of the
Gaussian mixture model with variational inference algorithms. The API is
-similar as the one defined by :class:`GaussianMixture`.
+similar to the one defined by :class:`GaussianMixture`.
.. _variational_inference:
@@ -199,13 +199,13 @@ expectation-maximization solutions but introduces some subtle biases
to the model. Inference is often notably slower, but not usually as
much so as to render usage unpractical.
-Due to its Bayesian nature, the variational algorithm needs more hyper-
-parameters than expectation-maximization, the most important of these being the
+Due to its Bayesian nature, the variational algorithm needs more hyperparameters
+than expectation-maximization, the most important of these being the
concentration parameter ``weight_concentration_prior``. Specifying a low value
-for the concentration prior will make the model put most of the weight on few
-components set the remaining components weights very close to zero. High values
-of the concentration prior will allow a larger number of components to be active
-in the mixture.
+for the concentration prior will make the model put most of the weight on a few
+components and set the remaining components' weights very close to zero. High
+values of the concentration prior will allow a larger number of components to
+be active in the mixture.
The parameters implementation of the :class:`BayesianGaussianMixture` class
proposes two types of prior for the weights distribution: a finite mixture model
@@ -313,7 +313,7 @@ Pros
Cons
.....
-:Speed: the extra parametrization necessary for variational inference make
+:Speed: the extra parametrization necessary for variational inference makes
inference slower, although not by much.
:Hyperparameters: this algorithm needs an extra hyperparameter
@@ -349,7 +349,7 @@ group of the mixture. At the end, to represent the infinite mixture, we
associate the last remaining piece of the stick to the proportion of points
that don't fall into all the other groups. The length of each piece is a random
variable with probability proportional to the concentration parameter. Smaller
-value of the concentration will divide the unit-length into larger pieces of
+values of the concentration will divide the unit-length into larger pieces of
the stick (defining more concentrated distribution). Larger concentration
values will create smaller pieces of the stick (increasing the number of
components with non zero weights).
diff --git a/doc/modules/model_evaluation.rst b/doc/modules/model_evaluation.rst
index d8fe7d87eec7a..8a9c28fb96304 100644
--- a/doc/modules/model_evaluation.rst
+++ b/doc/modules/model_evaluation.rst
@@ -693,10 +693,6 @@ and inferred labels::
for an example of classification report usage for
hand-written digits.
- * See :ref:`sphx_glr_auto_examples_text_plot_document_classification_20newsgroups.py`
- for an example of classification report usage for text
- documents.
-
* See :ref:`sphx_glr_auto_examples_model_selection_plot_grid_search_digits.py`
for an example of classification report usage for
grid search with nested cross-validation.
@@ -813,10 +809,6 @@ precision-recall curve as follows.
.. topic:: Examples:
- * See :ref:`sphx_glr_auto_examples_text_plot_document_classification_20newsgroups.py`
- for an example of :func:`f1_score` usage to classify text
- documents.
-
* See :ref:`sphx_glr_auto_examples_model_selection_plot_grid_search_digits.py`
for an example of :func:`precision_score` and :func:`recall_score` usage
to estimate parameters using grid search with nested cross-validation.
@@ -1976,13 +1968,13 @@ to handle the multioutput case: :func:`mean_squared_error`,
and :func:`d2_absolute_error_score`.
-These functions have an ``multioutput`` keyword argument which specifies the
+These functions have a ``multioutput`` keyword argument which specifies the
way the scores or losses for each individual target should be averaged. The
default is ``'uniform_average'``, which specifies a uniformly weighted mean
over outputs. If an ``ndarray`` of shape ``(n_outputs,)`` is passed, then its
entries are interpreted as weights and an according weighted average is
-returned. If ``multioutput`` is ``'raw_values'`` is specified, then all
-unaltered individual scores or losses will be returned in an array of shape
+returned. If ``multioutput`` is ``'raw_values'``, then all unaltered
+individual scores or losses will be returned in an array of shape
``(n_outputs,)``.
@@ -1991,7 +1983,7 @@ value ``'variance_weighted'`` for the ``multioutput`` parameter. This option
leads to a weighting of each individual score by the variance of the
corresponding target variable. This setting quantifies the globally captured
unscaled variance. If the target variables are of different scale, then this
-score puts more importance on well explaining the higher variance variables.
+score puts more importance on explaining the higher variance variables.
``multioutput='variance_weighted'`` is the default value for :func:`r2_score`
for backward compatibility. This will be changed to ``uniform_average`` in the
future.
@@ -2003,14 +1995,14 @@ R² score, the coefficient of determination
The :func:`r2_score` function computes the `coefficient of
determination `_,
-usually denoted as R².
+usually denoted as :math:`R^2`.
It represents the proportion of variance (of y) that has been explained by the
independent variables in the model. It provides an indication of goodness of
fit and therefore a measure of how well unseen samples are likely to be
predicted by the model, through the proportion of explained variance.
-As such variance is dataset dependent, R² may not be meaningfully comparable
+As such variance is dataset dependent, :math:`R^2` may not be meaningfully comparable
across different datasets. Best possible score is 1.0 and it can be negative
(because the model can be arbitrarily worse). A constant model that always
predicts the expected (average) value of y, disregarding the input features,
@@ -2021,7 +2013,7 @@ the :ref:`explained_variance_score` are identical.
If :math:`\hat{y}_i` is the predicted value of the :math:`i`-th sample
and :math:`y_i` is the corresponding true value for total :math:`n` samples,
-the estimated R² is defined as:
+the estimated :math:`R^2` is defined as:
.. math::
@@ -2029,7 +2021,7 @@ the estimated R² is defined as:
where :math:`\bar{y} = \frac{1}{n} \sum_{i=1}^{n} y_i` and :math:`\sum_{i=1}^{n} (y_i - \hat{y}_i)^2 = \sum_{i=1}^{n} \epsilon_i^2`.
-Note that :func:`r2_score` calculates unadjusted R² without correcting for
+Note that :func:`r2_score` calculates unadjusted :math:`R^2` without correcting for
bias in sample variance of y.
In the particular case where the true target is constant, the :math:`R^2` score is
@@ -2425,15 +2417,17 @@ Pinball loss
------------
The :func:`mean_pinball_loss` function is used to evaluate the predictive
-performance of quantile regression models. The `pinball loss
-`_ is equivalent
-to :func:`mean_absolute_error` when the quantile parameter ``alpha`` is set to
-0.5.
+performance of `quantile regression
+`_ models.
.. math::
\text{pinball}(y, \hat{y}) = \frac{1}{n_{\text{samples}}} \sum_{i=0}^{n_{\text{samples}}-1} \alpha \max(y_i - \hat{y}_i, 0) + (1 - \alpha) \max(\hat{y}_i - y_i, 0)
+The value of pinball loss is equivalent to half of :func:`mean_absolute_error` when the quantile
+parameter ``alpha`` is set to 0.5.
+
+
Here is a small example of usage of the :func:`mean_pinball_loss` function::
>>> from sklearn.metrics import mean_pinball_loss
diff --git a/doc/modules/naive_bayes.rst b/doc/modules/naive_bayes.rst
index b2dd4cf5a7cd3..3b566465f65cc 100644
--- a/doc/modules/naive_bayes.rst
+++ b/doc/modules/naive_bayes.rst
@@ -197,7 +197,7 @@ The decision rule for Bernoulli naive Bayes is based on
.. math::
- P(x_i \mid y) = P(i \mid y) x_i + (1 - P(i \mid y)) (1 - x_i)
+ P(x_i \mid y) = P(x_i = 1 \mid y) x_i + (1 - P(x_i = 1 \mid y)) (1 - x_i)
which differs from multinomial NB's rule
in that it explicitly penalizes the non-occurrence of a feature :math:`i`
@@ -229,10 +229,10 @@ It is advisable to evaluate both models, if time permits.
Categorical Naive Bayes
-----------------------
-:class:`CategoricalNB` implements the categorical naive Bayes
-algorithm for categorically distributed data. It assumes that each feature,
-which is described by the index :math:`i`, has its own categorical
-distribution.
+:class:`CategoricalNB` implements the categorical naive Bayes
+algorithm for categorically distributed data. It assumes that each feature,
+which is described by the index :math:`i`, has its own categorical
+distribution.
For each feature :math:`i` in the training set :math:`X`,
:class:`CategoricalNB` estimates a categorical distribution for each feature i
diff --git a/doc/modules/outlier_detection.rst b/doc/modules/outlier_detection.rst
index b0475b809ecb1..75a191a767aa5 100644
--- a/doc/modules/outlier_detection.rst
+++ b/doc/modules/outlier_detection.rst
@@ -170,7 +170,7 @@ but regular, observation outside the frontier.
.. topic:: References:
* `Estimating the support of a high-dimensional distribution
- `_
+ `_
Schölkopf, Bernhard, et al. Neural computation 13.7 (2001): 1443-1471.
.. topic:: Examples:
@@ -332,7 +332,7 @@ lower density than their neighbors.
In practice the local density is obtained from the k-nearest neighbors.
The LOF score of an observation is equal to the ratio of the
-average local density of his k-nearest neighbors, and its own local density:
+average local density of its k-nearest neighbors, and its own local density:
a normal instance is expected to have a local density similar to that of its
neighbors, while abnormal data are expected to have much smaller local density.
diff --git a/doc/modules/preprocessing.rst b/doc/modules/preprocessing.rst
index 7e2ee3defec17..9953b95257a19 100644
--- a/doc/modules/preprocessing.rst
+++ b/doc/modules/preprocessing.rst
@@ -34,8 +34,8 @@ standard deviation.
For instance, many elements used in the objective function of
a learning algorithm (such as the RBF kernel of Support Vector
-Machines or the l1 and l2 regularizers of linear models) assume that
-all features are centered around zero and have variance in the same
+Machines or the l1 and l2 regularizers of linear models) may assume that
+all features are centered around zero or have variance in the same
order. If a feature has a variance that is orders of magnitude larger
than others, it might dominate the objective function and make the
estimator unable to learn from other features correctly as expected.
diff --git a/doc/modules/semi_supervised.rst b/doc/modules/semi_supervised.rst
index 249c6f98a7976..47e8bfffdd9a7 100644
--- a/doc/modules/semi_supervised.rst
+++ b/doc/modules/semi_supervised.rst
@@ -148,4 +148,4 @@ which can drastically reduce running times.
[3] Olivier Delalleau, Yoshua Bengio, Nicolas Le Roux. Efficient
Non-Parametric Function Induction in Semi-Supervised Learning. AISTAT 2005
- https://research.microsoft.com/en-us/people/nicolasl/efficient_ssl.pdf
+ https://www.gatsby.ucl.ac.uk/aistats/fullpapers/204.pdf
diff --git a/doc/modules/svm.rst b/doc/modules/svm.rst
index 0f68366012d39..75609adf38c9c 100644
--- a/doc/modules/svm.rst
+++ b/doc/modules/svm.rst
@@ -483,6 +483,8 @@ Different kernels are specified by the `kernel` parameter::
>>> rbf_svc.kernel
'rbf'
+See also :ref:`kernel_approximation` for a solution to use RBF kernels that is much faster and more scalable.
+
Parameters of the RBF Kernel
----------------------------
diff --git a/doc/modules/tree.rst b/doc/modules/tree.rst
index 102bda44c3436..28bcd07ab978d 100644
--- a/doc/modules/tree.rst
+++ b/doc/modules/tree.rst
@@ -33,7 +33,7 @@ Some advantages of decision trees are:
- The cost of using the tree (i.e., predicting data) is logarithmic in the
number of data points used to train the tree.
- - Able to handle both numerical and categorical data. However scikit-learn
+ - Able to handle both numerical and categorical data. However, the scikit-learn
implementation does not support categorical variables for now. Other
techniques are usually specialized in analyzing datasets that have only one type
of variable. See :ref:`algorithms ` for more
@@ -415,7 +415,7 @@ must be categorical by dynamically defining a discrete attribute (based
on numerical variables) that partitions the continuous attribute value
into a discrete set of intervals. C4.5 converts the trained trees
(i.e. the output of the ID3 algorithm) into sets of if-then rules.
-These accuracy of each rule is then evaluated to determine the order
+The accuracy of each rule is then evaluated to determine the order
in which they should be applied. Pruning is done by removing a rule's
precondition if the accuracy of the rule improves without it.
@@ -423,16 +423,15 @@ C5.0 is Quinlan's latest version release under a proprietary license.
It uses less memory and builds smaller rulesets than C4.5 while being
more accurate.
-CART_ (Classification and Regression Trees) is very similar to C4.5, but
+CART (Classification and Regression Trees) is very similar to C4.5, but
it differs in that it supports numerical target variables (regression) and
does not compute rule sets. CART constructs binary trees using the feature
and threshold that yield the largest information gain at each node.
-scikit-learn uses an optimized version of the CART algorithm; however, scikit-learn
-implementation does not support categorical variables for now.
+scikit-learn uses an optimized version of the CART algorithm; however, the
+scikit-learn implementation does not support categorical variables for now.
.. _ID3: https://en.wikipedia.org/wiki/ID3_algorithm
-.. _CART: https://en.wikipedia.org/wiki/Predictive_analytics#Classification_and_regression_trees_.28CART.29
.. _tree_mathematical_formulation:
@@ -452,7 +451,7 @@ feature :math:`j` and threshold :math:`t_m`, partition the data into
.. math::
- Q_m^{left}(\theta) = \{(x, y) | x_j <= t_m\}
+ Q_m^{left}(\theta) = \{(x, y) | x_j \leq t_m\}
Q_m^{right}(\theta) = Q_m \setminus Q_m^{left}(\theta)
@@ -515,7 +514,7 @@ Log Loss or Entropy:
computed on a dataset :math:`D` is defined as follows:
.. math::
-
+
\mathrm{LL}(D, T) = -\frac{1}{n} \sum_{(x_i, y_i) \in D} \sum_k I(y_i = k) \log(T_k(x_i))
where :math:`D` is a training dataset of :math:`n` pairs :math:`(x_i, y_i)`.
@@ -529,7 +528,7 @@ Log Loss or Entropy:
the number of training data points that reached each leaf:
.. math::
-
+
\mathrm{LL}(D, T) = \sum_{m \in T} \frac{n_m}{n} H(Q_m)
Regression criteria
diff --git a/doc/presentations.rst b/doc/presentations.rst
index 15b02469d3a6c..2a465af8247a7 100644
--- a/doc/presentations.rst
+++ b/doc/presentations.rst
@@ -9,7 +9,7 @@ New to Scientific Python?
==========================
For those that are still new to the scientific Python ecosystem, we highly
recommend the `Python Scientific Lecture Notes
-`_. This will help you find your footing a
+`_. This will help you find your footing a
bit and will definitely improve your scikit-learn experience. A basic
understanding of NumPy arrays is recommended to make the most of scikit-learn.
@@ -63,7 +63,7 @@ Videos
3-hours long introduction to prediction tasks using scikit-learn.
-- `scikit-learn - Machine Learning in Python `_
+- `scikit-learn - Machine Learning in Python `_
by `Jake Vanderplas`_ at the 2012 PyData workshop at Google
Interactive demonstration of some scikit-learn features. 75 minutes.
@@ -74,5 +74,5 @@ Videos
.. _Gael Varoquaux: http://gael-varoquaux.info
-.. _Jake Vanderplas: https://staff.washington.edu/jakevdp
+.. _Jake Vanderplas: http://www.vanderplas.com
.. _Olivier Grisel: https://twitter.com/ogrisel
diff --git a/doc/related_projects.rst b/doc/related_projects.rst
index 0f5532bd52357..a1202a6cdd27e 100644
--- a/doc/related_projects.rst
+++ b/doc/related_projects.rst
@@ -335,7 +335,7 @@ Domain specific packages
Translations of scikit-learn documentation
------------------------------------------
-Translation’s purpose is to ease reading and understanding in languages
+Translation's purpose is to ease reading and understanding in languages
other than English. Its aim is to help people who do not understand English
or have doubts about its interpretation. Additionally, some people prefer
to read documentation in their native language, but please bear in mind that
diff --git a/doc/roadmap.rst b/doc/roadmap.rst
index df8811b968d7e..be3607cf542fb 100644
--- a/doc/roadmap.rst
+++ b/doc/roadmap.rst
@@ -257,8 +257,8 @@ Subpackage-specific goals
* Cross-validation should be able to be replaced by OOB estimates whenever a
cross-validation iterator is used.
* Redundant computations in pipelines should be avoided (related to point
- above) cf `daskml
- `_
+ above) cf `dask-ml
+ `_
:mod:`sklearn.neighbors`
diff --git a/doc/sphinxext/allow_nan_estimators.py b/doc/sphinxext/allow_nan_estimators.py
index bf51644b67116..89af4bbee6670 100755
--- a/doc/sphinxext/allow_nan_estimators.py
+++ b/doc/sphinxext/allow_nan_estimators.py
@@ -1,11 +1,7 @@
from sklearn.utils import all_estimators
-from sklearn.compose import ColumnTransformer
-from sklearn.pipeline import FeatureUnion
-from sklearn.decomposition import SparseCoder
from sklearn.utils.estimator_checks import _construct_instance
from sklearn.utils._testing import SkipTest
from docutils import nodes
-import warnings
from contextlib import suppress
from docutils.parsers.rst import Directive
@@ -27,7 +23,7 @@ def make_paragraph_for_estimator_type(estimator_type):
if est._get_tags().get("allow_nan"):
module_name = ".".join(est_class.__module__.split(".")[:2])
class_title = f"{est_class.__name__}"
- class_url = f"generated/{module_name}.{class_title}.html"
+ class_url = f"./generated/{module_name}.{class_title}.html"
item = nodes.list_item()
para = nodes.paragraph()
para += nodes.reference(
diff --git a/doc/templates/index.html b/doc/templates/index.html
index 923d0e940c191..755ff52821938 100644
--- a/doc/templates/index.html
+++ b/doc/templates/index.html
@@ -166,6 +166,8 @@
diff --git a/doc/testimonials/testimonials.rst b/doc/testimonials/testimonials.rst
index 88997285e347e..fbf53ae36ef2c 100644
--- a/doc/testimonials/testimonials.rst
+++ b/doc/testimonials/testimonials.rst
@@ -138,14 +138,14 @@ Gaël Varoquaux, research at Parietal
Betaworks is a NYC-based startup studio that builds new products, grows
-companies, and invests in others. Over the past 8 years we’ve launched a
+companies, and invests in others. Over the past 8 years we've launched a
handful of social data analytics-driven services, such as Bitly, Chartbeat,
digg and Scale Model. Consistently the betaworks data science team uses
Scikit-learn for a variety of tasks. From exploratory analysis, to product
development, it is an essential part of our toolkit. Recent uses are included
-in `digg’s new video recommender system
+in `digg's new video recommender system
`_,
-and Poncho’s `dynamic heuristic subspace clustering
+and Poncho's `dynamic heuristic subspace clustering
`_.
.. raw:: html
@@ -609,11 +609,11 @@ Vincent Dubourg, PHIMECA Engineering, PhD Engineer
At HowAboutWe, scikit-learn lets us implement a wide array of machine learning
techniques in analysis and in production, despite having a small team. We use
-scikit-learn’s classification algorithms to predict user behavior, enabling us
+scikit-learn's classification algorithms to predict user behavior, enabling us
to (for example) estimate the value of leads from a given traffic source early
-in the lead’s tenure on our site. Also, our users' profiles consist of
+in the lead's tenure on our site. Also, our users' profiles consist of
primarily unstructured data (answers to open-ended questions), so we use
-scikit-learn’s feature extraction and dimensionality reduction tools to
+scikit-learn's feature extraction and dimensionality reduction tools to
translate these unstructured data into inputs for our matchmaking system.
.. raw:: html
@@ -648,7 +648,7 @@ Daniel Weitzenfeld, Senior Data Scientist at HowAboutWe
At PeerIndex we use scientific methodology to build the Influence Graph - a
-unique dataset that allows us to identify who’s really influential and in which
+unique dataset that allows us to identify who's really influential and in which
context. To do this, we have to tackle a range of machine learning and
predictive modeling problems. Scikit-learn has emerged as our primary tool for
developing prototypes and making quick progress. From predicting missing data
@@ -879,12 +879,12 @@ Rafael Carrascosa, Lead developer
-Scikit-learn is helping to drive Moore’s Law, via Solido. Solido creates
+Scikit-learn is helping to drive Moore's Law, via Solido. Solido creates
computer-aided design tools used by the majority of top-20 semiconductor
companies and fabs, to design the bleeding-edge chips inside smartphones,
-automobiles, and more. Scikit-learn helps to power Solido’s algorithms for
+automobiles, and more. Scikit-learn helps to power Solido's algorithms for
rare-event estimation, worst-case verification, optimization, and more. At
-Solido, we are particularly fond of scikit-learn’s libraries for Gaussian
+Solido, we are particularly fond of scikit-learn's libraries for Gaussian
Process models, large-scale regularized linear regression, and classification.
Scikit-learn has increased our productivity, because for many ML problems we no
longer need to “roll our own” code. `This PyData 2014 talk `_ has details.
diff --git a/doc/tutorial/machine_learning_map/parse_path.py b/doc/tutorial/machine_learning_map/parse_path.py
index 770fd1481f53b..b1c68cec7f76b 100644
--- a/doc/tutorial/machine_learning_map/parse_path.py
+++ b/doc/tutorial/machine_learning_map/parse_path.py
@@ -89,7 +89,7 @@ def convertToFloat(s, loc, toks):
coordinateSequence = Sequence(coordinate)
-coordinatePair = (coordinate + maybeComma + coordinate).setParseAction(lambda t: tuple(t))
+coordinatePair = (coordinate + maybeComma + coordinate).setParseAction(tuple)
coordinatePairSequence = Sequence(coordinatePair)
coordinatePairPair = coordinatePair + maybeComma + coordinatePair
@@ -111,9 +111,9 @@ def convertToFloat(s, loc, toks):
arcRadius = (
nonnegativeNumber + maybeComma + #rx
nonnegativeNumber #ry
-).setParseAction(lambda t: tuple(t))
+).setParseAction(tuple)
-arcFlags = (flag + maybeComma + flag).setParseAction(lambda t: tuple(t))
+arcFlags = (flag + maybeComma + flag).setParseAction(tuple)
ellipticalArcArgument = Group(
arcRadius + maybeComma + #rx, ry
diff --git a/doc/tutorial/statistical_inference/model_selection.rst b/doc/tutorial/statistical_inference/model_selection.rst
index 070e86c18e8b1..dd0cec4de4db0 100644
--- a/doc/tutorial/statistical_inference/model_selection.rst
+++ b/doc/tutorial/statistical_inference/model_selection.rst
@@ -182,7 +182,7 @@ scoring method.
.. topic:: **Exercise**
On the digits dataset, plot the cross-validation score of a :class:`SVC`
- estimator with an linear kernel as a function of parameter ``C`` (use a
+ estimator with a linear kernel as a function of parameter ``C`` (use a
logarithmic grid of points, from 1 to 10).
.. literalinclude:: ../../auto_examples/exercises/plot_cv_digits.py
diff --git a/doc/visualizations.rst b/doc/visualizations.rst
index 0f0ec73549355..0c6590335232a 100644
--- a/doc/visualizations.rst
+++ b/doc/visualizations.rst
@@ -13,7 +13,7 @@ Visualizations
Scikit-learn defines a simple API for creating visualizations for machine
learning. The key feature of this API is to allow for quick plotting and
visual adjustments without recalculation. We provide `Display` classes that
-exposes two methods allowing to make the plotting: `from_estimator` and
+expose two methods for creating plots: `from_estimator` and
`from_predictions`. The `from_estimator` method will take a fitted estimator
and some data (`X` and `y`) and create a `Display` object. Sometimes, we would
like to only compute the predictions once and one should use `from_predictions`
@@ -42,7 +42,7 @@ ROC curve for SVC in future plots. In this case, the `svc_disp` is a
:class:`~sklearn.metrics.RocCurveDisplay` that stores the computed values as
attributes called `roc_auc`, `fpr`, and `tpr`. Be aware that we could get
the predictions from the support vector machine and then use `from_predictions`
-instead of `from_estimator` Next, we train a random forest classifier and plot
+instead of `from_estimator`. Next, we train a random forest classifier and plot
the previously computed roc curve again by using the `plot` method of the
`Display` object.
diff --git a/doc/whats_new.rst b/doc/whats_new.rst
index 931420a30b02f..3354a6b13f32b 100644
--- a/doc/whats_new.rst
+++ b/doc/whats_new.rst
@@ -12,6 +12,7 @@ on libraries.io to be notified when new versions are released.
.. toctree::
:maxdepth: 1
+ Version 1.2
Version 1.1
Version 1.0
Version 0.24
diff --git a/doc/whats_new/older_versions.rst b/doc/whats_new/older_versions.rst
index 63b5c6e9ea4cb..221de4cdb7e4c 100644
--- a/doc/whats_new/older_versions.rst
+++ b/doc/whats_new/older_versions.rst
@@ -1134,7 +1134,7 @@ Changelog
example_gaussian_process_plot_gp_probabilistic_classification_after_regression.py
for a taste of what can be done.
-- It is now possible to use liblinear’s Multi-class SVC (option
+- It is now possible to use liblinear's Multi-class SVC (option
multi_class in :class:`~svm.LinearSVC`)
- New features and performance improvements of text feature
diff --git a/doc/whats_new/v1.1.rst b/doc/whats_new/v1.1.rst
index 0ce67e9d1f8d5..27fa545901dd0 100644
--- a/doc/whats_new/v1.1.rst
+++ b/doc/whats_new/v1.1.rst
@@ -2,6 +2,106 @@
.. currentmodule:: sklearn
+.. _changes_1_1_2:
+
+Version 1.1.2
+=============
+
+**August 2022**
+
+Changed models
+--------------
+
+The following estimators and functions, when fit with the same data and
+parameters, may produce different models from the previous version. This often
+occurs due to changes in the modelling logic (bug fixes or enhancements), or in
+random sampling procedures.
+
+- |Fix| :class:`manifold.TSNE` now throws a `ValueError` when fit with
+ `perplexity>=n_samples` to ensure mathematical correctness of the algorithm.
+ :pr:`10805` by :user:`Mathias Andersen ` and
+ :pr:`23471` by :user:`Meekail Zain `.
+
+Changelog
+---------
+
+- |Fix| A default HTML representation is shown for meta-estimators with invalid
+ parameters. :pr:`24015` by `Thomas Fan`_.
+
+- |Fix| Add support for F-contiguous arrays for estimators and functions whose back-end
+ have been changed in 1.1.
+ :pr:`23990` by :user:`Julien Jerphanion `.
+
+- |Fix| Wheels are now available for MacOS 10.9 and greater. :pr:`23833` by
+ `Thomas Fan`_.
+
+:mod:`sklearn.base`
+...................
+
+- |Fix| The `get_params` method of the :class:`BaseEstimator` class now supports
+ estimators with `type`-type params that have the `get_params` method.
+ :pr:`24017` by :user:`Henry Sorsky `.
+
+:mod:`sklearn.cluster`
+......................
+
+- |Fix| Fixed a bug in :class:`cluster.Birch` that could trigger an error when splitting
+ a node if there are duplicates in the dataset.
+ :pr:`23395` by :user:`Jérémie du Boisberranger `.
+
+:mod:`sklearn.feature_selection`
+................................
+
+- |Fix| :class:`feature_selection.SelectFromModel` defaults to selection
+ threshold 1e-5 when the estimator is either :class:`linear_model.ElasticNet`
+ or :class:`linear_model.ElasticNetCV` with `l1_ratio` equals 1 or
+ :class:`linear_model.LassoCV`.
+ :pr:`23636` by :user:`Hao Chun Chang `.
+
+:mod:`sklearn.impute`
+.....................
+
+- |Fix| :class:`impute.SimpleImputer` uses the dtype seen in `fit` for
+ `transform` when the dtype is object. :pr:`22063` by `Thomas Fan`_.
+
+:mod:`sklearn.linear_model`
+...........................
+
+- |Fix| Use dtype-aware tolerances for the validation of gram matrices (passed by users
+ or precomputed). :pr:`22059` by :user:`Malte S. Kurz `.
+
+- |Fix| Fixed an error in :class:`linear_model.LogisticRegression` with
+ `solver="newton-cg"`, `fit_intercept=True`, and a single feature. :pr:`23608`
+ by `Tom Dupre la Tour`_.
+
+:mod:`sklearn.manifold`
+.......................
+
+- |Fix| :class:`manifold.TSNE` now throws a `ValueError` when fit with
+ `perplexity>=n_samples` to ensure mathematical correctness of the algorithm.
+ :pr:`10805` by :user:`Mathias Andersen ` and
+ :pr:`23471` by :user:`Meekail Zain `.
+
+:mod:`sklearn.metrics`
+......................
+
+- |Fix| Fixed error message of :class:`metrics.coverage_error` for 1D array input.
+ :pr:`23548` by :user:`Hao Chun Chang `.
+
+:mod:`sklearn.preprocessing`
+............................
+
+- |Fix| :meth:`preprocessing.OrdinalEncoder.inverse_transform` correctly handles
+ use cases where `unknown_value` or `encoded_missing_value` is `nan`. :pr:`24087`
+ by `Thomas Fan`_.
+
+:mod:`sklearn.tree`
+...................
+
+- |Fix| Fixed invalid memory access bug during fit in
+ :class:`tree.DecisionTreeRegressor` and :class:`tree.DecisionTreeClassifier`.
+ :pr:`23273` by `Thomas Fan`_.
+
.. _changes_1_1_1:
Version 1.1.1
@@ -38,6 +138,19 @@ Changelog
:mod:`sklearn.feature_selection`
................................
+- |Fix| The `partial_fit` method of :class:`feature_selection.SelectFromModel`
+ now conducts validation for `max_features` and `feature_names_in` parameters.
+ :pr:`23299` by :user:`Long Bao `.
+
+:mod:`sklearn.decomposition`
+............................
+
+- |Fix| Avoid spurious warning in :class:`decomposition.IncrementalPCA` when
+ `n_samples == n_components`. :pr:`23264` by :user:`Lucy Liu `.
+
+:mod:`sklearn.feature_selection`
+................................
+
- |Fix| The `partial_fit` method of :class:`feature_selection.SelectFromModel`
now conducts validation for `max_features` and `feature_names_in` parameters.
:pr:`23299` by :user:`Long Bao `.
@@ -609,6 +722,9 @@ Changelog
:class:`ensemble.ExtraTreesClassifier`.
:pr:`20803` by :user:`Brian Sun `.
+- |Efficiency| Improve runtime performance of :class:`ensemble.IsolationForest`
+ by skipping repetitive input checks. :pr:`23149` by :user:`Zhehao Liu `.
+
:mod:`sklearn.feature_extraction`
.................................
diff --git a/examples/applications/plot_model_complexity_influence.py b/examples/applications/plot_model_complexity_influence.py
index d05f4ab497ada..60475e2c4302e 100644
--- a/examples/applications/plot_model_complexity_influence.py
+++ b/examples/applications/plot_model_complexity_influence.py
@@ -21,8 +21,12 @@
- :class:`~sklearn.svm.NuSVR` (for regression data) which implements
Nu support vector regression;
- - :class:`~sklearn.ensemble.GradientBoostingRegressor` (for regression
- data) which builds an additive model in a forward stage-wise fashion.
+ - :class:`~sklearn.ensemble.GradientBoostingRegressor` builds an additive
+ model in a forward stage-wise fashion. Notice that
+ :class:`~sklearn.ensemble.HistGradientBoostingRegressor` is much faster
+ than :class:`~sklearn.ensemble.GradientBoostingRegressor` starting with
+ intermediate datasets (`n_samples >= 10_000`), which is not the case for
+ this example.
We make the model complexity vary through the choice of relevant model
diff --git a/examples/classification/plot_lda.py b/examples/classification/plot_lda.py
index 47487fc1f2caf..4213fc614a31a 100644
--- a/examples/classification/plot_lda.py
+++ b/examples/classification/plot_lda.py
@@ -71,6 +71,7 @@ def generate_data(n_samples, n_features):
linewidth=2,
label="Linear Discriminant Analysis with Ledoit Wolf",
color="navy",
+ linestyle="dashed",
)
plt.plot(
features_samples_ratio,
@@ -78,6 +79,7 @@ def generate_data(n_samples, n_features):
linewidth=2,
label="Linear Discriminant Analysis",
color="gold",
+ linestyle="solid",
)
plt.plot(
features_samples_ratio,
@@ -85,6 +87,7 @@ def generate_data(n_samples, n_features):
linewidth=2,
label="Linear Discriminant Analysis with OAS",
color="red",
+ linestyle="dotted",
)
plt.xlabel("n_features / n_samples")
diff --git a/examples/cluster/plot_agglomerative_clustering.py b/examples/cluster/plot_agglomerative_clustering.py
index 9d590f572f121..5bb87a9386bf8 100644
--- a/examples/cluster/plot_agglomerative_clustering.py
+++ b/examples/cluster/plot_agglomerative_clustering.py
@@ -6,7 +6,7 @@
local structure in the data. The graph is simply the graph of 20 nearest
neighbors.
-Two consequences of imposing a connectivity can be seen. First, clustering
+There are two advantages of imposing a connectivity. First, clustering
without a connectivity matrix is much faster.
Second, when using a connectivity matrix, single, average and complete
diff --git a/examples/compose/plot_digits_pipe.py b/examples/compose/plot_digits_pipe.py
index acd3068d991c9..dcedfe0da2beb 100644
--- a/examples/compose/plot_digits_pipe.py
+++ b/examples/compose/plot_digits_pipe.py
@@ -37,7 +37,7 @@
pipe = Pipeline(steps=[("scaler", scaler), ("pca", pca), ("logistic", logistic)])
X_digits, y_digits = datasets.load_digits(return_X_y=True)
-# Parameters of pipelines can be set using ‘__’ separated parameter names:
+# Parameters of pipelines can be set using '__' separated parameter names:
param_grid = {
"pca__n_components": [5, 15, 30, 45, 60],
"logistic__C": np.logspace(-4, 4, 4),
diff --git a/examples/decomposition/plot_faces_decomposition.py b/examples/decomposition/plot_faces_decomposition.py
index 0eb07dc3efb2d..c21ac347c0e06 100644
--- a/examples/decomposition/plot_faces_decomposition.py
+++ b/examples/decomposition/plot_faces_decomposition.py
@@ -79,7 +79,7 @@ def plot_gallery(title, images, n_col=n_col, n_row=n_row, cmap=plt.cm.gray):
# %%
-# Let’s take a look at our data. Gray color indicates negative values,
+# Let's take a look at our data. Gray color indicates negative values,
# white indicates positive values.
plot_gallery("Faces from dataset", faces_centered[:n_components])
diff --git a/examples/ensemble/plot_feature_transformation.py b/examples/ensemble/plot_feature_transformation.py
index 409396a0376b8..36eb87bb757cd 100644
--- a/examples/ensemble/plot_feature_transformation.py
+++ b/examples/ensemble/plot_feature_transformation.py
@@ -39,7 +39,7 @@
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
-X, y = make_classification(n_samples=80000, random_state=10)
+X, y = make_classification(n_samples=80_000, random_state=10)
X_full_train, X_test, y_full_train, y_test = train_test_split(
X, y, test_size=0.5, random_state=10
@@ -72,6 +72,11 @@
_ = gradient_boosting.fit(X_train_ensemble, y_train_ensemble)
# %%
+# Notice that :class:`~sklearn.ensemble.HistGradientBoostingClassifier` is much
+# faster than :class:`~sklearn.ensemble.GradientBoostingClassifier` starting
+# with intermediate datasets (`n_samples >= 10_000`), which is not the case of
+# the present example.
+#
# The :class:`~sklearn.ensemble.RandomTreesEmbedding` is an unsupervised method
# and thus does not required to be trained independently.
diff --git a/examples/ensemble/plot_gradient_boosting_quantile.py b/examples/ensemble/plot_gradient_boosting_quantile.py
index 9e823439b948b..2aa04c3988d9e 100644
--- a/examples/ensemble/plot_gradient_boosting_quantile.py
+++ b/examples/ensemble/plot_gradient_boosting_quantile.py
@@ -72,6 +72,11 @@ def f(x):
all_models["q %1.2f" % alpha] = gbr.fit(X_train, y_train)
# %%
+# Notice that :class:`~sklearn.ensemble.HistGradientBoostingRegressor` is much
+# faster than :class:`~sklearn.ensemble.GradientBoostingRegressor` starting with
+# intermediate datasets (`n_samples >= 10_000`), which is not the case of the
+# present example.
+#
# For the sake of comparison, we also fit a baseline model trained with the
# usual (mean) squared error (MSE).
gbr_ls = GradientBoostingRegressor(loss="squared_error", **common_params)
diff --git a/examples/gaussian_process/plot_gpc_xor.py b/examples/gaussian_process/plot_gpc_xor.py
index 6eebbcf80098e..6e6217dba8b9e 100644
--- a/examples/gaussian_process/plot_gpc_xor.py
+++ b/examples/gaussian_process/plot_gpc_xor.py
@@ -29,7 +29,7 @@
# fit the model
plt.figure(figsize=(10, 5))
-kernels = [1.0 * RBF(length_scale=1.0), 1.0 * DotProduct(sigma_0=1.0) ** 2]
+kernels = [1.0 * RBF(length_scale=1.15), 1.0 * DotProduct(sigma_0=1.0) ** 2]
for i, kernel in enumerate(kernels):
clf = GaussianProcessClassifier(kernel=kernel, warm_start=True).fit(X, Y)
diff --git a/examples/gaussian_process/plot_gpr_noisy.py b/examples/gaussian_process/plot_gpr_noisy.py
index 04ea696e4319f..e15c9a6470d38 100644
--- a/examples/gaussian_process/plot_gpr_noisy.py
+++ b/examples/gaussian_process/plot_gpr_noisy.py
@@ -97,7 +97,7 @@ def target_generator(X, add_noise=False):
# %%
plt.plot(X, y, label="Expected signal")
-plt.scatter(x=X_train[:, 0], y=y_train, color="black", alpha=0.4, label="Observsations")
+plt.scatter(x=X_train[:, 0], y=y_train, color="black", alpha=0.4, label="Observations")
plt.errorbar(X, y_mean, y_std)
plt.legend()
plt.xlabel("X")
diff --git a/examples/gaussian_process/plot_gpr_prior_posterior.py b/examples/gaussian_process/plot_gpr_prior_posterior.py
index 437d67f5b0ab9..b36d49617432d 100644
--- a/examples/gaussian_process/plot_gpr_prior_posterior.py
+++ b/examples/gaussian_process/plot_gpr_prior_posterior.py
@@ -158,7 +158,7 @@ def plot_gpr_samples(gpr_model, n_samples, ax):
)
# %%
-# Periodic kernel
+# Exp-Sine-Squared kernel
# ...............
from sklearn.gaussian_process.kernels import ExpSineSquared
@@ -183,7 +183,7 @@ def plot_gpr_samples(gpr_model, n_samples, ax):
axs[1].legend(bbox_to_anchor=(1.05, 1.5), loc="upper left")
axs[1].set_title("Samples from posterior distribution")
-fig.suptitle("Periodic kernel", fontsize=18)
+fig.suptitle("Exp-Sine-Squared kernel", fontsize=18)
plt.tight_layout()
# %%
@@ -194,7 +194,7 @@ def plot_gpr_samples(gpr_model, n_samples, ax):
)
# %%
-# Dot product kernel
+# Dot-product kernel
# ..................
from sklearn.gaussian_process.kernels import ConstantKernel, DotProduct
@@ -216,7 +216,7 @@ def plot_gpr_samples(gpr_model, n_samples, ax):
axs[1].legend(bbox_to_anchor=(1.05, 1.5), loc="upper left")
axs[1].set_title("Samples from posterior distribution")
-fig.suptitle("Dot product kernel", fontsize=18)
+fig.suptitle("Dot-product kernel", fontsize=18)
plt.tight_layout()
# %%
@@ -227,7 +227,7 @@ def plot_gpr_samples(gpr_model, n_samples, ax):
)
# %%
-# Mattern kernel
+# Matérn kernel
# ..............
from sklearn.gaussian_process.kernels import Matern
@@ -247,7 +247,7 @@ def plot_gpr_samples(gpr_model, n_samples, ax):
axs[1].legend(bbox_to_anchor=(1.05, 1.5), loc="upper left")
axs[1].set_title("Samples from posterior distribution")
-fig.suptitle("Mattern kernel", fontsize=18)
+fig.suptitle("Matérn kernel", fontsize=18)
plt.tight_layout()
# %%
diff --git a/examples/inspection/plot_permutation_importance.py b/examples/inspection/plot_permutation_importance.py
index 53e4969f1740e..cd6823adf5f9b 100644
--- a/examples/inspection/plot_permutation_importance.py
+++ b/examples/inspection/plot_permutation_importance.py
@@ -58,7 +58,7 @@
# We define a predictive model based on a random forest. Therefore, we will make
# the following preprocessing steps:
#
-# - use :class:`~sklearn.preprocessing.OrdinaleEcnoder` to encode the
+# - use :class:`~sklearn.preprocessing.OrdinalEncoder` to encode the
# categorical features;
# - use :class:`~sklearn.impute.SimpleImputer` to fill missing values for
# numerical features using a mean strategy.
diff --git a/examples/model_selection/plot_grid_search_digits.py b/examples/model_selection/plot_grid_search_digits.py
index 2aaa64043749b..ec4360692aaf3 100644
--- a/examples/model_selection/plot_grid_search_digits.py
+++ b/examples/model_selection/plot_grid_search_digits.py
@@ -1,6 +1,6 @@
"""
============================================================
-Parameter estimation using grid search with cross-validation
+Custom refit strategy of a grid search with cross-validation
============================================================
This examples shows how a classifier is optimized by cross-validation,
@@ -13,56 +13,196 @@
More details on tools available for model selection can be found in the
sections on :ref:`cross_validation` and :ref:`grid_search`.
-
"""
+# %%
+# The dataset
+# -----------
+#
+# We will work with the `digits` dataset. The goal is to classify handwritten
+# digits images.
+# We transform the problem into a binary classification for easier
+# understanding: the goal is to identify whether a digit is `8` or not.
from sklearn import datasets
-from sklearn.model_selection import train_test_split
-from sklearn.model_selection import GridSearchCV
-from sklearn.metrics import classification_report
-from sklearn.svm import SVC
-# Loading the Digits dataset
-X, y = datasets.load_digits(return_X_y=True)
+digits = datasets.load_digits()
+
+# %%
+# In order to train a classifier on images, we need to flatten them into vectors.
+# Each image of 8 by 8 pixels needs to be transformed to a vector of 64 pixels.
+# Thus, we will get a final data array of shape `(n_images, n_pixels)`.
+n_samples = len(digits.images)
+X = digits.images.reshape((n_samples, -1))
+y = digits.target == 8
+print(
+ f"The number of images is {X.shape[0]} and each image contains {X.shape[1]} pixels"
+)
+
+# %%
+# As presented in the introduction, the data will be split into a training
+# and a testing set of equal size.
+from sklearn.model_selection import train_test_split
-# Split the dataset in two equal parts
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.5, random_state=0)
-# Set the parameters by cross-validation
+# %%
+# Define our grid-search strategy
+# -------------------------------
+#
+# We will select a classifier by searching the best hyper-parameters on folds
+# of the training set. To do this, we need to define
+# the scores to select the best candidate.
+
+scores = ["precision", "recall"]
+
+# %%
+# We can also define a function to be passed to the `refit` parameter of the
+# :class:`~sklearn.model_selection.GridSearchCV` instance. It will implement the
+# custom strategy to select the best candidate from the `cv_results_` attribute
+# of the :class:`~sklearn.model_selection.GridSearchCV`. Once the candidate is
+# selected, it is automatically refitted by the
+# :class:`~sklearn.model_selection.GridSearchCV` instance.
+#
+# Here, the strategy is to short-list the models which are the best in terms of
+# precision and recall. From the selected models, we finally select the fastest
+# model at predicting. Notice that these custom choices are completely
+# arbitrary.
+
+import pandas as pd
+
+
+def print_dataframe(filtered_cv_results):
+ """Pretty print for filtered dataframe"""
+ for mean_precision, std_precision, mean_recall, std_recall, params in zip(
+ filtered_cv_results["mean_test_precision"],
+ filtered_cv_results["std_test_precision"],
+ filtered_cv_results["mean_test_recall"],
+ filtered_cv_results["std_test_recall"],
+ filtered_cv_results["params"],
+ ):
+ print(
+ f"precision: {mean_precision:0.3f} (±{std_precision:0.03f}),"
+ f" recall: {mean_recall:0.3f} (±{std_recall:0.03f}),"
+ f" for {params}"
+ )
+ print()
+
+
+def refit_strategy(cv_results):
+ """Define the strategy to select the best estimator.
+
+ The strategy defined here is to filter-out all results below a precision threshold
+ of 0.98, rank the remaining by recall and keep all models with one standard
+ deviation of the best by recall. Once these models are selected, we can select the
+ fastest model to predict.
+
+ Parameters
+ ----------
+ cv_results : dict of numpy (masked) ndarrays
+ CV results as returned by the `GridSearchCV`.
+
+ Returns
+ -------
+ best_index : int
+ The index of the best estimator as it appears in `cv_results`.
+ """
+ # print the info about the grid-search for the different scores
+ precision_threshold = 0.98
+
+ cv_results_ = pd.DataFrame(cv_results)
+ print("All grid-search results:")
+ print_dataframe(cv_results_)
+
+ # Filter-out all results below the threshold
+ high_precision_cv_results = cv_results_[
+ cv_results_["mean_test_precision"] > precision_threshold
+ ]
+
+ print(f"Models with a precision higher than {precision_threshold}:")
+ print_dataframe(high_precision_cv_results)
+
+ high_precision_cv_results = high_precision_cv_results[
+ [
+ "mean_score_time",
+ "mean_test_recall",
+ "std_test_recall",
+ "mean_test_precision",
+ "std_test_precision",
+ "rank_test_recall",
+ "rank_test_precision",
+ "params",
+ ]
+ ]
+
+ # Select the most performant models in terms of recall
+ # (within 1 sigma from the best)
+ best_recall_std = high_precision_cv_results["mean_test_recall"].std()
+ best_recall = high_precision_cv_results["mean_test_recall"].max()
+ best_recall_threshold = best_recall - best_recall_std
+
+ high_recall_cv_results = high_precision_cv_results[
+ high_precision_cv_results["mean_test_recall"] > best_recall_threshold
+ ]
+ print(
+ "Out of the previously selected high precision models, we keep all the\n"
+ "the models within one standard deviation of the highest recall model:"
+ )
+ print_dataframe(high_recall_cv_results)
+
+ # From the best candidates, select the fastest model to predict
+ fastest_top_recall_high_precision_index = high_recall_cv_results[
+ "mean_score_time"
+ ].idxmin()
+
+ print(
+ "\nThe selected final model is the fastest to predict out of the previously\n"
+ "selected subset of best models based on precision and recall.\n"
+ "Its scoring time is:\n\n"
+ f"{high_recall_cv_results.loc[fastest_top_recall_high_precision_index]}"
+ )
+
+ return fastest_top_recall_high_precision_index
+
+
+# %%
+#
+# Tuning hyper-parameters
+# -----------------------
+#
+# Once we defined our strategy to select the best model, we define the values
+# of the hyper-parameters and create the grid-search instance:
+from sklearn.model_selection import GridSearchCV
+from sklearn.svm import SVC
+
tuned_parameters = [
{"kernel": ["rbf"], "gamma": [1e-3, 1e-4], "C": [1, 10, 100, 1000]},
{"kernel": ["linear"], "C": [1, 10, 100, 1000]},
]
-scores = ["precision", "recall"]
-
-for score in scores:
- print("# Tuning hyper-parameters for %s" % score)
- print()
+grid_search = GridSearchCV(
+ SVC(), tuned_parameters, scoring=scores, refit=refit_strategy
+)
+grid_search.fit(X_train, y_train)
- clf = GridSearchCV(SVC(), tuned_parameters, scoring="%s_macro" % score)
- clf.fit(X_train, y_train)
+# %%
+#
+# The parameters selected by the grid-search with our custom strategy are:
+grid_search.best_params_
- print("Best parameters set found on development set:")
- print()
- print(clf.best_params_)
- print()
- print("Grid scores on development set:")
- print()
- means = clf.cv_results_["mean_test_score"]
- stds = clf.cv_results_["std_test_score"]
- for mean, std, params in zip(means, stds, clf.cv_results_["params"]):
- print("%0.3f (+/-%0.03f) for %r" % (mean, std * 2, params))
- print()
+# %%
+#
+# Finally, we evaluate the fine-tuned model on the left-out evaluation set: the
+# `grid_search` object **has automatically been refit** on the full training
+# set with the parameters selected by our custom refit strategy.
+#
+# We can use the classification report to compute standard classification
+# metrics on the left-out set:
+from sklearn.metrics import classification_report
- print("Detailed classification report:")
- print()
- print("The model is trained on the full development set.")
- print("The scores are computed on the full evaluation set.")
- print()
- y_true, y_pred = y_test, clf.predict(X_test)
- print(classification_report(y_true, y_pred))
- print()
+y_pred = grid_search.predict(X_test)
+print(classification_report(y_test, y_pred))
-# Note the problem is too easy: the hyperparameter plateau is too flat and the
-# output model is the same for precision and recall with ties in quality.
+# %%
+# .. note::
+# The problem is too easy: the hyperparameter plateau is too flat and the
+# output model is the same for precision and recall with ties in quality.
diff --git a/examples/model_selection/plot_learning_curve.py b/examples/model_selection/plot_learning_curve.py
index 25f43d8b8a3e4..5430f673d76a5 100644
--- a/examples/model_selection/plot_learning_curve.py
+++ b/examples/model_selection/plot_learning_curve.py
@@ -35,6 +35,7 @@ def plot_learning_curve(
ylim=None,
cv=None,
n_jobs=None,
+ scoring=None,
train_sizes=np.linspace(0.1, 1.0, 5),
):
"""
@@ -86,6 +87,11 @@ def plot_learning_curve(
``-1`` means using all processors. See :term:`Glossary `
for more details.
+ scoring : str or callable, default=None
+ A str (see model evaluation documentation) or
+ a scorer callable object / function with signature
+ ``scorer(estimator, X, y)``.
+
train_sizes : array-like of shape (n_ticks,)
Relative or absolute numbers of training examples that will be used to
generate the learning curve. If the ``dtype`` is float, it is regarded
@@ -109,6 +115,7 @@ def plot_learning_curve(
estimator,
X,
y,
+ scoring=scoring,
cv=cv,
n_jobs=n_jobs,
train_sizes=train_sizes,
@@ -189,7 +196,15 @@ def plot_learning_curve(
estimator = GaussianNB()
plot_learning_curve(
- estimator, title, X, y, axes=axes[:, 0], ylim=(0.7, 1.01), cv=cv, n_jobs=4
+ estimator,
+ title,
+ X,
+ y,
+ axes=axes[:, 0],
+ ylim=(0.7, 1.01),
+ cv=cv,
+ n_jobs=4,
+ scoring="accuracy",
)
title = r"Learning Curves (SVM, RBF kernel, $\gamma=0.001$)"
diff --git a/examples/neighbors/plot_species_kde.py b/examples/neighbors/plot_species_kde.py
index c409d354ec986..35ea40158a45c 100644
--- a/examples/neighbors/plot_species_kde.py
+++ b/examples/neighbors/plot_species_kde.py
@@ -19,7 +19,7 @@
The two species are:
- `"Bradypus variegatus"
- `_ ,
+ `_ ,
the Brown-throated Sloth.
- `"Microryzomys minutus"
diff --git a/examples/preprocessing/plot_all_scaling.py b/examples/preprocessing/plot_all_scaling.py
index 49af744011d12..d8a20ece5c56c 100644
--- a/examples/preprocessing/plot_all_scaling.py
+++ b/examples/preprocessing/plot_all_scaling.py
@@ -324,7 +324,7 @@ def make_plot(item_idx):
#
# Unlike the previous scalers, the centering and scaling statistics of
# :class:`~sklearn.preprocessing.RobustScaler`
-# is based on percentiles and are therefore not influenced by a few
+# are based on percentiles and are therefore not influenced by a small
# number of very large marginal outliers. Consequently, the resulting range of
# the transformed feature values is larger than for the previous scalers and,
# more importantly, are approximately similar: for both features most of the
diff --git a/examples/text/plot_document_classification_20newsgroups.py b/examples/text/plot_document_classification_20newsgroups.py
index 7f24861a0e9ce..7eb14a94a724f 100644
--- a/examples/text/plot_document_classification_20newsgroups.py
+++ b/examples/text/plot_document_classification_20newsgroups.py
@@ -3,45 +3,42 @@
Classification of text documents using sparse features
======================================================
-This is an example showing how scikit-learn can be used to classify documents
-by topics using a bag-of-words approach. This example uses a scipy.sparse
-matrix to store the features and demonstrates various classifiers that can
-efficiently handle sparse matrices.
+This is an example showing how scikit-learn can be used to classify documents by
+topics using a `Bag of Words approach
+`_. This example uses a
+Tf-idf-weighted document-term sparse matrix to encode the features and
+demonstrates various classifiers that can efficiently handle sparse matrices.
-The dataset used in this example is the 20 newsgroups dataset. It will be
-automatically downloaded, then cached.
+For document analysis via an unsupervised learning approach, see the example
+script :ref:`sphx_glr_auto_examples_text_plot_document_clustering.py`.
"""
# Author: Peter Prettenhofer
# Olivier Grisel
# Mathieu Blondel
+# Arturo Amor
# Lars Buitinck
# License: BSD 3 clause
# %%
-# Configuration options for the analysis
-# --------------------------------------
-
-# If True, we use `HashingVectorizer`, otherwise we use a `TfidfVectorizer`
-USE_HASHING = False
-
-# Number of features used by `HashingVectorizer`
-N_FEATURES = 2**16
-
-# Optional feature selection: either False, or an integer: the number of
-# features to select
-SELECT_CHI2 = False
-
+# Loading and vectorizing the 20 newsgroups text dataset
+# ======================================================
+#
+# We define a function to load data from :ref:`20newsgroups_dataset`, which
+# comprises around 18,000 newsgroups posts on 20 topics split in two subsets:
+# one for training (or development) and the other one for testing (or for
+# performance evaluation). Note that, by default, the text samples contain some
+# message metadata such as `'headers'`, `'footers'` (signatures) and `'quotes'`
+# to other posts. The `fetch_20newsgroups` function therefore accepts a
+# parameter named `remove` to attempt stripping such information that can make
+# the classification problem "too easy". This is achieved using simple
+# heuristics that are neither perfect nor standard, hence disabled by default.
-# %%
-# Load data from the training set
-# ------------------------------------
-# Let's load data from the newsgroups dataset which comprises around 18000
-# newsgroups posts on 20 topics split in two subsets: one for training (or
-# development) and the other one for testing (or for performance evaluation).
from sklearn.datasets import fetch_20newsgroups
+from sklearn.feature_extraction.text import TfidfVectorizer
+from time import time
categories = [
"alt.atheism",
@@ -50,157 +47,322 @@
"sci.space",
]
-data_train = fetch_20newsgroups(
- subset="train", categories=categories, shuffle=True, random_state=42
-)
-data_test = fetch_20newsgroups(
- subset="test", categories=categories, shuffle=True, random_state=42
-)
-print("data loaded")
+def size_mb(docs):
+ return sum(len(s.encode("utf-8")) for s in docs) / 1e6
-# order of labels in `target_names` can be different from `categories`
-target_names = data_train.target_names
+def load_dataset(verbose=False, remove=()):
+ """Load and vectorize the 20 newsgroups dataset."""
-def size_mb(docs):
- return sum(len(s.encode("utf-8")) for s in docs) / 1e6
+ data_train = fetch_20newsgroups(
+ subset="train",
+ categories=categories,
+ shuffle=True,
+ random_state=42,
+ remove=remove,
+ )
+ data_test = fetch_20newsgroups(
+ subset="test",
+ categories=categories,
+ shuffle=True,
+ random_state=42,
+ remove=remove,
+ )
-data_train_size_mb = size_mb(data_train.data)
-data_test_size_mb = size_mb(data_test.data)
+ # order of labels in `target_names` can be different from `categories`
+ target_names = data_train.target_names
+
+ # split target in a training set and a test set
+ y_train, y_test = data_train.target, data_test.target
+
+ # Extracting features from the training data using a sparse vectorizer
+ t0 = time()
+ vectorizer = TfidfVectorizer(
+ sublinear_tf=True, max_df=0.5, min_df=5, stop_words="english"
+ )
+ X_train = vectorizer.fit_transform(data_train.data)
+ duration_train = time() - t0
+
+ # Extracting features from the test data using the same vectorizer
+ t0 = time()
+ X_test = vectorizer.transform(data_test.data)
+ duration_test = time() - t0
+
+ feature_names = vectorizer.get_feature_names_out()
+
+ if verbose:
+
+ # compute size of loaded data
+ data_train_size_mb = size_mb(data_train.data)
+ data_test_size_mb = size_mb(data_test.data)
+
+ print(
+ f"{len(data_train.data)} documents - "
+ f"{data_train_size_mb:.2f}MB (training set)"
+ )
+ print(f"{len(data_test.data)} documents - {data_test_size_mb:.2f}MB (test set)")
+ print(f"{len(target_names)} categories")
+ print(
+ f"vectorize training done in {duration_train:.3f}s "
+ f"at {data_train_size_mb / duration_train:.3f}MB/s"
+ )
+ print(f"n_samples: {X_train.shape[0]}, n_features: {X_train.shape[1]}")
+ print(
+ f"vectorize testing done in {duration_test:.3f}s "
+ f"at {data_test_size_mb / duration_test:.3f}MB/s"
+ )
+ print(f"n_samples: {X_test.shape[0]}, n_features: {X_test.shape[1]}")
+
+ return X_train, X_test, y_train, y_test, feature_names, target_names
-print(
- "%d documents - %0.3fMB (training set)" % (len(data_train.data), data_train_size_mb)
-)
-print("%d documents - %0.3fMB (test set)" % (len(data_test.data), data_test_size_mb))
-print("%d categories" % len(target_names))
# %%
-# Vectorize the training and test data
-# -------------------------------------
+# Analysis of a bag-of-words document classifier
+# ==============================================
+#
+# We will now train a classifier twice, once on the text samples including
+# metadata and once after stripping the metadata. For both cases we will analyze
+# the classification errors on a test set using a confusion matrix and inspect
+# the coefficients that define the classification function of the trained
+# models.
#
-# split a training set and a test set
-y_train, y_test = data_train.target, data_test.target
+# Model without metadata stripping
+# --------------------------------
+#
+# We start by using the custom function `load_dataset` to load the data without
+# metadata stripping.
+
+X_train, X_test, y_train, y_test, feature_names, target_names = load_dataset(
+ verbose=True
+)
# %%
-# Extracting features from the training data using a sparse vectorizer
-from time import time
+# Our first model is an instance of the
+# :class:`~sklearn.linear_model.RidgeClassifier` class. This is a linear
+# classification model that uses the mean squared error on {-1, 1} encoded
+# targets, one for each possible class. Contrary to
+# :class:`~sklearn.linear_model.LogisticRegression`,
+# :class:`~sklearn.linear_model.RidgeClassifier` does not
+# provide probabilistic predictions (no `predict_proba` method),
+# but it is often faster to train.
-from sklearn.feature_extraction.text import TfidfVectorizer
-from sklearn.feature_extraction.text import HashingVectorizer
+from sklearn.linear_model import RidgeClassifier
+
+clf = RidgeClassifier(tol=1e-2, solver="sparse_cg")
+clf.fit(X_train, y_train)
+pred = clf.predict(X_test)
+
+# %%
+# We plot the confusion matrix of this classifier to find if there is a pattern
+# in the classification errors.
-t0 = time()
+import matplotlib.pyplot as plt
+from sklearn.metrics import ConfusionMatrixDisplay
+
+fig, ax = plt.subplots(figsize=(10, 5))
+ConfusionMatrixDisplay.from_predictions(y_test, pred, ax=ax)
+ax.xaxis.set_ticklabels(target_names)
+ax.yaxis.set_ticklabels(target_names)
+_ = ax.set_title(
+ f"Confusion Matrix for {clf.__class__.__name__}\non the original documents"
+)
+
+# %%
+# The confusion matrix highlights that documents of the `alt.atheism` class are
+# often confused with documents with the class `talk.religion.misc` class and
+# vice-versa which is expected since the topics are semantically related.
+#
+# We also observe that some documents of the `sci.space` class can be misclassified as
+# `comp.graphics` while the converse is much rarer. A manual inspection of those
+# badly classified documents would be required to get some insights on this
+# asymmetry. It could be the case that the vocabulary of the space topic could
+# be more specific than the vocabulary for computer graphics.
+#
+# We can gain a deeper understanding of how this classifier makes its decisions
+# by looking at the words with the highest average feature effects:
-if USE_HASHING:
- vectorizer = HashingVectorizer(
- stop_words="english", alternate_sign=False, n_features=N_FEATURES
+import pandas as pd
+import numpy as np
+
+
+def plot_feature_effects():
+ # learned coefficients weighted by frequency of appearance
+ average_feature_effects = clf.coef_ * np.asarray(X_train.mean(axis=0)).ravel()
+
+ for i, label in enumerate(target_names):
+ top5 = np.argsort(average_feature_effects[i])[-5:][::-1]
+ if i == 0:
+ top = pd.DataFrame(feature_names[top5], columns=[label])
+ top_indices = top5
+ else:
+ top[label] = feature_names[top5]
+ top_indices = np.concatenate((top_indices, top5), axis=None)
+ top_indices = np.unique(top_indices)
+ predictive_words = feature_names[top_indices]
+
+ # plot feature effects
+ bar_size = 0.25
+ padding = 0.75
+ y_locs = np.arange(len(top_indices)) * (4 * bar_size + padding)
+
+ fig, ax = plt.subplots(figsize=(10, 8))
+ for i, label in enumerate(target_names):
+ ax.barh(
+ y_locs + (i - 2) * bar_size,
+ average_feature_effects[i, top_indices],
+ height=bar_size,
+ label=label,
+ )
+ ax.set(
+ yticks=y_locs,
+ yticklabels=predictive_words,
+ ylim=[
+ 0 - 4 * bar_size,
+ len(top_indices) * (4 * bar_size + padding) - 4 * bar_size,
+ ],
)
- X_train = vectorizer.transform(data_train.data)
-else:
- vectorizer = TfidfVectorizer(sublinear_tf=True, max_df=0.5, stop_words="english")
- X_train = vectorizer.fit_transform(data_train.data)
-duration = time() - t0
-print("done in %fs at %0.3fMB/s" % (duration, data_train_size_mb / duration))
-print("n_samples: %d, n_features: %d" % X_train.shape)
+ ax.legend(loc="lower right")
+
+ print("top 5 keywords per class:")
+ print(top)
+
+ return ax
+
+
+_ = plot_feature_effects().set_title("Average feature effect on the original data")
# %%
-# Extracting features from the test data using the same vectorizer
-t0 = time()
-X_test = vectorizer.transform(data_test.data)
-duration = time() - t0
-print("done in %fs at %0.3fMB/s" % (duration, data_test_size_mb / duration))
-print("n_samples: %d, n_features: %d" % X_test.shape)
+# We can observe that the most predictive words are often strongly positively
+# associated with a single class and negatively associated with all the other
+# classes. Most of those positive associations are quite easy to interpret.
+# However, some words such as `"god"` and `"people"` are positively associated to
+# both `"talk.misc.religion"` and `"alt.atheism"` as those two classes expectedly
+# share some common vocabulary. Notice however that there are also words such as
+# `"christian"` and `"morality"` that are only positively associated with
+# `"talk.misc.religion"`. Furthermore, in this version of the dataset, the word
+# `"caltech"` is one of the top predictive features for atheism due to pollution
+# in the dataset coming from some sort of metadata such as the email addresses
+# of the sender of previous emails in the discussion as can be seen below:
+
+data_train = fetch_20newsgroups(
+ subset="train", categories=categories, shuffle=True, random_state=42
+)
+
+for doc in data_train.data:
+ if "caltech" in doc:
+ print(doc)
+ break
# %%
-# mapping from integer feature name to original token string
-if USE_HASHING:
- feature_names = None
-else:
- feature_names = vectorizer.get_feature_names_out()
+# Such headers, signature footers (and quoted metadata from previous messages)
+# can be considered side information that artificially reveals the newsgroup by
+# identifying the registered members and one would rather want our text
+# classifier to only learn from the "main content" of each text document instead
+# of relying on the leaked identity of the writers.
+#
+# Model with metadata stripping
+# -----------------------------
+#
+# The `remove` option of the 20 newsgroups dataset loader in scikit-learn allows
+# to heuristically attempt to filter out some of this unwanted metadata that
+# makes the classification problem artificially easier. Be aware that such
+# filtering of the text contents is far from perfect.
+#
+# Let us try to leverage this option to train a text classifier that does not
+# rely too much on this kind of metadata to make its decisions:
+(
+ X_train,
+ X_test,
+ y_train,
+ y_test,
+ feature_names,
+ target_names,
+) = load_dataset(remove=("headers", "footers", "quotes"))
+
+clf = RidgeClassifier(tol=1e-2, solver="sparse_cg")
+clf.fit(X_train, y_train)
+pred = clf.predict(X_test)
+
+fig, ax = plt.subplots(figsize=(10, 5))
+ConfusionMatrixDisplay.from_predictions(y_test, pred, ax=ax)
+ax.xaxis.set_ticklabels(target_names)
+ax.yaxis.set_ticklabels(target_names)
+_ = ax.set_title(
+ f"Confusion Matrix for {clf.__class__.__name__}\non filtered documents"
+)
# %%
-# Keeping only the best features
-from sklearn.feature_selection import SelectKBest, chi2
+# By looking at the confusion matrix, it is more evident that the scores of the
+# model trained with metadata were over-optimistic. The classification problem
+# without access to the metadata is less accurate but more representative of the
+# intended text classification problem.
-if SELECT_CHI2:
- print("Extracting %d best features by a chi-squared test" % SELECT_CHI2)
- t0 = time()
- ch2 = SelectKBest(chi2, k=SELECT_CHI2)
- X_train = ch2.fit_transform(X_train, y_train)
- X_test = ch2.transform(X_test)
- if feature_names is not None:
- # keep selected feature names
- feature_names = feature_names[ch2.get_support()]
- print("done in %fs" % (time() - t0))
- print()
+_ = plot_feature_effects().set_title("Average feature effects on filtered documents")
+# %%
+# In the next section we keep the dataset without metadata to compare several
+# classifiers.
# %%
-# Benchmark classifiers
-# ------------------------------------
+# Benchmarking classifiers
+# ========================
#
-# First we define small benchmarking utilities
-import numpy as np
-from sklearn import metrics
-from sklearn.utils.extmath import density
+# Scikit-learn provides many different kinds of classification algorithms. In
+# this section we will train a selection of those classifiers on the same text
+# classification problem and measure both their generalization performance
+# (accuracy on the test set) and their computation performance (speed), both at
+# training time and testing time. For such purpose we define the following
+# benchmarking utilities:
-
-def trim(s):
- """Trim string to fit on terminal (assuming 80-column display)"""
- return s if len(s) <= 80 else s[:77] + "..."
+from sklearn.utils.extmath import density
+from sklearn import metrics
-def benchmark(clf):
+def benchmark(clf, custom_name=False):
print("_" * 80)
print("Training: ")
print(clf)
t0 = time()
clf.fit(X_train, y_train)
train_time = time() - t0
- print("train time: %0.3fs" % train_time)
+ print(f"train time: {train_time:.3}s")
t0 = time()
pred = clf.predict(X_test)
test_time = time() - t0
- print("test time: %0.3fs" % test_time)
+ print(f"test time: {test_time:.3}s")
score = metrics.accuracy_score(y_test, pred)
- print("accuracy: %0.3f" % score)
+ print(f"accuracy: {score:.3}")
if hasattr(clf, "coef_"):
- print("dimensionality: %d" % clf.coef_.shape[1])
- print("density: %f" % density(clf.coef_))
-
- if feature_names is not None:
- print("top 10 keywords per class:")
- for i, label in enumerate(target_names):
- top10 = np.argsort(clf.coef_[i])[-10:]
- print(trim("%s: %s" % (label, " ".join(feature_names[top10]))))
+ print(f"dimensionality: {clf.coef_.shape[1]}")
+ print(f"density: {density(clf.coef_)}")
print()
- print("classification report:")
- print(metrics.classification_report(y_test, pred, target_names=target_names))
-
- print("confusion matrix:")
- print(metrics.confusion_matrix(y_test, pred))
-
print()
- clf_descr = str(clf).split("(")[0]
+ if custom_name:
+ clf_descr = str(custom_name)
+ else:
+ clf_descr = clf.__class__.__name__
return clf_descr, score, train_time, test_time
# %%
-# We now train and test the datasets with 15 different classification
-# models and get performance results for each model.
-from sklearn.feature_selection import SelectFromModel
-from sklearn.linear_model import RidgeClassifier
-from sklearn.pipeline import Pipeline
+# We now train and test the datasets with 8 different classification models and
+# get performance results for each model. The goal of this study is to highlight
+# the computation/accuracy tradeoffs of different types of classifiers for
+# such a multi-class text classification problem.
+#
+# Notice that the most important hyperparameters values were tuned using a grid
+# search procedure not shown in this notebook for the sake of simplicity.
+
+from sklearn.linear_model import LogisticRegression
from sklearn.svm import LinearSVC
from sklearn.linear_model import SGDClassifier
-from sklearn.linear_model import Perceptron
-from sklearn.linear_model import PassiveAggressiveClassifier
-from sklearn.naive_bayes import BernoulliNB, ComplementNB, MultinomialNB
+from sklearn.naive_bayes import ComplementNB
from sklearn.neighbors import KNeighborsClassifier
from sklearn.neighbors import NearestCentroid
from sklearn.ensemble import RandomForestClassifier
@@ -208,90 +370,84 @@ def benchmark(clf):
results = []
for clf, name in (
- (RidgeClassifier(tol=1e-2, solver="sag"), "Ridge Classifier"),
- (Perceptron(max_iter=50), "Perceptron"),
- (PassiveAggressiveClassifier(max_iter=50), "Passive-Aggressive"),
- (KNeighborsClassifier(n_neighbors=10), "kNN"),
- (RandomForestClassifier(), "Random forest"),
+ (LogisticRegression(C=5, max_iter=1000), "Logistic Regression"),
+ (RidgeClassifier(alpha=1.0, solver="sparse_cg"), "Ridge Classifier"),
+ (KNeighborsClassifier(n_neighbors=100), "kNN"),
+ (RandomForestClassifier(), "Random Forest"),
+ # L2 penalty Linear SVC
+ (LinearSVC(C=0.1, dual=False, max_iter=1000), "Linear SVC"),
+ # L2 penalty Linear SGD
+ (
+ SGDClassifier(
+ loss="log_loss", alpha=1e-4, n_iter_no_change=3, early_stopping=True
+ ),
+ "log-loss SGD",
+ ),
+ # NearestCentroid (aka Rocchio classifier)
+ (NearestCentroid(), "NearestCentroid"),
+ # Sparse naive Bayes classifier
+ (ComplementNB(alpha=0.1), "Complement naive Bayes"),
):
print("=" * 80)
print(name)
- results.append(benchmark(clf))
-
-for penalty in ["l2", "l1"]:
- print("=" * 80)
- print("%s penalty" % penalty.upper())
- # Train Liblinear model
- results.append(benchmark(LinearSVC(penalty=penalty, dual=False, tol=1e-3)))
-
- # Train SGD model
- results.append(benchmark(SGDClassifier(alpha=0.0001, max_iter=50, penalty=penalty)))
-
-# Train SGD with Elastic Net penalty
-print("=" * 80)
-print("Elastic-Net penalty")
-results.append(
- benchmark(SGDClassifier(alpha=0.0001, max_iter=50, penalty="elasticnet"))
-)
-
-# Train NearestCentroid without threshold
-print("=" * 80)
-print("NearestCentroid (aka Rocchio classifier)")
-results.append(benchmark(NearestCentroid()))
-
-# Train sparse Naive Bayes classifiers
-print("=" * 80)
-print("Naive Bayes")
-results.append(benchmark(MultinomialNB(alpha=0.01)))
-results.append(benchmark(BernoulliNB(alpha=0.01)))
-results.append(benchmark(ComplementNB(alpha=0.1)))
-
-print("=" * 80)
-print("LinearSVC with L1-based feature selection")
-# The smaller C, the stronger the regularization.
-# The more regularization, the more sparsity.
-results.append(
- benchmark(
- Pipeline(
- [
- (
- "feature_selection",
- SelectFromModel(LinearSVC(penalty="l1", dual=False, tol=1e-3)),
- ),
- ("classification", LinearSVC(penalty="l2")),
- ]
- )
- )
-)
-
+ results.append(benchmark(clf, name))
# %%
-# Add plots
-# ------------------------------------
-# The bar plot indicates the accuracy, training time (normalized) and test time
-# (normalized) of each classifier.
-import matplotlib.pyplot as plt
+# Plot accuracy, training and test time of each classifier
+# ========================================================
+#
+# The scatter plots show the trade-off between the test accuracy and the
+# training and testing time of each classifier.
indices = np.arange(len(results))
results = [[x[i] for x in results] for i in range(4)]
clf_names, score, training_time, test_time = results
-training_time = np.array(training_time) / np.max(training_time)
-test_time = np.array(test_time) / np.max(test_time)
-
-plt.figure(figsize=(12, 8))
-plt.title("Score")
-plt.barh(indices, score, 0.2, label="score", color="navy")
-plt.barh(indices + 0.3, training_time, 0.2, label="training time", color="c")
-plt.barh(indices + 0.6, test_time, 0.2, label="test time", color="darkorange")
-plt.yticks(())
-plt.legend(loc="best")
-plt.subplots_adjust(left=0.25)
-plt.subplots_adjust(top=0.95)
-plt.subplots_adjust(bottom=0.05)
-
-for i, c in zip(indices, clf_names):
- plt.text(-0.3, i, c)
-
-plt.show()
+training_time = np.array(training_time)
+test_time = np.array(test_time)
+
+fig, ax1 = plt.subplots(figsize=(10, 8))
+ax1.scatter(score, training_time, s=60)
+ax1.set(
+ title="Score-training time trade-off",
+ yscale="log",
+ xlabel="test accuracy",
+ ylabel="training time (s)",
+)
+fig, ax2 = plt.subplots(figsize=(10, 8))
+ax2.scatter(score, test_time, s=60)
+ax2.set(
+ title="Score-test time trade-off",
+ yscale="log",
+ xlabel="test accuracy",
+ ylabel="test time (s)",
+)
+
+for i, txt in enumerate(clf_names):
+ ax1.annotate(txt, (score[i], training_time[i]))
+ ax2.annotate(txt, (score[i], test_time[i]))
+
+# %%
+# The naive Bayes model has the best trade-off between score and
+# training/testing time, while Random Forest is both slow to train, expensive to
+# predict and has a comparatively bad accuracy. This is expected: for
+# high-dimensional prediction problems, linear models are often better suited as
+# most problems become linearly separable when the feature space has 10,000
+# dimensions or more.
+#
+# The difference in training speed and accuracy of the linear models can be
+# explained by the choice of the loss function they optimize and the kind of
+# regularization they use. Be aware that some linear models with the same loss
+# but a different solver or regularization configuration may yield different
+# fitting times and test accuracy. We can observe on the second plot that once
+# trained, all linear models have approximately the same prediction speed which
+# is expected because they all implement the same prediction function.
+#
+# KNeighborsClassifier has a relatively low accuracy and has the highest testing
+# time. The long prediction time is also expected: for each prediction the model
+# has to compute the pairwise distances between the testing sample and each
+# document in the training set, which is computationally expensive. Furthermore,
+# the "curse of dimensionality" harms the ability of this model to yield
+# competitive accuracy in the high dimensional feature space of text
+# classification problems.
diff --git a/examples/text/plot_document_clustering.py b/examples/text/plot_document_clustering.py
index 66b25ec9851de..04446fd82f964 100644
--- a/examples/text/plot_document_clustering.py
+++ b/examples/text/plot_document_clustering.py
@@ -3,284 +3,448 @@
Clustering text documents using k-means
=======================================
-This is an example showing how the scikit-learn can be used to cluster
-documents by topics using a bag-of-words approach. This example uses
-a scipy.sparse matrix to store the features instead of standard numpy arrays.
-
-Two feature extraction methods can be used in this example:
-
- - TfidfVectorizer uses a in-memory vocabulary (a python dict) to map the most
- frequent words to features indices and hence compute a word occurrence
- frequency (sparse) matrix. The word frequencies are then reweighted using
- the Inverse Document Frequency (IDF) vector collected feature-wise over
- the corpus.
-
- - HashingVectorizer hashes word occurrences to a fixed dimensional space,
- possibly with collisions. The word count vectors are then normalized to
- each have l2-norm equal to one (projected to the euclidean unit-ball) which
- seems to be important for k-means to work in high dimensional space.
-
- HashingVectorizer does not provide IDF weighting as this is a stateless
- model (the fit method does nothing). When IDF weighting is needed it can
- be added by pipelining its output to a TfidfTransformer instance.
-
-Two algorithms are demoed: ordinary k-means and its more scalable cousin
-minibatch k-means.
-
-Additionally, latent semantic analysis can also be used to reduce
-dimensionality and discover latent patterns in the data.
-
-It can be noted that k-means (and minibatch k-means) are very sensitive to
-feature scaling and that in this case the IDF weighting helps improve the
-quality of the clustering by quite a lot as measured against the "ground truth"
-provided by the class label assignments of the 20 newsgroups dataset.
-
-This improvement is not visible in the Silhouette Coefficient which is small
-for both as this measure seem to suffer from the phenomenon called
-"Concentration of Measure" or "Curse of Dimensionality" for high dimensional
-datasets such as text data. Other measures such as V-measure and Adjusted Rand
-Index are information theoretic based evaluation scores: as they are only based
-on cluster assignments rather than distances, hence not affected by the curse
-of dimensionality.
-
-Note: as k-means is optimizing a non-convex objective function, it will likely
-end up in a local optimum. Several runs with independent random init might be
-necessary to get a good convergence.
+This is an example showing how the scikit-learn API can be used to cluster
+documents by topics using a `Bag of Words approach
+`_.
+
+Two algorithms are demoed: :class:`~sklearn.cluster.KMeans` and its more
+scalable variant, :class:`~sklearn.cluster.MiniBatchKMeans`. Additionally,
+latent semantic analysis is used to reduce dimensionality and discover latent
+patterns in the data.
+
+This example uses two different text vectorizers: a
+:class:`~sklearn.feature_extraction.text.TfidfVectorizer` and a
+:class:`~sklearn.feature_extraction.text.HashingVectorizer`. See the example
+notebook :ref:`sphx_glr_auto_examples_text_plot_hashing_vs_dict_vectorizer.py`
+for more information on vectorizers and a comparison of their processing times.
+
+For document analysis via a supervised learning approach, see the example script
+:ref:`sphx_glr_auto_examples_text_plot_document_classification_20newsgroups.py`.
"""
# Author: Peter Prettenhofer
# Lars Buitinck
+# Olivier Grisel
+# Arturo Amor
# License: BSD 3 clause
-from sklearn.datasets import fetch_20newsgroups
-from sklearn.decomposition import TruncatedSVD
-from sklearn.feature_extraction.text import TfidfVectorizer
-from sklearn.feature_extraction.text import HashingVectorizer
-from sklearn.feature_extraction.text import TfidfTransformer
-from sklearn.pipeline import make_pipeline
-from sklearn.preprocessing import Normalizer
-from sklearn import metrics
-
-from sklearn.cluster import KMeans, MiniBatchKMeans
-
-import logging
-from optparse import OptionParser
-import sys
-from time import time
+# %%
+# Loading text data
+# =================
+#
+# We load data from :ref:`20newsgroups_dataset`, which comprises around 18,000
+# newsgroups posts on 20 topics. For illustrative purposes and to reduce the
+# computational cost, we select a subset of 4 topics only accounting for around
+# 3,400 documents. See the example
+# :ref:`sphx_glr_auto_examples_text_plot_document_classification_20newsgroups.py`
+# to gain intuition on the overlap of such topics.
+#
+# Notice that, by default, the text samples contain some message metadata such
+# as `"headers"`, `"footers"` (signatures) and `"quotes"` to other posts. We use
+# the `remove` parameter from :func:`~sklearn.datasets.fetch_20newsgroups` to
+# strip those features and have a more sensible clustering problem.
import numpy as np
+from sklearn.datasets import fetch_20newsgroups
+categories = [
+ "alt.atheism",
+ "talk.religion.misc",
+ "comp.graphics",
+ "sci.space",
+]
-# Display progress logs on stdout
-logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
-
-# parse commandline arguments
-op = OptionParser()
-op.add_option(
- "--lsa",
- dest="n_components",
- type="int",
- help="Preprocess documents with latent semantic analysis.",
-)
-op.add_option(
- "--no-minibatch",
- action="store_false",
- dest="minibatch",
- default=True,
- help="Use ordinary k-means algorithm (in batch mode).",
-)
-op.add_option(
- "--no-idf",
- action="store_false",
- dest="use_idf",
- default=True,
- help="Disable Inverse Document Frequency feature weighting.",
-)
-op.add_option(
- "--use-hashing",
- action="store_true",
- default=False,
- help="Use a hashing feature vectorizer",
-)
-op.add_option(
- "--n-features",
- type=int,
- default=10000,
- help="Maximum number of features (dimensions) to extract from text.",
-)
-op.add_option(
- "--verbose",
- action="store_true",
- dest="verbose",
- default=False,
- help="Print progress reports inside k-means algorithm.",
+dataset = fetch_20newsgroups(
+ remove=("headers", "footers", "quotes"),
+ subset="all",
+ categories=categories,
+ shuffle=True,
+ random_state=42,
)
-print(__doc__)
+labels = dataset.target
+unique_labels, category_sizes = np.unique(labels, return_counts=True)
+true_k = unique_labels.shape[0]
+print(f"{len(dataset.data)} documents - {true_k} categories")
-def is_interactive():
- return not hasattr(sys.modules["__main__"], "__file__")
+# %%
+# Quantifying the quality of clustering results
+# =============================================
+#
+# In this section we define a function to score different clustering pipelines
+# using several metrics.
+#
+# Clustering algorithms are fundamentally unsupervised learning methods.
+# However, since we happen to have class labels for this specific dataset, it is
+# possible to use evaluation metrics that leverage this "supervised" ground
+# truth information to quantify the quality of the resulting clusters. Examples
+# of such metrics are the following:
+#
+# - homogeneity, which quantifies how much clusters contain only members of a
+# single class;
+#
+# - completeness, which quantifies how much members of a given class are
+# assigned to the same clusters;
+#
+# - V-measure, the harmonic mean of completeness and homogeneity;
+#
+# - Rand-Index, which measures how frequently pairs of data points are grouped
+# consistently according to the result of the clustering algorithm and the
+# ground truth class assignment;
+#
+# - Adjusted Rand-Index, a chance-adjusted Rand-Index such that random cluster
+# assignment have an ARI of 0.0 in expectation.
+#
+# If the ground truth labels are not known, evaluation can only be performed
+# using the model results itself. In that case, the Silhouette Coefficient comes
+# in handy.
+#
+# For more reference, see :ref:`clustering_evaluation`.
+
+from collections import defaultdict
+from sklearn import metrics
+from time import time
+evaluations = []
+evaluations_std = []
+
+
+def fit_and_evaluate(km, X, name=None, n_runs=5):
+ name = km.__class__.__name__ if name is None else name
+
+ train_times = []
+ scores = defaultdict(list)
+ for seed in range(n_runs):
+ km.set_params(random_state=seed)
+ t0 = time()
+ km.fit(X)
+ train_times.append(time() - t0)
+ scores["Homogeneity"].append(metrics.homogeneity_score(labels, km.labels_))
+ scores["Completeness"].append(metrics.completeness_score(labels, km.labels_))
+ scores["V-measure"].append(metrics.v_measure_score(labels, km.labels_))
+ scores["Adjusted Rand-Index"].append(
+ metrics.adjusted_rand_score(labels, km.labels_)
+ )
+ scores["Silhouette Coefficient"].append(
+ metrics.silhouette_score(X, km.labels_, sample_size=2000)
+ )
+ train_times = np.asarray(train_times)
+
+ print(f"clustering done in {train_times.mean():.2f} ± {train_times.std():.2f} s ")
+ evaluation = {
+ "estimator": name,
+ "train_time": train_times.mean(),
+ }
+ evaluation_std = {
+ "estimator": name,
+ "train_time": train_times.std(),
+ }
+ for score_name, score_values in scores.items():
+ mean_score, std_score = np.mean(score_values), np.std(score_values)
+ print(f"{score_name}: {mean_score:.3f} ± {std_score:.3f}")
+ evaluation[score_name] = mean_score
+ evaluation_std[score_name] = std_score
+ evaluations.append(evaluation)
+ evaluations_std.append(evaluation_std)
-if not is_interactive():
- op.print_help()
- print()
-# work-around for Jupyter notebook and IPython console
-argv = [] if is_interactive() else sys.argv[1:]
-(opts, args) = op.parse_args(argv)
-if len(args) > 0:
- op.error("this script takes no arguments.")
- sys.exit(1)
+# %%
+# K-means clustering on text features
+# ===================================
+#
+# Two feature extraction methods are used in this example:
+#
+# - :class:`~sklearn.feature_extraction.text.TfidfVectorizer` uses an in-memory
+# vocabulary (a Python dict) to map the most frequent words to features
+# indices and hence compute a word occurrence frequency (sparse) matrix. The
+# word frequencies are then reweighted using the Inverse Document Frequency
+# (IDF) vector collected feature-wise over the corpus.
+#
+# - :class:`~sklearn.feature_extraction.text.HashingVectorizer` hashes word
+# occurrences to a fixed dimensional space, possibly with collisions. The word
+# count vectors are then normalized to each have l2-norm equal to one
+# (projected to the euclidean unit-sphere) which seems to be important for
+# k-means to work in high dimensional space.
+#
+# Furthermore it is possible to post-process those extracted features using
+# dimensionality reduction. We will explore the impact of those choices on the
+# clustering quality in the following.
+#
+# Feature Extraction using TfidfVectorizer
+# ----------------------------------------
+#
+# We first benchmark the estimators using a dictionary vectorizer along with an
+# IDF normalization as provided by
+# :class:`~sklearn.feature_extraction.text.TfidfVectorizer`.
+from sklearn.feature_extraction.text import TfidfVectorizer
-# %%
-# Load some categories from the training set
-# ------------------------------------------
+vectorizer = TfidfVectorizer(
+ max_df=0.5,
+ min_df=5,
+ stop_words="english",
+)
+t0 = time()
+X_tfidf = vectorizer.fit_transform(dataset.data)
-categories = [
- "alt.atheism",
- "talk.religion.misc",
- "comp.graphics",
- "sci.space",
-]
-# Uncomment the following to do the analysis on all the categories
-# categories = None
+print(f"vectorization done in {time() - t0:.3f} s")
+print(f"n_samples: {X_tfidf.shape[0]}, n_features: {X_tfidf.shape[1]}")
-print("Loading 20 newsgroups dataset for categories:")
-print(categories)
+# %%
+# After ignoring terms that appear in more than 50% of the documents (as set by
+# `max_df=0.5`) and terms that are not present in at least 5 documents (set by
+# `min_df=5`), the resulting number of unique terms `n_features` is around
+# 8,000. We can additionally quantify the sparsity of the `X_tfidf` matrix as
+# the fraction of non-zero entries devided by the total number of elements.
-dataset = fetch_20newsgroups(
- subset="all", categories=categories, shuffle=True, random_state=42
-)
+print(f"{X_tfidf.nnz / np.prod(X_tfidf.shape):.3f}")
-print("%d documents" % len(dataset.data))
-print("%d categories" % len(dataset.target_names))
+# %%
+# We find that around 0.7% of the entries of the `X_tfidf` matrix are non-zero.
+#
+# .. _kmeans_sparse_high_dim:
+#
+# Clustering sparse data with k-means
+# -----------------------------------
+#
+# As both :class:`~sklearn.cluster.KMeans` and
+# :class:`~sklearn.cluster.MiniBatchKMeans` optimize a non-convex objective
+# function, their clustering is not guaranteed to be optimal for a given random
+# init. Even further, on sparse high-dimensional data such as text vectorized
+# using the Bag of Words approach, k-means can initialize centroids on extremely
+# isolated data points. Those data points can stay their own centroids all
+# along.
+#
+# The following code illustrates how the previous phenomenon can sometimes lead
+# to highly imbalanced clusters, depending on the random initialization:
+
+from sklearn.cluster import KMeans
+
+for seed in range(5):
+ kmeans = KMeans(
+ n_clusters=true_k,
+ max_iter=100,
+ n_init=1,
+ random_state=seed,
+ ).fit(X_tfidf)
+ cluster_ids, cluster_sizes = np.unique(kmeans.labels_, return_counts=True)
+ print(f"Number of elements asigned to each cluster: {cluster_sizes}")
print()
+print(
+ "True number of documents in each category according to the class labels: "
+ f"{category_sizes}"
+)
+
+# %%
+# To avoid this problem, one possibility is to increase the number of runs with
+# independent random initiations `n_init`. In such case the clustering with the
+# best inertia (objective function of k-means) is chosen.
+
+kmeans = KMeans(
+ n_clusters=true_k,
+ max_iter=100,
+ n_init=5,
+)
+fit_and_evaluate(kmeans, X_tfidf, name="KMeans\non tf-idf vectors")
# %%
-# Feature Extraction
-# ------------------
+# All those clustering evaluation metrics have a maximum value of 1.0 (for a
+# perfect clustering result). Higher values are better. Values of the Adjusted
+# Rand-Index close to 0.0 correspond to a random labeling. Notice from the
+# scores above that the cluster assignment is indeed well above chance level,
+# but the overall quality can certainly improve.
+#
+# Keep in mind that the class labels may not reflect accurately the document
+# topics and therefore metrics that use labels are not necessarily the best to
+# evaluate the quality of our clustering pipeline.
+#
+# Performing dimensionality reduction using LSA
+# ---------------------------------------------
+#
+# A `n_init=1` can still be used as long as the dimension of the vectorized
+# space is reduced first to make k-means more stable. For such purpose we use
+# :class:`~sklearn.decomposition.TruncatedSVD`, which works on term count/tf-idf
+# matrices. Since SVD results are not normalized, we redo the normalization to
+# improve the :class:`~sklearn.cluster.KMeans` result. Using SVD to reduce the
+# dimensionality of TF-IDF document vectors is often known as `latent semantic
+# analysis `_ (LSA) in
+# the information retrieval and text mining literature.
-labels = dataset.target
-true_k = np.unique(labels).shape[0]
+from sklearn.decomposition import TruncatedSVD
+from sklearn.pipeline import make_pipeline
+from sklearn.preprocessing import Normalizer
-print("Extracting features from the training dataset using a sparse vectorizer")
+
+lsa = make_pipeline(TruncatedSVD(n_components=100), Normalizer(copy=False))
t0 = time()
-if opts.use_hashing:
- if opts.use_idf:
- # Perform an IDF normalization on the output of HashingVectorizer
- hasher = HashingVectorizer(
- n_features=opts.n_features,
- stop_words="english",
- alternate_sign=False,
- norm=None,
- )
- vectorizer = make_pipeline(hasher, TfidfTransformer())
- else:
- vectorizer = HashingVectorizer(
- n_features=opts.n_features,
- stop_words="english",
- alternate_sign=False,
- norm="l2",
- )
-else:
- vectorizer = TfidfVectorizer(
- max_df=0.5,
- max_features=opts.n_features,
- min_df=2,
- stop_words="english",
- use_idf=opts.use_idf,
- )
-X = vectorizer.fit_transform(dataset.data)
-
-print("done in %fs" % (time() - t0))
-print("n_samples: %d, n_features: %d" % X.shape)
-print()
+X_lsa = lsa.fit_transform(X_tfidf)
+explained_variance = lsa[0].explained_variance_ratio_.sum()
-if opts.n_components:
- print("Performing dimensionality reduction using LSA")
- t0 = time()
- # Since LSA/SVD results are not normalized,
- # we redo the normalization to improve the k-means result.
- svd = TruncatedSVD(opts.n_components)
- normalizer = Normalizer(copy=False)
- lsa = make_pipeline(svd, normalizer)
+print(f"LSA done in {time() - t0:.3f} s")
+print(f"Explained variance of the SVD step: {explained_variance * 100:.1f}%")
- X = lsa.fit_transform(X)
+# %%
+# Using a single initialization means the processing time will be reduced for
+# both :class:`~sklearn.cluster.KMeans` and
+# :class:`~sklearn.cluster.MiniBatchKMeans`.
+
+kmeans = KMeans(
+ n_clusters=true_k,
+ max_iter=100,
+ n_init=1,
+)
- print("done in %fs" % (time() - t0))
+fit_and_evaluate(kmeans, X_lsa, name="KMeans\nwith LSA on tf-idf vectors")
- explained_variance = svd.explained_variance_ratio_.sum()
- print(
- "Explained variance of the SVD step: {}%".format(int(explained_variance * 100))
- )
+# %%
+# We can observe that clustering on the LSA representation of the document is
+# significantly faster (both because of `n_init=1` and because the
+# dimensionality of the LSA feature space is much smaller). Furthermore, all the
+# clustering evaluation metrics have improved. We repeat the experiment with
+# :class:`~sklearn.cluster.MiniBatchKMeans`.
+
+from sklearn.cluster import MiniBatchKMeans
+
+minibatch_kmeans = MiniBatchKMeans(
+ n_clusters=true_k,
+ n_init=1,
+ init_size=1000,
+ batch_size=1000,
+)
- print()
+fit_and_evaluate(
+ minibatch_kmeans,
+ X_lsa,
+ name="MiniBatchKMeans\nwith LSA on tf-idf vectors",
+)
+# %%
+# Top terms per cluster
+# ---------------------
+#
+# Since :class:`~sklearn.feature_extraction.text.TfidfVectorizer` can be
+# inverted we can identify the cluster centers, which provide an intuition of
+# the most influential words **for each cluster**. See the example script
+# :ref:`sphx_glr_auto_examples_text_plot_document_classification_20newsgroups.py`
+# for a comparison with the most predictive words **for each target class**.
+
+original_space_centroids = lsa[0].inverse_transform(kmeans.cluster_centers_)
+order_centroids = original_space_centroids.argsort()[:, ::-1]
+terms = vectorizer.get_feature_names_out()
+
+for i in range(true_k):
+ print(f"Cluster {i}: ", end="")
+ for ind in order_centroids[i, :10]:
+ print(f"{terms[ind]} ", end="")
+ print()
# %%
-# Clustering
-# ----------
+# HashingVectorizer
+# -----------------
+# An alternative vectorization can be done using a
+# :class:`~sklearn.feature_extraction.text.HashingVectorizer` instance, which
+# does not provide IDF weighting as this is a stateless model (the fit method
+# does nothing). When IDF weighting is needed it can be added by pipelining the
+# :class:`~sklearn.feature_extraction.text.HashingVectorizer` output to a
+# :class:`~sklearn.feature_extraction.text.TfidfTransformer` instance. In this
+# case we also add LSA to the pipeline to reduce the dimension and sparcity of
+# the hashed vector space.
-if opts.minibatch:
- km = MiniBatchKMeans(
- n_clusters=true_k,
- init="k-means++",
- n_init=1,
- init_size=1000,
- batch_size=1000,
- verbose=opts.verbose,
- )
-else:
- km = KMeans(
- n_clusters=true_k,
- init="k-means++",
- max_iter=100,
- n_init=1,
- verbose=opts.verbose,
- )
+from sklearn.feature_extraction.text import HashingVectorizer
+from sklearn.feature_extraction.text import TfidfTransformer
-print("Clustering sparse data with %s" % km)
-t0 = time()
-km.fit(X)
-print("done in %0.3fs" % (time() - t0))
-print()
+lsa_vectorizer = make_pipeline(
+ HashingVectorizer(stop_words="english", n_features=50_000),
+ TfidfTransformer(),
+ TruncatedSVD(n_components=100, random_state=0),
+ Normalizer(copy=False),
+)
+t0 = time()
+X_hashed_lsa = lsa_vectorizer.fit_transform(dataset.data)
+print(f"vectorization done in {time() - t0:.3f} s")
# %%
-# Performance metrics
-# -------------------
+# One can observe that the LSA step takes a relatively long time to fit,
+# especially with hashed vectors. The reason is that a hashed space is typically
+# large (set to `n_features=50_000` in this example). One can try lowering the
+# number of features at the expense of having a larger fraction of features with
+# hash collisions as shown in the example notebook
+# :ref:`sphx_glr_auto_examples_text_plot_hashing_vs_dict_vectorizer.py`.
+#
+# We now fit and evaluate the `kmeans` and `minibatch_kmeans` instances on this
+# hashed-lsa-reduced data:
+
+fit_and_evaluate(kmeans, X_hashed_lsa, name="KMeans\nwith LSA on hashed vectors")
-print("Homogeneity: %0.3f" % metrics.homogeneity_score(labels, km.labels_))
-print("Completeness: %0.3f" % metrics.completeness_score(labels, km.labels_))
-print("V-measure: %0.3f" % metrics.v_measure_score(labels, km.labels_))
-print("Adjusted Rand-Index: %.3f" % metrics.adjusted_rand_score(labels, km.labels_))
-print(
- "Silhouette Coefficient: %0.3f"
- % metrics.silhouette_score(X, km.labels_, sample_size=1000)
+# %%
+fit_and_evaluate(
+ minibatch_kmeans,
+ X_hashed_lsa,
+ name="MiniBatchKMeans\nwith LSA on hashed vectors",
)
-print()
+# %%
+# Both methods lead to good results that are similar to running the same models
+# on the traditional LSA vectors (without hashing).
+#
+# Clustering evaluation summary
+# ==============================
+import pandas as pd
+import matplotlib.pyplot as plt
-# %%
+fig, (ax0, ax1) = plt.subplots(ncols=2, figsize=(16, 6), sharey=True)
+
+df = pd.DataFrame(evaluations[::-1]).set_index("estimator")
+df_std = pd.DataFrame(evaluations_std[::-1]).set_index("estimator")
+
+df.drop(
+ ["train_time"],
+ axis="columns",
+).plot.barh(ax=ax0, xerr=df_std)
+ax0.set_xlabel("Clustering scores")
+ax0.set_ylabel("")
-if not opts.use_hashing:
- print("Top terms per cluster:")
-
- if opts.n_components:
- original_space_centroids = svd.inverse_transform(km.cluster_centers_)
- order_centroids = original_space_centroids.argsort()[:, ::-1]
- else:
- order_centroids = km.cluster_centers_.argsort()[:, ::-1]
-
- terms = vectorizer.get_feature_names_out()
- for i in range(true_k):
- print("Cluster %d:" % i, end="")
- for ind in order_centroids[i, :10]:
- print(" %s" % terms[ind], end="")
- print()
+df["train_time"].plot.barh(ax=ax1, xerr=df_std["train_time"])
+ax1.set_xlabel("Clustering time (s)")
+plt.tight_layout()
+
+# %%
+# :class:`~sklearn.cluster.KMeans` and :class:`~sklearn.cluster.MiniBatchKMeans`
+# suffer from the phenomenon called the `Curse of Dimensionality
+# `_ for high dimensional
+# datasets such as text data. That is the reason why the overall scores improve
+# when using LSA. Using LSA reduced data also improves the stability and
+# requires lower clustering time, though keep in mind that the LSA step itself
+# takes a long time, especially with hashed vectors.
+#
+# The Silhouette Coefficient is defined between 0 and 1. In all cases we obtain
+# values close to 0 (even if they improve a bit after using LSA) because its
+# definition requires measuring distances, in contrast with other evaluation
+# metrics such as the V-measure and the Adjusted Rand Index which are only based
+# on cluster assignments rather than distances. Notice that strictly speaking,
+# one should not compare the Silhouette Coefficient between spaces of different
+# dimension, due to the different notions of distance they imply.
+#
+# The homogeneity, completeness and hence v-measure metrics do not yield a
+# baseline with regards to random labeling: this means that depending on the
+# number of samples, clusters and ground truth classes, a completely random
+# labeling will not always yield the same values. In particular random labeling
+# won't yield zero scores, especially when the number of clusters is large. This
+# problem can safely be ignored when the number of samples is more than a
+# thousand and the number of clusters is less than 10, which is the case of the
+# present example. For smaller sample sizes or larger number of clusters it is
+# safer to use an adjusted index such as the Adjusted Rand Index (ARI). See the
+# example
+# :ref:`sphx_glr_auto_examples_cluster_plot_adjusted_for_chance_measures.py` for
+# a demo on the effect of random labeling.
+#
+# The size of the error bars show that :class:`~sklearn.cluster.MiniBatchKMeans`
+# is less stable than :class:`~sklearn.cluster.KMeans` for this relatively small
+# dataset. It is more interesting to use when the number of samples is much
+# bigger, but it can come at the expense of a small degradation in clustering
+# quality compared to the traditional k-means algorithm.
diff --git a/examples/text/plot_hashing_vs_dict_vectorizer.py b/examples/text/plot_hashing_vs_dict_vectorizer.py
index ce359cd137487..8200c646f69ee 100644
--- a/examples/text/plot_hashing_vs_dict_vectorizer.py
+++ b/examples/text/plot_hashing_vs_dict_vectorizer.py
@@ -3,109 +3,382 @@
FeatureHasher and DictVectorizer Comparison
===========================================
-Compares FeatureHasher and DictVectorizer by using both to vectorize
-text documents.
+In this example we illustrate text vectorization, which is the process of
+representing non-numerical input data (such as dictionaries or text documents)
+as vectors of real numbers.
-The example demonstrates syntax and speed only; it doesn't actually do
-anything useful with the extracted vectors. See the example scripts
-{document_classification_20newsgroups,clustering}.py for actual learning
-on text documents.
+We first compare :func:`~sklearn.feature_extraction.FeatureHasher` and
+:func:`~sklearn.feature_extraction.DictVectorizer` by using both methods to
+vectorize text documents that are preprocessed (tokenized) with the help of a
+custom Python function.
-A discrepancy between the number of terms reported for DictVectorizer and
-for FeatureHasher is to be expected due to hash collisions.
+Later we introduce and analyze the text-specific vectorizers
+:func:`~sklearn.feature_extraction.text.HashingVectorizer`,
+:func:`~sklearn.feature_extraction.text.CountVectorizer` and
+:func:`~sklearn.feature_extraction.text.TfidfVectorizer` that handle both the
+tokenization and the assembling of the feature matrix within a single class.
+
+The objective of the example is to demonstrate the usage of text vectorization
+API and to compare their processing time. See the example scripts
+:ref:`sphx_glr_auto_examples_text_plot_document_classification_20newsgroups.py`
+and :ref:`sphx_glr_auto_examples_text_plot_document_clustering.py` for actual
+learning on text documents.
"""
# Author: Lars Buitinck
+# Olivier Grisel
+# Arturo Amor
# License: BSD 3 clause
-from collections import defaultdict
-import re
-import sys
-from time import time
-
-import numpy as np
+# %%
+# Load Data
+# ---------
+#
+# We load data from :ref:`20newsgroups_dataset`, which comprises around
+# 18000 newsgroups posts on 20 topics split in two subsets: one for training and
+# one for testing. For the sake of simplicity and reducing the computational
+# cost, we select a subset of 7 topics and use the training set only.
from sklearn.datasets import fetch_20newsgroups
-from sklearn.feature_extraction import DictVectorizer, FeatureHasher
+categories = [
+ "alt.atheism",
+ "comp.graphics",
+ "comp.sys.ibm.pc.hardware",
+ "misc.forsale",
+ "rec.autos",
+ "sci.space",
+ "talk.religion.misc",
+]
+
+print("Loading 20 newsgroups training data")
+raw_data, _ = fetch_20newsgroups(subset="train", categories=categories, return_X_y=True)
+data_size_mb = sum(len(s.encode("utf-8")) for s in raw_data) / 1e6
+print(f"{len(raw_data)} documents - {data_size_mb:.3f}MB")
-def n_nonzero_columns(X):
- """Returns the number of non-zero columns in a CSR matrix X."""
- return len(np.unique(X.nonzero()[1]))
+# %%
+# Define preprocessing functions
+# ------------------------------
+#
+# A token may be a word, part of a word or anything comprised between spaces or
+# symbols in a string. Here we define a function that extracts the tokens using
+# a simple regular expression (regex) that matches Unicode word characters. This
+# includes most characters that can be part of a word in any language, as well
+# as numbers and the underscore:
+
+import re
-def tokens(doc):
+def tokenize(doc):
"""Extract tokens from doc.
- This uses a simple regex to break strings into tokens. For a more
- principled approach, see CountVectorizer or TfidfVectorizer.
+ This uses a simple regex that matches word characters to break strings
+ into tokens. For a more principled approach, see CountVectorizer or
+ TfidfVectorizer.
"""
return (tok.lower() for tok in re.findall(r"\w+", doc))
+list(tokenize("This is a simple example, isn't it?"))
+
+# %%
+# We define an additional function that counts the (frequency of) occurrence of
+# each token in a given document. It returns a frequency dictionary to be used
+# by the vectorizers.
+
+from collections import defaultdict
+
+
def token_freqs(doc):
- """Extract a dict mapping tokens from doc to their frequencies."""
+ """Extract a dict mapping tokens from doc to their occurrences."""
+
freq = defaultdict(int)
- for tok in tokens(doc):
+ for tok in tokenize(doc):
freq[tok] += 1
return freq
-categories = [
- "alt.atheism",
- "comp.graphics",
- "comp.sys.ibm.pc.hardware",
- "misc.forsale",
- "rec.autos",
- "sci.space",
- "talk.religion.misc",
-]
-# Uncomment the following line to use a larger set (11k+ documents)
-# categories = None
+token_freqs("That is one example, but this is another one")
-print(__doc__)
-print("Usage: %s [n_features_for_hashing]" % sys.argv[0])
-print(" The default number of features is 2**18.")
-print()
+# %%
+# Observe in particular that the repeated token `"is"` is counted twice for
+# instance.
+#
+# Breaking a text document into word tokens, potentially losing the order
+# information between the words in a sentence is often called a `Bag of Words
+# representation `_.
-try:
- n_features = int(sys.argv[1])
-except IndexError:
- n_features = 2**18
-except ValueError:
- print("not a valid number of features: %r" % sys.argv[1])
- sys.exit(1)
+# %%
+# DictVectorizer
+# --------------
+#
+# First we benchmark the :func:`~sklearn.feature_extraction.DictVectorizer`,
+# then we compare it to :func:`~sklearn.feature_extraction.FeatureHasher` as
+# both of them receive dictionaries as input.
+from time import time
+from sklearn.feature_extraction import DictVectorizer
-print("Loading 20 newsgroups training data")
-raw_data, _ = fetch_20newsgroups(subset="train", categories=categories, return_X_y=True)
-data_size_mb = sum(len(s.encode("utf-8")) for s in raw_data) / 1e6
-print("%d documents - %0.3fMB" % (len(raw_data), data_size_mb))
-print()
+dict_count_vectorizers = defaultdict(list)
-print("DictVectorizer")
t0 = time()
vectorizer = DictVectorizer()
vectorizer.fit_transform(token_freqs(d) for d in raw_data)
duration = time() - t0
-print("done in %fs at %0.3fMB/s" % (duration, data_size_mb / duration))
-print("Found %d unique terms" % len(vectorizer.get_feature_names_out()))
-print()
+dict_count_vectorizers["vectorizer"].append(
+ vectorizer.__class__.__name__ + "\non freq dicts"
+)
+dict_count_vectorizers["speed"].append(data_size_mb / duration)
+print(f"done in {duration:.3f} s at {data_size_mb / duration:.1f} MB/s")
+print(f"Found {len(vectorizer.get_feature_names_out())} unique terms")
+
+# %%
+# The actual mapping from text token to column index is explicitly stored in
+# the `.vocabulary_` attribute which is a potentially very large Python
+# dictionary:
+type(vectorizer.vocabulary_)
+
+# %%
+len(vectorizer.vocabulary_)
+
+# %%
+vectorizer.vocabulary_["example"]
+
+# %%
+# FeatureHasher
+# -------------
+#
+# Dictionaries take up a large amount of storage space and grow in size as the
+# training set grows. Instead of growing the vectors along with a dictionary,
+# feature hashing builds a vector of pre-defined length by applying a hash
+# function `h` to the features (e.g., tokens), then using the hash values
+# directly as feature indices and updating the resulting vector at those
+# indices. When the feature space is not large enough, hashing functions tend to
+# map distinct values to the same hash code (hash collisions). As a result, it
+# is impossible to determine what object generated any particular hash code.
+#
+# Because of the above it is impossible to recover the original tokens from the
+# feature matrix and the best approach to estimate the number of unique terms in
+# the original dictionary is to count the number of active columns in the
+# encoded feature matrix. For such a purpose we define the following function:
+
+import numpy as np
+
+
+def n_nonzero_columns(X):
+ """Number of columns with at least one non-zero value in a CSR matrix.
+
+ This is useful to count the number of features columns that are effectively
+ active when using the FeatureHasher.
+ """
+ return len(np.unique(X.nonzero()[1]))
+
+
+# %%
+# The default number of features for the
+# :func:`~sklearn.feature_extraction.FeatureHasher` is 2**20. Here we set
+# `n_features = 2**18` to illustrate hash collisions.
+#
+# **FeatureHasher on frequency dictionaries**
+
+from sklearn.feature_extraction import FeatureHasher
-print("FeatureHasher on frequency dicts")
t0 = time()
-hasher = FeatureHasher(n_features=n_features)
+hasher = FeatureHasher(n_features=2**18)
X = hasher.transform(token_freqs(d) for d in raw_data)
duration = time() - t0
-print("done in %fs at %0.3fMB/s" % (duration, data_size_mb / duration))
-print("Found %d unique terms" % n_nonzero_columns(X))
-print()
+dict_count_vectorizers["vectorizer"].append(
+ hasher.__class__.__name__ + "\non freq dicts"
+)
+dict_count_vectorizers["speed"].append(data_size_mb / duration)
+print(f"done in {duration:.3f} s at {data_size_mb / duration:.1f} MB/s")
+print(f"Found {n_nonzero_columns(X)} unique tokens")
+
+# %%
+# The number of unique tokens when using the
+# :func:`~sklearn.feature_extraction.FeatureHasher` is lower than those obtained
+# using the :func:`~sklearn.feature_extraction.DictVectorizer`. This is due to
+# hash collisions.
+#
+# The number of collisions can be reduced by increasing the feature space.
+# Notice that the speed of the vectorizer does not change significantly when
+# setting a large number of features, though it causes larger coefficient
+# dimensions and then requires more memory usage to store them, even if a
+# majority of them is inactive.
-print("FeatureHasher on raw tokens")
t0 = time()
-hasher = FeatureHasher(n_features=n_features, input_type="string")
-X = hasher.transform(tokens(d) for d in raw_data)
+hasher = FeatureHasher(n_features=2**22)
+X = hasher.transform(token_freqs(d) for d in raw_data)
duration = time() - t0
-print("done in %fs at %0.3fMB/s" % (duration, data_size_mb / duration))
-print("Found %d unique terms" % n_nonzero_columns(X))
+
+print(f"done in {duration:.3f} s at {data_size_mb / duration:.1f} MB/s")
+print(f"Found {n_nonzero_columns(X)} unique tokens")
+
+# %%
+# We confirm that the number of unique tokens gets closer to the number of
+# unique terms found by the :func:`~sklearn.feature_extraction.DictVectorizer`.
+#
+# **FeatureHasher on raw tokens**
+#
+# Alternatively, one can set `input_type="string"` in the
+# :func:`~sklearn.feature_extraction.FeatureHasher` to vectorize the strings
+# output directly from the customized `tokenize` function. This is equivalent to
+# passing a dictionary with an implied frequency of 1 for each feature name.
+
+t0 = time()
+hasher = FeatureHasher(n_features=2**18, input_type="string")
+X = hasher.transform(tokenize(d) for d in raw_data)
+duration = time() - t0
+dict_count_vectorizers["vectorizer"].append(
+ hasher.__class__.__name__ + "\non raw tokens"
+)
+dict_count_vectorizers["speed"].append(data_size_mb / duration)
+print(f"done in {duration:.3f} s at {data_size_mb / duration:.1f} MB/s")
+print(f"Found {n_nonzero_columns(X)} unique tokens")
+
+# %%
+# We now plot the speed of the above methods for vectorizing.
+
+import matplotlib.pyplot as plt
+
+fig, ax = plt.subplots(figsize=(12, 6))
+
+y_pos = np.arange(len(dict_count_vectorizers["vectorizer"]))
+ax.barh(y_pos, dict_count_vectorizers["speed"], align="center")
+ax.set_yticks(y_pos)
+ax.set_yticklabels(dict_count_vectorizers["vectorizer"])
+ax.invert_yaxis()
+_ = ax.set_xlabel("speed (MB/s)")
+
+# %%
+# In both cases :func:`~sklearn.feature_extraction.FeatureHasher` is
+# approximately twice as fast as
+# :func:`~sklearn.feature_extraction.DictVectorizer`. This is handy when dealing
+# with large amounts of data, with the downside of losing the invertibility of
+# the transformation, which in turn makes the interpretation of a model a more
+# complex task.
+#
+# The `FeatureHeasher` with `input_type="string"` is slightly faster than the
+# variant that works on frequency dict because it does not count repeated
+# tokens: each token is implicitly counted once, even if it was repeated.
+# Depending on the downstream machine learning task, it can be a limitation or
+# not.
+#
+# Comparison with special purpose text vectorizers
+# ------------------------------------------------
+#
+# :func:`~sklearn.feature_extraction.text.CountVectorizer` accepts raw data as
+# it internally implements tokenization and occurrence counting. It is similar
+# to the :func:`~sklearn.feature_extraction.DictVectorizer` when used along with
+# the customized function `token_freqs` as done in the previous section. The
+# difference being that :func:`~sklearn.feature_extraction.text.CountVectorizer`
+# is more flexible. In particular it accepts various regex patterns through the
+# `token_pattern` parameter.
+
+from sklearn.feature_extraction.text import CountVectorizer
+
+t0 = time()
+vectorizer = CountVectorizer()
+vectorizer.fit_transform(raw_data)
+duration = time() - t0
+dict_count_vectorizers["vectorizer"].append(vectorizer.__class__.__name__)
+dict_count_vectorizers["speed"].append(data_size_mb / duration)
+print(f"done in {duration:.3f} s at {data_size_mb / duration:.1f} MB/s")
+print(f"Found {len(vectorizer.get_feature_names_out())} unique terms")
+
+# %%
+# We see that using the :func:`~sklearn.feature_extraction.text.CountVectorizer`
+# implementation is approximately twice as fast as using the
+# :func:`~sklearn.feature_extraction.DictVectorizer` along with the simple
+# function we defined for mapping the tokens. The reason is that
+# :func:`~sklearn.feature_extraction.text.CountVectorizer` is optimized by
+# reusing a compiled regular expression for the full training set instead of
+# creating one per document as done in our naive tokenize function.
+#
+# Now we make a similar experiment with the
+# :func:`~sklearn.feature_extraction.text.HashingVectorizer`, which is
+# equivalent to combining the “hashing trick” implemented by the
+# :func:`~sklearn.feature_extraction.FeatureHasher` class and the text
+# preprocessing and tokenization of the
+# :func:`~sklearn.feature_extraction.text.CountVectorizer`.
+
+from sklearn.feature_extraction.text import HashingVectorizer
+
+t0 = time()
+vectorizer = HashingVectorizer(n_features=2**18)
+vectorizer.fit_transform(raw_data)
+duration = time() - t0
+dict_count_vectorizers["vectorizer"].append(vectorizer.__class__.__name__)
+dict_count_vectorizers["speed"].append(data_size_mb / duration)
+print(f"done in {duration:.3f} s at {data_size_mb / duration:.1f} MB/s")
+
+# %%
+# We can observe that this is the fastest text tokenization strategy so far,
+# assuming that the downstream machine learning task can tolerate a few
+# collisions.
+#
+# TfidfVectorizer
+# ---------------
+#
+# In a large text corpus, some words appear with higher frequency (e.g. “the”,
+# “a”, “is” in English) and do not carry meaningful information about the actual
+# contents of a document. If we were to feed the word count data directly to a
+# classifier, those very common terms would shadow the frequencies of rarer yet
+# more informative terms. In order to re-weight the count features into floating
+# point values suitable for usage by a classifier it is very common to use the
+# tf–idf transform as implemented by the
+# :func:`~sklearn.feature_extraction.text.TfidfTransformer`. TF stands for
+# "term-frequency" while "tf–idf" means term-frequency times inverse
+# document-frequency.
+#
+# We now benchmark the :func:`~sklearn.feature_extraction.text.TfidfVectorizer`,
+# which is equivalent to combining the tokenization and occurrence counting of
+# the :func:`~sklearn.feature_extraction.text.CountVectorizer` along with the
+# normalizing and weighting from a
+# :func:`~sklearn.feature_extraction.text.TfidfTransformer`.
+
+from sklearn.feature_extraction.text import TfidfVectorizer
+
+t0 = time()
+vectorizer = TfidfVectorizer()
+vectorizer.fit_transform(raw_data)
+duration = time() - t0
+dict_count_vectorizers["vectorizer"].append(vectorizer.__class__.__name__)
+dict_count_vectorizers["speed"].append(data_size_mb / duration)
+print(f"done in {duration:.3f} s at {data_size_mb / duration:.1f} MB/s")
+print(f"Found {len(vectorizer.get_feature_names_out())} unique terms")
+
+# %%
+# Summary
+# -------
+# Let's conclude this notebook by summarizing all the recorded processing speeds
+# in a single plot:
+
+fig, ax = plt.subplots(figsize=(12, 6))
+
+y_pos = np.arange(len(dict_count_vectorizers["vectorizer"]))
+ax.barh(y_pos, dict_count_vectorizers["speed"], align="center")
+ax.set_yticks(y_pos)
+ax.set_yticklabels(dict_count_vectorizers["vectorizer"])
+ax.invert_yaxis()
+_ = ax.set_xlabel("speed (MB/s)")
+
+# %%
+# Notice from the plot that
+# :func:`~sklearn.feature_extraction.text.TfidfVectorizer` is slightly slower
+# than :func:`~sklearn.feature_extraction.text.CountVectorizer` because of the
+# extra operation induced by the
+# :func:`~sklearn.feature_extraction.text.TfidfTransformer`.
+#
+# Also notice that, by setting the number of features `n_features = 2**18`, the
+# :func:`~sklearn.feature_extraction.text.HashingVectorizer` performs better
+# than the :func:`~sklearn.feature_extraction.text.CountVectorizer` at the
+# expense of inversibility of the transformation due to hash collisions.
+#
+# We highlight that :func:`~sklearn.feature_extraction.text.CountVectorizer` and
+# :func:`~sklearn.feature_extraction.text.HashingVectorizer` perform better than
+# their equivalent :func:`~sklearn.feature_extraction.DictVectorizer` and
+# :func:`~sklearn.feature_extraction.FeatureHasher` on manually tokenized
+# documents since the internal tokenization step of the former vectorizers
+# compiles a regular expression once and then reuses it for all the documents.
diff --git a/lgtm.yml b/lgtm.yml
index 5394cc1664bc9..2dfdba3d160cc 100644
--- a/lgtm.yml
+++ b/lgtm.yml
@@ -1,7 +1,7 @@
extraction:
cpp:
before_index:
- - pip3 install numpy==1.16.3
+ - pip3 install numpy==1.17.3
- pip3 install --no-deps scipy Cython
index:
build_command:
diff --git a/maint_tools/update_tracking_issue.py b/maint_tools/update_tracking_issue.py
index 855c733cffb31..4ddc9d1bfe8e6 100644
--- a/maint_tools/update_tracking_issue.py
+++ b/maint_tools/update_tracking_issue.py
@@ -14,6 +14,7 @@
from pathlib import Path
import sys
import argparse
+from datetime import datetime, timezone
import defusedxml.ElementTree as ET
from github import Github
@@ -56,6 +57,8 @@
gh = Github(args.bot_github_token)
issue_repo = gh.get_repo(args.issue_repo)
+dt_now = datetime.now(tz=timezone.utc)
+date_str = dt_now.strftime("%b %d, %Y")
title = f"⚠️ CI failed on {args.ci_name} ⚠️"
@@ -71,18 +74,28 @@ def get_issue():
def create_or_update_issue(body=""):
# Interact with GitHub API to create issue
- header = f"**CI Failed on [{args.ci_name}]({args.link_to_ci_run})**"
- body_text = f"{header}\n{body}"
+ link = f"[{args.ci_name}]({args.link_to_ci_run})"
issue = get_issue()
+ max_body_length = 60_000
+ original_body_length = len(body)
+ # Avoid "body is too long (maximum is 65536 characters)" error from github REST API
+ if original_body_length > max_body_length:
+ body = (
+ f"{body[:max_body_length]}\n...\n"
+ f"Body was too long ({original_body_length} characters) and was shortened"
+ )
+
if issue is None:
# Create new issue
- issue = issue_repo.create_issue(title=title, body=body_text)
+ header = f"**CI failed on {link}** ({date_str})"
+ issue = issue_repo.create_issue(title=title, body=f"{header}\n{body}")
print(f"Created issue in {args.issue_repo}#{issue.number}")
sys.exit()
else:
- # Add comment to existing issue
- issue.create_comment(body=body_text)
+ # Update existing issue
+ header = f"**CI is still failing on {link}** ({date_str})"
+ issue.edit(body=f"{header}\n{body}")
print(f"Commented on issue: {args.issue_repo}#{issue.number}")
sys.exit()
@@ -91,11 +104,21 @@ def close_issue_if_opened():
print("Test has no failures!")
issue = get_issue()
if issue is not None:
- comment = (
- f"## CI is no longer failing! ✅\n\n[Successful run]({args.link_to_ci_run})"
+ header_str = "## CI is no longer failing!"
+ comment_str = (
+ f"{header_str} ✅\n\n[Successful run]({args.link_to_ci_run}) on {date_str}"
)
+
print(f"Commented on issue #{issue.number}")
- issue.create_comment(body=comment)
+ # New comment if "## CI is no longer failing!" comment does not exist
+ # If it does exist update the original comment which includes the new date
+ for comment in issue.get_comments():
+ if comment.body.startswith(header_str):
+ comment.edit(body=comment_str)
+ break
+ else: # no break
+ issue.create_comment(body=comment_str)
+
if args.auto_close.lower() == "true":
print(f"Closing issue #{issue.number}")
issue.edit(state="closed")
diff --git a/setup.cfg b/setup.cfg
index 2d59f866547c8..d5c4bba3e5a86 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -33,7 +33,7 @@ ignore=
E226, # missing whitespace around arithmetic operator
E704, # multiple statements on one line (def)
E731, # do not assign a lambda expression, use a def
- E741, # do not use variables named ‘l’, ‘O’, or ‘I’
+ E741, # do not use variables named 'l', 'O', or 'I'
W503, # line break before binary operator
W504 # line break after binary operator
exclude=
diff --git a/setup.py b/setup.py
index 7ad32e95e53a5..2ecc5ba0bcc2e 100755
--- a/setup.py
+++ b/setup.py
@@ -254,7 +254,7 @@ def setup_package():
classifiers=[
"Intended Audience :: Science/Research",
"Intended Audience :: Developers",
- "License :: OSI Approved",
+ "License :: OSI Approved :: BSD License",
"Programming Language :: C",
"Programming Language :: Python",
"Topic :: Software Development",
@@ -290,6 +290,7 @@ def setup_package():
from setuptools import setup
metadata["version"] = VERSION
+ metadata["packages"] = ["sklearn"]
else:
if sys.version_info < required_python_version:
required_version = "%d.%d" % required_python_version
diff --git a/sklearn/__init__.py b/sklearn/__init__.py
index c11158380d3c3..4e335ed928904 100644
--- a/sklearn/__init__.py
+++ b/sklearn/__init__.py
@@ -39,7 +39,7 @@
# Dev branch marker is: 'X.Y.dev' or 'X.Y.devN' where N is an integer.
# 'X.Y.dev0' is the canonical version of 'X.Y.dev'
#
-__version__ = "1.1.1"
+__version__ = "1.1.2"
# On OSX, we can get a runtime error due to multiple OpenMP libraries loaded
diff --git a/sklearn/_min_dependencies.py b/sklearn/_min_dependencies.py
index 957e1e01f0551..3e28d6bc7dc98 100644
--- a/sklearn/_min_dependencies.py
+++ b/sklearn/_min_dependencies.py
@@ -1,4 +1,5 @@
"""All minimum dependencies for scikit-learn."""
+from collections import defaultdict
import platform
import argparse
@@ -30,7 +31,7 @@
"threadpoolctl": (THREADPOOLCTL_MIN_VERSION, "install"),
"cython": (CYTHON_MIN_VERSION, "build"),
"matplotlib": ("3.1.2", "benchmark, docs, examples, tests"),
- "scikit-image": ("0.14.5", "docs, examples, tests"),
+ "scikit-image": ("0.16.2", "docs, examples, tests"),
"pandas": ("1.0.5", "benchmark, docs, examples, tests"),
"seaborn": ("0.9.0", "docs, examples"),
"memory_profiler": ("0.57.0", "benchmark, docs"),
@@ -38,7 +39,7 @@
"pytest-cov": ("2.9.0", "tests"),
"flake8": ("3.8.2", "tests"),
"black": ("22.3.0", "tests"),
- "mypy": ("0.770", "tests"),
+ "mypy": ("0.961", "tests"),
"pyamg": ("4.0.0", "tests"),
"sphinx": ("4.0.1", "docs"),
"sphinx-gallery": ("0.7.0", "docs"),
@@ -46,14 +47,14 @@
"Pillow": ("7.1.2", "docs"),
"sphinx-prompt": ("1.3.0", "docs"),
"sphinxext-opengraph": ("0.4.2", "docs"),
+ # XXX: Pin conda-lock to the latest released version (needs manual update
+ # from time to time)
+ "conda-lock": ("1.0.5", "maintenance"),
}
# create inverse mapping for setuptools
-tag_to_packages: dict = {
- extra: []
- for extra in ["build", "install", "docs", "examples", "tests", "benchmark"]
-}
+tag_to_packages: dict = defaultdict(list)
for package, (min_version, extras) in dependent_packages.items():
for extra in extras.split(", "):
tag_to_packages[extra].append("{}>={}".format(package, min_version))
diff --git a/sklearn/base.py b/sklearn/base.py
index 3bf19fb96f0c3..8e64ab3ac4156 100644
--- a/sklearn/base.py
+++ b/sklearn/base.py
@@ -209,7 +209,7 @@ def get_params(self, deep=True):
out = dict()
for key in self._get_param_names():
value = getattr(self, key)
- if deep and hasattr(value, "get_params"):
+ if deep and hasattr(value, "get_params") and not isinstance(value, type):
deep_items = value.get_params().items()
out.update((key + "__" + k, val) for k, val in deep_items)
out[key] = value
diff --git a/sklearn/cluster/_bicluster.py b/sklearn/cluster/_bicluster.py
index a360802009f2c..c8b46fa38c6d1 100644
--- a/sklearn/cluster/_bicluster.py
+++ b/sklearn/cluster/_bicluster.py
@@ -385,7 +385,7 @@ class SpectralBiclustering(BaseSpectral):
default is 'bistochastic'.
.. warning::
- if `method='log'`, the data must be sparse.
+ if `method='log'`, the data must not be sparse.
n_components : int, default=6
Number of singular vectors to check.
diff --git a/sklearn/cluster/_birch.py b/sklearn/cluster/_birch.py
index cfdfeab27b15c..604365a184af7 100644
--- a/sklearn/cluster/_birch.py
+++ b/sklearn/cluster/_birch.py
@@ -89,6 +89,11 @@ def _split_node(node, threshold, branching_factor):
node1_dist, node2_dist = dist[(farthest_idx,)]
node1_closer = node1_dist < node2_dist
+ # make sure node1 is closest to itself even if all distances are equal.
+ # This can only happen when all node.centroids_ are duplicates leading to all
+ # distances between centroids being zero.
+ node1_closer[farthest_idx[0]] = True
+
for idx, subcluster in enumerate(node.subclusters_):
if node1_closer[idx]:
new_node1.append_subcluster(subcluster)
diff --git a/sklearn/cluster/_bisect_k_means.py b/sklearn/cluster/_bisect_k_means.py
index c7dc2c5a772e5..90ca5107f67b4 100644
--- a/sklearn/cluster/_bisect_k_means.py
+++ b/sklearn/cluster/_bisect_k_means.py
@@ -183,7 +183,7 @@ class BisectingKMeans(_BaseKMeans):
Notes
-----
- It might be inefficient when n_cluster is less than 3, due to unnecassary
+ It might be inefficient when n_cluster is less than 3, due to unnecessary
calculations for that case.
Examples
diff --git a/sklearn/cluster/_kmeans.py b/sklearn/cluster/_kmeans.py
index eca8a5c2dc3ce..70a595936b41c 100644
--- a/sklearn/cluster/_kmeans.py
+++ b/sklearn/cluster/_kmeans.py
@@ -642,7 +642,7 @@ def _kmeans_single_lloyd(
strict_convergence = False
# Threadpoolctl context to limit the number of threads in second level of
- # nested parallelism (i.e. BLAS) to avoid oversubsciption.
+ # nested parallelism (i.e. BLAS) to avoid oversubscription.
with threadpool_limits(limits=1, user_api="blas"):
for i in range(max_iter):
lloyd_iter(
@@ -1139,9 +1139,11 @@ class KMeans(_BaseKMeans):
(n_clusters, n_features), default='k-means++'
Method for initialization:
- 'k-means++' : selects initial cluster centers for k-mean
- clustering in a smart way to speed up convergence. See section
- Notes in k_init for more details.
+ 'k-means++' : selects initial cluster centroids using sampling based on
+ an empirical probability distribution of the points' contribution to the
+ overall inertia. This technique speeds up convergence, and is
+ theoretically proven to be :math:`\\mathcal{O}(\\log k)`-optimal.
+ See the description of `n_init` for more details.
'random': choose `n_clusters` observations (rows) at random from data
for the initial centroids.
@@ -1601,9 +1603,11 @@ class MiniBatchKMeans(_BaseKMeans):
(n_clusters, n_features), default='k-means++'
Method for initialization:
- 'k-means++' : selects initial cluster centers for k-mean
- clustering in a smart way to speed up convergence. See section
- Notes in k_init for more details.
+ 'k-means++' : selects initial cluster centroids using sampling based on
+ an empirical probability distribution of the points' contribution to the
+ overall inertia. This technique speeds up convergence, and is
+ theoretically proven to be :math:`\\mathcal{O}(\\log k)`-optimal.
+ See the description of `n_init` for more details.
'random': choose `n_clusters` observations (rows) at random from data
for the initial centroids.
@@ -1667,8 +1671,10 @@ class MiniBatchKMeans(_BaseKMeans):
n_init : int, default=3
Number of random initializations that are tried.
- In contrast to KMeans, the algorithm is only run once, using the
- best of the ``n_init`` initializations as measured by inertia.
+ In contrast to KMeans, the algorithm is only run once, using the best of
+ the `n_init` initializations as measured by inertia. Several runs are
+ recommended for sparse high-dimensional problems (see
+ :ref:`kmeans_sparse_high_dim`).
reassignment_ratio : float, default=0.01
Control the fraction of the maximum number of counts for a center to
@@ -1724,6 +1730,12 @@ class MiniBatchKMeans(_BaseKMeans):
-----
See https://www.eecs.tufts.edu/~dsculley/papers/fastkmeans.pdf
+ When there are too few points in the dataset, some centers may be
+ duplicated, which means that a proper clustering in terms of the number
+ of requesting clusters and the number of returned clusters will not
+ always match. One solution is to set `reassignment_ratio=0`, which
+ prevents reassignments of clusters that are too small.
+
Examples
--------
>>> from sklearn.cluster import MiniBatchKMeans
diff --git a/sklearn/cluster/_spectral.py b/sklearn/cluster/_spectral.py
index 390b567c0d0bb..22bf204f2094c 100644
--- a/sklearn/cluster/_spectral.py
+++ b/sklearn/cluster/_spectral.py
@@ -561,11 +561,11 @@ class SpectralClustering(ClusterMixin, BaseEstimator):
Stella X. Yu, Jianbo Shi
`_
- .. [4] `Toward the Optimal Preconditioned Eigensolver:
- Locally Optimal Block Preconditioned Conjugate Gradient Method, 2001.
+ .. [4] :doi:`Toward the Optimal Preconditioned Eigensolver:
+ Locally Optimal Block Preconditioned Conjugate Gradient Method, 2001
A. V. Knyazev
SIAM Journal on Scientific Computing 23, no. 2, pp. 517-541.
- `_
+ <10.1137/S1064827500366124>`
.. [5] :doi:`Simple, direct, and efficient multi-way spectral clustering, 2019
Anil Damle, Victor Minden, Lexing Ying
diff --git a/sklearn/cluster/tests/test_birch.py b/sklearn/cluster/tests/test_birch.py
index e0051704653ae..a42f421c8f248 100644
--- a/sklearn/cluster/tests/test_birch.py
+++ b/sklearn/cluster/tests/test_birch.py
@@ -228,3 +228,39 @@ def test_feature_names_out():
names_out = brc.get_feature_names_out()
assert_array_equal([f"birch{i}" for i in range(n_clusters)], names_out)
+
+
+def test_both_subclusters_updated():
+ """Check that both subclusters are updated when a node a split, even when there are
+ duplicated data points. Non-regression test for #23269.
+ """
+
+ X = np.array(
+ [
+ [-2.6192791, -1.5053215],
+ [-2.9993038, -1.6863596],
+ [-2.3724914, -1.3438171],
+ [-2.336792, -1.3417323],
+ [-2.4089134, -1.3290224],
+ [-2.3724914, -1.3438171],
+ [-3.364009, -1.8846745],
+ [-2.3724914, -1.3438171],
+ [-2.617677, -1.5003285],
+ [-2.2960556, -1.3260119],
+ [-2.3724914, -1.3438171],
+ [-2.5459878, -1.4533926],
+ [-2.25979, -1.3003055],
+ [-2.4089134, -1.3290224],
+ [-2.3724914, -1.3438171],
+ [-2.4089134, -1.3290224],
+ [-2.5459878, -1.4533926],
+ [-2.3724914, -1.3438171],
+ [-2.9720619, -1.7058647],
+ [-2.336792, -1.3417323],
+ [-2.3724914, -1.3438171],
+ ],
+ dtype=np.float32,
+ )
+
+ # no error
+ Birch(branching_factor=5, threshold=1e-5, n_clusters=None).fit(X)
diff --git a/sklearn/compose/_column_transformer.py b/sklearn/compose/_column_transformer.py
index 15f1424498856..84e122fab3cc8 100644
--- a/sklearn/compose/_column_transformer.py
+++ b/sklearn/compose/_column_transformer.py
@@ -190,6 +190,23 @@ class ColumnTransformer(TransformerMixin, _BaseComposition):
>>> ct.fit_transform(X)
array([[0. , 1. , 0.5, 0.5],
[0.5, 0.5, 0. , 1. ]])
+
+ :class:`ColumnTransformer` can be configured with a transformer that requires
+ a 1d array by setting the column to a string:
+
+ >>> from sklearn.feature_extraction import FeatureHasher
+ >>> from sklearn.preprocessing import MinMaxScaler
+ >>> import pandas as pd # doctest: +SKIP
+ >>> X = pd.DataFrame({
+ ... "documents": ["First item", "second one here", "Is this the last?"],
+ ... "width": [3, 4, 5],
+ ... }) # doctest: +SKIP
+ >>> # "documents" is a string which configures ColumnTransformer to
+ >>> # pass the documents column as a 1d array to the FeatureHasher
+ >>> ct = ColumnTransformer(
+ ... [("text_preprocess", FeatureHasher(input_type="string"), "documents"),
+ ... ("num_preprocess", MinMaxScaler(), ["width"])])
+ >>> X_trans = ct.fit_transform(X) # doctest: +SKIP
"""
_required_parameters = ["transformers"]
diff --git a/sklearn/covariance/_empirical_covariance.py b/sklearn/covariance/_empirical_covariance.py
index 4362a14f04f6e..532390d74bded 100644
--- a/sklearn/covariance/_empirical_covariance.py
+++ b/sklearn/covariance/_empirical_covariance.py
@@ -207,7 +207,7 @@ def get_precision(self):
return precision
def fit(self, X, y=None):
- """Fit the maximum liklihood covariance estimator to X.
+ """Fit the maximum likelihood covariance estimator to X.
Parameters
----------
diff --git a/sklearn/cross_decomposition/_pls.py b/sklearn/cross_decomposition/_pls.py
index 8a804142e13bb..905e0d108d40b 100644
--- a/sklearn/cross_decomposition/_pls.py
+++ b/sklearn/cross_decomposition/_pls.py
@@ -170,7 +170,7 @@ class _PLS(
Main ref: Wegelin, a survey of Partial Least Squares (PLS) methods,
with emphasis on the two-block case
- https://www.stat.washington.edu/research/reports/2000/tr371.pdf
+ https://stat.uw.edu/sites/default/files/files/reports/2000/tr371.pdf
"""
@abstractmethod
@@ -1038,7 +1038,7 @@ def transform(self, X, Y=None):
Returns
-------
x_scores : array-like or tuple of array-like
- The transformed data `X_tranformed` if `Y is not None`,
+ The transformed data `X_transformed` if `Y is not None`,
`(X_transformed, Y_transformed)` otherwise.
"""
check_is_fitted(self)
@@ -1069,7 +1069,7 @@ def fit_transform(self, X, y=None):
Returns
-------
out : array-like or tuple of array-like
- The transformed data `X_tranformed` if `Y is not None`,
+ The transformed data `X_transformed` if `Y is not None`,
`(X_transformed, Y_transformed)` otherwise.
"""
return self.fit(X, y).transform(X, y)
diff --git a/sklearn/datasets/_base.py b/sklearn/datasets/_base.py
index 367816fa4a467..a9a5f3b39c3ec 100644
--- a/sklearn/datasets/_base.py
+++ b/sklearn/datasets/_base.py
@@ -346,7 +346,7 @@ def load_gzip_compressed_csv_data(
encoding="utf-8",
**kwargs,
):
- """Loads gzip-compressed `data_file_name` from `data_module` with `importlib.resources`.
+ """Loads gzip-compressed with `importlib.resources`.
1) Open resource file with `importlib.resources.open_binary`
2) Decompress file obj with `gzip.open`
diff --git a/sklearn/datasets/_kddcup99.py b/sklearn/datasets/_kddcup99.py
index b698d299b7c8d..f9d609d362c04 100644
--- a/sklearn/datasets/_kddcup99.py
+++ b/sklearn/datasets/_kddcup99.py
@@ -133,6 +133,10 @@ def fetch_kddcup99(
The names of the target columns
(data, target) : tuple if ``return_X_y`` is True
+ A tuple of two ndarray. The first containing a 2D array of
+ shape (n_samples, n_features) with each row representing one
+ sample and each column representing the features. The second
+ ndarray of shape (n_samples,) containing the target samples.
.. versionadded:: 0.20
"""
@@ -225,7 +229,6 @@ def fetch_kddcup99(
def _fetch_brute_kddcup99(data_home=None, download_if_missing=True, percent10=True):
-
"""Load the kddcup99 dataset, downloading it if necessary.
Parameters
diff --git a/sklearn/datasets/_lfw.py b/sklearn/datasets/_lfw.py
index dc1267af59f96..33c1234f907b7 100644
--- a/sklearn/datasets/_lfw.py
+++ b/sklearn/datasets/_lfw.py
@@ -308,13 +308,15 @@ def fetch_lfw_people(
target : numpy array of shape (13233,)
Labels associated to each face image.
Those labels range from 0-5748 and correspond to the person IDs.
+ target_names : numpy array of shape (5749,)
+ Names of all persons in the dataset.
+ Position in array corresponds to the person ID in the target array.
DESCR : str
Description of the Labeled Faces in the Wild (LFW) dataset.
(data, target) : tuple if ``return_X_y`` is True
.. versionadded:: 0.20
-
"""
lfw_home, data_folder_path = _check_fetch_lfw(
data_home=data_home, funneled=funneled, download_if_missing=download_if_missing
@@ -464,7 +466,7 @@ def fetch_lfw_pairs(
slice_ : tuple of slice, default=(slice(70, 195), slice(78, 172))
Provide a custom 2D slice (height, width) to extract the
'interesting' part of the jpeg files and avoid use statistical
- correlation from the background
+ correlation from the background.
download_if_missing : bool, default=True
If False, raise a IOError if the data is not locally available
@@ -489,9 +491,11 @@ def fetch_lfw_pairs(
target : numpy array of shape (2200,). Shape depends on ``subset``.
Labels associated to each pair of images.
The two label values being different persons or the same person.
+ target_names : numpy array of shape (2,)
+ Explains the target values of the target array.
+ 0 corresponds to "Different person", 1 corresponds to "same person".
DESCR : str
Description of the Labeled Faces in the Wild (LFW) dataset.
-
"""
lfw_home, data_folder_path = _check_fetch_lfw(
data_home=data_home, funneled=funneled, download_if_missing=download_if_missing
diff --git a/sklearn/datasets/_samples_generator.py b/sklearn/datasets/_samples_generator.py
index 4bb61cf85dbca..c144f9b255a06 100644
--- a/sklearn/datasets/_samples_generator.py
+++ b/sklearn/datasets/_samples_generator.py
@@ -1405,7 +1405,7 @@ def make_spd_matrix(n_dim, *, random_state=None):
See Also
--------
- make_sparse_spd_matrix
+ make_sparse_spd_matrix: Generate a sparse symmetric definite positive matrix.
"""
generator = check_random_state(random_state)
@@ -1532,9 +1532,9 @@ def make_swiss_roll(n_samples=100, *, noise=0.0, random_state=None, hole=False):
References
----------
- .. [1] S. Marsland, "Machine Learning: An Algorithmic Perspective",
- Chapter 10, 2009.
- http://seat.massey.ac.nz/personal/s.r.marsland/Code/10/lle.py
+ .. [1] S. Marsland, "Machine Learning: An Algorithmic Perspective", 2nd edition,
+ Chapter 6, 2014.
+ https://homepages.ecs.vuw.ac.nz/~marslast/Code/Ch6/lle.py
"""
generator = check_random_state(random_state)
@@ -1637,7 +1637,7 @@ def make_gaussian_quantiles(
The number of features for each sample.
n_classes : int, default=3
- The number of classes
+ The number of classes.
shuffle : bool, default=True
Shuffle the samples.
@@ -1662,7 +1662,6 @@ def make_gaussian_quantiles(
References
----------
.. [1] J. Zhu, H. Zou, S. Rosset, T. Hastie, "Multi-class AdaBoost", 2009.
-
"""
if n_samples < n_classes:
raise ValueError("n_samples must be at least n_classes")
diff --git a/sklearn/datasets/descr/olivetti_faces.rst b/sklearn/datasets/descr/olivetti_faces.rst
index c6193d5056538..4feadcc4b2fb1 100644
--- a/sklearn/datasets/descr/olivetti_faces.rst
+++ b/sklearn/datasets/descr/olivetti_faces.rst
@@ -9,7 +9,7 @@ April 1994 at AT&T Laboratories Cambridge. The
fetching / caching function that downloads the data
archive from AT&T.
-.. _This dataset contains a set of face images: http://www.cl.cam.ac.uk/research/dtg/attarchive/facedatabase.html
+.. _This dataset contains a set of face images: https://cam-orl.co.uk/facedatabase.html
As described on the original website:
diff --git a/sklearn/datasets/descr/twenty_newsgroups.rst b/sklearn/datasets/descr/twenty_newsgroups.rst
index a7542ea57d529..3a327a4cbc19c 100644
--- a/sklearn/datasets/descr/twenty_newsgroups.rst
+++ b/sklearn/datasets/descr/twenty_newsgroups.rst
@@ -226,7 +226,7 @@ the ``--filter`` option to compare the results.
discussion sparked by the death of George Floyd and a national reckoning over
race and colonialism, the Cleveland Indians have decided to change their
name." Team owner Paul Dolan "did make it clear that the team will not make
- its informal nickname -- the Tribe -- its new team name." "It’s not going to
+ its informal nickname -- the Tribe -- its new team name." "It's not going to
be a half-step away from the Indians," Dolan said."We will not have a Native
American-themed name."
diff --git a/sklearn/datasets/descr/wine_data.rst b/sklearn/datasets/descr/wine_data.rst
index bfde9288fa4dd..dbe7f38e44aa6 100644
--- a/sklearn/datasets/descr/wine_data.rst
+++ b/sklearn/datasets/descr/wine_data.rst
@@ -5,7 +5,7 @@ Wine recognition dataset
**Data Set Characteristics:**
- :Number of Instances: 178 (50 in each of three classes)
+ :Number of Instances: 178
:Number of Attributes: 13 numeric, predictive attributes and the class
:Attribute Information:
- Alcohol
diff --git a/sklearn/decomposition/_factor_analysis.py b/sklearn/decomposition/_factor_analysis.py
index 4b8eab3492ca8..303e7ea280b93 100644
--- a/sklearn/decomposition/_factor_analysis.py
+++ b/sklearn/decomposition/_factor_analysis.py
@@ -45,7 +45,7 @@ class FactorAnalysis(_ClassNamePrefixFeaturesOutMixin, TransformerMixin, BaseEst
If we would restrict the model further, by assuming that the Gaussian
noise is even isotropic (all diagonal entries are the same) we would obtain
- :class:`PPCA`.
+ :class:`PCA`.
FactorAnalysis performs a maximum likelihood estimate of the so-called
`loading` matrix, the transformation of the latent variables to the
diff --git a/sklearn/decomposition/_fastica.py b/sklearn/decomposition/_fastica.py
index 490a3323344d1..a6ddac4cb3347 100644
--- a/sklearn/decomposition/_fastica.py
+++ b/sklearn/decomposition/_fastica.py
@@ -180,22 +180,23 @@ def fastica(
`n_features` is the number of features.
n_components : int, default=None
- Number of components to extract. If None no dimension reduction
- is performed.
+ Number of components to use. If None is passed, all are used.
algorithm : {'parallel', 'deflation'}, default='parallel'
- Apply a parallel or deflational FASTICA algorithm.
+ Specify which algorithm to use for FastICA.
whiten : str or bool, default="warn"
Specify the whitening strategy to use.
- If 'arbitrary-variance' (default), a whitening with variance arbitrary is used.
- If 'unit-variance', the whitening matrix is rescaled to ensure that each
- recovered source has unit variance.
- If False, the data is already considered to be whitened, and no
- whitening is performed.
+
+ - If 'arbitrary-variance' (default), a whitening with variance
+ arbitrary is used.
+ - If 'unit-variance', the whitening matrix is rescaled to ensure that
+ each recovered source has unit variance.
+ - If False, the data is already considered to be whitened, and no
+ whitening is performed.
.. deprecated:: 1.1
- From version 1.3, `whiten='unit-variance'` will be used by default.
+ Starting in v1.3, `whiten='unit-variance'` will be used by default.
`whiten=True` is deprecated from 1.1 and will raise ValueError in 1.3.
Use `whiten=arbitrary-variance` instead.
@@ -206,10 +207,10 @@ def fastica(
You can also provide your own function. It should return a tuple
containing the value of the function, and of its derivative, in the
point. The derivative should be averaged along its last dimension.
- Example:
+ Example::
- def my_g(x):
- return x ** 3, np.mean(3 * x ** 2, axis=-1)
+ def my_g(x):
+ return x ** 3, (3 * x ** 2).mean(axis=-1)
fun_args : dict, default=None
Arguments to send to the functional form.
@@ -219,13 +220,13 @@ def my_g(x):
max_iter : int, default=200
Maximum number of iterations to perform.
- tol : float, default=1e-04
+ tol : float, default=1e-4
A positive scalar giving the tolerance at which the
un-mixing matrix is considered to have converged.
w_init : ndarray of shape (n_components, n_components), default=None
- Initial un-mixing array of dimension (n.comp,n.comp).
- If None (default) then an array of normal r.v.'s is used.
+ Initial un-mixing array. If `w_init=None`, then an array of values
+ drawn from a normal distribution is used.
random_state : int, RandomState instance or None, default=None
Used to initialize ``w_init`` when not specified, with a
@@ -332,18 +333,20 @@ class FastICA(_ClassNamePrefixFeaturesOutMixin, TransformerMixin, BaseEstimator)
Number of components to use. If None is passed, all are used.
algorithm : {'parallel', 'deflation'}, default='parallel'
- Apply parallel or deflational algorithm for FastICA.
+ Specify which algorithm to use for FastICA.
whiten : str or bool, default="warn"
Specify the whitening strategy to use.
- If 'arbitrary-variance' (default), a whitening with variance arbitrary is used.
- If 'unit-variance', the whitening matrix is rescaled to ensure that each
- recovered source has unit variance.
- If False, the data is already considered to be whitened, and no
- whitening is performed.
+
+ - If 'arbitrary-variance' (default), a whitening with variance
+ arbitrary is used.
+ - If 'unit-variance', the whitening matrix is rescaled to ensure that
+ each recovered source has unit variance.
+ - If False, the data is already considered to be whitened, and no
+ whitening is performed.
.. deprecated:: 1.1
- From version 1.3 whiten='unit-variance' will be used by default.
+ Starting in v1.3, `whiten='unit-variance'` will be used by default.
`whiten=True` is deprecated from 1.1 and will raise ValueError in 1.3.
Use `whiten=arbitrary-variance` instead.
@@ -353,24 +356,27 @@ class FastICA(_ClassNamePrefixFeaturesOutMixin, TransformerMixin, BaseEstimator)
or 'cube'.
You can also provide your own function. It should return a tuple
containing the value of the function, and of its derivative, in the
- point. Example::
+ point. The derivative should be averaged along its last dimension.
+ Example::
def my_g(x):
return x ** 3, (3 * x ** 2).mean(axis=-1)
fun_args : dict, default=None
Arguments to send to the functional form.
- If empty and if fun='logcosh', fun_args will take value
+ If empty or None and if fun='logcosh', fun_args will take value
{'alpha' : 1.0}.
max_iter : int, default=200
Maximum number of iterations during fit.
tol : float, default=1e-4
- Tolerance on update at each iteration.
+ A positive scalar giving the tolerance at which the
+ un-mixing matrix is considered to have converged.
w_init : ndarray of shape (n_components, n_components), default=None
- The mixing matrix to be used to initialize the algorithm.
+ Initial un-mixing array. If `w_init=None`, then an array of values
+ drawn from a normal distribution is used.
random_state : int, RandomState instance or None, default=None
Used to initialize ``w_init`` when not specified, with a
@@ -486,14 +492,14 @@ def _fit(self, X, compute_sources=False):
if self._whiten == "warn":
warnings.warn(
- "From version 1.3 whiten='unit-variance' will be used by default.",
+ "Starting in v1.3, whiten='unit-variance' will be used by default.",
FutureWarning,
)
self._whiten = "arbitrary-variance"
if self._whiten is True:
warnings.warn(
- "From version 1.3 whiten=True should be specified as "
+ "Starting in v1.3, whiten=True should be specified as "
"whiten='arbitrary-variance' (its current behaviour). This "
"behavior is deprecated in 1.1 and will raise ValueError in 1.3.",
FutureWarning,
diff --git a/sklearn/decomposition/_incremental_pca.py b/sklearn/decomposition/_incremental_pca.py
index 589796a7c97f7..da050a7e42efa 100644
--- a/sklearn/decomposition/_incremental_pca.py
+++ b/sklearn/decomposition/_incremental_pca.py
@@ -137,10 +137,9 @@ class IncrementalPCA(_BasePCA):
See https://www.cs.toronto.edu/~dross/ivt/RossLimLinYang_ijcv.pdf
This model is an extension of the Sequential Karhunen-Loeve Transform from:
- *A. Levy and M. Lindenbaum, Sequential Karhunen-Loeve Basis Extraction and
+ :doi:`A. Levy and M. Lindenbaum, Sequential Karhunen-Loeve Basis Extraction and
its Application to Images, IEEE Transactions on Image Processing, Volume 9,
- Number 8, pp. 1371-1374, August 2000.*
- See https://www.cs.technion.ac.il/~mic/doc/skl-ip.pdf
+ Number 8, pp. 1371-1374, August 2000. <10.1109/83.855432>`
We have specifically abstained from an optimization used by authors of both
papers, a QR decomposition used in specific situations to reduce the
diff --git a/sklearn/decomposition/_kernel_pca.py b/sklearn/decomposition/_kernel_pca.py
index 4e3ad720ae126..9f598c3eba670 100644
--- a/sklearn/decomposition/_kernel_pca.py
+++ b/sklearn/decomposition/_kernel_pca.py
@@ -216,7 +216,7 @@ class KernelPCA(_ClassNamePrefixFeaturesOutMixin, TransformerMixin, BaseEstimato
.. [2] `Bakır, Gökhan H., Jason Weston, and Bernhard Schölkopf.
"Learning to find pre-images."
Advances in neural information processing systems 16 (2004): 449-456.
- `_
+ `_
.. [3] :arxiv:`Halko, Nathan, Per-Gunnar Martinsson, and Joel A. Tropp.
"Finding structure with randomness: Probabilistic algorithms for
@@ -396,7 +396,7 @@ def _fit_inverse_transform(self, X_transformed, X):
n_samples = X_transformed.shape[0]
K = self._get_kernel(X_transformed)
K.flat[:: n_samples + 1] += self.alpha
- self.dual_coef_ = linalg.solve(K, X, sym_pos=True, overwrite_a=True)
+ self.dual_coef_ = linalg.solve(K, X, assume_a="pos", overwrite_a=True)
self.X_transformed_fit_ = X_transformed
def fit(self, X, y=None):
@@ -532,7 +532,7 @@ def inverse_transform(self, X):
`Bakır, Gökhan H., Jason Weston, and Bernhard Schölkopf.
"Learning to find pre-images."
Advances in neural information processing systems 16 (2004): 449-456.
- `_
+ `_
"""
if not self.fit_inverse_transform:
raise NotFittedError(
diff --git a/sklearn/decomposition/_lda.py b/sklearn/decomposition/_lda.py
index 6db9d900566eb..0d72f18cce1d1 100644
--- a/sklearn/decomposition/_lda.py
+++ b/sklearn/decomposition/_lda.py
@@ -292,7 +292,7 @@ class LatentDirichletAllocation(
--------
sklearn.discriminant_analysis.LinearDiscriminantAnalysis:
A classifier with a linear decision boundary, generated by fitting
- class conditional densities to the data and using Bayes’ rule.
+ class conditional densities to the data and using Bayes' rule.
References
----------
diff --git a/sklearn/decomposition/_pca.py b/sklearn/decomposition/_pca.py
index 635e119ae445d..07502c5a034cd 100644
--- a/sklearn/decomposition/_pca.py
+++ b/sklearn/decomposition/_pca.py
@@ -211,7 +211,7 @@ class PCA(_BasePCA):
.. versionadded:: 1.1
- power_iteration_normalizer : {‘auto’, ‘QR’, ‘LU’, ‘none’}, default=’auto’
+ power_iteration_normalizer : {'auto', 'QR', 'LU', 'none'}, default='auto'
Power iteration normalizer for randomized SVD solver.
Not used by ARPACK. See :func:`~sklearn.utils.extmath.randomized_svd`
for more details.
diff --git a/sklearn/decomposition/_truncated_svd.py b/sklearn/decomposition/_truncated_svd.py
index b8417543783d4..b5b44c236f46b 100644
--- a/sklearn/decomposition/_truncated_svd.py
+++ b/sklearn/decomposition/_truncated_svd.py
@@ -67,7 +67,7 @@ class TruncatedSVD(_ClassNamePrefixFeaturesOutMixin, TransformerMixin, BaseEstim
.. versionadded:: 1.1
- power_iteration_normalizer : {‘auto’, ‘QR’, ‘LU’, ‘none’}, default=’auto’
+ power_iteration_normalizer : {'auto', 'QR', 'LU', 'none'}, default='auto'
Power iteration normalizer for randomized SVD solver.
Not used by ARPACK. See :func:`~sklearn.utils.extmath.randomized_svd`
for more details.
diff --git a/sklearn/decomposition/tests/test_fastica.py b/sklearn/decomposition/tests/test_fastica.py
index 082b7d68dee79..b8c99803df373 100644
--- a/sklearn/decomposition/tests/test_fastica.py
+++ b/sklearn/decomposition/tests/test_fastica.py
@@ -71,7 +71,7 @@ def test_fastica_return_dtypes(global_dtype):
# FIXME remove filter in 1.3
@pytest.mark.filterwarnings(
- "ignore:From version 1.3 whiten='unit-variance' will be used by default."
+ "ignore:Starting in v1.3, whiten='unit-variance' will be used by default."
)
@pytest.mark.parametrize("add_noise", [True, False])
def test_fastica_simple(add_noise, global_random_seed, global_dtype):
@@ -353,7 +353,7 @@ def test_inverse_transform(
# FIXME remove filter in 1.3
@pytest.mark.filterwarnings(
- "ignore:From version 1.3 whiten='unit-variance' will be used by default."
+ "ignore:Starting in v1.3, whiten='unit-variance' will be used by default."
)
def test_fastica_errors():
n_features = 3
@@ -398,7 +398,7 @@ def test_fastica_whiten_default_value_deprecation(ica):
"""
rng = np.random.RandomState(0)
X = rng.random_sample((100, 10))
- with pytest.warns(FutureWarning, match=r"From version 1.3 whiten="):
+ with pytest.warns(FutureWarning, match=r"Starting in v1.3, whiten="):
ica.fit(X)
assert ica._whiten == "arbitrary-variance"
diff --git a/sklearn/decomposition/tests/test_nmf.py b/sklearn/decomposition/tests/test_nmf.py
index 9f3df5b64a803..930483eaa438e 100644
--- a/sklearn/decomposition/tests/test_nmf.py
+++ b/sklearn/decomposition/tests/test_nmf.py
@@ -51,13 +51,12 @@ def test_initialize_nn_output():
)
def test_parameter_checking():
A = np.ones((2, 2))
- name = "spam"
with ignore_warnings(category=FutureWarning):
# TODO remove in 1.2
msg = "Invalid regularization parameter: got 'spam' instead of one of"
with pytest.raises(ValueError, match=msg):
- NMF(regularization=name).fit(A)
+ NMF(regularization="spam").fit(A)
msg = "Invalid beta_loss parameter: solver 'cd' does not handle beta_loss = 1.0"
with pytest.raises(ValueError, match=msg):
diff --git a/sklearn/ensemble/_bagging.py b/sklearn/ensemble/_bagging.py
index adac7e063191f..6db09c3996406 100644
--- a/sklearn/ensemble/_bagging.py
+++ b/sklearn/ensemble/_bagging.py
@@ -561,7 +561,7 @@ class BaggingClassifier(ClassifierMixin, BaseBagging):
details).
- If int, then draw `max_features` features.
- - If float, then draw `max_features * X.shape[1]` features.
+ - If float, then draw `max(1, int(max_features * n_features_in_))` features.
bootstrap : bool, default=True
Whether samples are drawn with replacement. If False, sampling
@@ -993,7 +993,7 @@ class BaggingRegressor(RegressorMixin, BaseBagging):
details).
- If int, then draw `max_features` features.
- - If float, then draw `max_features * X.shape[1]` features.
+ - If float, then draw `max(1, int(max_features * n_features_in_))` features.
bootstrap : bool, default=True
Whether samples are drawn with replacement. If False, sampling
diff --git a/sklearn/ensemble/_forest.py b/sklearn/ensemble/_forest.py
index 919586001c58e..3212d6bb59457 100644
--- a/sklearn/ensemble/_forest.py
+++ b/sklearn/ensemble/_forest.py
@@ -1157,7 +1157,7 @@ class RandomForestClassifier(ForestClassifier):
- If int, then consider `max_features` features at each split.
- If float, then `max_features` is a fraction and
- `round(max_features * n_features)` features are considered at each
+ `max(1, int(max_features * n_features_in_))` features are considered at each
split.
- If "auto", then `max_features=sqrt(n_features)`.
- If "sqrt", then `max_features=sqrt(n_features)`.
@@ -1518,7 +1518,7 @@ class RandomForestRegressor(ForestRegressor):
- If int, then consider `max_features` features at each split.
- If float, then `max_features` is a fraction and
- `round(max_features * n_features)` features are considered at each
+ `max(1, int(max_features * n_features_in_))` features are considered at each
split.
- If "auto", then `max_features=n_features`.
- If "sqrt", then `max_features=sqrt(n_features)`.
@@ -1829,7 +1829,7 @@ class ExtraTreesClassifier(ForestClassifier):
- If int, then consider `max_features` features at each split.
- If float, then `max_features` is a fraction and
- `round(max_features * n_features)` features are considered at each
+ `max(1, int(max_features * n_features_in_))` features are considered at each
split.
- If "auto", then `max_features=sqrt(n_features)`.
- If "sqrt", then `max_features=sqrt(n_features)`.
@@ -2177,7 +2177,7 @@ class ExtraTreesRegressor(ForestRegressor):
- If int, then consider `max_features` features at each split.
- If float, then `max_features` is a fraction and
- `round(max_features * n_features)` features are considered at each
+ `max(1, int(max_features * n_features_in_))` features are considered at each
split.
- If "auto", then `max_features=n_features`.
- If "sqrt", then `max_features=sqrt(n_features)`.
diff --git a/sklearn/ensemble/_gb.py b/sklearn/ensemble/_gb.py
index 9b776a7feab10..316a30699b4be 100644
--- a/sklearn/ensemble/_gb.py
+++ b/sklearn/ensemble/_gb.py
@@ -991,12 +991,15 @@ def loss_(self):
class GradientBoostingClassifier(ClassifierMixin, BaseGradientBoosting):
"""Gradient Boosting for classification.
- GB builds an additive model in a
- forward stage-wise fashion; it allows for the optimization of
- arbitrary differentiable loss functions. In each stage ``n_classes_``
- regression trees are fit on the negative gradient of the loss function,
- e.g. binary or multiclass log loss. Binary classification
- is a special case where only a single regression tree is induced.
+ This algorithm builds an additive model in a forward stage-wise fashion; it
+ allows for the optimization of arbitrary differentiable loss functions. In
+ each stage ``n_classes_`` regression trees are fit on the negative gradient
+ of the loss function, e.g. binary or multiclass log loss. Binary
+ classification is a special case where only a single regression tree is
+ induced.
+
+ :class:`sklearn.ensemble.HistGradientBoostingClassifier` is a much faster
+ variant of this algorithm for intermediate datasets (`n_samples >= 10_000`).
Read more in the :ref:`User Guide `.
@@ -1122,7 +1125,7 @@ class GradientBoostingClassifier(ClassifierMixin, BaseGradientBoosting):
- If int, values must be in the range `[1, inf)`.
- If float, values must be in the range `(0.0, 1.0]` and the features
- considered at each split will be `int(max_features * n_features)`.
+ considered at each split will be `max(1, int(max_features * n_features_in_))`.
- If 'auto', then `max_features=sqrt(n_features)`.
- If 'sqrt', then `max_features=sqrt(n_features)`.
- If 'log2', then `max_features=log2(n_features)`.
@@ -1559,10 +1562,13 @@ def staged_predict_proba(self, X):
class GradientBoostingRegressor(RegressorMixin, BaseGradientBoosting):
"""Gradient Boosting for regression.
- GB builds an additive model in a forward stage-wise fashion;
- it allows for the optimization of arbitrary differentiable loss functions.
- In each stage a regression tree is fit on the negative gradient of the
- given loss function.
+ This estimator builds an additive model in a forward stage-wise fashion; it
+ allows for the optimization of arbitrary differentiable loss functions. In
+ each stage a regression tree is fit on the negative gradient of the given
+ loss function.
+
+ :class:`sklearn.ensemble.HistGradientBoostingRegressor` is a much faster
+ variant of this algorithm for intermediate datasets (`n_samples >= 10_000`).
Read more in the :ref:`User Guide `.
@@ -1695,7 +1701,7 @@ class GradientBoostingRegressor(RegressorMixin, BaseGradientBoosting):
- If int, values must be in the range `[1, inf)`.
- If float, values must be in the range `(0.0, 1.0]` and the features
- considered at each split will be `int(max_features * n_features)`.
+ considered at each split will be `max(1, int(max_features * n_features_in_))`.
- If "auto", then `max_features=n_features`.
- If "sqrt", then `max_features=sqrt(n_features)`.
- If "log2", then `max_features=log2(n_features)`.
diff --git a/sklearn/ensemble/_iforest.py b/sklearn/ensemble/_iforest.py
index 4be74d2873b9e..a80cd31294b1c 100644
--- a/sklearn/ensemble/_iforest.py
+++ b/sklearn/ensemble/_iforest.py
@@ -78,7 +78,7 @@ class IsolationForest(OutlierMixin, BaseBagging):
The number of features to draw from X to train each base estimator.
- If int, then draw `max_features` features.
- - If float, then draw `max_features * X.shape[1]` features.
+ - If float, then draw `max(1, int(max_features * n_features_in_))` features.
bootstrap : bool, default=False
If True, individual trees are fit on random subsets of the training
diff --git a/sklearn/externals/_lobpcg.py b/sklearn/externals/_lobpcg.py
index 1de3900b3f89c..8340b322c7b3c 100644
--- a/sklearn/externals/_lobpcg.py
+++ b/sklearn/externals/_lobpcg.py
@@ -141,7 +141,7 @@ def lobpcg(
retLambdaHistory=False,
retResidualNormsHistory=False,
):
- """Locally Optimal Block Preconditioned Conjugate Gradient Method (LOBPCG)
+ """Locally Optimal Block Preconditioned Conjugate Gradient Method (LOBPCG).
LOBPCG is a preconditioned eigensolver for large symmetric positive
definite (SPD) generalized eigenproblems.
@@ -161,7 +161,7 @@ def lobpcg(
Preconditioner to `A`; by default ``M = Identity``.
`M` should approximate the inverse of `A`.
Y : ndarray, float32 or float64, optional
- n-by-sizeY matrix of constraints (non-sparse), sizeY < n
+ An n-by-sizeY matrix of constraints (non-sparse), sizeY < n.
The iterations will be performed in the B-orthogonal complement
of the column-space of Y. Y must be full rank.
tol : scalar, optional
@@ -181,7 +181,7 @@ def lobpcg(
Returns
-------
w : ndarray
- Array of ``k`` eigenvalues
+ Array of ``k`` eigenvalues.
v : ndarray
An array of ``k`` eigenvectors. `v` has the same shape as `X`.
lambdas : list of ndarray, optional
@@ -240,7 +240,6 @@ def lobpcg(
Examples
--------
-
Solve ``A x = lambda x`` with constraints and preconditioning.
>>> import numpy as np
@@ -293,7 +292,6 @@ def lobpcg(
Note that the vectors passed in Y are the eigenvectors of the 3 smallest
eigenvalues. The results returned are orthogonal to those.
-
"""
blockVectorX = X
blockVectorY = Y
diff --git a/sklearn/feature_extraction/image.py b/sklearn/feature_extraction/image.py
index 031a597591afc..515a7990306b6 100644
--- a/sklearn/feature_extraction/image.py
+++ b/sklearn/feature_extraction/image.py
@@ -321,7 +321,7 @@ def _extract_patches(arr, patch_shape=8, extraction_step=1):
def extract_patches_2d(image, patch_size, *, max_patches=None, random_state=None):
- """Reshape a 2D image into a collection of patches
+ """Reshape a 2D image into a collection of patches.
The resulting patches are allocated in a dedicated array.
diff --git a/sklearn/feature_extraction/text.py b/sklearn/feature_extraction/text.py
index b565aeadc53c8..6f033d9b8b6d6 100644
--- a/sklearn/feature_extraction/text.py
+++ b/sklearn/feature_extraction/text.py
@@ -1519,6 +1519,7 @@ class TfidfTransformer(_OneToOneFeatureMixin, TransformerMixin, BaseEstimator):
been applied.
- 'l1': Sum of absolute values of vector elements is 1.
See :func:`preprocessing.normalize`.
+ - None: No normalization.
use_idf : bool, default=True
Enable inverse-document-frequency reweighting. If False, idf(t) = 1.
diff --git a/sklearn/feature_selection/_from_model.py b/sklearn/feature_selection/_from_model.py
index 0c41c66fbef1f..7c78902fcb172 100644
--- a/sklearn/feature_selection/_from_model.py
+++ b/sklearn/feature_selection/_from_model.py
@@ -22,9 +22,13 @@ def _calculate_threshold(estimator, importances, threshold):
if threshold is None:
# determine default from estimator
est_name = estimator.__class__.__name__
- if (
- hasattr(estimator, "penalty") and estimator.penalty == "l1"
- ) or "Lasso" in est_name:
+ is_l1_penalized = hasattr(estimator, "penalty") and estimator.penalty == "l1"
+ is_lasso = "Lasso" in est_name
+ is_elasticnet_l1_penalized = "ElasticNet" in est_name and (
+ (hasattr(estimator, "l1_ratio_") and np.isclose(estimator.l1_ratio_, 1.0))
+ or (hasattr(estimator, "l1_ratio") and np.isclose(estimator.l1_ratio, 1.0))
+ )
+ if is_l1_penalized or is_lasso or is_elasticnet_l1_penalized:
# the natural default threshold is 0 when l1 penalty was used
threshold = 1e-5
else:
@@ -93,9 +97,9 @@ class SelectFromModel(MetaEstimatorMixin, SelectorMixin, BaseEstimator):
threshold : str or float, default=None
The threshold value to use for feature selection. Features whose
- importance is greater or equal are kept while the others are
- discarded. If "median" (resp. "mean"), then the ``threshold`` value is
- the median (resp. the mean) of the feature importances. A scaling
+ absolute importance value is greater or equal are kept while the others
+ are discarded. If "median" (resp. "mean"), then the ``threshold`` value
+ is the median (resp. the mean) of the feature importances. A scaling
factor (e.g., "1.25*mean") may also be used. If None and if the
estimator has a parameter penalty set to l1, either explicitly
or implicitly (e.g, Lasso), the threshold used is 1e-5.
diff --git a/sklearn/feature_selection/_rfe.py b/sklearn/feature_selection/_rfe.py
index 0f82e1775ee15..877b3bed1558a 100644
--- a/sklearn/feature_selection/_rfe.py
+++ b/sklearn/feature_selection/_rfe.py
@@ -333,7 +333,7 @@ def _fit(self, X, y, step_score=None, **fit_params):
@available_if(_estimator_has("predict"))
def predict(self, X):
- """Reduce X to the selected features and then predict using the underlying estimator.
+ """Reduce X to the selected features and predict using the estimator.
Parameters
----------
@@ -350,7 +350,7 @@ def predict(self, X):
@available_if(_estimator_has("score"))
def score(self, X, y, **fit_params):
- """Reduce X to the selected features and return the score of the underlying estimator.
+ """Reduce X to the selected features and return the score of the estimator.
Parameters
----------
@@ -448,7 +448,7 @@ def _more_tags(self):
class RFECV(RFE):
- """Recursive feature elimination with cross-validation to select the number of features.
+ """Recursive feature elimination with cross-validation to select features.
See glossary entry for :term:`cross-validation estimator`.
@@ -550,13 +550,13 @@ class RFECV(RFE):
cv_results_ : dict of ndarrays
A dict with keys:
- split(k)_test_score : ndarray of shape (n_features,)
+ split(k)_test_score : ndarray of shape (n_subsets_of_features,)
The cross-validation scores across (k)th fold.
- mean_test_score : ndarray of shape (n_features,)
+ mean_test_score : ndarray of shape (n_subsets_of_features,)
Mean of scores over the folds.
- std_test_score : ndarray of shape (n_features,)
+ std_test_score : ndarray of shape (n_subsets_of_features,)
Standard deviation of scores over the folds.
.. versionadded:: 1.0
diff --git a/sklearn/feature_selection/_univariate_selection.py b/sklearn/feature_selection/_univariate_selection.py
index 7754ea3bea7f4..920eb2220e84a 100644
--- a/sklearn/feature_selection/_univariate_selection.py
+++ b/sklearn/feature_selection/_univariate_selection.py
@@ -39,7 +39,7 @@ def _clean_nans(scores):
# Contrary to the scipy.stats.f_oneway implementation it does not
# copy the data while keeping the inputs unchanged.
def f_oneway(*args):
- """Performs a 1-way ANOVA.
+ """Perform a 1-way ANOVA.
The one-way ANOVA tests the null hypothesis that 2 or more groups have
the same population mean. The test is applied to samples from two or
@@ -50,7 +50,7 @@ def f_oneway(*args):
Parameters
----------
*args : {array-like, sparse matrix}
- sample1, sample2... The sample measurements should be given as
+ Sample1, sample2... The sample measurements should be given as
arguments.
Returns
@@ -81,13 +81,11 @@ def f_oneway(*args):
References
----------
-
.. [1] Lowry, Richard. "Concepts and Applications of Inferential
Statistics". Chapter 14.
http://faculty.vassar.edu/lowry/ch14pt1.html
.. [2] Heiman, G.W. Research Methods in Statistics. 2002.
-
"""
n_classes = len(args)
args = [as_float_array(a) for a in args]
@@ -198,14 +196,14 @@ def chi2(X, y):
p_values : ndarray of shape (n_features,)
P-values for each feature.
- Notes
- -----
- Complexity of this algorithm is O(n_classes * n_features).
-
See Also
--------
f_classif : ANOVA F-value between label/feature for classification tasks.
f_regression : F-value between label/feature for regression tasks.
+
+ Notes
+ -----
+ Complexity of this algorithm is O(n_classes * n_features).
"""
# XXX: we might want to do some of the following in logspace instead for
diff --git a/sklearn/feature_selection/tests/test_from_model.py b/sklearn/feature_selection/tests/test_from_model.py
index de45d9e0ab6a4..d193df981e4a6 100644
--- a/sklearn/feature_selection/tests/test_from_model.py
+++ b/sklearn/feature_selection/tests/test_from_model.py
@@ -14,7 +14,14 @@
from sklearn.cross_decomposition import CCA, PLSCanonical, PLSRegression
from sklearn.datasets import make_friedman1
from sklearn.exceptions import NotFittedError
-from sklearn.linear_model import LogisticRegression, SGDClassifier, Lasso
+from sklearn.linear_model import (
+ LogisticRegression,
+ SGDClassifier,
+ Lasso,
+ LassoCV,
+ ElasticNet,
+ ElasticNetCV,
+)
from sklearn.svm import LinearSVC
from sklearn.feature_selection import SelectFromModel
from sklearn.ensemble import RandomForestClassifier, HistGradientBoostingClassifier
@@ -318,7 +325,16 @@ def test_sample_weight():
assert np.all(weighted_mask == reweighted_mask)
-def test_coef_default_threshold():
+@pytest.mark.parametrize(
+ "estimator",
+ [
+ Lasso(alpha=0.1, random_state=42),
+ LassoCV(random_state=42),
+ ElasticNet(l1_ratio=1, random_state=42),
+ ElasticNetCV(l1_ratio=[1], random_state=42),
+ ],
+)
+def test_coef_default_threshold(estimator):
X, y = datasets.make_classification(
n_samples=100,
n_features=10,
@@ -330,7 +346,7 @@ def test_coef_default_threshold():
)
# For the Lasso and related models, the threshold defaults to 1e-5
- transformer = SelectFromModel(estimator=Lasso(alpha=0.1, random_state=42))
+ transformer = SelectFromModel(estimator=estimator)
transformer.fit(X, y)
X_new = transformer.transform(X)
mask = np.abs(transformer.estimator_.coef_) > 1e-5
diff --git a/sklearn/gaussian_process/kernels.py b/sklearn/gaussian_process/kernels.py
index 4e36dfa7add42..3ac3866cf9ba7 100644
--- a/sklearn/gaussian_process/kernels.py
+++ b/sklearn/gaussian_process/kernels.py
@@ -1421,7 +1421,7 @@ def __repr__(self):
class RBF(StationaryKernelMixin, NormalizedKernelMixin, Kernel):
- """Radial-basis function kernel (aka squared-exponential kernel).
+ """Radial basis function kernel (aka squared-exponential kernel).
The RBF kernel is a stationary kernel. It is also known as the
"squared exponential" kernel. It is parameterized by a length scale
diff --git a/sklearn/impute/_base.py b/sklearn/impute/_base.py
index 0c8a6f2c07a21..73e20c2cd5101 100644
--- a/sklearn/impute/_base.py
+++ b/sklearn/impute/_base.py
@@ -9,9 +9,9 @@
import numpy as np
import numpy.ma as ma
from scipy import sparse as sp
-from scipy import stats
from ..base import BaseEstimator, TransformerMixin
+from ..utils.fixes import _mode
from ..utils.sparsefuncs import _get_median
from ..utils.validation import check_is_fitted
from ..utils.validation import FLOAT_DTYPES
@@ -51,7 +51,7 @@ def _most_frequent(array, extra_value, n_repeat):
if count == most_frequent_count
)
else:
- mode = stats.mode(array)
+ mode = _mode(array)
most_frequent_value = mode[0][0]
most_frequent_count = mode[1][0]
else:
@@ -130,7 +130,10 @@ def _more_tags(self):
class SimpleImputer(_BaseImputer):
- """Imputation transformer for completing missing values.
+ """Univariate imputer for completing missing values with simple strategies.
+
+ Replace missing values using a descriptive statistic (e.g. mean, median, or
+ most frequent) along each column, or using a constant value.
Read more in the :ref:`User Guide `.
@@ -218,13 +221,21 @@ class SimpleImputer(_BaseImputer):
See Also
--------
- IterativeImputer : Multivariate imputation of missing values.
+ IterativeImputer : Multivariate imputer that estimates values to impute for
+ each feature with missing values from all the others.
+ KNNImputer : Multivariate imputer that estimates missing features using
+ nearest samples.
Notes
-----
Columns which only contained missing values at :meth:`fit` are discarded
upon :meth:`transform` if strategy is not `"constant"`.
+ In a prediction context, simple imputation usually performs poorly when
+ associated with a weak learner. However, with a powerful learner, it can
+ lead to as good or better performance than complex imputation such as
+ :class:`~sklearn.impute.IterativeImputer` or :class:`~sklearn.impute.KNNImputer`.
+
Examples
--------
>>> import numpy as np
@@ -278,6 +289,10 @@ def _validate_input(self, X, in_fit):
else:
dtype = FLOAT_DTYPES
+ if not in_fit and self._fit_dtype.kind == "O":
+ # Use object dtype if fitted on object dtypes
+ dtype = self._fit_dtype
+
if _is_pandas_na(self.missing_values) or is_scalar_nan(self.missing_values):
force_all_finite = "allow-nan"
else:
@@ -303,6 +318,10 @@ def _validate_input(self, X, in_fit):
else:
raise ve
+ if in_fit:
+ # Use the dtype seen in `fit` for non-`fit` conversion
+ self._fit_dtype = X.dtype
+
_check_inputs_dtype(X, self.missing_values)
if X.dtype.kind not in ("i", "u", "f", "O"):
raise ValueError(
diff --git a/sklearn/impute/_iterative.py b/sklearn/impute/_iterative.py
index f6c32a6818455..12032c89d949d 100644
--- a/sklearn/impute/_iterative.py
+++ b/sklearn/impute/_iterative.py
@@ -183,7 +183,10 @@ class IterativeImputer(_BaseImputer):
See Also
--------
- SimpleImputer : Univariate imputation of missing values.
+ SimpleImputer : Univariate imputer for completing missing values
+ with simple strategies.
+ KNNImputer : Multivariate imputer that estimates missing features using
+ nearest samples.
Notes
-----
@@ -194,6 +197,16 @@ class IterativeImputer(_BaseImputer):
Features which contain all missing values at :meth:`fit` are discarded upon
:meth:`transform`.
+ Using defaults, the imputer scales in :math:`\\mathcal{O}(knp^3\\min(n,p))`
+ where :math:`k` = `max_iter`, :math:`n` the number of samples and
+ :math:`p` the number of features. It thus becomes prohibitively costly when
+ the number of features increases. Setting
+ `n_nearest_features << n_features`, `skip_complete=True` or increasing `tol`
+ can help to reduce its computational cost.
+
+ Depending on the nature of missing values, simple imputers can be
+ preferable in a prediction context.
+
References
----------
.. [1] `Stef van Buuren, Karin Groothuis-Oudshoorn (2011). "mice:
diff --git a/sklearn/impute/_knn.py b/sklearn/impute/_knn.py
index 497bcfafb074a..cac960e9a3436 100644
--- a/sklearn/impute/_knn.py
+++ b/sklearn/impute/_knn.py
@@ -90,10 +90,10 @@ class KNNImputer(_BaseImputer):
See Also
--------
- SimpleImputer : Imputation transformer for completing missing values
+ SimpleImputer : Univariate imputer for completing missing values
with simple strategies.
- IterativeImputer : Multivariate imputer that estimates each feature
- from all the others.
+ IterativeImputer : Multivariate imputer that estimates values to impute for
+ each feature with missing values from all the others.
References
----------
diff --git a/sklearn/impute/tests/test_impute.py b/sklearn/impute/tests/test_impute.py
index dc585571124b5..512558d28851d 100644
--- a/sklearn/impute/tests/test_impute.py
+++ b/sklearn/impute/tests/test_impute.py
@@ -1618,3 +1618,29 @@ def test_missing_indicator_feature_names_out():
feature_names = indicator.get_feature_names_out()
expected_names = ["missingindicator_a", "missingindicator_b", "missingindicator_d"]
assert_array_equal(expected_names, feature_names)
+
+
+def test_imputer_lists_fit_transform():
+ """Check transform uses object dtype when fitted on an object dtype.
+
+ Non-regression test for #19572.
+ """
+
+ X = [["a", "b"], ["c", "b"], ["a", "a"]]
+ imp_frequent = SimpleImputer(strategy="most_frequent").fit(X)
+ X_trans = imp_frequent.transform([[np.nan, np.nan]])
+ assert X_trans.dtype == object
+ assert_array_equal(X_trans, [["a", "b"]])
+
+
+@pytest.mark.parametrize("dtype_test", [np.float32, np.float64])
+def test_imputer_transform_preserves_numeric_dtype(dtype_test):
+ """Check transform preserves numeric dtype independent of fit dtype."""
+ X = np.asarray(
+ [[1.2, 3.4, np.nan], [np.nan, 1.2, 1.3], [4.2, 2, 1]], dtype=np.float64
+ )
+ imp = SimpleImputer().fit(X)
+
+ X_test = np.asarray([[np.nan, np.nan, np.nan]], dtype=dtype_test)
+ X_trans = imp.transform(X_test)
+ assert X_trans.dtype == dtype_test
diff --git a/sklearn/linear_model/_base.py b/sklearn/linear_model/_base.py
index 5e8417de21996..de747ef0850df 100644
--- a/sklearn/linear_model/_base.py
+++ b/sklearn/linear_model/_base.py
@@ -743,7 +743,7 @@ def rmatvec(b):
def _check_precomputed_gram_matrix(
- X, precompute, X_offset, X_scale, rtol=1e-7, atol=1e-5
+ X, precompute, X_offset, X_scale, rtol=None, atol=1e-5
):
"""Computes a single element of the gram matrix and compares it to
the corresponding element of the user supplied gram matrix.
@@ -764,8 +764,10 @@ def _check_precomputed_gram_matrix(
X_scale : ndarray of shape (n_features,)
Array of feature scale factors used to normalize design matrix.
- rtol : float, default=1e-7
- Relative tolerance; see numpy.allclose.
+ rtol : float, default=None
+ Relative tolerance; see numpy.allclose
+ If None, it is set to 1e-4 for arrays of dtype numpy.float32 and 1e-7
+ otherwise.
atol : float, default=1e-5
absolute tolerance; see :func`numpy.allclose`. Note that the default
@@ -788,6 +790,11 @@ def _check_precomputed_gram_matrix(
expected = np.dot(v1, v2)
actual = precompute[f1, f2]
+ dtypes = [precompute.dtype, expected.dtype]
+ if rtol is None:
+ rtols = [1e-4 if dtype == np.float32 else 1e-7 for dtype in dtypes]
+ rtol = max(rtols)
+
if not np.isclose(expected, actual, rtol=rtol, atol=atol):
raise ValueError(
"Gram matrix passed in via 'precompute' parameter "
diff --git a/sklearn/linear_model/_huber.py b/sklearn/linear_model/_huber.py
index 3fdf5aa73743f..afadc8c1efc2e 100644
--- a/sklearn/linear_model/_huber.py
+++ b/sklearn/linear_model/_huber.py
@@ -124,18 +124,19 @@ def _huber_loss_and_gradient(w, X, y, epsilon, alpha, sample_weight=None):
class HuberRegressor(LinearModel, RegressorMixin, BaseEstimator):
- """Linear regression model that is robust to outliers.
+ """L2-regularized linear regression model that is robust to outliers.
The Huber Regressor optimizes the squared loss for the samples where
- ``|(y - X'w) / sigma| < epsilon`` and the absolute loss for the samples
- where ``|(y - X'w) / sigma| > epsilon``, where w and sigma are parameters
+ ``|(y - Xw - c) / sigma| < epsilon`` and the absolute loss for the samples
+ where ``|(y - Xw - c) / sigma| > epsilon``, where the model coefficients
+ ``w``, the intercept ``c`` and the scale ``sigma`` are parameters
to be optimized. The parameter sigma makes sure that if y is scaled up
or down by a certain factor, one does not need to rescale epsilon to
achieve the same robustness. Note that this does not take into account
the fact that the different features of X may be of different scales.
- This makes sure that the loss function is not heavily influenced by the
- outliers while not completely ignoring their effect.
+ The Huber loss function has the advantage of not being heavily influenced
+ by the outliers while not completely ignoring their effect.
Read more in the :ref:`User Guide `
@@ -153,7 +154,9 @@ class HuberRegressor(LinearModel, RegressorMixin, BaseEstimator):
``scipy.optimize.minimize(method="L-BFGS-B")`` should run for.
alpha : float, default=0.0001
- Regularization parameter.
+ Strength of the squared L2 regularization. Note that the penalty is
+ equal to ``alpha * ||w||^2``.
+ Must be in the range `[0, inf)`.
warm_start : bool, default=False
This is useful if the stored attributes of a previously used model
@@ -173,13 +176,13 @@ class HuberRegressor(LinearModel, RegressorMixin, BaseEstimator):
Attributes
----------
coef_ : array, shape (n_features,)
- Features got by optimizing the Huber loss.
+ Features got by optimizing the L2-regularized Huber loss.
intercept_ : float
Bias.
scale_ : float
- The value by which ``|y - X'w - c|`` is scaled down.
+ The value by which ``|y - Xw - c|`` is scaled down.
n_features_in_ : int
Number of features seen during :term:`fit`.
diff --git a/sklearn/linear_model/_linear_loss.py b/sklearn/linear_model/_linear_loss.py
index 64a99325dcd7a..7623a7fb20838 100644
--- a/sklearn/linear_model/_linear_loss.py
+++ b/sklearn/linear_model/_linear_loss.py
@@ -327,6 +327,8 @@ def gradient_hessian_product(
# Calculate the double derivative with respect to intercept.
# Note: In case hX is sparse, hX.sum is a matrix object.
hX_sum = np.squeeze(np.asarray(hX.sum(axis=0)))
+ # prevent squeezing to zero-dim array if n_features == 1
+ hX_sum = np.atleast_1d(hX_sum)
# With intercept included and l2_reg_strength = 0, hessp returns
# res = (X, 1)' @ diag(h) @ (X, 1) @ s
diff --git a/sklearn/linear_model/_logistic.py b/sklearn/linear_model/_logistic.py
index 72b602e409801..93f6e31b12223 100644
--- a/sklearn/linear_model/_logistic.py
+++ b/sklearn/linear_model/_logistic.py
@@ -872,8 +872,8 @@ class LogisticRegression(LinearClassifierMixin, SparseCoefMixin, BaseEstimator):
.. seealso::
Refer to the User Guide for more information regarding
:class:`LogisticRegression` and more specifically the
- `Table `_
- summarazing solver/penalty supports.
+ :ref:`Table `
+ summarizing solver/penalty supports.
.. versionadded:: 0.17
Stochastic Average Gradient descent solver.
diff --git a/sklearn/linear_model/_omp.py b/sklearn/linear_model/_omp.py
index b86c35c41de85..c1e3762343628 100644
--- a/sklearn/linear_model/_omp.py
+++ b/sklearn/linear_model/_omp.py
@@ -361,7 +361,7 @@ def orthogonal_mp(
Orthogonal matching pursuit was introduced in S. Mallat, Z. Zhang,
Matching pursuits with time-frequency dictionaries, IEEE Transactions on
Signal Processing, Vol. 41, No. 12. (December 1993), pp. 3397-3415.
- (http://blanche.polytechnique.fr/~mallat/papiers/MallatPursuit93.pdf)
+ (https://www.di.ens.fr/~mallat/papiers/MallatPursuit93.pdf)
This implementation is based on Rubinstein, R., Zibulevsky, M. and Elad,
M., Efficient Implementation of the K-SVD Algorithm using Batch Orthogonal
@@ -515,7 +515,7 @@ def orthogonal_mp_gram(
Orthogonal matching pursuit was introduced in G. Mallat, Z. Zhang,
Matching pursuits with time-frequency dictionaries, IEEE Transactions on
Signal Processing, Vol. 41, No. 12. (December 1993), pp. 3397-3415.
- (http://blanche.polytechnique.fr/~mallat/papiers/MallatPursuit93.pdf)
+ (https://www.di.ens.fr/~mallat/papiers/MallatPursuit93.pdf)
This implementation is based on Rubinstein, R., Zibulevsky, M. and Elad,
M., Efficient Implementation of the K-SVD Algorithm using Batch Orthogonal
@@ -670,7 +670,7 @@ class OrthogonalMatchingPursuit(MultiOutputMixin, RegressorMixin, LinearModel):
Orthogonal matching pursuit was introduced in G. Mallat, Z. Zhang,
Matching pursuits with time-frequency dictionaries, IEEE Transactions on
Signal Processing, Vol. 41, No. 12. (December 1993), pp. 3397-3415.
- (http://blanche.polytechnique.fr/~mallat/papiers/MallatPursuit93.pdf)
+ (https://www.di.ens.fr/~mallat/papiers/MallatPursuit93.pdf)
This implementation is based on Rubinstein, R., Zibulevsky, M. and Elad,
M., Efficient Implementation of the K-SVD Algorithm using Batch Orthogonal
diff --git a/sklearn/linear_model/_ransac.py b/sklearn/linear_model/_ransac.py
index 8d20005430769..5ce0c7b1735df 100644
--- a/sklearn/linear_model/_ransac.py
+++ b/sklearn/linear_model/_ransac.py
@@ -228,7 +228,7 @@ class RANSACRegressor(
References
----------
.. [1] https://en.wikipedia.org/wiki/RANSAC
- .. [2] https://www.sri.com/sites/default/files/publications/ransac-publication.pdf
+ .. [2] https://www.sri.com/wp-content/uploads/2021/12/ransac-publication.pdf
.. [3] http://www.bmva.org/bmvc/2009/Papers/Paper355/Paper355.pdf
Examples
diff --git a/sklearn/linear_model/_ridge.py b/sklearn/linear_model/_ridge.py
index dee703b73c059..a3fc5cdc82baf 100644
--- a/sklearn/linear_model/_ridge.py
+++ b/sklearn/linear_model/_ridge.py
@@ -209,12 +209,12 @@ def _solve_cholesky(X, y, alpha):
if one_alpha:
A.flat[:: n_features + 1] += alpha[0]
- return linalg.solve(A, Xy, sym_pos=True, overwrite_a=True).T
+ return linalg.solve(A, Xy, assume_a="pos", overwrite_a=True).T
else:
coefs = np.empty([n_targets, n_features], dtype=X.dtype)
for coef, target, current_alpha in zip(coefs, Xy.T, alpha):
A.flat[:: n_features + 1] += current_alpha
- coef[:] = linalg.solve(A, target, sym_pos=True, overwrite_a=False).ravel()
+ coef[:] = linalg.solve(A, target, assume_a="pos", overwrite_a=False).ravel()
A.flat[:: n_features + 1] -= current_alpha
return coefs
@@ -246,7 +246,7 @@ def _solve_cholesky_kernel(K, y, alpha, sample_weight=None, copy=False):
# Note: we must use overwrite_a=False in order to be able to
# use the fall-back solution below in case a LinAlgError
# is raised
- dual_coef = linalg.solve(K, y, sym_pos=True, overwrite_a=False)
+ dual_coef = linalg.solve(K, y, assume_a="pos", overwrite_a=False)
except np.linalg.LinAlgError:
warnings.warn(
"Singular matrix in solving dual problem. Using "
@@ -270,7 +270,7 @@ def _solve_cholesky_kernel(K, y, alpha, sample_weight=None, copy=False):
K.flat[:: n_samples + 1] += current_alpha
dual_coef[:] = linalg.solve(
- K, target, sym_pos=True, overwrite_a=False
+ K, target, assume_a="pos", overwrite_a=False
).ravel()
K.flat[:: n_samples + 1] -= current_alpha
diff --git a/sklearn/linear_model/tests/test_coordinate_descent.py b/sklearn/linear_model/tests/test_coordinate_descent.py
index e5d7ba358c1f5..124da4f921e00 100644
--- a/sklearn/linear_model/tests/test_coordinate_descent.py
+++ b/sklearn/linear_model/tests/test_coordinate_descent.py
@@ -1065,6 +1065,29 @@ def test_elasticnet_precompute_gram_weighted_samples():
assert_allclose(clf1.coef_, clf2.coef_)
+def test_elasticnet_precompute_gram():
+ # Check the dtype-aware check for a precomputed Gram matrix
+ # (see https://github.com/scikit-learn/scikit-learn/pull/22059
+ # and https://github.com/scikit-learn/scikit-learn/issues/21997).
+ # Here: (X_c.T, X_c)[2, 3] is not equal to np.dot(X_c[:, 2], X_c[:, 3])
+ # but within tolerance for np.float32
+
+ rng = np.random.RandomState(58)
+ X = rng.binomial(1, 0.25, (1000, 4)).astype(np.float32)
+ y = rng.rand(1000).astype(np.float32)
+
+ X_c = X - np.average(X, axis=0)
+ gram = np.dot(X_c.T, X_c)
+
+ clf1 = ElasticNet(alpha=0.01, precompute=gram)
+ clf1.fit(X_c, y)
+
+ clf2 = ElasticNet(alpha=0.01, precompute=False)
+ clf2.fit(X, y)
+
+ assert_allclose(clf1.coef_, clf2.coef_)
+
+
def test_warm_start_convergence():
X, y, _, _ = build_dataset()
model = ElasticNet(alpha=1e-3, tol=1e-3).fit(X, y)
diff --git a/sklearn/linear_model/tests/test_logistic.py b/sklearn/linear_model/tests/test_logistic.py
index 5bb2b83094290..4c8c9daf78731 100644
--- a/sklearn/linear_model/tests/test_logistic.py
+++ b/sklearn/linear_model/tests/test_logistic.py
@@ -2071,3 +2071,13 @@ def test_large_sparse_matrix(solver):
LogisticRegression(solver=solver).fit(X, y)
else:
LogisticRegression(solver=solver).fit(X, y)
+
+
+def test_single_feature_newton_cg():
+ # Test that Newton-CG works with a single feature and intercept.
+ # Non-regression test for issue #23605.
+
+ X = np.array([[0.5, 0.65, 1.1, 1.25, 0.8, 0.54, 0.95, 0.7]]).T
+ y = np.array([1, 1, 0, 0, 1, 1, 0, 1])
+ assert X.shape[1] == 1
+ LogisticRegression(solver="newton-cg", fit_intercept=True).fit(X, y)
diff --git a/sklearn/manifold/_locally_linear.py b/sklearn/manifold/_locally_linear.py
index a9c6ec350b912..f5f64dad2c1a8 100644
--- a/sklearn/manifold/_locally_linear.py
+++ b/sklearn/manifold/_locally_linear.py
@@ -72,7 +72,7 @@ def barycenter_weights(X, Y, indices, reg=1e-3):
else:
R = reg
G.flat[:: n_neighbors + 1] += R
- w = solve(G, v, sym_pos=True)
+ w = solve(G, v, assume_a="pos")
B[i, :] = w / np.sum(w)
return B
diff --git a/sklearn/manifold/_mds.py b/sklearn/manifold/_mds.py
index 930f8d19b7b5e..a848b5d63c5a8 100644
--- a/sklearn/manifold/_mds.py
+++ b/sklearn/manifold/_mds.py
@@ -36,6 +36,8 @@ def _smacof_single(
metric : bool, default=True
Compute metric or nonmetric SMACOF algorithm.
+ When ``False`` (i.e. non-metric MDS), dissimilarities with 0 are considered as
+ missing values.
n_components : int, default=2
Number of dimensions in which to immerse the dissimilarities. If an
@@ -180,6 +182,8 @@ def smacof(
metric : bool, default=True
Compute metric or nonmetric SMACOF algorithm.
+ When ``False`` (i.e. non-metric MDS), dissimilarities with 0 are considered as
+ missing values.
n_components : int, default=2
Number of dimensions in which to immerse the dissimilarities. If an
@@ -318,6 +322,8 @@ class MDS(BaseEstimator):
metric : bool, default=True
If ``True``, perform metric MDS; otherwise, perform nonmetric MDS.
+ When ``False`` (i.e. non-metric MDS), dissimilarities with 0 are considered as
+ missing values.
n_init : int, default=4
Number of times the SMACOF algorithm will be run with different
@@ -491,7 +497,7 @@ def fit_transform(self, X, y=None, init=None):
y : Ignored
Not used, present for API consistency by convention.
- init : ndarray of shape (n_samples,), default=None
+ init : ndarray of shape (n_samples, n_components), default=None
Starting configuration of the embedding to initialize the SMACOF
algorithm. By default, the algorithm is initialized with a randomly
chosen array.
diff --git a/sklearn/manifold/_t_sne.py b/sklearn/manifold/_t_sne.py
index 5b7a3c4efd753..8e7b7f12cc59a 100644
--- a/sklearn/manifold/_t_sne.py
+++ b/sklearn/manifold/_t_sne.py
@@ -564,7 +564,8 @@ class TSNE(BaseEstimator):
is used in other manifold learning algorithms. Larger datasets
usually require a larger perplexity. Consider selecting a value
between 5 and 50. Different values can result in significantly
- different results.
+ different results. The perplexity must be less that the number
+ of samples.
early_exaggeration : float, default=12.0
Controls how tight natural clusters in the original space are in
@@ -739,7 +740,7 @@ class TSNE(BaseEstimator):
>>> from sklearn.manifold import TSNE
>>> X = np.array([[0, 0, 0], [0, 1, 1], [1, 0, 1], [1, 1, 1]])
>>> X_embedded = TSNE(n_components=2, learning_rate='auto',
- ... init='random').fit_transform(X)
+ ... init='random', perplexity=3).fit_transform(X)
>>> X_embedded.shape
(4, 2)
"""
@@ -787,6 +788,10 @@ def __init__(
self.n_jobs = n_jobs
self.square_distances = square_distances
+ def _check_params_vs_input(self, X):
+ if self.perplexity >= X.shape[0]:
+ raise ValueError("perplexity must be less than n_samples")
+
def _fit(self, X, skip_num_points=0):
"""Private function to fit the model using X as training data."""
@@ -1114,6 +1119,7 @@ def fit_transform(self, X, y=None):
X_new : ndarray of shape (n_samples, n_components)
Embedding of the training data in low-dimensional space.
"""
+ self._check_params_vs_input(X)
embedding = self._fit(X)
self.embedding_ = embedding
return self.embedding_
diff --git a/sklearn/manifold/tests/test_t_sne.py b/sklearn/manifold/tests/test_t_sne.py
index 861500e4a8891..997da9e542fda 100644
--- a/sklearn/manifold/tests/test_t_sne.py
+++ b/sklearn/manifold/tests/test_t_sne.py
@@ -389,7 +389,7 @@ def test_trustworthiness_not_euclidean_metric():
@pytest.mark.filterwarnings("ignore:The default initialization in TSNE")
def test_early_exaggeration_too_small():
# Early exaggeration factor must be >= 1.
- tsne = TSNE(early_exaggeration=0.99)
+ tsne = TSNE(early_exaggeration=0.99, perplexity=1)
with pytest.raises(ValueError, match="early_exaggeration .*"):
tsne.fit_transform(np.array([[0.0], [0.0]]))
@@ -398,7 +398,7 @@ def test_early_exaggeration_too_small():
@pytest.mark.filterwarnings("ignore:The default initialization in TSNE")
def test_too_few_iterations():
# Number of gradient descent iterations must be at least 200.
- tsne = TSNE(n_iter=199)
+ tsne = TSNE(n_iter=199, perplexity=1)
with pytest.raises(ValueError, match="n_iter .*"):
tsne.fit_transform(np.array([[0.0], [0.0]]))
@@ -425,6 +425,7 @@ def test_bad_precomputed_distances(method, D, retype, message_regex):
method=method,
init="random",
random_state=42,
+ perplexity=1,
)
with pytest.raises(ValueError, match=message_regex):
tsne.fit_transform(retype(D))
@@ -437,6 +438,7 @@ def test_exact_no_precomputed_sparse():
method="exact",
init="random",
random_state=42,
+ perplexity=1,
)
with pytest.raises(TypeError, match="sparse"):
tsne.fit_transform(sp.csr_matrix([[0, 5], [5, 0]]))
@@ -447,7 +449,7 @@ def test_high_perplexity_precomputed_sparse_distances():
# Perplexity should be less than 50
dist = np.array([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 0.0, 0.0]])
bad_dist = sp.csr_matrix(dist)
- tsne = TSNE(metric="precomputed", init="random", random_state=42)
+ tsne = TSNE(metric="precomputed", init="random", random_state=42, perplexity=1)
msg = "3 neighbors per samples are required, but some samples have only 1"
with pytest.raises(ValueError, match=msg):
tsne.fit_transform(bad_dist)
@@ -482,7 +484,7 @@ def metric(x, y):
return -1
# Negative computed distances should be caught even if result is squared
- tsne = TSNE(metric=metric, method="exact")
+ tsne = TSNE(metric=metric, method="exact", perplexity=1)
X = np.array([[0.0, 0.0], [1.0, 1.0]])
with pytest.raises(ValueError, match="All distances .*metric given.*"):
tsne.fit_transform(X)
@@ -491,7 +493,7 @@ def metric(x, y):
@pytest.mark.filterwarnings("ignore:The default learning rate in TSNE")
def test_init_not_available():
# 'init' must be 'pca', 'random', or numpy array.
- tsne = TSNE(init="not available")
+ tsne = TSNE(init="not available", perplexity=1)
m = "'init' must be 'pca', 'random', or a numpy array"
with pytest.raises(ValueError, match=m):
tsne.fit_transform(np.array([[0.0], [1.0]]))
@@ -519,11 +521,11 @@ def test_init_ndarray_precomputed():
@pytest.mark.filterwarnings("ignore:The default initialization in TSNE")
def test_distance_not_available():
# 'metric' must be valid.
- tsne = TSNE(metric="not available", method="exact")
+ tsne = TSNE(metric="not available", method="exact", perplexity=1)
with pytest.raises(ValueError, match="Unknown metric not available.*"):
tsne.fit_transform(np.array([[0.0], [1.0]]))
- tsne = TSNE(metric="not available", method="barnes_hut")
+ tsne = TSNE(metric="not available", method="barnes_hut", perplexity=1)
with pytest.raises(ValueError, match="Metric 'not available' not valid.*"):
tsne.fit_transform(np.array([[0.0], [1.0]]))
@@ -532,7 +534,7 @@ def test_distance_not_available():
@pytest.mark.filterwarnings("ignore:The default initialization in TSNE")
def test_method_not_available():
# 'nethod' must be 'barnes_hut' or 'exact'
- tsne = TSNE(method="not available")
+ tsne = TSNE(method="not available", perplexity=1)
with pytest.raises(ValueError, match="'method' must be 'barnes_hut' or "):
tsne.fit_transform(np.array([[0.0], [1.0]]))
@@ -542,7 +544,7 @@ def test_method_not_available():
def test_angle_out_of_range_checks():
# check the angle parameter range
for angle in [-1, -1e-6, 1 + 1e-6, 2]:
- tsne = TSNE(angle=angle)
+ tsne = TSNE(angle=angle, perplexity=1)
with pytest.raises(ValueError, match="'angle' must be between 0.0 - 1.0"):
tsne.fit_transform(np.array([[0.0], [1.0]]))
@@ -550,7 +552,7 @@ def test_angle_out_of_range_checks():
@pytest.mark.filterwarnings("ignore:The default learning rate in TSNE")
def test_pca_initialization_not_compatible_with_precomputed_kernel():
# Precomputed distance matrices cannot use PCA initialization.
- tsne = TSNE(metric="precomputed", init="pca")
+ tsne = TSNE(metric="precomputed", init="pca", perplexity=1)
with pytest.raises(
ValueError,
match='The parameter init="pca" cannot be used with metric="precomputed".',
@@ -560,7 +562,7 @@ def test_pca_initialization_not_compatible_with_precomputed_kernel():
def test_pca_initialization_not_compatible_with_sparse_input():
# Sparse input matrices cannot use PCA initialization.
- tsne = TSNE(init="pca", learning_rate=100.0)
+ tsne = TSNE(init="pca", learning_rate=100.0, perplexity=1)
with pytest.raises(TypeError, match="PCA initialization.*"):
tsne.fit_transform(sp.csr_matrix([[0, 5], [5, 0]]))
@@ -569,7 +571,7 @@ def test_pca_initialization_not_compatible_with_sparse_input():
@pytest.mark.filterwarnings("ignore:The default initialization in TSNE")
def test_n_components_range():
# barnes_hut method should only be used with n_components <= 3
- tsne = TSNE(n_components=4, method="barnes_hut")
+ tsne = TSNE(n_components=4, method="barnes_hut", perplexity=1)
with pytest.raises(ValueError, match="'n_components' should be .*"):
tsne.fit_transform(np.array([[0.0], [1.0]]))
@@ -736,7 +738,7 @@ def _run_answer_test(
def test_verbose():
# Verbose options write to stdout.
random_state = check_random_state(0)
- tsne = TSNE(verbose=2)
+ tsne = TSNE(verbose=2, perplexity=4)
X = random_state.randn(5, 2)
old_stdout = sys.stdout
@@ -760,7 +762,7 @@ def test_verbose():
def test_chebyshev_metric():
# t-SNE should allow metrics that cannot be squared (issue #3526).
random_state = check_random_state(0)
- tsne = TSNE(metric="chebyshev")
+ tsne = TSNE(metric="chebyshev", perplexity=4)
X = random_state.randn(5, 2)
tsne.fit_transform(X)
@@ -770,7 +772,7 @@ def test_chebyshev_metric():
def test_reduction_to_one_component():
# t-SNE should allow reduction to one component (issue #4154).
random_state = check_random_state(0)
- tsne = TSNE(n_components=1)
+ tsne = TSNE(n_components=1, perplexity=4)
X = random_state.randn(5, 2)
X_embedded = tsne.fit(X).embedding_
assert np.all(np.isfinite(X_embedded))
@@ -1042,7 +1044,7 @@ def test_bh_match_exact():
init="random",
random_state=0,
n_iter=251,
- perplexity=30.0,
+ perplexity=29.5,
angle=0,
)
# Kill the early_exaggeration
@@ -1141,7 +1143,7 @@ def test_tsne_init_futurewarning(init):
random_state = check_random_state(0)
X = random_state.randn(5, 2)
- kwargs = dict(learning_rate=200.0, init=init)
+ kwargs = dict(learning_rate=200.0, init=init, perplexity=4)
tsne = TSNE(**{k: v for k, v in kwargs.items() if v is not None})
if init is None:
@@ -1164,7 +1166,7 @@ def test_tsne_learning_rate_futurewarning(learning_rate):
random_state = check_random_state(0)
X = random_state.randn(5, 2)
- kwargs = dict(learning_rate=learning_rate, init="random")
+ kwargs = dict(learning_rate=learning_rate, init="random", perplexity=4)
tsne = TSNE(**{k: v for k, v in kwargs.items() if v is not None})
if learning_rate is None:
@@ -1182,7 +1184,7 @@ def test_tsne_negative_learning_rate():
random_state = check_random_state(0)
X = random_state.randn(5, 2)
with pytest.raises(ValueError, match="'learning_rate' must be.*"):
- TSNE(learning_rate=-50.0).fit_transform(X)
+ TSNE(learning_rate=-50.0, perplexity=4).fit_transform(X)
@pytest.mark.parametrize("method", ["exact", "barnes_hut"])
@@ -1194,7 +1196,7 @@ def test_tsne_n_jobs(method):
X_tr_ref = TSNE(
n_components=2,
method=method,
- perplexity=30.0,
+ perplexity=25.0,
angle=0,
n_jobs=1,
random_state=0,
@@ -1204,7 +1206,7 @@ def test_tsne_n_jobs(method):
X_tr = TSNE(
n_components=2,
method=method,
- perplexity=30.0,
+ perplexity=25.0,
angle=0,
n_jobs=2,
random_state=0,
@@ -1260,7 +1262,7 @@ def test_tsne_deprecation_square_distances():
n_components=2,
init="pca",
learning_rate="auto",
- perplexity=30.0,
+ perplexity=25.0,
angle=0,
n_jobs=1,
random_state=0,
@@ -1277,10 +1279,27 @@ def test_tsne_deprecation_square_distances():
n_components=2,
init="pca",
learning_rate="auto",
- perplexity=30.0,
+ perplexity=25.0,
angle=0,
n_jobs=1,
random_state=0,
)
X_trans_2 = tsne.fit_transform(X)
assert_allclose(X_trans_1, X_trans_2)
+
+
+@pytest.mark.parametrize("perplexity", (20, 30))
+def test_tsne_perplexity_validation(perplexity):
+ """Make sure that perplexity > n_samples results in a ValueError"""
+
+ random_state = check_random_state(0)
+ X = random_state.randn(20, 2)
+ est = TSNE(
+ learning_rate="auto",
+ init="pca",
+ perplexity=perplexity,
+ random_state=random_state,
+ )
+ msg = "perplexity must be less than n_samples"
+ with pytest.raises(ValueError, match=msg):
+ est.fit_transform(X)
diff --git a/sklearn/metrics/_classification.py b/sklearn/metrics/_classification.py
index b0a44e9d83d31..97f3abe2b884d 100644
--- a/sklearn/metrics/_classification.py
+++ b/sklearn/metrics/_classification.py
@@ -586,7 +586,7 @@ def multilabel_confusion_matrix(
def cohen_kappa_score(y1, y2, *, labels=None, weights=None, sample_weight=None):
- r"""Cohen's kappa: a statistic that measures inter-annotator agreement.
+ r"""Compute Cohen's kappa: a statistic that measures inter-annotator agreement.
This function computes Cohen's kappa [1]_, a score that expresses the level
of agreement between two annotators on a classification problem. It is
@@ -738,12 +738,16 @@ def jaccard_score(
Returns
-------
- score : float (if average is not None) or array of floats, shape =\
- [n_unique_labels]
+ score : float or ndarray of shape (n_unique_labels,), dtype=np.float64
+ The Jaccard score. When `average` is not `None`, a single scalar is
+ returned.
See Also
--------
- accuracy_score, f1_score, multilabel_confusion_matrix
+ accuracy_score : Function for calculating the accuracy score.
+ f1_score : Function for calculating the F1 score.
+ multilabel_confusion_matrix : Function for computing a confusion matrix\
+ for each class or sample.
Notes
-----
@@ -1235,7 +1239,10 @@ def fbeta_score(
See Also
--------
- precision_recall_fscore_support, multilabel_confusion_matrix
+ precision_recall_fscore_support : Compute the precision, recall, F-score,
+ and support.
+ multilabel_confusion_matrix : Compute a confusion matrix for each class or
+ sample.
Notes
-----
@@ -2374,22 +2381,23 @@ def log_loss(
Returns
-------
loss : float
+ Log loss, aka logistic loss or cross-entropy loss.
Notes
-----
The logarithm used is the natural logarithm (base-e).
+ References
+ ----------
+ C.M. Bishop (2006). Pattern Recognition and Machine Learning. Springer,
+ p. 209.
+
Examples
--------
>>> from sklearn.metrics import log_loss
>>> log_loss(["spam", "ham", "ham", "spam"],
... [[.1, .9], [.9, .1], [.8, .2], [.35, .65]])
0.21616...
-
- References
- ----------
- C.M. Bishop (2006). Pattern Recognition and Machine Learning. Springer,
- p. 209.
"""
y_pred = check_array(y_pred, ensure_2d=False)
check_consistent_length(y_pred, y_true, sample_weight)
@@ -2508,8 +2516,7 @@ def hinge_loss(y_true, pred_decision, *, labels=None, sample_weight=None):
.. [3] `L1 AND L2 Regularization for Multiclass Hinge Loss Models
by Robert C. Moore, John DeNero
- `_.
+ `_.
Examples
--------
@@ -2653,6 +2660,11 @@ def brier_score_loss(y_true, y_prob, *, sample_weight=None, pos_label=None):
score : float
Brier score loss.
+ References
+ ----------
+ .. [1] `Wikipedia entry for the Brier score
+ `_.
+
Examples
--------
>>> import numpy as np
@@ -2668,11 +2680,6 @@ def brier_score_loss(y_true, y_prob, *, sample_weight=None, pos_label=None):
0.037...
>>> brier_score_loss(y_true, np.array(y_prob) > 0.5)
0.0
-
- References
- ----------
- .. [1] `Wikipedia entry for the Brier score
- `_.
"""
y_true = column_or_1d(y_true)
y_prob = column_or_1d(y_prob)
diff --git a/sklearn/metrics/_dist_metrics.pyx b/sklearn/metrics/_dist_metrics.pyx
index d17be2c8cb73d..953e6378adcb2 100644
--- a/sklearn/metrics/_dist_metrics.pyx
+++ b/sklearn/metrics/_dist_metrics.pyx
@@ -1097,72 +1097,6 @@ cdef class HaversineDistance(DistanceMetric):
tmp = np.sin(0.5 * dist)
return tmp * tmp
-
-#------------------------------------------------------------
-# Yule Distance (boolean)
-# D(x, y) = 2 * ntf * nft / (ntt * nff + ntf * nft)
-# [This is not a true metric, so we will leave it out.]
-#
-#cdef class YuleDistance(DistanceMetric):
-# cdef inline DTYPE_t dist(self, const DTYPE_t* x1, const DTYPE_t* x2,
-# ITYPE_t size):
-# cdef int tf1, tf2, ntf = 0, nft = 0, ntt = 0, nff = 0
-# cdef np.intp_t j
-# for j in range(size):
-# tf1 = x1[j] != 0
-# tf2 = x2[j] != 0
-# ntt += tf1 and tf2
-# ntf += tf1 and (tf2 == 0)
-# nft += (tf1 == 0) and tf2
-# nff = size - ntt - ntf - nft
-# return (2.0 * ntf * nft) / (ntt * nff + ntf * nft)
-
-
-#------------------------------------------------------------
-# Cosine Distance
-# D(x, y) = dot(x, y) / (|x| * |y|)
-# [This is not a true metric, so we will leave it out.]
-#
-#cdef class CosineDistance(DistanceMetric):
-# cdef inline DTYPE_t dist(self, const DTYPE_t* x1, const DTYPE_t* x2,
-# ITYPE_t size):
-# cdef DTYPE_t d = 0, norm1 = 0, norm2 = 0
-# cdef np.intp_t j
-# for j in range(size):
-# d += x1[j] * x2[j]
-# norm1 += x1[j] * x1[j]
-# norm2 += x2[j] * x2[j]
-# return 1.0 - d / sqrt(norm1 * norm2)
-
-
-#------------------------------------------------------------
-# Correlation Distance
-# D(x, y) = dot((x - mx), (y - my)) / (|x - mx| * |y - my|)
-# [This is not a true metric, so we will leave it out.]
-#
-#cdef class CorrelationDistance(DistanceMetric):
-# cdef inline DTYPE_t dist(self, const DTYPE_t* x1, const DTYPE_t* x2,
-# ITYPE_t size):
-# cdef DTYPE_t mu1 = 0, mu2 = 0, x1nrm = 0, x2nrm = 0, x1Tx2 = 0
-# cdef DTYPE_t tmp1, tmp2
-#
-# cdef np.intp_t i
-# for i in range(size):
-# mu1 += x1[i]
-# mu2 += x2[i]
-# mu1 /= size
-# mu2 /= size
-#
-# for i in range(size):
-# tmp1 = x1[i] - mu1
-# tmp2 = x2[i] - mu2
-# x1nrm += tmp1 * tmp1
-# x2nrm += tmp2 * tmp2
-# x1Tx2 += tmp1 * tmp2
-#
-# return (1. - x1Tx2) / sqrt(x1nrm * x2nrm)
-
-
#------------------------------------------------------------
# User-defined distance
#
diff --git a/sklearn/metrics/_pairwise_distances_reduction.pyx b/sklearn/metrics/_pairwise_distances_reduction.pyx
index a37bdb7ab0e8d..ff0d9947dfab8 100644
--- a/sklearn/metrics/_pairwise_distances_reduction.pyx
+++ b/sklearn/metrics/_pairwise_distances_reduction.pyx
@@ -245,9 +245,16 @@ cdef class PairwiseDistancesReduction:
True if the PairwiseDistancesReduction can be used, else False.
"""
# TODO: support sparse arrays and 32 bits
+ c_contiguity = (
+ hasattr(X, "flags")
+ and X.flags.c_contiguous
+ and hasattr(Y, "flags")
+ and Y.flags.c_contiguous
+ )
return (get_config().get("enable_cython_pairwise_dist", True) and
not issparse(X) and X.dtype == np.float64 and
not issparse(Y) and Y.dtype == np.float64 and
+ c_contiguity and
metric in cls.valid_metrics())
def __init__(
diff --git a/sklearn/metrics/_plot/roc_curve.py b/sklearn/metrics/_plot/roc_curve.py
index a56cd3755b8d6..49953d7032c71 100644
--- a/sklearn/metrics/_plot/roc_curve.py
+++ b/sklearn/metrics/_plot/roc_curve.py
@@ -352,8 +352,8 @@ def from_predictions(
@deprecated(
"Function :func:`plot_roc_curve` is deprecated in 1.0 and will be "
"removed in 1.2. Use one of the class methods: "
- ":meth:`sklearn.metric.RocCurveDisplay.from_predictions` or "
- ":meth:`sklearn.metric.RocCurveDisplay.from_estimator`."
+ ":meth:`sklearn.metrics.RocCurveDisplay.from_predictions` or "
+ ":meth:`sklearn.metrics.RocCurveDisplay.from_estimator`."
)
def plot_roc_curve(
estimator,
diff --git a/sklearn/metrics/_ranking.py b/sklearn/metrics/_ranking.py
index 4e88bd5edc888..81e201fe5a349 100644
--- a/sklearn/metrics/_ranking.py
+++ b/sklearn/metrics/_ranking.py
@@ -55,14 +55,15 @@ def auc(x, y):
Parameters
----------
x : ndarray of shape (n,)
- x coordinates. These must be either monotonic increasing or monotonic
+ X coordinates. These must be either monotonic increasing or monotonic
decreasing.
y : ndarray of shape, (n,)
- y coordinates.
+ Y coordinates.
Returns
-------
auc : float
+ Area Under the Curve.
See Also
--------
@@ -371,7 +372,7 @@ def roc_auc_score(
multi_class="raise",
labels=None,
):
- """Compute Area Under the Receiver Operating Characteristic Curve (ROC AUC)
+ """Compute Area Under the Receiver Operating Characteristic Curve (ROC AUC) \
from prediction scores.
Note: this implementation can be used with binary, multiclass and
@@ -470,6 +471,16 @@ class scores must correspond to the order of ``labels``,
Returns
-------
auc : float
+ Area Under the Curve score.
+
+ See Also
+ --------
+ average_precision_score : Area under the precision-recall curve.
+ roc_curve : Compute Receiver operating characteristic (ROC) curve.
+ RocCurveDisplay.from_estimator : Plot Receiver Operating Characteristic
+ (ROC) curve given an estimator and some data.
+ RocCurveDisplay.from_predictions : Plot Receiver Operating Characteristic
+ (ROC) curve given the true and predicted values.
References
----------
@@ -492,15 +503,6 @@ class scores must correspond to the order of ``labels``,
Machine Learning, 45(2), 171-186.
`_
- See Also
- --------
- average_precision_score : Area under the precision-recall curve.
- roc_curve : Compute Receiver operating characteristic (ROC) curve.
- RocCurveDisplay.from_estimator : Plot Receiver Operating Characteristic
- (ROC) curve given an estimator and some data.
- RocCurveDisplay.from_predictions : Plot Receiver Operating Characteristic
- (ROC) curve given the true and predicted values.
-
Examples
--------
Binary case:
@@ -1057,6 +1059,7 @@ def label_ranking_average_precision_score(y_true, y_score, *, sample_weight=None
Returns
-------
score : float
+ Ranking-based average precision score.
Examples
--------
@@ -1066,7 +1069,6 @@ def label_ranking_average_precision_score(y_true, y_score, *, sample_weight=None
>>> y_score = np.array([[0.75, 0.5, 1], [1, 0.2, 0.1]])
>>> label_ranking_average_precision_score(y_true, y_score)
0.416...
-
"""
check_consistent_length(y_true, y_score, sample_weight)
y_true = check_array(y_true, ensure_2d=False)
@@ -1153,8 +1155,8 @@ def coverage_error(y_true, y_score, *, sample_weight=None):
handbook (pp. 667-685). Springer US.
"""
- y_true = check_array(y_true, ensure_2d=False)
- y_score = check_array(y_score, ensure_2d=False)
+ y_true = check_array(y_true, ensure_2d=True)
+ y_score = check_array(y_score, ensure_2d=True)
check_consistent_length(y_true, y_score, sample_weight)
y_type = type_of_target(y_true, input_name="y_true")
@@ -1617,19 +1619,19 @@ def ndcg_score(y_true, y_score, *, k=None, sample_weight=None, ignore_ties=False
>>> # the normalization takes k into account so a perfect answer
>>> # would still get 1.0
>>> ndcg_score(true_relevance, true_relevance, k=4)
- 1.0
+ 1.0...
>>> # now we have some ties in our prediction
>>> scores = np.asarray([[1, 0, 0, 0, 1]])
>>> # by default ties are averaged, so here we get the average (normalized)
>>> # true relevance of our top predictions: (10 / 10 + 5 / 10) / 2 = .75
>>> ndcg_score(true_relevance, scores, k=1)
- 0.75
+ 0.75...
>>> # we can choose to ignore ties for faster results, but only
>>> # if we know there aren't ties in our scores, otherwise we get
>>> # wrong results:
>>> ndcg_score(true_relevance,
... scores, k=1, ignore_ties=True)
- 0.5
+ 0.5...
"""
y_true = check_array(y_true, ensure_2d=False)
y_score = check_array(y_score, ensure_2d=False)
diff --git a/sklearn/metrics/_regression.py b/sklearn/metrics/_regression.py
index 57986692fb896..9d4315470bc97 100644
--- a/sklearn/metrics/_regression.py
+++ b/sklearn/metrics/_regression.py
@@ -1223,7 +1223,7 @@ def d2_tweedie_score(y_true, y_pred, *, sample_weight=None, power=0):
----------
.. [1] Eq. (3.11) of Hastie, Trevor J., Robert Tibshirani and Martin J.
Wainwright. "Statistical Learning with Sparsity: The Lasso and
- Generalizations." (2015). https://trevorhastie.github.io
+ Generalizations." (2015). https://hastie.su.domains/StatLearnSparsity/
Examples
--------
@@ -1326,7 +1326,7 @@ def d2_pinball_score(
`_
.. [2] Eq. (3.11) of Hastie, Trevor J., Robert Tibshirani and Martin J.
Wainwright. "Statistical Learning with Sparsity: The Lasso and
- Generalizations." (2015). https://trevorhastie.github.io
+ Generalizations." (2015). https://hastie.su.domains/StatLearnSparsity/
Examples
--------
@@ -1464,7 +1464,7 @@ def d2_absolute_error_score(
----------
.. [1] Eq. (3.11) of Hastie, Trevor J., Robert Tibshirani and Martin J.
Wainwright. "Statistical Learning with Sparsity: The Lasso and
- Generalizations." (2015). https://trevorhastie.github.io
+ Generalizations." (2015). https://hastie.su.domains/StatLearnSparsity/
Examples
--------
diff --git a/sklearn/metrics/cluster/_supervised.py b/sklearn/metrics/cluster/_supervised.py
index a6a66884b70b2..3add417716089 100644
--- a/sklearn/metrics/cluster/_supervised.py
+++ b/sklearn/metrics/cluster/_supervised.py
@@ -159,7 +159,7 @@ def contingency_matrix(
def pair_confusion_matrix(labels_true, labels_pred):
- """Pair confusion matrix arising from two clusterings.
+ """Pair confusion matrix arising from two clusterings [1]_.
The pair confusion matrix :math:`C` computes a 2 by 2 similarity matrix
between two clusterings by considering all pairs of samples and counting
@@ -188,9 +188,15 @@ def pair_confusion_matrix(labels_true, labels_pred):
See Also
--------
- rand_score: Rand Score
- adjusted_rand_score: Adjusted Rand Score
- adjusted_mutual_info_score: Adjusted Mutual Information
+ rand_score: Rand Score.
+ adjusted_rand_score: Adjusted Rand Score.
+ adjusted_mutual_info_score: Adjusted Mutual Information.
+
+ References
+ ----------
+ .. [1] :doi:`Hubert, L., Arabie, P. "Comparing partitions."
+ Journal of Classification 2, 193–218 (1985).
+ <10.1007/BF01908075>`
Examples
--------
@@ -211,12 +217,6 @@ def pair_confusion_matrix(labels_true, labels_pred):
[0, 2]]...
Note that the matrix is not symmetric.
-
- References
- ----------
- .. L. Hubert and P. Arabie, Comparing Partitions, Journal of
- Classification 1985
- https://link.springer.com/article/10.1007%2FBF01908075
"""
labels_true, labels_pred = check_clusterings(labels_true, labels_pred)
n_samples = np.int64(labels_true.shape[0])
@@ -429,10 +429,10 @@ def homogeneity_completeness_v_measure(labels_true, labels_pred, *, beta=1.0):
Parameters
----------
labels_true : int array, shape = [n_samples]
- ground truth class labels to be used as a reference
+ Ground truth class labels to be used as a reference.
labels_pred : array-like of shape (n_samples,)
- cluster labels to evaluate
+ Gluster labels to evaluate.
beta : float, default=1.0
Ratio of weight attributed to ``homogeneity`` vs ``completeness``.
@@ -443,19 +443,19 @@ def homogeneity_completeness_v_measure(labels_true, labels_pred, *, beta=1.0):
Returns
-------
homogeneity : float
- score between 0.0 and 1.0. 1.0 stands for perfectly homogeneous labeling
+ Score between 0.0 and 1.0. 1.0 stands for perfectly homogeneous labeling.
completeness : float
- score between 0.0 and 1.0. 1.0 stands for perfectly complete labeling
+ Score between 0.0 and 1.0. 1.0 stands for perfectly complete labeling.
v_measure : float
- harmonic mean of the first two
+ Harmonic mean of the first two.
See Also
--------
- homogeneity_score
- completeness_score
- v_measure_score
+ homogeneity_score : Homogeneity metric of cluster labeling.
+ completeness_score : Completeness metric of cluster labeling.
+ v_measure_score : V-Measure (NMI with arithmetic mean option).
"""
labels_true, labels_pred = check_clusterings(labels_true, labels_pred)
@@ -689,7 +689,7 @@ def v_measure_score(labels_true, labels_pred, *, beta=1.0):
1.0
Labelings that assign all classes members to the same clusters
- are complete be not homogeneous, hence penalized::
+ are complete but not homogeneous, hence penalized::
>>> print("%.6f" % v_measure_score([0, 0, 1, 2], [0, 0, 1, 1]))
0.8...
@@ -697,7 +697,7 @@ def v_measure_score(labels_true, labels_pred, *, beta=1.0):
0.66...
Labelings that have pure clusters with members coming from the same
- classes are homogeneous but un-necessary splits harms completeness
+ classes are homogeneous but un-necessary splits harm completeness
and thus penalize V-measure as well::
>>> print("%.6f" % v_measure_score([0, 0, 1, 1], [0, 0, 1, 2]))
@@ -768,14 +768,14 @@ def mutual_info_score(labels_true, labels_pred, *, contingency=None):
Mutual information, a non-negative value, measured in nats using the
natural logarithm.
- Notes
- -----
- The logarithm used is the natural logarithm (base-e).
-
See Also
--------
adjusted_mutual_info_score : Adjusted against chance Mutual Information.
normalized_mutual_info_score : Normalized Mutual Information.
+
+ Notes
+ -----
+ The logarithm used is the natural logarithm (base-e).
"""
if contingency is None:
labels_true, labels_pred = check_clusterings(labels_true, labels_pred)
diff --git a/sklearn/metrics/pairwise.py b/sklearn/metrics/pairwise.py
index 33b2a9901902b..bcf01bc2925a3 100644
--- a/sklearn/metrics/pairwise.py
+++ b/sklearn/metrics/pairwise.py
@@ -135,7 +135,6 @@ def check_pairwise_arrays(
safe_Y : {array-like, sparse matrix} of shape (n_samples_Y, n_features)
An array equal to Y if Y was not None, guaranteed to be a numpy array.
If Y was None, safe_Y will be a pointer to X.
-
"""
X, Y, dtype_float = _return_float_dtype(X, Y)
@@ -211,7 +210,6 @@ def check_paired_arrays(X, Y):
safe_Y : {array-like, sparse matrix} of shape (n_samples_Y, n_features)
An array equal to Y if Y was not None, guaranteed to be a numpy array.
If Y was None, safe_Y will be a pointer to X.
-
"""
X, Y = check_pairwise_arrays(X, Y)
if X.shape != Y.shape:
@@ -654,13 +652,14 @@ def pairwise_distances_argmin_min(
Y[argmin[i], :] is the row in Y that is closest to X[i, :].
distances : ndarray
- distances[i] is the distance between the i-th row in X and the
- argmin[i]-th row in Y.
+ The array of minimum distances. `distances[i]` is the distance between
+ the i-th row in X and the argmin[i]-th row in Y.
See Also
--------
- sklearn.metrics.pairwise_distances
- sklearn.metrics.pairwise_distances_argmin
+ pairwise_distances : Distances between every pair of samples of X and Y.
+ pairwise_distances_argmin : Same as `pairwise_distances_argmin_min` but only
+ returns the argmins.
"""
X, Y = check_pairwise_arrays(X, Y)
@@ -767,8 +766,9 @@ def pairwise_distances_argmin(X, Y, *, axis=1, metric="euclidean", metric_kwargs
See Also
--------
- sklearn.metrics.pairwise_distances
- sklearn.metrics.pairwise_distances_argmin_min
+ pairwise_distances : Distances between every pair of samples of X and Y.
+ pairwise_distances_argmin_min : Same as `pairwise_distances_argmin` but also
+ returns the distances.
"""
if metric_kwargs is None:
metric_kwargs = {}
@@ -973,10 +973,11 @@ def cosine_distances(X, Y=None):
Returns
-------
distance matrix : ndarray of shape (n_samples_X, n_samples_Y)
+ Returns the cosine distance between samples in X and Y.
See Also
--------
- cosine_similarity
+ cosine_similarity : Compute cosine similarity between samples in X and Y.
scipy.spatial.distance.cosine : Dense matrices only.
"""
# 1.0 - cosine_similarity(X, Y) without copy
@@ -1016,19 +1017,35 @@ def paired_euclidean_distances(X, Y):
def paired_manhattan_distances(X, Y):
- """Compute the L1 distances between the vectors in X and Y.
+ """Compute the paired L1 distances between X and Y.
+
+ Distances are calculated between (X[0], Y[0]), (X[1], Y[1]), ...,
+ (X[n_samples], Y[n_samples]).
Read more in the :ref:`User Guide `.
Parameters
----------
X : array-like of shape (n_samples, n_features)
+ An array-like where each row is a sample and each column is a feature.
Y : array-like of shape (n_samples, n_features)
+ An array-like where each row is a sample and each column is a feature.
Returns
-------
distances : ndarray of shape (n_samples,)
+ L1 paired distances between the row vectors of `X`
+ and the row vectors of `Y`.
+
+ Examples
+ --------
+ >>> from sklearn.metrics.pairwise import paired_manhattan_distances
+ >>> import numpy as np
+ >>> X = np.array([[1, 1, 0], [0, 1, 0], [0, 0, 1]])
+ >>> Y = np.array([[0, 1, 0], [0, 0, 1], [0, 0, 0]])
+ >>> paired_manhattan_distances(X, Y)
+ array([1., 2., 1.])
"""
X, Y = check_paired_arrays(X, Y)
diff = X - Y
@@ -1173,28 +1190,33 @@ def linear_kernel(X, Y=None, dense_output=True):
def polynomial_kernel(X, Y=None, degree=3, gamma=None, coef0=1):
"""
- Compute the polynomial kernel between X and Y::
+ Compute the polynomial kernel between X and Y.
- K(X, Y) = (gamma + coef0)^degree
+ :math:`K(X, Y) = (gamma + coef0)^degree`
Read more in the :ref:`User Guide `.
Parameters
----------
X : ndarray of shape (n_samples_X, n_features)
+ A feature array.
Y : ndarray of shape (n_samples_Y, n_features), default=None
+ An optional second feature array. If `None`, uses `Y=X`.
degree : int, default=3
+ Kernel degree.
gamma : float, default=None
- If None, defaults to 1.0 / n_features.
+ Coefficient of the vector inner product. If None, defaults to 1.0 / n_features.
coef0 : float, default=1
+ Constant offset added to scaled inner product.
Returns
-------
Gram matrix : ndarray of shape (n_samples_X, n_samples_Y)
+ The polynomial kernel.
"""
X, Y = check_pairwise_arrays(X, Y)
if gamma is None:
@@ -1208,8 +1230,7 @@ def polynomial_kernel(X, Y=None, degree=3, gamma=None, coef0=1):
def sigmoid_kernel(X, Y=None, gamma=None, coef0=1):
- """
- Compute the sigmoid kernel between X and Y::
+ """Compute the sigmoid kernel between X and Y.
K(X, Y) = tanh(gamma + coef0)
@@ -1218,18 +1239,21 @@ def sigmoid_kernel(X, Y=None, gamma=None, coef0=1):
Parameters
----------
X : ndarray of shape (n_samples_X, n_features)
+ A feature array.
Y : ndarray of shape (n_samples_Y, n_features), default=None
- If `None`, uses `Y=X`.
+ An optional second feature array. If `None`, uses `Y=X`.
gamma : float, default=None
- If None, defaults to 1.0 / n_features.
+ Coefficient of the vector inner product. If None, defaults to 1.0 / n_features.
coef0 : float, default=1
+ Constant offset added to scaled inner product.
Returns
-------
Gram matrix : ndarray of shape (n_samples_X, n_samples_Y)
+ Sigmoid kernel between two arrays.
"""
X, Y = check_pairwise_arrays(X, Y)
if gamma is None:
@@ -1243,8 +1267,7 @@ def sigmoid_kernel(X, Y=None, gamma=None, coef0=1):
def rbf_kernel(X, Y=None, gamma=None):
- """
- Compute the rbf (gaussian) kernel between X and Y::
+ """Compute the rbf (gaussian) kernel between X and Y.
K(x, y) = exp(-gamma ||x-y||^2)
@@ -1255,9 +1278,10 @@ def rbf_kernel(X, Y=None, gamma=None):
Parameters
----------
X : ndarray of shape (n_samples_X, n_features)
+ A feature array.
Y : ndarray of shape (n_samples_Y, n_features), default=None
- If `None`, uses `Y=X`.
+ An optional second feature array. If `None`, uses `Y=X`.
gamma : float, default=None
If None, defaults to 1.0 / n_features.
@@ -1265,6 +1289,7 @@ def rbf_kernel(X, Y=None, gamma=None):
Returns
-------
kernel_matrix : ndarray of shape (n_samples_X, n_samples_Y)
+ The RBF kernel.
"""
X, Y = check_pairwise_arrays(X, Y)
if gamma is None:
@@ -1345,6 +1370,7 @@ def cosine_similarity(X, Y=None, dense_output=True):
Returns
-------
kernel matrix : ndarray of shape (n_samples_X, n_samples_Y)
+ Returns the cosine similarity between samples in X and Y.
"""
# to avoid recursive import
@@ -1362,8 +1388,7 @@ def cosine_similarity(X, Y=None, dense_output=True):
def additive_chi2_kernel(X, Y=None):
- """Computes the additive chi-squared kernel between observations in X and
- Y.
+ """Compute the additive chi-squared kernel between observations in X and Y.
The chi-squared kernel is computed between each pair of rows in X and Y. X
and Y have to be non-negative. This kernel is most commonly applied to
@@ -1377,22 +1402,18 @@ def additive_chi2_kernel(X, Y=None):
Read more in the :ref:`User Guide `.
- Notes
- -----
- As the negative of a distance, this kernel is only conditionally positive
- definite.
-
-
Parameters
----------
X : array-like of shape (n_samples_X, n_features)
+ A feature array.
Y : ndarray of shape (n_samples_Y, n_features), default=None
- If `None`, uses `Y=X`.
+ An optional second feature array. If `None`, uses `Y=X`.
Returns
-------
kernel_matrix : ndarray of shape (n_samples_X, n_samples_Y)
+ The kernel matrix.
See Also
--------
@@ -1401,13 +1422,18 @@ def additive_chi2_kernel(X, Y=None):
sklearn.kernel_approximation.AdditiveChi2Sampler : A Fourier approximation
to this kernel.
+ Notes
+ -----
+ As the negative of a distance, this kernel is only conditionally positive
+ definite.
+
References
----------
* Zhang, J. and Marszalek, M. and Lazebnik, S. and Schmid, C.
Local features and kernels for classification of texture and object
categories: A comprehensive study
International Journal of Computer Vision 2007
- https://research.microsoft.com/en-us/um/people/manik/projects/trade-off/papers/ZhangIJCV06.pdf
+ https://hal.archives-ouvertes.fr/hal-00171412/document
"""
if issparse(X) or issparse(Y):
raise ValueError("additive_chi2 does not support sparse matrices.")
@@ -1423,7 +1449,7 @@ def additive_chi2_kernel(X, Y=None):
def chi2_kernel(X, Y=None, gamma=1.0):
- """Computes the exponential chi-squared kernel X and Y.
+ """Compute the exponential chi-squared kernel between X and Y.
The chi-squared kernel is computed between each pair of rows in X and Y. X
and Y have to be non-negative. This kernel is most commonly applied to
@@ -1440,15 +1466,18 @@ def chi2_kernel(X, Y=None, gamma=1.0):
Parameters
----------
X : array-like of shape (n_samples_X, n_features)
+ A feature array.
Y : ndarray of shape (n_samples_Y, n_features), default=None
+ An optional second feature array. If `None`, uses `Y=X`.
- gamma : float, default=1.
+ gamma : float, default=1
Scaling parameter of the chi2 kernel.
Returns
-------
kernel_matrix : ndarray of shape (n_samples_X, n_samples_Y)
+ The kernel matrix.
See Also
--------
@@ -1462,7 +1491,7 @@ def chi2_kernel(X, Y=None, gamma=1.0):
Local features and kernels for classification of texture and object
categories: A comprehensive study
International Journal of Computer Vision 2007
- https://research.microsoft.com/en-us/um/people/manik/projects/trade-off/papers/ZhangIJCV06.pdf
+ https://hal.archives-ouvertes.fr/hal-00171412/document
"""
K = additive_chi2_kernel(X, Y)
K *= gamma
@@ -1509,6 +1538,10 @@ def distance_metrics():
Read more in the :ref:`User Guide `.
+ Returns
+ -------
+ distance_metrics : dict
+ Returns valid metrics for pairwise_distances.
"""
return PAIRWISE_DISTANCE_FUNCTIONS
@@ -1862,7 +1895,7 @@ def pairwise_distances(
valid scipy.spatial.distance metrics), the scikit-learn implementation
will be used, which is faster and has support for sparse matrices (except
for 'cityblock'). For a verbose description of the metrics from
- scikit-learn, see the __doc__ of the sklearn.pairwise.distance_metrics
+ scikit-learn, see :func:`sklearn.metrics.pairwise.distance_metrics`
function.
Read more in the :ref:`User Guide `.
@@ -2041,6 +2074,11 @@ def kernel_metrics():
=============== ========================================
Read more in the :ref:`User Guide `.
+
+ Returns
+ -------
+ kernal_metrics : dict
+ Returns valid metrics for pairwise_kernels.
"""
return PAIRWISE_KERNEL_FUNCTIONS
@@ -2082,8 +2120,7 @@ def pairwise_kernels(
Parameters
----------
- X : ndarray of shape (n_samples_X, n_samples_X) or \
- (n_samples_X, n_features)
+ X : ndarray of shape (n_samples_X, n_samples_X) or (n_samples_X, n_features)
Array of pairwise kernels between samples, or a feature array.
The shape of the array should be (n_samples_X, n_samples_X) if
metric == "precomputed" and (n_samples_X, n_features) otherwise.
@@ -2121,8 +2158,7 @@ def pairwise_kernels(
Returns
-------
- K : ndarray of shape (n_samples_X, n_samples_X) or \
- (n_samples_X, n_samples_Y)
+ K : ndarray of shape (n_samples_X, n_samples_X) or (n_samples_X, n_samples_Y)
A kernel matrix K such that K_{i, j} is the kernel between the
ith and jth vectors of the given matrix X, if Y is None.
If Y is not None, then K_{i, j} is the kernel between the ith array
@@ -2131,7 +2167,6 @@ def pairwise_kernels(
Notes
-----
If metric is 'precomputed', Y is ignored and X is returned.
-
"""
# import GPKernel locally to prevent circular imports
from ..gaussian_process.kernels import Kernel as GPKernel
diff --git a/sklearn/metrics/tests/test_common.py b/sklearn/metrics/tests/test_common.py
index 1e627f9f86676..c0d6d351b8c3e 100644
--- a/sklearn/metrics/tests/test_common.py
+++ b/sklearn/metrics/tests/test_common.py
@@ -910,6 +910,10 @@ def test_thresholded_invariance_string_vs_numbers_labels(name):
)
@pytest.mark.parametrize("y_true, y_score", invalids_nan_inf)
def test_regression_thresholded_inf_nan_input(metric, y_true, y_score):
+ # Reshape since coverage_error only accepts 2D arrays.
+ if metric == coverage_error:
+ y_true = [y_true]
+ y_score = [y_score]
with pytest.raises(ValueError, match=r"contains (NaN|infinity)"):
metric(y_true, y_score)
diff --git a/sklearn/metrics/tests/test_pairwise.py b/sklearn/metrics/tests/test_pairwise.py
index f14c558d5a3c1..31964e2d182dd 100644
--- a/sklearn/metrics/tests/test_pairwise.py
+++ b/sklearn/metrics/tests/test_pairwise.py
@@ -534,6 +534,14 @@ def test_pairwise_distances_argmin_min(dtype):
assert_array_equal(argmin_0, argmin_1)
+ # F-contiguous arrays must be supported and must return identical results.
+ argmin_C_contiguous = pairwise_distances_argmin(X, Y)
+ argmin_F_contiguous = pairwise_distances_argmin(
+ np.asfortranarray(X), np.asfortranarray(Y)
+ )
+
+ assert_array_equal(argmin_C_contiguous, argmin_F_contiguous)
+
def _reduce_func(dist, start):
return dist[:, :100]
diff --git a/sklearn/metrics/tests/test_pairwise_distances_reduction.py b/sklearn/metrics/tests/test_pairwise_distances_reduction.py
index 192f7ef43a6c6..c6a71596e3472 100644
--- a/sklearn/metrics/tests/test_pairwise_distances_reduction.py
+++ b/sklearn/metrics/tests/test_pairwise_distances_reduction.py
@@ -121,6 +121,9 @@ def test_pairwise_distances_reduction_is_usable_for():
assert not PairwiseDistancesReduction.is_usable_for(csr_matrix(X), Y, metric)
assert not PairwiseDistancesReduction.is_usable_for(X, csr_matrix(Y), metric)
+ # F-ordered arrays are not supported
+ assert not PairwiseDistancesReduction.is_usable_for(np.asfortranarray(X), Y, metric)
+
def test_argkmin_factory_method_wrong_usages():
rng = np.random.RandomState(1)
@@ -510,9 +513,6 @@ def test_pairwise_distances_radius_neighbors(
neigh_indices_ref.append(ind)
neigh_distances_ref.append(dist)
- neigh_indices_ref = np.array(neigh_indices_ref)
- neigh_distances_ref = np.array(neigh_distances_ref)
-
neigh_distances, neigh_indices = PairwiseDistancesRadiusNeighborhood.compute(
X,
Y,
diff --git a/sklearn/metrics/tests/test_ranking.py b/sklearn/metrics/tests/test_ranking.py
index 7d2338337b83d..c27d0f326fc0b 100644
--- a/sklearn/metrics/tests/test_ranking.py
+++ b/sklearn/metrics/tests/test_ranking.py
@@ -1569,6 +1569,21 @@ def test_coverage_tie_handling():
assert_almost_equal(coverage_error([[1, 1, 1]], [[0.25, 0.5, 0.5]]), 3)
+@pytest.mark.parametrize(
+ "y_true, y_score",
+ [
+ ([1, 0, 1], [0.25, 0.5, 0.5]),
+ ([1, 0, 1], [[0.25, 0.5, 0.5]]),
+ ([[1, 0, 1]], [0.25, 0.5, 0.5]),
+ ],
+)
+def test_coverage_1d_error_message(y_true, y_score):
+ # Non-regression test for:
+ # https://github.com/scikit-learn/scikit-learn/issues/23368
+ with pytest.raises(ValueError, match=r"Expected 2D array, got 1D array instead"):
+ coverage_error(y_true, y_score)
+
+
def test_label_ranking_loss():
assert_almost_equal(label_ranking_loss([[0, 1]], [[0.25, 0.75]]), 0)
assert_almost_equal(label_ranking_loss([[0, 1]], [[0.75, 0.25]]), 1)
diff --git a/sklearn/metrics/tests/test_regression.py b/sklearn/metrics/tests/test_regression.py
index 090bc64bf0fe4..b51012d6c1f1b 100644
--- a/sklearn/metrics/tests/test_regression.py
+++ b/sklearn/metrics/tests/test_regression.py
@@ -613,3 +613,15 @@ def test_dummy_quantile_parameter_tuning():
).fit(X, y)
assert grid_search.best_params_["quantile"] == pytest.approx(alpha)
+
+
+def test_pinball_loss_relation_with_mae():
+ # Test that mean_pinball loss with alpha=0.5 if half of mean absolute error
+ rng = np.random.RandomState(714)
+ n = 100
+ y_true = rng.normal(size=n)
+ y_pred = y_true.copy() + rng.uniform(n)
+ assert (
+ mean_absolute_error(y_true, y_pred)
+ == mean_pinball_loss(y_true, y_pred, alpha=0.5) * 2
+ )
diff --git a/sklearn/model_selection/_search.py b/sklearn/model_selection/_search.py
index 5ceb71569b932..4e2a0bd1c5207 100644
--- a/sklearn/model_selection/_search.py
+++ b/sklearn/model_selection/_search.py
@@ -1101,6 +1101,10 @@ class GridSearchCV(BaseSearchCV):
See ``scoring`` parameter to know more about multiple metric
evaluation.
+ See :ref:`sphx_glr_auto_examples_model_selection_plot_grid_search_digits.py`
+ to see how to design a custom selection strategy using a callable
+ via `refit`.
+
.. versionchanged:: 0.20
Support for callable added.
diff --git a/sklearn/model_selection/_split.py b/sklearn/model_selection/_split.py
index d2a0b5e1fc329..3be828b4c6114 100644
--- a/sklearn/model_selection/_split.py
+++ b/sklearn/model_selection/_split.py
@@ -422,7 +422,7 @@ class KFold(_BaseKFold):
See Also
--------
- StratifiedKFold : Takes group information into account to avoid building
+ StratifiedKFold : Takes class information into account to avoid building
folds with imbalanced class distributions (for binary or multiclass
classification tasks).
@@ -453,8 +453,8 @@ def _iter_test_indices(self, X, y=None, groups=None):
class GroupKFold(_BaseKFold):
"""K-fold iterator variant with non-overlapping groups.
- The same group will not appear in two different folds (the number of
- distinct groups has to be at least equal to the number of folds).
+ Each group will appear exactly once in the test set across all folds (the
+ number of distinct groups has to be at least equal to the number of folds).
The folds are approximately balanced in the sense that the number of
distinct groups is approximately the same in each fold.
@@ -504,6 +504,10 @@ class GroupKFold(_BaseKFold):
--------
LeaveOneGroupOut : For splitting the data according to explicit
domain-specific stratification of the dataset.
+
+ StratifiedKFold : Takes class information into account to avoid building
+ folds with imbalanced class proportions (for binary or multiclass
+ classification tasks).
"""
def __init__(self, n_splits=5):
@@ -759,10 +763,11 @@ class StratifiedGroupKFold(_BaseKFold):
return stratified folds with non-overlapping groups. The folds are made by
preserving the percentage of samples for each class.
- The same group will not appear in two different folds (the number of
- distinct groups has to be at least equal to the number of folds).
+ Each group will appear exactly once in the test set across all folds (the
+ number of distinct groups has to be at least equal to the number of folds).
- The difference between GroupKFold and StratifiedGroupKFold is that
+ The difference between :class:`~sklearn.model_selection.GroupKFold`
+ and :class:`~sklearn.model_selection.StratifiedGroupKFold` is that
the former attempts to create balanced folds such that the number of
distinct groups is approximately the same in each fold, whereas
StratifiedGroupKFold attempts to create folds which preserve the
@@ -1147,6 +1152,10 @@ class LeaveOneGroupOut(BaseCrossValidator):
[[1 2]
[3 4]] [[5 6]
[7 8]] [1 2] [1 2]
+
+ See also
+ --------
+ GroupKFold: K-fold iterator variant with non-overlapping groups.
"""
def _iter_test_masks(self, X, y, groups):
@@ -1799,6 +1808,12 @@ class GroupShuffleSplit(ShuffleSplit):
... print("TRAIN:", train_idx, "TEST:", test_idx)
TRAIN: [2 3 4 5 6 7] TEST: [0 1]
TRAIN: [0 1 5 6 7] TEST: [2 3 4]
+
+ See Also
+ --------
+ ShuffleSplit : Shuffles samples to create independent test/train sets.
+
+ LeavePGroupsOut : Train set leaves out all possible subsets of `p` groups.
"""
def __init__(
diff --git a/sklearn/model_selection/_validation.py b/sklearn/model_selection/_validation.py
index d83fca63da48c..94bbf532f5a59 100644
--- a/sklearn/model_selection/_validation.py
+++ b/sklearn/model_selection/_validation.py
@@ -1180,7 +1180,7 @@ def permutation_test_score(
scoring=None,
fit_params=None,
):
- """Evaluate the significance of a cross-validated score with permutations
+ """Evaluate the significance of a cross-validated score with permutations.
Permutes targets to generate 'randomized data' and compute the empirical
p-value against the null hypothesis that features and targets are
@@ -1218,12 +1218,6 @@ def permutation_test_score(
cross-validator uses them for grouping the samples while splitting
the dataset into train/test set.
- scoring : str or callable, default=None
- A single str (see :ref:`scoring_parameter`) or a callable
- (see :ref:`scoring`) to evaluate the predictions on the test set.
-
- If `None` the estimator's score method is used.
-
cv : int, cross-validation generator or an iterable, default=None
Determines the cross-validation splitting strategy.
Possible inputs for cv are:
@@ -1261,6 +1255,12 @@ def permutation_test_score(
verbose : int, default=0
The verbosity level.
+ scoring : str or callable, default=None
+ A single str (see :ref:`scoring_parameter`) or a callable
+ (see :ref:`scoring`) to evaluate the predictions on the test set.
+
+ If `None` the estimator's score method is used.
+
fit_params : dict, default=None
Parameters to pass to the fit method of the estimator.
@@ -1292,7 +1292,6 @@ def permutation_test_score(
Performance
`_. The
Journal of Machine Learning Research (2010) vol. 11
-
"""
X, y, groups = indexable(X, y, groups)
@@ -1805,11 +1804,6 @@ def validation_curve(
verbose : int, default=0
Controls the verbosity: the higher, the more messages.
- fit_params : dict, default=None
- Parameters to pass to the fit method of the estimator.
-
- .. versionadded:: 0.24
-
error_score : 'raise' or numeric, default=np.nan
Value to assign to the score if an error occurs in estimator fitting.
If set to 'raise', the error is raised.
@@ -1817,6 +1811,11 @@ def validation_curve(
.. versionadded:: 0.20
+ fit_params : dict, default=None
+ Parameters to pass to the fit method of the estimator.
+
+ .. versionadded:: 0.24
+
Returns
-------
train_scores : array of shape (n_ticks, n_cv_folds)
@@ -1828,7 +1827,6 @@ def validation_curve(
Notes
-----
See :ref:`sphx_glr_auto_examples_model_selection_plot_validation_curve.py`
-
"""
X, y, groups = indexable(X, y, groups)
diff --git a/sklearn/model_selection/tests/test_search.py b/sklearn/model_selection/tests/test_search.py
index 2c1a66dcb3fed..447727830d84d 100644
--- a/sklearn/model_selection/tests/test_search.py
+++ b/sklearn/model_selection/tests/test_search.py
@@ -114,7 +114,7 @@ def set_params(self, **params):
class LinearSVCNoScore(LinearSVC):
- """An LinearSVC classifier that has no score method."""
+ """A LinearSVC classifier that has no score method."""
@property
def score(self):
diff --git a/sklearn/model_selection/tests/test_validation.py b/sklearn/model_selection/tests/test_validation.py
index 90b5a605ac2e4..339f98687733d 100644
--- a/sklearn/model_selection/tests/test_validation.py
+++ b/sklearn/model_selection/tests/test_validation.py
@@ -79,7 +79,7 @@
try:
- WindowsError
+ WindowsError # type: ignore
except NameError:
WindowsError = None
diff --git a/sklearn/multioutput.py b/sklearn/multioutput.py
index 24e4cc8dda7e8..095b700e3e5f7 100644
--- a/sklearn/multioutput.py
+++ b/sklearn/multioutput.py
@@ -853,7 +853,7 @@ class RegressorChain(MetaEstimatorMixin, RegressorMixin, _BaseChain):
Parameters
----------
base_estimator : estimator
- The base estimator from which the classifier chain is built.
+ The base estimator from which the regressor chain is built.
order : array-like of shape (n_outputs,) or 'random', default=None
If `None`, the order will be determined by the order of columns in
diff --git a/sklearn/neighbors/_binary_tree.pxi b/sklearn/neighbors/_binary_tree.pxi
index 36781a770906c..ee3adfe3a8027 100644
--- a/sklearn/neighbors/_binary_tree.pxi
+++ b/sklearn/neighbors/_binary_tree.pxi
@@ -230,12 +230,14 @@ leaf_size : positive int, default=40
satisfy ``leaf_size <= n_points <= 2 * leaf_size``, except in
the case that ``n_samples < leaf_size``.
-metric : str or DistanceMetric object
- The distance metric to use for the tree. Default='minkowski'
- with p=2 (that is, a euclidean metric). See the documentation
- of the DistanceMetric class for a list of available metrics.
- {binary_tree}.valid_metrics gives a list of the metrics which
- are valid for {BinaryTree}.
+metric : str or DistanceMetric object, default='minkowski'
+ Metric to use for distance computation. Default is "minkowski", which
+ results in the standard Euclidean distance when p = 2.
+ {binary_tree}.valid_metrics gives a list of the metrics which are valid for
+ {BinaryTree}. See the documentation of `scipy.spatial.distance
+ `_ and the
+ metrics listed in :class:`~sklearn.metrics.pairwise.distance_metrics` for
+ more information.
Additional keywords are passed to the distance metric class.
Note: Callable functions in the metric parameter are NOT supported for KDTree
diff --git a/sklearn/neighbors/_classification.py b/sklearn/neighbors/_classification.py
index bcad8c71aee07..254ec1af67543 100644
--- a/sklearn/neighbors/_classification.py
+++ b/sklearn/neighbors/_classification.py
@@ -9,7 +9,7 @@
# License: BSD 3 clause (C) INRIA, University of Amsterdam
import numpy as np
-from scipy import stats
+from ..utils.fixes import _mode
from ..utils.extmath import weighted_mode
from ..utils.validation import _is_arraylike, _num_samples
@@ -65,15 +65,22 @@ class KNeighborsClassifier(KNeighborsMixin, ClassifierMixin, NeighborsBase):
(l2) for p = 2. For arbitrary p, minkowski_distance (l_p) is used.
metric : str or callable, default='minkowski'
- The distance metric to use for the tree. The default metric is
- minkowski, and with p=2 is equivalent to the standard Euclidean
- metric. For a list of available metrics, see the documentation of
- :class:`~sklearn.metrics.DistanceMetric` and the metrics listed in
- `sklearn.metrics.pairwise.PAIRWISE_DISTANCE_FUNCTIONS`. Note that the
- "cosine" metric uses :func:`~sklearn.metrics.pairwise.cosine_distances`.
+ Metric to use for distance computation. Default is "minkowski", which
+ results in the standard Euclidean distance when p = 2. See the
+ documentation of `scipy.spatial.distance
+ `_ and
+ the metrics listed in
+ :class:`~sklearn.metrics.pairwise.distance_metrics` for valid metric
+ values.
+
If metric is "precomputed", X is assumed to be a distance matrix and
- must be square during fit. X may be a :term:`sparse graph`,
- in which case only "nonzero" elements may be considered neighbors.
+ must be square during fit. X may be a :term:`sparse graph`, in which
+ case only "nonzero" elements may be considered neighbors.
+
+ If metric is a callable function, it takes two arrays representing 1D
+ vectors as inputs and must return one value indicating the distance
+ between those vectors. This works for Scipy's metrics, but is less
+ efficient than passing the metric name as a string.
metric_params : dict, default=None
Additional keyword arguments for the metric function.
@@ -234,7 +241,7 @@ def predict(self, X):
y_pred = np.empty((n_queries, n_outputs), dtype=classes_[0].dtype)
for k, classes_k in enumerate(classes_):
if weights is None:
- mode, _ = stats.mode(_y[neigh_ind, k], axis=1)
+ mode, _ = _mode(_y[neigh_ind, k], axis=1)
else:
mode, _ = weighted_mode(_y[neigh_ind, k], weights, axis=1)
@@ -357,13 +364,22 @@ class RadiusNeighborsClassifier(RadiusNeighborsMixin, ClassifierMixin, Neighbors
(l2) for p = 2. For arbitrary p, minkowski_distance (l_p) is used.
metric : str or callable, default='minkowski'
- Distance metric to use for the tree. The default metric is
- minkowski, and with p=2 is equivalent to the standard Euclidean
- metric. For a list of available metrics, see the documentation of
- :class:`~sklearn.metrics.DistanceMetric`.
+ Metric to use for distance computation. Default is "minkowski", which
+ results in the standard Euclidean distance when p = 2. See the
+ documentation of `scipy.spatial.distance
+ `_ and
+ the metrics listed in
+ :class:`~sklearn.metrics.pairwise.distance_metrics` for valid metric
+ values.
+
If metric is "precomputed", X is assumed to be a distance matrix and
- must be square during fit. X may be a :term:`sparse graph`,
- in which case only "nonzero" elements may be considered neighbors.
+ must be square during fit. X may be a :term:`sparse graph`, in which
+ case only "nonzero" elements may be considered neighbors.
+
+ If metric is a callable function, it takes two arrays representing 1D
+ vectors as inputs and must return one value indicating the distance
+ between those vectors. This works for Scipy's metrics, but is less
+ efficient than passing the metric name as a string.
outlier_label : {manual label, 'most_frequent'}, default=None
Label for outlier samples (samples with no neighbors in given radius).
diff --git a/sklearn/neighbors/_graph.py b/sklearn/neighbors/_graph.py
index 2be70c0638517..310f3667d9fa3 100644
--- a/sklearn/neighbors/_graph.py
+++ b/sklearn/neighbors/_graph.py
@@ -65,13 +65,13 @@ def kneighbors_graph(
between neighbors according to the given metric.
metric : str, default='minkowski'
- The distance metric to use for the tree. The default metric is
- minkowski, and with p=2 is equivalent to the standard Euclidean
- metric.
- For a list of available metrics, see the documentation of
- :class:`~sklearn.metrics.DistanceMetric` and the metrics listed in
- `sklearn.metrics.pairwise.PAIRWISE_DISTANCE_FUNCTIONS`. Note that the
- "cosine" metric uses :func:`~sklearn.metrics.pairwise.cosine_distances`.
+ Metric to use for distance computation. Default is "minkowski", which
+ results in the standard Euclidean distance when p = 2. See the
+ documentation of `scipy.spatial.distance
+ `_ and
+ the metrics listed in
+ :class:`~sklearn.metrics.pairwise.distance_metrics` for valid metric
+ values.
p : int, default=2
Power parameter for the Minkowski metric. When p = 1, this is
@@ -160,13 +160,13 @@ def radius_neighbors_graph(
between neighbors according to the given metric.
metric : str, default='minkowski'
- The distance metric to use for the tree. The default metric is
- minkowski, and with p=2 is equivalent to the standard Euclidean
- metric.
- For a list of available metrics, see the documentation of
- :class:`~sklearn.metrics.DistanceMetric` and the metrics listed in
- `sklearn.metrics.pairwise.PAIRWISE_DISTANCE_FUNCTIONS`. Note that the
- "cosine" metric uses :func:`~sklearn.metrics.pairwise.cosine_distances`.
+ Metric to use for distance computation. Default is "minkowski", which
+ results in the standard Euclidean distance when p = 2. See the
+ documentation of `scipy.spatial.distance
+ `_ and
+ the metrics listed in
+ :class:`~sklearn.metrics.pairwise.distance_metrics` for valid metric
+ values.
p : int, default=2
Power parameter for the Minkowski metric. When p = 1, this is
@@ -266,31 +266,21 @@ class KNeighborsTransformer(
nature of the problem.
metric : str or callable, default='minkowski'
- Metric to use for distance computation. Any metric from scikit-learn
- or scipy.spatial.distance can be used.
-
- If metric is a callable function, it is called on each
- pair of instances (rows) and the resulting value recorded. The callable
- should take two arrays as input and return one value indicating the
- distance between them. This works for Scipy's metrics, but is less
+ Metric to use for distance computation. Default is "minkowski", which
+ results in the standard Euclidean distance when p = 2. See the
+ documentation of `scipy.spatial.distance
+ `_ and
+ the metrics listed in
+ :class:`~sklearn.metrics.pairwise.distance_metrics` for valid metric
+ values.
+
+ If metric is a callable function, it takes two arrays representing 1D
+ vectors as inputs and must return one value indicating the distance
+ between those vectors. This works for Scipy's metrics, but is less
efficient than passing the metric name as a string.
Distance matrices are not supported.
- Valid values for metric are:
-
- - from scikit-learn: ['cityblock', 'cosine', 'euclidean', 'l1', 'l2',
- 'manhattan']
-
- - from scipy.spatial.distance: ['braycurtis', 'canberra', 'chebyshev',
- 'correlation', 'dice', 'hamming', 'jaccard', 'kulsinski',
- 'mahalanobis', 'minkowski', 'rogerstanimoto', 'russellrao',
- 'seuclidean', 'sokalmichener', 'sokalsneath', 'sqeuclidean',
- 'yule']
-
- See the documentation for scipy.spatial.distance for details on these
- metrics.
-
p : int, default=2
Parameter for the Minkowski metric from
sklearn.metrics.pairwise.pairwise_distances. When p = 1, this is
@@ -493,31 +483,21 @@ class RadiusNeighborsTransformer(
nature of the problem.
metric : str or callable, default='minkowski'
- Metric to use for distance computation. Any metric from scikit-learn
- or scipy.spatial.distance can be used.
-
- If metric is a callable function, it is called on each
- pair of instances (rows) and the resulting value recorded. The callable
- should take two arrays as input and return one value indicating the
- distance between them. This works for Scipy's metrics, but is less
+ Metric to use for distance computation. Default is "minkowski", which
+ results in the standard Euclidean distance when p = 2. See the
+ documentation of `scipy.spatial.distance
+ `_ and
+ the metrics listed in
+ :class:`~sklearn.metrics.pairwise.distance_metrics` for valid metric
+ values.
+
+ If metric is a callable function, it takes two arrays representing 1D
+ vectors as inputs and must return one value indicating the distance
+ between those vectors. This works for Scipy's metrics, but is less
efficient than passing the metric name as a string.
Distance matrices are not supported.
- Valid values for metric are:
-
- - from scikit-learn: ['cityblock', 'cosine', 'euclidean', 'l1', 'l2',
- 'manhattan']
-
- - from scipy.spatial.distance: ['braycurtis', 'canberra', 'chebyshev',
- 'correlation', 'dice', 'hamming', 'jaccard', 'kulsinski',
- 'mahalanobis', 'minkowski', 'rogerstanimoto', 'russellrao',
- 'seuclidean', 'sokalmichener', 'sokalsneath', 'sqeuclidean',
- 'yule']
-
- See the documentation for scipy.spatial.distance for details on these
- metrics.
-
p : int, default=2
Parameter for the Minkowski metric from
sklearn.metrics.pairwise.pairwise_distances. When p = 1, this is
diff --git a/sklearn/neighbors/_kde.py b/sklearn/neighbors/_kde.py
index a785fcd86939f..d4dd498ce351d 100644
--- a/sklearn/neighbors/_kde.py
+++ b/sklearn/neighbors/_kde.py
@@ -47,12 +47,17 @@ class KernelDensity(BaseEstimator):
The kernel to use.
metric : str, default='euclidean'
- The distance metric to use. Note that not all metrics are
- valid with all algorithms. Refer to the documentation of
- :class:`BallTree` and :class:`KDTree` for a description of
- available algorithms. Note that the normalization of the density
- output is correct only for the Euclidean distance metric. Default
- is 'euclidean'.
+ Metric to use for distance computation. See the
+ documentation of `scipy.spatial.distance
+ `_ and
+ the metrics listed in
+ :class:`~sklearn.metrics.pairwise.distance_metrics` for valid metric
+ values.
+
+ Not all metrics are valid with all algorithms: refer to the
+ documentation of :class:`BallTree` and :class:`KDTree`. Note that the
+ normalization of the density output is correct only for the Euclidean
+ distance metric.
atol : float, default=0
The desired absolute tolerance of the result. A larger tolerance will
diff --git a/sklearn/neighbors/_lof.py b/sklearn/neighbors/_lof.py
index 025a1c6d80768..b1d2e2a50ce27 100644
--- a/sklearn/neighbors/_lof.py
+++ b/sklearn/neighbors/_lof.py
@@ -58,34 +58,23 @@ class LocalOutlierFactor(KNeighborsMixin, OutlierMixin, NeighborsBase):
nature of the problem.
metric : str or callable, default='minkowski'
- The metric is used for distance computation. Any metric from scikit-learn
- or scipy.spatial.distance can be used.
+ Metric to use for distance computation. Default is "minkowski", which
+ results in the standard Euclidean distance when p = 2. See the
+ documentation of `scipy.spatial.distance
+ `_ and
+ the metrics listed in
+ :class:`~sklearn.metrics.pairwise.distance_metrics` for valid metric
+ values.
If metric is "precomputed", X is assumed to be a distance matrix and
- must be square. X may be a sparse matrix, in which case only "nonzero"
- elements may be considered neighbors.
+ must be square during fit. X may be a :term:`sparse graph`, in which
+ case only "nonzero" elements may be considered neighbors.
- If metric is a callable function, it is called on each
- pair of instances (rows) and the resulting value recorded. The callable
- should take two arrays as input and return one value indicating the
- distance between them. This works for Scipy's metrics, but is less
+ If metric is a callable function, it takes two arrays representing 1D
+ vectors as inputs and must return one value indicating the distance
+ between those vectors. This works for Scipy's metrics, but is less
efficient than passing the metric name as a string.
- Valid values for metric are:
-
- - from scikit-learn: ['cityblock', 'cosine', 'euclidean', 'l1', 'l2',
- 'manhattan']
-
- - from scipy.spatial.distance: ['braycurtis', 'canberra', 'chebyshev',
- 'correlation', 'dice', 'hamming', 'jaccard', 'kulsinski',
- 'mahalanobis', 'minkowski', 'rogerstanimoto', 'russellrao',
- 'seuclidean', 'sokalmichener', 'sokalsneath', 'sqeuclidean',
- 'yule']
-
- See the documentation for scipy.spatial.distance for details on these
- metrics:
- https://docs.scipy.org/doc/scipy/reference/spatial.distance.html.
-
p : int, default=2
Parameter for the Minkowski metric from
:func:`sklearn.metrics.pairwise.pairwise_distances`. When p = 1, this
diff --git a/sklearn/neighbors/_nca.py b/sklearn/neighbors/_nca.py
index af76a000ef2cb..96cdc3052c66e 100644
--- a/sklearn/neighbors/_nca.py
+++ b/sklearn/neighbors/_nca.py
@@ -229,7 +229,7 @@ def fit(self, X, y):
# (n_samples, n_samples)
# Initialize the transformation
- transformation = self._initialize(X, y, init)
+ transformation = np.ravel(self._initialize(X, y, init))
# Create a dictionary of parameters to be passed to the optimizer
disp = self.verbose - 2 if self.verbose > 1 else -1
diff --git a/sklearn/neighbors/_nearest_centroid.py b/sklearn/neighbors/_nearest_centroid.py
index b52d9407333a6..9403623ea8de9 100644
--- a/sklearn/neighbors/_nearest_centroid.py
+++ b/sklearn/neighbors/_nearest_centroid.py
@@ -30,11 +30,16 @@ class NearestCentroid(ClassifierMixin, BaseEstimator):
Parameters
----------
metric : str or callable, default="euclidean"
- The metric to use when calculating distance between instances in a
- feature array. If metric is a string or callable, it must be one of
- the options allowed by
- :func:`~sklearn.metrics.pairwise_distances` for its metric
- parameter. The centroids for the samples corresponding to each class is
+ Metric to use for distance computation. Default is "minkowski", which
+ results in the standard Euclidean distance when p = 2. See the
+ documentation of `scipy.spatial.distance
+ `_ and
+ the metrics listed in
+ :class:`~sklearn.metrics.pairwise.distance_metrics` for valid metric
+ values. Note that "wminkowski", "seuclidean" and "mahalanobis" are not
+ supported.
+
+ The centroids for the samples corresponding to each class is
the point from which the sum of the distances (according to the metric)
of all samples that belong to that particular class are minimized.
If the `"manhattan"` metric is provided, this centroid is the median
diff --git a/sklearn/neighbors/_regression.py b/sklearn/neighbors/_regression.py
index 4c995e5062277..aef20b9baa243 100644
--- a/sklearn/neighbors/_regression.py
+++ b/sklearn/neighbors/_regression.py
@@ -72,15 +72,22 @@ class KNeighborsRegressor(KNeighborsMixin, RegressorMixin, NeighborsBase):
(l2) for p = 2. For arbitrary p, minkowski_distance (l_p) is used.
metric : str or callable, default='minkowski'
- The distance metric to use for the tree. The default metric is
- minkowski, and with p=2 is equivalent to the standard Euclidean
- metric. For a list of available metrics, see the documentation of
- :class:`~sklearn.metrics.DistanceMetric` and the metrics listed in
- `sklearn.metrics.pairwise.PAIRWISE_DISTANCE_FUNCTIONS`. Note that the
- "cosine" metric uses :func:`~sklearn.metrics.pairwise.cosine_distances`.
+ Metric to use for distance computation. Default is "minkowski", which
+ results in the standard Euclidean distance when p = 2. See the
+ documentation of `scipy.spatial.distance
+ `_ and
+ the metrics listed in
+ :class:`~sklearn.metrics.pairwise.distance_metrics` for valid metric
+ values.
+
If metric is "precomputed", X is assumed to be a distance matrix and
- must be square during fit. X may be a :term:`sparse graph`,
- in which case only "nonzero" elements may be considered neighbors.
+ must be square during fit. X may be a :term:`sparse graph`, in which
+ case only "nonzero" elements may be considered neighbors.
+
+ If metric is a callable function, it takes two arrays representing 1D
+ vectors as inputs and must return one value indicating the distance
+ between those vectors. This works for Scipy's metrics, but is less
+ efficient than passing the metric name as a string.
metric_params : dict, default=None
Additional keyword arguments for the metric function.
@@ -300,13 +307,22 @@ class RadiusNeighborsRegressor(RadiusNeighborsMixin, RegressorMixin, NeighborsBa
(l2) for p = 2. For arbitrary p, minkowski_distance (l_p) is used.
metric : str or callable, default='minkowski'
- The distance metric to use for the tree. The default metric is
- minkowski, and with p=2 is equivalent to the standard Euclidean
- metric. See the documentation of :class:`DistanceMetric` for a
- list of available metrics.
+ Metric to use for distance computation. Default is "minkowski", which
+ results in the standard Euclidean distance when p = 2. See the
+ documentation of `scipy.spatial.distance
+ `_ and
+ the metrics listed in
+ :class:`~sklearn.metrics.pairwise.distance_metrics` for valid metric
+ values.
+
If metric is "precomputed", X is assumed to be a distance matrix and
- must be square during fit. X may be a :term:`sparse graph`,
- in which case only "nonzero" elements may be considered neighbors.
+ must be square during fit. X may be a :term:`sparse graph`, in which
+ case only "nonzero" elements may be considered neighbors.
+
+ If metric is a callable function, it takes two arrays representing 1D
+ vectors as inputs and must return one value indicating the distance
+ between those vectors. This works for Scipy's metrics, but is less
+ efficient than passing the metric name as a string.
metric_params : dict, default=None
Additional keyword arguments for the metric function.
diff --git a/sklearn/neighbors/_unsupervised.py b/sklearn/neighbors/_unsupervised.py
index 6399363112378..162eff0ac2a63 100644
--- a/sklearn/neighbors/_unsupervised.py
+++ b/sklearn/neighbors/_unsupervised.py
@@ -39,15 +39,22 @@ class NearestNeighbors(KNeighborsMixin, RadiusNeighborsMixin, NeighborsBase):
nature of the problem.
metric : str or callable, default='minkowski'
- The distance metric to use for the tree. The default metric is
- minkowski, and with p=2 is equivalent to the standard Euclidean
- metric. For a list of available metrics, see the documentation of
- :class:`~sklearn.metrics.DistanceMetric` and the metrics listed in
- `sklearn.metrics.pairwise.PAIRWISE_DISTANCE_FUNCTIONS`. Note that the
- "cosine" metric uses :func:`~sklearn.metrics.pairwise.cosine_distances`.
+ Metric to use for distance computation. Default is "minkowski", which
+ results in the standard Euclidean distance when p = 2. See the
+ documentation of `scipy.spatial.distance
+ `_ and
+ the metrics listed in
+ :class:`~sklearn.metrics.pairwise.distance_metrics` for valid metric
+ values.
+
If metric is "precomputed", X is assumed to be a distance matrix and
- must be square during fit. X may be a :term:`sparse graph`,
- in which case only "nonzero" elements may be considered neighbors.
+ must be square during fit. X may be a :term:`sparse graph`, in which
+ case only "nonzero" elements may be considered neighbors.
+
+ If metric is a callable function, it takes two arrays representing 1D
+ vectors as inputs and must return one value indicating the distance
+ between those vectors. This works for Scipy's metrics, but is less
+ efficient than passing the metric name as a string.
p : int, default=2
Parameter for the Minkowski metric from
diff --git a/sklearn/neighbors/tests/test_neighbors.py b/sklearn/neighbors/tests/test_neighbors.py
index 6b303886e2b5a..6ffd49422b1a3 100644
--- a/sklearn/neighbors/tests/test_neighbors.py
+++ b/sklearn/neighbors/tests/test_neighbors.py
@@ -70,7 +70,7 @@
ALGORITHMS = ("ball_tree", "brute", "kd_tree", "auto")
COMMON_VALID_METRICS = sorted(
set.intersection(*map(set, neighbors.VALID_METRICS.values()))
-)
+) # type: ignore
P = (1, 2, 3, 4, np.inf)
JOBLIB_BACKENDS = list(joblib.parallel.BACKENDS.keys())
diff --git a/sklearn/pipeline.py b/sklearn/pipeline.py
index 74347f250bc83..086bc4e1f0557 100644
--- a/sklearn/pipeline.py
+++ b/sklearn/pipeline.py
@@ -75,8 +75,8 @@ class Pipeline(_BaseComposition):
----------
steps : list of tuple
List of (name, transform) tuples (implementing `fit`/`transform`) that
- are chained, in the order in which they are chained, with the last
- object an estimator.
+ are chained in sequential order. The last transform must be an
+ estimator.
memory : str or object with the joblib.Memory interface, default=None
Used to cache the fitted transformers of the pipeline. By default,
@@ -1244,8 +1244,7 @@ def _sk_visual_block_(self):
def make_union(*transformers, n_jobs=None, verbose=False):
- """
- Construct a FeatureUnion from the given transformers.
+ """Construct a FeatureUnion from the given transformers.
This is a shorthand for the FeatureUnion constructor; it does not require,
and does not permit, naming the transformers. Instead, they will be given
@@ -1254,6 +1253,7 @@ def make_union(*transformers, n_jobs=None, verbose=False):
Parameters
----------
*transformers : list of estimators
+ One or more estimators.
n_jobs : int, default=None
Number of jobs to run in parallel.
@@ -1262,7 +1262,7 @@ def make_union(*transformers, n_jobs=None, verbose=False):
for more details.
.. versionchanged:: v0.20
- `n_jobs` default changed from 1 to None
+ `n_jobs` default changed from 1 to None.
verbose : bool, default=False
If True, the time elapsed while fitting each transformer will be
@@ -1271,6 +1271,8 @@ def make_union(*transformers, n_jobs=None, verbose=False):
Returns
-------
f : FeatureUnion
+ A :class:`FeatureUnion` object for concatenating the results of multiple
+ transformer objects.
See Also
--------
diff --git a/sklearn/preprocessing/_data.py b/sklearn/preprocessing/_data.py
index f0088aab521ad..410501dfbcebd 100644
--- a/sklearn/preprocessing/_data.py
+++ b/sklearn/preprocessing/_data.py
@@ -1659,6 +1659,11 @@ def robust_scale(
X_tr : {ndarray, sparse matrix} of shape (n_samples, n_features)
The transformed data.
+ See Also
+ --------
+ RobustScaler : Performs centering and scaling using the Transformer API
+ (e.g. as part of a preprocessing :class:`~sklearn.pipeline.Pipeline`).
+
Notes
-----
This implementation will refuse to center scipy.sparse matrices
@@ -1687,11 +1692,6 @@ def robust_scale(
:class:`~sklearn.preprocessing.RobustScaler` within a
:ref:`Pipeline ` in order to prevent most risks of data
leaking: `pipe = make_pipeline(RobustScaler(), LogisticRegression())`.
-
- See Also
- --------
- RobustScaler : Performs centering and scaling using the Transformer API
- (e.g. as part of a preprocessing :class:`~sklearn.pipeline.Pipeline`).
"""
X = check_array(
X,
@@ -3293,8 +3293,8 @@ def _check_input(
reset=in_fit,
)
- with np.warnings.catch_warnings():
- np.warnings.filterwarnings("ignore", r"All-NaN (slice|axis) encountered")
+ with warnings.catch_warnings():
+ warnings.filterwarnings("ignore", r"All-NaN (slice|axis) encountered")
if check_positive and self.method == "box-cox" and np.nanmin(X) <= 0:
raise ValueError(
"The Box-Cox transformation can only be "
diff --git a/sklearn/preprocessing/_encoders.py b/sklearn/preprocessing/_encoders.py
index d4cc642a18562..0aad790d8b098 100644
--- a/sklearn/preprocessing/_encoders.py
+++ b/sklearn/preprocessing/_encoders.py
@@ -664,7 +664,7 @@ def _fit_infrequent_category_mapping(self, n_samples, category_counts):
to a single output:
`_default_to_infrequent_mappings[7] = array([0, 3, 1, 3, 2, 3])`
- Defines private attrite: `_infrequent_indices`. `_infrequent_indices[i]`
+ Defines private attribute: `_infrequent_indices`. `_infrequent_indices[i]`
is an array of indices such that
`categories_[i][_infrequent_indices[i]]` are all the infrequent category
labels. If the feature `i` has no infrequent categories
@@ -1408,19 +1408,23 @@ def inverse_transform(self, X):
found_unknown = {}
for i in range(n_features):
- labels = X[:, i].astype("int64", copy=False)
+ labels = X[:, i]
# replace values of X[:, i] that were nan with actual indices
if i in self._missing_indices:
- X_i_mask = _get_mask(X[:, i], self.encoded_missing_value)
+ X_i_mask = _get_mask(labels, self.encoded_missing_value)
labels[X_i_mask] = self._missing_indices[i]
if self.handle_unknown == "use_encoded_value":
- unknown_labels = labels == self.unknown_value
- X_tr[:, i] = self.categories_[i][np.where(unknown_labels, 0, labels)]
+ unknown_labels = _get_mask(labels, self.unknown_value)
+
+ known_labels = ~unknown_labels
+ X_tr[known_labels, i] = self.categories_[i][
+ labels[known_labels].astype("int64", copy=False)
+ ]
found_unknown[i] = unknown_labels
else:
- X_tr[:, i] = self.categories_[i][labels]
+ X_tr[:, i] = self.categories_[i][labels.astype("int64", copy=False)]
# insert None values for unknown values
if found_unknown:
diff --git a/sklearn/preprocessing/_label.py b/sklearn/preprocessing/_label.py
index e7f4a5e337208..caf887e2bb648 100644
--- a/sklearn/preprocessing/_label.py
+++ b/sklearn/preprocessing/_label.py
@@ -447,6 +447,11 @@ def label_binarize(y, *, classes, neg_label=0, pos_label=1, sparse_output=False)
Shape will be (n_samples, 1) for binary problems. Sparse matrix will
be of CSR format.
+ See Also
+ --------
+ LabelBinarizer : Class used to wrap the functionality of label_binarize and
+ allow for fitting to classes independently of the transform operation.
+
Examples
--------
>>> from sklearn.preprocessing import label_binarize
@@ -467,11 +472,6 @@ def label_binarize(y, *, classes, neg_label=0, pos_label=1, sparse_output=False)
[0],
[0],
[1]])
-
- See Also
- --------
- LabelBinarizer : Class used to wrap the functionality of label_binarize and
- allow for fitting to classes independently of the transform operation.
"""
if not isinstance(y, list):
# XXX Workaround that will be removed when list of list format is
diff --git a/sklearn/preprocessing/tests/test_encoders.py b/sklearn/preprocessing/tests/test_encoders.py
index ea32de22cd2f0..2a90f9894f073 100644
--- a/sklearn/preprocessing/tests/test_encoders.py
+++ b/sklearn/preprocessing/tests/test_encoders.py
@@ -1928,6 +1928,15 @@ def test_ordinal_encoder_unknown_missing_interaction():
X_test_trans = oe.transform(X_test)
assert_allclose(X_test_trans, [[np.nan], [-3]])
+ # Non-regression test for #24082
+ X_roundtrip = oe.inverse_transform(X_test_trans)
+
+ # np.nan is unknown so it maps to None
+ assert X_roundtrip[0][0] is None
+
+ # -3 is the encoded missing value so it maps back to nan
+ assert np.isnan(X_roundtrip[1][0])
+
@pytest.mark.parametrize("with_pandas", [True, False])
def test_ordinal_encoder_encoded_missing_value_error(with_pandas):
@@ -1953,3 +1962,55 @@ def test_ordinal_encoder_encoded_missing_value_error(with_pandas):
with pytest.raises(ValueError, match=error_msg):
oe.fit(X)
+
+
+@pytest.mark.parametrize(
+ "X_train, X_test_trans_expected, X_roundtrip_expected",
+ [
+ (
+ # missing value is not in training set
+ # inverse transform will considering encoded nan as unknown
+ np.array([["a"], ["1"]], dtype=object),
+ [[0], [np.nan], [np.nan]],
+ np.asarray([["1"], [None], [None]], dtype=object),
+ ),
+ (
+ # missing value in training set,
+ # inverse transform will considering encoded nan as missing
+ np.array([[np.nan], ["1"], ["a"]], dtype=object),
+ [[0], [np.nan], [np.nan]],
+ np.asarray([["1"], [np.nan], [np.nan]], dtype=object),
+ ),
+ ],
+)
+def test_ordinal_encoder_unknown_missing_interaction_both_nan(
+ X_train, X_test_trans_expected, X_roundtrip_expected
+):
+ """Check transform when unknown_value and encoded_missing_value is nan.
+
+ Non-regression test for #24082.
+ """
+ oe = OrdinalEncoder(
+ handle_unknown="use_encoded_value",
+ unknown_value=np.nan,
+ encoded_missing_value=np.nan,
+ ).fit(X_train)
+
+ X_test = np.array([["1"], [np.nan], ["b"]])
+ X_test_trans = oe.transform(X_test)
+
+ # both nan and unknown are encoded as nan
+ assert_allclose(X_test_trans, X_test_trans_expected)
+ X_roundtrip = oe.inverse_transform(X_test_trans)
+
+ n_samples = X_roundtrip_expected.shape[0]
+ for i in range(n_samples):
+ expected_val = X_roundtrip_expected[i, 0]
+ val = X_roundtrip[i, 0]
+
+ if expected_val is None:
+ assert val is None
+ elif is_scalar_nan(expected_val):
+ assert np.isnan(val)
+ else:
+ assert val == expected_val
diff --git a/sklearn/random_projection.py b/sklearn/random_projection.py
index 3b4a5e2236db5..7bda89bfec3b2 100644
--- a/sklearn/random_projection.py
+++ b/sklearn/random_projection.py
@@ -94,6 +94,15 @@ def johnson_lindenstrauss_min_dim(n_samples, *, eps=0.1):
The minimal number of components to guarantee with good probability
an eps-embedding with n_samples.
+ References
+ ----------
+
+ .. [1] https://en.wikipedia.org/wiki/Johnson%E2%80%93Lindenstrauss_lemma
+
+ .. [2] Sanjoy Dasgupta and Anupam Gupta, 1999,
+ "An elementary proof of the Johnson-Lindenstrauss Lemma."
+ http://citeseer.ist.psu.edu/viewdoc/summary?doi=10.1.1.45.3654
+
Examples
--------
>>> from sklearn.random_projection import johnson_lindenstrauss_min_dim
@@ -105,16 +114,6 @@ def johnson_lindenstrauss_min_dim(n_samples, *, eps=0.1):
>>> johnson_lindenstrauss_min_dim([1e4, 1e5, 1e6], eps=0.1)
array([ 7894, 9868, 11841])
-
- References
- ----------
-
- .. [1] https://en.wikipedia.org/wiki/Johnson%E2%80%93Lindenstrauss_lemma
-
- .. [2] Sanjoy Dasgupta and Anupam Gupta, 1999,
- "An elementary proof of the Johnson-Lindenstrauss Lemma."
- http://citeseer.ist.psu.edu/viewdoc/summary?doi=10.1.1.45.3654
-
"""
eps = np.asarray(eps)
n_samples = np.asarray(n_samples)
@@ -250,7 +249,7 @@ def _sparse_random_matrix(n_components, n_features, density="auto", random_state
https://web.stanford.edu/~hastie/Papers/Ping/KDD06_rp.pdf
.. [2] D. Achlioptas, 2001, "Database-friendly random projections",
- http://www.cs.ucsc.edu/~optas/papers/jl.pdf
+ https://cgi.di.uoa.gr/~optas/papers/jl.pdf
"""
_check_input_size(n_components, n_features)
@@ -710,7 +709,7 @@ class SparseRandomProjection(BaseRandomProjection):
https://web.stanford.edu/~hastie/Papers/Ping/KDD06_rp.pdf
.. [2] D. Achlioptas, 2001, "Database-friendly random projections",
- https://users.soe.ucsc.edu/~optas/papers/jl.pdf
+ https://cgi.di.uoa.gr/~optas/papers/jl.pdf
Examples
--------
diff --git a/sklearn/svm/_classes.py b/sklearn/svm/_classes.py
index 3cfbafce876ea..42e696875728d 100644
--- a/sklearn/svm/_classes.py
+++ b/sklearn/svm/_classes.py
@@ -1167,8 +1167,8 @@ class SVR(RegressorMixin, BaseLibSVM):
.. versionadded:: 1.1
- n_support_ : ndarray of shape (n_classes,), dtype=int32
- Number of support vectors for each class.
+ n_support_ : ndarray of shape (1,), dtype=int32
+ Number of support vectors.
shape_fit_ : tuple of int of shape (n_dimensions_of_X,)
Array dimensions of training vector ``X``.
@@ -1359,8 +1359,8 @@ class NuSVR(RegressorMixin, BaseLibSVM):
.. versionadded:: 1.1
- n_support_ : ndarray of shape (n_classes,), dtype=int32
- Number of support vectors for each class.
+ n_support_ : ndarray of shape (1,), dtype=int32
+ Number of support vectors.
shape_fit_ : tuple of int of shape (n_dimensions_of_X,)
Array dimensions of training vector ``X``.
diff --git a/sklearn/svm/tests/test_svm.py b/sklearn/svm/tests/test_svm.py
index db1d49ab4bcf9..dccd18217e710 100644
--- a/sklearn/svm/tests/test_svm.py
+++ b/sklearn/svm/tests/test_svm.py
@@ -4,7 +4,6 @@
TODO: remove hard coded numerical results when possible
"""
import numpy as np
-import itertools
import pytest
import re
@@ -828,36 +827,29 @@ def test_sparse_fit_support_vectors_empty():
assert not model.dual_coef_.data.size
-def test_linearsvc_parameters():
+@pytest.mark.parametrize("loss", ["hinge", "squared_hinge"])
+@pytest.mark.parametrize("penalty", ["l1", "l2"])
+@pytest.mark.parametrize("dual", [True, False])
+def test_linearsvc_parameters(loss, penalty, dual):
# Test possible parameter combinations in LinearSVC
# Generate list of possible parameter combinations
- losses = ["hinge", "squared_hinge", "logistic_regression", "foo"]
- penalties, duals = ["l1", "l2", "bar"], [True, False]
-
- X, y = make_classification(n_samples=5, n_features=5)
-
- for loss, penalty, dual in itertools.product(losses, penalties, duals):
- clf = svm.LinearSVC(penalty=penalty, loss=loss, dual=dual)
- if (
- (loss, penalty) == ("hinge", "l1")
- or (loss, penalty, dual) == ("hinge", "l2", False)
- or (penalty, dual) == ("l1", True)
- or loss == "foo"
- or penalty == "bar"
- ):
+ X, y = make_classification(n_samples=5, n_features=5, random_state=0)
- with pytest.raises(
- ValueError,
- match="Unsupported set of arguments.*penalty='%s.*loss='%s.*dual=%s"
- % (penalty, loss, dual),
- ):
- clf.fit(X, y)
- else:
- clf.fit(X, y)
+ clf = svm.LinearSVC(penalty=penalty, loss=loss, dual=dual, random_state=0)
+ if (
+ (loss, penalty) == ("hinge", "l1")
+ or (loss, penalty, dual) == ("hinge", "l2", False)
+ or (penalty, dual) == ("l1", True)
+ ):
- # Incorrect loss value - test if explicit error message is raised
- with pytest.raises(ValueError, match=".*loss='l3' is not supported.*"):
- svm.LinearSVC(loss="l3").fit(X, y)
+ with pytest.raises(
+ ValueError,
+ match="Unsupported set of arguments.*penalty='%s.*loss='%s.*dual=%s"
+ % (penalty, loss, dual),
+ ):
+ clf.fit(X, y)
+ else:
+ clf.fit(X, y)
def test_linear_svx_uppercase_loss_penality_raises_error():
@@ -1398,23 +1390,18 @@ def test_linearsvm_liblinear_sample_weight(SVM, params):
assert_allclose(X_est_no_weight, X_est_with_weight)
-def test_n_support_oneclass_svr():
+@pytest.mark.parametrize("Klass", (svm.OneClassSVM, svm.SVR, svm.NuSVR))
+def test_n_support(Klass):
# Make n_support is correct for oneclass and SVR (used to be
# non-initialized)
# this is a non regression test for issue #14774
X = np.array([[0], [0.44], [0.45], [0.46], [1]])
- clf = svm.OneClassSVM()
- assert not hasattr(clf, "n_support_")
- clf.fit(X)
- assert clf.n_support_ == clf.support_vectors_.shape[0]
- assert clf.n_support_.size == 1
- assert clf.n_support_ == 3
-
y = np.arange(X.shape[0])
- reg = svm.SVR().fit(X, y)
- assert reg.n_support_ == reg.support_vectors_.shape[0]
- assert reg.n_support_.size == 1
- assert reg.n_support_ == 4
+ est = Klass()
+ assert not hasattr(est, "n_support_")
+ est.fit(X, y)
+ assert est.n_support_[0] == est.support_vectors_.shape[0]
+ assert est.n_support_.size == 1
@pytest.mark.parametrize("Estimator", [svm.SVC, svm.SVR])
diff --git a/sklearn/tests/test_base.py b/sklearn/tests/test_base.py
index bdbe55c463841..3e1915c10af79 100644
--- a/sklearn/tests/test_base.py
+++ b/sklearn/tests/test_base.py
@@ -229,7 +229,7 @@ def test_str():
def test_get_params():
- test = T(K(), K())
+ test = T(K(), K)
assert "a__d" in test.get_params(deep=True)
assert "a__d" not in test.get_params(deep=False)
@@ -653,9 +653,9 @@ def transform(self, X):
"Feature names only support names that are all strings. "
"Got feature names with dtypes: ['int', 'str']"
)
- with pytest.warns(FutureWarning, match=msg) as record:
+ with pytest.warns(FutureWarning, match=msg):
trans.fit(df_mixed)
# transform on feature names that are mixed also warns:
- with pytest.warns(FutureWarning, match=msg) as record:
+ with pytest.warns(FutureWarning, match=msg):
trans.transform(df_mixed)
diff --git a/sklearn/tests/test_common.py b/sklearn/tests/test_common.py
index b5fc83d1028b3..922573f7e3fc8 100644
--- a/sklearn/tests/test_common.py
+++ b/sklearn/tests/test_common.py
@@ -18,6 +18,24 @@
import pytest
import numpy as np
+from sklearn.cluster import (
+ AffinityPropagation,
+ Birch,
+ MeanShift,
+ OPTICS,
+ SpectralClustering,
+)
+from sklearn.datasets import make_blobs
+from sklearn.manifold import Isomap, TSNE, LocallyLinearEmbedding
+from sklearn.neighbors import (
+ LocalOutlierFactor,
+ KNeighborsClassifier,
+ KNeighborsRegressor,
+ RadiusNeighborsClassifier,
+ RadiusNeighborsRegressor,
+)
+from sklearn.semi_supervised import LabelPropagation, LabelSpreading
+
from sklearn.utils import all_estimators
from sklearn.utils._testing import ignore_warnings
from sklearn.exceptions import ConvergenceWarning
@@ -442,3 +460,44 @@ def test_estimators_do_not_raise_errors_in_init_or_set_params(Estimator):
# Also do does not raise
est.set_params(**new_params)
+
+
+# TODO: remove this filter in 1.2
+@pytest.mark.filterwarnings("ignore::FutureWarning:sklearn")
+@pytest.mark.parametrize(
+ "Estimator",
+ [
+ AffinityPropagation,
+ Birch,
+ MeanShift,
+ KNeighborsClassifier,
+ KNeighborsRegressor,
+ RadiusNeighborsClassifier,
+ RadiusNeighborsRegressor,
+ LabelPropagation,
+ LabelSpreading,
+ OPTICS,
+ SpectralClustering,
+ LocalOutlierFactor,
+ LocallyLinearEmbedding,
+ Isomap,
+ TSNE,
+ ],
+)
+def test_f_contiguous_array_estimator(Estimator):
+ # Non-regression test for:
+ # https://github.com/scikit-learn/scikit-learn/issues/23988
+ # https://github.com/scikit-learn/scikit-learn/issues/24013
+
+ X, _ = make_blobs(n_samples=80, n_features=4, random_state=0)
+ X = np.asfortranarray(X)
+ y = np.round(X[:, 0])
+
+ est = Estimator()
+ est.fit(X, y)
+
+ if hasattr(est, "transform"):
+ est.transform(X)
+
+ if hasattr(est, "predict"):
+ est.predict(X)
diff --git a/sklearn/tests/test_docstring_parameters.py b/sklearn/tests/test_docstring_parameters.py
index 5e22b425be1ec..4503446823b2a 100644
--- a/sklearn/tests/test_docstring_parameters.py
+++ b/sklearn/tests/test_docstring_parameters.py
@@ -248,7 +248,7 @@ def test_fit_docstring_attributes(name, Estimator):
# FIXME: TO BE REMOVED for 1.2 (avoid FutureWarning)
if Estimator.__name__ == "TSNE":
- est.set_params(learning_rate=200.0, init="random")
+ est.set_params(learning_rate=200.0, init="random", perplexity=2)
# FIXME: TO BE REMOVED for 1.3 (avoid FutureWarning)
if Estimator.__name__ == "SequentialFeatureSelector":
diff --git a/sklearn/tests/test_docstrings.py b/sklearn/tests/test_docstrings.py
index 0131dae6c01a3..88d37ef7b232d 100644
--- a/sklearn/tests/test_docstrings.py
+++ b/sklearn/tests/test_docstrings.py
@@ -12,11 +12,7 @@
numpydoc_validation = pytest.importorskip("numpydoc.validate")
FUNCTION_DOCSTRING_IGNORE_LIST = [
- "sklearn.datasets._kddcup99.fetch_kddcup99",
- "sklearn.datasets._lfw.fetch_lfw_pairs",
"sklearn.datasets._lfw.fetch_lfw_people",
- "sklearn.datasets._samples_generator.make_gaussian_quantiles",
- "sklearn.datasets._samples_generator.make_spd_matrix",
"sklearn.datasets._species_distributions.fetch_species_distributions",
"sklearn.datasets._svmlight_format_io.load_svmlight_file",
"sklearn.datasets._svmlight_format_io.load_svmlight_files",
@@ -24,28 +20,17 @@
"sklearn.decomposition._dict_learning.dict_learning_online",
"sklearn.decomposition._nmf.non_negative_factorization",
"sklearn.externals._packaging.version.parse",
- "sklearn.feature_extraction.image.extract_patches_2d",
"sklearn.feature_extraction.text.strip_accents_unicode",
- "sklearn.feature_selection._univariate_selection.chi2",
- "sklearn.feature_selection._univariate_selection.f_oneway",
"sklearn.inspection._partial_dependence.partial_dependence",
"sklearn.inspection._plot.partial_dependence.plot_partial_dependence",
"sklearn.linear_model._least_angle.lars_path_gram",
"sklearn.linear_model._omp.orthogonal_mp_gram",
"sklearn.manifold._locally_linear.locally_linear_embedding",
"sklearn.manifold._t_sne.trustworthiness",
- "sklearn.metrics._classification.brier_score_loss",
- "sklearn.metrics._classification.cohen_kappa_score",
- "sklearn.metrics._classification.fbeta_score",
- "sklearn.metrics._classification.jaccard_score",
- "sklearn.metrics._classification.log_loss",
"sklearn.metrics._plot.det_curve.plot_det_curve",
"sklearn.metrics._plot.precision_recall_curve.plot_precision_recall_curve",
- "sklearn.metrics._ranking.auc",
"sklearn.metrics._ranking.coverage_error",
"sklearn.metrics._ranking.dcg_score",
- "sklearn.metrics._ranking.label_ranking_average_precision_score",
- "sklearn.metrics._ranking.roc_auc_score",
"sklearn.metrics._ranking.roc_curve",
"sklearn.metrics._ranking.top_k_accuracy_score",
"sklearn.metrics._regression.mean_pinball_loss",
@@ -54,37 +39,11 @@
"sklearn.metrics.cluster._supervised.adjusted_rand_score",
"sklearn.metrics.cluster._supervised.entropy",
"sklearn.metrics.cluster._supervised.fowlkes_mallows_score",
- "sklearn.metrics.cluster._supervised.homogeneity_completeness_v_measure",
- "sklearn.metrics.cluster._supervised.mutual_info_score",
- "sklearn.metrics.cluster._supervised.normalized_mutual_info_score",
- "sklearn.metrics.cluster._supervised.pair_confusion_matrix",
"sklearn.metrics.cluster._supervised.rand_score",
"sklearn.metrics.cluster._supervised.v_measure_score",
- "sklearn.metrics.pairwise.additive_chi2_kernel",
- "sklearn.metrics.pairwise.check_paired_arrays",
- "sklearn.metrics.pairwise.check_pairwise_arrays",
- "sklearn.metrics.pairwise.chi2_kernel",
- "sklearn.metrics.pairwise.cosine_distances",
- "sklearn.metrics.pairwise.cosine_similarity",
- "sklearn.metrics.pairwise.distance_metrics",
- "sklearn.metrics.pairwise.kernel_metrics",
- "sklearn.metrics.pairwise.paired_manhattan_distances",
- "sklearn.metrics.pairwise.pairwise_distances_argmin",
- "sklearn.metrics.pairwise.pairwise_distances_argmin_min",
"sklearn.metrics.pairwise.pairwise_distances_chunked",
- "sklearn.metrics.pairwise.pairwise_kernels",
- "sklearn.metrics.pairwise.polynomial_kernel",
- "sklearn.metrics.pairwise.rbf_kernel",
- "sklearn.metrics.pairwise.sigmoid_kernel",
- "sklearn.model_selection._validation.learning_curve",
- "sklearn.model_selection._validation.permutation_test_score",
- "sklearn.model_selection._validation.validation_curve",
- "sklearn.pipeline.make_union",
"sklearn.preprocessing._data.maxabs_scale",
- "sklearn.preprocessing._data.robust_scale",
"sklearn.preprocessing._data.scale",
- "sklearn.preprocessing._label.label_binarize",
- "sklearn.random_projection.johnson_lindenstrauss_min_dim",
"sklearn.svm._bounds.l1_min_c",
"sklearn.tree._export.plot_tree",
"sklearn.utils.axis0_safe_slice",
@@ -112,14 +71,12 @@
"sklearn.utils.multiclass.class_distribution",
"sklearn.utils.multiclass.type_of_target",
"sklearn.utils.multiclass.unique_labels",
- "sklearn.utils.resample",
"sklearn.utils.safe_mask",
"sklearn.utils.safe_sqr",
"sklearn.utils.shuffle",
"sklearn.utils.sparsefuncs.count_nonzero",
"sklearn.utils.sparsefuncs.csc_median_axis_0",
"sklearn.utils.sparsefuncs.incr_mean_variance_axis",
- "sklearn.utils.sparsefuncs.inplace_swap_column",
"sklearn.utils.sparsefuncs.inplace_swap_row",
"sklearn.utils.sparsefuncs.inplace_swap_row_csc",
"sklearn.utils.sparsefuncs.inplace_swap_row_csr",
@@ -144,7 +101,7 @@ def get_all_methods():
methods.append(name)
methods.append(None)
- for method in sorted(methods, key=lambda x: str(x)):
+ for method in sorted(methods, key=str):
yield Estimator, method
diff --git a/sklearn/tests/test_isotonic.py b/sklearn/tests/test_isotonic.py
index 5600cf8706e75..0b646439a3b41 100644
--- a/sklearn/tests/test_isotonic.py
+++ b/sklearn/tests/test_isotonic.py
@@ -336,7 +336,7 @@ def test_isotonic_regression_oob_raise():
ir.fit(x, y)
# Check that an exception is thrown
- msg = "A value in x_new is below the interpolation range"
+ msg = "in x_new is below the interpolation range"
with pytest.raises(ValueError, match=msg):
ir.predict([min(x) - 10, max(x) + 10])
diff --git a/sklearn/tree/_classes.py b/sklearn/tree/_classes.py
index 79257355a4150..c6e528c4f50f6 100644
--- a/sklearn/tree/_classes.py
+++ b/sklearn/tree/_classes.py
@@ -729,8 +729,8 @@ class DecisionTreeClassifier(ClassifierMixin, BaseDecisionTree):
- If int, then consider `max_features` features at each split.
- If float, then `max_features` is a fraction and
- `int(max_features * n_features)` features are considered at each
- split.
+ `max(1, int(max_features * n_features_in_))` features are considered at
+ each split.
- If "auto", then `max_features=sqrt(n_features)`.
- If "sqrt", then `max_features=sqrt(n_features)`.
- If "log2", then `max_features=log2(n_features)`.
@@ -1140,7 +1140,7 @@ class DecisionTreeRegressor(RegressorMixin, BaseDecisionTree):
- If int, then consider `max_features` features at each split.
- If float, then `max_features` is a fraction and
- `int(max_features * n_features)` features are considered at each
+ `max(1, int(max_features * n_features_in_))` features are considered at each
split.
- If "auto", then `max_features=n_features`.
- If "sqrt", then `max_features=sqrt(n_features)`.
@@ -1450,8 +1450,8 @@ class ExtraTreeClassifier(DecisionTreeClassifier):
- If int, then consider `max_features` features at each split.
- If float, then `max_features` is a fraction and
- `int(max_features * n_features)` features are considered at each
- split.
+ `max(1, int(max_features * n_features_in_))` features are considered at
+ each split.
- If "auto", then `max_features=sqrt(n_features)`.
- If "sqrt", then `max_features=sqrt(n_features)`.
- If "log2", then `max_features=log2(n_features)`.
@@ -1730,7 +1730,7 @@ class ExtraTreeRegressor(DecisionTreeRegressor):
- If int, then consider `max_features` features at each split.
- If float, then `max_features` is a fraction and
- `int(max_features * n_features)` features are considered at each
+ `max(1, int(max_features * n_features_in_))` features are considered at each
split.
- If "auto", then `max_features=n_features`.
- If "sqrt", then `max_features=sqrt(n_features)`.
diff --git a/sklearn/tree/_criterion.pxd b/sklearn/tree/_criterion.pxd
index 1639b5f4b3195..bc78c09b6ff5e 100644
--- a/sklearn/tree/_criterion.pxd
+++ b/sklearn/tree/_criterion.pxd
@@ -9,9 +9,6 @@
# See _criterion.pyx for implementation details.
-import numpy as np
-cimport numpy as np
-
from ._tree cimport DTYPE_t # Type of X
from ._tree cimport DOUBLE_t # Type of y, sample_weight
from ._tree cimport SIZE_t # Type for indices and counters
diff --git a/sklearn/tree/_criterion.pyx b/sklearn/tree/_criterion.pyx
index 57012fcab2296..680719464e11d 100644
--- a/sklearn/tree/_criterion.pyx
+++ b/sklearn/tree/_criterion.pyx
@@ -17,8 +17,8 @@ from libc.string cimport memset
from libc.math cimport fabs
import numpy as np
-cimport numpy as np
-np.import_array()
+cimport numpy as cnp
+cnp.import_array()
from numpy.math cimport INFINITY
from scipy.special.cython_special cimport xlogy
@@ -197,7 +197,7 @@ cdef class ClassificationCriterion(Criterion):
"""Abstract criterion for classification."""
def __cinit__(self, SIZE_t n_outputs,
- np.ndarray[SIZE_t, ndim=1] n_classes):
+ cnp.ndarray[SIZE_t, ndim=1] n_classes):
"""Initialize attributes for this criterion.
Parameters
@@ -874,8 +874,8 @@ cdef class MAE(RegressionCriterion):
MAE = (1 / n)*(\sum_i |y_i - f_i|), where y_i is the true
value and f_i is the predicted value."""
- cdef np.ndarray left_child
- cdef np.ndarray right_child
+ cdef cnp.ndarray left_child
+ cdef cnp.ndarray right_child
cdef DOUBLE_t[::1] node_medians
def __cinit__(self, SIZE_t n_outputs, SIZE_t n_samples):
diff --git a/sklearn/tree/_splitter.pxd b/sklearn/tree/_splitter.pxd
index cf01fed9cfd7d..1b899d2c66454 100644
--- a/sklearn/tree/_splitter.pxd
+++ b/sklearn/tree/_splitter.pxd
@@ -9,9 +9,6 @@
# See _splitter.pyx for details.
-import numpy as np
-cimport numpy as np
-
from ._criterion cimport Criterion
from ._tree cimport DTYPE_t # Type of X
@@ -46,13 +43,13 @@ cdef class Splitter:
cdef object random_state # Random state
cdef UINT32_t rand_r_state # sklearn_rand_r random number state
- cdef SIZE_t* samples # Sample indices in X, y
+ cdef SIZE_t[::1] samples # Sample indices in X, y
cdef SIZE_t n_samples # X.shape[0]
cdef double weighted_n_samples # Weighted number of samples
- cdef SIZE_t* features # Feature indices in X
- cdef SIZE_t* constant_features # Constant features indices
+ cdef SIZE_t[::1] features # Feature indices in X
+ cdef SIZE_t[::1] constant_features # Constant features indices
cdef SIZE_t n_features # X.shape[1]
- cdef DTYPE_t* feature_values # temp. array holding feature values
+ cdef DTYPE_t[::1] feature_values # temp. array holding feature values
cdef SIZE_t start # Start position for the current node
cdef SIZE_t end # End position for the current node
diff --git a/sklearn/tree/_splitter.pyx b/sklearn/tree/_splitter.pyx
index 514975380a8b5..76b502f98f144 100644
--- a/sklearn/tree/_splitter.pyx
+++ b/sklearn/tree/_splitter.pyx
@@ -19,8 +19,6 @@ from libc.string cimport memcpy
from libc.string cimport memset
import numpy as np
-cimport numpy as np
-np.import_array()
from scipy.sparse import csc_matrix
@@ -28,7 +26,6 @@ from ._utils cimport log
from ._utils cimport rand_int
from ._utils cimport rand_uniform
from ._utils cimport RAND_R_MAX
-from ._utils cimport safe_realloc
cdef double INFINITY = np.inf
@@ -82,11 +79,8 @@ cdef class Splitter:
self.criterion = criterion
- self.samples = NULL
self.n_samples = 0
- self.features = NULL
self.n_features = 0
- self.feature_values = NULL
self.sample_weight = NULL
@@ -95,14 +89,6 @@ cdef class Splitter:
self.min_weight_leaf = min_weight_leaf
self.random_state = random_state
- def __dealloc__(self):
- """Destructor."""
-
- free(self.samples)
- free(self.features)
- free(self.constant_features)
- free(self.feature_values)
-
def __getstate__(self):
return {}
@@ -139,7 +125,8 @@ cdef class Splitter:
# Create a new array which will be used to store nonzero
# samples from the feature of interest
- cdef SIZE_t* samples = safe_realloc(&self.samples, n_samples)
+ self.samples = np.empty(n_samples, dtype=np.intp)
+ cdef SIZE_t[::1] samples = self.samples
cdef SIZE_t i, j
cdef double weighted_n_samples = 0.0
@@ -161,15 +148,11 @@ cdef class Splitter:
self.weighted_n_samples = weighted_n_samples
cdef SIZE_t n_features = X.shape[1]
- cdef SIZE_t* features = safe_realloc(&self.features, n_features)
-
- for i in range(n_features):
- features[i] = i
-
+ self.features = np.arange(n_features, dtype=np.intp)
self.n_features = n_features
- safe_realloc(&self.feature_values, n_samples)
- safe_realloc(&self.constant_features, n_features)
+ self.feature_values = np.empty(n_samples, dtype=np.float32)
+ self.constant_features = np.empty(n_features, dtype=np.intp)
self.y = y
@@ -199,7 +182,7 @@ cdef class Splitter:
self.criterion.init(self.y,
self.sample_weight,
self.weighted_n_samples,
- self.samples,
+ &self.samples[0],
start,
end)
@@ -268,15 +251,15 @@ cdef class BestSplitter(BaseDenseSplitter):
or 0 otherwise.
"""
# Find the best split
- cdef SIZE_t* samples = self.samples
+ cdef SIZE_t[::1] samples = self.samples
cdef SIZE_t start = self.start
cdef SIZE_t end = self.end
- cdef SIZE_t* features = self.features
- cdef SIZE_t* constant_features = self.constant_features
+ cdef SIZE_t[::1] features = self.features
+ cdef SIZE_t[::1] constant_features = self.constant_features
cdef SIZE_t n_features = self.n_features
- cdef DTYPE_t* Xf = self.feature_values
+ cdef DTYPE_t[::1] Xf = self.feature_values
cdef SIZE_t max_features = self.max_features
cdef SIZE_t min_samples_leaf = self.min_samples_leaf
cdef double min_weight_leaf = self.min_weight_leaf
@@ -344,75 +327,78 @@ cdef class BestSplitter(BaseDenseSplitter):
features[n_drawn_constants], features[f_j] = features[f_j], features[n_drawn_constants]
n_drawn_constants += 1
+ continue
- else:
- # f_j in the interval [n_known_constants, f_i - n_found_constants[
- f_j += n_found_constants
- # f_j in the interval [n_total_constants, f_i[
- current.feature = features[f_j]
+ # f_j in the interval [n_known_constants, f_i - n_found_constants[
+ f_j += n_found_constants
+ # f_j in the interval [n_total_constants, f_i[
+ current.feature = features[f_j]
- # Sort samples along that feature; by
- # copying the values into an array and
- # sorting the array in a manner which utilizes the cache more
- # effectively.
- for i in range(start, end):
- Xf[i] = self.X[samples[i], current.feature]
+ # Sort samples along that feature; by
+ # copying the values into an array and
+ # sorting the array in a manner which utilizes the cache more
+ # effectively.
+ for i in range(start, end):
+ Xf[i] = self.X[samples[i], current.feature]
- sort(Xf + start, samples + start, end - start)
+ sort(&Xf[start], &samples[start], end - start)
- if Xf[end - 1] <= Xf[start] + FEATURE_THRESHOLD:
- features[f_j], features[n_total_constants] = features[n_total_constants], features[f_j]
+ if Xf[end - 1] <= Xf[start] + FEATURE_THRESHOLD:
+ features[f_j], features[n_total_constants] = features[n_total_constants], features[f_j]
- n_found_constants += 1
- n_total_constants += 1
+ n_found_constants += 1
+ n_total_constants += 1
+ continue
- else:
- f_i -= 1
- features[f_i], features[f_j] = features[f_j], features[f_i]
+ f_i -= 1
+ features[f_i], features[f_j] = features[f_j], features[f_i]
- # Evaluate all splits
- self.criterion.reset()
- p = start
+ # Evaluate all splits
+ self.criterion.reset()
+ p = start
- while p < end:
- while (p + 1 < end and
- Xf[p + 1] <= Xf[p] + FEATURE_THRESHOLD):
- p += 1
+ while p < end:
+ while p + 1 < end and Xf[p + 1] <= Xf[p] + FEATURE_THRESHOLD:
+ p += 1
- # (p + 1 >= end) or (X[samples[p + 1], current.feature] >
- # X[samples[p], current.feature])
- p += 1
- # (p >= end) or (X[samples[p], current.feature] >
- # X[samples[p - 1], current.feature])
+ # (p + 1 >= end) or (X[samples[p + 1], current.feature] >
+ # X[samples[p], current.feature])
+ p += 1
+ # (p >= end) or (X[samples[p], current.feature] >
+ # X[samples[p - 1], current.feature])
+
+ if p >= end:
+ continue
- if p < end:
- current.pos = p
+ current.pos = p
- # Reject if min_samples_leaf is not guaranteed
- if (((current.pos - start) < min_samples_leaf) or
- ((end - current.pos) < min_samples_leaf)):
- continue
+ # Reject if min_samples_leaf is not guaranteed
+ if (((current.pos - start) < min_samples_leaf) or
+ ((end - current.pos) < min_samples_leaf)):
+ continue
- self.criterion.update(current.pos)
+ self.criterion.update(current.pos)
- # Reject if min_weight_leaf is not satisfied
- if ((self.criterion.weighted_n_left < min_weight_leaf) or
- (self.criterion.weighted_n_right < min_weight_leaf)):
- continue
+ # Reject if min_weight_leaf is not satisfied
+ if ((self.criterion.weighted_n_left < min_weight_leaf) or
+ (self.criterion.weighted_n_right < min_weight_leaf)):
+ continue
- current_proxy_improvement = self.criterion.proxy_impurity_improvement()
+ current_proxy_improvement = self.criterion.proxy_impurity_improvement()
- if current_proxy_improvement > best_proxy_improvement:
- best_proxy_improvement = current_proxy_improvement
- # sum of halves is used to avoid infinite value
- current.threshold = Xf[p - 1] / 2.0 + Xf[p] / 2.0
+ if current_proxy_improvement > best_proxy_improvement:
+ best_proxy_improvement = current_proxy_improvement
+ # sum of halves is used to avoid infinite value
+ current.threshold = Xf[p - 1] / 2.0 + Xf[p] / 2.0
- if ((current.threshold == Xf[p]) or
- (current.threshold == INFINITY) or
- (current.threshold == -INFINITY)):
- current.threshold = Xf[p - 1]
+ if (
+ current.threshold == Xf[p] or
+ current.threshold == INFINITY or
+ current.threshold == -INFINITY
+ ):
+ current.threshold = Xf[p - 1]
- best = current # copy
+ best = current # copy
# Reorganize into samples[start:best.pos] + samples[best.pos:end]
if best.pos < end:
@@ -438,11 +424,11 @@ cdef class BestSplitter(BaseDenseSplitter):
# Respect invariant for constant features: the original order of
# element in features[:n_known_constants] must be preserved for sibling
# and child nodes
- memcpy(features, constant_features, sizeof(SIZE_t) * n_known_constants)
+ memcpy(&features[0], &constant_features[0], sizeof(SIZE_t) * n_known_constants)
# Copy newly found constant features
- memcpy(constant_features + n_known_constants,
- features + n_known_constants,
+ memcpy(&constant_features[n_known_constants],
+ &features[n_known_constants],
sizeof(SIZE_t) * n_found_constants)
# Return values
@@ -582,15 +568,15 @@ cdef class RandomSplitter(BaseDenseSplitter):
or 0 otherwise.
"""
# Draw random splits and pick the best
- cdef SIZE_t* samples = self.samples
+ cdef SIZE_t[::1] samples = self.samples
cdef SIZE_t start = self.start
cdef SIZE_t end = self.end
- cdef SIZE_t* features = self.features
- cdef SIZE_t* constant_features = self.constant_features
+ cdef SIZE_t[::1] features = self.features
+ cdef SIZE_t[::1] constant_features = self.constant_features
cdef SIZE_t n_features = self.n_features
- cdef DTYPE_t* Xf = self.feature_values
+ cdef DTYPE_t[::1] Xf = self.feature_values
cdef SIZE_t max_features = self.max_features
cdef SIZE_t min_samples_leaf = self.min_samples_leaf
cdef double min_weight_leaf = self.min_weight_leaf
@@ -654,78 +640,78 @@ cdef class RandomSplitter(BaseDenseSplitter):
# f_j in the interval [n_drawn_constants, n_known_constants[
features[n_drawn_constants], features[f_j] = features[f_j], features[n_drawn_constants]
n_drawn_constants += 1
+ continue
- else:
- # f_j in the interval [n_known_constants, f_i - n_found_constants[
- f_j += n_found_constants
- # f_j in the interval [n_total_constants, f_i[
+ # f_j in the interval [n_known_constants, f_i - n_found_constants[
+ f_j += n_found_constants
+ # f_j in the interval [n_total_constants, f_i[
- current.feature = features[f_j]
+ current.feature = features[f_j]
- # Find min, max
- min_feature_value = self.X[samples[start], current.feature]
- max_feature_value = min_feature_value
- Xf[start] = min_feature_value
+ # Find min, max
+ min_feature_value = self.X[samples[start], current.feature]
+ max_feature_value = min_feature_value
+ Xf[start] = min_feature_value
- for p in range(start + 1, end):
- current_feature_value = self.X[samples[p], current.feature]
- Xf[p] = current_feature_value
+ for p in range(start + 1, end):
+ current_feature_value = self.X[samples[p], current.feature]
+ Xf[p] = current_feature_value
- if current_feature_value < min_feature_value:
- min_feature_value = current_feature_value
- elif current_feature_value > max_feature_value:
- max_feature_value = current_feature_value
+ if current_feature_value < min_feature_value:
+ min_feature_value = current_feature_value
+ elif current_feature_value > max_feature_value:
+ max_feature_value = current_feature_value
- if max_feature_value <= min_feature_value + FEATURE_THRESHOLD:
- features[f_j], features[n_total_constants] = features[n_total_constants], current.feature
+ if max_feature_value <= min_feature_value + FEATURE_THRESHOLD:
+ features[f_j], features[n_total_constants] = features[n_total_constants], current.feature
- n_found_constants += 1
- n_total_constants += 1
+ n_found_constants += 1
+ n_total_constants += 1
+ continue
- else:
- f_i -= 1
- features[f_i], features[f_j] = features[f_j], features[f_i]
+ f_i -= 1
+ features[f_i], features[f_j] = features[f_j], features[f_i]
- # Draw a random threshold
- current.threshold = rand_uniform(min_feature_value,
- max_feature_value,
- random_state)
+ # Draw a random threshold
+ current.threshold = rand_uniform(min_feature_value,
+ max_feature_value,
+ random_state)
- if current.threshold == max_feature_value:
- current.threshold = min_feature_value
+ if current.threshold == max_feature_value:
+ current.threshold = min_feature_value
- # Partition
- p, partition_end = start, end
- while p < partition_end:
- if Xf[p] <= current.threshold:
- p += 1
- else:
- partition_end -= 1
+ # Partition
+ p, partition_end = start, end
+ while p < partition_end:
+ if Xf[p] <= current.threshold:
+ p += 1
+ else:
+ partition_end -= 1
- Xf[p], Xf[partition_end] = Xf[partition_end], Xf[p]
- samples[p], samples[partition_end] = samples[partition_end], samples[p]
+ Xf[p], Xf[partition_end] = Xf[partition_end], Xf[p]
+ samples[p], samples[partition_end] = samples[partition_end], samples[p]
- current.pos = partition_end
+ current.pos = partition_end
- # Reject if min_samples_leaf is not guaranteed
- if (((current.pos - start) < min_samples_leaf) or
- ((end - current.pos) < min_samples_leaf)):
- continue
+ # Reject if min_samples_leaf is not guaranteed
+ if (((current.pos - start) < min_samples_leaf) or
+ ((end - current.pos) < min_samples_leaf)):
+ continue
- # Evaluate split
- self.criterion.reset()
- self.criterion.update(current.pos)
+ # Evaluate split
+ self.criterion.reset()
+ self.criterion.update(current.pos)
- # Reject if min_weight_leaf is not satisfied
- if ((self.criterion.weighted_n_left < min_weight_leaf) or
- (self.criterion.weighted_n_right < min_weight_leaf)):
- continue
+ # Reject if min_weight_leaf is not satisfied
+ if ((self.criterion.weighted_n_left < min_weight_leaf) or
+ (self.criterion.weighted_n_right < min_weight_leaf)):
+ continue
- current_proxy_improvement = self.criterion.proxy_impurity_improvement()
+ current_proxy_improvement = self.criterion.proxy_impurity_improvement()
- if current_proxy_improvement > best_proxy_improvement:
- best_proxy_improvement = current_proxy_improvement
- best = current # copy
+ if current_proxy_improvement > best_proxy_improvement:
+ best_proxy_improvement = current_proxy_improvement
+ best = current # copy
# Reorganize into samples[start:best.pos] + samples[best.pos:end]
if best.pos < end:
@@ -750,11 +736,11 @@ cdef class RandomSplitter(BaseDenseSplitter):
# Respect invariant for constant features: the original order of
# element in features[:n_known_constants] must be preserved for sibling
# and child nodes
- memcpy(features, constant_features, sizeof(SIZE_t) * n_known_constants)
+ memcpy(&features[0], &constant_features[0], sizeof(SIZE_t) * n_known_constants)
# Copy newly found constant features
- memcpy(constant_features + n_known_constants,
- features + n_known_constants,
+ memcpy(&constant_features[n_known_constants],
+ &features[n_known_constants],
sizeof(SIZE_t) * n_found_constants)
# Return values
@@ -765,34 +751,21 @@ cdef class RandomSplitter(BaseDenseSplitter):
cdef class BaseSparseSplitter(Splitter):
# The sparse splitter works only with csc sparse matrix format
- cdef DTYPE_t* X_data
- cdef INT32_t* X_indices
- cdef INT32_t* X_indptr
+ cdef DTYPE_t[::1] X_data
+ cdef INT32_t[::1] X_indices
+ cdef INT32_t[::1] X_indptr
cdef SIZE_t n_total_samples
- cdef SIZE_t* index_to_samples
- cdef SIZE_t* sorted_samples
+ cdef SIZE_t[::1] index_to_samples
+ cdef SIZE_t[::1] sorted_samples
def __cinit__(self, Criterion criterion, SIZE_t max_features,
SIZE_t min_samples_leaf, double min_weight_leaf,
object random_state):
# Parent __cinit__ is automatically called
-
- self.X_data = NULL
- self.X_indices = NULL
- self.X_indptr = NULL
-
self.n_total_samples = 0
- self.index_to_samples = NULL
- self.sorted_samples = NULL
-
- def __dealloc__(self):
- """Deallocate memory."""
- free(self.index_to_samples)
- free(self.sorted_samples)
-
cdef int init(self,
object X,
const DOUBLE_t[:, ::1] y,
@@ -808,31 +781,24 @@ cdef class BaseSparseSplitter(Splitter):
if not isinstance(X, csc_matrix):
raise ValueError("X should be in csc format")
- cdef SIZE_t* samples = self.samples
+ cdef SIZE_t[::1] samples = self.samples
cdef SIZE_t n_samples = self.n_samples
# Initialize X
- cdef np.ndarray[dtype=DTYPE_t, ndim=1] data = X.data
- cdef np.ndarray[dtype=INT32_t, ndim=1] indices = X.indices
- cdef np.ndarray[dtype=INT32_t, ndim=1] indptr = X.indptr
cdef SIZE_t n_total_samples = X.shape[0]
- self.X_data = data.data
- self.X_indices = indices.data
- self.X_indptr = indptr.data
+ self.X_data = X.data
+ self.X_indices = X.indices
+ self.X_indptr = X.indptr
self.n_total_samples = n_total_samples
# Initialize auxiliary array used to perform split
- safe_realloc(&self.index_to_samples, n_total_samples)
- safe_realloc(&self.sorted_samples, n_samples)
+ self.index_to_samples = np.full(n_total_samples, fill_value=-1, dtype=np.intp)
+ self.sorted_samples = np.empty(n_samples, dtype=np.intp)
- cdef SIZE_t* index_to_samples = self.index_to_samples
cdef SIZE_t p
- for p in range(n_total_samples):
- index_to_samples[p] = -1
-
for p in range(n_samples):
- index_to_samples[samples[p]] = p
+ self.index_to_samples[samples[p]] = p
return 0
cdef inline SIZE_t _partition(self, double threshold,
@@ -843,9 +809,9 @@ cdef class BaseSparseSplitter(Splitter):
cdef SIZE_t p
cdef SIZE_t partition_end
- cdef DTYPE_t* Xf = self.feature_values
- cdef SIZE_t* samples = self.samples
- cdef SIZE_t* index_to_samples = self.index_to_samples
+ cdef DTYPE_t[::1] Xf = self.feature_values
+ cdef SIZE_t[::1] samples = self.samples
+ cdef SIZE_t[::1] index_to_samples = self.index_to_samples
if threshold < 0.:
p = self.start
@@ -905,6 +871,12 @@ cdef class BaseSparseSplitter(Splitter):
cdef SIZE_t indptr_end = self.X_indptr[feature + 1]
cdef SIZE_t n_indices = (indptr_end - indptr_start)
cdef SIZE_t n_samples = self.end - self.start
+ cdef SIZE_t[::1] samples = self.samples
+ cdef DTYPE_t[::1] feature_values = self.feature_values
+ cdef SIZE_t[::1] index_to_samples = self.index_to_samples
+ cdef SIZE_t[::1] sorted_samples = self.sorted_samples
+ cdef INT32_t[::1] X_indices = self.X_indices
+ cdef DTYPE_t[::1] X_data = self.X_data
# Use binary search if n_samples * log(n_indices) <
# n_indices and index_to_samples approach otherwise.
@@ -913,22 +885,22 @@ cdef class BaseSparseSplitter(Splitter):
# approach.
if ((1 - is_samples_sorted[0]) * n_samples * log(n_samples) +
n_samples * log(n_indices) < EXTRACT_NNZ_SWITCH * n_indices):
- extract_nnz_binary_search(self.X_indices, self.X_data,
+ extract_nnz_binary_search(X_indices, X_data,
indptr_start, indptr_end,
- self.samples, self.start, self.end,
- self.index_to_samples,
- self.feature_values,
+ samples, self.start, self.end,
+ index_to_samples,
+ feature_values,
end_negative, start_positive,
- self.sorted_samples, is_samples_sorted)
+ sorted_samples, is_samples_sorted)
# Using an index to samples technique to extract non zero values
# index_to_samples is a mapping from X_indices to samples
else:
- extract_nnz_index_to_samples(self.X_indices, self.X_data,
+ extract_nnz_index_to_samples(X_indices, X_data,
indptr_start, indptr_end,
- self.samples, self.start, self.end,
- self.index_to_samples,
- self.feature_values,
+ samples, self.start, self.end,
+ index_to_samples,
+ feature_values,
end_negative, start_positive)
@@ -937,7 +909,7 @@ cdef int compare_SIZE_t(const void* a, const void* b) nogil:
return ((a)[0] - (b)[0])
-cdef inline void binary_search(INT32_t* sorted_array,
+cdef inline void binary_search(INT32_t[::1] sorted_array,
INT32_t start, INT32_t end,
SIZE_t value, SIZE_t* index,
INT32_t* new_start) nogil:
@@ -962,15 +934,15 @@ cdef inline void binary_search(INT32_t* sorted_array,
new_start[0] = start
-cdef inline void extract_nnz_index_to_samples(INT32_t* X_indices,
- DTYPE_t* X_data,
+cdef inline void extract_nnz_index_to_samples(INT32_t[::1] X_indices,
+ DTYPE_t[::1] X_data,
INT32_t indptr_start,
INT32_t indptr_end,
- SIZE_t* samples,
+ SIZE_t[::1] samples,
SIZE_t start,
SIZE_t end,
- SIZE_t* index_to_samples,
- DTYPE_t* Xf,
+ SIZE_t[::1] index_to_samples,
+ DTYPE_t[::1] Xf,
SIZE_t* end_negative,
SIZE_t* start_positive) nogil:
"""Extract and partition values for a feature using index_to_samples.
@@ -1002,18 +974,18 @@ cdef inline void extract_nnz_index_to_samples(INT32_t* X_indices,
start_positive[0] = start_positive_
-cdef inline void extract_nnz_binary_search(INT32_t* X_indices,
- DTYPE_t* X_data,
+cdef inline void extract_nnz_binary_search(INT32_t[::1] X_indices,
+ DTYPE_t[::1] X_data,
INT32_t indptr_start,
INT32_t indptr_end,
- SIZE_t* samples,
+ SIZE_t[::1] samples,
SIZE_t start,
SIZE_t end,
- SIZE_t* index_to_samples,
- DTYPE_t* Xf,
+ SIZE_t[::1] index_to_samples,
+ DTYPE_t[::1] Xf,
SIZE_t* end_negative,
SIZE_t* start_positive,
- SIZE_t* sorted_samples,
+ SIZE_t[::1] sorted_samples,
bint* is_samples_sorted) nogil:
"""Extract and partition values for a given feature using binary search.
@@ -1027,9 +999,9 @@ cdef inline void extract_nnz_binary_search(INT32_t* X_indices,
if not is_samples_sorted[0]:
n_samples = end - start
- memcpy(sorted_samples + start, samples + start,
+ memcpy(&sorted_samples[start], &samples[start],
n_samples * sizeof(SIZE_t))
- qsort(sorted_samples + start, n_samples, sizeof(SIZE_t),
+ qsort(&sorted_samples[start], n_samples, sizeof(SIZE_t),
compare_SIZE_t)
is_samples_sorted[0] = 1
@@ -1074,7 +1046,7 @@ cdef inline void extract_nnz_binary_search(INT32_t* X_indices,
start_positive[0] = start_positive_
-cdef inline void sparse_swap(SIZE_t* index_to_samples, SIZE_t* samples,
+cdef inline void sparse_swap(SIZE_t[::1] index_to_samples, SIZE_t[::1] samples,
SIZE_t pos_1, SIZE_t pos_2) nogil:
"""Swap sample pos_1 and pos_2 preserving sparse invariant."""
samples[pos_1], samples[pos_2] = samples[pos_2], samples[pos_1]
@@ -1100,21 +1072,16 @@ cdef class BestSparseSplitter(BaseSparseSplitter):
or 0 otherwise.
"""
# Find the best split
- cdef SIZE_t* samples = self.samples
+ cdef SIZE_t[::1] samples = self.samples
cdef SIZE_t start = self.start
cdef SIZE_t end = self.end
- cdef INT32_t* X_indices = self.X_indices
- cdef INT32_t* X_indptr = self.X_indptr
- cdef DTYPE_t* X_data = self.X_data
-
- cdef SIZE_t* features = self.features
- cdef SIZE_t* constant_features = self.constant_features
+ cdef SIZE_t[::1] features = self.features
+ cdef SIZE_t[::1] constant_features = self.constant_features
cdef SIZE_t n_features = self.n_features
- cdef DTYPE_t* Xf = self.feature_values
- cdef SIZE_t* sorted_samples = self.sorted_samples
- cdef SIZE_t* index_to_samples = self.index_to_samples
+ cdef DTYPE_t[::1] Xf = self.feature_values
+ cdef SIZE_t[::1] index_to_samples = self.index_to_samples
cdef SIZE_t max_features = self.max_features
cdef SIZE_t min_samples_leaf = self.min_samples_leaf
cdef double min_weight_leaf = self.min_weight_leaf
@@ -1184,101 +1151,104 @@ cdef class BestSparseSplitter(BaseSparseSplitter):
features[f_j], features[n_drawn_constants] = features[n_drawn_constants], features[f_j]
n_drawn_constants += 1
+ continue
- else:
- # f_j in the interval [n_known_constants, f_i - n_found_constants[
- f_j += n_found_constants
- # f_j in the interval [n_total_constants, f_i[
-
- current.feature = features[f_j]
- self.extract_nnz(current.feature, &end_negative, &start_positive,
- &is_samples_sorted)
- # Sort the positive and negative parts of `Xf`
- sort(Xf + start, samples + start, end_negative - start)
- if start_positive < end:
- sort(Xf + start_positive, samples + start_positive,
- end - start_positive)
-
- # Update index_to_samples to take into account the sort
- for p in range(start, end_negative):
- index_to_samples[samples[p]] = p
- for p in range(start_positive, end):
- index_to_samples[samples[p]] = p
-
- # Add one or two zeros in Xf, if there is any
- if end_negative < start_positive:
- start_positive -= 1
- Xf[start_positive] = 0.
-
- if end_negative != start_positive:
- Xf[end_negative] = 0.
- end_negative += 1
-
- if Xf[end - 1] <= Xf[start] + FEATURE_THRESHOLD:
- features[f_j], features[n_total_constants] = features[n_total_constants], features[f_j]
-
- n_found_constants += 1
- n_total_constants += 1
+ # f_j in the interval [n_known_constants, f_i - n_found_constants[
+ f_j += n_found_constants
+ # f_j in the interval [n_total_constants, f_i[
- else:
- f_i -= 1
- features[f_i], features[f_j] = features[f_j], features[f_i]
-
- # Evaluate all splits
- self.criterion.reset()
- p = start
+ current.feature = features[f_j]
+ self.extract_nnz(current.feature, &end_negative, &start_positive,
+ &is_samples_sorted)
+ # Sort the positive and negative parts of `Xf`
+ sort(&Xf[start], &samples[start], end_negative - start)
+ if start_positive < end:
+ sort(&Xf[start_positive], &samples[start_positive],
+ end - start_positive)
+
+ # Update index_to_samples to take into account the sort
+ for p in range(start, end_negative):
+ index_to_samples[samples[p]] = p
+ for p in range(start_positive, end):
+ index_to_samples[samples[p]] = p
+
+ # Add one or two zeros in Xf, if there is any
+ if end_negative < start_positive:
+ start_positive -= 1
+ Xf[start_positive] = 0.
+
+ if end_negative != start_positive:
+ Xf[end_negative] = 0.
+ end_negative += 1
+
+ if Xf[end - 1] <= Xf[start] + FEATURE_THRESHOLD:
+ features[f_j], features[n_total_constants] = features[n_total_constants], features[f_j]
+
+ n_found_constants += 1
+ n_total_constants += 1
+ continue
+
+ f_i -= 1
+ features[f_i], features[f_j] = features[f_j], features[f_i]
+
+ # Evaluate all splits
+ self.criterion.reset()
+ p = start
- while p < end:
- if p + 1 != end_negative:
- p_next = p + 1
- else:
- p_next = start_positive
+ while p < end:
+ if p + 1 != end_negative:
+ p_next = p + 1
+ else:
+ p_next = start_positive
- while (p_next < end and
- Xf[p_next] <= Xf[p] + FEATURE_THRESHOLD):
- p = p_next
- if p + 1 != end_negative:
- p_next = p + 1
- else:
- p_next = start_positive
+ while (p_next < end and
+ Xf[p_next] <= Xf[p] + FEATURE_THRESHOLD):
+ p = p_next
+ if p + 1 != end_negative:
+ p_next = p + 1
+ else:
+ p_next = start_positive
- # (p_next >= end) or (X[samples[p_next], current.feature] >
- # X[samples[p], current.feature])
- p_prev = p
- p = p_next
- # (p >= end) or (X[samples[p], current.feature] >
- # X[samples[p_prev], current.feature])
+ # (p_next >= end) or (X[samples[p_next], current.feature] >
+ # X[samples[p], current.feature])
+ p_prev = p
+ p = p_next
+ # (p >= end) or (X[samples[p], current.feature] >
+ # X[samples[p_prev], current.feature])
+ if p >= end:
+ continue
- if p < end:
- current.pos = p
+ current.pos = p
- # Reject if min_samples_leaf is not guaranteed
- if (((current.pos - start) < min_samples_leaf) or
- ((end - current.pos) < min_samples_leaf)):
- continue
+ # Reject if min_samples_leaf is not guaranteed
+ if (((current.pos - start) < min_samples_leaf) or
+ ((end - current.pos) < min_samples_leaf)):
+ continue
- self.criterion.update(current.pos)
+ self.criterion.update(current.pos)
- # Reject if min_weight_leaf is not satisfied
- if ((self.criterion.weighted_n_left < min_weight_leaf) or
- (self.criterion.weighted_n_right < min_weight_leaf)):
- continue
+ # Reject if min_weight_leaf is not satisfied
+ if ((self.criterion.weighted_n_left < min_weight_leaf) or
+ (self.criterion.weighted_n_right < min_weight_leaf)):
+ continue
- current_proxy_improvement = self.criterion.proxy_impurity_improvement()
+ current_proxy_improvement = self.criterion.proxy_impurity_improvement()
- if current_proxy_improvement > best_proxy_improvement:
- best_proxy_improvement = current_proxy_improvement
- # sum of halves used to avoid infinite values
- current.threshold = Xf[p_prev] / 2.0 + Xf[p] / 2.0
+ if current_proxy_improvement > best_proxy_improvement:
+ best_proxy_improvement = current_proxy_improvement
+ # sum of halves used to avoid infinite values
+ current.threshold = Xf[p_prev] / 2.0 + Xf[p] / 2.0
- if ((current.threshold == Xf[p]) or
- (current.threshold == INFINITY) or
- (current.threshold == -INFINITY)):
- current.threshold = Xf[p_prev]
+ if (
+ current.threshold == Xf[p] or
+ current.threshold == INFINITY or
+ current.threshold == -INFINITY
+ ):
+ current.threshold = Xf[p_prev]
- best = current
+ best = current
# Reorganize into samples[start:best.pos] + samples[best.pos:end]
if best.pos < end:
@@ -1298,11 +1268,11 @@ cdef class BestSparseSplitter(BaseSparseSplitter):
# Respect invariant for constant features: the original order of
# element in features[:n_known_constants] must be preserved for sibling
# and child nodes
- memcpy(features, constant_features, sizeof(SIZE_t) * n_known_constants)
+ memcpy(&features[0], &constant_features[0], sizeof(SIZE_t) * n_known_constants)
# Copy newly found constant features
- memcpy(constant_features + n_known_constants,
- features + n_known_constants,
+ memcpy(&constant_features[n_known_constants],
+ &features[n_known_constants],
sizeof(SIZE_t) * n_found_constants)
# Return values
@@ -1329,21 +1299,16 @@ cdef class RandomSparseSplitter(BaseSparseSplitter):
or 0 otherwise.
"""
# Find the best split
- cdef SIZE_t* samples = self.samples
+ cdef SIZE_t[::1] samples = self.samples
cdef SIZE_t start = self.start
cdef SIZE_t end = self.end
- cdef INT32_t* X_indices = self.X_indices
- cdef INT32_t* X_indptr = self.X_indptr
- cdef DTYPE_t* X_data = self.X_data
-
- cdef SIZE_t* features = self.features
- cdef SIZE_t* constant_features = self.constant_features
+ cdef SIZE_t[::1] features = self.features
+ cdef SIZE_t[::1] constant_features = self.constant_features
cdef SIZE_t n_features = self.n_features
- cdef DTYPE_t* Xf = self.feature_values
- cdef SIZE_t* sorted_samples = self.sorted_samples
- cdef SIZE_t* index_to_samples = self.index_to_samples
+ cdef DTYPE_t[::1] Xf = self.feature_values
+ cdef SIZE_t[::1] index_to_samples = self.index_to_samples
cdef SIZE_t max_features = self.max_features
cdef SIZE_t min_samples_leaf = self.min_samples_leaf
cdef double min_weight_leaf = self.min_weight_leaf
@@ -1416,97 +1381,92 @@ cdef class RandomSparseSplitter(BaseSparseSplitter):
features[f_j], features[n_drawn_constants] = features[n_drawn_constants], features[f_j]
n_drawn_constants += 1
+ continue
+
+ # f_j in the interval [n_known_constants, f_i - n_found_constants[
+ f_j += n_found_constants
+ # f_j in the interval [n_total_constants, f_i[
+
+ current.feature = features[f_j]
+ self.extract_nnz(current.feature,
+ &end_negative, &start_positive,
+ &is_samples_sorted)
+
+ if end_negative != start_positive:
+ # There is a zero
+ min_feature_value = 0
+ max_feature_value = 0
else:
- # f_j in the interval [n_known_constants, f_i - n_found_constants[
- f_j += n_found_constants
- # f_j in the interval [n_total_constants, f_i[
+ min_feature_value = Xf[start]
+ max_feature_value = min_feature_value
- current.feature = features[f_j]
+ # Find min, max in Xf[start:end_negative]
+ for p in range(start, end_negative):
+ current_feature_value = Xf[p]
- self.extract_nnz(current.feature,
- &end_negative, &start_positive,
- &is_samples_sorted)
+ if current_feature_value < min_feature_value:
+ min_feature_value = current_feature_value
+ elif current_feature_value > max_feature_value:
+ max_feature_value = current_feature_value
- # Add one or two zeros in Xf, if there is any
- if end_negative < start_positive:
- start_positive -= 1
- Xf[start_positive] = 0.
+ # Update min, max given Xf[start_positive:end]
+ for p in range(start_positive, end):
+ current_feature_value = Xf[p]
- if end_negative != start_positive:
- Xf[end_negative] = 0.
- end_negative += 1
+ if current_feature_value < min_feature_value:
+ min_feature_value = current_feature_value
+ elif current_feature_value > max_feature_value:
+ max_feature_value = current_feature_value
- # Find min, max in Xf[start:end_negative]
- min_feature_value = Xf[start]
- max_feature_value = min_feature_value
+ if max_feature_value <= min_feature_value + FEATURE_THRESHOLD:
+ features[f_j] = features[n_total_constants]
+ features[n_total_constants] = current.feature
- for p in range(start, end_negative):
- current_feature_value = Xf[p]
+ n_found_constants += 1
+ n_total_constants += 1
+ continue
- if current_feature_value < min_feature_value:
- min_feature_value = current_feature_value
- elif current_feature_value > max_feature_value:
- max_feature_value = current_feature_value
+ f_i -= 1
+ features[f_i], features[f_j] = features[f_j], features[f_i]
- # Update min, max given Xf[start_positive:end]
- for p in range(start_positive, end):
- current_feature_value = Xf[p]
+ # Draw a random threshold
+ current.threshold = rand_uniform(min_feature_value,
+ max_feature_value,
+ random_state)
- if current_feature_value < min_feature_value:
- min_feature_value = current_feature_value
- elif current_feature_value > max_feature_value:
- max_feature_value = current_feature_value
+ if current.threshold == max_feature_value:
+ current.threshold = min_feature_value
- if max_feature_value <= min_feature_value + FEATURE_THRESHOLD:
- features[f_j] = features[n_total_constants]
- features[n_total_constants] = current.feature
+ # Partition
+ current.pos = self._partition(current.threshold,
+ end_negative,
+ start_positive,
+ start_positive)
- n_found_constants += 1
- n_total_constants += 1
+ # Reject if min_samples_leaf is not guaranteed
+ if (((current.pos - start) < min_samples_leaf) or
+ ((end - current.pos) < min_samples_leaf)):
+ continue
- else:
- f_i -= 1
- features[f_i], features[f_j] = features[f_j], features[f_i]
-
- # Draw a random threshold
- current.threshold = rand_uniform(min_feature_value,
- max_feature_value,
- random_state)
-
- if current.threshold == max_feature_value:
- current.threshold = min_feature_value
-
- # Partition
- current.pos = self._partition(current.threshold,
- end_negative,
- start_positive,
- start_positive +
- (Xf[start_positive] == 0.))
-
- # Reject if min_samples_leaf is not guaranteed
- if (((current.pos - start) < min_samples_leaf) or
- ((end - current.pos) < min_samples_leaf)):
- continue
-
- # Evaluate split
- self.criterion.reset()
- self.criterion.update(current.pos)
-
- # Reject if min_weight_leaf is not satisfied
- if ((self.criterion.weighted_n_left < min_weight_leaf) or
- (self.criterion.weighted_n_right < min_weight_leaf)):
- continue
-
- current_proxy_improvement = self.criterion.proxy_impurity_improvement()
-
- if current_proxy_improvement > best_proxy_improvement:
- best_proxy_improvement = current_proxy_improvement
- self.criterion.children_impurity(¤t.impurity_left,
- ¤t.impurity_right)
- current.improvement = self.criterion.impurity_improvement(
- impurity, current.impurity_left, current.impurity_right)
- best = current
+ # Evaluate split
+ self.criterion.reset()
+ self.criterion.update(current.pos)
+
+ # Reject if min_weight_leaf is not satisfied
+ if ((self.criterion.weighted_n_left < min_weight_leaf) or
+ (self.criterion.weighted_n_right < min_weight_leaf)):
+ continue
+
+ current_proxy_improvement = self.criterion.proxy_impurity_improvement()
+
+ if current_proxy_improvement > best_proxy_improvement:
+ best_proxy_improvement = current_proxy_improvement
+ self.criterion.children_impurity(¤t.impurity_left,
+ ¤t.impurity_right)
+ current.improvement = self.criterion.impurity_improvement(
+ impurity, current.impurity_left, current.impurity_right)
+ best = current
# Reorganize into samples[start:best.pos] + samples[best.pos:end]
if best.pos < end:
@@ -1527,11 +1487,11 @@ cdef class RandomSparseSplitter(BaseSparseSplitter):
# Respect invariant for constant features: the original order of
# element in features[:n_known_constants] must be preserved for sibling
# and child nodes
- memcpy(features, constant_features, sizeof(SIZE_t) * n_known_constants)
+ memcpy(&features[0], &constant_features[0], sizeof(SIZE_t) * n_known_constants)
# Copy newly found constant features
- memcpy(constant_features + n_known_constants,
- features + n_known_constants,
+ memcpy(&constant_features[n_known_constants],
+ &features[n_known_constants],
sizeof(SIZE_t) * n_found_constants)
# Return values
diff --git a/sklearn/tree/_tree.pxd b/sklearn/tree/_tree.pxd
index 0874187ee98ae..55895a8279828 100644
--- a/sklearn/tree/_tree.pxd
+++ b/sklearn/tree/_tree.pxd
@@ -11,13 +11,13 @@
# See _tree.pyx for details.
import numpy as np
-cimport numpy as np
+cimport numpy as cnp
-ctypedef np.npy_float32 DTYPE_t # Type of X
-ctypedef np.npy_float64 DOUBLE_t # Type of y, sample_weight
-ctypedef np.npy_intp SIZE_t # Type for indices and counters
-ctypedef np.npy_int32 INT32_t # Signed 32 bit integer
-ctypedef np.npy_uint32 UINT32_t # Unsigned 32 bit integer
+ctypedef cnp.npy_float32 DTYPE_t # Type of X
+ctypedef cnp.npy_float64 DOUBLE_t # Type of y, sample_weight
+ctypedef cnp.npy_intp SIZE_t # Type for indices and counters
+ctypedef cnp.npy_int32 INT32_t # Signed 32 bit integer
+ctypedef cnp.npy_uint32 UINT32_t # Unsigned 32 bit integer
from ._splitter cimport Splitter
from ._splitter cimport SplitRecord
@@ -62,14 +62,14 @@ cdef class Tree:
cdef int _resize(self, SIZE_t capacity) nogil except -1
cdef int _resize_c(self, SIZE_t capacity=*) nogil except -1
- cdef np.ndarray _get_value_ndarray(self)
- cdef np.ndarray _get_node_ndarray(self)
+ cdef cnp.ndarray _get_value_ndarray(self)
+ cdef cnp.ndarray _get_node_ndarray(self)
- cpdef np.ndarray predict(self, object X)
+ cpdef cnp.ndarray predict(self, object X)
- cpdef np.ndarray apply(self, object X)
- cdef np.ndarray _apply_dense(self, object X)
- cdef np.ndarray _apply_sparse_csr(self, object X)
+ cpdef cnp.ndarray apply(self, object X)
+ cdef cnp.ndarray _apply_dense(self, object X)
+ cdef cnp.ndarray _apply_sparse_csr(self, object X)
cpdef object decision_path(self, object X)
cdef object _decision_path_dense(self, object X)
@@ -98,6 +98,6 @@ cdef class TreeBuilder:
cdef SIZE_t max_depth # Maximal tree depth
cdef double min_impurity_decrease # Impurity threshold for early stopping
- cpdef build(self, Tree tree, object X, np.ndarray y,
- np.ndarray sample_weight=*)
- cdef _check_input(self, object X, np.ndarray y, np.ndarray sample_weight)
+ cpdef build(self, Tree tree, object X, cnp.ndarray y,
+ cnp.ndarray sample_weight=*)
+ cdef _check_input(self, object X, cnp.ndarray y, cnp.ndarray sample_weight)
diff --git a/sklearn/tree/_tree.pyx b/sklearn/tree/_tree.pyx
index 85c44b5eaf9b8..c2a2e80caaec6 100644
--- a/sklearn/tree/_tree.pyx
+++ b/sklearn/tree/_tree.pyx
@@ -27,8 +27,8 @@ from libcpp cimport bool
import struct
import numpy as np
-cimport numpy as np
-np.import_array()
+cimport numpy as cnp
+cnp.import_array()
from scipy.sparse import issparse
from scipy.sparse import csr_matrix
@@ -37,11 +37,11 @@ from ._utils cimport safe_realloc
from ._utils cimport sizet_ptr_to_ndarray
cdef extern from "numpy/arrayobject.h":
- object PyArray_NewFromDescr(PyTypeObject* subtype, np.dtype descr,
- int nd, np.npy_intp* dims,
- np.npy_intp* strides,
+ object PyArray_NewFromDescr(PyTypeObject* subtype, cnp.dtype descr,
+ int nd, cnp.npy_intp* dims,
+ cnp.npy_intp* strides,
void* data, int flags, object obj)
- int PyArray_SetBaseObject(np.ndarray arr, PyObject* obj)
+ int PyArray_SetBaseObject(cnp.ndarray arr, PyObject* obj)
cdef extern from "" namespace "std" nogil:
cdef cppclass stack[T]:
@@ -87,13 +87,13 @@ NODE_DTYPE = np.asarray((&dummy)).dtype
cdef class TreeBuilder:
"""Interface for different tree building strategies."""
- cpdef build(self, Tree tree, object X, np.ndarray y,
- np.ndarray sample_weight=None):
+ cpdef build(self, Tree tree, object X, cnp.ndarray y,
+ cnp.ndarray sample_weight=None):
"""Build a decision tree from the training set (X, y)."""
pass
- cdef inline _check_input(self, object X, np.ndarray y,
- np.ndarray sample_weight):
+ cdef inline _check_input(self, object X, cnp.ndarray y,
+ cnp.ndarray sample_weight):
"""Check input dtype, layout and format"""
if issparse(X):
X = X.tocsc()
@@ -145,8 +145,8 @@ cdef class DepthFirstTreeBuilder(TreeBuilder):
self.max_depth = max_depth
self.min_impurity_decrease = min_impurity_decrease
- cpdef build(self, Tree tree, object X, np.ndarray y,
- np.ndarray sample_weight=None):
+ cpdef build(self, Tree tree, object X, cnp.ndarray y,
+ cnp.ndarray sample_weight=None):
"""Build a decision tree from the training set (X, y)."""
# check input
@@ -341,8 +341,8 @@ cdef class BestFirstTreeBuilder(TreeBuilder):
self.max_leaf_nodes = max_leaf_nodes
self.min_impurity_decrease = min_impurity_decrease
- cpdef build(self, Tree tree, object X, np.ndarray y,
- np.ndarray sample_weight=None):
+ cpdef build(self, Tree tree, object X, cnp.ndarray y,
+ cnp.ndarray sample_weight=None):
"""Build a decision tree from the training set (X, y)."""
# check input
@@ -624,7 +624,7 @@ cdef class Tree:
def __get__(self):
return self._get_value_ndarray()[:self.node_count]
- def __cinit__(self, int n_features, np.ndarray n_classes, int n_outputs):
+ def __cinit__(self, int n_features, cnp.ndarray n_classes, int n_outputs):
"""Constructor."""
cdef SIZE_t dummy = 0
size_t_dtype = np.array(dummy).dtype
@@ -699,9 +699,9 @@ cdef class Tree:
self.capacity = node_ndarray.shape[0]
if self._resize_c(self.capacity) != 0:
raise MemoryError("resizing tree to %d" % self.capacity)
- nodes = memcpy(self.nodes, ( node_ndarray).data,
+ nodes = memcpy(self.nodes, ( node_ndarray).data,
self.capacity * sizeof(Node))
- value = memcpy(self.value, ( value_ndarray).data,
+ value = memcpy(self.value, ( value_ndarray).data,
self.capacity * self.value_stride * sizeof(double))
cdef int _resize(self, SIZE_t capacity) nogil except -1:
@@ -789,7 +789,7 @@ cdef class Tree:
return node_id
- cpdef np.ndarray predict(self, object X):
+ cpdef cnp.ndarray predict(self, object X):
"""Predict target for X."""
out = self._get_value_ndarray().take(self.apply(X), axis=0,
mode='clip')
@@ -797,14 +797,14 @@ cdef class Tree:
out = out.reshape(X.shape[0], self.max_n_classes)
return out
- cpdef np.ndarray apply(self, object X):
+ cpdef cnp.ndarray apply(self, object X):
"""Finds the terminal region (=leaf node) for each sample in X."""
if issparse(X):
return self._apply_sparse_csr(X)
else:
return self._apply_dense(X)
- cdef inline np.ndarray _apply_dense(self, object X):
+ cdef inline cnp.ndarray _apply_dense(self, object X):
"""Finds the terminal region (=leaf node) for each sample in X."""
# Check input
@@ -820,7 +820,7 @@ cdef class Tree:
cdef SIZE_t n_samples = X.shape[0]
# Initialize output
- cdef np.ndarray[SIZE_t] out = np.zeros((n_samples,), dtype=np.intp)
+ cdef cnp.ndarray[SIZE_t] out = np.zeros((n_samples,), dtype=np.intp)
cdef SIZE_t* out_ptr = out.data
# Initialize auxiliary data-structure
@@ -842,7 +842,7 @@ cdef class Tree:
return out
- cdef inline np.ndarray _apply_sparse_csr(self, object X):
+ cdef inline cnp.ndarray _apply_sparse_csr(self, object X):
"""Finds the terminal region (=leaf node) for each sample in sparse X.
"""
# Check input
@@ -854,9 +854,9 @@ cdef class Tree:
raise ValueError("X.dtype should be np.float32, got %s" % X.dtype)
# Extract input
- cdef np.ndarray[ndim=1, dtype=DTYPE_t] X_data_ndarray = X.data
- cdef np.ndarray[ndim=1, dtype=INT32_t] X_indices_ndarray = X.indices
- cdef np.ndarray[ndim=1, dtype=INT32_t] X_indptr_ndarray = X.indptr
+ cdef cnp.ndarray[ndim=1, dtype=DTYPE_t] X_data_ndarray = X.data
+ cdef cnp.ndarray[ndim=1, dtype=INT32_t] X_indices_ndarray = X.indices
+ cdef cnp.ndarray[ndim=1, dtype=INT32_t] X_indptr_ndarray = X.indptr
cdef DTYPE_t* X_data = X_data_ndarray.data
cdef INT32_t* X_indices = X_indices_ndarray.data
@@ -866,8 +866,8 @@ cdef class Tree:
cdef SIZE_t n_features = X.shape[1]
# Initialize output
- cdef np.ndarray[SIZE_t, ndim=1] out = np.zeros((n_samples,),
- dtype=np.intp)
+ cdef cnp.ndarray[SIZE_t, ndim=1] out = np.zeros((n_samples,),
+ dtype=np.intp)
cdef SIZE_t* out_ptr = out.data
# Initialize auxiliary data-structure
@@ -940,12 +940,12 @@ cdef class Tree:
cdef SIZE_t n_samples = X.shape[0]
# Initialize output
- cdef np.ndarray[SIZE_t] indptr = np.zeros(n_samples + 1, dtype=np.intp)
+ cdef cnp.ndarray[SIZE_t] indptr = np.zeros(n_samples + 1, dtype=np.intp)
cdef SIZE_t* indptr_ptr = indptr.data
- cdef np.ndarray[SIZE_t] indices = np.zeros(n_samples *
- (1 + self.max_depth),
- dtype=np.intp)
+ cdef cnp.ndarray[SIZE_t] indices = np.zeros(n_samples *
+ (1 + self.max_depth),
+ dtype=np.intp)
cdef SIZE_t* indices_ptr = indices.data
# Initialize auxiliary data-structure
@@ -973,8 +973,8 @@ cdef class Tree:
indptr_ptr[i + 1] += 1
indices = indices[:indptr[n_samples]]
- cdef np.ndarray[SIZE_t] data = np.ones(shape=len(indices),
- dtype=np.intp)
+ cdef cnp.ndarray[SIZE_t] data = np.ones(shape=len(indices),
+ dtype=np.intp)
out = csr_matrix((data, indices, indptr),
shape=(n_samples, self.node_count))
@@ -992,9 +992,9 @@ cdef class Tree:
raise ValueError("X.dtype should be np.float32, got %s" % X.dtype)
# Extract input
- cdef np.ndarray[ndim=1, dtype=DTYPE_t] X_data_ndarray = X.data
- cdef np.ndarray[ndim=1, dtype=INT32_t] X_indices_ndarray = X.indices
- cdef np.ndarray[ndim=1, dtype=INT32_t] X_indptr_ndarray = X.indptr
+ cdef cnp.ndarray[ndim=1, dtype=DTYPE_t] X_data_ndarray = X.data
+ cdef cnp.ndarray[ndim=1, dtype=INT32_t] X_indices_ndarray = X.indices
+ cdef cnp.ndarray[ndim=1, dtype=INT32_t] X_indptr_ndarray = X.indptr
cdef DTYPE_t* X_data = X_data_ndarray.data
cdef INT32_t* X_indices = X_indices_ndarray.data
@@ -1004,12 +1004,12 @@ cdef class Tree:
cdef SIZE_t n_features = X.shape[1]
# Initialize output
- cdef np.ndarray[SIZE_t] indptr = np.zeros(n_samples + 1, dtype=np.intp)
+ cdef cnp.ndarray[SIZE_t] indptr = np.zeros(n_samples + 1, dtype=np.intp)
cdef SIZE_t* indptr_ptr = indptr.data
- cdef np.ndarray[SIZE_t] indices = np.zeros(n_samples *
- (1 + self.max_depth),
- dtype=np.intp)
+ cdef cnp.ndarray[SIZE_t] indices = np.zeros(n_samples *
+ (1 + self.max_depth),
+ dtype=np.intp)
cdef SIZE_t* indices_ptr = indices.data
# Initialize auxiliary data-structure
@@ -1065,8 +1065,8 @@ cdef class Tree:
free(feature_to_sample)
indices = indices[:indptr[n_samples]]
- cdef np.ndarray[SIZE_t] data = np.ones(shape=len(indices),
- dtype=np.intp)
+ cdef cnp.ndarray[SIZE_t] data = np.ones(shape=len(indices),
+ dtype=np.intp)
out = csr_matrix((data, indices, indptr),
shape=(n_samples, self.node_count))
@@ -1083,7 +1083,7 @@ cdef class Tree:
cdef double normalizer = 0.
- cdef np.ndarray[np.float64_t, ndim=1] importances
+ cdef cnp.ndarray[cnp.float64_t, ndim=1] importances
importances = np.zeros((self.n_features,))
cdef DOUBLE_t* importance_data = importances.data
@@ -1111,40 +1111,40 @@ cdef class Tree:
return importances
- cdef np.ndarray _get_value_ndarray(self):
+ cdef cnp.ndarray _get_value_ndarray(self):
"""Wraps value as a 3-d NumPy array.
The array keeps a reference to this Tree, which manages the underlying
memory.
"""
- cdef np.npy_intp shape[3]
- shape[0] = self.node_count
- shape[1] = self.n_outputs
- shape[2] = self.max_n_classes
- cdef np.ndarray arr
- arr = np.PyArray_SimpleNewFromData(3, shape, np.NPY_DOUBLE, self.value)
+ cdef cnp.npy_intp shape[3]
+ shape[0] = self.node_count
+ shape[1] = self.n_outputs
+ shape[2] = self.max_n_classes
+ cdef cnp.ndarray arr
+ arr = cnp.PyArray_SimpleNewFromData(3, shape, cnp.NPY_DOUBLE, self.value)
Py_INCREF(self)
if PyArray_SetBaseObject(arr, self) < 0:
raise ValueError("Can't initialize array.")
return arr
- cdef np.ndarray _get_node_ndarray(self):
+ cdef cnp.ndarray _get_node_ndarray(self):
"""Wraps nodes as a NumPy struct array.
The array keeps a reference to this Tree, which manages the underlying
memory. Individual fields are publicly accessible as properties of the
Tree.
"""
- cdef np.npy_intp shape[1]
- shape[0] = self.node_count
- cdef np.npy_intp strides[1]
+ cdef cnp.npy_intp shape[1]
+ shape[0] = self.node_count
+ cdef cnp.npy_intp strides[1]
strides[0] = sizeof(Node)
- cdef np.ndarray arr
+ cdef cnp.ndarray arr
Py_INCREF(NODE_DTYPE)
- arr = PyArray_NewFromDescr( np.ndarray,
- NODE_DTYPE, 1, shape,
+ arr = PyArray_NewFromDescr( cnp.ndarray,
+ NODE_DTYPE, 1, shape,
strides, self.nodes,
- np.NPY_DEFAULT, None)
+ cnp.NPY_DEFAULT, None)
Py_INCREF(self)
if PyArray_SetBaseObject(arr, self) < 0:
raise ValueError("Can't initialize array.")
@@ -1680,10 +1680,10 @@ def ccp_pruning_path(Tree orig_tree):
cdef:
UINT32_t total_items = path_finder.count
- np.ndarray ccp_alphas = np.empty(shape=total_items,
- dtype=np.float64)
- np.ndarray impurities = np.empty(shape=total_items,
- dtype=np.float64)
+ cnp.ndarray ccp_alphas = np.empty(shape=total_items,
+ dtype=np.float64)
+ cnp.ndarray impurities = np.empty(shape=total_items,
+ dtype=np.float64)
UINT32_t count = 0
while count < total_items:
diff --git a/sklearn/tree/_utils.pxd b/sklearn/tree/_utils.pxd
index fe4aca67d7b52..9d41b757d85dc 100644
--- a/sklearn/tree/_utils.pxd
+++ b/sklearn/tree/_utils.pxd
@@ -8,16 +8,15 @@
# See _utils.pyx for details.
-import numpy as np
-cimport numpy as np
+cimport numpy as cnp
from ._tree cimport Node
from ..neighbors._quad_tree cimport Cell
-ctypedef np.npy_float32 DTYPE_t # Type of X
-ctypedef np.npy_float64 DOUBLE_t # Type of y, sample_weight
-ctypedef np.npy_intp SIZE_t # Type for indices and counters
-ctypedef np.npy_int32 INT32_t # Signed 32 bit integer
-ctypedef np.npy_uint32 UINT32_t # Unsigned 32 bit integer
+ctypedef cnp.npy_float32 DTYPE_t # Type of X
+ctypedef cnp.npy_float64 DOUBLE_t # Type of y, sample_weight
+ctypedef cnp.npy_intp SIZE_t # Type for indices and counters
+ctypedef cnp.npy_int32 INT32_t # Signed 32 bit integer
+ctypedef cnp.npy_uint32 UINT32_t # Unsigned 32 bit integer
cdef enum:
@@ -47,7 +46,7 @@ ctypedef fused realloc_ptr:
cdef realloc_ptr safe_realloc(realloc_ptr* p, size_t nelems) nogil except *
-cdef np.ndarray sizet_ptr_to_ndarray(SIZE_t* data, SIZE_t size)
+cdef cnp.ndarray sizet_ptr_to_ndarray(SIZE_t* data, SIZE_t size)
cdef SIZE_t rand_int(SIZE_t low, SIZE_t high,
diff --git a/sklearn/tree/_utils.pyx b/sklearn/tree/_utils.pyx
index ba4c0f716a985..7346070b7a149 100644
--- a/sklearn/tree/_utils.pyx
+++ b/sklearn/tree/_utils.pyx
@@ -12,9 +12,8 @@ from libc.stdlib cimport malloc
from libc.stdlib cimport realloc
from libc.math cimport log as ln
-import numpy as np
-cimport numpy as np
-np.import_array()
+cimport numpy as cnp
+cnp.import_array()
from ..utils._random cimport our_rand_r
@@ -50,11 +49,11 @@ def _realloc_test():
assert False
-cdef inline np.ndarray sizet_ptr_to_ndarray(SIZE_t* data, SIZE_t size):
+cdef inline cnp.ndarray sizet_ptr_to_ndarray(SIZE_t* data, SIZE_t size):
"""Return copied data as 1D numpy array of intp's."""
- cdef np.npy_intp shape[1]
- shape[0] = size
- return np.PyArray_SimpleNewFromData(1, shape, np.NPY_INTP, data).copy()
+ cdef cnp.npy_intp shape[1]
+ shape[0] = size
+ return cnp.PyArray_SimpleNewFromData(1, shape, cnp.NPY_INTP, data).copy()
cdef inline SIZE_t rand_int(SIZE_t low, SIZE_t high,
diff --git a/sklearn/utils/__init__.py b/sklearn/utils/__init__.py
index aa056e92b3d12..a4857d62f7a38 100644
--- a/sklearn/utils/__init__.py
+++ b/sklearn/utils/__init__.py
@@ -473,6 +473,10 @@ def resample(*arrays, replace=True, n_samples=None, random_state=None, stratify=
Sequence of resampled copies of the collections. The original arrays
are not impacted.
+ See Also
+ --------
+ shuffle : Shuffle arrays or sparse matrices in a consistent way.
+
Examples
--------
It is possible to mix sparse and dense arrays in the same run::
@@ -512,10 +516,6 @@ def resample(*arrays, replace=True, n_samples=None, random_state=None, stratify=
>>> resample(y, n_samples=5, replace=False, stratify=y,
... random_state=0)
[1, 1, 1, 0, 1]
-
- See Also
- --------
- shuffle
"""
max_n_samples = n_samples
random_state = check_random_state(random_state)
diff --git a/sklearn/utils/_estimator_html_repr.py b/sklearn/utils/_estimator_html_repr.py
index f8911b5c38b08..e5291b6de3701 100644
--- a/sklearn/utils/_estimator_html_repr.py
+++ b/sklearn/utils/_estimator_html_repr.py
@@ -1,5 +1,4 @@
from contextlib import closing
-from contextlib import suppress
from io import StringIO
from string import Template
import html
@@ -103,8 +102,16 @@ def _write_label_html(
def _get_visual_block(estimator):
"""Generate information about how to display an estimator."""
- with suppress(AttributeError):
- return estimator._sk_visual_block_()
+ if hasattr(estimator, "_sk_visual_block_"):
+ try:
+ return estimator._sk_visual_block_()
+ except Exception:
+ return _VisualBlock(
+ "single",
+ estimator,
+ names=estimator.__class__.__name__,
+ name_details=str(estimator),
+ )
if isinstance(estimator, str):
return _VisualBlock(
diff --git a/sklearn/utils/_testing.py b/sklearn/utils/_testing.py
index 453f3437307a9..4851322197d7b 100644
--- a/sklearn/utils/_testing.py
+++ b/sklearn/utils/_testing.py
@@ -22,6 +22,7 @@
import re
import contextlib
from collections.abc import Iterable
+from collections.abc import Sequence
import scipy as sp
from functools import wraps
@@ -34,7 +35,7 @@
# WindowsError only exist on Windows
try:
- WindowsError
+ WindowsError # type: ignore
except NameError:
WindowsError = None
@@ -60,6 +61,7 @@
check_is_fitted,
check_X_y,
)
+from sklearn.utils.fixes import threadpool_info
__all__ = [
@@ -414,7 +416,6 @@ def assert_allclose(
If None, it is set based on the provided arrays' dtypes.
atol : float, optional, default=0.
Absolute tolerance.
- If None, it is set based on the provided arrays' dtypes.
equal_nan : bool, optional, default=True
If True, NaNs will compare equal.
err_msg : str, optional, default=''
@@ -603,6 +604,38 @@ def __exit__(self, exc_type, exc_val, exc_tb):
_delete_folder(self.temp_folder)
+def _create_memmap_backed_array(array, filename, mmap_mode):
+ # https://numpy.org/doc/stable/reference/generated/numpy.memmap.html
+ fp = np.memmap(filename, dtype=array.dtype, mode="w+", shape=array.shape)
+ fp[:] = array[:] # write array to memmap array
+ fp.flush()
+ memmap_backed_array = np.memmap(
+ filename, dtype=array.dtype, mode=mmap_mode, shape=array.shape
+ )
+ return memmap_backed_array
+
+
+def _create_aligned_memmap_backed_arrays(data, mmap_mode, folder):
+ if isinstance(data, np.ndarray):
+ filename = op.join(folder, "data.dat")
+ return _create_memmap_backed_array(data, filename, mmap_mode)
+
+ if isinstance(data, Sequence) and all(
+ isinstance(each, np.ndarray) for each in data
+ ):
+ return [
+ _create_memmap_backed_array(
+ array, op.join(folder, f"data{index}.dat"), mmap_mode
+ )
+ for index, array in enumerate(data)
+ ]
+
+ raise ValueError(
+ "When creating aligned memmap-backed arrays, input must be a single array or a"
+ " sequence of arrays"
+ )
+
+
def create_memmap_backed_data(data, mmap_mode="r", return_folder=False, aligned=False):
"""
Parameters
@@ -617,18 +650,23 @@ def create_memmap_backed_data(data, mmap_mode="r", return_folder=False, aligned=
"""
temp_folder = tempfile.mkdtemp(prefix="sklearn_testing_")
atexit.register(functools.partial(_delete_folder, temp_folder, warn=True))
+ # OpenBLAS is known to segfault with unaligned data on the Prescott
+ # architecture so force aligned=True on Prescott. For more details, see:
+ # https://github.com/scipy/scipy/issues/14886
+ has_prescott_openblas = any(
+ True
+ for info in threadpool_info()
+ if info["internal_api"] == "openblas"
+ # Prudently assume Prescott might be the architecture if it is unknown.
+ and info.get("architecture", "prescott").lower() == "prescott"
+ )
+ if has_prescott_openblas:
+ aligned = True
+
if aligned:
- if isinstance(data, np.ndarray) and data.flags.aligned:
- # https://numpy.org/doc/stable/reference/generated/numpy.memmap.html
- filename = op.join(temp_folder, "data.dat")
- fp = np.memmap(filename, dtype=data.dtype, mode="w+", shape=data.shape)
- fp[:] = data[:] # write data to memmap array
- fp.flush()
- memmap_backed_data = np.memmap(
- filename, dtype=data.dtype, mode=mmap_mode, shape=data.shape
- )
- else:
- raise ValueError("If aligned=True, input must be a single numpy array.")
+ memmap_backed_data = _create_aligned_memmap_backed_arrays(
+ data, mmap_mode, temp_folder
+ )
else:
filename = op.join(temp_folder, "data.pkl")
joblib.dump(data, filename)
diff --git a/sklearn/utils/estimator_checks.py b/sklearn/utils/estimator_checks.py
index 0cafae42ea2aa..979053d4a61e2 100644
--- a/sklearn/utils/estimator_checks.py
+++ b/sklearn/utils/estimator_checks.py
@@ -54,7 +54,8 @@
from ..model_selection import ShuffleSplit
from ..model_selection._validation import _safe_split
from ..metrics.pairwise import rbf_kernel, linear_kernel, pairwise_distances
-from ..utils.fixes import threadpool_info
+from ..utils.fixes import sp_version
+from ..utils.fixes import parse_version
from ..utils.validation import check_is_fitted
from . import shuffle
@@ -648,6 +649,8 @@ def _set_checking_parameters(estimator):
# avoid deprecated behaviour
params = estimator.get_params()
name = estimator.__class__.__name__
+ if name == "TSNE":
+ estimator.set_params(perplexity=2)
if "n_iter" in params and name != "TSNE":
estimator.set_params(n_iter=5)
if "max_iter" in params:
@@ -744,6 +747,11 @@ def _set_checking_parameters(estimator):
if name == "OneHotEncoder":
estimator.set_params(handle_unknown="ignore")
+ if name == "QuantileRegressor":
+ # Avoid warning due to Scipy deprecating interior-point solver
+ solver = "highs" if sp_version >= parse_version("1.6.0") else "interior-point"
+ estimator.set_params(solver=solver)
+
if name in CROSS_DECOMPOSITION:
estimator.set_params(n_components=1)
@@ -1419,6 +1427,10 @@ def check_fit2d_1sample(name, estimator_orig):
if name == "OPTICS":
estimator.set_params(min_samples=1)
+ # perplexity cannot be more than the number of samples for TSNE.
+ if name == "TSNE":
+ estimator.set_params(perplexity=0.5)
+
msgs = [
"1 sample",
"n_samples = 1",
@@ -2105,22 +2117,6 @@ def check_classifiers_one_label(name, classifier_orig):
assert_array_equal(classifier.predict(X_test), y, err_msg=error_string_predict)
-def _create_memmap_backed_data(numpy_arrays):
- # OpenBLAS is known to segfault with unaligned data on the Prescott architecture
- # See: https://github.com/scipy/scipy/issues/14886
- has_prescott_openblas = any(
- True
- for info in threadpool_info()
- if info["internal_api"] == "openblas"
- # Prudently assume Prescott might be the architecture if it is unknown.
- and info.get("architecture", "prescott").lower() == "prescott"
- )
- return [
- create_memmap_backed_data(array, aligned=has_prescott_openblas)
- for array in numpy_arrays
- ]
-
-
@ignore_warnings # Warnings are raised by decision function
def check_classifiers_train(
name, classifier_orig, readonly_memmap=False, X_dtype="float64"
@@ -2138,7 +2134,7 @@ def check_classifiers_train(
X_b -= X_b.min()
if readonly_memmap:
- X_m, y_m, X_b, y_b = _create_memmap_backed_data([X_m, y_m, X_b, y_b])
+ X_m, y_m, X_b, y_b = create_memmap_backed_data([X_m, y_m, X_b, y_b])
problems = [(X_b, y_b)]
tags = _safe_tags(classifier_orig)
@@ -2806,7 +2802,7 @@ def check_regressors_train(
y_ = y
if readonly_memmap:
- X, y, y_ = _create_memmap_backed_data([X, y, y_])
+ X, y, y_ = create_memmap_backed_data([X, y, y_])
if not hasattr(regressor, "alphas") and hasattr(regressor, "alpha"):
# linear regressors need to set alpha, but not generalized CV ones
diff --git a/sklearn/utils/extmath.py b/sklearn/utils/extmath.py
index 2521990e6cc68..089bd2efadff5 100644
--- a/sklearn/utils/extmath.py
+++ b/sklearn/utils/extmath.py
@@ -216,7 +216,7 @@ def randomized_range_finder(
# Generating normal random vectors with shape: (A.shape[1], size)
Q = random_state.normal(size=(A.shape[1], size))
- if A.dtype.kind == "f":
+ if hasattr(A, "dtype") and A.dtype.kind == "f":
# Ensure f32 is preserved as f32
Q = Q.astype(A.dtype, copy=False)
@@ -243,6 +243,7 @@ def randomized_range_finder(
# Sample the range of A using by linear projection of Q
# Extract an orthonormal basis
Q, _ = linalg.qr(safe_sparse_dot(A, Q), mode="economic")
+
return Q
diff --git a/sklearn/utils/fixes.py b/sklearn/utils/fixes.py
index b0074ae7e3a18..cdd63e00cd381 100644
--- a/sklearn/utils/fixes.py
+++ b/sklearn/utils/fixes.py
@@ -163,3 +163,10 @@ def threadpool_info():
threadpool_info.__doc__ = threadpoolctl.threadpool_info.__doc__
+
+
+# TODO: Remove when SciPy 1.9 is the minimum supported version
+def _mode(a, axis=0):
+ if sp_version >= parse_version("1.9.0"):
+ return scipy.stats.mode(a, axis=axis, keepdims=True)
+ return scipy.stats.mode(a, axis=axis)
diff --git a/sklearn/utils/metaestimators.py b/sklearn/utils/metaestimators.py
index 1cee8d1d42cf4..b0b20db7a5017 100644
--- a/sklearn/utils/metaestimators.py
+++ b/sklearn/utils/metaestimators.py
@@ -200,7 +200,7 @@ def __init__(self, fn, delegate_names, attribute_name):
def _check(self, obj):
warnings.warn(
"if_delegate_has_method was deprecated in version 1.1 and will be "
- "removed in version 1.3. Use if_available instead.",
+ "removed in version 1.3. Use available_if instead.",
FutureWarning,
)
diff --git a/sklearn/utils/multiclass.py b/sklearn/utils/multiclass.py
index 5311076e64eb8..c72846f9aa323 100644
--- a/sklearn/utils/multiclass.py
+++ b/sklearn/utils/multiclass.py
@@ -150,7 +150,7 @@ def is_multilabel(y):
warnings.simplefilter("error", np.VisibleDeprecationWarning)
try:
y = np.asarray(y)
- except np.VisibleDeprecationWarning:
+ except (np.VisibleDeprecationWarning, ValueError):
# dtype=object should be provided explicitly for ragged arrays,
# see NEP 34
y = np.array(y, dtype=object)
@@ -292,7 +292,7 @@ def type_of_target(y, input_name=""):
warnings.simplefilter("error", np.VisibleDeprecationWarning)
try:
y = np.asarray(y)
- except np.VisibleDeprecationWarning:
+ except (np.VisibleDeprecationWarning, ValueError):
# dtype=object should be provided explicitly for ragged arrays,
# see NEP 34
y = np.asarray(y, dtype=object)
diff --git a/sklearn/utils/sparsefuncs.py b/sklearn/utils/sparsefuncs.py
index d53741c044c47..64a86cf1180bf 100644
--- a/sklearn/utils/sparsefuncs.py
+++ b/sklearn/utils/sparsefuncs.py
@@ -385,7 +385,7 @@ def inplace_swap_row(X, m, n):
def inplace_swap_column(X, m, n):
"""
- Swaps two columns of a CSC/CSR matrix in-place.
+ Swap two columns of a CSC/CSR matrix in-place.
Parameters
----------
diff --git a/sklearn/utils/tests/test_estimator_html_repr.py b/sklearn/utils/tests/test_estimator_html_repr.py
index 91644819864eb..4624896abd307 100644
--- a/sklearn/utils/tests/test_estimator_html_repr.py
+++ b/sklearn/utils/tests/test_estimator_html_repr.py
@@ -309,3 +309,14 @@ def test_show_arrow_pipeline():
'class="sk-toggleable__label sk-toggleable__label-arrow">Pipeline'
in html_output
)
+
+
+def test_invalid_parameters_in_stacking():
+ """Invalidate stacking configuration uses default repr.
+
+ Non-regression test for #24009.
+ """
+ stacker = StackingClassifier(estimators=[])
+
+ html_output = estimator_html_repr(stacker)
+ assert html.escape(str(stacker)) in html_output
diff --git a/sklearn/utils/tests/test_extmath.py b/sklearn/utils/tests/test_extmath.py
index ece7c180300a1..a9ba7a96685d6 100644
--- a/sklearn/utils/tests/test_extmath.py
+++ b/sklearn/utils/tests/test_extmath.py
@@ -7,7 +7,6 @@
import numpy as np
from scipy import sparse
from scipy import linalg
-from scipy import stats
from scipy.sparse.linalg import eigsh
from scipy.special import expit
@@ -20,6 +19,7 @@
from sklearn.utils._testing import assert_array_equal
from sklearn.utils._testing import assert_array_almost_equal
from sklearn.utils._testing import skip_if_32bit
+from sklearn.utils.fixes import _mode
from sklearn.utils.extmath import density, _safe_accumulator_op
from sklearn.utils.extmath import randomized_svd, _randomized_eigsh
@@ -57,7 +57,7 @@ def test_uniform_weights():
weights = np.ones(x.shape)
for axis in (None, 0, 1):
- mode, score = stats.mode(x, axis)
+ mode, score = _mode(x, axis)
mode2, score2 = weighted_mode(x, weights, axis=axis)
assert_array_equal(mode, mode2)
diff --git a/sklearn/utils/tests/test_testing.py b/sklearn/utils/tests/test_testing.py
index fca7a07b14c19..75f35a3dea83c 100644
--- a/sklearn/utils/tests/test_testing.py
+++ b/sklearn/utils/tests/test_testing.py
@@ -702,16 +702,19 @@ def test_create_memmap_backed_data(monkeypatch, aligned):
assert registration_counter.nb_calls == 3
input_list = [input_array, input_array + 1, input_array + 2]
- if aligned:
- with pytest.raises(
- ValueError, match="If aligned=True, input must be a single numpy array."
- ):
- create_memmap_backed_data(input_list, aligned=True)
- else:
- mmap_data_list = create_memmap_backed_data(input_list, aligned=False)
- for input_array, data in zip(input_list, mmap_data_list):
- check_memmap(input_array, data)
- assert registration_counter.nb_calls == 4
+ mmap_data_list = create_memmap_backed_data(input_list, aligned=aligned)
+ for input_array, data in zip(input_list, mmap_data_list):
+ check_memmap(input_array, data)
+ assert registration_counter.nb_calls == 4
+
+ with pytest.raises(
+ ValueError,
+ match=(
+ "When creating aligned memmap-backed arrays, input must be a single array"
+ " or a sequence of arrays"
+ ),
+ ):
+ create_memmap_backed_data([input_array, "not-an-array"], aligned=True)
@pytest.mark.parametrize("dtype", [np.float32, np.float64, np.int32, np.int64])
diff --git a/sklearn/utils/tests/test_utils.py b/sklearn/utils/tests/test_utils.py
index 82be82afa5eed..4fc483e5a85b2 100644
--- a/sklearn/utils/tests/test_utils.py
+++ b/sklearn/utils/tests/test_utils.py
@@ -472,8 +472,13 @@ def test_safe_indexing_pandas_no_settingwithcopy_warning():
X = pd.DataFrame({"a": [1, 2, 3], "b": [3, 4, 5]})
subset = _safe_indexing(X, [0, 1], axis=0)
+ if hasattr(pd.errors, "SettingWithCopyWarning"):
+ SettingWithCopyWarning = pd.errors.SettingWithCopyWarning
+ else:
+ # backward compatibility for pandas < 1.5
+ SettingWithCopyWarning = pd.core.common.SettingWithCopyWarning
with warnings.catch_warnings():
- warnings.simplefilter("error", pd.core.common.SettingWithCopyWarning)
+ warnings.simplefilter("error", SettingWithCopyWarning)
subset.iloc[0, 0] = 10
# The original dataframe is unaffected by the assignment on the subset:
assert X.iloc[0, 0] == 1
diff --git a/sklearn/utils/tests/test_validation.py b/sklearn/utils/tests/test_validation.py
index a8078a2b39416..478617bcc4031 100644
--- a/sklearn/utils/tests/test_validation.py
+++ b/sklearn/utils/tests/test_validation.py
@@ -358,21 +358,13 @@ def test_check_array():
Xs = [X_csc, X_coo, X_dok, X_int, X_float]
accept_sparses = [["csr", "coo"], ["coo", "dok"]]
- for X, dtype, accept_sparse, copy in product(Xs, dtypes, accept_sparses, copys):
- with warnings.catch_warnings(record=True) as w:
- X_checked = check_array(
- X, dtype=dtype, accept_sparse=accept_sparse, copy=copy
- )
- if (dtype is object or sp.isspmatrix_dok(X)) and len(w):
- # XXX unreached code as of v0.22
- message = str(w[0].message)
- messages = [
- "object dtype is not supported by sparse matrices",
- "Can't check dok sparse matrix for nan or inf.",
- ]
- assert message in messages
- else:
- assert len(w) == 0
+ # scipy sparse matrices do not support the object dtype so
+ # this dtype is skipped in this loop
+ non_object_dtypes = [dt for dt in dtypes if dt is not object]
+ for X, dtype, accept_sparse, copy in product(
+ Xs, non_object_dtypes, accept_sparses, copys
+ ):
+ X_checked = check_array(X, dtype=dtype, accept_sparse=accept_sparse, copy=copy)
if dtype is not None:
assert X_checked.dtype == dtype
else:
@@ -1286,15 +1278,20 @@ def test_check_psd_eigenvalues_valid(
if not enable_warnings:
w_type = None
- w_msg = ""
- with pytest.warns(w_type, match=w_msg) as w:
- assert_array_equal(
- _check_psd_eigenvalues(lambdas, enable_warnings=enable_warnings),
- expected_lambdas,
- )
if w_type is None:
- assert not w
+ with warnings.catch_warnings():
+ warnings.simplefilter("error", PositiveSpectrumWarning)
+ lambdas_fixed = _check_psd_eigenvalues(
+ lambdas, enable_warnings=enable_warnings
+ )
+ else:
+ with pytest.warns(w_type, match=w_msg):
+ lambdas_fixed = _check_psd_eigenvalues(
+ lambdas, enable_warnings=enable_warnings
+ )
+
+ assert_allclose(expected_lambdas, lambdas_fixed)
_psd_cases_invalid = {