Pandas将一列列表转换为虚拟对象

hfsqlsce  于 2023-03-28  发布在  其他
关注(0)|答案(6)|浏览(166)

我有一个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+行。

njthzxwz

njthzxwz1#

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.explode()).groupby(level=0).sum()
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

其逻辑是:

  • .explode()将一系列列表展平为一系列单个值(索引跟踪原始行号)
  • pd.get_dummies( )创建虚拟对象
  • .groupby(level=0).sum(),用于组合应该是一行的不同行(通过按索引(level=0)(即原始行号)分组求和)

我不知道这是否足够有效,但无论如何,如果性能很重要,将列表存储在dataframe中不是一个好主意。

原始答案后更新

  • 从版本0.25开始,s.explode()可以用来扁平化Series列表,而不是原来的s.apply(pd.Series).stack()
  • 自1.3.0版起,不建议在聚合中使用level关键字,并且将很快从较新版本中删除,因此建议使用df.groupby(level=0).sum()而不是df.sum(level=0)
wfveoks0

wfveoks02#

***非常快速的解决方案,如果您有一个大的 Dataframe ***

使用sklearn.preprocessing.MultiLabelBinarizer

import pandas as pd
from sklearn.preprocessing import MultiLabelBinarizer

df = pd.DataFrame(
    {'groups':
        [['a','b','c'],
        ['c'],
        ['b','c','e'],
        ['a','c'],
        ['b','e']]
    }, columns=['groups'])

s = df['groups']

mlb = MultiLabelBinarizer()

pd.DataFrame(mlb.fit_transform(s),columns=mlb.classes_, index=df.index)

结果:

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

对我有效,也建议使用herehere

gpfsuwkq

gpfsuwkq3#

这样更快:pd.get_dummies(df['groups'].explode()).sum(level=0)
使用.explode()代替.apply(pd.Series).stack()
与其他解决方案相比:

import timeit
import pandas as pd
setup = '''
import time
import pandas as pd
s = pd.Series({0:['a','b','c'],1:['c'],2:['b','c','e'],3:['a','c'],4:['b','e']})
df = s.rename('groups').to_frame()
'''
m1 = "pd.get_dummies(s.apply(pd.Series).stack()).sum(level=0)"
m2 = "df.groups.apply(lambda x: pd.Series([1] * len(x), index=x)).fillna(0, downcast='infer')"
m3 = "pd.get_dummies(df['groups'].explode()).sum(level=0)"
times = {f"m{i+1}":min(timeit.Timer(m, setup=setup).repeat(7, 1000)) for i, m in enumerate([m1, m2, m3])}
pd.DataFrame([times],index=['ms'])
#           m1        m2        m3
# ms  5.586517  3.821662  2.547167
x9ybnkn6

x9ybnkn64#

尽管这个问题已经解决了,但我有一个更快的解决方案:

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

工作原理

在lambda中,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;缺失的index变成了columnNaN如下所示:

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

现在fillna0填充那些NaN

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

注:不需要使用.fillna(0, downcast='infer')

nx7onnlm

nx7onnlm5#

你可以使用str.join将list中的所有元素串联成string,然后使用str.get_dummies

out = df.join(df['groups'].str.join('|').str.get_dummies())
print(out)

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

yjghlzjz6#

您可以使用explodecrosstab

s = pd.Series([['a', 'b', 'c'], ['c'], ['b', 'c', 'e'], ['a', 'c'], ['b', 'e']])

s = s.explode()
pd.crosstab(s.index, s)

输出:

col_0  a  b  c  e
row_0            
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

相关问题