Python的Pandas,分组

6
我有一个数据集,其中每个时间戳包含多个元组 - 每个元组都有一个计数。每个时间戳可能存在不同的元组。我想将它们分组到5分钟的区间,并为每个唯一的元组添加计数。是否有一种简洁明了的方法使用Pandas中的group-by来实现?
它们的形式为: ((u'67.163.47.231', u'8.27.82.254', 50186, 80, 6, 1377565195000), 2)
这是一个列表,其中包含一个6元组(最后一个条目是时间戳)和计数。
对于每个时间戳,将有一个5元组的集合: (5元组), t-时间戳, 计数,例如(仅针对一个时间戳)。
[((u'71.57.43.240', u'8.27.82.254', 33108, 80, 6, 1377565195000), 1),
 ((u'67.163.47.231', u'8.27.82.254', 50186, 80, 6, 1377565195000), 2),
 ((u'8.27.82.254', u'98.206.29.242', 25159, 80, 6, 1377565195000), 1),
 ((u'71.179.102.253', u'8.27.82.254', 50958, 80, 6, 1377565195000), 1)]

In [220]: df = DataFrame ( { 'key1' : [ (u'71.57.43.240', u'8.27.82.254', 33108, 80, 6), (u'67.163.47.231', u'8.27.82.254', 50186, 80, 6) ], 'data1' : np.array((1,2)), 'data2': np.array((1377565195000,1377565195000))})

In [226]: df
Out[226]: 
   data1          data2                                        key1
0      1  1377565195000   (71.57.43.240, 8.27.82.254, 33108, 80, 6)
1      2  1377565195000  (67.163.47.231, 8.27.82.254, 50186, 80, 6)

或转换为:

In [231]: df = DataFrame ( { 'key1' : [ (u'71.57.43.240', u'8.27.82.254', 33108, 80, 6), (u'67.163.47.231', u'8.27.82.254', 50186, 80, 6) ], 'data1' : np.array((1,2)), 
   .....: 'data2': np.array(( datetime.utcfromtimestamp(1377565195),datetime.utcfromtimestamp(1377565195) )) })

In [232]: df
Out[232]: 
   data1               data2                                        key1
0      1 2013-08-27 00:59:55   (71.57.43.240, 8.27.82.254, 33108, 80, 6)
1      2 2013-08-27 00:59:55  (67.163.47.231, 8.27.82.254, 50186, 80, 6)


Here's a simpler example:

time         count       city
00:00:00       1         Montreal
00:00:00       2         New York
00:00:00       1         Chicago
00:01:00       2         Montreal
00:01:00       3         New York

after bin-ing

time         count       city
00:05:00       3         Montreal
00:05:00       5         New York
00:05:00       1         Chicago

这似乎是有效的方法:

以下是有效的内容:

times = [ parse('00:00:00'), parse('00:00:00'), parse('00:00:00'), parse('00:01:00'), parse('00:01:00'),
parse('00:02:00'), parse('00:02:00'), parse('00:03:00'), parse('00:04:00'), parse('00:05:00'),
parse('00:05:00'), parse('00:06:00'), parse('00:06:00') ]
cities = [ 'Montreal', 'New York', 'Chicago', 'Montreal', 'New York', 
'New York', 'Chicago', 'Montreal', 'Montreal', 'New York', 'Chicago', 'Montreal', 'Chicago']
counts = [ 1, 2, 1, 2, 3, 1, 1, 1, 2, 2, 2, 1, 1]
frame = DataFrame( { 'city': cities, 'time': times, 'count': counts } )

In [150]: frame
Out[150]: 
        city  count                time
0   Montreal      1 2013-09-07 00:00:00
1   New York      2 2013-09-07 00:00:00
2    Chicago      1 2013-09-07 00:00:00
3   Montreal      2 2013-09-07 00:01:00
4   New York      3 2013-09-07 00:01:00
5   New York      1 2013-09-07 00:02:00
6    Chicago      1 2013-09-07 00:02:00
7   Montreal      1 2013-09-07 00:03:00
8   Montreal      2 2013-09-07 00:04:00
9   New York      2 2013-09-07 00:05:00
10   Chicago      2 2013-09-07 00:05:00
11  Montreal      1 2013-09-07 00:06:00
12   Chicago      1 2013-09-07 00:06:00

frame['time_5min'] = frame['time'].map(lambda x: pd.DataFrame([0],index=pd.DatetimeIndex([x])).resample('5min').index[0])

In [152]: frame
Out[152]: 
        city  count                time           time_5min
0   Montreal      1 2013-09-07 00:00:00 2013-09-07 00:00:00
1   New York      2 2013-09-07 00:00:00 2013-09-07 00:00:00
2    Chicago      1 2013-09-07 00:00:00 2013-09-07 00:00:00
3   Montreal      2 2013-09-07 00:01:00 2013-09-07 00:00:00
4   New York      3 2013-09-07 00:01:00 2013-09-07 00:00:00
5   New York      1 2013-09-07 00:02:00 2013-09-07 00:00:00
6    Chicago      1 2013-09-07 00:02:00 2013-09-07 00:00:00
7   Montreal      1 2013-09-07 00:03:00 2013-09-07 00:00:00
8   Montreal      2 2013-09-07 00:04:00 2013-09-07 00:00:00
9   New York      2 2013-09-07 00:05:00 2013-09-07 00:05:00
10   Chicago      2 2013-09-07 00:05:00 2013-09-07 00:05:00
11  Montreal      1 2013-09-07 00:06:00 2013-09-07 00:05:00
12   Chicago      1 2013-09-07 00:06:00 2013-09-07 00:05:00

In [153]: df = frame.groupby(['time_5min', 'city']).aggregate('sum')

In [154]: df
Out[154]: 
                              count
time_5min           city           
2013-09-07 00:00:00 Chicago       2
                    Montreal      6
                    New York      6
2013-09-07 00:05:00 Chicago       3
                    Montreal      1
                    New York      2

In [155]: df.reset_index(1)
Out[155]: 
                         city  count
time_5min                           
2013-09-07 00:00:00   Chicago      2
2013-09-07 00:00:00  Montreal      6
2013-09-07 00:00:00  New York      6
2013-09-07 00:05:00   Chicago      3
2013-09-07 00:05:00  Montreal      1
2013-09-07 00:05:00  New York      2

2
你能否提供整个数据框的一个简短示例数据? - joris
6元组中的最后一个条目 - Stephen Thomas
1
你已经将它放入pandas DataFrame中了吗?它看起来是什么样子的(元组是否为一列)? - joris
我还没有将它们放入DataFrame中。考虑到这是一个列表(如果我不按时间戳排序,则为字典)。但意图是将元组作为列。然后使用时间戳作为时间序列-真正的第一个5元组是唯一键,然后是时间戳和计数。 - Stephen Thomas
你的时间戳只有每5分钟一次吗?还是需要将几分钟合并成一个5分钟的区间? - joris
显示剩余10条评论
2个回答

4

如果您将日期设置为索引,则可以使用TimeGrouper(它允许您按照例如5分钟间隔进行分组):

In [11]: from pandas.tseries.resample import TimeGrouper

In [12]: df.set_index('data2', inplace=True)

In [13]: g = df.groupby(TimeGrouper('5Min'))

您可以使用nunique函数来计算每个5分钟间隔内唯一项的数量:

In [14]: g['key1'].nunique()
Out[14]: 
2013-08-27 00:55:00    2
dtype: int64

如果您想要每个元组的计数,可以使用value_counts:
In [15]: g['key1'].apply(pd.value_counts)
Out[15]: 
2013-08-27 00:55:00  (71.57.43.240, 8.27.82.254, 33108, 80, 6)     1
                     (67.163.47.231, 8.27.82.254, 50186, 80, 6)    1
dtype: int64

注意:这是一个带有MultiIndex的Series(使用reset_index将其转换为DataFrame)。
In [16]: g['key1'].apply(pd.value_counts).reset_index(1)
Out[16]: 
                                                        level_1  0
2013-08-27 00:55:00   (71.57.43.240, 8.27.82.254, 33108, 80, 6)  1
2013-08-27 00:55:00  (67.163.47.231, 8.27.82.254, 50186, 80, 6)  1

您可能希望为这些列提供更具信息性的列名 :).

更新:之前我曾经修改过 get_dummies,请参见编辑历史。


我喜欢使用get_dummies,但是可能有一个快捷方式可以代替get_dummies(x).sum(),哈哈,它就是value_counts。 - Andy Hayden
TimeGrouper的技巧不错!但我认为他想要“添加计数”(这是一个现有的列),而不是“计算唯一键”的数量。 - joris
是的,应该在文档中有!(而且不能对列执行此操作似乎很遗憾...) - Andy Hayden
但是毫无疑问,这些计数需要被加起来。 - Stephen Thomas
啊,你也想按data1分组吗?(即我的解决方案,但也按data1分开) - Andy Hayden
显示剩余6条评论

1
如果您只想将每个唯一元组的计数相加,只需按key1进行分组:
df.groupby('key1').aggregate('sum')

如果您想对每个时间步和每个唯一元组执行此操作,则可以提供多列进行分组:
df.groupby(['data2', 'key1']).aggregate('sum')

如果需要将不同的时间步骤组合到一个5分钟的容器中,一种可能的方法是将时间戳舍入到5分钟,然后按此分组:
df['data2_5min'] = (np.ceil(df['data2'].values.astype('int64')/(5.0*60*1000000000))*(5.0*60*1000000000)).astype('int64').astype('M8[ns]')
df.groupby(['data2_5min', 'key1']).aggregate('sum')

如果你想保留一些原始时间戳(但是如果你把它们分组,你必须选择哪个),你可以指定一个在各个列上应用的函数。例如,取第一个:
df2 = df.groupby(['data2_5min', 'key1']).aggregate({'data1':'sum', 'data2':'first'})
df2.reset_index(0, drop=True).set_index('data2', append=True)

如果您只想在5分钟内重新采样并忽略键名添加计数,您可以简单地执行以下操作:
df.set_index('data2', inplace=True)
df.resample('5min', 'sum')

这些键是唯一的(但可能不会在每个时间步骤中出现)。我想为每个唯一的键将data1(计数)相加 - 然后将它们分成5分钟的区间 - 所以采用第二种方法。 - Stephen Thomas
是的,它会将唯一的键(元组)分组在一起。这不正是你想要的吗? - joris
我想要将与唯一键相关联的计数(data1)相加 - 因此,如果在任何5分钟间隔中出现时间戳的(count,key),则将添加计数。不想将5分钟桶中所有键的计数相加。 - Stephen Thomas
2
@Andy 哈哈,又来一招四舍五入的技巧 :-) df['data2'].map(lambda x: pd.DataFrame([0],index=pd.DatetimeIndex([x])).resample('5min').index[0]) - joris
能否让resample()在创建5分钟的时间段后,保留唯一键(以及它们的计数)? - Stephen Thomas
显示剩余23条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接