估摸着程序还得跑个半小时,顺手写写今天碰到一件小事的感想。
一组DataFrame数据,columns有 [index, user_id, time_stamp],index无重复,user_id有重复,time_stamp是用户某一行为的时间戳且无序。
我要做一件事:计算同用户此刻时间戳与之前最近的时间戳之差,第一次行为时间戳直接记为-1。
比如:
index, user_id, time_stamp, time_diff
0, yanwu, 100, -1
1, yanwu , 130, 10
2, yanwu, 120, 20
因为time_stamp = 100的是最小的,意味着是第一次行为时间戳,标记为-1;
而下个最近的time_stamp = 120,而120-100=20,所以差值为20;
同理130的这一行差值为130-120=10。
好,现在打算怎么做?
我第一个做法是这样:
两个for循环,第一个for里面找user_id,第二个for里面给user_id计算差值:
for i in set([user_id]):
sort(i.[time_stamp]) //给i用户的时间戳排序
for j in range(len(i.[time_stamp])):
if j == 0:
time_diff = -1 //第一个的差值设置为-1
else:
time_diff = i.[time_stamp][i] - i.[time_stamp] [i-1] //其余的差值就当前的减去上一个
很好理解吧,但是这个程序跑了我10小时... ... 因为我原数据接近100w条... ...
这么写,无非就是把我最真实的想法表现了出来,差值就是同一用户里时间戳两两相减,但是造成的结果呢?耗时耗力,强行用双重for遍历了这100w的数据。况且DataFrame中一条条遍历,已经蠢得不行了,这时候就必须考虑重新设计算法。是啊,跑了10小时才想起来修改算法... ... 真是血的教训... ...
我的第二个做法跑了不到1分钟,是这样的:
我最耗时间的是什么操作?遍历。
遍历是为了什么?给每一条数据做减法。
那能不能一次就减完呢?当然行啊。
首先我们确定一个东西,每一条数据该去减哪一条数据都是确定了的,已经有了确定的规则:“同一用户,上一条”。
sort_values(by=['user_id', 'time_tamp']) //通过user_id和timestamp为关键词排序
[tmp] = ['time_tamp'][-1:].append([' time_tamp'][0:-1]) //创建一个新的数组型数据,内容按序设为time_stamp的倒数第一条加上前面所有条
[time_diff] = ['time_tamp'] - [tmp] //所有差值等于原时间戳列对应减去tmp
....... //最后通过groupby+rank找到每个user_id的第一条数据,将time_diff变为-1就是
看懂了吗?最核心的就第一个按两个关键字的排序,直接把确定的规则:“同一用户,上一条”给体现了出来。
按用户排序就直接把同一用户分在了一起,而给time_stamp排序则直接告诉我,减数与被减数,就是两两相邻的两条数据。
所以我直接减数这一列,也就是 ['time_tamp']不变,而被减数这一列数据就是减数['time_tamp']整体下移一条,然后两列数据对应相减,齐活,哪来什么遍历什么循环等等乱七八糟的,还10个小时呢.... ...
这只是个日常编程中的一个小插曲,但是给我感触挺深的,少用for循环 虽然代码是表达人的思维,但是在表达思维的同时也要刻意注意算法的设计,毕竟算法是编程中很重要的一环,需要时刻注意去打磨去锻炼,否则... ... 否则... ... 否则... ... 哎,反正算法很重要!
(文章中的代码实属乱写,只是表达个基本意思;另外文章内容欢迎批评指正。)