【问题标题】:Python Pandas Expand a Column of List of Lists to Two New ColumnPython Pandas 将一列列表扩展为两个新列
【发布时间】:2019-09-29 09:33:52
【问题描述】:

我有一个像这样的 DF。

name    id  apps
john    1   [[app1, v1], [app2, v2], [app3,v3]]
smith   2   [[app1, v1], [app4, v4]]

我想扩展应用程序列,使其看起来像这样。

name    id  app_name    app_version
john    1   app1        v1
john    1   app2        v2
john    1   app3        v3
smith   2   app1        v1
smith   2   app4        v4

感谢任何帮助

【问题讨论】:

    标签: python pandas list


    【解决方案1】:

    我已经采纳了 James 的回答,并根据 rafaelc 关于使用 pd.Dataframe.apps.tolist()) 加快速度的建议进行了修改:

    import pandas as pd
    
    df_melted = pd.DataFrame(df.apps.tolist()).T.melt().dropna()
    df_tmp = pd.DataFrame(df_melted.value.tolist(), 
                 columns = ['app_name', 'app_version'],
                 index = df_melted.variable)
    
    df = df_prices.join(df_tmp)
    df.drop(columns=['prices'], inplace = True)
    

    【讨论】:

      【解决方案2】:

      我的建议(可能有更简单的方法)是使用DataFrame.applypd.concat

      def expand_row(row):
          return pd.DataFrame({
              'name': row['name'], # row.name is the name of the series
              'id': row['id'],
              'app_name': [app[0] for app in row.apps],
              'app_version': [app[1] for app in row.apps]
          })
      
      temp_dfs = df.apply(expand_row, axis=1).tolist()
      expanded = pd.concat(temp_dfs)
      expanded = expanded.reset_index() # put index in the correct order
      
      print(expanded)
      
      #     name  id app_name app_version
      # 0   john   1     app1          v1
      # 1   john   1     app2          v2
      # 2   john   1     app3          v3
      # 3  smith   2     app1          v1
      # 4  smith   2     app4          v4
      

      另外,这是一个仅使用 python 的解决方案,如果我的直觉是正确的,应该很快:

      rows = df.values.tolist()
      expanded = [[row[0], row[1], app[0], app[1]]
                  for row in rows
                  for app in row[2]]
      df = pd.DataFrame(
          expanded, columns=['name', 'id', 'app_name', 'app_version'])
      
      #     name  id app_name app_version
      # 0   john   1     app1          v1
      # 1   john   1     app2          v2
      # 2   john   1     app3          v3
      # 3  smith   2     app1          v1
      # 4  smith   2     app4          v4
      

      【讨论】:

        【解决方案3】:

        pd.Series的链很容易理解,如果您想了解更多方法,请查看unnesting

        df.set_index(['name','id']).apps.apply(pd.Series).\
                 stack().apply(pd.Series).\
                    reset_index(level=[0,1]).\
                        rename(columns={0:'app_name',1:'app_version'})
        Out[541]: 
            name  id app_name app_version
        0   john   1     app1          v1
        1   john   1     app2          v2
        2   john   1     app3          v3
        0  smith   2     app1          v1
        1  smith   2     app4          v4
        

        方法二稍微修改我写的函数

        def unnesting(df, explode):
            idx = df.index.repeat(df[explode[0]].str.len())
            df1 = pd.concat([
                pd.DataFrame({x: sum(df[x].tolist(),[])}) for x in explode], axis=1)
            df1.index = idx
            return df1.join(df.drop(explode, 1), how='left')
        

        然后

        yourdf=unnesting(df,['apps'])
        
        yourdf['app_name'],yourdf['app_version']=yourdf.apps.str[0],yourdf.apps.str[1]
        yourdf
        Out[548]: 
                 apps  id   name app_name app_version
        0  [app1, v1]   1   john     app1          v1
        0  [app2, v2]   1   john     app2          v2
        0  [app3, v3]   1   john     app3          v3
        1  [app1, v1]   2  smith     app1          v1
        1  [app4, v4]   2  smith     app4          v4
        

        或者

        yourdf=unnesting(df,['apps']).reindex(columns=df.columns.tolist()+['app_name','app_version'])
        yourdf[['app_name','app_version']]=yourdf.apps.tolist()
        yourdf
        Out[567]: 
                 apps  id   name app_name app_version
        0  [app1, v1]   1   john     app1          v1
        0  [app2, v2]   1   john     app2          v2
        0  [app3, v3]   1   john     app3          v3
        1  [app1, v1]   2  smith     app1          v1
        1  [app4, v4]   2  smith     app4          v4
        

        【讨论】:

          【解决方案4】:

          另一种方法是(也应该很快):

          #Repeat the columns without the list by the str length of the list
          m=df.drop('apps',1).loc[df.index.repeat(df.apps.str.len())].reset_index(drop=True)
          #creating a df exploding the list to 2 columns
          n=pd.DataFrame(np.concatenate(df.apps.values),columns=['app_name','app_version'])
          #concat them together
          df_new=pd.concat([m,n],axis=1)
          

              name id app_name app_version
          0   john  1     app1          v1
          1   john  1     app2          v2
          2   john  1     app3          v3
          3  smith  2     app1          v1
          4  smith  2     app4          v4
          

          【讨论】:

            【解决方案5】:

            您始终可以使用蛮力解决方案。比如:

            name, id, app_name, app_version = [], [], [], []
            for i in range(len(df)):
                for v in df.loc[i,'apps']:
                    app_name.append(v[0])
                    app_version.append(v[1])
                    name.append(df.loc[i, 'name'])
                    id.append(df.loc[i, 'id'])
            df = pd.DataFrame({'name': name, 'id': id, 'app_name': app_name, 'app_version': app_version})
            

            会做的。

            请注意,如果 df['apps'] 是字符串,我假设 df['apps'] 是字符串列表,那么您需要:eval(df.loc[i,'apps']) 而不是 df.loc[i,'apps']

            【讨论】:

            • 即使这样可行,但对于大型数据帧来说可能是不可行的。在 pandas 中,一个 for 循环已经够糟糕了,所以想象一下两个嵌套的 for 循环;} 总是尽量避免直接迭代!
            【解决方案6】:

            您可以.apply(pd.Series) 两次以获得您需要的中间步骤,然后合并回原始数据框。

            import pandas as pd
            
            df = pd.DataFrame({
                'name': ['john', 'smith'],
                'id': [1, 2],
                'apps': [[['app1', 'v1'], ['app2', 'v2'], ['app3','v3']], 
                         [['app1', 'v1'], ['app4', 'v4']]]
            })
            
            dftmp = df.apps.apply(pd.Series).T.melt().dropna()
            dfapp = (dftmp.value
                          .apply(pd.Series)
                          .set_index(dftmp.variable)
                          .rename(columns={0:'app_name', 1:'app_version'})
                    )
            
            df[['name', 'id']].merge(dfapp, left_index=True, right_index=True)
            # returns:
                name  id app_name app_version
            0   john   1     app1          v1
            0   john   1     app2          v2
            0   john   1     app3          v3
            1  smith   2     app1          v1
            1  smith   2     app4          v4
            

            【讨论】:

            • pd.DataFrame(df.apps.tolist())代替.apply(pd.Series)(非常慢)
            • 无论哪种方式,您都将其从 C 支持的 API 中提取到 Python 中。 .apply 隐藏了一个 for 循环,而 tolist 将封装的对象推回 Python。我没有做任何测试来看看哪个更快。
            • 我有,这就是我评论的原因。
            • 详情也可以参考here
            • @James 这是 1.1 秒对 900 微秒,所以它快了 1000 倍,这太棒了。
            猜你喜欢
            • 1970-01-01
            • 2023-03-11
            • 1970-01-01
            • 2019-10-22
            • 1970-01-01
            • 2017-08-12
            • 2019-10-22
            • 2019-01-09
            • 2014-10-15
            相关资源
            最近更新 更多