【问题标题】:How to explode a list inside a Dataframe cell into separate rows如何将数据框单元格内的列表分解为单独的行
【发布时间】:2015-12-04 18:20:58
【问题描述】:

我希望将包含列表的 pandas 单元格转换为每个值的行。

所以,拿着这个:

如果我想解压并堆叠nearest_neighbors 列中的值,以便每个值在每个opponent 索引中成为一行,我最好如何处理?是否有适用于此类操作的 pandas 方法?

【问题讨论】:

标签: python pandas dataframe


【解决方案1】:

在下面的代码中,我首先重置了索引以使行迭代更容易。

我创建了一个列表列表,其中外部列表​​的每个元素都是目标 DataFrame 的一行,而内部列表的每个元素都是其中的一列。这个嵌套列表最终将被连接起来以创建所需的 DataFrame。

我使用lambda 函数和列表迭代来为nearest_neighbors 的每个元素与相关的nameopponent 配对创建一行。

最后,我从这个列表中创建一个新的 DataFrame(使用原始列名并将索引设置回 nameopponent)。

df = (pd.DataFrame({'name': ['A.J. Price'] * 3, 
                    'opponent': ['76ers', 'blazers', 'bobcats'], 
                    'nearest_neighbors': [['Zach LaVine', 'Jeremy Lin', 'Nate Robinson', 'Isaia']] * 3})
      .set_index(['name', 'opponent']))

>>> df
                                                    nearest_neighbors
name       opponent                                                  
A.J. Price 76ers     [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
           blazers   [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
           bobcats   [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]

df.reset_index(inplace=True)
rows = []
_ = df.apply(lambda row: [rows.append([row['name'], row['opponent'], nn]) 
                         for nn in row.nearest_neighbors], axis=1)
df_new = pd.DataFrame(rows, columns=df.columns).set_index(['name', 'opponent'])

>>> df_new
                    nearest_neighbors
name       opponent                  
A.J. Price 76ers          Zach LaVine
           76ers           Jeremy Lin
           76ers        Nate Robinson
           76ers                Isaia
           blazers        Zach LaVine
           blazers         Jeremy Lin
           blazers      Nate Robinson
           blazers              Isaia
           bobcats        Zach LaVine
           bobcats         Jeremy Lin
           bobcats      Nate Robinson
           bobcats              Isaia

2017 年 6 月编辑

另一种方法如下:

>>> (pd.melt(df.nearest_neighbors.apply(pd.Series).reset_index(), 
             id_vars=['name', 'opponent'],
             value_name='nearest_neighbors')
     .set_index(['name', 'opponent'])
     .drop('variable', axis=1)
     .dropna()
     .sort_index()
     )

【讨论】:

    【解决方案2】:

    爆炸类似列表的列是simplified significantly in pandas 0.25,并添加了 explode()方法:

    df = (pd.DataFrame({'name': ['A.J. Price'] * 3, 
                        'opponent': ['76ers', 'blazers', 'bobcats'], 
                        'nearest_neighbors': [['Zach LaVine', 'Jeremy Lin', 'Nate Robinson', 'Isaia']] * 3})
          .set_index(['name', 'opponent']))
    
    df.explode('nearest_neighbors')
    

    输出:

                        nearest_neighbors
    name       opponent                  
    A.J. Price 76ers          Zach LaVine
               76ers           Jeremy Lin
               76ers        Nate Robinson
               76ers                Isaia
               blazers        Zach LaVine
               blazers         Jeremy Lin
               blazers      Nate Robinson
               blazers              Isaia
               bobcats        Zach LaVine
               bobcats         Jeremy Lin
               bobcats      Nate Robinson
               bobcats              Isaia
    

    【讨论】:

    • 请注意,这只适用于单列(截至 0.25)。有关更通用的解决方案,请参阅 herehere
    【解决方案3】:

    使用apply(pd.Series)stack,然后使用reset_indexto_frame

    In [1803]: (df.nearest_neighbors.apply(pd.Series)
                  .stack()
                  .reset_index(level=2, drop=True)
                  .to_frame('nearest_neighbors'))
    Out[1803]:
                        nearest_neighbors
    name       opponent
    A.J. Price 76ers          Zach LaVine
               76ers           Jeremy Lin
               76ers        Nate Robinson
               76ers                Isaia
               blazers        Zach LaVine
               blazers         Jeremy Lin
               blazers      Nate Robinson
               blazers              Isaia
               bobcats        Zach LaVine
               bobcats         Jeremy Lin
               bobcats      Nate Robinson
               bobcats              Isaia
    

    详情

    In [1804]: df
    Out[1804]:
                                                       nearest_neighbors
    name       opponent
    A.J. Price 76ers     [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
               blazers   [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
               bobcats   [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
    

    【讨论】:

    • 喜欢您的解决方案的优雅!您是否有机会将其与其他方法进行基准测试?
    • df.nearest_neighbors.apply(pd.Series) 的结果让我非常惊讶;
    • @rpyzh 是的,它非常优雅,但是慢得可怜。
    【解决方案4】:

    我认为这是一个非常好的问题,在 Hive 中你会使用EXPLODE,我认为有理由认为 Pandas 应该默认包含此功能。我可能会用这样的嵌套生成器理解来爆炸列表列:

    pd.DataFrame({
        "name": i[0],
        "opponent": i[1],
        "nearest_neighbor": neighbour
        }
        for i, row in df.iterrows() for neighbour in row.nearest_neighbors
        ).set_index(["name", "opponent"])
    

    【讨论】:

    • 我喜欢这个解决方案如何允许每行的列表项数量不同。
    • 有没有办法用这种方法保留原来的索引?
    • @SummerEla 大声笑这是一个非常古老的答案,我已经更新以展示我现在将如何做到这一点
    • @maxymoo 不过,这仍然是一个很好的问题。感谢更新!
    • 我发现这很有用,并把它变成了package
    【解决方案5】:

    目前我发现的最快方法是使用.iloc 扩展DataFrame 并分配回flattened 目标列。

    给定通常的输入(稍微复制一下):

    df = (pd.DataFrame({'name': ['A.J. Price'] * 3, 
                        'opponent': ['76ers', 'blazers', 'bobcats'], 
                        'nearest_neighbors': [['Zach LaVine', 'Jeremy Lin', 'Nate Robinson', 'Isaia']] * 3})
          .set_index(['name', 'opponent']))
    df = pd.concat([df]*10)
    
    df
    Out[3]: 
                                                       nearest_neighbors
    name       opponent                                                 
    A.J. Price 76ers     [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
               blazers   [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
               bobcats   [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
               76ers     [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
               blazers   [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
    ...
    

    鉴于以下建议的替代方案:

    col_target = 'nearest_neighbors'
    
    def extend_iloc():
        # Flatten columns of lists
        col_flat = [item for sublist in df[col_target] for item in sublist] 
        # Row numbers to repeat 
        lens = df[col_target].apply(len)
        vals = range(df.shape[0])
        ilocations = np.repeat(vals, lens)
        # Replicate rows and add flattened column of lists
        cols = [i for i,c in enumerate(df.columns) if c != col_target]
        new_df = df.iloc[ilocations, cols].copy()
        new_df[col_target] = col_flat
        return new_df
    
    def melt():
        return (pd.melt(df[col_target].apply(pd.Series).reset_index(), 
                 id_vars=['name', 'opponent'],
                 value_name=col_target)
                .set_index(['name', 'opponent'])
                .drop('variable', axis=1)
                .dropna()
                .sort_index())
    
    def stack_unstack():
        return (df[col_target].apply(pd.Series)
                .stack()
                .reset_index(level=2, drop=True)
                .to_frame(col_target))
    

    我发现extend_iloc()最快的

    %timeit extend_iloc()
    3.11 ms ± 544 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    
    %timeit melt()
    22.5 ms ± 1.25 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)
    
    %timeit stack_unstack()
    11.5 ms ± 410 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    

    【讨论】:

    • 不错的评价
    • 谢谢你,它真的帮助了我。我使用了extend_iloc解决方案,发现cols = [c for c in df.columns if c != col_target]应该是:cols = [i for i,c in enumerate(df.columns) if c != col_target]df.iloc[ilocations, cols].copy()如果没有显示列索引就会出错。
    • 再次感谢 iloc 的建议。我在这里详细解释了它的工作原理:medium.com/@johnadungan/…。希望它可以帮助任何有类似挑战的人。
    【解决方案6】:

    使用 apply(pd.Series) 的更好的替代解决方案:

    df = pd.DataFrame({'listcol':[[1,2,3],[4,5,6]]})
    
    # expand df.listcol into its own dataframe
    tags = df['listcol'].apply(pd.Series)
    
    # rename each variable is listcol
    tags = tags.rename(columns = lambda x : 'listcol_' + str(x))
    
    # join the tags dataframe back to the original dataframe
    df = pd.concat([df[:], tags[:]], axis=1)
    

    【讨论】:

    • 这个扩展列而不是行。
    • @Oleg 对,但您始终可以转置 DataFrame,然后应用 pd.Series -方式比大多数其他建议更简单
    【解决方案7】:

    类似于 Hive 的 EXPLODE 功能:

    import copy
    
    def pandas_explode(df, column_to_explode):
        """
        Similar to Hive's EXPLODE function, take a column with iterable elements, and flatten the iterable to one element 
        per observation in the output table
    
        :param df: A dataframe to explod
        :type df: pandas.DataFrame
        :param column_to_explode: 
        :type column_to_explode: str
        :return: An exploded data frame
        :rtype: pandas.DataFrame
        """
    
        # Create a list of new observations
        new_observations = list()
    
        # Iterate through existing observations
        for row in df.to_dict(orient='records'):
    
            # Take out the exploding iterable
            explode_values = row[column_to_explode]
            del row[column_to_explode]
    
            # Create a new observation for every entry in the exploding iterable & add all of the other columns
            for explode_value in explode_values:
    
                # Deep copy existing observation
                new_observation = copy.deepcopy(row)
    
                # Add one (newly flattened) value from exploding iterable
                new_observation[column_to_explode] = explode_value
    
                # Add to the list of new observations
                new_observations.append(new_observation)
    
        # Create a DataFrame
        return_df = pandas.DataFrame(new_observations)
    
        # Return
        return return_df
    

    【讨论】:

    • 当我运行这个时,我得到以下错误:NameError: global name 'copy' is not defined
    【解决方案8】:

    所以所有这些答案都很好,但我想要一些^非常简单的东西^所以这是我的贡献:

    def explode(series):
        return pd.Series([x for inner_list in series for x in inner_list])                               
    

    就是这样......当你想要一个列表被“分解”的新系列时,只需使用它。这是我们执行 value_counts() 的示例

    In[1]: df = pd.DataFrame({'column': [['a','b','c'],['b','c'],['c']]})
    In [2]: df
    Out[2]:
          column
    0  [a, b, c]
    1     [b, c]
    2        [c]
    
    In [3]: explode(df['column'])
    Out[3]:
    0    a
    1    b
    2    c
    3    b
    4    c
    5    c
    
    In [4]: explode(df['column']).value_counts()
    Out[4]:
    c    3
    b    2
    a    1
    

    【讨论】:

      【解决方案9】:

      这是针对较大数据帧的潜在优化。当“exploding”字段中有几个相等的值时,这会运行得更快。 (数据帧与字段中的唯一值计数相比越大,此代码的性能就越好。)

      def lateral_explode(dataframe, fieldname): 
          temp_fieldname = fieldname + '_made_tuple_' 
          dataframe[temp_fieldname] = dataframe[fieldname].apply(tuple)       
          list_of_dataframes = []
          for values in dataframe[temp_fieldname].unique().tolist(): 
              list_of_dataframes.append(pd.DataFrame({
                  temp_fieldname: [values] * len(values), 
                  fieldname: list(values), 
              }))
          dataframe = dataframe[list(set(dataframe.columns) - set([fieldname]))]\ 
              .merge(pd.concat(list_of_dataframes), how='left', on=temp_fieldname) 
          del dataframe[temp_fieldname]
      
          return dataframe
      

      【讨论】:

        【解决方案10】:

        扩展 Oleg 的 .iloc 答案以自动展平所有列表列:

        def extend_iloc(df):
            cols_to_flatten = [colname for colname in df.columns if 
            isinstance(df.iloc[0][colname], list)]
            # Row numbers to repeat 
            lens = df[cols_to_flatten[0]].apply(len)
            vals = range(df.shape[0])
            ilocations = np.repeat(vals, lens)
            # Replicate rows and add flattened column of lists
            with_idxs = [(i, c) for (i, c) in enumerate(df.columns) if c not in cols_to_flatten]
            col_idxs = list(zip(*with_idxs)[0])
            new_df = df.iloc[ilocations, col_idxs].copy()
        
            # Flatten columns of lists
            for col_target in cols_to_flatten:
                col_flat = [item for sublist in df[col_target] for item in sublist]
                new_df[col_target] = col_flat
        
            return new_df
        

        这假设每个列表列具有相同的列表长度。

        【讨论】:

          【解决方案11】:

          您可以展平列,而不是使用 apply(pd.Series)。这提高了性能。

          df = (pd.DataFrame({'name': ['A.J. Price'] * 3, 
                          'opponent': ['76ers', 'blazers', 'bobcats'], 
                          'nearest_neighbors': [['Zach LaVine', 'Jeremy Lin', 'Nate Robinson', 'Isaia']] * 3})
            .set_index(['name', 'opponent']))
          
          
          
          %timeit (pd.DataFrame(df['nearest_neighbors'].values.tolist(), index = df.index)
                     .stack()
                     .reset_index(level = 2, drop=True).to_frame('nearest_neighbors'))
          
          1.87 ms ± 9.74 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
          
          
          %timeit (df.nearest_neighbors.apply(pd.Series)
                    .stack()
                    .reset_index(level=2, drop=True)
                    .to_frame('nearest_neighbors'))
          
          2.73 ms ± 16.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
          

          【讨论】:

          • IndexError: Too many levels: 当我尝试我的示例时,索引只有 2 个级别,而不是 3 个
          • 您必须根据您的示例更改 reset_index 中的“级别”
          猜你喜欢
          • 1970-01-01
          • 2018-10-07
          • 2020-03-28
          • 2015-11-14
          • 1970-01-01
          • 1970-01-01
          • 2020-08-24
          • 2017-01-31
          相关资源
          最近更新 更多