Dummy variables when not all categories are present
Question:
I have a set of dataframes where one of the columns contains a categorical variable. I’d like to convert it to several dummy variables, in which case I’d normally use get_dummies
.
What happens is that get_dummies
looks at the data available in each dataframe to find out how many categories there are, and thus create the appropriate number of dummy variables. However, in the problem I’m working right now, I actually know in advance what the possible categories are. But when looking at each dataframe individually, not all categories necessarily appear.
My question is: is there a way to pass to get_dummies
(or an equivalent function) the names of the categories, so that, for the categories that don’t appear in a given dataframe, it’d just create a column of 0s?
Something that would make this:
categories = ['a', 'b', 'c']
cat
1 a
2 b
3 a
Become this:
cat_a cat_b cat_c
1 1 0 0
2 0 1 0
3 1 0 0
Answers:
I don’t think get_dummies
provides this out of the box, it only allows for creating an extra column
that highlights NaN
values.
To add the missing columns
yourself, you could use pd.concat
along axis=0
to vertically ‘stack’ the DataFrames
(the dummy columns plus a DataFrame
id
) and automatically create any missing columns, use fillna(0)
to replace missing values, and then use .groupby('id')
to separate the various DataFrame
again.
Try this:
In[1]: import pandas as pd
cats = ["a", "b", "c"]
In[2]: df = pd.DataFrame({"cat": ["a", "b", "a"]})
In[3]: pd.concat((pd.get_dummies(df.cat, columns=cats), pd.DataFrame(columns=cats))).fillna(0)
Out[3]:
a b c
0 1.0 0.0 0
1 0.0 1.0 0
2 1.0 0.0 0
Using transpose and reindex
import pandas as pd
cats = ['a', 'b', 'c']
df = pd.DataFrame({'cat': ['a', 'b', 'a']})
dummies = pd.get_dummies(df, prefix='', prefix_sep='')
dummies = dummies.T.reindex(cats).T.fillna(0)
print dummies
a b c
0 1.0 0.0 0.0
1 0.0 1.0 0.0
2 1.0 0.0 0.0
TL;DR:
pd.get_dummies(cat.astype(pd.CategoricalDtype(categories=categories)))
- Older pandas:
pd.get_dummies(cat.astype('category', categories=categories))
is there a way to pass to get_dummies (or an equivalent function) the names of the categories, so that, for the categories that don’t appear in a given dataframe, it’d just create a column of 0s?
Yes, there is! Pandas has a special type of Series just for categorical data. One of the attributes of this series is the possible categories, which get_dummies
takes into account. Here’s an example:
In [1]: import pandas as pd
In [2]: possible_categories = list('abc')
In [3]: dtype = pd.CategoricalDtype(categories=possible_categories)
In [4]: cat = pd.Series(list('aba'), dtype=dtype)
In [5]: cat
Out[5]:
0 a
1 b
2 a
dtype: category
Categories (3, object): [a, b, c]
Then, get_dummies
will do exactly what you want!
In [6]: pd.get_dummies(cat)
Out[6]:
a b c
0 1 0 0
1 0 1 0
2 1 0 0
There are a bunch of other ways to create a categorical Series
or DataFrame
, this is just the one I find most convenient. You can read about all of them in the pandas documentation.
EDIT:
I haven’t followed the exact versioning, but there was a bug in how pandas treats sparse matrices, at least until version 0.17.0. It was corrected by version 0.18.1 (released May 2016).
For version 0.17.0, if you try to do this with the sparse=True
option with a DataFrame
, the column of zeros for the missing dummy variable will be a column of NaN
, and it will be converted to dense.
It looks like pandas 0.21.0 added a CategoricalDType
, and creating categoricals which explicitly include the categories as in the original answer was deprecated, I’m not quite sure when.
Adding the missing category in the test set:
# Get missing columns in the training test
missing_cols = set( train.columns ) - set( test.columns )
# Add a missing column in test set with default value equal to 0
for c in missing_cols:
test[c] = 0
# Ensure the order of column in the test set is in the same order than in train set
test = test[train.columns]
Notice that this code also remove column resulting from category in the test dataset but not present in the training dataset
I did ask this on the pandas github. Turns out it is really easy to get around it when you define the column as a Categorical
where you define all the possible categories.
df['col'] = pd.Categorical(df['col'], categories=['a', 'b', 'c', 'd'])
get_dummies()
will do the rest then as expected.
As suggested by others – Converting your Categorical features to ‘category’ data type should resolve the unseen label issue using ‘get_dummies‘.
# Your Data frame(df)
from sklearn.model_selection import train_test_split
X = df.loc[:,df.columns !='label']
Y = df.loc[:,df.columns =='label']
# Split the data into 70% training and 30% test
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.3)
# Convert Categorical Columns in your data frame to type 'category'
for col in df.select_dtypes(include=[np.object]).columns:
X_train[col] = X_train[col].astype('category', categories = df[col].unique())
X_test[col] = X_test[col].astype('category', categories = df[col].unique())
# Now, use get_dummies on training, test data and we will get same set of columns
X_train = pd.get_dummies(X_train,columns = ["Categorical_Columns"])
X_test = pd.get_dummies(X_test,columns = ["Categorical_Columns"])
The shorter the better:
import pandas as pd
cats = pd.Index(['a', 'b', 'c'])
df = pd.DataFrame({'cat': ['a', 'b', 'a']})
pd.get_dummies(df, prefix='', prefix_sep='').reindex(columns = cats, fill_value=0)
Result:
a b c
0 1 0 0
1 0 1 0
2 1 0 0
Notes:
cats
need to be a pandas index
prefix=''
and prefix_sep=''
need to be set in order to use the cats category as you defined in a first place. Otherwise, get_dummies
converts into: cats_a
, cats_b
and cats_c
). To me this is better because it is explicit.
- use the fill_value=0 to convert the
NaN
from column c
. Alternatively, you can use fillna(0)
at the end of the sentence. (I don’t which is faster).
Here’s a shorter-shorter version (changed the Index values):
import pandas as pd
cats = pd.Index(['cat_a', 'cat_b', 'cat_c'])
df = pd.DataFrame({'cat': ['a', 'b', 'a']})
pd.get_dummies(df).reindex(columns = cats, fill_value=0)
Result:
cat_a cat_b cat_c
0 1 0 0
1 0 1 0
2 1 0 0
Bonus track!
I imagine you have the categories because you did a previous dummy/one hot using training data. You can save the original encoding (.columns
), and then apply during production time:
cats = pd.Index(['cat_a', 'cat_b', 'cat_c']) # it might come from the original onehot encoding (df_ohe.columns)
import pickle
with open('cats.pickle', 'wb') as handle:
pickle.dump(cats, handle, protocol=pickle.HIGHEST_PROTOCOL)
with open('cats.pickle', 'rb') as handle:
saved_cats = pickle.load(handle)
df = pd.DataFrame({'cat': ['a', 'b', 'a']})
pd.get_dummies(df).reindex(columns = saved_cats, fill_value=0)
Result:
cat_a cat_b cat_c
0 1 0 0
1 0 1 0
2 1 0 0
I was recently looking to solve this same issue, but working with a multi-column dataframe and with two datasets (a train set and test set for a machine learning task). The test dataframe had the same categorical columns as the train dataframe, but some of these columns had missing categories that were present in the train dataframe.
I did not want to manually define all the possible categories for every column. Instead, I combined the train and test dataframes into one, called get_dummies, and then split that back into two.
# train_cat, test_cat are dataframes instantiated elsewhere
train_test_cat = pd.concat([train_cat, test_cat]
tran_test_cat = pd.get_dummies(train_test_cat, axis=0))
train_cat = train_test_cat.iloc[:train_cat.shape[0], :]
test_cat = train_test_cat.iloc[train_cat.shape[0]:, :]
If you know your categories you can first apply pd.get_dummies()
as you suggested and add the missing category columns afterwards.
This will create your example with the missing cat_c
:
import pandas as pd
categories = ['a', 'b', 'c']
df = pd.DataFrame(list('aba'), columns=['cat'])
df = pd.get_dummies(df)
print(df)
cat_a cat_b
0 1 0
1 0 1
2 1 0
Now simply add the missing category columns with a union operation (as suggested here).
possible_categories = ['cat_' + cat for cat in categories]
df = df.reindex(df.columns.union(possible_categories, sort=False), axis=1, fill_value=0)
print(df)
cat_a cat_b cat_c
0 1 0 0
1 0 1 0
2 1 0 0
Change the column to categorical and everything will work.
For eg.
df = pd.DataFrame({'a': ['one', 'two', 'three', 'three'],
'b':['hello', 'hello', 'hello', 'hello']})
# outputs this
a b
0 one hello
1 two hello
2 three hello
3 three hello
Now turn them into categorical variable like this:
a_categories = ['one', 'two', 'three', 'four']
b_categories = ['hello', 'world']
df['a'] = pd.Categorical(df['a'], categories=a_categories, ordered=False)
df['b'] = pd.Categorical(df['b'], categories=b_categories, ordered=False)
Get the dummies and it will look like this:
pd.get_dummies(df)
# outputs:
a_one a_two a_three a_four b_hello b_world
0 True False False False True False
1 False True False False True False
2 False False True False True False
3 False False True False True False
I have a set of dataframes where one of the columns contains a categorical variable. I’d like to convert it to several dummy variables, in which case I’d normally use get_dummies
.
What happens is that get_dummies
looks at the data available in each dataframe to find out how many categories there are, and thus create the appropriate number of dummy variables. However, in the problem I’m working right now, I actually know in advance what the possible categories are. But when looking at each dataframe individually, not all categories necessarily appear.
My question is: is there a way to pass to get_dummies
(or an equivalent function) the names of the categories, so that, for the categories that don’t appear in a given dataframe, it’d just create a column of 0s?
Something that would make this:
categories = ['a', 'b', 'c']
cat
1 a
2 b
3 a
Become this:
cat_a cat_b cat_c
1 1 0 0
2 0 1 0
3 1 0 0
I don’t think get_dummies
provides this out of the box, it only allows for creating an extra column
that highlights NaN
values.
To add the missing columns
yourself, you could use pd.concat
along axis=0
to vertically ‘stack’ the DataFrames
(the dummy columns plus a DataFrame
id
) and automatically create any missing columns, use fillna(0)
to replace missing values, and then use .groupby('id')
to separate the various DataFrame
again.
Try this:
In[1]: import pandas as pd
cats = ["a", "b", "c"]
In[2]: df = pd.DataFrame({"cat": ["a", "b", "a"]})
In[3]: pd.concat((pd.get_dummies(df.cat, columns=cats), pd.DataFrame(columns=cats))).fillna(0)
Out[3]:
a b c
0 1.0 0.0 0
1 0.0 1.0 0
2 1.0 0.0 0
Using transpose and reindex
import pandas as pd
cats = ['a', 'b', 'c']
df = pd.DataFrame({'cat': ['a', 'b', 'a']})
dummies = pd.get_dummies(df, prefix='', prefix_sep='')
dummies = dummies.T.reindex(cats).T.fillna(0)
print dummies
a b c
0 1.0 0.0 0.0
1 0.0 1.0 0.0
2 1.0 0.0 0.0
TL;DR:
pd.get_dummies(cat.astype(pd.CategoricalDtype(categories=categories)))
- Older pandas:
pd.get_dummies(cat.astype('category', categories=categories))
is there a way to pass to get_dummies (or an equivalent function) the names of the categories, so that, for the categories that don’t appear in a given dataframe, it’d just create a column of 0s?
Yes, there is! Pandas has a special type of Series just for categorical data. One of the attributes of this series is the possible categories, which get_dummies
takes into account. Here’s an example:
In [1]: import pandas as pd
In [2]: possible_categories = list('abc')
In [3]: dtype = pd.CategoricalDtype(categories=possible_categories)
In [4]: cat = pd.Series(list('aba'), dtype=dtype)
In [5]: cat
Out[5]:
0 a
1 b
2 a
dtype: category
Categories (3, object): [a, b, c]
Then, get_dummies
will do exactly what you want!
In [6]: pd.get_dummies(cat)
Out[6]:
a b c
0 1 0 0
1 0 1 0
2 1 0 0
There are a bunch of other ways to create a categorical Series
or DataFrame
, this is just the one I find most convenient. You can read about all of them in the pandas documentation.
EDIT:
I haven’t followed the exact versioning, but there was a bug in how pandas treats sparse matrices, at least until version 0.17.0. It was corrected by version 0.18.1 (released May 2016).
For version 0.17.0, if you try to do this with the sparse=True
option with a DataFrame
, the column of zeros for the missing dummy variable will be a column of NaN
, and it will be converted to dense.
It looks like pandas 0.21.0 added a CategoricalDType
, and creating categoricals which explicitly include the categories as in the original answer was deprecated, I’m not quite sure when.
Adding the missing category in the test set:
# Get missing columns in the training test
missing_cols = set( train.columns ) - set( test.columns )
# Add a missing column in test set with default value equal to 0
for c in missing_cols:
test[c] = 0
# Ensure the order of column in the test set is in the same order than in train set
test = test[train.columns]
Notice that this code also remove column resulting from category in the test dataset but not present in the training dataset
I did ask this on the pandas github. Turns out it is really easy to get around it when you define the column as a Categorical
where you define all the possible categories.
df['col'] = pd.Categorical(df['col'], categories=['a', 'b', 'c', 'd'])
get_dummies()
will do the rest then as expected.
As suggested by others – Converting your Categorical features to ‘category’ data type should resolve the unseen label issue using ‘get_dummies‘.
# Your Data frame(df)
from sklearn.model_selection import train_test_split
X = df.loc[:,df.columns !='label']
Y = df.loc[:,df.columns =='label']
# Split the data into 70% training and 30% test
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.3)
# Convert Categorical Columns in your data frame to type 'category'
for col in df.select_dtypes(include=[np.object]).columns:
X_train[col] = X_train[col].astype('category', categories = df[col].unique())
X_test[col] = X_test[col].astype('category', categories = df[col].unique())
# Now, use get_dummies on training, test data and we will get same set of columns
X_train = pd.get_dummies(X_train,columns = ["Categorical_Columns"])
X_test = pd.get_dummies(X_test,columns = ["Categorical_Columns"])
The shorter the better:
import pandas as pd
cats = pd.Index(['a', 'b', 'c'])
df = pd.DataFrame({'cat': ['a', 'b', 'a']})
pd.get_dummies(df, prefix='', prefix_sep='').reindex(columns = cats, fill_value=0)
Result:
a b c
0 1 0 0
1 0 1 0
2 1 0 0
Notes:
cats
need to be a pandas indexprefix=''
andprefix_sep=''
need to be set in order to use the cats category as you defined in a first place. Otherwise,get_dummies
converts into:cats_a
,cats_b
andcats_c
). To me this is better because it is explicit.- use the fill_value=0 to convert the
NaN
from columnc
. Alternatively, you can usefillna(0)
at the end of the sentence. (I don’t which is faster).
Here’s a shorter-shorter version (changed the Index values):
import pandas as pd
cats = pd.Index(['cat_a', 'cat_b', 'cat_c'])
df = pd.DataFrame({'cat': ['a', 'b', 'a']})
pd.get_dummies(df).reindex(columns = cats, fill_value=0)
Result:
cat_a cat_b cat_c
0 1 0 0
1 0 1 0
2 1 0 0
Bonus track!
I imagine you have the categories because you did a previous dummy/one hot using training data. You can save the original encoding (.columns
), and then apply during production time:
cats = pd.Index(['cat_a', 'cat_b', 'cat_c']) # it might come from the original onehot encoding (df_ohe.columns)
import pickle
with open('cats.pickle', 'wb') as handle:
pickle.dump(cats, handle, protocol=pickle.HIGHEST_PROTOCOL)
with open('cats.pickle', 'rb') as handle:
saved_cats = pickle.load(handle)
df = pd.DataFrame({'cat': ['a', 'b', 'a']})
pd.get_dummies(df).reindex(columns = saved_cats, fill_value=0)
Result:
cat_a cat_b cat_c
0 1 0 0
1 0 1 0
2 1 0 0
I was recently looking to solve this same issue, but working with a multi-column dataframe and with two datasets (a train set and test set for a machine learning task). The test dataframe had the same categorical columns as the train dataframe, but some of these columns had missing categories that were present in the train dataframe.
I did not want to manually define all the possible categories for every column. Instead, I combined the train and test dataframes into one, called get_dummies, and then split that back into two.
# train_cat, test_cat are dataframes instantiated elsewhere
train_test_cat = pd.concat([train_cat, test_cat]
tran_test_cat = pd.get_dummies(train_test_cat, axis=0))
train_cat = train_test_cat.iloc[:train_cat.shape[0], :]
test_cat = train_test_cat.iloc[train_cat.shape[0]:, :]
If you know your categories you can first apply pd.get_dummies()
as you suggested and add the missing category columns afterwards.
This will create your example with the missing cat_c
:
import pandas as pd
categories = ['a', 'b', 'c']
df = pd.DataFrame(list('aba'), columns=['cat'])
df = pd.get_dummies(df)
print(df)
cat_a cat_b
0 1 0
1 0 1
2 1 0
Now simply add the missing category columns with a union operation (as suggested here).
possible_categories = ['cat_' + cat for cat in categories]
df = df.reindex(df.columns.union(possible_categories, sort=False), axis=1, fill_value=0)
print(df)
cat_a cat_b cat_c
0 1 0 0
1 0 1 0
2 1 0 0
Change the column to categorical and everything will work.
For eg.
df = pd.DataFrame({'a': ['one', 'two', 'three', 'three'],
'b':['hello', 'hello', 'hello', 'hello']})
# outputs this
a b
0 one hello
1 two hello
2 three hello
3 three hello
Now turn them into categorical variable like this:
a_categories = ['one', 'two', 'three', 'four']
b_categories = ['hello', 'world']
df['a'] = pd.Categorical(df['a'], categories=a_categories, ordered=False)
df['b'] = pd.Categorical(df['b'], categories=b_categories, ordered=False)
Get the dummies and it will look like this:
pd.get_dummies(df)
# outputs:
a_one a_two a_three a_four b_hello b_world
0 True False False False True False
1 False True False False True False
2 False False True False True False
3 False False True False True False