【问题标题】:How to count the number of time intervals that meet a boolean condition within a pandas dataframe?如何计算熊猫数据框中满足布尔条件的时间间隔数?
【发布时间】:2017-01-20 02:03:34
【问题描述】:

我有一个熊猫df,在column1 中有一个时间序列,在column2 中有一个布尔条件。这描述了满足特定条件的连续时间间隔。请注意,时间间隔的长度不等。

Timestamp   Boolean_condition
1           1
2           1
3           0
4           1
5           1
6           1
7           0
8           0
9           1
10          0

如何统计整个序列中满足这个条件的时间间隔总数?

所需的输出应如下所示:

Timestamp   Boolean_condition   Event_number
1           1                   1
2           1                   1
3           0                   NaN
4           1                   2
5           1                   2
6           1                   2
7           0                   NaN
8           0                   NaN
9           1                   3
10          0                   NaN

【问题讨论】:

  • 看到 jezrael 的回答后,它激励我更加努力地工作,因此我想出了一个更快的解决方案,但改用 numpy。不过你不必接受我的,但这只是为了向你展示处理手头相同问题的各种观点/方法。

标签: python pandas


【解决方案1】:

您可以使用两个masks 中的cumsum 创建Series,然后通过函数Series.mask 创建NaN

mask0 = df.Boolean_condition.eq(0)
mask2 = df.Boolean_condition.ne(df.Boolean_condition.shift(1))
print ((mask2 & mask0).cumsum().add(1))
0    1
1    1
2    2
3    2
4    2
5    2
6    3
7    3
8    3
9    4
Name: Boolean_condition, dtype: int32

df['Event_number'] = (mask2 & mask0).cumsum().add(1).mask(mask0)
print (df)
   Timestamp  Boolean_condition  Event_number
0          1                  1           1.0
1          2                  1           1.0
2          3                  0           NaN
3          4                  1           2.0
4          5                  1           2.0
5          6                  1           2.0
6          7                  0           NaN
7          8                  0           NaN
8          9                  1           3.0
9         10                  0           NaN

时间安排

#[100000 rows x 2 columns
df = pd.concat([df]*10000).reset_index(drop=True)
df1 = df.copy()
df2 = df.copy()

def nick(df):
    isone = df.Boolean_condition[df.Boolean_condition.eq(1)]
    idx = isone.index
    grp = (isone != idx.to_series().diff().eq(1)).cumsum()
    df.loc[idx, 'Event_number'] = pd.Categorical(grp).codes + 1
    return df

def jez(df):
    mask0 = df.Boolean_condition.eq(0)
    mask2 = df.Boolean_condition.ne(df.Boolean_condition.shift(1))
    df['Event_number'] = (mask2 & mask0).cumsum().add(1).mask(mask0)
    return (df)

def jez1(df):
    mask0 = ~df.Boolean_condition
    mask2 = df.Boolean_condition.ne(df.Boolean_condition.shift(1))
    df['Event_number'] = (mask2 & mask0).cumsum().add(1).mask(mask0)
    return (df)

In [68]: %timeit (jez1(df))
100 loops, best of 3: 6.45 ms per loop

In [69]: %timeit (nick(df1))
100 loops, best of 3: 12 ms per loop

In [70]: %timeit (jez(df2))
100 loops, best of 3: 5.34 ms per loop

【讨论】:

  • 你可以查看我的解决方案,看起来更简单,所以快了 2.24 倍。
  • @NickilMaveli - 嗯,我知道numpy 解决方案显然很快,但不幸的是复杂。所以我喜欢pandas,它有点慢,但没有那么复杂,同样的工作需要更少的功能。此外,pandas 对NaN 值有更好的支持,在numpy 中需要为NaN 支持添加一些新功能,因此复杂的解决方案更加复杂:((通常,我不确定你的解决方案)。
【解决方案2】:

您可以尝试以下方法:

1) 获取True 实例(此处为1)的所有值,其中包含isone

2) 获取相应的索引集并将其转换为系列表示,以便新系列具有它的索引和值作为早期计算的索引。执行连续行之间的差异并检查它们是否等于 1。这成为我们的布尔掩码。

3) 将isone 与获得的布尔掩码进行比较,每当它们不相等时,我们就取它们的累积和(也称为元素之间的邻接检查)。这些有助于我们进行分组。

4) 使用loc 作为isone 的索引,我们将在将grp 数组更改为分类格式后计算的代码分配给创建的新列Event_number


isone = df.Bolean_condition[df.Bolean_condition.eq(1)]
idx = isone.index
grp = (isone != idx.to_series().diff().eq(1)).cumsum()
df.loc[idx, 'Event_number'] = pd.Categorical(grp).codes + 1


更快的方法:

仅使用numpy:

1) 获取它的数组表示。

2) 计算非零,此处为 (1's) 索引。

3) 在这个数组的开头插入NaN,这将作为我们在考虑连续行的情况下执行差异的起点。

4) 初始化一个用Nan's 填充的新数组,其形状与原数组相同。

5) 每当连续行之间的差不等于 1 时,我们取它们的累积和,否则它们属于同一组。这些值在之前有 1's 的索引处进行估算。

6) 将这些分配回新列。


def nick(df):
    b = df.Bolean_condition.values
    slc = np.flatnonzero(b)
    slc_pl_1 = np.append(np.nan, slc)
    nan_arr = np.full(b.size, fill_value=np.nan)
    nan_arr[slc] = np.cumsum(slc_pl_1[1:] - slc_pl_1[:-1] != 1)
    df['Event_number'] = nan_arr
    return df

时间安排:

对于 10,000 行的 DF

np.random.seed(42)
df1 = pd.DataFrame(dict(
        Timestamp=np.arange(10000),
        Bolean_condition=np.random.choice(np.array([0,1]), 10000, p=[0.4, 0.6]))
                  )

df1.shape
# (10000, 2)

def jez(df):
    mask0 = df.Bolean_condition.eq(0)
    mask2 = df.Bolean_condition.ne(df.Bolean_condition.shift(1))
    df['Event_number'] = (mask2 & mask0).cumsum().mask(mask0)
    return (df)

nick(df1).equals(jez(df1))
# True

%%timeit
nick(df1)
1000 loops, best of 3: 362 µs per loop

%%timeit
jez(df1)
100 loops, best of 3: 1.56 ms per loop

对于包含 100 万行的 DF

np.random.seed(42)
df1 = pd.DataFrame(dict(
        Timestamp=np.arange(1000000),
        Bolean_condition=np.random.choice(np.array([0,1]), 1000000, p=[0.4, 0.6]))
                  )

df1.shape
# (1000000, 2)

nick(df1).equals(jez(df1))
# True

%%timeit
nick(df1)
10 loops, best of 3: 34.9 ms per loop

%%timeit
jez(df1)
10 loops, best of 3: 50.1 ms per loop

【讨论】:

    【解决方案3】:

    这应该可以,但对于很长的 df 可能会有点慢。

    df = pd.concat([df,pd.Series([0]*len(df), name = '2')], axis = 1)
    if df.iloc[0,1] == 1:
            counter = 1
            df.iloc[0, 2] = counter
        else:
            counter = 0
            df.iloc[0,2] = 0
        previous = df.iloc[0,1]
        for y,x in df.iloc[1:,].iterrows():
            print(y)
            if x[1] == 1 and previous == 1:
                previous = x[1]
                df.iloc[y, 2] = counter
            if x[1] == 0:
                previous = x[1]
                df.iloc[y,2] = 0
            if x[1] == 1 and previous == 0:
                counter += 1
                previous = x[1]
                df.iloc[y,2] = counter
    

    【讨论】:

      【解决方案4】:

      自定义函数可以解决问题。这是 Matlab 代码中的一个解决方案:

      Boolean_condition = [1 1 0 1 1 1 0 0 1 0];
      Event_number = [NA NA NA NA NA NA NA NA NA NA];
      loop_event_number = 1;
      
      for timestamp=1:10
      if Boolean_condition(timestamp)==1
      Event_number(timestamp) = loop_event_number;
      last_event_number = loop_event_number;
      else
      loop_event_number = last_event_number +1;
      end
      end
      
      % Event_number = 1 1 NA 2 2 2 NA NA 3 NA
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-06-23
        • 1970-01-01
        • 2020-12-08
        • 2021-02-06
        • 2021-08-18
        • 1970-01-01
        • 1970-01-01
        • 2020-09-28
        相关资源
        最近更新 更多