【问题标题】:pandas groupby-apply behavior, returning a Series (inconsistent output type)pandas groupby-apply 行为,返回一个系列(输出类型不一致)
【发布时间】:2019-03-04 13:09:49
【问题描述】:

我很好奇pandas groupby-apply 在apply 函数返回一个系列时的行为。

当序列长度不同时,它返回一个多索引序列。

In [1]: import pandas as pd

In [2]: df1=pd.DataFrame({'state':list("AABBB"),
   ...:                 'city':list("vwxyz")})

In [3]: df1
Out[3]:
  city state
0    v     A
1    w     A
2    x     B
3    y     B
4    z     B

In [4]: def f(x):
   ...:         return pd.Series(x['city'].values,index=range(len(x)))
   ...:

In [5]: df1.groupby('state').apply(f)
Out[5]:
state
A      0    v
       1    w
B      0    x
       1    y
       2    z
dtype: object

这会返回一个Series 对象。

但是,如果每个系列都具有相同的长度,那么它会将其转为 DataFrame

In [6]: df2=pd.DataFrame({'state':list("AAABBB"),
   ...:                 'city':list("uvwxyz")})

In [7]: df2
Out[7]:
  city state
0    u     A
1    v     A
2    w     A
3    x     B
4    y     B
5    z     B

In [8]: df2.groupby('state').apply(f)
Out[8]:
       0  1  2
state
A      u  v  w
B      x  y  z

这真的是预期的行为吗?如果我们以这种方式使用 apply,我们是否要检查返回类型?或者apply 中是否有我不欣赏的选项?

如果您好奇,在我的实际用例中,返回的 Series 的长度将与组的长度相同。这似乎是transform 的理想案例,除了我发现apply 返回一个系列实际上在大型数据集上要快一个数量级。这可以是另一个话题。

编辑:根据 Parfait 的回答,我们当然可以这样做:

X=df.groupby('state').apply(f)
if not isinstance(X,pd.Series):
    X=X.stack()
X

这将为df=df1df=df2 提供相同的输出类型。我想我只是在问这是否真的是正常或首选的处理方式。

【问题讨论】:

    标签: python pandas


    【解决方案1】:

    本质上,数据框由等长系列(技术上是系列对象的字典容器)组成。正如 pandas split-apply-combine 文档中所述,运行 groupby() 是指以下一项或多项

    • 根据某些标准将数据分组
    • 对每个组独立应用函数
    • 将结果组合成一个数据结构

    请注意,这并不是说总是生成数据帧,而是生成一个通用的数据结构。因此,groupby() 操作可以向下转换为系列,或者如果将系列作为输入,则可以向上转换为数据帧。

    对于您的第一个数据帧,您运行不相等的分组(或不相等的索引长度)来强制一系列返回,这在“组合”处理中不足以产生数据帧。由于数据帧不能组合不同长度的序列,它会产生一个多索引序列。您可以通过定义函数中的 print 语句看到这一点,其中 state==A 组的长度为 2,B 组的长度为 3。

    def f(x):
        print(x)
        return pd.Series(x['city'].values, index=range(len(x)))
    
    s1 = df1.groupby('state').apply(f)
    
    print(s1)
    #   city state
    # 0    v     A
    # 1    w     A
    #   city state
    # 0    v     A
    # 1    w     A
    #   city state
    # 2    x     B
    # 3    y     B
    # 4    z     B
    # state   
    # A      0    v
    #        1    w
    # B      0    x
    #        1    y
    #        2    z
    # dtype: object
    

    但是,您可以通过重置索引来操纵多索引系列结果,从而调整其层次级别:

    df = df1.groupby('state').apply(f).reset_index()
    print(df)
    
    #   state  level_1  0
    # 0     A        0  v
    # 1     A        1  w
    # 2     B        0  x
    # 3     B        1  y
    # 4     B        2  z
    

    但与您的需求更相关的是unstack(),它以索引标签的级别为中心,产生一个数据框。考虑用fillna() 填充None 结果。

    df = df1.groupby('state').apply(f).unstack()
    print(df)
    
    #        0  1     2
    # state            
    # A      v  w  None
    # B      x  y     z
    

    【讨论】:

    • 我理解为什么会发生这种情况,但它看起来仍然很奇怪。如果我不是以交互方式工作,似乎我必须检查结果的数据类型才能继续。调用.reset_index().unstack() 仍然会产生截然不同的结果,具体取决于我是从df1 还是df2 数据开始的。
    • 虽然使用stackunstack 是在两种输出类型之间进行强制转换的最佳方式,但很好,谢谢。我已编辑问题以包含此内容。
    • 正如提到的groupby().apply() 不保证数据帧返回,因为它取决于输入和操作,因此可以产生不同的结构。考虑一个用户定义的函数/类来处理各种输出。
    • apply 将返回一个多索引系列,如果给 apply 的函数返回具有不同索引的系列。否则,如果给apply的函数返回的所有Seri​​es具有相同的索引,apply将返回一个Dataframe
    【解决方案2】:

    而不是在你的函数 f 中做 index=range(len(x)),你 可以做index=x.index 来防止这种不良行为

    【讨论】:

      猜你喜欢
      • 2022-01-26
      • 2019-11-19
      • 2015-06-12
      • 1970-01-01
      • 1970-01-01
      • 2019-08-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多