ValueError: could not convert string to float" in sklearnThis error shows up the moment a scikit-learn estimator's fit() or predict() receives a column it can't treat as a number, almost always a categorical or text column left unencoded in a DataFrame that otherwise looks numeric. This article reproduces the error directly, covers the two common encoding fixes and where each one breaks, and the specific predict-time bug that a naive fix introduces without you noticing at training time.
Any DataFrame with a mix of numeric and string columns triggers this the moment it's passed to fit() without preprocessing:
import pandas as pd
from sklearn.linear_model import LinearRegression
df = pd.DataFrame({
'sqft': [1000, 1500, 2000, 1200],
'city': ['Santiago', 'Vina', 'Concepcion', 'Santiago'],
'price': [100000, 150000, 200000, 110000]
})
X = df[['sqft', 'city']]
y = df['price']
LinearRegression().fit(X, y)
# ValueError: could not convert string to float: 'Santiago'
The traceback points at whichever internal NumPy conversion step first tries to cast the column, not necessarily the line that looks most suspicious, so on a wider DataFrame it's easy to lose time scanning the wrong column. The actual cause is always the same: at least one column scikit-learn is being asked to treat as a numeric feature contains strings.
np.asarray(df[['sqft', 'city']], dtype=float) raises the identical error on its own, confirming the DataFrame's mixed dtypes are the real cause, not anything about the specific estimator.
For a one-off script, one-hot encoding the categorical column with pandas directly is the fastest path to something that runs:
X_dummies = pd.get_dummies(X, columns=['city'])
print(X_dummies.columns.tolist())
# ['sqft', 'city_Concepcion', 'city_Santiago', 'city_Vina']
model = LinearRegression().fit(X_dummies, y) # works
This resolves the immediate error. It also introduces a second, quieter bug that only surfaces later, at predict time, on different data.
pd.get_dummies() only creates columns for the categories actually present in whatever DataFrame you pass it. Training data and new data rarely contain the exact same set of categories, so calling it separately on each produces two DataFrames with different columns:
X_new = pd.DataFrame({'sqft': [1300], 'city': ['Vina']})
X_new_dummies = pd.get_dummies(X_new, columns=['city'])
print(X_new_dummies.columns.tolist())
# ['sqft', 'city_Vina'] -- only one city column, because only one city appears here
model.predict(X_new_dummies)
# ValueError: The feature names should match those that were passed during fit.
# Feature names seen at fit time, yet now missing:
# - city_Concepcion
# - city_Santiago
Training used three city columns because the training data contained three cities; this single-row prediction request only contains one, so get_dummies() only generates one. Scikit-learn correctly refuses to guess what the missing columns should contain and raises, rather than silently treating them as zero.
get_dummies() call, so this bug never appears during development. It shows up for the first time in a serving path, days or weeks later, on a single new row with a narrower category set than the training data, which is exactly the kind of environment where it's hardest to debug quickly.
Reaching for LabelEncoder instead of one-hot encoding avoids the column-mismatch problem entirely, since it always produces exactly one integer column. That's also what makes it the wrong tool here:
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
codes = le.fit_transform(['Santiago', 'Vina', 'Concepcion', 'Santiago'])
print(codes, list(le.classes_))
# [1 2 0 1] ['Concepcion', 'Santiago', 'Vina']
This fits without error and predicts without error. The problem is what it encodes: alphabetically-assigned integers with no real relationship to each other. A linear model fitted on this column will treat Vina (2) as numerically "twice" Santiago (1), an artifact of encoding order, not a fact about the data. Tree-based models are less affected since they only ever compare against a threshold, but for anything based on distance or a linear coefficient, this silently corrupts the feature, with no error or warning at any point to flag it. LabelEncoder's own documentation states it's intended for encoding the target variable y, not input features.
The version that avoids both problems above, fake ordering and predict-time column mismatches, wraps OneHotEncoder in a ColumnTransformer inside a Pipeline. The encoder remembers the exact categories seen at fit time and applies that same column layout to any future input, including handling a category it has never seen before:
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder
from sklearn.linear_model import LinearRegression
preprocessor = ColumnTransformer(
[('city_ohe', OneHotEncoder(handle_unknown='ignore'), ['city'])],
remainder='passthrough'
)
pipe = Pipeline([
('prep', preprocessor),
('model', LinearRegression())
])
pipe.fit(X, y)
# Same single-row prediction that broke get_dummies() above
pipe.predict(pd.DataFrame({'sqft': [1300], 'city': ['Vina']}))
# works, produces the correct column layout automatically
# A city never seen during training
pipe.predict(pd.DataFrame({'sqft': [1300], 'city': ['Valparaiso']}))
# also works, because handle_unknown='ignore' encodes it as all-zeros
# instead of raising
handle_unknown='ignore' is doing the real work here: without it, an unseen category at predict time raises instead of silently zeroing, which is usually the safer failure mode for anything running unattended, since you'd rather see an explicit error the first time it happens than a silent all-zeros row.
Situation Fix
──────────────────────────────────────────── ─────────────────────────────────────────────
One-off script or notebook, no future predict() pd.get_dummies(), fine as-is
Model will call predict() on new data later ColumnTransformer + OneHotEncoder in a Pipeline
Categories have a genuine order (low/medium/high) OrdinalEncoder with an explicit categories= list
Encoding the target y for classification LabelEncoder (its actual intended use)
Encoding an unordered input feature column Never LabelEncoder; see ColumnTransformer fix above