【问题标题】:Pandas groupby - apply different functions to half the records in each groupPandas groupby - 对每组中的一半记录应用不同的函数
【发布时间】:2016-02-09 17:33:23
【问题描述】:

我有类似以下数据框的内容,其中我有街道地址范围和街道名称的非唯一组合。

import pandas as pd
df=pd.DataFrame()
df['BlockRange']=['100-150','100-150','100-150','100-150','200-300','200-300','300-400','300-400','300-400']
df['Street']=['Main','Main','Main','Main','Spruce','Spruce','2nd','2nd','2nd']
df
  BlockRange  Street
0    100-150    Main
1    100-150    Main
2    100-150    Main
3    100-150    Main
4    200-300  Spruce
5    200-300  Spruce
6    300-400     2nd
7    300-400     2nd
8    300-400     2nd

在 3 个“组”中的每一个中 - (100-150, Main)、(200-300, Spruce) 和 (300-400, 2nd) - 我希望每个组中的一半记录获得一个块number 等于块范围的中点和一半的记录,得到一个等于块范围的中点加 1 的块号(将其放在街道的另一侧)。

我知道这应该可以使用 groupby 转换来完成,但我不知道该怎么做(我无法将函数应用于 groupby 键,'BlockRange')。

我只能通过遍历每个唯一组来获得我正在寻找的结果,这在我的完整数据集上运行时需要一段时间。有关我当前的解决方案和我正在寻找的最终结果,请参见下文:

groups=df.groupby(['BlockRange','Street'])

#Write function that calculates the mid point of the block range
def get_mid(x):
    block_nums=[int(y) for y in x.split('-')]
    return sum(block_nums)/len(block_nums)

final=pd.DataFrame()
for groupkey,group in groups:
    block_mid=get_mid(groupkey[0])
    halfway_point=len(group)/2
    group['Block']=0
    group.iloc[:halfway_point]['Block']=block_mid
    group.iloc[halfway_point:]['Block']=block_mid+1
    final=final.append(group)

final
  BlockRange  Street  Block
0    100-150    Main    125
1    100-150    Main    125
2    100-150    Main    126
3    100-150    Main    126
4    200-300  Spruce    250
5    200-300  Spruce    251
6    300-400     2nd    350
7    300-400     2nd    351
8    300-400     2nd    351

关于如何更有效地做到这一点有什么建议吗?也许使用 groupby 转换?

【问题讨论】:

    标签: python pandas group-by


    【解决方案1】:

    您可以将apply与自定义函数f一起使用:

    def f(x):
        df = pd.DataFrame([y.split('-') for y in x['BlockRange'].tolist()])
        df = df.astype(int)
        block_nums = df.sum(axis=1) / 2
        x['Block'] = block_nums[0]
        halfway_point=len(x)/2
        x.iloc[halfway_point:, 2] = block_nums[0] + 1
        return x
    
    print df.groupby(['BlockRange','Street']).apply(f)
    
      BlockRange  Street  Block
    0    100-150    Main    125
    1    100-150    Main    125
    2    100-150    Main    126
    3    100-150    Main    126
    4    200-300  Spruce    250
    5    200-300  Spruce    251
    6    300-400     2nd    350
    7    300-400     2nd    351
    8    300-400     2nd    351  
    

    时间安排:

    In [32]: %timeit orig(df)
    __main__:26: SettingWithCopyWarning: 
    A value is trying to be set on a copy of a slice from a DataFrame.
    Try using .loc[row_indexer,col_indexer] = value instead
    
    See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
    __main__:27: SettingWithCopyWarning: 
    A value is trying to be set on a copy of a slice from a DataFrame.
    Try using .loc[row_indexer,col_indexer] = value instead
    
    See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
    __main__:28: SettingWithCopyWarning: 
    A value is trying to be set on a copy of a slice from a DataFrame.
    Try using .loc[row_indexer,col_indexer] = value instead
    
    See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
    1 loops, best of 3: 290 ms per loop
    
    In [33]: %timeit new(df)
    100 loops, best of 3: 10.2 ms per loop  
    

    测试:

    print df
    df1 = df.copy()
    
    def orig(df):
        groups=df.groupby(['BlockRange','Street'])
    
        #Write function that calculates the mid point of the block range
        def get_mid(x):
            block_nums=[int(y) for y in x.split('-')]
            return sum(block_nums)/len(block_nums)
        final=pd.DataFrame()
    
        for groupkey,group in groups:
            block_mid=get_mid(groupkey[0])
            halfway_point=len(group)/2
            group['Block']=0
            group.iloc[:halfway_point]['Block']=block_mid
            group.iloc[halfway_point:]['Block']=block_mid+1
            final=final.append(group)
        return final    
    
    def new(df):
        def f(x):
            df = pd.DataFrame([y.split('-') for y in x['BlockRange'].tolist() ])
            df = df.astype(int)
            block_nums = df.sum(axis=1) / 2
            x['Block'] = block_nums[0]
            halfway_point=len(x)/2
            x.iloc[halfway_point:, 2] = block_nums[0] + 1
            return x
    
        return df.groupby(['BlockRange','Street']).apply(f)
    
    print orig(df)
    print new(df1)   
    

    【讨论】:

      【解决方案2】:

      为了比较,请注意您可以在没有apply 的情况下执行此操作:

      ss = df["BlockRange"].str.split("-")
      midnum = (ss.str[1].astype(float) + ss.str[0].astype(float))//2
      grouped = df.groupby(["BlockRange", "Street"])
      df["Block"] = midnum + (grouped.cumcount()>= grouped["Street"].transform(len) // 2)
      

      这给了我

      >>> df
        BlockRange  Street  Block
      0    100-150    Main    125
      1    100-150    Main    125
      2    100-150    Main    126
      3    100-150    Main    126
      4    200-300  Spruce    250
      5    200-300  Spruce    251
      6    300-400     2nd    350
      7    300-400     2nd    351
      8    300-400     2nd    351
      

      之所以有效,是因为 cumcounttransform(len) 为我们提供了我们需要的部分:

      >>> grouped.cumcount()
      0    0
      1    1
      2    2
      3    3
      4    0
      5    1
      6    0
      7    1
      8    2
      dtype: int64
      >>> grouped.transform(len)
         Block
      0      4
      1      4
      2      4
      3      4
      4      2
      5      2
      6      3
      7      3
      8      3
      

      【讨论】:

        猜你喜欢
        • 2017-11-30
        • 1970-01-01
        • 2020-08-24
        • 2021-09-23
        • 1970-01-01
        • 1970-01-01
        • 2021-06-19
        • 2015-08-20
        • 2023-01-10
        相关资源
        最近更新 更多