¿Cuáles son los pros y los contras entre get_dummies (Pandas) y OneHotEncoder (Scikit-learn)?

84

Estoy aprendiendo diferentes métodos para convertir variables categóricas en numéricas para clasificadores de aprendizaje automático. Encontré el pd.get_dummiesmétodo y sklearn.preprocessing.OneHotEncoder()quería ver cómo se diferenciaban en términos de rendimiento y uso.

Encontré un tutorial sobre cómo usar OneHotEncoder()en https://xgdgsc.wordpress.com/2015/03/20/note-on-using-onehotencoder-in-scikit-learn-to-work-on-categorical-features/ desde la sklearndocumentación no fue muy útil sobre esta función. Tengo la sensación de que no lo estoy haciendo correctamente ... pero

¿Pueden algunos explicar los pros y los contras de usar pd.dummiesover sklearn.preprocessing.OneHotEncoder()y viceversa? Sé que OneHotEncoder()le da una matriz dispersa, pero aparte de eso, no estoy seguro de cómo se usa y cuáles son los beneficios del pandasmétodo. ¿Lo estoy usando de manera ineficiente?

import pandas as pd
import numpy as np
from sklearn.datasets import load_iris
sns.set()

%matplotlib inline

#Iris Plot
iris = load_iris()
n_samples, m_features = iris.data.shape

#Load Data
X, y = iris.data, iris.target
D_target_dummy = dict(zip(np.arange(iris.target_names.shape[0]), iris.target_names))

DF_data = pd.DataFrame(X,columns=iris.feature_names)
DF_data["target"] = pd.Series(y).map(D_target_dummy)
#sepal length (cm)  sepal width (cm)  petal length (cm)  petal width (cm)  \
#0                  5.1               3.5                1.4               0.2   
#1                  4.9               3.0                1.4               0.2   
#2                  4.7               3.2                1.3               0.2   
#3                  4.6               3.1                1.5               0.2   
#4                  5.0               3.6                1.4               0.2   
#5                  5.4               3.9                1.7               0.4   

DF_dummies = pd.get_dummies(DF_data["target"])
#setosa  versicolor  virginica
#0         1           0          0
#1         1           0          0
#2         1           0          0
#3         1           0          0
#4         1           0          0
#5         1           0          0

from sklearn.preprocessing import OneHotEncoder, LabelEncoder
def f1(DF_data):
    Enc_ohe, Enc_label = OneHotEncoder(), LabelEncoder()
    DF_data["Dummies"] = Enc_label.fit_transform(DF_data["target"])
    DF_dummies2 = pd.DataFrame(Enc_ohe.fit_transform(DF_data[["Dummies"]]).todense(), columns = Enc_label.classes_)
    return(DF_dummies2)

%timeit pd.get_dummies(DF_data["target"])
#1000 loops, best of 3: 777 µs per loop

%timeit f1(DF_data)
#100 loops, best of 3: 2.91 ms per loop
O.rka
fuente

Respuestas:

55

OneHotEncoderno puede procesar valores de cadena directamente. Si sus características nominales son cadenas, primero debe asignarlas a números enteros.

pandas.get_dummieses todo lo contrario. De forma predeterminada, solo convierte columnas de cadena en una representación de un solo uso, a menos que se especifiquen columnas.

nos
fuente
Hola @nos, perdón por la demora en responderte con esta respuesta
O.rka
1
Aparte de eso, ¿uno es eficiente sobre otro?
Ankit Seth
6
actualización, OneHotEncoderno se puede aplicar en cadenas también en la versión 0.20.0.
Bs He
15
@BsHe Ya no es cierto en sklearn 0.20.3: OneHotEncoder(sparse=False).fit_transform(pd.DataFrame(pd.Series(['good','bad','worst','good', 'good', 'bad'])))funciona, lo que significa que OneHotEncoderse pueden aplicar en agitaciones.
dzieciou
1
No puede codificar nuevos datos no vistos con pd.get_dummies.
gented el
127

Para el aprendizaje automático, es casi seguro que desea utilizar sklearn.OneHotEncoder. Para otras tareas como análisis simples, es posible que pueda usar pd.get_dummies, lo cual es un poco más conveniente.

Tenga en cuenta que sklearn.OneHotEncoderse ha actualizado en la última versión para que acepte cadenas para variables categóricas, así como enteros.

El quid de esto es que el sklearncodificador crea una función que persiste y luego se puede aplicar a nuevos conjuntos de datos que usan las mismas variables categóricas, con resultados consistentes .

from sklearn.preprocessing import OneHotEncoder

# Create the encoder.
encoder = OneHotEncoder(handle_unknown="ignore")
encoder.fit(X_train)    # Assume for simplicity all features are categorical.

# Apply the encoder.
X_train = encoder.transform(X_train)
X_test = encoder.transform(X_test)

Observe cómo aplicamos el mismo codificador que creamos a través X_traindel nuevo conjunto de datos X_test.

Considere lo que sucede si X_testcontiene niveles diferentes a los X_trainde una de sus variables. Por ejemplo, digamos que X_train["color"]contiene solo "red"y "green", pero además de esos, a X_test["color"]veces contiene "blue".

Si usamos pd.get_dummies, X_testterminará con una "color_blue"columna adicional que X_trainno tiene, y la inconsistencia probablemente romperá nuestro código más adelante, especialmente si estamos alimentando X_testun sklearnmodelo en el que entrenamos X_train.

Y si queremos procesar los datos como este en producción, donde estamos recibiendo un solo ejemplo a la vez, pd.get_dummiesno será útil.

Por sklearn.OneHotEncoderotro lado, una vez que hemos creado el codificador, podemos reutilizarlo para producir la misma salida cada vez, con columnas solo para "red"y "green". Y podemos controlar explícitamente lo que sucede cuando se encuentra con el nuevo nivel "blue": si creemos que eso es imposible, entonces podemos decirle que arroje un error handle_unknown="error"; de lo contrario, podemos decirle que continúe y simplemente establecer las columnas roja y verde en 0, con handle_unknown="ignore".

Denziloe
fuente
22
Creo que esta respuesta tiene un impacto mucho mayor que el aceptado. La verdadera magia es manejar características categóricas desconocidas que seguramente aparecerán en producción.
barker
2
Creo que esta es una respuesta mejor y más completa que la respuesta aceptada.
Chiraz BenAbdelkader
1
Si. En mi humilde opinión, esta es una mejor respuesta que la respuesta aceptada.
dami.max
1
Sip . Esta respuesta definitivamente explica mejor por qué one_hot_encoder podría ser mejor junto con un ejemplo claro
Binod Mathews
1
Esa fue una hermosa explicación
Felicitaciones
4

¿Por qué no almacenar en caché o guardar las columnas como variable col_list del get_dummies resultante y luego usar pd.reindex para alinear los conjuntos de datos de tren vs prueba ... ejemplo:

df = pd.get_dummies(data)
col_list = df.columns.tolist()

new_df = pd.get_dummies(new_data)
new_df = new_df.reindex(columns=col_list).fillna(0.00) 
Carl
fuente
¿Cómo responde esto a la pregunta?
jorijnsmit
más para refutar el comentario anterior de que Sklearn OHE es superior debido a handle_unknown. Lo mismo se puede lograr usando pandas reindex.
Carl
Puede haber un problema furtivo con el uso de get_dummies, excepto como una ejecución única. ¿Qué sucede si tiene drop_first = True y la siguiente muestra no incluye el valor eliminado?
Casa de la Moneda
2

Realmente me gustó la respuesta de Carl y la voté a favor. Simplemente ampliaré un poco el ejemplo de Carl para que, con suerte, más personas aprecien que pd.get_dummies puede manejar unknown. Los dos ejemplos siguientes muestran que pd.get_dummies puede lograr lo mismo en el manejo de desconocido que OHE.

# data is from @dzieciou's comment above
>>> data =pd.DataFrame(pd.Series(['good','bad','worst','good', 'good', 'bad']))
# new_data has two values that data does not have. 
>>> new_data= pd.DataFrame(
pd.Series(['good','bad','worst','good', 'good', 'bad','excellent', 'perfect']))

Usando pd.get_dummies

>>> df = pd.get_dummies(data)
>>> col_list = df.columns.tolist()
>>> print(df)
   0_bad  0_good  0_worst
0      0       1        0
1      1       0        0
2      0       0        1
3      0       1        0
4      0       1        0
5      1       0        0
6      0       0        0
7      0       0        0

>>> new_df = pd.get_dummies(new_data)
# handle unknow by using .reindex and .fillna()
>>> new_df = new_df.reindex(columns=col_list).fillna(0.00)
>>> print(new_df)
#    0_bad  0_good  0_worst
# 0      0       1        0
# 1      1       0        0
# 2      0       0        1
# 3      0       1        0
# 4      0       1        0
# 5      1       0        0
# 6      0       0        0
# 7      0       0        0

Usando OneHotEncoder

>>> encoder = OneHotEncoder(handle_unknown="ignore", sparse=False)
>>> encoder.fit(data)
>>> encoder.transform(new_data)
# array([[0., 1., 0.],
#        [1., 0., 0.],
#        [0., 0., 1.],
#        [0., 1., 0.],
#        [0., 1., 0.],
#        [1., 0., 0.],
#        [0., 0., 0.],
#        [0., 0., 0.]])
Sarah
fuente
¿Puede expandir su respuesta para incluir un ejemplo con drop_first = True, y luego también mostrar nuevos datos que no incluyen el valor eliminado?
Casa de la Moneda