【问题标题】:Pandas convert a column of list to dummies熊猫将一列列表转换为假人
【发布时间】:2021-10-20 18:46:24
【问题描述】:

我有一个数据框,其中一列是我的每个用户所属的组列表。比如:

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

不起作用,因为这只会为我的列中的每个不同列表返回一列。

解决方案需要高效,因为数据框将包含 500,000 多行。任何建议将不胜感激!

【问题讨论】:

    标签: python pandas


    【解决方案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) 将一系列列表转换为数据框
    • .stack() 再次将所有内容放在一列中(创建多级索引)
    • pd.get_dummies( ) 创建假人
    • .sum(level=0) 用于重新合并应该是一行的不同行(通过总结第二个级别,只保留原始级别(level=0))

    稍微等价于pd.get_dummies(s.apply(pd.Series), prefix='', prefix_sep='').sum(level=0, axis=1)

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

    【讨论】:

    • 你使用的是什么版本的 Pandas?
    • @joris 你的意思可能是这样的:pd.get_dummies(s.apply(pd.Series), prefix='', prefix_sep='').sum(level=0, axis=1) 因为你的代码输出了一个总和不是数据框的系列。
    • 啊,抱歉,括号放错了位置(堆栈应该在 get_dummies 内)。我正在使用熊猫 0.15.2。 @Primer 是的,我先写了,但我发现它的堆栈更干净(更短),但它给出的输出完全相同。
    • @Alex,您从不同的输入开始(格式化为列表的字符串,我从列表开始),但我不确定 OP 想要什么。除此之外,您在应用程序中执行了get_dummies(因此对于每一行而不是一次),这使得它比上述方法更慢。
    • @joris True - 实际上,OP 帖子中字符周围的引号让我认为可能是这种情况......未删除。
    【解决方案2】:

    非常快速的解决方案,以防您有大型数据框

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

    【讨论】:

      【解决方案3】:

      这甚至更快: 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
      

      【讨论】:

        【解决方案4】:

        即使这个任务得到了回答,我有一个更快的解决方案:

        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
        

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

        【讨论】:

        • 我已经测试了你的解决方案:它就像一个魅力。你介意进一步评论它以解释它是如何工作的吗?
        • 要为列添加前缀,请使用:dummies.columns = ['D_'+col_name for col_name in dummies.columns]
        • @Ufos,你可以.add_prefix('D_')
        • @PauloAlves,哎哟!
        • @PauloAlves 我尝试了您的解决方案,因为另一个解决方案对于我的数据集来说太慢了,但我不断收到以下错误:“InvalidIndexError:重新索引仅对具有唯一值的索引对象有效”。你知道这可能来自哪里吗?如果它来自原始数据帧的索引,我已经检查了df.index.is_unique,它输出True
        猜你喜欢
        • 2023-01-17
        • 2017-04-08
        • 1970-01-01
        • 2019-10-12
        • 2018-10-21
        • 1970-01-01
        • 2014-03-05
        • 2022-10-07
        相关资源
        最近更新 更多