XGBoost
feature_importance
plot_importance
feature_names
machine_learning

XGBoost plot_importance doesn't show feature names

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

Introduction

When xgboost.plot_importance shows labels such as f0, f1, and f2 instead of your real column names, the model usually lost the feature metadata somewhere between preprocessing and training. The fix is to make sure the booster receives named features, either by training from a DataFrame or by setting feature_names explicitly on the DMatrix or booster.

Why Feature Names Disappear

XGBoost stores feature importance on the trained booster, not on the original pandas object. If you train with a plain NumPy array, XGBoost has values but no column labels, so it invents placeholder names such as f0.

This is the pattern that often leads to generic names:

python
1import numpy as np
2import xgboost as xgb
3
4X = np.array([
5    [1.0, 30.0],
6    [2.0, 10.0],
7    [3.0, 25.0],
8    [4.0, 20.0],
9])
10y = np.array([0, 0, 1, 1])
11
12dtrain = xgb.DMatrix(X, label=y)
13model = xgb.train({"objective": "binary:logistic"}, dtrain, num_boost_round=5)
14
15print(model.get_score(importance_type="gain"))

The booster can rank features correctly, but it only knows them as f0 and f1.

Train from a pandas DataFrame When Possible

The easiest fix is to keep the data as a DataFrame so the column names travel with it.

python
1import pandas as pd
2import xgboost as xgb
3
4X = pd.DataFrame(
5    {
6        "age": [30, 10, 25, 20],
7        "income": [50000, 30000, 70000, 45000],
8    }
9)
10y = pd.Series([0, 0, 1, 1])
11
12dtrain = xgb.DMatrix(X, label=y)
13model = xgb.train({"objective": "binary:logistic"}, dtrain, num_boost_round=5)
14
15print(model.get_score(importance_type="gain"))

Now plot_importance can display age and income instead of anonymous placeholders.

If you are using the scikit-learn wrapper, the same principle applies:

python
1import pandas as pd
2from xgboost import XGBClassifier
3
4X = pd.DataFrame(
5    {
6        "age": [30, 10, 25, 20],
7        "income": [50000, 30000, 70000, 45000],
8    }
9)
10y = [0, 0, 1, 1]
11
12model = XGBClassifier(n_estimators=10, max_depth=2, eval_metric="logloss")
13model.fit(X, y)
14
15booster = model.get_booster()
16print(booster.feature_names)

Set feature_names Explicitly After Preprocessing

Many real pipelines use one-hot encoding or numeric transforms that produce a NumPy array. In that case, you need to carry the transformed names forward manually.

python
1import numpy as np
2import xgboost as xgb
3
4X = np.array([
5    [30.0, 1.0, 0.0],
6    [10.0, 0.0, 1.0],
7    [25.0, 1.0, 0.0],
8    [20.0, 0.0, 1.0],
9])
10y = np.array([0, 0, 1, 1])
11feature_names = ["age", "city_toronto", "city_montreal"]
12
13dtrain = xgb.DMatrix(X, label=y, feature_names=feature_names)
14model = xgb.train({"objective": "binary:logistic"}, dtrain, num_boost_round=5)
15
16print(model.feature_names)
17print(model.get_score(importance_type="gain"))

This is the correct pattern after a ColumnTransformer, manual encoding, or any other step that strips column labels.

Plot Importance from the Right Object

Another small but common issue is plotting from the wrong object. When you use XGBClassifier or XGBRegressor, call get_booster() and plot that booster.

python
1import matplotlib.pyplot as plt
2from xgboost import XGBClassifier, plot_importance
3
4model = XGBClassifier(n_estimators=10, max_depth=2, eval_metric="logloss")
5model.fit(X, y)
6
7plot_importance(model.get_booster(), importance_type="gain")
8plt.tight_layout()
9plt.show()

If the booster has names, the plot can use them. If the booster only has placeholder names, the plotting function cannot recover the original labels on its own.

Check Transformed Feature Names in Pipelines

The hardest version of this problem happens after preprocessing, because the original column names may no longer match the model input columns. For example, one categorical column can become many one-hot encoded columns. In that situation, using the original raw names is inaccurate. You need the post-transform names.

If you are using scikit-learn transformers, inspect the transformed names from the preprocessing step and pass those names into the final DMatrix or model. That keeps the feature importance chart aligned with what the model actually consumed.

Common Pitfalls

The most common mistake is training with a NumPy array and expecting XGBoost to remember the original DataFrame column names. Another frequent issue is setting feature names on the raw input but then fitting on transformed output that has a different number of columns. Teams also forget that the scikit-learn wrapper stores the real training state on the booster, not on a separate plotting object. Finally, after one-hot encoding, using the original pre-transform column names can produce a chart that looks labeled but is still wrong.

Summary

  • 'plot_importance can only display feature names that were stored on the trained booster.'
  • Train from a pandas DataFrame when possible so names are preserved automatically.
  • If preprocessing produces a NumPy array, pass feature_names explicitly to DMatrix.
  • When using the scikit-learn API, plot the object returned by get_booster().
  • After feature engineering, use the transformed column names rather than the raw input names.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

All Rights Reserved.