计算15分钟周期数的代码
我遇到了一个问题。我有一个数据集,里面记录了旅行的开始时间(STRTTIME)和结束时间(ENDTIME),时间格式是24小时制的。我想计算每15分钟内的旅行次数。我的目标是确定从0000到2359(总共96个时间段)每个15分钟时间段内发生的旅行次数。我可以在Excel里写96个虚拟变量来实现,但我更希望能用R或Python写一些代码(我正在学习这两种语言,所以我的知识还很基础)。我可以设置一个计数器,然后增加计数,但我不太确定如何处理两个时间变量,感觉有些无从下手。以下是我的例子。这里有一些示例数据(CSV格式)。
- 假设一次旅行在0805开始,0840结束,那么每15分钟的时间段将有以下值:
- 0000-0015 - 0
- 0015-0030 - 0
- ....
- 0800-0815 - 2/3
- 0815-0830 - 1
- 0830-0845 - 2/3
- 0845-0900 - 0
- ...
- 2330-2345 - 0
- 2345-2400 - 0
- 假设另一趟旅行在0810开始,0850结束,那么每15分钟的时间段将有以下值:
- 0000-0015 - 0
- 0015-0030 - 0
- ....
- 0800-0815 - 1/3
- 0815-0830 - 1
- 0830-0845 - 1
- 0845-0900 - 1/3
- ...
- 2330-2345 - 0
- 2345-2400 - 0
- 处理完这两条记录后,15分钟时间段的虚拟字段值将如下(也就是说,它根据前一条记录的字段值进行了累加):
- 0000-0015 - 0
- 0015-0030 - 0
- ....
- 0800-0815 - 1
- 0815-0830 - 2
- 0830-0845 - 5/3
- 0845-0900 - 1/3
- ...
- 2330-2345 - 0
- 2345-2400 - 0
任何能实现这个功能的代码都非常感谢。
4 个回答
因为你想要制作一个直方图,所以实际上你是在解决一个常见的问题,就是“将数据分组”,不过方式稍微有点不同!
最简单的办法是先创建一个从0到95的字典(你提到的96个区间)。每个区间代表一个15分钟的时间段。
然后逐条处理每个记录,找出它们开始和结束的索引。对于这两个索引之间的每个值,在你的字典中加1,表示在那个时间段内有一次行程发生。
import csv
spamReader = csv.reader(open('sample_data.csv', 'rb'), delimiter=',')
histogram = dict()
def toMinutes(militaryTime):
if type(militaryTime) != str:
raise ValueError("requires string as arg")
hours = int(militaryTime[:2])
mins = int(militaryTime[2:])
return 60*hours + mins
for record in spamReader:
if record[0] == 'STRTTIME':
continue #skip first record which contains headers
startTime = toMinutes(record[0]) #must convert militarytime to minutes
endTime = toMinutes(record[1])
startIndex = int(int(startTime)/15.0) #int division in python 3.0 and 2.X
endIndex = int(int(endTime)/15) #is handled different, this unifies the two
for i in range(startIndex,endIndex+1):
valAd = 1
if i == startIndex:
valAd = 1-((startTime-(15*i))/15.0)
if i == endIndex:
valAd = ((endTime-(15*i))/15.0) #opposite boundary condition
histogram[i] = histogram.get(i,0) + valAd
for key,val in histogram.items():
print key,val
'''
output from your example csv, in minutes, which can easily be converted to militaryTime
41 0.666666666667
42 1
43 0.333333333333
46 0.333333333333
47 1
48 1.8
49 0.666666666667
50 1.26666666667
51 1
52 1
53 1
54 1
55 1
56 1
57 1
58 0.666666666667
59 1.33333333333
60 1.0
61 1
62 1
63 1
64 1
65 1
66 0.333333333333
67 0.266666666667
68 1
69 1.8
70 0.0
72 1.0
73 1
74 2.0
75 1.33333333333
76 1
77 1
78 1
79 1
80 0.0
94 1.0
95 0.333333333333
360 1.0
361 1
362 1
363 1
364 1
365 1
366 1
367 1
368 1
369 1
370 1
371 1
372 0.0
'''
让我试着按照你提供的方式来展示解决方案
首先,我们定义15分钟的时间范围。可以使用Itertools.product来创建整个时间范围,并用datetime strftime格式化,之后再用time进行转换。
timeset=[datetime.time(h,m).strftime("%H%M") for h,m in itertools.product(xrange(0,24),xrange(0,60,15))]+['2400'] >>> timeset ['0000', '0015', '0030', '0045', '0100', '0115', '0130', '0145', '0200', '0215', '0230', '0245', '0300', '0315', '0330', '0345', '0400', '0415', '0430', '0445', '0500', '0515', '0530', '0545', '0600', '0615', '0630', '0645', '0700', '0715', '0730', '0745', '0800', '0815', '0830', '0845', '0900', '0915', '0930', '0945', '1000', '1015', '1030', '1045', '1100', '1115', '1130', '1145', '1200', '1215', '1230', '1245', '1300', '1315', '1330', '1345', '1400', '1415', '1430', '1445', '1500', '1515', '1530', '1545', '1600', '1615', '1630', '1645', '1700', '1715', '1730', '1745', '1800', '1815', '1830', '1845', '1900', '1915', '1930', '1945', '2000', '2015', '2030', '2045', '2100', '2115', '2130', '2145', '2200', '2215', '2230', '2245', '2300', '2315', '2330', '2345', '2400']
接下来,我们定义一个时间记录器,它的长度和时间集合相同,但初始值为零。
timekeeper=[0]*len(timeset)
为了简单起见,我不从CSV文件读取数据,而是定义一个元组,里面的数据和你提供的XLS表格是一样的。
counter=[('1020','1050'),('0900','0930'),('1830','2000'),('2330','2350'),('1200','1202'),('1232','1234'),('1450','1635'),('1220','1440'),('0930','1205'),('1656','1730'),('1800','1850'),('1200','1210'),('1715','1727'),('1140','1215'),('1450','1500')]
下面的函数是主要的处理程序。我使用了bisect来确定开始和结束的时间序列。同时,我还使用了fraction来避免使用浮点数,并保持问题中所描述的格式。
def TimeCounter(timekeeper,timeset,(sttime,entime)): st=bisect.bisect_left(timeset,sttime) en=bisect.bisect_left(timeset,entime) timekeeper[st]+=fractions.Fraction(int(timeset[st])-int(sttime),15) timekeeper[en]+=fractions.Fraction(int(entime)-int(timeset[en-1]),15) for i in xrange(st+1,en): timekeeper[i]+=1
最后,下面这两行代码会遍历提供的计数数据,并为每个数据序列调用TimeCounter,以更新时间记录器。
for c in counter: TimeCounter(timekeeper,timeset,c)
最终的输出看起来大概是这样的。
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Fraction(0, 1), 2, Fraction(2, 1), 2, 2, 2, Fraction(10, 3), 4, Fraction(8, 3), 2, 2, Fraction(8, 3), Fraction(4, 1), Fraction(64, 15), Fraction(4, 3), Fraction(64, 15), 2, 2, 2, 2, 2, 2, 2, Fraction(4, 3), Fraction(62, 3), 2, 2, 2, 2, 2, 2, Fraction(2, 3), Fraction(88, 15), Fraction(2, 1), Fraction(18, 5), 0, Fraction(0, 1), 2, Fraction(2, 1), 4, Fraction(8, 3), 2, 2, 2, Fraction(22, 3), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Fraction(0, 1), 2, Fraction(2, 3)]
如果你想要以问题中描述的格式打印数据,可以使用这段代码。
for i in xrange(0,len(timeset)-1): print '-'.join([timeset[i],timeset[i+1],str(timekeeper[i+1])])
这里是最终显示语句的一个示例输出。
1015-1030-10/3
1030-1045-4
1045-1100-8/3
1100-1115-2
1115-1130-2
1130-1145-8/3
1145-1200-4
1200-1215-64/15
1215-1230-4/3
1230-1245-64/15
由于在R语言中还没有答案,我来补充一个。我觉得这个解决方案可能比Python的更优雅,但这只是个人喜好问题。
首先,我们需要读取数据:
data <- read.csv('sample_data.csv')
接下来,我想把时间转换成小数格式。因此,我会使用提供的小时和分钟,而不是军事时间格式。不过,这并不是问题,因为你总是可以通过简单的整数运算来转换这些值。
data <- cbind(data, start = data$STARTHR + data$STARTMIN/60, end= data$ENDHR + data$ENDMIN/60)
现在生成时间区间(我们将通过它们的开始时间来识别这些区间)
intervals <- seq(0, 23.75, by=0.25)
这部分有点复杂……首先,我们会检查哪些行程结束时间晚于我们的区间结束时间。所有这些行程我们会标记为1,而在我们区间结束之前的行程则标记为0。如果行程在区间内结束,我们会根据它在区间中的位置给它分配一个介于0和1之间的值。
endvalues <- (pmax(pmin(outer(data$end, intervals, FUN="-"), 0.25), 0) / 0.25)
注意这里使用了outer函数。在这里,"-"(减法)这个函数会对所有结束时间和区间向量的组合进行运算。其他操作都是逐个元素进行的。我建议你一步一步地测试这个操作,这样就能清楚地知道在做什么了。
同样,我们会对开始区间做类似的操作,但这次我们会使用负号。
startvalues <- (pmax(pmin(-outer(data$start, intervals, FUN="-"), 0), -0.25) / 0.25)
这样我们就能生成一个矩阵,当区间完全包含在行程内时,矩阵中对应的位置会是1:
resultmatrix <- endvalues + startvalues
最后,我们可以对所有行程进行求和,得到每个区间内的行程数量:
intervalcount <- apply(resultmatrix, 2, sum)