Pandas测量满足条件后的经过时间

17

我有以下数据框:

               Time   Work
2018-12-01 10:00:00     Off
2018-12-01 10:00:02     On
2018-12-01 10:00:05     On
2018-12-01 10:00:06     On
2018-12-01 10:00:07     On
2018-12-01 10:00:09    Off
2018-12-01 10:00:11    Off
2018-12-01 10:00:14     On
2018-12-01 10:00:16     On
2018-12-01 10:00:18     On
2018-12-01 10:00:20    Off

我想创建一个新的列,用于记录设备启动后经过的时间。

               Time   Work    Elapsed Time
2018-12-01 10:00:00    Off               0
2018-12-01 10:00:02     On               2
2018-12-01 10:00:05     On               5
2018-12-01 10:00:06     On               6
2018-12-01 10:00:07     On               7
2018-12-01 10:00:09    Off               0
2018-12-01 10:00:11    Off               0
2018-12-01 10:00:14     On               3
2018-12-01 10:00:16     On               5
2018-12-01 10:00:18     On               7
2018-12-01 10:00:20    Off               0

我该怎么做?


4
欢迎来到Stack Overflow,Rafael!我当初只是因为标题看起来有趣才来的,但最终却学到了Pandas在这个上下文中的实际含义。 - zr00
5个回答

14
你可以使用 groupby:
# df['Time'] = pd.to_datetime(df['Time'], errors='coerce') # Uncomment if needed.
sec = df['Time'].dt.second
df['Elapsed Time'] = (
    sec - sec.groupby(df.Work.eq('Off').cumsum()).transform('first'))

df
                  Time Work  Elapsed Time
0  2018-12-01 10:00:00  Off             0
1  2018-12-01 10:00:02   On             2
2  2018-12-01 10:00:05   On             5
3  2018-12-01 10:00:06   On             6
4  2018-12-01 10:00:07   On             7
5  2018-12-01 10:00:09  Off             0
6  2018-12-01 10:00:11  Off             0
7  2018-12-01 10:00:14   On             3
8  2018-12-01 10:00:16   On             5
9  2018-12-01 10:00:18   On             7
10 2018-12-01 10:00:20  Off             0

该想法是提取秒数部分,从状态从“关闭”变为“开启”的第一时刻开始减去经过的时间。这是使用transformfirst完成的。

cumsum用于标识组:

df.Work.eq('Off').cumsum()

0     1
1     1
2     1
3     1
4     1
5     2
6     3
7     3
8     3
9     3
10    4
Name: Work, dtype: int64
如果您的设备可能在“开启”状态下跨越多分钟,则将 sec 初始化为:
sec = df['Time'].values.astype(np.int64) // 10e8

df['Elapsed Time'] = (
    sec - sec.groupby(df.Work.eq('Off').cumsum()).transform('first'))

df
                  Time Work  Elapsed Time
0  2018-12-01 10:00:00  Off           0.0
1  2018-12-01 10:00:02   On           2.0
2  2018-12-01 10:00:05   On           5.0
3  2018-12-01 10:00:06   On           6.0
4  2018-12-01 10:00:07   On           7.0
5  2018-12-01 10:00:09  Off           0.0
6  2018-12-01 10:00:11  Off           0.0
7  2018-12-01 10:00:14   On           3.0
8  2018-12-01 10:00:16   On           5.0
9  2018-12-01 10:00:18   On           7.0
10 2018-12-01 10:00:20  Off           0.0

@Rafael 是的,这里的假设是你的行从“关闭”状态开始。你能在你的框架开头附加一行吗? - cs95
@Rafael 好的,关于你提到的第二点,df['Time'].values.astype(np.int64) // 10e8 这个可以吗? - cs95
代码运行了几秒钟没问题。但是,当列“Work”的第一个单元格为“On”时,经过的时间不从零开始。此外,当时间变成下一分钟时,经过的时间是负数。我尝试使用sec = df['Time'].astype(int),但是出现了错误:无法将[datetime64[ns]]转换为[int32]。 - Rafael
@Rafael,你能再读一遍我的评论吗?关于你的那个。 - cs95
我删除了评论并重新发布,以便我可以编辑它。关于你的答案,我每天都会收到数据,它以“On”开头和结尾,所以我不确定是否可以添加一行,但我会尝试使用日期更改作为条件。代码 df['Time'].values.astype(np.int64) // 10e8 确实起作用。 - Rafael

8

如果我理解正确,首先要使用transform

(df.Time-df.Time.groupby(df.Work.eq('Off').cumsum()).transform('first')).dt.seconds
Out[1090]: 
0     0
1     2
2     5
3     6
4     7
5     0
6     0
7     3
8     5
9     7
10    0
Name: Time, dtype: int64

如果我将时间列设置为索引,我应该如何更改代码以使其正常工作? - Rafael
@Rafael df.reset_index(inplace=True) @Rafael df.reset_index(inplace=True) - BENY
我在你编写计算经过时间的代码之前添加了一行代码df.set_index('Time', inplace=True)。因此,我必须调整代码以在索引列中进行减法运算,而不是在时间列中进行。我尝试过(df.index-df.index.groupby(df.Operation.eq('Off').cumsum()).transform('first')),但它没有起作用。 - Rafael
@Rafael,这里的df.reset_index(inplace=True)是重置而不是设置。 - BENY

7
你可以使用两个groupby。第一个计算每个组内的时间差。第二个则对每个组内的时间差求和。
s = (df.Work=='Off').cumsum()
df['Elapsed Time'] = df.groupby(s).Time.diff().dt.total_seconds().fillna(0).groupby(s).cumsum()

输出

                  Time Work  Elapsed Time
0  2018-12-01 10:00:00  Off           0.0
1  2018-12-01 10:00:02   On           2.0
2  2018-12-01 10:00:05   On           5.0
3  2018-12-01 10:00:06   On           6.0
4  2018-12-01 10:00:07   On           7.0
5  2018-12-01 10:00:09  Off           0.0
6  2018-12-01 10:00:11  Off           0.0
7  2018-12-01 10:00:14   On           3.0
8  2018-12-01 10:00:16   On           5.0
9  2018-12-01 10:00:18   On           7.0
10 2018-12-01 10:00:20  Off           0.0

代码运行良好。然而,当数据框的第一个工作单元格为“On”时,经过的时间不为零。 - Rafael
@Rafael 说得好。也许有一种巧妙的方法可以在计算中修复它,但是您可以使用 df.loc[df.index < s[s==1].idxmax(), 'Elapsed Time'] = 0 在事后进行修复。我猜如果机器从未开启仍然存在问题,但这也可以被修复或处理。 - ALollz

4
使用groupby,你可以这样做:
df['Elapsed Time'] = (df.groupby(df.Work.eq('Off').cumsum()).Time
                      .transform(lambda x: x.diff()
                                 .dt.total_seconds()
                                 .cumsum())
                      .fillna(0))

>>> df
                  Time Work  Elapsed Time
0  2018-12-01 10:00:00  Off           0.0
1  2018-12-01 10:00:02   On           2.0
2  2018-12-01 10:00:05   On           5.0
3  2018-12-01 10:00:06   On           6.0
4  2018-12-01 10:00:07   On           7.0
5  2018-12-01 10:00:09  Off           0.0
6  2018-12-01 10:00:11  Off           0.0
7  2018-12-01 10:00:14   On           3.0
8  2018-12-01 10:00:16   On           5.0
9  2018-12-01 10:00:18   On           7.0
10 2018-12-01 10:00:20  Off           0.0

4
一种numpy切片的方法
u, f, i = np.unique(df.Work.eq('Off').values.cumsum(), True, True)
t = df.Time.values

df['Elapsed Time'] = t - t[f[i]]
df

                  Time Work Elapsed Time
0  2018-12-01 10:00:00  Off     00:00:00
1  2018-12-01 10:00:02   On     00:00:02
2  2018-12-01 10:00:05   On     00:00:05
3  2018-12-01 10:00:06   On     00:00:06
4  2018-12-01 10:00:07   On     00:00:07
5  2018-12-01 10:00:09  Off     00:00:00
6  2018-12-01 10:00:11  Off     00:00:00
7  2018-12-01 10:00:14   On     00:00:03
8  2018-12-01 10:00:16   On     00:00:05
9  2018-12-01 10:00:18   On     00:00:07
10 2018-12-01 10:00:20  Off     00:00:00

我们可以使用以下方法确定整数位:
df['Elapsed Time'] = (t - t[f[i]]).astype('timedelta64[s]').astype(int)
df

                  Time Work  Elapsed Time
0  2018-12-01 10:00:00  Off             0
1  2018-12-01 10:00:02   On             2
2  2018-12-01 10:00:05   On             5
3  2018-12-01 10:00:06   On             6
4  2018-12-01 10:00:07   On             7
5  2018-12-01 10:00:09  Off             0
6  2018-12-01 10:00:11  Off             0
7  2018-12-01 10:00:14   On             3
8  2018-12-01 10:00:16   On             5
9  2018-12-01 10:00:18   On             7
10 2018-12-01 10:00:20  Off             0

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