根据另一个Series创建带有标识的新pandas Series

3

我有一个类似于以下的数据框:

>>> d = {'ID': ['ID1', 'ID2', 'ID3', 'ID4', 'ID5', 'ID6', 'ID7', 'ID8', 'ID9', 'ID10'], 
         'A': [1, 1, 1, 1, 2, 2, 2, 2, 2, 2], 
         'B': [145,158,240,250,199,204,300,350,467,578]}
>>> df = pd.DataFrame(data=d)

我想创建一个新系列 F,用于标记每个100个单元格的B列(从B列中的第一个值开始计算,而不是从0开始)。B列中的数字对于A列中的每个数字都会“重新启动”。对于A列中的新数字,它应该开始一个新的标志,并将B列中相应的值作为新的100个范围的第一个数字。为了澄清,这种情况的预期结果应该是:
>>> outcome = {'ID': ['ID1', 'ID2', 'ID3', 'ID4', 'ID5', 'ID6', 'ID7', 'ID8', 'ID9', 'ID10'], 
           'A': [1, 1, 1, 1, 2, 2, 2, 2, 2, 2], 
           'B': [145,158,240,250,199,204,300,350,467,578],
           'F': ['F1','F1','F1','F2','F3','F4','F4','F5','F6','F7']}
>>> outcome
      A    B    F
ID1   1   145   F1
ID2   1   158   F1
ID3   1   240   F1
ID4   1   250   F2
ID5   2   199   F3
ID6   2   204   F3
ID7   2   300   F4
ID8   2   350   F4
ID9   2   467   F5
ID10  2   578   F6

我希望你能够理解,提前感谢!

你如何计算100个单位的B?更好的例子可以帮助理解。为什么ID3本身没有以F2开始,因为B有158个单位而不是“零”号。 - Siva Kumar Sunku
感谢您的评论。我计算出:145+100=245。在145和244之间的每个B元素都应该是F1,这就是为什么ID3是F1。F2从245开始,到344结束,以此类推...直到A2从199开始,"窗口"将是:199到298,299到398等等... - Alana
5个回答

2

您可以做以下事情:

import numpy as np

df['d100'] = df.groupby('A')['B'].diff().fillna(0)
df['d100'] = df.groupby('A')['d100'].cumsum() // 100

df['F'] = np.where(df['A'].ne(df['A'].shift()) | df['d100'].ne(df['d100'].shift()), 1, 0).cumsum()
df['F'] = 'F' + df['F'].astype(str)

df.drop('d100', axis=1, inplace=True)

输出:

     ID  A    B   F
0   ID1  1  145  F1
1   ID2  1  158  F1
2   ID3  1  240  F1
3   ID4  1  250  F2
4   ID5  2  199  F3
5   ID6  2  204  F3
6   ID7  2  300  F4
7   ID8  2  350  F4
8   ID9  2  467  F5
9  ID10  2  578  F6

0

这是我提出的(暴力)解决方案:

df = df.reset_index()                # iloc is easier with a clean integer index
B0 = df['B'][0]                      # initialize B

df['F'] = ''                         # create a result column 'F'
df.loc[0,'F'] = 'F1'                 # set the first result
idx = 1                              # initialize your index 
for i in range(1,len(df)):           # iterate over all rows
    if(df['A'][i] == df['A'][i-1]):  # condition 1 : Ai == Ai-1
        if((df['B'][i]-B0)>100):     # condition 2 : Bi - B0 > 100
            idx += 1                 # increment index
            B0 = df.loc[i,'B']       # reset B0
    else:                            # Ai != Ai-1
        idx +=1                      # increment index
        B0 = df.loc[i,'B']           # reset B0

    df.loc[i,'F'] = 'F' + str(idx)   # set output Fi

有没有人能提供一个更优美的解决方案,我很感兴趣。


1
好的,所以你可以不使用循环来完成它 - 但是如果你更喜欢循环,只需使用.iterrows()即可:https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.iterrows.html - Grzegorz Skibinski

0

我想到了一种更简短但可读性较差的简化方法,并将其发布为另一个答案,让您选择喜欢哪个:

df = df.reset_index()                # iloc is easier with a clean integer index
B0 = df['B'][0]                      # initialize B

df['F'] = ''                         # create a result column 'F'
df.loc[0,'F'] = 'F1'                 # set the first result
idx = 1                              # initialize your index 
for i in range(1,len(df)):           # iterate over all rows
    if(df['A'][i] != df['A'][i-1]) |  if((df['B'][i]-B0)>100):     # combining both conditions
        idx += 1                 # increment index
        B0 = df.loc[i,'B']       # reset B0

    df.loc[i,'F'] = 'F' + str(idx)   # set output Fi

0

我找不到一种不用循环的方法,而且还能适用于稍微不同的示例数据。我将在末尾包含不同的测试数据。

首先设置示例数据

import pandas as pd
import numpy as np

d = {'ID': ['ID1', 'ID2', 'ID3', 'ID4', 'ID5', 'ID6', 'ID7', 'ID8', 'ID9', 'ID10'], 
         'A': [1, 1, 1, 1, 2, 2, 2, 2, 2, 2], 
         'B': [145,158,240,250,199,204,300,350,467,578]}
df = pd.DataFrame(data=d)
print(df)

输出:

     ID  A    B
0   ID1  1  145
1   ID2  1  158
2   ID3  1  240
3   ID4  1  250
4   ID5  2  199
5   ID6  2  204
6   ID7  2  300
7   ID8  2  350
8   ID9  2  467
9  ID10  2  578

创建一个帮助系列以查找行之间的差异,并创建一个辅助函数以查找总和大于100的行。
gr = df.groupby('A')['B'].apply(lambda x: x.diff()).fillna(0)

less100 = np.frompyfunc(lambda x,y: 0 if x + y > 100 else x + y, 2, 1)

df['F'] = 'F' + gr.groupby(df.A).apply(
        lambda x: ~less100.accumulate(x.astype(object)).astype('bool')
    ).cumsum().astype('str')
print(df)

输出:

     ID  A    B   F
0   ID1  1  145  F1
1   ID2  1  158  F1
2   ID3  1  240  F1
3   ID4  1  250  F2
4   ID5  2  199  F3
5   ID6  2  204  F3
6   ID7  2  300  F4
7   ID8  2  350  F4
8   ID9  2  467  F5
9  ID10  2  578  F6

数据稍有不同,第4行,B列=99

d = {'ID': ['ID1', 'ID2', 'ID3', 'ID4', 'ID5', 'ID6', 'ID7', 'ID8', 'ID9', 'ID10'], 
         'A': [1, 1, 1, 1, 2, 2, 2, 2, 2, 2], 
         'B': [145,158,240,250, 99,204,300,350,467,578]}
df = pd.DataFrame(data=d)
print(df)

输出:

     ID  A    B
0   ID1  1  145
1   ID2  1  158
2   ID3  1  240
3   ID4  1  250
4   ID5  2   99
5   ID6  2  204
6   ID7  2  300
7   ID8  2  350
8   ID9  2  467
9  ID10  2  578

gr = df.groupby('A')['B'].apply(lambda x: x.diff()).fillna(0)
less100 = np.frompyfunc(lambda x,y: 0 if x + y > 100 else x + y, 2, 1)
df['F'] = 'F' + gr.groupby(df.A).apply(
        lambda x: ~less100.accumulate(x.astype(object)).astype('bool')
    ).cumsum().astype('str')
print(df)

输出:

     ID  A    B   F
0   ID1  1  145  F1
1   ID2  1  158  F1
2   ID3  1  240  F1
3   ID4  1  250  F2
4   ID5  2   99  F3
5   ID6  2  204  F4
6   ID7  2  300  F4
7   ID8  2  350  F5
8   ID9  2  467  F6
9  ID10  2  578  F7

0

使用带有头部(/最小值)的分组操作会更加高效。

# Get the first row of each group, it is minimum
df_grp = df.groupby('A').head(1)

# Get the difference of  first row to each row
df_res = pd.merge(df, df_grp[['A','B']], how='inner', on=['A'])

# Make bucket name as new column
l_1 = ['F' + str(x) for x in (df_res['B_x'] - df_res['B_y'])//100 + 1]
df_res['F'] = pd.Series(l_1)

# Drop the unnecessary columns and rename
df_res.drop(columns=['B_y'], inplace=True)
df_res.rename(columns={'B_x':'B'}, inplace=True)

和输出

ID  A    B   F
0   ID1  1  145  F1
1   ID2  1  158  F1
2   ID3  1  240  F1
3   ID4  1  250  F2
4   ID5  2  199  F1
5   ID6  2  204  F1
6   ID7  2  300  F2
7   ID8  2  350  F2
8   ID9  2  467  F3
9  ID10  2  578  F4
> 

我喜欢你使用列表推导的方法。//100 是需要作为我的代码中的修正引入的 - 非常感谢。就我理解的请求解决方案,F1 不应该被重复使用,在这方面,解决方案还不完整。如果内存关键或运行时性能关键,我认为我的解决方案更合适。 - Oliver Prislan
你说得对,我没有意识到数字 F1 不应该被重复使用。在这种情况下,这是没有帮助的。回到运行时性能方面,我相信合并胜过循环。尽管如此,当解决方案本身不正确时,性能就无关紧要了。让我修正解决方案。谢谢你指出。 :-) - Siva Kumar Sunku
出于好奇,我对这两个解决方案进行了分析,结果是:Siva的。 - Oliver Prislan
1
出于好奇,我对两个解决方案进行了分析,结果如下: 解决方案 Siva的 Oliver的 执行时间 5.4毫秒 3.2毫秒 额外内存使用量 700千字节 100千字节 - Oliver Prislan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接