Missing as its own category
Treating missing as its own category means replacing the gaps in a text column with a label such as Missing, so the absence becomes a value the model can learn from instead of a hole that has to be patched.
Last updated: 12 Oct, 2026 · pandas 3.0
Frequent category imputation filled a column that was 47.26 percent missing with its mode and the median price of the Gd grade fell from 206,950 to 149,525. This lesson does the same column properly, and the change is one line.
Syntax: Series.fillna('Missing'). The label is arbitrary and any string that cannot collide with a real category works. The column stays text, so every encoder that follows treats the new label as one more level.
df['FireplaceQu'] = df['FireplaceQu'].fillna('Missing') # one more level
df['GarageType'] = df['GarageType'].fillna('Missing') # the same everywhereLoading the house price data
These examples use the house price table and three of its columns: two text columns with missing values and the sale price to read the result against. Run this block first, then the example under it.
import pandas as pd
import matplotlib
matplotlib.use('svg')
import matplotlib.pyplot as plt
url = ("https://raw.githubusercontent.com/krishnaik06/"
"Advanced-House-Price-Prediction-/master/train.csv")
df = pd.read_csv(url, usecols=['FireplaceQu', 'GarageType', 'SalePrice'])Turning 690 gaps into a level
The fill is one call, and the result is a column with six levels instead of five. The new level is the largest one, which is honest: no fireplace is the most common thing to know about a house in this table.
df['FireplaceQu'] = df['FireplaceQu'].fillna('Missing')
print(df['FireplaceQu'].value_counts().to_string())
print()
print("levels now:", df['FireplaceQu'].nunique())
print("nulls left:", int(df['FireplaceQu'].isnull().sum()))
print("dtype:", df['FireplaceQu'].dtype)FireplaceQu Missing 690 Gd 380 TA 313 Fa 33 Ex 24 Po 20 levels now: 6 nulls left: 0 dtype: str
Reading the new level against the target
The test of whether the label deserves to exist is whether it behaves differently from the real categories. Grouping the sale price by the six levels answers it.
df['FireplaceQu'] = df['FireplaceQu'].fillna('Missing')
summary = (df.groupby('FireplaceQu')['SalePrice']
.agg(['size', 'median']).sort_values('median'))
print(summary.round(0).to_string())
fig, ax = plt.subplots(figsize=(7, 4))
summary['median'].plot.bar(ax=ax, rot=0,
color=['#d64541' if i == 'Missing' else '#3a6fd8'
for i in summary.index])
ax.set_ylabel('median SalePrice')
ax.set_title('Missing is a level of its own, and nearly the cheapest')
fig.savefig('chart.svg')size median FireplaceQu Po 20 131500.0 Missing 690 135000.0 Fa 33 158000.0 TA 313 187500.0 Gd 380 206950.0 Ex 24 314250.0
- Missing is 690 houses with a median price of 135,000. That is nearly 72,000 below the Gd grade and within 3,500 of Po, the cheapest real grade.
- The five real grades keep their prices. Nothing was poured into them, so Gd is still 206,950 and the ordering of the grades still makes sense.
- The absence turns out to mean something concrete. A missing fireplace grade in this table means the house has no fireplace, and houses without one are cheaper.
- The column is still text. It will be encoded like any other categorical column, with one extra level.
When a label beats a fill
| Fill with the mode | A category of its own | |
|---|---|---|
| Share missing it suits | a few percent | any, and especially a large one |
| What it assumes | the gaps are ordinary rows | the gap means something |
| Effect on the real categories | the modal one is diluted | none |
| Columns after | the same | the same, with one more level |
| Cost at encoding time | none | one more dummy column per encoded column |
| Reads as | a repaired column | a column that records what was unknown |
Where this is the first thing to reach for
- A column whose gap has a meaning in the domain. No fireplace, no garage, no second phone number: absence is a fact, not a loss.
- A column with a large share missing. No fill value is honest over half a column, and a label costs nothing.
- Before any categorical encoder. One hot encoding and the target based encoders need a complete column, and this is the cheapest way to give them one, as One hot encoding assumes.
fillna('NA') on a column that already uses NA for North America merges two different meanings into one level and nothing complains. Check with 'Missing' in df[col].unique() before choosing it.Related
- Previous: Frequent category imputation
- Next: SimpleImputer in scikit-learn
- See also: One hot encoding
- Reference: SimpleImputer with
strategy='constant'
- Apply the same fill to
GarageTypeand read the median price of its Missing level. - Change the label to
'unknown'and check that nothing downstream depends on the spelling. - Count the dummy columns
pd.get_dummies(df['FireplaceQu'], dtype=int)produces with and without the Missing level.
Every expert started right here.