【问题标题】:reduce datetime list by timedelta按 timedelta 减少日期时间列表
【发布时间】:2015-06-23 08:32:51
【问题描述】:

在 python 中,如何通过 timedelta 邻域减少日期时间列表?

如果我有

    dates = [
        dt.datetime(1970, 1, 1, 0, 2),
        dt.datetime(1970, 1, 1, 0, 3),
        dt.datetime(1970, 1, 1, 0, 7),
        dt.datetime(1970, 1, 1, 0, 8)
    ]

还有一个时间增量

delta = dt.timedelta(minutes=2)

我怎样才能得到这个?

    expected = [
        dt.datetime(1970, 1, 1, 0, 2, 30),
        dt.datetime(1970, 1, 1, 0, 7, 30)
    ]

编辑

数字示例,如果我有这个数字列表

numbers = [1,2,6,7]
delta = 1

我尝试将近似值分组并获得该组的特征值(中心值)。 delta 是值之间的最大距离。

对于数字,特征值为

[1.5, 6.5]

因为这些值在 [1,2] 和 [6,7] 中分组并计算了平均值。

【问题讨论】:

  • 澄清一下,您的目标是遍历初始列表并在当前值的时间增量内消除任何条目吗?
  • timedelta neborhood 到底是什么意思?在预期中,您将 30 秒添加到第一个和第三个值。
  • @tgdn 邻域是一组近似值
  • 听起来你想要某种聚类算法。也许 Pandas 有类似的东西可以开箱即用。有点像这个问题:stackoverflow.com/questions/25516477/…
  • [1,2,3,4]delta = 1 的输出应该是什么?

标签: python datetime timedelta


【解决方案1】:
import datetime as dt

dates = [
    dt.datetime(1970, 1, 1, 0, 2),
    dt.datetime(1970, 1, 1, 0, 3),
    dt.datetime(1970, 1, 1, 0, 12),
    dt.datetime(1970, 1, 1, 0, 7),
    dt.datetime(1970, 1, 1, 0, 8),
    dt.datetime(1970, 1, 1, 0, 9),
    dt.datetime(1970, 1, 1, 0, 13)
]

def group_dates(dates, delta):
    it = iter(dates)
    prev = next(it)
    grouped, total =  [[prev]], delta.total_seconds()
    for dte in it:
        if (dte - prev).total_seconds() <= total:
            grouped[-1].append(dte)
        else:
            grouped.append([dte])
        prev = dte
    return grouped
def td(l):
    seconds = sum((d - dt.datetime(1970, 1, 1)).total_seconds() for d in l) / len(l)
    return dt.datetime.utcfromtimestamp(seconds)


from pprint import pprint as pp
pp([td(sub) for sub in group_dates(dates,dt.timedelta(minutes=2))])

为避免不必要的函数调用,请检查 len:

pp([td(sub) if len(sub) > 1 else sub[0] for sub in [datetime.datetime(1970, 1, 1, 0, 2, 30),
 datetime.datetime(1970, 1, 1, 0, 12),
 datetime.datetime(1970, 1, 1, 0, 8),
 datetime.datetime(1970, 1, 1, 0, 13)]group_dates(dates,dt.timedelta(minutes=2))])

或者随时产生值:

def group_dates(dates, delta):
    it = iter(dates)
    prev = next(it)
    grouped, total = (prev,),delta.total_seconds()
    for dte in it:
        if (dte - prev).total_seconds() <= total:
            grouped = grouped + (dte,)
        else:
            yield td(grouped)
            grouped = (dte,)
        prev = dte
    yield td(grouped)

pp(list(group_dates(dates,  delta=dt.timedelta(minutes=2))))
[datetime.datetime(1970, 1, 1, 0, 2, 30),
 datetime.datetime(1970, 1, 1, 0, 12),
 datetime.datetime(1970, 1, 1, 0, 8),
 datetime.datetime(1970, 1, 1, 0, 13)]

一些时间安排:

In [28]: dates = [                                                         
    dt.datetime(1970, 1, 1, 0, 2),
    dt.datetime(1970, 1, 1, 0, 3),
    dt.datetime(1970, 1, 1, 0, 4),
    dt.datetime(1970, 1, 1, 0, 7),
    dt.datetime(1970, 1, 1, 0, 8),
    dt.datetime(1970, 1, 1, 0, 9),
    dt.datetime(1970, 1, 1, 0, 15),
    dt.datetime(1970, 1, 1, 0, 22),
    dt.datetime(1970, 1, 1, 0, 24),
    dt.datetime(1970, 1, 1, 0, 27)
]

In [41]: for i in range(10000):    
          dates.append(dates[-1]+dt.timedelta(minutes=choice([1,2,3,4])))
   ....:     
In [42]: timeit [td(sub) if len(sub) > 1 else sub[0] for sub in group_dates(dates,dt.timedelta(minutes=2))]
100 loops, best of 3: 15.8 ms per loop

In [43]: timeit reduce_datetime_list_by_delta(dates, delta)                         
100 loops, best of 3: 16.9 ms per loop

In [44]: timeit timestamps = map(avgtm, groupby(dates, key=grouper(delta)))
10 loops, best of 3: 18.8 ms per loop

In [45]: timeit (list(group_dates_iter(dates,  delta = dt.timedelta(minutes=2))))
10 loops, best of 3: 18.4 ms per loop

【讨论】:

  • 是的...但是dt.datetime(1970, 1, 1, 0, 12) 值发生了什么?这个值应该在一个单独的组中
  • 啊,好吧,我的意思是问你只有一个约会时会发生什么。你只是保持日期?
  • @JuanPablo,已编辑,我应该在开头添加 prev 并基于此逻辑。您可以通过在循环中进行计算来避免存储所有日期时间,存储临时连续元素
【解决方案2】:
import datetime as dt

def datetime_to_epoch(dtime):
    return (dtime - dt.datetime(1970,1,1)).total_seconds()

def datetime_sublists(datetime_list, time_delta = dt.timedelta(days=1)):
    sublists = []

    temp = [datetime_list[0]]
    for i in range(len(datetime_list)-1):
        prev_date = datetime_list[i]
        current_date = datetime_list[i+1]

        if current_date - prev_date <= time_delta:
            temp.append(current_date)
        else:
            sublists.append(temp)
            temp = [current_date]
    sublists.append(temp)

    return sublists

def reduce_datetime_list_by_delta(date_list, delta):
    sublist = datetime_sublists(date_list, delta)

    reduced = []
    for dates in sublist:
        epochs = [ datetime_to_epoch(date) for date in dates]
        epoch_average = sum(epochs)/len(epochs)
        reduced.append(dt.datetime.utcfromtimestamp(epoch_average))

    return reduced


dates = [
    dt.datetime(1970, 1, 1, 0, 2),
    dt.datetime(1970, 1, 1, 0, 3),
    dt.datetime(1970, 1, 1, 0, 7),
    dt.datetime(1970, 1, 1, 0, 8),
    dt.datetime(1970, 1, 1, 0, 12)
]

delta = dt.timedelta(minutes=2)

print reduce_datetime_list_by_delta(dates, delta)

【讨论】:

    【解决方案3】:

    问题描述已经暴露了:你想使用itertools中的groupby()函数

    所需要的只是一个更智能的key 函数,它可以记住最后一个状态并继续提供相同的key 值,只要连续的时间戳比delta 更接近。

    分组后,将找到的组转换为平均时间,注意单个时间戳(包括示例)。

    import datetime as dt
    from itertools import groupby
    
    dates = [
            dt.datetime(1970, 1, 1, 0, 2),
            dt.datetime(1970, 1, 1, 0, 3),
            dt.datetime(1970, 1, 1, 0, 7),
            dt.datetime(1970, 1, 1, 0, 8),
            dt.datetime(1970, 1, 1, 0, 13)
        ]
    delta = dt.timedelta(minutes=2)
    
    class grouper:
        def __init__(self, delta):
            self.delta= delta
            self.last = None
    
        def __call__(self, tm):
            # we keep on returning the same key as long as successive time
            # stamps are within the last time stamp + delta
            self.last = tm if (self.last is None) or (tm - self.last)>self.delta \
                           else self.last
            return self.last
    
    # transform the result of groupby into average times
    def avgtm(item):
        (key, tms) = item
        tms = list(tms) # transform generator into list so we can index it
        return tms[0] + (tms[-1]-tms[0])/2 if len(tms)>1 else tms[0]
    
    timestamps = map(avgtm, groupby(dates, key=grouper(delta)))
    print "Time stamps: ",timestamps
    

    产量输出:

    Time stamps:  [datetime.datetime(1970, 1, 1, 0, 2, 30), 
                   datetime.datetime(1970, 1, 1, 0, 7, 30),
                   datetime.datetime(1970, 1, 1, 0, 13)]
    

    【讨论】:

    • itertools.groupby 与合适的比较器一起使用也是我想到的第一件事。你可以直接说not self.last 而不是self.last is None,如果你使用timestamps = (avgtm(list(tms)) for (_, tms) in groupby(dates, key=grouper(delta))) 中的生成器表达式而不是map.,'avgtm' 耦合会被简化一点
    猜你喜欢
    • 1970-01-01
    • 2021-08-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-09
    • 2017-11-20
    • 1970-01-01
    相关资源
    最近更新 更多