One hot encoding
One hot encoding turns a column of n categories into n columns of 0 and 1, one per category, where each row carries a single 1 in the column that names its category.
Last updated: 12 Oct, 2026 · pandas 3.0 · scikit-learn 1.9
Nominal and ordinal features ended on the encoder that claims nothing about order. This is that encoder, and the two arguments that decide what it produces today.
Syntax: pd.get_dummies takes the frame or the column and returns the indicator columns.
pd.get_dummies(df, dtype=int) # 0 and 1, not True and False
pd.get_dummies(df, drop_first=True, dtype=int) # n-1 columns per category
pd.get_dummies(df, columns=["Sex"], dtype=int) # only the named columnsThe clip encodes a binary gender column, first with get_dummies and then with Series.map, and argues for writing the result back into the frame rather than building a second one.
The page runs the same two calls on the Titanic Sex column, which holds female and male where the clip's column holds F and M.
What get_dummies returns now
The default dtype is bool
Called with no arguments, get_dummies returns True and False, not 1 and 0. That is a change from the uint8 older tutorials show. Models accept the boolean columns, and everything you print, add up or correlate afterwards reads differently.
pd.get_dummies(df["Sex"]) # dtype bool
pd.get_dummies(df["Sex"], dtype=int) # dtype int64drop_first keeps n-1 of the n columns
The columns come out in alphabetical order of the level, and drop_first removes the first of them. A row that belonged to the dropped level is the row whose remaining columns are all 0, so no information is lost.
pd.get_dummies(df["Sex"], drop_first=True, dtype=int) # female is droppedEncoding Sex, both ways
import pandas as pd
URL = ("https://raw.githubusercontent.com/krishnaik06/Feature-Engineering-Live-sessions/"
"master/titanic.csv")
df = pd.read_csv(URL, usecols=["Sex", "Embarked", "Survived"])
plain = pd.get_dummies(df["Sex"])
print("default dtypes:", dict(plain.dtypes))
print(plain.head(3).to_string())
print()
ints = pd.get_dummies(df["Sex"], drop_first=True, dtype=int)
print("with dtype=int:", dict(ints.dtypes))
print(ints.head(5).to_string())default dtypes: {'female': dtype('bool'), 'male': dtype('bool')}
female male
0 False True
1 True False
2 True False
with dtype=int: {'male': dtype('int64')}
male
0 1
1 0
2 0
3 0
4 1Reading the two frames
- The first call returned bool. Two columns named female and male, holding True and False. Multiplying, summing or correlating those columns works and prints values that look wrong to anyone expecting 1 and 0.
dtype=intreturned int64. The same information as 1 and 0, which is what every tutorial written before pandas 2 shows and what most downstream code assumes.drop_first=Trueleft one column. female came first alphabetically and was dropped, so the remaining column is male and a 0 in it means female.- The first five rows read 1, 0, 0, 0, 1. A male passenger, three female passengers and a male passenger, which is what the Titanic frame starts with.
Why n-1 columns carry n categories
Embarked has three levels, so this is where the argument becomes visible. With all three columns kept, they add up to 1 on every row, which means any one of them can be worked out from the other two. A linear model cannot separate three columns that always sum to a constant, and the extra column buys nothing.
import pandas as pd
URL = ("https://raw.githubusercontent.com/krishnaik06/Feature-Engineering-Live-sessions/"
"master/titanic.csv")
embarked = pd.read_csv(URL, usecols=["Embarked"])["Embarked"]
full = pd.get_dummies(embarked, dtype=int)
print("all levels :", list(full.columns))
print("row sums :", full.sum(axis=1).value_counts().to_dict())
cut = pd.get_dummies(embarked, drop_first=True, dtype=int)
print("drop_first :", list(cut.columns))
print("rows that are all zero:", int((cut.sum(axis=1) == 0).sum()))
print("C passengers in the raw column:", int((embarked == "C").sum()))
print("missing Embarked:", int(embarked.isnull().sum()))all levels : ['C', 'Q', 'S']
row sums : {1: 889, 0: 2}
drop_first : ['Q', 'S']
rows that are all zero: 170
C passengers in the raw column: 168
missing Embarked: 2What the row sums give away
- 889 rows sum to 1 and 2 rows sum to 0. The two rows summing to zero are the two passengers whose port is missing.
get_dummiesignores NaN rather than encoding it, so a missing value and a dropped level look identical. - After
drop_first, 170 rows are all zero. That is the 168 passengers who boarded at Cherbourg plus the 2 whose port is unknown. - Those two groups are now the same thing to the model. Fill or flag the missing values before encoding, which Missing as its own category covers, or keep all n columns so the all-zero pattern means only one thing.
- Three columns always sum to 1. So the third is the other two subtracted from 1, and dropping it removes a redundancy rather than information.
get_dummies vs OneHotEncoder
| pd.get_dummies | sklearn OneHotEncoder | |
|---|---|---|
| Returns | a DataFrame with named columns | an array, or a DataFrame with set_output |
| Remembers the training categories | no | yes, in categories_ after fit |
| A level only in the test data | makes a column the model never saw | ignored or raised, your choice |
| A training level missing from the test data | the column disappears | the column stays, filled with 0 |
| Drop one column | drop_first=True | drop='first' |
| Fits in a pipeline | no, it is a function | yes, it is a transformer |
The two middle rows are the reason the scikit-learn version exists, and Handling an unknown category builds the whole case on them.
Where one hot is the right choice
- A nominal column with few levels. Port, payment method, device type. The frame grows by a handful of columns and no order is invented.
- Before a linear or distance-based model. Linear regression, logistic regression and K nearest neighbours all read a coded column as a scale, so they need the indicator form.
- When the levels are fixed and known. A column whose levels come from a database enumeration will not surprise you with a new one.
pd.get_dummies reads whatever frame you hand it. Run it on the training data and then on the test data and you get two different sets of columns, with no error to tell you. A fitted encoder is the fix.Related
- Previous: Nominal and ordinal features
- Next: One hot encoding with many categories
- See also: Handling an unknown category
- Reference: pandas.get_dummies
- Drop
dtype=intfrom the second call and printints.sum(). The totals are the same and the dtype is not. - Pass
dummy_na=Trueto the Embarked call and count the rows that are all zero now. - Call
pd.get_dummies(df, columns=["Sex"], dtype=int)on the whole frame and look at which columns survived untouched.
You understood something today that you didn't yesterday.