You open a notebook, run the first cell, and the kernel dies with something like this:
ImportError: cannot import name 'cross_validation' from 'sklearn'
Or you clone a project from GitHub, install its requirements, and immediately see:
ImportError: cannot import name 'Imputer' from 'sklearn.preprocessing'
These errors all have the same root cause: scikit-learn reorganized and removed large parts of its public API between the 0.x series and 1.x. Code written against older scikit-learn breaks silently when the package is upgraded — the module still imports, but the specific name no longer lives where the old code expects it.
This guide shows which names moved, where they went, and how to fix each one.
The error surface varies depending on which removed or moved name you are trying to import. Here are three representative tracebacks from real code.
Traceback (most recent call last):
File "train.py", line 3, in <module>
from sklearn.cross_validation import train_test_split, StratifiedKFold
File "/usr/local/lib/python3.10/site-packages/sklearn/__init__.py", line 79, in __getattr__
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
ImportError: cannot import name 'cross_validation' from 'sklearn'
(/usr/local/lib/python3.10/site-packages/sklearn/__init__.py)
Traceback (most recent call last):
File "preprocess.py", line 5, in <module>
from sklearn.preprocessing import Imputer
ImportError: cannot import name 'Imputer' from 'sklearn.preprocessing'
(/usr/local/lib/python3.10/site-packages/sklearn/preprocessing/__init__.py)
Traceback (most recent call last):
File "pipeline.py", line 2, in <module>
from sklearn.externals import joblib
File "/usr/local/lib/python3.10/site-packages/sklearn/externals/__init__.py", line 3, in <module>
raise ImportError(
ImportError: cannot import name 'joblib' from 'sklearn.externals'
(/usr/local/lib/python3.10/site-packages/sklearn/externals/__init__.py)
If you are relying on the 'sklearn.externals.joblib' import path, you can
simply install joblib with 'pip install joblib' and use 'import joblib'
directly.
In all three cases the installed scikit-learn version is newer than the code was written for. The fix is either to update the import path in your code or to pin the package to an older version that still contains the name.
Before changing any code, confirm which version is installed in the environment that is running your script. The version tells you exactly which APIs are available.
import sklearn
print(sklearn.__version__)
# Example outputs:
# 0.24.2 — old; cross_validation, Imputer, externals.joblib still exist
# 1.0.2 — these names have been removed
# 1.3.0 — same removals, plus a few additional deprecations resolved
# 1.6.1 — current stable as of mid-2025
You can also check from the shell without starting a Python session:
python -m sklearn --version
# scikit-learn 1.3.2
# Or via pip:
pip show scikit-learn
# Name: scikit-learn
# Version: 1.3.2
# Location: /usr/local/lib/python3.10/site-packages
If the version is 1.0 or higher and your code imports from sklearn.cross_validation, sklearn.grid_search, sklearn.learning_curve, or sklearn.externals, those imports will fail. The submodules were removed in 0.22 (December 2019) after a multi-version deprecation cycle. sklearn.preprocessing.Imputer was removed in 0.22 as well.
The table below covers every common import path that broke across the 0.x → 1.x transition. The "Notes" column explains whether the class was renamed, moved, or replaced by a different API.
| Old import (0.x) | New import (1.x) | Notes |
|---|---|---|
from sklearn.cross_validation import train_test_split |
from sklearn.model_selection import train_test_split |
Entire cross_validation submodule folded into model_selection in 0.18. Removed in 0.20. |
from sklearn.cross_validation import StratifiedKFold, KFold |
from sklearn.model_selection import StratifiedKFold, KFold |
Same move. All CV splitters now live in model_selection. |
from sklearn.cross_validation import cross_val_score |
from sklearn.model_selection import cross_val_score |
Same move. cross_val_predict and cross_validate also live here. |
from sklearn.grid_search import GridSearchCV |
from sklearn.model_selection import GridSearchCV |
sklearn.grid_search removed in 0.20. RandomizedSearchCV also moved here. |
from sklearn.grid_search import RandomizedSearchCV |
from sklearn.model_selection import RandomizedSearchCV |
Same move. ParameterGrid and ParameterSampler also in model_selection. |
from sklearn.learning_curve import learning_curve |
from sklearn.model_selection import learning_curve |
sklearn.learning_curve submodule removed in 0.20. validation_curve also moved here. |
from sklearn.learning_curve import validation_curve |
from sklearn.model_selection import validation_curve |
Same move. |
from sklearn.externals import joblib |
import joblib |
joblib became a standalone package. Install separately: pip install joblib. sklearn still depends on it internally but no longer re-exports it. Removed in 0.23. |
from sklearn.externals.joblib import Parallel, delayed |
from joblib import Parallel, delayed |
Same external-package change. All joblib symbols import directly from joblib. |
from sklearn.utils.linear_assignment_ import linear_assignment |
from scipy.optimize import linear_sum_assignment |
Removed in 0.21. scipy's version returns (row_ind, col_ind) arrays instead of a 2-column array; callers need minor adaptation. |
from sklearn.preprocessing import Imputer |
from sklearn.impute import SimpleImputer |
Imputer removed in 0.22. Replaced by SimpleImputer in the new sklearn.impute module. API is nearly identical; constructor param missing_values now accepts np.nan by default. |
from sklearn.base import BaseEstimator |
from sklearn.base import BaseEstimator ✓ |
Not moved. BaseEstimator, TransformerMixin, ClassifierMixin, and RegressorMixin all remain in sklearn.base across all versions. |
from sklearn.cross_decomposition import PLSRegression |
from sklearn.cross_decomposition import PLSRegression ✓ |
Not moved. sklearn.cross_decomposition still exists and contains PLSRegression, PLSCanonical, and CCA. |
from sklearn.neighbors import LSHForest |
No direct replacement in sklearn | LSHForest was removed in 0.21 without a replacement. Use sklearn.neighbors.NearestNeighbors with algorithm 'ball_tree' or 'kd_tree', or switch to faiss for approximate nearest neighbor search. |
from sklearn.covariance import GraphLasso |
from sklearn.covariance import GraphicalLasso |
Renamed in 0.20. GraphLassoCV → GraphicalLassoCV as well. |
For the most common cases, the fix is a one-line change. Below are before/after examples for each category.
# Before (sklearn 0.17 and earlier)
from sklearn.cross_validation import train_test_split, StratifiedKFold, cross_val_score
from sklearn.grid_search import GridSearchCV, RandomizedSearchCV
from sklearn.learning_curve import learning_curve, validation_curve
# After (sklearn 0.18+, required in 1.x)
from sklearn.model_selection import train_test_split, StratifiedKFold, cross_val_score
from sklearn.model_selection import GridSearchCV, RandomizedSearchCV
from sklearn.model_selection import learning_curve, validation_curve
# Before (sklearn 0.22 and earlier)
from sklearn.externals import joblib
from sklearn.externals.joblib import Parallel, delayed
# After (sklearn 0.23+, required in 1.x)
import joblib
from joblib import Parallel, delayed
# Install if missing:
# pip install joblib
# Before (sklearn 0.19 and earlier)
from sklearn.preprocessing import Imputer
imp = Imputer(missing_values='NaN', strategy='mean', axis=0)
# After (sklearn 0.20+, required in 1.x)
from sklearn.impute import SimpleImputer
imp = SimpleImputer(missing_values=float('nan'), strategy='mean')
# Note: 'NaN' string is no longer valid; pass np.nan or float('nan')
# Note: axis parameter removed — SimpleImputer always operates column-wise
# Before (sklearn 0.20 and earlier)
from sklearn.utils.linear_assignment_ import linear_assignment
cost_matrix = build_cost_matrix()
assignment = linear_assignment(cost_matrix)
# assignment is shape (n, 2) where assignment[i] = [row, col]
# After (sklearn 0.21+)
from scipy.optimize import linear_sum_assignment
row_ind, col_ind = linear_sum_assignment(cost_matrix)
# Equivalent: list(zip(row_ind, col_ind)) reproduces the old row-pair format
If you need to run code that targets a specific older version of scikit-learn — for example to reproduce published results from a paper that lists scikit-learn 0.24 — the cleanest approach is a dedicated virtual environment with pinned dependencies.
# Create a new environment named after the project
python -m venv env-legacy-sklearn
source env-legacy-sklearn/bin/activate # Linux/macOS
# env-legacy-sklearn\Scripts\activate # Windows
# Install an exact scikit-learn version
pip install "scikit-learn==0.24.2"
# Verify
python -c "import sklearn; print(sklearn.__version__)"
# 0.24.2
For a project that should run on a specific version, pin it in requirements.txt:
scikit-learn==0.24.2
numpy==1.23.5
scipy==1.9.3
joblib==1.2.0
Use == for exact pinning when you need to guarantee a specific set of APIs. Use >= with an upper bound when you can tolerate a range:
# Allows any 1.x version but not 2.0 if it ever ships
scikit-learn>=1.0,<2.0
Install from the file with:
pip install -r requirements.txt
If you want to capture the exact state of an environment that is currently working, freeze it first:
pip freeze > requirements.txt
# Produces lines like:
# scikit-learn==1.3.2
# numpy==1.26.4
# scipy==1.12.0
# joblib==1.3.2
If you have already diagnosed the version mismatch and know which version to target, here is how to install or downgrade.
# Upgrade to latest scikit-learn (recommended for new projects)
pip install --upgrade scikit-learn
# With version floor
pip install "scikit-learn>=1.3"
# Pin to 0.24.2 (last version before several removals)
pip install "scikit-learn==0.24.2"
# pip will uninstall the current version and install the requested one
# Checking the result:
python -m sklearn --version
# scikit-learn 0.24.2
# Create a new conda environment with a specific scikit-learn version
conda create -n sklearn-legacy python=3.9 scikit-learn=0.24.2
conda activate sklearn-legacy
# Or update within an existing environment
conda install scikit-learn=1.3.2
When using conda, be aware that conda install scikit-learn and pip install scikit-learn in the same environment can install different versions into different locations. Always prefer one package manager per environment to avoid version conflicts.
If you are not sure whether a specific name exists in the version you have installed, inspect the module's contents directly using dir(). This is faster than reading changelogs and works regardless of which version you have.
import sklearn.model_selection
print(dir(sklearn.model_selection))
# ['BaseCrossValidator', 'BaseShuffleSplit', 'GridSearchCV', 'GroupKFold',
# 'GroupShuffleSplit', 'KFold', 'LeaveOneGroupOut', 'LeaveOneOut',
# 'LeavePGroupsOut', 'LeavePOut', 'ParameterGrid', 'ParameterSampler',
# 'PredefinedSplit', 'RandomizedSearchCV', 'RepeatedKFold',
# 'RepeatedStratifiedKFold', 'ShuffleSplit', 'StratifiedGroupKFold',
# 'StratifiedKFold', 'StratifiedShuffleSplit', 'TimeSeriesSplit',
# 'check_cv', 'cross_val_predict', 'cross_val_score', 'cross_validate',
# 'learning_curve', 'train_test_split', 'validation_curve']
import sklearn.impute
print(dir(sklearn.impute))
# ['IterativeImputer', 'KNNImputer', 'MissingIndicator', 'SimpleImputer']
# Note: 'Imputer' is NOT in this list — it was removed in 0.22
import sklearn.preprocessing
# Confirm Imputer is gone
print('Imputer' in dir(sklearn.preprocessing))
# False — on any version >= 0.22
You can also search across all sklearn submodules for a name using pkgutil:
import pkgutil
import importlib
import sklearn
def find_in_sklearn(target_name):
"""Search all sklearn submodules for a given class or function name."""
found = []
for importer, modname, ispkg in pkgutil.walk_packages(
path=sklearn.__path__,
prefix='sklearn.',
onerror=lambda x: None
):
try:
mod = importlib.import_module(modname)
if hasattr(mod, target_name):
found.append(modname)
except Exception:
pass
return found
print(find_in_sklearn('SimpleImputer'))
# ['sklearn.impute', 'sklearn.impute._base']
print(find_in_sklearn('GridSearchCV'))
# ['sklearn.model_selection', 'sklearn.model_selection._search']
print(find_in_sklearn('Imputer'))
# [] — confirms it is truly gone
This search is slow on large installations but is definitive: if the list is empty, the name does not exist in your installed version.
A frequent source of confusion is having multiple Python environments active, or having both pip and conda installed in the same environment. You can end up with one version of scikit-learn installed via pip and a different version visible to conda — and the one your script uses depends on which Python executable runs it.
# Check which Python binary your shell uses
which python
# /home/user/miniconda3/envs/myenv/bin/python
# Check which pip that Python uses
python -m pip --version
# pip 23.3.1 from /home/user/miniconda3/envs/myenv/lib/python3.10/site-packages/pip (python 3.10)
# Check scikit-learn through that exact Python
python -c "import sklearn; print(sklearn.__version__, sklearn.__file__)"
# 1.3.2 /home/user/miniconda3/envs/myenv/lib/python3.10/site-packages/sklearn/__init__.py
The key is to look at sklearn.__file__. The path tells you exactly which installation is being loaded. If the path is different from where you expect (for example it points to a system Python instead of your venv), your environment is not activated correctly.
# Diagnose stale environment: run this before importing sklearn
python -c "import sys; print(sys.prefix)"
# Should print the path to your active venv or conda environment
# If it prints /usr or /usr/local, you are using system Python, not your env
Notebooks add another layer of indirection: the kernel that runs your cells may be attached to a different Python than the one you used to install scikit-learn. You can check from inside the notebook:
import sys
print(sys.executable)
# /home/user/miniconda3/envs/myenv/bin/python
import sklearn
print(sklearn.__version__)
# 1.3.2
# If the version here differs from what `python -c "import sklearn; print(sklearn.__version__)"` shows
# in your terminal, the notebook kernel is using a different Python installation.
To fix a kernel mismatch, install the ipykernel package into the correct environment and register it:
conda activate myenv
pip install ipykernel
python -m ipykernel install --user --name myenv --display-name "Python (myenv)"
# Restart Jupyter and select "Python (myenv)" from the kernel menu
If you are trying to trace when a specific API disappeared, this timeline covers the major removal milestones:
sklearn.cross_validation, sklearn.grid_search, sklearn.learning_curve deprecated (deprecated means: import still works but emits a warning)sklearn.covariance.GraphLasso → GraphicalLassosklearn.utils.linear_assignment_ removed; LSHForest removedsklearn.preprocessing.Imputer removed (replaced by sklearn.impute.SimpleImputer)sklearn.externals.joblib removed; install joblib separatelyNotFittedError; several deprecated n_features_ attributes removed in favor of n_features_in_sklearn.utils.estimator_checks.parametrize_with_checks signature changed; feature_importances_std_ removed from RandomForestClassifierset_output API stabilized; older get_feature_names removed from most transformers in favor of get_feature_names_outOccasionally you update the import path but the error continues. The most common remaining causes are:
.pyc files. Python may cache old bytecode. Delete __pycache__ directories and .pyc files: find . -name "*.pyc" -delete && find . -name "__pycache__" -type d -exec rm -rf {} +imbalanced-learn, mlxtend, or eli5) internally import from the old sklearn paths. Upgrading those packages alongside scikit-learn usually resolves it: pip install --upgrade imbalanced-learn mlxtendpip show scikit-learn and check the Location field. If you see a path you do not recognize, you may have a system-level installation shadowing your venv one. Activate your venv and re-install.pip install -e . on a project that pins old sklearn, that project's code still uses the old import paths even if you upgrade sklearn globally. Update the source code of that project.If your model trains successfully but emits warnings during fitting, see Fix sklearn ConvergenceWarning: What It Means and How to Fix It — which covers the separate issue of iterative solvers hitting their iteration limit before converging.