问题描述:
在处理一段GPS车辆定位数据时遇见了问题。出租车平均10s采集一条数据,记录了速度、位置和车辆状态等一系列信息(本文仅针对速度字段进行处理)。由于GPS数据的误差和车辆的走走停停,车辆每一次运行中数据记录的条数不固定,且具有时序的信息。本文的目的就是清除脏数据之后提取车辆的每一段连续运行的记录(连续时间记录:前后两条记录时间间隔不超过25秒),并采用三次样条曲线进行数据插值。
样本数据test.csv:
datatime,speed
2019/01/01 16:41:40,0.1
2019/01/01 16:41:50,4.6
2019/01/01 16:42:02,42.3
2019/01/01 16:42:12,61.2
2019/01/01 16:42:22,62.7
2019/01/01 16:42:32,72
2019/01/01 16:42:42,50.6
2019/01/01 16:42:52,34.39
2019/01/01 16:43:02,51.06
2019/01/01 16:43:12,31.34
2019/01/01 16:43:43,56.82
2019/01/01 16:43:52,49.23
2019/01/01 16:44:02,40.63
2019/01/01 16:44:12,50.6
2019/01/01 16:44:22,34.39
2019/01/01 16:44:32,51.06
2019/01/01 16:44:42,31.34
2019/01/01 16:44:52,64.51
2019/01/01 16:45:02,65535
2019/01/01 16:45:12,65535
2019/01/01 16:45:23,69.94
2019/01/01 16:45:32,54
2019/01/01 16:45:42,49.34
2019/01/01 16:45:52,37.7
2019/01/01 16:46:02,14.14
2019/01/01 16:46:12,21.53
2019/01/01 16:46:22,49.34
2019/01/01 16:46:32,37.7
可以看到,20与21行数据明显是错误数据(速度太大,需要去除);测试数据(test.csv)依据时间的连续性可以分为三段(每段数据内部前后两条记录时间差不超过25s,不同段的数据时间差超过25s)。我们需要通过程序得到这三段数据。
数据分析:
可以看到,文件中时间字段有着固定的格式,如果将日期作为str类型进行存储会极大地影响处理效率。对于时间序列的数据而言,pandas提供了一个datatime的对象数组(称为时间戳),可以高效处理格式化时间序列。另外,为了方便时间计算,我们需要使用统一的时间基准,将其转化为unix时间(unix时间戳是从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数,不考虑闰秒),并将其转化为协调世界时(UTC时间,比北京时间晚8h)。
代码:
def test(filename):
data = pd.read_csv(filename)[["datatime","speed"]]
# step1:时间转换
data['datatime'] = pd.to_datetime(data['datatime'], format = "%Y/%m/%d %H:%M:%S")
data['unix_utctime'] = data.apply(
lambda row:row['datatime'].timestamp()-28800, axis=1)
# step2:数据清洗
data = data.drop_duplicates(['unix_utctime'])
data = data[(data['speed']<220)]
# step3:构建划分区间,阈值为25s
data['diff'] = data['unix_utctime'].diff()
data['tag1'] = data['diff'].apply(
lambda x: 1 if x < 25 else 0)
g1 = data.groupby(data['tag1'])
sbin = g1.get_group(0).index.tolist()
sbin.append(int(data.index[-1])+1)
# step4:数据分割,获取分段数据
data['index'] = data.index
data['tag2'] = np.digitize(data['index'], bins = sbin)
g = data.groupby(['tag2'])
# step5:分段数据处理:插值
count = 0
for name,dataset in g:
x = np.array(dataset['unix_utctime'].values)
if x.shape[0] == 1:
continue
yspeed = np.array(dataset['speed'].values)
xnew = np.linspace(int(x[0]), int(x[-1]), int(x[-1]-x[0]+1))
fun_speed= interpolate.interp1d(x, yspeed, kind="cubic")
ynew =fun_speed(xnew)
count = count + 1
pl.plot(xnew, ynew, label = str(count))
pl.legend(loc="lower right")
pl.show()
- step1:时间转换
采用pd.to_datetime()
函数将格式化了的时间转为datetime数组,然后提取datetime时间的时间戳转为unix时间再转为utc时间。python中时间处理请参考这个链接,超详细(跳转)。 - step2:数据清洗
主要是采用了data.drop_duplicates()
进行数据去重和筛选出小于220km/h的出租车数据。 - step3:构建划分区间
直接这一步运行后的结果:
1、diff字段记录了该行的unix_utctime时间减去上一行的unix_utctime
2、tag1字段是根据diff值来进行记录的赋值的,当diff<25赋值为1,否则为零
3、sbin=[0,10,20,28]表示tag1值为0时的索引。
看完上述结果应该比较清晰了:本文尝试判断前后两个数据的时间差,时间差在阈值(25s)外的记录了其行索引(为后面切割数据做准备) - step4:数据分割
- 首先添加一个index字段记录行号
- 使用
np.digitize()
函数对index字段按照sbin数组进行分割赋值,记录为tag2
可以看到依据sbin=[0,10,20,28]将index字段划分为三个区间,分别赋值为1,2,3。tag2这个字段就标记出了哪些数据是连续的。 - step5:分段数据处理
分段数据处理部分中:依据tag2字段,对其使用groupby函数进行分组,就可以得到时序数据连续的部分,然后进行插值、绘图得到上述结果,在这里就不详细说了。
后记:
本文主要想聊聊时序数据根据数据时间的连续性将数据进行分段的方法。新手记录一些自己写的玩意儿,不至于忘得太快。如果有其他方法,欢迎交流。