基于时间(python)对列表进行聚类/分组

时间:2013-11-21 03:56:50

标签: python python-2.7

我有一个列表列表,我想根据时间集合分组到不同的列表中。

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

[['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的组也是一个奖励,但最后可以轻松完成。

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

非常感谢任何帮助。如果其中任何一个没有意义,我会尽力澄清。

5 个答案:

答案 0 :(得分:7)

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

# 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])]

可能会奏效。 (谨防fencepost错误,但是......我太容易了!)

答案 1 :(得分:1)

  

...循环列表比较   使用新时间的当前时间并以此方式拆分列表

似乎就是这样做的方式。使用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

答案 2 :(得分:1)

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

忘记问题的所有细节,并考虑一下普通整数列表。假设您希望通过至少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()行为),除非应用于非常有利的情况。

对于您一眼就能理解的简单循环感到高兴: - )

答案 3 :(得分:0)

也许不是最优雅,但这样的事情应该有效:

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],)]

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

答案 4 :(得分:0)

这是我最近使用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']],

]