2016-02-09 2 views
4

У меня есть что-то вроде следующего фреймворка данных, где у меня есть уникальные комбинации диапазонов адресов улиц и названий улиц.Pandas groupby - применять разные функции к половине записей в каждой группе

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, ель) и (300-400, второй) - Я хочу, чтобы половина записей в каждой группе чтобы получить номер блока, равный средней точке диапазона блока и половину записей, чтобы получить номер блока, равный средней точке диапазона блоков плюс 1 (как разместить его на другой стороне улицы).

Я знаю, что это должно быть сделано с помощью преобразования groupby, но я не могу понять, как это сделать (у меня возникли проблемы с применением функции к keyby key, «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 

Любые предложения о том, как я могу сделать это более эффективно? Возможно, с помощью группового преобразования?

ответ

4

Вы можете использовать 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) 
1

Для сравнения отметим, что вы можете сделать это без 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 

Это работает, потому что cumcount и transform(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 
Смежные вопросы