2017-07-25 55 views
12

Chciałbym podzielić kolumnę pandy składającą się z listy elementów na tyle kolumn, ile jest unikatowych elementów, tj. one-hot-encode (z wartością 1 reprezentującą dany element istniejący w rzędzie i 0 w przypadku nieobecności).Jak jedno-hot-kodowanie z kolumny pandy zawierającej listę?

Na przykład, biorąc dataframe df

Col1 Col2   Col3 
C  33  [Apple, Orange, Banana] 
A  2.5 [Apple, Grape] 
B  42  [Banana] 

Chciałbym przekonwertować to do:

df

Col1 Col2 Apple Orange Banana Grape 
C  33  1  1  1  0 
A  2.5 1  0  0  1 
B  42  0  0  1  0 

Jak mogę używać pandy/sklearn osiągnąć to?

Odpowiedz

15

Możemy również użyć sklearn.preprocessing.MultiLabelBinarizer:

from sklearn.preprocessing import MultiLabelBinarizer 

mlb = MultiLabelBinarizer() 
df = df.join(pd.DataFrame(mlb.fit_transform(df.pop('Col3')), 
          columns=mlb.classes_, 
          index=df.index)) 

Wynik:

In [77]: df 
Out[77]: 
    Col1 Col2 Apple Banana Grape Orange 
0 C 33.0  1  1  0  1 
1 A 2.5  1  0  1  0 
2 B 42.0  0  1  0  0 
+1

Możesz znaleźć interesujący czas. – piRSquared

6

Zastosowanie get_dummies:

df_out = df.assign(**pd.get_dummies(df.Col3.apply(lambda x:pd.Series(x)).stack().reset_index(level=1,drop=True)).sum(level=0)) 

wyjściowa:

Col1 Col2      Col3 Apple Banana Grape Orange 
0 C 33.0 [Apple, Orange, Banana]  1  1  0  1 
1 A 2.5   [Apple, Grape]  1  0  1  0 
2 B 42.0     [Banana]  0  1  0  0 

kolumny Oczyszczanie:

df_out.drop('Col3',axis=1) 

wyjściowa:

Col1 Col2 Apple Banana Grape Orange 
0 C 33.0  1  1  0  1 
1 A 2.5  1  0  1  0 
2 B 42.0  0  1  0  0 
+1

+1 do użycia '**' 'z get_dummies', ale może to być powolne dla dużych dataframes powodu' .stack() 'i metody łańcuchowych. –

+0

@BradSolomon Dzięki. –

+0

Nie jestem pewien, czy to działa poprawnie ... Wypróbuj po: 'df = pd.concat ([df, df])' – Alexander

5

Można pętli Col3 z apply i przekształcić każdy element w serii z listy jako wskaźnika, które stają się nagłówek w ramce danych Wynik:

pd.concat([ 
     df.drop("Col3", 1), 
     df.Col3.apply(lambda x: pd.Series(1, x)).fillna(0) 
    ], axis=1) 

#Col1 Col2 Apple Banana Grape Orange 
#0 C 33.0  1.0  1.0 0.0  1.0 
#1 A 2.5  1.0  0.0 1.0  0.0 
#2 B 42.0  0.0  1.0 0.0  0.0 
5

można uzyskać wszystkie unikatowe owoce w Col3 korzystając ustawić zrozumieniem w następujący sposób:

set(fruit for fruits in df.Col3 for fruit in fruits) 

Korzystanie ze zrozumieniem słownika, można następnie przejść przez każdego unikalnego owoców i sprawdzić, czy jest w kolumnie.

>>> df[['Col1', 'Col2']].assign(**{fruit: [1 if fruit in cell else 0 for cell in df.Col3] 
            for fruit in set(fruit for fruits in df.Col3 
                for fruit in fruits)}) 
    Col1 Col2 Apple Banana Grape Orange 
0 C 33.0  1  1  0  1 
1 A 2.5  1  0  1  0 
2 B 42.0  0  1  0  0 

Timings

dfs = pd.concat([df] * 1000) # Use 3,000 rows in the dataframe. 

# Solution 1 by @Alexander (me) 
%%timeit -n 1000 
dfs[['Col1', 'Col2']].assign(**{fruit: [1 if fruit in cell else 0 for cell in dfs.Col3] 
           for fruit in set(fruit for fruits in dfs.Col3 for fruit in fruits)}) 
# 10 loops, best of 3: 4.57 ms per loop 

# Solution 2 by @Psidom 
%%timeit -n 1000 
pd.concat([ 
     dfs.drop("Col3", 1), 
     dfs.Col3.apply(lambda x: pd.Series(1, x)).fillna(0) 
    ], axis=1) 
# 10 loops, best of 3: 748 ms per loop 

# Solution 3 by @MaxU 
from sklearn.preprocessing import MultiLabelBinarizer 
mlb = MultiLabelBinarizer() 

%%timeit -n 10 
dfs.join(pd.DataFrame(mlb.fit_transform(dfs.Col3), 
          columns=mlb.classes_, 
          index=dfs.index)) 
# 10 loops, best of 3: 283 ms per loop 

# Solution 4 by @ScottBoston 
%%timeit -n 10 
df_out = dfs.assign(**pd.get_dummies(dfs.Col3.apply(lambda x:pd.Series(x)).stack().reset_index(level=1,drop=True)).sum(level=0)) 
# 10 loops, best of 3: 512 ms per loop 

But... 
>>> print(df_out.head()) 
    Col1 Col2      Col3 Apple Banana Grape Orange 
0 C 33.0 [Apple, Orange, Banana] 1000 1000  0 1000 
1 A 2.5   [Apple, Grape] 1000  0 1000  0 
2 B 42.0     [Banana]  0 1000  0  0 
0 C 33.0 [Apple, Orange, Banana] 1000 1000  0 1000 
1 A 2.5   [Apple, Grape] 1000  0 1000  0 
10

Wariant 1
Krótka odpowiedź
pir_slow

df.drop('Col3', 1).join(df.Col3.str.join('|').str.get_dummies()) 

    Col1 Col2 Apple Banana Grape Orange 
0 C 33.0  1  1  0  1 
1 A 2.5  1  0  1  0 
2 B 42.0  0  1  0  0 

Wariant 2
Szybka odpowiedź
pir_fast

v = df.Col3.values 
l = [len(x) for x in v.tolist()] 
f, u = pd.factorize(np.concatenate(v)) 
n, m = len(v), u.size 
i = np.arange(n).repeat(l) 

dummies = pd.DataFrame(
    np.bincount(i * m + f, minlength=n * m).reshape(n, m), 
    df.index, u 
) 

df.drop('Col3', 1).join(dummies) 

    Col1 Col2 Apple Orange Banana Grape 
0 C 33.0  1  1  1  0 
1 A 2.5  1  0  0  1 
2 B 42.0  0  0  1  0 

Wariant 3
pir_alt1

df.drop('Col3', 1).join(
    pd.get_dummies(
     pd.DataFrame(df.Col3.tolist()).stack() 
    ).astype(int).sum(level=0) 
) 

    Col1 Col2 Apple Orange Banana Grape 
0 C 33.0  1  1  1  0 
1 A 2.5  1  0  0  1 
2 B 42.0  0  0  1  0 

Timing Wyniki
poniższy kod

enter image description here


def maxu(df): 
    mlb = MultiLabelBinarizer() 
    d = pd.DataFrame(
     mlb.fit_transform(df.Col3.values) 
     , df.index, mlb.classes_ 
    ) 
    return df.drop('Col3', 1).join(d) 


def bos(df): 
    return df.drop('Col3', 1).assign(**pd.get_dummies(df.Col3.apply(lambda x:pd.Series(x)).stack().reset_index(level=1,drop=True)).sum(level=0)) 

def psi(df): 
    return pd.concat([ 
     df.drop("Col3", 1), 
     df.Col3.apply(lambda x: pd.Series(1, x)).fillna(0) 
    ], axis=1) 

def alex(df): 
    return df[['Col1', 'Col2']].assign(**{fruit: [1 if fruit in cell else 0 for cell in df.Col3] 
             for fruit in set(fruit for fruits in df.Col3 
                 for fruit in fruits)}) 

def pir_slow(df): 
    return df.drop('Col3', 1).join(df.Col3.str.join('|').str.get_dummies()) 

def pir_alt1(df): 
    return df.drop('Col3', 1).join(pd.get_dummies(pd.DataFrame(df.Col3.tolist()).stack()).astype(int).sum(level=0)) 

def pir_fast(df): 
    v = df.Col3.values 
    l = [len(x) for x in v.tolist()] 
    f, u = pd.factorize(np.concatenate(v)) 
    n, m = len(v), u.size 
    i = np.arange(n).repeat(l) 

    dummies = pd.DataFrame(
     np.bincount(i * m + f, minlength=n * m).reshape(n, m), 
     df.index, u 
    ) 

    return df.drop('Col3', 1).join(dummies) 

results = pd.DataFrame(
    index=(1, 3, 10, 30, 100, 300, 1000, 3000), 
    columns='maxu bos psi alex pir_slow pir_fast pir_alt1'.split() 
) 

for i in results.index: 
    d = pd.concat([df] * i, ignore_index=True) 
    for j in results.columns: 
     stmt = '{}(d)'.format(j) 
     setp = 'from __main__ import d, {}'.format(j) 
     results.set_value(i, j, timeit(stmt, setp, number=10)) 
+1

To naprawdę genialne! PS Właśnie użyłem mojego ostatniego strzału do głosowania na dziś ;-) – MaxU

+0

@MaxU dziękuję (-: – piRSquared

+0

To szybko! Podobnie jak twój wykres czasowy Zakładam, że * oś * * to liczba wierszy w ramce danych? – Alexander