Pandas преобразовать столбец списка в макеты

У меня есть dataframe, где один столбец представляет собой список групп, к которым принадлежит каждый из моих пользователей. Что-то вроде:

index groups  
0     ['a','b','c']
1     ['c']
2     ['b','c','e']
3     ['a','c']
4     ['b','e']

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

index  a   b   c   d   e
0      1   1   1   0   0
1      0   0   1   0   0
2      0   1   1   0   1
3      1   0   1   0   0
4      0   1   0   0   0


pd.get_dummies(df['groups'])

не будет работать, потому что это просто возвращает столбец для каждого другого списка в моем столбце.

Решение должно быть эффективным, так как dataframe будет содержать 500 000 строк. Любые советы будут оценены!

Ответы

Ответ 1

Используя s для вашего df['groups']:

In [21]: s = pd.Series({0: ['a', 'b', 'c'], 1:['c'], 2: ['b', 'c', 'e'], 3: ['a', 'c'], 4: ['b', 'e'] })

In [22]: s
Out[22]:
0    [a, b, c]
1          [c]
2    [b, c, e]
3       [a, c]
4       [b, e]
dtype: object

Это возможное решение:

In [23]: pd.get_dummies(s.apply(pd.Series).stack()).sum(level=0)
Out[23]:
   a  b  c  e
0  1  1  1  0
1  0  0  1  0
2  0  1  1  1
3  1  0  1  0
4  0  1  0  1

Логика этого:

  • .apply(Series) преобразует серию списков в dataframe
  • .stack() снова помещает все в один столбец (создание многоуровневого индекса)
  • pd.get_dummies( ) создание манекенов
  • .sum(level=0) для объединения разных строк, которые должны быть одной строкой (путем суммирования второго уровня, сохраняя только исходный уровень (level=0))

Небольшой эквивалент pd.get_dummies(s.apply(pd.Series), prefix='', prefix_sep='').sum(level=0, axis=1)

Если это будет достаточно эффективно, я не знаю, но в любом случае, если производительность важна, сохранение списков в фрейме данных не очень хорошая идея.

Ответ 2

Несмотря на то, что на этот квест был дан ответ, у меня есть более быстрое решение:

df.groups.apply(lambda x: pd.Series([1] * len(x), index=x)).fillna(0, downcast='infer')

И, если у вас есть пустые группы или NaN, вы можете просто:

df.loc[df.groups.str.len() > 0].apply(lambda x: pd.Series([1] * len(x), index=x)).fillna(0, downcast='infer')

Как это работает

Внутри лямбда x - ваш список, например ['a', 'b', 'c']. Итак, pd.Series будет выглядеть следующим образом:

In [2]: pd.Series([1, 1, 1], index=['a', 'b', 'c'])
Out[2]: 
a    1
b    1
c    1
dtype: int64

Когда все pd.Series объединяются, они становятся pd.DataFrame, а их index становятся columns; missing index стал column с NaN, как вы можете видеть далее:

In [4]: a = pd.Series([1, 1, 1], index=['a', 'b', 'c'])
In [5]: b = pd.Series([1, 1, 1], index=['a', 'b', 'd'])
In [6]: pd.DataFrame([a, b])
Out[6]: 
     a    b    c    d
0  1.0  1.0  1.0  NaN
1  1.0  1.0  NaN  1.0

Теперь fillna заполняет те NaN 0:

In [7]: pd.DataFrame([a, b]).fillna(0)
Out[7]: 
     a    b    c    d
0  1.0  1.0  1.0  0.0
1  1.0  1.0  0.0  1.0

И downcast='infer' состоит в том, чтобы сбрасывать от float до int:

In [11]: pd.DataFrame([a, b]).fillna(0, downcast='infer')
Out[11]: 
   a  b  c  d
0  1  1  1  0
1  1  1  0  1

PS: Не требуется использование .fillna(0, downcast='infer').