【问题标题】:Fast looping through Python dataframe with previous row reference使用前一行引用快速循环 Python 数据帧
【发布时间】:2019-03-07 00:45:12
【问题描述】:

假设我有一个包含两列的 pandas 数据框:ID 和 Days。 DataFrame 在两个变量中按升序排序。例如:

# Initial dataset
data = pd.DataFrame({'id': np.repeat([1, 2 ,3], 4),
                 'day': [1, 2, 10, 11, 3, 4, 12, 15, 1, 20, 21, 24]})

    id  day
0   1   1
1   1   2
2   1   10
3   1   11
4   2   3
5   2   4
6   2   12
7   2   15
8   3   1
9   3   20
10  3   21
11  3   24

我想添加第三列,它将为每个 ID*day 提供一个“会话”编号。 “会话”是指一系列天,一个会话的天数之间的差异小于 2 天。例如,序列5,6,7 将被视为一个会话,而5,6,9 将被视为两个会话并应标记为0, 0, 1,即第5 天和第6 天被引用到会话#0,而第9 天被引用到会话#1。 每个新 ID 的会话编号应该从 0 开始。

也就是说,我想要得到的是:

    id  day session
0   1   1   0
1   1   2   0
2   1   10  1
3   1   11  1
4   2   3   0
5   2   4   0
6   2   12  1
7   2   15  2  
8   3   1   0
9   3   20  1
10  3   21  1
11  3   24  2

为了解决这个任务,我使用基本的 for 循环。在这个循环中,我迭代地遍历所有唯一 ID,然后从初始数据集中子集数据块,并为特定 ID 的每一天分配会话编号。我遇到的问题 - 因为初始数据集是数百万行 - 循环需要大量时间!例如,对于 100 万行,我的循环花费大约一分钟,这太多了。

如何提高速度?什么方法都好!如果您知道如何获得所需的结果,例如,使用一些 numpy 矩阵操作可以减少时间 - 也很好......

我的循环代码:

# Get sessions for every id
sessions = []
for i in data.id.unique():
    id_data = data['day'][data['id']==i].reset_index(drop=True)
    for ind in id_data.index:
        if ind == 0:
            temp = [0]
        elif ((id_data[ind] - id_data[ind - 1]) < 2):
            temp.append(temp[ind - 1])
        else:
            temp.append(temp[ind - 1] + 1)
    sessions.extend(temp)

# Add sessions to the table
data['session'] = sessions 

【问题讨论】:

  • data['session'] = np.where(data['day'].shift(1) &gt; data['day'], 1, 0) 让你开始,session 的增量给我带来了一个小问题:)
  • 介意根据您的数据测试此处提供的所有解决方案,然后根据您的时间安排接受最好的解决方案吗?
  • @coldspeed ,是的,这将是解决问题的一个很好的例子!我可以做到,但我想明天。我的国家现在是晚上,所以我要睡觉了;)

标签: python pandas loops


【解决方案1】:

您可以将groupby()np.where()diff()cumsum() 一起使用两次:

data['session'] = np.where(data.groupby('id')['day'].diff().fillna(0)>1, 1, 0)
data['session'] = data.groupby('id')['session'].cumsum()

产量:

    id  day  session
0    1    1        0
1    1    2        0
2    1   10        1
3    1   11        1
4    2    3        0
5    2    4        0
6    2   12        1
7    2   15        2
8    3    1        0
9    3   20        1
10   3   21        1
11   3   24        2

【讨论】:

  • data.groupby('id')['day'].diff().fillna(0).gt(1).astype(int) 也可能有效。
【解决方案2】:

你可以对布尔值求和

data.groupby('id').day.apply(lambda x : x.diff().gt(1).cumsum())
Out[614]: 
0     0
1     0
2     1
3     1
4     0
5     0
6     1
7     2
8     0
9     1
10    1
11    2
Name: day, dtype: int32

【讨论】:

    【解决方案3】:

    我们可以利用您的数据已排序这一事实来消除fillna,将两次groupby 调用减少到一次,并消除对apply 的需要。

    df['session'] = df.day.diff().ge(2)
    df['session'] = df.groupby('id').session.cumsum()
    

    df
        id  day  session
    0    1    1      0.0
    1    1    2      0.0
    2    1   10      1.0
    3    1   11      1.0
    4    2    3      0.0
    5    2    4      0.0
    6    2   12      1.0
    7    2   15      2.0
    8    3    1      0.0
    9    3   20      1.0
    10   3   21      1.0
    11   3   24      2.0
    

    作为回报,"session" 将是一个浮点列。

    【讨论】:

    • 嗨,@coldspeed,你错过了df.groupby('id').day.diff().ge(2) 中的分组。如果不按用户分组,那么在 daycolumn 的值之间的差异小于 2 天的情况下,会话指示符不会重置为零
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-12
    • 2017-11-30
    • 1970-01-01
    • 1970-01-01
    • 2019-09-06
    • 2017-08-04
    相关资源
    最近更新 更多