【问题标题】:Apply function to pandas DataFrame that can return multiple rows将函数应用于可以返回多行的 pandas DataFrame
【发布时间】:2012-10-14 12:57:03
【问题描述】:

我正在尝试转换 DataFrame,以便将某些行复制给定次数。例如:

df = pd.DataFrame({'class': ['A', 'B', 'C'], 'count':[1,0,2]})

  class  count
0     A      1
1     B      0
2     C      2

应该转化为:

  class 
0     A   
1     C   
2     C 

这是带有计数功能的聚合的逆向。有没有一种简单的方法可以在 pandas 中实现它(不使用 for 循环或列表推导)?

一种可能是允许DataFrame.applymap 函数返回多行(类似于GroupByapply 方法)。但是,我认为现在在 pandas 中是不可能的。

【问题讨论】:

  • 我还想到了一个通用函数,它允许根据count 列中的值返回多行、一行或零行。
  • 如果您在 2017 年+ 来回答这个问题,请查看我的答案以获得更有效、更直接的解决方案。

标签: python pandas dataframe


【解决方案1】:

还有一个更简单、更高效的解决方案。 我不得不对大约 350 万行的表进行类似的修改,并且之前建议的解决方案非常慢。

更好的方法是使用 numpy 的 repeat 过程生成一个新索引,其中每个行索引根据其给定计数重复多次,并使用 iloc 来根据该索引选择原表的行:

import pandas as pd
import numpy as np

df = pd.DataFrame({'class': ['A', 'B', 'C'], 'count': [1, 0, 2]})
spread_ixs = np.repeat(range(len(df)), df['count'])
spread_ixs 

array([0, 2, 2])

df.iloc[spread_ixs, :].drop(columns='count').reset_index(drop=True)

  class
0     A
1     C
2     C

【讨论】:

  • 这是一个很好的答案。比评分更高的答案快得多
【解决方案2】:

这个问题很老了,答案并不反映熊猫的现代能力。您可以使用 iterrows 循环遍历每一行,然后使用 DataFrame 构造函数创建具有正确行数的新 DataFrame。最后,使用pd.concat 将所有行连接在一起。

pd.concat([pd.DataFrame(data=[row], index=range(row['count'])) 
           for _, row in df.iterrows()], ignore_index=True)

  class  count
0     A      1
1     C      2
2     C      2

这有利于使用任何大小的 DataFrame。

【讨论】:

  • 这比 Wes 的 group by & apply 答案更高效吗?创建的所有中间数据帧是否会过度使用内存?我想跨数据框使用列表理解与跨行使用列表理解不同,尽管问题确实要求在不使用列表理解的情况下提供解决方案。我已经阅读了一些关于 apply 方法比 iterrows 更快的文章,以及一些关于 apply 进行深度复制和耗尽内存使用的文章,它们会对您在回答上下文中的想法感兴趣。
  • iterrows 不是很慢吗?
【解决方案3】:

我知道这是一个老问题,但我无法让 Wes 的答案适用于数据框中的多个列,所以我让他的代码更通用一些。以为我会分享,以防其他人在这个问题上遇到同样的问题。

您只需指定其中包含计数的列,然后您会得到一个扩展的数据框作为回报。

import pandas as pd
df = pd.DataFrame({'class 1': ['A','B','C','A'],
                   'class 2': [ 1,  2,  3,  1], 
                   'count':   [ 3,  3,  3,  1]})
print df,"\n"

def f(group, *args):
    row = group.irow(0)
    Dict = {}
    row_dict = row.to_dict()
    for item in row_dict: Dict[item] = [row[item]] * row[args[0]]
    return pd.DataFrame(Dict)

def ExpandRows(df,WeightsColumnName):
    df_expand = df.groupby(df.columns.tolist(), group_keys=False).apply(f,WeightsColumnName).reset_index(drop=True)
    return df_expand


df_expanded = ExpandRows(df,'count')
print df_expanded

返回:

  class 1  class 2  count
0       A        1      3
1       B        2      3
2       C        3      3
3       A        1      1 

  class 1  class 2  count
0       A        1      1
1       A        1      3
2       A        1      3
3       A        1      3
4       B        2      3
5       B        2      3
6       B        2      3
7       C        3      3
8       C        3      3
9       C        3      3

关于速度,我的基本 df 是 10 列 x 约 6k 行,扩展为约 100,000 行时需要约 7 秒。在这种情况下,我不确定分组是必要的还是明智的,因为它会将所有列都分组,但是无论如何只有 7 秒。

【讨论】:

    【解决方案4】:

    你可以使用 groupby:

    def f(group):
        row = group.irow(0)
        return DataFrame({'class': [row['class']] * row['count']})
    df.groupby('class', group_keys=False).apply(f)
    

    所以你得到

    In [25]: df.groupby('class', group_keys=False).apply(f)
    Out[25]: 
      class
    0     A
    0     C
    1     C
    

    你可以随意修改结果的索引

    【讨论】:

    • 好答案!如果我有几十个其他列,是否有一种简单的方法可以将这些列保留在f 的结果中,而不是明确地全部输入?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-06-06
    • 2018-02-23
    • 2020-05-11
    • 2019-11-26
    • 1970-01-01
    • 2018-01-01
    • 2019-04-10
    相关资源
    最近更新 更多