【问题标题】:Pandas convert a column of list to dummies熊猫将一列列表转换为虚拟对象
【发布时间】:2022-10-07 16:51:08
【问题描述】:

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

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 dataframe


    【解决方案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 是的——实际上,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:重新索引仅对具有唯一值的索引对象有效”。你知道那可能来自哪里吗?如果它来自原始数据帧的索引,我已经检查了输出Truedf.index.is_unique
        【解决方案5】:

        你可以使用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
        

        【讨论】:

          【解决方案6】:

          您可以使用str.join 将列表中存在的所有元素串联成字符串,然后使用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
          

          【讨论】:

            猜你喜欢
            • 2017-04-08
            • 2021-10-20
            • 1970-01-01
            • 2019-10-12
            • 2018-10-21
            • 1970-01-01
            • 2014-03-05
            • 2014-11-23
            相关资源
            最近更新 更多