合并 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.timedelta
或 numpy.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.
请帮我找到一个不需要大量循环的解决方案。我有一个时间戳列表,例如
["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.timedelta
或 numpy.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.