Fix ImportError: cannot import name from sklearn (Version Mismatch Guide)

Fix ImportError: cannot import name from sklearn (Version Mismatch Guide)

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 — Three Real Tracebacks

The error surface varies depending on which removed or moved name you are trying to import. Here are three representative tracebacks from real code.

Variant 1: Entire submodule removed

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)

Variant 2: Class moved to a new module

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)

Variant 3: Third-party package re-exported through sklearn.externals

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.


Step 1: Check Your sklearn Version

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 Big API Migration Table

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. GraphLassoCVGraphicalLassoCV as well.

Fix: Update the Import Paths in Your Code

For the most common cases, the fix is a one-line change. Below are before/after examples for each category.

cross_validation and grid_search

# 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

joblib

# 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

Imputer

# 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

linear_assignment

# 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

Fix: Virtual Environments and Pinning Versions

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.

Creating an isolated environment

# 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

requirements.txt pinning pattern

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

Fix: Installing the Right Version

If you have already diagnosed the version mismatch and know which version to target, here is how to install or downgrade.

Upgrade to current stable

# Upgrade to latest scikit-learn (recommended for new projects)
pip install --upgrade scikit-learn

# With version floor
pip install "scikit-learn>=1.3"

Downgrade to a specific old version

# 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

Using conda

# 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.


How to Check What Is Available in Your Installed Version

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.


Common Cause: pip and conda Seeing Different Versions

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

Jupyter notebooks and kernel mismatch

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

Quick-Reference: Version Removal Timeline

If you are trying to trace when a specific API disappeared, this timeline covers the major removal milestones:

  • 0.18sklearn.cross_validation, sklearn.grid_search, sklearn.learning_curve deprecated (deprecated means: import still works but emits a warning)
  • 0.20 — above three submodules removed; sklearn.covariance.GraphLassoGraphicalLasso
  • 0.21sklearn.utils.linear_assignment_ removed; LSHForest removed
  • 0.22sklearn.preprocessing.Imputer removed (replaced by sklearn.impute.SimpleImputer)
  • 0.23sklearn.externals.joblib removed; install joblib separately
  • 1.0 — attribute access on estimator without fitting raises NotFittedError; several deprecated n_features_ attributes removed in favor of n_features_in_
  • 1.2sklearn.utils.estimator_checks.parametrize_with_checks signature changed; feature_importances_std_ removed from RandomForestClassifier
  • 1.4set_output API stabilized; older get_feature_names removed from most transformers in favor of get_feature_names_out

If the Error Persists After Updating the Import

Occasionally you update the import path but the error continues. The most common remaining causes are:

  1. Stale .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 {} +
  2. A dependency re-exports the old path. Some older third-party packages (e.g., old versions of 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 mlxtend
  3. Two installations in the same environment. Run pip 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.
  4. editable install of an old package. If you ran 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.

See Also

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.