Scikit-Learn: TypeError: The feature_names parameter of plot_tree must be an instance of list or None
This error occurs when Python passes a pandas Index (df.columns) or a numpy array to plot_tree(feature_names=...). Convert feature_names explicitly to a Python list using feature_names=list(df.columns) or df.columns.tolist().
Root Cause Analysis
This error occurs when Python code visualizing a Decision Tree classifier or regressor calls sklearn.tree.plot_tree(clf, feature_names=df.columns) and passes a Pandas Index object, NumPy array, or tuple instead of a native Python list.
Root Cause 1: Strict Parameter Type Validation in Modern Scikit-Learn
Scikit-Learn 1.2+ introduced strict declarative parameter validation across all public functions and estimators (via @validate_params). The parameter specification for plot_tree strictly defines feature_names: [list of str, None]. In older versions, passing a Pandas Index (df.columns) was accepted via duck typing. In modern versions, passing a pd.Index or np.ndarray raises TypeError: The 'feature_names' parameter of plot_tree must be an instance of 'list' or None, got: Index(['col1', 'col2']).
Root Cause 2: Passing df.columns Directly Without .tolist()
Developers training models on Pandas DataFrames naturally pass feature_names=df.columns or feature_names=X.columns. Because df.columns returns a pandas.core.indexes.base.Index instance rather than a Python list, type validation fails immediately.
Root Cause 3: Passing NumPy Arrays from Model.feature_names_in_
clf.feature_names_in_ is stored as a NumPy array (np.ndarray). Passing feature_names=clf.feature_names_in_ fails with the same TypeError.
Root Cause 4: Class Names Parameter Experiencing Similar Constraint
The class_names parameter in plot_tree enforces identical type constraints: it requires a list of str or bool (or None), rejecting raw arrays or series.
Reproduction Code (MCVE)
# Simulating Scikit-Learn 1.2+ strict parameter validation in plot_tree
class MockPandasColumnsIndex:
def __init__(self, names: list):
self.names = names
def __repr__(self):
return f"Index({self.names}, dtype='object')"
def mock_plot_tree(decision_tree_model, feature_names=None):
# Scikit-Learn strict parameter validation check
if feature_names is not None and not isinstance(feature_names, list):
raise TypeError(
f"The 'feature_names' parameter of plot_tree must be an instance of 'list' or None, "
f"got: {type(feature_names).__name__} ({repr(feature_names)}). "
"Convert to list using list(df.columns) or df.columns.tolist()."
)
return "Tree plotted successfully"
# Passing raw Pandas Index object triggers TypeError
raw_columns = MockPandasColumnsIndex(["age", "income", "credit_score"])
mock_plot_tree(None, feature_names=raw_columns)
Solution 1: Convert df.columns to a Python List Using .tolist()
Convert the Pandas Index to a native list using list(df.columns) or df.columns.tolist() before passing it to plot_tree.
from sklearn.tree import DecisionTreeClassifier, plot_tree
import pandas as pd
import numpy as np
# Create sample dataset
df = pd.DataFrame({
"feature_a": [1.0, 2.0, 3.0, 4.0],
"feature_b": [10.0, 20.0, 15.0, 25.0],
"target": [0, 0, 1, 1]
})
X = df[["feature_a", "feature_b"]]
y = df["target"]
clf = DecisionTreeClassifier(max_depth=2)
clf.fit(X, y)
# Solution 1: Explicit conversion to list
feature_names_list = list(X.columns) # or X.columns.tolist()
class_names_list = ["Class_0", "Class_1"]
print("Feature names list type:", type(feature_names_list))
assert isinstance(feature_names_list, list)
# Verification that plot_tree receives valid list
# tree_nodes = plot_tree(clf, feature_names=feature_names_list, class_names=class_names_list)
print("Decision tree configured with valid feature names:", feature_names_list)
Solution 2: Convert clf.feature_names_in_ with tolist()
If referencing the fitted estimator's feature names, convert clf.feature_names_in_.tolist().
import numpy as np
# Solution 2: Convert numpy feature_names_in_ array to list
feature_array = np.array(["sepal_length", "sepal_width", "petal_length"])
safe_feature_list = feature_array.tolist()
print("Converted feature names:", safe_feature_list)
assert isinstance(safe_feature_list, list)
The same rule applies to class_names: if your classes are integers ([0, 1]), you must convert them to strings inside a list: class_names=[str(c) for c in clf.classes_]. Passing a list of integers will raise a TypeError stating that elements must be strings.
Contrast df.columns with df.columns.tolist(): df.columns is a Pandas Index; df.columns.tolist() produces a standard Python list.