Как один-горячий-кодировать из столбца pandas, содержащего список?

Я хотел бы разбить столбец pandas, состоящий из списка элементов, на столько столбцов, что есть уникальные элементы, т.е. one-hot-encode them (со значением 1, представляющим данный элемент, существующий в строке, и 0 в случае отсутствия).

Например, при использовании dataframe df

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

Я хотел бы преобразовать это в:

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

Как я могу использовать pandas/sklearn для этого?

Ответ 1

Мы также можем использовать 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))

Результат:

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

Ответ 2

Вариант 1
Краткий ответ
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

Вариант 2
Быстрый ответ
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

Вариант 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

Результаты синхронизации
Код ниже

введите описание изображения здесь


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))

Ответ 3

Используйте 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))

Вывод:

  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

Столбец очистки:

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

Вывод:

  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

Ответ 4

Вы можете выполнить цикл Col3 с помощью apply и преобразовать каждый элемент в серию со списком в качестве индекса, который станет заголовком в кадре данных результата:

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

Вы можете получить все уникальные фрукты в Col3, используя следующее понимание:

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

Используя понимание словаря, вы можете пройти через каждый уникальный плод и посмотреть, находится ли он в столбце.

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

Задержка

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