目录
前言
本文主要内容是对GitHub上的移动数据分析库 – MovingPandas 源代码进行解读,解释各部分函数功能的使用以及算法。
一、Movingpandas是什么?
MovingPandas 是在 pandas 的基础上,尤其是在 DataFrame 上,针对移动数据进行优化的数据操作分析库。
作者引入了 GeoPandas 的内容。同时在GeoPandas的基础上,加入了 trajectory 这一数据结构。基于 trajectory 作为基本单元,实现一系列轨迹数据的处理和分析。
1. MovingPandas结构
- geometry_utils
- overlay
- trajectory
- trajectory_aggregator
- trajectory_collection
- trajectory_generalizer
- trajectory_plotter
上述结构中,trajectory 是基本单元,代表某一 agent 的轨迹数据。
trajectory中包括了以 DF 结构保存的数据,以及相关的属性和操作函数。
trajectory_aggregator 聚合:提取显著点、聚类、流向计算
trajectory_collection 是 trajectory 的集合。整合了所有trajectory的计算操作
trajectory_generalizer 提供了大量数据预处理操作。包括最短间距、最大间距、时间间距。D-P算法等。
trajectory_plotter 则基于 pandas 和 hvplot 提供了轨迹出图的功能。
2.文档
官方文档在这里
文档包括了一些基本的轨迹预处理和生成分析的案例以及api文档。
二、范例
2.使用
使用pd自带的数据读取即可,基于自身数据量的考量还可以使用类似chunk的方式逐步读取数据。
reader = pd.read_csv('F:/ship/apr.csv',
encoding= 'gb2312',iterator = True,chunksize = 100000)
为轨迹添加时间索引字段
df['t'] = pd.to_datetime(
df['Receivedtime(UTC+8)'],
format='%Y-%m-%d %H:%M:%S'
)
df = df.set_index('t')
利用shapely库将经纬度坐标转为点要素,设置地理要素字段,生成GeoDataFrame,设置坐标系,这边默认WGS84
geom = [Point(xy) for xy in zip(df.Lon_d,df.Lat_d)]
df = gpd.GeoDataFrame(df,geometry = geom)
df.crs = 'epsg:4326'
利用 trajectory_collection 生成轨迹集对象
traj_collection = mpd.TrajectoryCollection(df, 'MMSI',MIN_LENGTH)
print("Finished creating {} trajectories".format(len(traj_collection)))
随后针对轨迹集的操作和制图可以参考官方的示例,实例使用 binder 给出。不需要安装对应环境就可以跑实例代码了。
函数和算法解析
轨迹分段 split_by_observation_gap
def split_by_observation_gap(self, gap):
"""
Split the trajectory into subtrajectories whenever there is a gap in the observations.
Parameters
----------
gap : datetime.timedelta
Time gap threshold
Returns
-------
list
List of trajectories
"""
result = []
temp_df = self.df.copy()
temp_df['t'] = temp_df.index
temp_df['gap'] = temp_df['t'].diff() > gap
temp_df['gap'] = temp_df['gap'].apply(lambda x: 1 if x else 0).cumsum()
dfs = [group[1] for group in temp_df.groupby(temp_df['gap'])]
for i, df in enumerate(dfs):
df = df.drop(columns=['t', 'gap'])
if len(df) > 1:
result.append(Trajectory(df, '{}_{}'.format(self.id, i)))
return result
首先生成 gap 字段,通过使用 diff() 函数计算本行记录与上一行记录中的时间字段 ‘t’ 之间的差距。如果差距大于设定的阈值 gap 则记录下数据。比方说阈值为 10 ,生成数据如下:
true => 1 => 1
true => 1 => 2
true => 1 => 3
false => 0 => 3
false => 0 => 3
false => 0 => 3
true => 1 => 4
随后使用 lambda 句式设计算法,将所有 true、false 转换成数值类型 0,1 ,随后将当前行的数据变成,第一行到当前行数据的总和,得到新的行
对新行进行分组groupby,得到的结果就是利用阈值进行分段的结果。
数据稀松 MinDistanceGeneralizer
class MinDistanceGeneralizer(TrajectoryGeneralizer):
"""
Generalizes based on distance.
This generalization ensures that consecutive locations are at least a certain distance apart.
tolerance : float
Desired minimum distance between consecutive points
Examples
--------
>>> mpd.MinDistanceGeneralizer(traj).generalize(tolerance=1.0)
"""
def _generalize_traj(self, traj, tolerance):
temp_df = traj.df.copy()
prev_pt = temp_df.iloc[0][traj.get_geom_column_name()]
keep_rows = [0]
i = 0
for index, row in temp_df.iterrows():
pt = row[traj.get_geom_column_name()]
if traj.is_latlon:
dist = measure_distance_spherical(pt, prev_pt)
else:
dist = measure_distance_euclidean(pt, prev_pt)
if dist >= tolerance:
keep_rows.append(i)
prev_pt = pt
i += 1
keep_rows.append(len(traj.df)-1)
new_df = traj.df.iloc[keep_rows]
new_traj = Trajectory(new_df, traj.id)
return new_traj
写了很长的函数,但其实算法很简单,就是计算每个点与上一个点之间的距离,然后一点一点距离叠加,知道超过阈值,将超过阈值时的那个点作为新点,再进行叠加。最后把所有新点都提取出来就成了稀松过后的点了。
轨迹显著点提取
就不放源码了。
输入设置好的参数。设置最大距离,最小距离。最短时间间隔,最小角度等多个阈值。
将满足所有阈值的点最为显著点提取出来。
首先判断第二点与第一点的距离,如果距离大于最大距离阈值则加入显著点,
否则进入第二步,判断后续的轨迹点中,是否有小于最短距离的,
如果有进入第三步,判断该点与后一点之间距离是否超过最短时间阈值。如果是的话加入显著点,
如果不是的话进入第四步,计算与后一点之间的角度距离是否超过最小角度阈值。如果是的话加入显著点。不是的话就进入下一点
ps(其实我觉得这个叫离群点还合适一些)
轨迹点聚类
聚类算法,作者参考了论文
算法如下:
- 设置 cellsize 将工作空间进行矩形网格划分。
- 遍历所有点,从点 A 开始,
- 如果 A点 附近(九宫格)存在聚类中心点 Ci,计算是否小于最大距离,默认是1×10^8 或最短距离,默认是 cellsize × 200 。遍历 8 个九宫格里的所有聚类中心点 ,如果存在的话就计算该点与 A 的距离,判断出距离最近的点 Ci ,并加入这个聚类
- 如果不存在,则该点成为初始聚类中心点 。
- 将所有聚类中心点中的所有点清楚,只保留所有聚类中心,重新再对所有点聚类,寻找对应的聚类中心。ps:我当时没想明白为什么要把所有点都删了,还特地去问了作者
流flow
遍历所有的轨迹,逐个轨迹点找该点最近的格网 cell 。将每条轨迹的所有点找到的最近 cell 加入到序列 (Sequence),每两个 cell 形成一个 flow ,flow的权重就是两个 cell 之间的 Sequence 条数。
配置过程
要是在海外就容易的多了,使用默认的软件源,再加一个conda-forge 就完事了。但是国内的话,就可能遇见网速慢甚至无法访问的问题。期间还遇到了 pip 过程中的 SSL 连接问题。
软件源
由于软件的依赖包比较多,需要同时使用到 conda 和 pip 两个安装工具。
conda
我这边 conda 使用的软件源经过测试之后比较合适的是 清华 的。
show_channel_urls: true
channel_alias: http://mirrors.tuna.tsinghua.edu.cn/anaconda
default_channels:
- http://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main
- http://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free
- http://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/r
- http://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/pro
- http://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/msys2
custom_channels:
conda-forge: http://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
msys2: http://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
bioconda: http://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
menpo: http://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
pytorch: http://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
simpleitk: http://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
ssl_verify: true
channels:
- conda-forge
channel_priority: flexible
将这个软件源的内容放在 C:/用户/用户名/.condarc 中即可。
也可以使用 conda config --set 的方法
pip
pip 中,经过测试,使用豆瓣和清华的都没办法完整安装,缺少依赖包,最后采用了阿里云的源
- 在 C:\Users\用户名\AppData\Roaming\ 这个目录下新建 pip 文件夹(如果没有的话)
- 在 pip 文件夹下新建文件 pip.ini
并添加以下文字
[global]
timeout = 6000
index-url =http://mirrors.aliyun.com/pypi/simple/
trusted-host =mirrors.aliyun.com
- 其中的 trusted-host 是为了解决 pip 过程中出现的 ssl 信任问题,如果不需要可以不加。附带 SSL 的正确解决方式(我这是临时的)
创建环境
环境文件 environment.yaml
environment.yaml(金山文档)
https://kdocs.cn/l/svvKLcHJIDZP
这个是项目开发者提供的环境文件,会比较干净,但是我没测试过这个软件源能不能下全。
环境创建
得到环境文件之后
在 anaconda prompt 中,输入以下命令
conda env create -f environment.yaml
将会创建一个名为 TrajEnv 的 python 环境。
启用环境:conda activate TrajEnv