【问题标题】:Clustering / Grouping a list based on time (python)根据时间对列表进行聚类/分组(python)
【发布时间】:2013-12-05 09:38:57
【问题描述】:

我有一个列表列表,我想根据时间集群将其分组到单独的列表中。

我可以轻松地根据时间对其进行排序,但我还没有确定一种简单的方法将其组合在一起。我对它是日期时间/时间格式或文本很好,任何一种都适合我。我需要根据集群处理其他数据。这是我可能正在使用的示例数据集。

[['asdf', '2012-01-01 00:00:12', '1234'],
 ['asdf', '2012-01-01 00:00:31', '1235'],
 ['asdf', '2012-01-01 00:00:57', '2345'],
 ['asdf', '2012-01-01 00:01:19', '2346'],
 ['asdf', '2012-01-01 00:01:25', '2345'],
 ['asdf', '2012-01-01 09:04:14', '3465'],
 ['asdf', '2012-01-01 09:04:34', '1613'],
 ['asdf', '2012-01-01 09:04:51', '8636'],
 ['asdf', '2012-01-01 09:05:15', '5847'],
 ['asdf', '2012-01-01 09:05:29', '3672'],
 ['asdf', '2012-01-01 09:05:30', '2367'],
 ['asdf', '2012-01-01 09:05:43', '9544'],
 ['asdf', '2012-01-01 14:48:15', '2572'],
 ['asdf', '2012-01-01 14:48:34', '7483'],
 ['asdf', '2012-01-01 14:48:56', '5782']]

结果应该是这样的。每个组的嵌套列表列表。

[[['asdf', '2012-01-01 00:00:12', '1234'],
  ['asdf', '2012-01-01 00:00:31', '1235'],
  ['asdf', '2012-01-01 00:00:57', '2345'],
  ['asdf', '2012-01-01 00:01:19', '2346'],
  ['asdf', '2012-01-01 00:01:25', '2345']],
 [['asdf', '2012-01-01 09:04:14', '3465'],
  ['asdf', '2012-01-01 09:04:34', '1613'],
  ['asdf', '2012-01-01 09:04:51', '8636'],
  ['asdf', '2012-01-01 09:05:15', '5847'],
  ['asdf', '2012-01-01 09:05:29', '3672'],
  ['asdf', '2012-01-01 09:05:30', '2367'],
  ['asdf', '2012-01-01 09:05:43', '9544']],
 [['asdf', '2012-01-01 14:48:15', '2572'],
  ['asdf', '2012-01-01 14:48:34', '7483'],
  ['asdf', '2012-01-01 14:48:56', '5782']]]

集群没有固定的大小,也没有固定的时间。它们可以在一天中随机发生,并且需要根据较大的时间间隔进行聚类。

第一组发生在午夜之后,有 5 个条目,下一组以 09:05 为中心,有 7 个条目。最后一个发生在 14:48 左右,只有 3 个条目。我也可以在每个小时结束时有两组,所以我不能只按小时分组。

我已经按照列表中的第一个字段对数据进行了排序和分组,我只需要将它们分解成更小的块进行处理。我愿意将日期更改为完成分组所需的任何格式,因为这是我对数据进行的分析的关键部分。

我希望将解决方案保留在基本的 python 库中,但如果没有解决方案,我可以尝试获取其他包。

我已经查看了herehereherehere 和许多其他解决方案,但没有一个解决这些时代的随机性问题。

在大于 X 时间的任何间隔处拆分列表将是一个很好的解决方案,因此我可以将 X 更改为 5 或 10 分钟,无论是否合适。删除长度小于 3 的任何组也将是一个奖励,但可以在最后轻松完成。

我现在唯一真正的想法是循环遍历列表,比较当前时间和新时间并以这种方式拆分列表,但是当有数百万条记录要排序时,这似乎是解决这个问题的一种非常低效的方法和组。

任何帮助将不胜感激。如果其中任何一个没有意义,我会尽力澄清。

【问题讨论】:

  • 您似乎要求每小时一组存储桶。这种风格会返回您期望的结果。这也是您发布的第一个 SO 链接中显示的确切方法:stackoverflow.com/questions/2344639/…
  • 你能解释一下I could also have two groups at either end of the hour as well, so I can not just group by the hour.吗?
  • @thefourtheye: OP might mean something like that(效率低,但应该会产生预期的结果)。
  • 您的示例中的数据是按时间排序的——您的真实数据是按时间排序的吗?
  • @VooDooNOFX & thefourtheye 不,我不想按小时分组,因为我可以在一小时内有两个单独的组。意思是 00:00:10,20,30 和 00:59:00,10,20 由于差距很大,它们需要是单独的组。我也可以有 00:59:50、01:00:00 和 01:00:10,这将是一组。

标签: python python-2.7


【解决方案1】:

这是我最近学到的另一种方法,使用 defaultdict。您可以轻松调整它以按分钟、秒等进行进一步分组!

from collections import defaultdict

mylist = [['asdf', '2012-01-01 00:00:12', '1234'],
 ['asdf', '2012-01-01 00:00:31', '1235'],
 ['asdf', '2012-01-01 00:00:57', '2345'],
 ['asdf', '2012-01-01 00:01:19', '2346'],
 ['asdf', '2012-01-01 00:01:25', '2345'],
 ['asdf', '2012-01-01 09:04:14', '3465'],
 ['asdf', '2012-01-01 09:04:34', '1613'],
 ['asdf', '2012-01-01 09:04:51', '8636'],
 ['asdf', '2012-01-01 09:05:15', '5847'],
 ['asdf', '2012-01-01 09:05:29', '3672'],
 ['asdf', '2012-01-01 09:05:30', '2367'],
 ['asdf', '2012-01-01 09:05:43', '9544'],
 ['asdf', '2012-01-01 14:48:15', '2572'],
 ['asdf', '2012-01-01 14:48:34', '7483'],
 ['asdf', '2012-01-01 14:48:56', '5782']]

record_dict = defaultdict(list)

for item in mylist: 
    date_time = item[1]
    date_time2 = date_time.split(" ")
    date_time3 = date_time2[1].split(":")
    date_time4 = date_time3[0]
    record_dict[date_time4].append(item)

res_list = list(record_dict.values())

print(res_list)

输出:

OUTPUT:
[

[['asdf', '2012-01-01 00:00:12', '1234'], ['asdf', '2012-01-01 00:00:31', '1235'], 
['asdf', '2012-01-01 00:00:57', '2345'], ['asdf', '2012-01-01 00:01:19', '2346'], 
['asdf', '2012-01-01 00:01:25', '2345']], 

[['asdf', '2012-01-01 09:04:14', '3465'], ['asdf', '2012-01-01 09:04:34', '1613'], 
['asdf', '2012-01-01 09:04:51', '8636'], ['asdf', '2012-01-01 09:05:15', '5847'], 
['asdf', '2012-01-01 09:05:29', '3672'], ['asdf', '2012-01-01 09:05:30', '2367'], 
['asdf', '2012-01-01 09:05:43', '9544']], 

[['asdf', '2012-01-01 14:48:15', '2572'], ['asdf', '2012-01-01 14:48:34', '7483'], 
['asdf', '2012-01-01 14:48:56', '5782']],

]

【讨论】:

    【解决方案2】:

    ... 遍历列表比较 当前时间与新时间并以这种方式拆分列表

    似乎就是这样做的。使用 itertools.groupyby()(J. F. Sebastian 的评论)
    可能会更好地扩展,但这似乎与提供的 15 个竞争。

    def grp(data, dHours, dMinutes, dSeconds):
    
        delta = datetime.timedelta(hours = dHours, minutes = dMinutes, seconds = dSeconds)
        final = list()
        tmp = list()
        date_format = "%Y-%m-%d %H:%M:%S"
    
        tmp.append(data[0])
        previous = datetime.datetime.strptime(data[0][1], date_format)
    
        for row in data[1:]:
            dt = datetime.datetime.strptime(row[1], date_format)
            if dt - previous > delta:
                #if len(tmp) > 2:
                final.append(tmp)
                tmp = list()
            tmp.append(row)
            previous = dt
    
        final.append(tmp)
        return final
    

    【讨论】:

      【解决方案3】:

      我不会解决你的问题,但我会努力让你对你已经知道的事情感觉更好;-)

      忘记问题的所有细节,而是考虑一个普通整数列表。假设您想通过至少 5 个间隔将其分成几组。以下是列表:

      [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, ...]
      

      哎呀!那么每个元素都在其自己的组中,如果不比较每对相邻的元素,则根本 无法 知道这一点。想想看。所以:

      我现在唯一真正的想法是遍历列表比较 当前时间与新时间并以这种方式拆分列表, 但这似乎是解决这个问题的一种非常低效的方法 当有数百万条记录需要排序和分组时。

      在上面的示例中,这是可以做到的最好的!它需要的时间与元素的数量成线性关系,这很少被认为是“非常低效的”。

      现在在某些情况下确实可能做得更好。让我们将上面的列表更改为:

      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, ...]
      

      再次与差距 5,总共只有一组。可以使用少于与列表长度成比例的比较次数来发现它吗?也许,使用二进制搜索的变体,可能会发现使用与列表长度的对数成比例的比较次数。但是细节就是一切,而且它们很棘手。太棘手了,我害怕让它们适应你更混乱的问题。

      而且,最后,除非你有 非常 大组,否则我预计它实际上会比做一件明显的事情要慢! DSM 的答案使用了高效且或多或少直截了当的 Python 习语;除非应用于非常有利的情况,否则需要跟踪许多小细节的复杂算法通常运行速度较慢(即使它具有更好的理论 O() 行为)。

      因此,对您一目了然的简单循环感到满意:-)

      【讨论】:

        【解决方案4】:

        如果我们在超出某个限制的时间差异处分裂,那么就像

        # turn strings into datetimes
        date_format = "%Y-%m-%d %H:%M:%S"
        for row in data:
            row[1] = datetime.datetime.strptime(row[1], date_format)
        
        split_dt = datetime.timedelta(minutes=5)
        dts = (d1[1]-d0[1] for d0, d1 in zip(data, data[1:]))
        split_at = [i for i, dt in enumerate(dts, 1) if dt >= split_dt]
        groups = [data[i:j] for i, j in zip([0]+split_at, split_at+[None])]
        

        可能会奏效。 (不过要小心栅栏错误……我太容易犯了!)

        【讨论】:

        • 这似乎完全符合我的要求。我什至可以在最终列表推导中添加“if len(data[i:j]) > 2”,以消除太小的组。我不能对答案投赞成票,但我已经接受了。
        • @DSM - 我把你的脚本放在一个函数中,http://dpaste.com/1477229/,它正在修改全局列表。我无法弄清楚为什么会这样。我想了解一下,有什么想法吗?
        • 看起来“if len(data[i:j]) > 2”毕竟不起作用,但是“groups = [g for g in groups if len(g) > 2] " 清理它
        • @wwii: data = data[:] 在你的代码中只做一个浅拷贝。 @eseglem:[data[i:j] for i, j in zip([0]+split_at, split_at+[None]) if len(data[i:j]) > 5] 应该可以工作,尽管有丑陋的重复。不过,我认为做你所做的事情会更干净,并将集群的构建与大小过滤分开。
        • @wwii 是不是因为您将 DATA 的引用作为数据传递,然后修改数据,而这些数据又指向全局? ... 不是最好的措辞,但我相信 data 是对 DATA 的引用,而不是它的副本
        【解决方案5】:

        也许不是最优雅的,但这样的东西应该可以工作:

        In [1]: from itertools import groupby
        
        In [2]: d = [['asdf',1],
           ...:      ['asdf',2],
           ...:      ['asdf',5],
           ...:      ['asdf',6],
           ...:      ['asdf',7],
           ...:      ['asdf',20]]
        
        In [3]: t = [x[1] for x in d]
        
        In [4]: diff = [0] + [t[i+1] - t[i] for i in range(len(t)-1)]
        
        In [5]: i = 0
        
        In [6]: key = []
        
        In [7]: for x in diff:
           ...:     if x > 2:
           ...:         i += 1
           ...:     key.append(i)
           ...:
        
        In [8]: [zip(*list(g))[0] for k, g in groupby(zip(d,key), lambda x: x[1])]
        Out[8]:
        [(['asdf', 1], ['asdf', 2]),
         (['asdf', 5], ['asdf', 6], ['asdf', 7]),
         (['asdf', 20],)]
        

        当然,您必须解析日期字符串以获得合理的时差。

        【讨论】:

          猜你喜欢
          • 2022-01-23
          • 1970-01-01
          • 2020-11-11
          • 1970-01-01
          • 2018-01-18
          • 2020-06-18
          • 1970-01-01
          • 2017-06-18
          相关资源
          最近更新 更多