Use pd.get_dummies() to turn categories into indicator columns. Choose the dtype, handle missing labels and keep a consistent category schema across datasets.
Call the top-level function pd.get_dummies(). It returns a new DataFrame without changing the source.
import pandas as pd
students = pd.DataFrame({
"ID": [1, 2, 3, 4, 5, 6],
"MATH": [80, 40, 70, 70, 70, 30],
"CLASS1": ["Four", "Three", "Three", "Four", "Five", "Three"]
})
print(students)Expected output
ID MATH CLASS1
0 1 80 Four
1 2 40 Three
2 3 70 Three
3 4 70 Four
4 5 70 Five
5 6 30 Threedata accepts an array-like object, Series or DataFrame. columns selects fields to encode. prefix and prefix_sep control labels; the default separator is an underscore. dummy_na adds a missing-value indicator. sparse uses sparse arrays. drop_first removes the first category indicator. dtype controls new indicator columns; the default is bool.
By default, object, string and category columns are encoded, while numeric columns remain unchanged. True indicates a matching category.
encoded = pd.get_dummies(students)
print(encoded)
assert encoded["CLASS1_Four"].tolist() == [True, False, False, True, False, False]
assert encoded["MATH"].equals(students["MATH"])Expected output
ID MATH CLASS1_Five CLASS1_Four CLASS1_Three
0 1 80 False True False
1 2 40 False False True
2 3 70 False False True
3 4 70 False True False
4 5 70 True False False
5 6 30 False False TrueSpecify dtype="int64" for 0 and 1 output. The meaning is the same as Boolean indicators.
encoded = pd.get_dummies(students, dtype="int64")
print(encoded)
assert encoded["CLASS1_Five"].tolist() == [0, 0, 0, 0, 1, 0]Expected output
ID MATH CLASS1_Five CLASS1_Four CLASS1_Three
0 1 80 0 1 0
1 2 40 0 0 1
2 3 70 0 0 1
3 4 70 0 1 0
4 5 70 1 0 0
5 6 30 0 0 1A prefix replaces the original field-name prefix. Set prefix_sep to choose the separator independently.
print(pd.get_dummies(students, prefix="my", dtype="int64"))
print(pd.get_dummies(students, prefix="my", prefix_sep="-", dtype="int64"))Expected output
ID MATH my_Five my_Four my_Three
0 1 80 0 1 0
1 2 40 0 0 1
2 3 70 0 0 1
3 4 70 0 1 0
4 5 70 1 0 0
5 6 30 0 0 1
ID MATH my-Five my-Four my-Three
0 1 80 0 1 0
1 2 40 0 0 1
2 3 70 0 0 1
3 4 70 0 1 0
4 5 70 1 0 0
5 6 30 0 0 1Explicit columns can include numeric fields. Encoding marks as categories removes numeric ordering and can create many columns; keep continuous measures numeric unless discrete categories are intended. Distinct prefixes prevent ambiguous labels.
encoded = pd.get_dummies(students, columns=["CLASS1", "MATH"],
prefix={"CLASS1": "my", "MATH": "mark"}, dtype="int64")
print(encoded)
assert "MATH" not in encoded.columns
assert encoded["mark_70"].sum() == 3Expected output
ID my_Five my_Four my_Three mark_30 mark_40 mark_70 mark_80
0 1 0 1 0 0 0 0 1
1 2 0 0 1 0 1 0 0
2 3 0 0 1 0 0 1 0
3 4 0 1 0 0 0 1 0
4 5 1 0 0 0 0 1 0
5 6 0 0 1 1 0 0 0Sparse arrays can help with many mostly-zero indicators. Measure memory with your data and check compatibility with the next tool; sparse output is not automatically smaller for every table.
encoded = pd.get_dummies(students, prefix="my", sparse=True, dtype="int64")
print(encoded)
print("Indicator dtype:", encoded["my_Four"].dtype)
assert isinstance(encoded["my_Four"].dtype, pd.SparseDtype)Expected output
ID MATH my_Five my_Four my_Three
0 1 80 0 1 0
1 2 40 0 0 1
2 3 70 0 0 1
3 4 70 0 1 0
4 5 70 1 0 0
5 6 30 0 0 1
Indicator dtype: Sparse[int64, 0]Five is omitted for these text values. All-zero indicators represent that category when there are no missing values. Some modelling designs use k-1 indicators; this is not required for every analysis.
encoded = pd.get_dummies(students, prefix="my", drop_first=True, dtype="int64")
print(encoded)
assert "my_Five" not in encoded.columns
assert encoded.loc[4, ["my_Four", "my_Three"]].sum() == 0Expected output
ID MATH my_Four my_Three
0 1 80 1 0
1 2 40 0 1
2 3 70 0 1
3 4 70 1 0
4 5 70 0 0
5 6 30 0 1Without dummy_na, a missing value produces all-zero indicators. dummy_na=True adds a missing indicator even when there are no missing values. With drop_first as well, a missing indicator distinguishes missing rows from the omitted category.
labels = pd.Series(["Book", "Pen", None, "Book"], dtype="string")
plain = pd.get_dummies(labels, prefix="product", dtype="int64")
explicit = pd.get_dummies(labels, prefix="product", dummy_na=True, dtype="int64")
print("Without missing indicator:")
print(plain)
print("With missing indicator:")
print(explicit)
assert plain.loc[2].sum() == 0
assert explicit.loc[2].sum() == 1Expected output
Without missing indicator:
product_Book product_Pen
0 1 0
1 0 1
2 0 0
3 1 0
With missing indicator:
product_Book product_Pen product_<NA>
0 1 0 0
1 0 1 0
2 0 0 1
3 1 0 0An explicit category definition controls indicator order and the category omitted by drop_first. Unknown labels become missing during categorical conversion, so validate them first.
kind = pd.CategoricalDtype(categories=["Four", "Three", "Five"], ordered=False)
classes = students["CLASS1"].astype(kind)
encoded = pd.get_dummies(classes, prefix="class", drop_first=True, dtype="int64")
print(encoded)
assert encoded.columns.tolist() == ["class_Three", "class_Five"]Expected output
class_Three class_Five
0 0 0
1 1 0
2 1 0
3 0 0
4 0 1
5 1 0This synthetic table includes inconsistent labels, a missing value and an unexpected channel. Normalize under a defined policy and retain source rows for review. See Data Cleaning and text case operations.
raw = pd.DataFrame({
"order_id": [101, 102, 103, 104, 105],
"channel": [" Web ", "STORE", "web", None, "fax"],
"amount": [20, 35, 15, 10, 8]
})
channel = raw["channel"].astype("string").str.strip().str.lower()
channel = channel.mask(channel.eq(""))
allowed = ["web", "store", "phone"]
unknown = channel.notna() & ~channel.isin(allowed)
review = raw.loc[channel.isna() | unknown].copy()
print("Unknown IDs:", raw.loc[unknown, "order_id"].tolist())
print(review)
assert review["order_id"].tolist() == [104, 105]Expected output
Unknown IDs: [105]
order_id channel amount
3 104 NaN 10
4 105 fax 8Keep known approved channels for this report. Phone is a valid category even though absent from the accepted rows. Keep order IDs for tracing records, not as categories to encode.
accepted = raw.loc[channel.notna() & ~unknown].copy()
accepted["channel"] = pd.Categorical(channel.loc[accepted.index], categories=allowed)
features = pd.get_dummies(accepted, columns=["channel"], prefix="channel", dtype="int64")
indicator_columns = ["channel_web", "channel_store", "channel_phone"]
print(features)
assert features["order_id"].tolist() == [101, 102, 103]
assert features[indicator_columns].sum(axis=1).eq(1).all()
assert len(accepted) + len(review) == len(raw)Expected output
order_id amount channel_web channel_store channel_phone
0 101 20 1 0 0
1 102 35 0 1 0
2 103 15 1 0 0Independent encoding can create different columns. Validate labels before applying saved categories, then use the agreed indicator order. For modelling, learn preprocessing from training data or a predefined schema rather than held-out evaluation data.
later = pd.DataFrame({"channel": ["phone", "web"]})
assert later["channel"].isin(allowed).all()
later["channel"] = pd.Categorical(later["channel"], categories=allowed)
later_features = pd.get_dummies(later, columns=["channel"], prefix="channel", dtype="int64")
later_features = later_features.reindex(columns=indicator_columns, fill_value=0)
print(later_features)
assert later_features["channel_phone"].tolist() == [1, 0]Expected output
channel_web channel_store channel_phone
0 0 0 1
1 1 0 0With one category per row and drop_first=False, every accepted row has one active indicator. Use value_counts() for simple frequencies and groupby() for revenue summaries. See Input and Output for files.
counts = features[indicator_columns].sum()
print(counts)
print(features.to_csv(index=False).rstrip())
assert counts.sum() == len(accepted)
assert counts["channel_web"] == 2
# Optional Colab export:
# features.to_csv("encoded_sales_channels.csv", index=False)Expected output
channel_web 2
channel_store 1
channel_phone 0
dtype: int64
order_id,amount,channel_web,channel_store,channel_phone
101,20,1,0,0
102,35,0,1,0
103,15,1,0,0Does encoding clean labels? No; normalize first under a defined policy. Why is a numeric field unchanged? Select it explicitly in columns to encode it. Should I encode customer IDs? Usually they identify records; high cardinality can create excessive columns. Does a multiword value split into categories? No; each whole value is a category. Why do batches have different columns? Their observed categories may differ; use a validated schema. Continue with unique(), cut(), row selection, plots, mean(), min(), sum() and text lengths.
Encode pickup, delivery, pickup and a missing value using integer indicators and dummy_na=True. Predict the row sums and pickup count.
Each row has one active indicator, including the missing row. Pickup occurs twice.
modes = pd.Series(["pickup", "delivery", "pickup", None], dtype="string")
answer = pd.get_dummies(modes, prefix="mode", dummy_na=True, dtype="int64")
print(answer)
print("Row sums:", answer.sum(axis=1).tolist())
assert answer.sum(axis=1).tolist() == [1, 1, 1, 1]
assert answer["mode_pickup"].sum() == 2Expected output
mode_delivery mode_pickup mode_<NA>
0 0 1 0
1 1 0 0
2 0 1 0
3 0 0 1
Row sums: [1, 1, 1, 1]Open in Google Colab View on GitHub
Run the examples in order, change channels and category definitions, then inspect indicators and review rows. Save your own copy to keep edits. All sample tables are included in the notebook.
Continue with the Data Cleaning hub, where(), loc, at, NumPy where(), fillna() and iloc.
Reference: Pandas get_dummies documentation.
Author & Instructor at plus2net
I write and maintain practical tutorials on Python, PHP, SQL, JavaScript, HTML, jQuery, and web development at plus2net. The tutorials focus on clear explanations, working examples, and code that readers can test and adapt while learning.