Skip to content

Fix sklearn "X does not have valid feature names" Warning

Tested with: scikit-learn 1.9.1, pandas 3.0.6, numpy 2.5.3, scipy 1.18.1, Python 3.12.3, Linux (Intel i5-7500). Core warnings and errors also checked on scikit-learn 1.3.2 with pandas 2.2.3 (identical text). Last run 2026-09-27.

TL;DR: the estimator was fitted on a pandas DataFrame and is now getting a NumPy array (or the reverse). It still computes a result; the warning says it could not check columns by name.
  1. Pass a DataFrame everywhere: build new rows as pd.DataFrame(rows)[model.feature_names_in_] instead of np.array(...) or df.values.
  2. Chained transformers: call .set_output(transform="pandas") (or put everything in one Pipeline) so the next step sees names, not an array.
  3. "The feature names should match those that were passed during fit": a real error. Columns are renamed, missing, extra or in a different order. df.reindex(columns=model.feature_names_in_) fixes order and drops extras.
  4. Integer or mixed column names: all-int names are silently not stored; a str/int mix raises TypeError. Fix with X.columns = X.columns.astype(str).

If you fit on a pandas DataFrame, the estimator stores the column names in feature_names_in_. When a later transform() or predict() gets a plain NumPy array, or a DataFrame when fit got an array, scikit-learn emits a UserWarning and carries on. This article covers what triggers it, when it is a real bug and when it is noise, and the ValueError and TypeError variants that look related.

Reproduced: Each Warning/Error and the Fix That Worked

Every row was run on the versions in the header; the text is copied from the output.

What was runExact warning / errorFix that worked
StandardScaler().fit(df), then transform(df.values)UserWarning: X does not have valid feature names, but StandardScaler was fitted with feature namesPass the DataFrame; for new rows pd.DataFrame([payload])[scaler.feature_names_in_]
LinearRegression().fit(X_df, y), then predict(np.array([[5, 10]]))Same warning with LinearRegressionSame
fit(df.values), then transform(df)UserWarning: X has feature names, but StandardScaler was fitted without feature namesFit on the DataFrame too
Scaler output (ndarray) fed to a model fitted on a DataFrameX does not have valid feature names, but LogisticRegression was fitted with feature names.set_output(transform="pandas") or one Pipeline
DataFrame with columns in a different orderValueError: The feature names should match those that were passed during fit.
Feature names must be in the same order as they were in fit.
X.reindex(columns=model.feature_names_in_)
Renamed, missing or extra columnSame ValueError, listing Feature names unseen at fit time: and/or Feature names seen at fit time, yet now missing:Rename back; reindex drops extras; a missing column has to be supplied
Fit on columns ['n', 1, 2, 3] (str + int, e.g. after pd.concat with integer dummies)TypeError: Feature names are only supported if all input features have string names, but your input has ['int', 'str'] as feature name / column name types. ...X.columns = X.columns.astype(str)
Fit on all-int columns (pd.DataFrame(ndarray))No warning, and no feature_names_in_ stored, so no name checks laterastype(str) or give real names
ColumnTransformer + LogisticRegression pipeline, predict on reordered or extra columnsNo warning; it selects columns by nameNone needed
Same pipeline, predict on X.valuesValueError: Specifying the columns using strings is only supported for dataframes.Pass the DataFrame
Same pipeline, a column missingValueError: columns are missing: {'income'}Supply the column

What the Warning Means

The two directions, as printed by scikit-learn 1.9.1:

UserWarning: X does not have valid feature names, but StandardScaler was fitted with feature names

UserWarning: X has feature names, but StandardScaler was fitted without feature names

Both come from the same underlying mechanism: when fit() receives a pandas DataFrame, sklearn stores the column names on the estimator as feature_names_in_. Every subsequent call checks whether the new input's "has names" state matches what was recorded at fit time. A mismatch in either direction triggers the warning; it does not require the names themselves to be different, just present-vs-absent.

The warning is emitted from sklearn/utils/validation.py, so the traceback points there rather than at your line. It is not new: scikit-learn 1.3.2 printed the same two messages word for word.

Cause 1: Fit on a DataFrame, Transform/Predict a NumPy Array

The most common trigger: training code uses a DataFrame (because that's what pd.read_csv() returns), but a downstream inference or serving path converts to a raw array first, often via .values, .to_numpy(), or a JSON payload turned straight into an array.

import pandas as pd
from sklearn.preprocessing import StandardScaler

df = pd.DataFrame({'age': [25, 32, 47], 'income': [40000, 55000, 82000]})

scaler = StandardScaler()
scaler.fit(df)  # feature_names_in_ = ['age', 'income']

# Triggers the warning: NumPy array, but scaler expects named columns
scaled = scaler.transform(df.values)
# UserWarning: X does not have valid feature names, but StandardScaler was fitted with feature names

The identical pattern shows up with any estimator, not just transformers: predict() on a model fitted with a DataFrame behaves the same way.

from sklearn.linear_model import LinearRegression

X = pd.DataFrame({'x1': [1, 2, 3, 4], 'x2': [2, 4, 6, 8]})
y = [1, 2, 3, 4]

model = LinearRegression().fit(X, y)
predictions = model.predict(X.values)
# UserWarning: X does not have valid feature names, but LinearRegression was fitted with feature names

Cause 2: Fit on a NumPy Array, Transform a DataFrame

The reverse case happens when a model is trained on array data (e.g. loaded from a .npy file or a non-pandas pipeline) and later reused with DataFrame input during evaluation or serving:

import pandas as pd
from sklearn.preprocessing import StandardScaler

df = pd.DataFrame({'a': [1, 2, 3], 'b': [4, 5, 6]})

scaler = StandardScaler()
scaler.fit(df.values)  # no feature_names_in_ recorded, plain array

scaled = scaler.transform(df[['b', 'a']])
# UserWarning: X has feature names, but StandardScaler was fitted without feature names

In this example the column order was also swapped (['b', 'a'] instead of ['a', 'b']). The scaler has no names to compare against, so it scaled b with a's mean and scale and returned values like 4.899 and -4.899 instead of numbers near zero. The only signal was the generic "has feature names" warning.

The Related ValueError: Mismatched Feature Names

A different, stricter case: if both sides have names, and the names themselves disagree, sklearn raises a hard ValueError instead of a warning. This one blocks execution rather than just alerting you:

import pandas as pd
from sklearn.preprocessing import StandardScaler

df = pd.DataFrame({'a': [1, 2, 3], 'b': [4, 5, 6]})
scaler = StandardScaler().fit(df)

df_renamed = df.rename(columns={'a': 'x', 'b': 'y'})
scaler.transform(df_renamed)
ValueError: The feature names should match those that were passed during fit.
Feature names unseen at fit time:
- x
- y
Feature names seen at fit time, yet now missing:
- a
- b

The same ValueError covers the other mismatches. With scaler fitted on ['a', 'b']:

# scaler.transform(df[['b', 'a']])       reordered
ValueError: The feature names should match those that were passed during fit.
Feature names must be in the same order as they were in fit.

# scaler.transform(df[['a']])            missing column
ValueError: The feature names should match those that were passed during fit.
Feature names seen at fit time, yet now missing:
- b

# scaler.transform(df.assign(c=[7, 8, 9]))   extra column
ValueError: The feature names should match those that were passed during fit.
Feature names unseen at fit time:
- c

A bare estimator is strict about order and extra columns. A ColumnTransformer is not: it picks columns by name, so a pipeline starting with one accepted reordered columns and an extra column without any warning. A missing column still fails there, with ValueError: columns are missing: {'income'}.

Warning vs. error: the UserWarning variants (Causes 1 and 2 above) happen when one side has names and the other doesn't, so sklearn can't fully check and just warns. The ValueError happens when both sides have names and they don't match, so sklearn can fully check, and refuses to proceed. If you're only ever seeing the warning and never this error, your column names are consistent when both sides have them; the warning is purely about the has-names/no-names mismatch.

Integer and Mixed Column Names

Feature names are only stored when every column name is a string. Two things happen otherwise, and neither involves the warning above:

import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler

ints = pd.DataFrame(np.arange(6).reshape(3, 2))   # columns 0, 1
s = StandardScaler().fit(ints)
hasattr(s, 'feature_names_in_')                   # False: names silently not stored

raw = pd.DataFrame({'n': [1, 2, 3], 'code': [1, 2, 3]})
dummies = pd.get_dummies(raw['code'].astype('category'))   # columns 1, 2, 3
X = pd.concat([raw[['n']], dummies], axis=1)      # columns 'n', 1, 2, 3
StandardScaler().fit(X)
TypeError: Feature names are only supported if all input features have string names, but your input has ['int', 'str'] as feature name / column name types. If you want feature names to be stored and validated, you must convert them all to strings, by using X.columns = X.columns.astype(str) for example. Otherwise you can remove feature / column names from your input data, or convert them all to a non-string data type.

The fix is what the message says: X.columns = X.columns.astype(str), after which feature_names_in_ was ['n', '1', '2', '3']. Do it before fit and in the same place at predict time. get_dummies on a string column is fine by itself: it produced names like color_b, color_g (as bool columns in pandas 3.0.6). The integer names come from pd.DataFrame(some_array) or dummies of integer categories. A model fitted on all-int columns also warned X has feature names, but StandardScaler was fitted without feature names when later given string-named columns.

When This Is Actually a Bug, Not Noise

The warning itself does not mean your results are wrong. If a NumPy array's columns are in the exact same order sklearn learned during fit(), the underlying computation is identical with or without names attached, so the warning is purely informational in that case.

It becomes a real bug in one specific situation: a NumPy array's column order silently differs from what the estimator was fitted on. Because arrays carry no names to check, sklearn cannot detect this case at all. You get no warning, no error, just wrong predictions computed from mismatched features. Keeping DataFrames (or checking against feature_names_in_) all the way through is what lets sklearn catch it.

A concrete run: a LinearRegression fitted on an array with columns a, b predicted [1, 3, 2, 4] on the training rows. The same rows with the two columns swapped gave [0.1, 0.2, 0.3, 0.4], with no warning and no error.

SituationRisk
DataFrame → array, same column order preservedCosmetic warning only, results correct
DataFrame → array, column order silently changedReal bug: wrong predictions, no error raised
DataFrame → DataFrame, columns renamed/reorderedCaught: raises ValueError, blocks execution

Fixes, by Scenario

Fix 1: Keep DataFrames end-to-end

Don't convert to a NumPy array between fit() and transform()/predict(). If your serving code receives a dict or JSON payload, build a single-row DataFrame from it instead of an array:

import pandas as pd

# Instead of: np.array([payload['age'], payload['income']]).reshape(1, -1)
X_new = pd.DataFrame([payload])[scaler.feature_names_in_]
scaled = scaler.transform(X_new)  # no warning; columns picked by name, in fit order

Indexing with [scaler.feature_names_in_] also reorders the incoming columns to the fit-time order, so the silent column-swap case in the table above cannot happen here.

Fix 2: Chained transformers: set_output(transform="pandas") or a Pipeline

A common hidden source: you fit a scaler on a DataFrame, feed its output (a NumPy array by default) to a model, then at predict time mix the two. With set_output the transformer returns a DataFrame with the original column names, so the model is fitted with names and later calls stay consistent:

from sklearn.linear_model import LogisticRegression

scaler = StandardScaler().set_output(transform="pandas").fit(X_train)
model = LogisticRegression().fit(scaler.transform(X_train), y_train)
model.feature_names_in_            # array(['age', 'income'], dtype=object)
model.predict(scaler.transform(X_new))   # no warning

set_output also works on a whole Pipeline, and sklearn.set_config(transform_output="pandas") turns it on globally. The cleaner option is to put preprocessing and model in one pipeline and pass it the raw DataFrame:

from sklearn.compose import ColumnTransformer
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import OneHotEncoder

pre = ColumnTransformer([
    ('num', StandardScaler(), ['age', 'income']),
    ('cat', OneHotEncoder(handle_unknown='ignore'), ['city']),
])
pipe = make_pipeline(pre, LogisticRegression()).fit(X_train, y_train)
pipe.predict(X_new)   # DataFrame in; columns selected by name

Fix 3: Reindex incoming data to the training columns

For the ValueError (order or extra columns), line the input up with what fit saw:

X_new = X_new.reindex(columns=model.feature_names_in_)

This reorders and drops extras. It does not invent missing columns: reindex fills them with NaN, and the pipeline above then failed with ValueError: Input X contains NaN. Check set(model.feature_names_in_) - set(X_new.columns) first if a column can go missing.

Fix 4: If you must use arrays, verify order explicitly

When a NumPy array is unavoidable (e.g. a hot inference path that can't afford DataFrame overhead), compare against feature_names_in_ once at startup rather than trusting positional order silently:

expected = list(scaler.feature_names_in_)
actual_order = ['age', 'income']  # whatever produces your array's column order

assert actual_order == expected, (
    f"Column order mismatch: array provides {actual_order}, "
    f"model expects {expected}"
)
scaled = scaler.transform(some_array)  # still warns, but order is now verified safe

Fix 5: Suppress the warning (only after confirming order is safe)

If you've verified column order is always consistent and the warning is just log noise, scope the suppression narrowly rather than globally:

import warnings

with warnings.catch_warnings():
    warnings.filterwarnings('ignore', message='X does not have valid feature names')
    scaled = scaler.transform(X_array)

Two things this does not do. It does not hide the ValueError for mismatched names, since that is an exception, not a warning. And avoid the popular global version, warnings.filterwarnings('ignore', category=UserWarning, module='sklearn'): ConvergenceWarning subclasses UserWarning, and in our run that filter also silenced a ConvergenceWarning from LogisticRegression(max_iter=1).

Summary: which fix applies

Symptom                                          Fix
────────────────────────────────────────────     ─────────────────────────────────────────────
UserWarning, array after DataFrame fit            Keep DataFrames end-to-end (Fix 1), set_output / Pipeline (Fix 2)
UserWarning, DataFrame after array fit             Fit on the DataFrame as well
ValueError: feature names should match             Reindex to feature_names_in_ (Fix 3), rename back
TypeError: ... all input features have string names  X.columns = X.columns.astype(str)
Warning is confirmed harmless, just noisy logs     Scope a message-based filter (Fix 5)