合并 Python 中的时间戳列表

Merge list of timestamps in Python

请帮我找到一个不需要大量循环的解决方案。我有一个时间戳列表,例如

["2014-04-11 08:00:00.000000",
 "2014-04-11 09:35:00.000000",
 "2014-04-11 09:35:00.000000",
 "2014-04-11 09:40:00.000000",
 "2014-04-11 11:00:00.000000",
 ...]

我想在列表中添加 'merge' 时间戳,以便彼此共同 window(例如 10 分钟)内的时间戳成为一个条目。所以上面的例子列表会变成

["2014-04-11 08:00:00.000000",
 "2014-04-11 09:35:00.000000",
 "2014-04-11 11:00:00.000000",
 ...]

另请注意,合并的三个时间戳的值是“9:35”而不是“9:40”。我想合并时间戳以转到最频繁的条目。如果出现平局,则在 earlier/most-frequent 时间戳上合并。

而且我也在尝试跟踪合并了多少时间戳。因此,对于上面的示例,保留计数的列表将是 [1,3,1,...]

如果您还没有看过它,那么在这种情况下将其转换为 pandas DataFrame 会很有效。

IMO 的一种方法是使用这些时间戳作为 index 并将计数作为 column 来制作数据帧。然后,通过循环一次,您可以删除相同公共 window 中的那些行(使用 datetime.timedeltanumpy.timedelta64)并更新列的值 count对于那一行。

获得更多信息有助于提供更详细的答案。例如,您的列表是否已排序,如果未排序,是否必须保持合并前的相同顺序? (从你的例子看来它已经排序)

你可以使用 groupby 和一个特殊的键,在组之间 "switches"。首先准备数据:

from itertools import groupby
from datetime import datetime

l = ["2014-04-11 08:00:00.000000",
 "2014-04-11 09:35:00.000000",
 "2014-04-11 09:35:00.000000",
 "2014-04-11 09:40:00.000000",
 "2014-04-11 11:00:00.000000"]
l = map(lambda x: datetime.strptime(x, "%Y-%m-%d %H:%M:%S.%f"), l)

现在您可以:

class grouper():
    def __call__(self, d):
        if not hasattr(self, 'prev'):    # first element: init switch
            self.switch = 1
        elif (d - self.prev).total_seconds() > 10*60:    # 10min
            self.switch *= -1
        self.prev = d                    # save current value
        return self.switch


def most_common(group):
    lst = list(group)
    return lst[0]   # choose the first element in the group

>>> [most_common(g) for k, g in groupby(l, key = grouper())]
[datetime.datetime(2014, 4, 11, 8, 0),
 datetime.datetime(2014, 4, 11, 9, 35),
 datetime.datetime(2014, 4, 11, 11, 0)]

您可以调整 most_common 函数以符合您的条件。

假设时间戳已排序,那么...:[=​​19=]

import datetime

def merged_ts(timestamps):
    merged_strings = []
    counts = []
    for ts in timestamps:
        dt = datetime.datetime.strptime(ts, '%Y-%m-%d %H:%M:%S.%f')
        if not merged_strings:  # first-time switch
            merged_string.append(ts)
            counts.append(1)
            oldt = dt
            continue
        dif = dt - oldt
        if dif.total_seconds < 300:  # 5 minutes
            counts[-1] += 1
            continue
        merged_string.append(ts)
        counts.append(1)
        oldt = dt
    return merged_strings, counts

添加:OP 指定时间戳最初没有排序(但可能会为此目的排序),如果时间戳是 T、T+4 分钟、T+8 分钟、T+12 分钟等,他们会必须合并到一个插槽中(w/appropriate 计数)。这个版本几乎不需要改动,然后...:[=​​19=]

import datetime

def merged_ts(timestamps):
    merged_strings = []
    counts = []
    for ts in sorted(timestamps):
        dt = datetime.datetime.strptime(ts, '%Y-%m-%d %H:%M:%S.%f')
        if not merged_strings:  # first-time switch
            merged_string.append(ts)
            counts.append(1)
            oldt = dt
            continue
        dif = dt - oldt
        oldt = dt
        if dif.total_seconds < 300:  # 5 minutes
            counts[-1] += 1
        else:
            merged_string.append(ts)
            counts.append(1)
    return merged_strings, counts

我刚刚添加了一个 sorted 调用,并将 oldt = dt 移动到它在循环的每一段发生的位置(除了第一次切换)——这样每个新的将检查传入的 ts 与当前存储桶中的 "closest"(最新)日期戳,而不是像以前那样与 "first"(最旧)的日期戳进行比较。 (仅作为风格问题,我将最后的条件更改为 if/else 而不是在那里使用 continue,因为条件的两条腿现在很平衡).

第一次切换很傻,但是删除这个(不重复 strptime 需要稍微精巧的代码,例如:

if not timestamps: return [], []
it = iter(sorted(
    (ts,datetime.datetime.strptime(ts, '%Y-%m-%d %H:%M:%S.%f'))
    for ts in timestamps))
first = next(it)
merged_strings = [first[1]]
oldt = first[0]
counts = [1]
for ts, st in it:
    dif = dt - oldt
    oldt = dt
    if dif.total_seconds < 300:  # 5 minutes
        counts[-1] += 1
    else:
        merged_string.append(ts)
        counts.append(1)
return merged_strings, counts

第一次切换的版本对我来说似乎更可取,在这种情况下,纯粹是出于风格原因。

可以这样解决:

import datetime

data = ["2014-04-11 08:00:00.000000", "2014-04-11 09:35:00.000000", "2014-04-11 09:35:00.000000", "2014-04-11 09:40:00.000000", "2014-04-11 11:00:00.000000"]

delta = datetime.timedelta(minutes=10)
result = []
bucket = []
current = None
for item in data:
    datetime_obj = datetime.datetime.strptime(item, '%Y-%m-%d %H:%S:%M.%f')
    if current is None:
        current = datetime_obj
        bucket = [current]
        continue
    if (datetime_obj - current) <= delta:
        bucket.append(datetime_obj)
    else:
        result.append(bucket)
        current = datetime_obj
        bucket = [current]

if bucket:
    result.append(bucket)

for bucket in result:
    print(bucket)

示例:

>>> for bucket in result:
...     print(bucket)
...
[datetime.datetime(2014, 4, 11, 8, 0)]
[datetime.datetime(2014, 4, 11, 9, 0, 35), datetime.datetime(2014, 4, 11, 9, 0, 40)]
[datetime.datetime(2014, 4, 11, 11, 0)]

result 数据结构可用于计算所需值:标识 window 的每个时间戳和可用于创建该时间戳的数量 ("consumed") window.