python - Pandas DataFrame 基于条件进行分组

标签 python python-3.x pandas dataframe pandas-groupby

我发现的最相似的问题是here但没有正确的答案。

基本上,我有一个问题,我试图在数据帧上使用 groupby 来生成公交路线的唯一 ID。问题是,我所掌握的数据有时(尽管很少)对于我的 groupby 列具有相同的值,因此它们被视为相同的总线,即使它们不是。

我能想到的唯一其他方法是根据另一个名为“停靠类型”的列对公交车进行分组,其中有一个“起点”、“中间点”和“终点”的指示符。我想使用 groupby 基于此列创建组,其中每个组从“停止类型”= 开始处开始,并在“停止类型”= 结束处结束。

考虑以下数据:

df = pd.DataFrame({'Vehicle_ID': ['A']*18,
    'Position': ['START', 'MID', 'MID', 'END', 'MID', 'START']*3)})

   Cond   Position
0     A   START
1     A   MID  
2     A   MID   
3     A   END    
4     A   MID    
5     A   START   
6     A   START   
7     A   MID    
8     A   MID    
9     A   END    
10    A   MID   
11    A   START    
12    A   START    
13    A   MID    
14    A   MID    
15    A   END     
16    A   MID    
17    A   START

我想出的将这些总线准确分组的唯一方法是生成一个带有总线序列 id 的附加列,但考虑到我正在处理大量数据,这不是一个非常有效的解决方案。如果可能的话,我希望能够管理我想要用单个 groupby 执行的操作,以便生成以下输出

   Cond   Position   Group
0     A   START      1
1     A   MID        1
2     A   MID        1
3     A   END        1
4     A   MID        
5     A   START      2
6     A   START      2
7     A   MID        2
8     A   MID        2
9     A   END        2 
10    A   MID        
11    A   START      3
12    A   START      3 
13    A   MID        3
14    A   MID        3
15    A   END        3 
16    A   MID        
17    A   START      4

最佳答案

一个想法是通过 np.select 进行因式分解,然后通过 numba 使用自定义循环:

from numba import njit

df = pd.DataFrame({'Vehicle_ID': ['A']*18,
                   'Position': ['START', 'MID', 'MID', 'END', 'MID', 'START']*3})

@njit
def grouper(pos):
    res = np.empty(pos.shape)
    num = 1
    started = 0
    for i in range(len(res)):
        current_pos = pos[i]
        if (started == 0) and (current_pos == 0):
            started = 1
            res[i] = num
        elif (started == 1) and (current_pos == 1):
            started = 0
            res[i] = num
            num += 1
        elif (started == 1) and (current_pos in [-1, 0]):
            res[i] = num
        else:
            res[i] = 0
    return res

arr = np.select([df['Position'].eq('START'), df['Position'].eq('END')], [0, 1], -1)

df['Group'] = grouper(arr).astype(int)

结果:

print(df)

   Position Vehicle_ID  Group
0     START          A      1
1       MID          A      1
2       MID          A      1
3       END          A      1
4       MID          A      0
5     START          A      2
6     START          A      2
7       MID          A      2
8       MID          A      2
9       END          A      2
10      MID          A      0
11    START          A      3
12    START          A      3
13      MID          A      3
14      MID          A      3
15      END          A      3
16      MID          A      0
17    START          A      4

在我看来,您应该包含“空白”值,因为这会迫使您的系列成为object dtype,从而导致任何后续处理效率低下。如上所述,您可以使用 0 代替。

性能基准测试

numba 比一种纯 Pandas 方法快约 10 倍:-

import pandas as pd, numpy as np
from numba import njit

df = pd.DataFrame({'Vehicle_ID': ['A']*18,
                   'Position': ['START', 'MID', 'MID', 'END', 'MID', 'START']*3})


df = pd.concat([df]*10, ignore_index=True)

assert joz(df.copy()).equals(jpp(df.copy()))

%timeit joz(df.copy())  # 18.6 ms per loop
%timeit jpp(df.copy())  # 1.95 ms per loop

基准测试函数:

def joz(df):
    # identification of sequences
    df['Position_Prev'] = df['Position'].shift(1)
    df['Sequence'] = 0
    df.loc[(df['Position'] == 'START') & (df['Position_Prev'] != 'START'), 'Sequence'] = 1
    df.loc[df['Position'] == 'END', 'Sequence'] = -1
    df['Sequence_Sum'] = df['Sequence'].cumsum()
    df.loc[df['Sequence'] == -1, 'Sequence_Sum'] = 1

    # take only items between START and END and generate Group number
    df2 = df[df['Sequence_Sum'] == 1].copy()
    df2.loc[df['Sequence'] == -1, 'Sequence'] = 0
    df2['Group'] = df2['Sequence'].cumsum()

    # merge results to one dataframe
    df = df.merge(df2[['Group']], left_index=True, right_index=True, how='left')
    df['Group'] = df['Group'].fillna(0)
    df['Group'] = df['Group'].astype(int)
    df.drop(['Position_Prev', 'Sequence', 'Sequence_Sum'], axis=1, inplace=True)    
    return df

@njit
def grouper(pos):
    res = np.empty(pos.shape)
    num = 1
    started = 0
    for i in range(len(res)):
        current_pos = pos[i]
        if (started == 0) and (current_pos == 0):
            started = 1
            res[i] = num
        elif (started == 1) and (current_pos == 1):
            started = 0
            res[i] = num
            num += 1
        elif (started == 1) and (current_pos in [-1, 0]):
            res[i] = num
        else:
            res[i] = 0
    return res

def jpp(df):
    arr = np.select([df['Position'].eq('START'), df['Position'].eq('END')], [0, 1], -1)
    df['Group'] = grouper(arr).astype(int)
    return df

关于python - Pandas DataFrame 基于条件进行分组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53190602/

相关文章:

python - 在 Pandas 中按特定月份和日期进行切片

python - 计算三维数据数组(纬度、经度、时间)中跨时间连续值的最长序列

python - 在 python unittest 框架中的每个测试用例之前调用相同的函数

python - 如何 json 序列化自定义可迭代对象?

python - 对触发新流程的函数进行单元测试

python - pandas.Series() 使用 DataFrame 列创建返回 NaN 数据条目

python pd.read 在运行 rodeo 时标记错误

python - 按年和月对 Pandas 数据框进行分组

javascript - Flask Javascript Json 数据 - 未定义

python - 如何计算 pandas 中后续行的数量