Python 如何将数据框单元格中的列表分解为单独的行

Python 如何将数据框单元格中的列表分解为单独的行,python,pandas,dataframe,Python,Pandas,Dataframe,我希望将包含列表的pandas单元格转换为每个值的行 那么,拿这个来说: 如果我想解包并堆叠最近邻列中的值,以便每个值都是每个对手索引中的一行,那么我最好怎么做?是否有pandas方法用于此类操作?我认为这是一个非常好的问题,在Hive中,您将使用EXPLODE,我认为pandas在默认情况下应该包括此功能。我可能会使用嵌套的生成器分解列表列,如下所示: pd.DataFrame({ "name": i[0], "opponent": i[1], "nearest_ne

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

那么,拿这个来说:


如果我想解包并堆叠
最近邻
列中的值,以便每个值都是每个
对手
索引中的一行,那么我最好怎么做?是否有pandas方法用于此类操作?

我认为这是一个非常好的问题,在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"])

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

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

我使用
lambda
函数和列表迭代为
最近邻的每个元素创建一行,并与相关的
名称和
对手
配对

最后,我从这个列表中创建了一个新的数据框(使用原始列名,并将索引设置回
name
对手

编辑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()
     )

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


与Hive的爆炸功能类似:

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
使用
应用(pd.Series)
堆栈
,然后
重置索引
到帧

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]

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


到目前为止,我发现的最快的方法是使用
.iloc
扩展数据帧,并将展平的目标列分配回来

给定常规输入(复制一点):

鉴于以下建议的备选方案:

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\u 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)

扩展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

这假设每个列表列的列表长度相等。

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

def explode(series):
    return pd.Series([x for _list in series for x in _list])                               
就这样。。当你想要一个列表被“分解”的新系列时,只需使用这个。下面是一个例子,我们对墨西哥玉米卷的选择做了value_counts()


通过添加
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')
输出:

您可以展平列,而不是使用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)


当我运行此程序时,会出现以下错误:
namererror:global name'copy'未定义
热爱您的解决方案的优雅!您是否有机会将其与其他方法进行对比?df.nearest_neights.apply(pd.Series)
的结果令我非常惊讶@rpyzh是的,它非常优雅,但速度慢得可怜。这一个扩展列而不是行。@Oleg是的,但您可以始终转置数据帧,然后应用pd.Series-比大多数其他建议简单得多尼斯评估感谢这一点,它真的帮助了我。我使用了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的建议。我在这里详细解释了它的工作原理:。希望它能帮助任何面临类似挑战的人。我喜欢这个解决方案如何允许每行的列表项数量不同。有没有办法用这个方法保持原始索引?@SummerEla lol这是一个非常古老的答案,我已经更新了,以展示我将如何做到这一点now@maxymoo不过,这仍然是一个很好的问题。谢谢更新!我发现这很有用,并将其转化为一个注释,即这只适用于单个列(从0.25开始)。更多通用解决方案,请参见和。索引器错误:级别太多:索引只有2个级别,而不是3个级别,当我尝试我的示例时,您必须根据示例更改重置索引中的“级别”。大多数情况下,正确的答案是现在使用,如图所示,或。
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
def explode(series):
    return pd.Series([x for _list in series for x in _list])                               
In [1]: my_df = pd.DataFrame(pd.Series([['a','b','c'],['b','c'],['c']]), columns=['tacos'])      
In [2]: my_df.head()                                                                               
Out[2]: 
   tacos
0  [a, b, c]
1     [b, c]
2        [c]

In [3]: explode(my_df['tacos']).value_counts()                                                     
Out[3]: 
c    3
b    2
a    1
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
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)