Jakie są wady i zalety między get_dummies (Pandy) a OneHotEncoder (Scikit-learn)?

92

Uczę się różnych metod konwersji zmiennych kategorialnych na numeryczne dla klasyfikatorów uczenia maszynowego. Natknąłem się na tę pd.get_dummiesmetodę i sklearn.preprocessing.OneHotEncoder()chciałem zobaczyć, jak różnią się pod względem wydajności i użytkowania.

Znalazłem poradnik, jak używać OneHotEncoder()na https://xgdgsc.wordpress.com/2015/03/20/note-on-using-onehotencoder-in-scikit-learn-to-work-on-categorical-features/ od sklearndokumentacja nie był zbyt pomocny w tej funkcji. Mam wrażenie, że nie robię tego poprawnie ... ale

Czy ktoś może wyjaśnić zalety i wady używania w pd.dummieskółko sklearn.preprocessing.OneHotEncoder()i na odwrót? Wiem, że OneHotEncoder()daje to rzadką matrycę, ale poza tym nie jestem pewien, jak jest używana i jakie są korzyści z tej pandasmetody. Czy używam go nieefektywnie?

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
źródło

Odpowiedzi:

56

OneHotEncodernie może bezpośrednio przetwarzać wartości łańcuchowych. Jeśli twoje cechy nominalne są ciągami, musisz najpierw zmapować je na liczby całkowite.

pandas.get_dummiesjest trochę odwrotnie. Domyślnie konwertuje tylko kolumny łańcuchowe na jedną gorącą reprezentację, chyba że określono kolumny.

nr
źródło
Hej @nos, przepraszam za opóźnienie w odpowiedzi na tę odpowiedź
O.rka
1
Poza tym, czy jeden jest skuteczniejszy od drugiego?
Ankit Seth
6
aktualizacja, OneHotEncodernie można jej również zastosować do stringów w wersji 0.20.0.
Bs He
15
@BsHe Nie jest już prawdziwe w sklearn 0.20.3: OneHotEncoder(sparse=False).fit_transform(pd.DataFrame(pd.Series(['good','bad','worst','good', 'good', 'bad'])))działa, co oznacza, że OneHotEncodermoże być stosowany do mieszania.
dzieciou
1
Nie możesz zakodować nowych niewidocznych danych za pomocą pd.get_dummies.
gented
140

W przypadku uczenia maszynowego prawie na pewno chcesz użyć sklearn.OneHotEncoder. W przypadku innych zadań, takich jak proste analizy, możesz użyć pd.get_dummies, co jest nieco wygodniejsze.

Zauważ, że sklearn.OneHotEncoderzostał zaktualizowany w najnowszej wersji, więc akceptuje ciągi znaków dla zmiennych kategorialnych, jak również liczby całkowite.

Sednem tego jest to, że sklearnkoder tworzy funkcję, która jest trwała i może być następnie zastosowana do nowych zestawów danych, które używają tych samych zmiennych kategorialnych, ze spójnymi wynikami .

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)

Zwróć uwagę, jak stosujemy ten sam koder, który utworzyliśmy, X_traindo nowego zestawu danych X_test.

Zastanów się, co się stanie, jeśli X_testzawiera inne poziomy niż X_traindla jednej z jego zmiennych. Na przykład, powiedzmy, że X_train["color"]zawiera tylko "red"i "green", ale oprócz nich X_test["color"]czasami zawiera "blue".

Jeśli używamy pd.get_dummies, X_testskończy się z dodatkową "color_blue"kolumnę, która X_trainnie ma, a niespójność prawdopodobnie złamać nasz kod później, zwłaszcza jeśli mamy do karmienia X_testdo sklearnmodelu, które ćwiczyliśmy na X_train.

A jeśli chcemy przetwarzać takie dane w środowisku produkcyjnym, w którym otrzymujemy pojedynczy przykład na raz, pd.get_dummiesnie będą przydatne.

Z sklearn.OneHotEncoderdrugiej strony, kiedy już stworzymy koder, możemy go ponownie użyć do generowania tego samego wyjścia za każdym razem, z kolumnami tylko dla "red"i "green". I możemy wyraźnie kontrolować, co się dzieje, gdy napotyka nowy poziom "blue": jeśli uważamy, że to niemożliwe, możemy powiedzieć mu, aby zgłosił błąd handle_unknown="error"; w przeciwnym razie możemy nakazać mu kontynuowanie i po prostu ustawić czerwone i zielone kolumny na 0, używając handle_unknown="ignore".

Denziloe
źródło
23
Uważam, że ta odpowiedź ma znacznie większy wpływ niż akceptowana. Prawdziwą magią jest obsługa nieznanych kategorii, które z pewnością pojawią się w produkcji.
szczekacz
2
Myślę, że to lepsza, pełniejsza odpowiedź niż zaakceptowana odpowiedź.
Chiraz BenAbdelkader,
1
Tak. IMHO, to jest lepsza odpowiedź niż zaakceptowana odpowiedź.
dami.max
1
Tak . Ta odpowiedź zdecydowanie lepiej wyjaśnia, dlaczego one_hot_encoder może być lepsze, wraz z wyraźnym przykładem
Binod Mathews
1
To było piękne wyjaśnienie.
Podziękowania
4

dlaczego nie chcesz po prostu buforować lub zapisywać kolumn jako zmiennej col_list z otrzymanych get_dummies, a następnie użyć pd.reindex, aby wyrównać pociąg z testowymi zestawami danych ... przykład:

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
źródło
Jak to odpowiada na pytanie?
jorijnsmit
więcej, aby obalić poprzedni komentarz, że Sklearn OHE jest nadrzędny z powodu handle_unknown. To samo można osiągnąć za pomocą reindeksowania pand.
Carl
Może wystąpić podstępny problem z używaniem get_dummies, z wyjątkiem jednorazowego uruchomienia. Co się stanie, jeśli drop_first = True, a następna próbka nie zawiera upuszczonej wartości?
Mennica
3

Naprawdę podoba mi się odpowiedź Carla i zagłosowałem za nią. Po prostu rozszerzę nieco przykład Carla, aby więcej ludzi, miejmy nadzieję, doceni fakt, że pd.get_dummies poradzi sobie z nieznanymi. Dwa poniższe przykłady pokazują, że pd.get_dummies może osiągnąć to samo w obsłudze nieznanych elementów, jak 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']))

Korzystanie z 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

Korzystanie z 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
źródło
Czy możesz rozszerzyć swoją odpowiedź o przykład z drop_first = True, a następnie pokazać nowe dane, które nie obejmują pominiętej wartości.
Mennica