Skip to main content

用于分析和处理移动数据的工具箱。

项目描述

DOI 命中计数

scikit-mobility - Python 中的移动性分析

尝试scikit-mobility不将其安装在 MyBinder 笔记本中:

粘合剂

scikit-mobility是 Python 中用于人体移动性分析的库。该库允许:

  • 用适当的数据结构表示轨迹和移动流,TrajDataFrame并且FlowDataFrame.

  • 管理和操作各种格式的移动数据(通话详细记录、GPS 数据、来自社交媒体的数据、调查数据等);

  • 从个人和集体层面的数据中提取流动性指标和模式(例如,位移长度、特征距离、起点-目的地矩阵等)

  • 使用标准数学模型(随机游走模型、探索和优先回报模型等)生成合成的个体轨迹

  • 使用标准迁移模型(重力模型、辐射模型等)生成合成流动流

  • 评估与移动数据集相关的隐私风险

目录

  1. 文档
  2. 引用
  3. 与我们合作
  4. 安装
  1. 教程
  2. 例子

文档

scikit-mobility 的类和函数的文档位于:https ://scikit-mobility.github.io/scikit-mobility/

引用

如果您使用 scikit-mobility,请引用以下论文:

卢卡·帕帕拉多、菲利波·西米尼、詹尼·巴拉基和罗伯托·佩隆格里尼。 scikit-mobility:用于分析、生成和风险评估移动数据的 Python 库,2019 年,https://arxiv.org/abs/1907.07062

中文提供:

@misc{pappalardo2019scikitmobility,
    title={scikit-mobility: a Python library for the analysis, generation and risk assessment of mobility data},
    author={Luca Pappalardo and Filippo Simini and Gianni Barlacchi and Roberto Pellungrini},
    year={2019},
    eprint={1907.07062},
    archivePrefix={arXiv},
    primaryClass={physics.soc-ph}
}

与我们合作

scikit-mobility是一个活跃的项目,欢迎任何贡献。

如果您想在 中包含您的算法scikit-mobility,请随时 fork 项目,打开问题并联系我们。

安装

首先,克隆存储库——这会创建一个新目录./scikit_mobility

    git clone https://github.com/scikit-mobility/scikit-mobility scikit_mobility

与 conda - miniconda

  1. 创建环境skmob并安装 pip

     conda create -n skmob pip python=3.7
    
  2. 启用

     source activate skmob
    
  3. 安装 skmob

     cd scikit_mobility
     python setup.py install
    

    如果所需库的安装失败,请使用conda install.

  4. 可选scikit-mobility在 jupyter notebook 上使用

    • 安装内核

      conda install ipykernel
      
    • 打开笔记本并检查内核skmob是否在内核列表中。如果没有,请运行以下命令:

      env=$(basename `echo $CONDA_PREFIX`)
      python -m ipykernel install --user --name "$env" --display-name "Python [conda env:"$env"]"
      

:exclamation: 如果你尝试在 Python 中导入包,你可能会遇到依赖问题。如果是这样,请尝试按如下方式安装以下软件包。

conda install -n skmob pyproj urllib3 chardet markupsafe

没有 conda(需要 python >= 3.6)

  1. 创建环境skmob

     python3 -m venv skmob
    
  2. 启用

     source skmob/bin/activate
    
  3. 安装 skmob

     cd scikit_mobility
     python setup.py install
    
  4. 可选scikit-mobility在 jupyter notebook 上使用

    • 激活virutalenv:

        source skmob/bin/activate
      
    • 安装 jupyter 笔记本:

        pip install jupyter 
      
    • 运行 jupyter 笔记本

        jupyter notebook
      
    • (可选)使用特定名称安装内核

        ipython kernel install --user --name=skmob
      

测试安装

> source activate skmob
(skmob)> python
>>> import skmob
>>>

教程

你可以在这里找到一些关于 scikit-mobility 的教程:https ://github.com/scikit-mobility/tutorials 。

例子

创建一个TrajDataFrame

在 scikit-mobility 中,一组轨迹由 a 描述TrajDataFrame,它是 pandas 的扩展DataFrame,具有特定的列名和数据类型。ATrajDataFrame可以包含许多轨迹,其中的每一行TrajDataFrame代表轨迹的一个点,由三个必填字段(又名列)描述:

  • latitude(类型:浮动);
  • longitude(类型:浮动);
  • datetime(类型:日期时间)。

此外,还可以指定两个可选列:

  • uid(类型:字符串)标识与轨迹点关联的对象。如果uid不存在,scikit-mobility 假定TrajDataFrame包含与单个移动对象关联的轨迹;
  • tid指定该点所属的轨迹的标识符。如果tid不存在,scikit-mobility 假设TrajDataFrame与 a 关联的所有行都uid属于同一轨迹;

请注意,除了强制列之外,用户可以根据需要添加任意TrajDataFrame数量的列,因为 scikit-mobility 中的数据结构继承了所有 pandasDataFrame功能。

TrajDataFrame从列表中创建一个:

>>> import skmob
>>> # create a TrajDataFrame from a list
>>> data_list = [[1, 39.984094, 116.319236, '2008-10-23 13:53:05'], [1, 39.984198, 116.319322, '2008-10-23 13:53:06'], [1, 39.984224, 116.319402, '2008-10-23 13:53:11'], [1, 39.984211, 116.319389, '2008-10-23 13:53:16']]
>>> tdf = skmob.TrajDataFrame(data_list, latitude=1, longitude=2, datetime=3)
>>> # print a portion of the TrajDataFrame
>>> print(tdf.head())
   0        lat         lng            datetime
0  1  39.984094  116.319236 2008-10-23 13:53:05
1  1  39.984198  116.319322 2008-10-23 13:53:06
2  1  39.984224  116.319402 2008-10-23 13:53:11
3  1  39.984211  116.319389 2008-10-23 13:53:16
>>> print(type(tdf))
<class 'skmob.core.trajectorydataframe.TrajDataFrame'>

TrajDataFramepandas 创建一个DataFrame

>>> import pandas as pd
>>> # create a DataFrame from the previous list
>>> data_df = pd.DataFrame(data_list, columns=['user', 'latitude', 'lng', 'hour'])
>>> # print the type of the object
>>> print(type(data_df))
<class 'pandas.core.frame.DataFrame'>
>>> # now create a TrajDataFrame from the pandas DataFrame
>>> tdf = skmob.TrajDataFrame(data_df, latitude='latitude', datetime='hour', user_id='user')
>>> # print the type of the object
>>> print(type(tdf))
<class 'skmob.core.trajectorydataframe.TrajDataFrame'>	
>>> # print a portion of the TrajDataFrame
>>> print(tdf.head())
   uid        lat         lng            datetime
0    1  39.984094  116.319236 2008-10-23 13:53:05
1    1  39.984198  116.319322 2008-10-23 13:53:06
2    1  39.984224  116.319402 2008-10-23 13:53:11
3    1  39.984211  116.319389 2008-10-23 13:53:16

我们也可以TrajDataFrame从文件中创建一个。例如,在下文中,我们从 2007 年 4 月至 2011 年 10 月的四年多期间内由 178 名用户在GeoLifeTrajDataFrame项目背景下收集的 GPS 轨迹数据集的一部分创建一个。

>>> # download the file from https://raw.githubusercontent.com/scikit-mobility/scikit-mobility/master/tutorial/data/geolife_sample.txt.gz
>>> # read the trajectory data (GeoLife, Beijing, China)
>>> tdf = skmob.TrajDataFrame.from_file('geolife_sample.txt.gz', latitude='lat', longitude='lon', user_id='user', datetime='datetime')
>>> # print a portion of the TrajDataFrame
>>> print(tdf.head())
	 lat         lng            datetime  uid
0  39.984094  116.319236 2008-10-23 05:53:05    1
1  39.984198  116.319322 2008-10-23 05:53:06    1
2  39.984224  116.319402 2008-10-23 05:53:11    1
3  39.984211  116.319389 2008-10-23 05:53:16    1
4  39.984217  116.319422 2008-10-23 05:53:21    1

TrajDataFrame可以使用该功能在folium交互式地图上绘制A。plot_trajectory

>>> tdf.plot_trajectory(zoom=12, weight=3, opacity=0.9, tiles='Stamen Toner')

情节轨迹

创建一个FlowDataFrame

在 scikit-mobility 中,起点-终点矩阵由FlowDataFrame结构描述,它是 pandas 的扩展DataFrame,具有特定的列名和数据类型。a 中的一行FlowDataFrame表示两个位置之间的对象流,由三个强制性列描述:

  • origin(类型:字符串);
  • destination(类型:字符串);
  • flow(类型:整数)。

同样,用户可以根据需要添加任意FlowDataFrame数量的列,因为FlowDataFrame数据结构继承了所有 pandasDataFrame功能。每个FlowDataFrame都与一个空间 tessellation相关联,一个包含两个强制性列的geopandas : GeoDataFrame

  • tile_ID(type: integer) 表示一个位置的标识符;
  • geometry表示描述领土上位置的几何形状的多边形(或点)(例如,正方形、voronoi 形状、邻域的形状)。

请注意,a 的origindestination列中的每个位置标识符都FlowDataFrame必须存在于关联的空间细分中。

从描述纽约州县的文件创建空间镶嵌:

>>> import skmob
>>> import geopandas as gpd
>>> # load a spatial tessellation
>>> url_tess = 'https://raw.githubusercontent.com/scikit-mobility/scikit-mobility/master/tutorial/data/NY_counties_2011.geojson'
>>> tessellation = gpd.read_file(url_tess).rename(columns={'tile_id': 'tile_ID'})
>>> # print a portion of the spatial tessellation
>>> print(tessellation.head())
  tile_ID  population                                           geometry
0   36019       81716  POLYGON ((-74.006668 44.886017, -74.027389 44....
1   36101       99145  POLYGON ((-77.099754 42.274215, -77.0996569999...
2   36107       50872  POLYGON ((-76.25014899999999 42.296676, -76.24...
3   36059     1346176  POLYGON ((-73.707662 40.727831, -73.700272 40....
4   36011       79693  POLYGON ((-76.279067 42.785866, -76.2753479999...

FlowDataFrame从空间细分和纽约州县之间的实际流量文件创建一个:

>>> # load real flows into a FlowDataFrame
>>> # download the file with the real fluxes from: https://raw.githubusercontent.com/scikit-mobility/scikit-mobility/master/tutorial/data/NY_commuting_flows_2011.csv
>>> fdf = skmob.FlowDataFrame.from_file("NY_commuting_flows_2011.csv",
				tessellation=tessellation,
				tile_id='tile_ID',
				sep=",")
>>> # print a portion of the flows
>>> print(fdf.head())
     flow origin destination
0  121606  36001       36001
1       5  36001       36005
2      29  36001       36007
3      11  36001       36017
4      30  36001       36019

FlowDataFrame可以使用函数在folium交互式地图上可视化 A,该函数plot_flows将地理地图上的流量绘制为FlowDataFrame的空间镶嵌中瓦片的质心之间的线:

>>> fdf.plot_flows(flow_color='red')

绘制通量

类似地,FlowDataFrame可以使用该plot_tessellation函数可视化 a 的空间镶嵌。参数popup_features(type:list, default:[ constants.TILE_ID]) 允许增强绘图的交互性,显示当用户单击图块时出现的弹出窗口,并包括GeoDataFrame参数列表中指定的镶嵌列中包含的信息:

>>> fdf.plot_tessellation(popup_features=['tile_ID', 'population'])

绘图细分

可以使用参数将空间镶嵌和流动一起可视化,该map_f参数指定要在其上绘制的 folium 对象:

>>> m = fdf.plot_tessellation() # plot the tessellation
>>> fdf.plot_flows(flow_color='red', map_f=m) # plot the flows

绘制曲面细分和流

轨迹预处理

与任何分析过程一样,移动数据分析需要数据清理和预处理步骤。该preprocessing模块允许用户执行四个主要的预处理步骤:

  • 噪声过滤;
  • 停止检测;
  • 停止聚类;
  • 轨迹压缩;

请注意,如果 aTrajDataFrame包含来自多个用户的多个轨迹,则预处理方法会自动应用于单个轨迹,并在必要时应用于单个移动对象。

噪声过滤

在 scikit-mobility 中,filter如果前一点的速度高于参数max_speed,则该函数会过滤掉一个点,该参数默认设置为 500km/h。

>>> from skmob.preprocessing import filtering
>>> # filter out all points with a speed (in km/h) from the previous point higher than 500 km/h
>>> ftdf = filtering.filter(tdf, max_speed_kmh=500.)
>>> print(ftdf.parameters)
{'from_file': 'geolife_sample.txt.gz', 'filter': {'function': 'filter', 'max_speed_kmh': 500.0, 'include_loops': False, 'speed_kmh': 5.0, 'max_loop': 6, 'ratio_max': 0.25}}
>>> n_deleted_points = len(tdf) - len(ftdf) # number of deleted points
>>> print(n_deleted_points)
54

请注意,该TrajDataFrame结构作为parameters属性,表示已应用于TrajDataFrame. 该属性是一个字典,其键是所应用函数的签名。

停止检测

轨迹中的某些点可以代表兴趣点 (POI),例如学校、餐馆和酒吧,也可以代表用户特定的地点,例如家庭和工作地点。这些点通常称为停留点或停止点,可以通过不同的方式检测它们。一种常见的方法是通过查看它们的空间接近度来应用空间聚类算法来聚类轨迹点。在 scikit-mobility 中stops,包含在detection模块中的函数查找移动对象访问的停留点。例如,要识别对象在minutes_for_a_stop距离内至少停留几分钟的停靠spatial_radius_km \time stop_radius_factor点,从给定点,我们可以使用以下代码:

>>> from skmob.preprocessing import detection
>>> # compute the stops for each individual in the TrajDataFrame
>>> stdf = detection.stops(tdf, stop_radius_factor=0.5, minutes_for_a_stop=20.0, spatial_radius_km=0.2, leaving_time=True)
>>> # print a portion of the detected stops
>>> print(stdf.head())
	 lat         lng            datetime  uid    leaving_datetime
0  39.978030  116.327481 2008-10-23 06:01:37    1 2008-10-23 10:32:53
1  40.013820  116.306532 2008-10-23 11:10:19    1 2008-10-23 23:45:27
2  39.978419  116.326870 2008-10-24 00:21:52    1 2008-10-24 01:47:30
3  39.981166  116.308475 2008-10-24 02:02:31    1 2008-10-24 02:30:29
4  39.981431  116.309902 2008-10-24 02:30:29    1 2008-10-24 03:16:35
>>> print('Points of the original trajectory:\t%s'%len(tdf))
>>> print('Points of stops:\t\t\t%s'%len(stdf))
Points of the original trajectory:	217653
Points of stops:			391

leaving_datetime将向 中添加一个新列TrajDataFrame,以指示用户离开停止位置的时间。plot_stops然后,我们可以使用以下函数可视化检测到的停止:

>>> m = stdf.plot_trajectory(max_users=1, start_end_markers=False)
>>> stdf.plot_stops(max_users=1, map_f=m)

情节停止

轨迹压缩

轨迹压缩的目标是在保留轨迹结构的同时减少轨迹点的数量。该步骤导致轨迹点的数量显着减少。在 scikit-mobility 中,我们可以使用compression模块下的preprocessing模块中的方法之一。例如,要合并所有距离小于 0.2km 的点,我们可以使用以下代码:

>>> from skmob.preprocessing import compression
>>> # compress the trajectory using a spatial radius of 0.2 km
>>> ctdf = compression.compress(tdf, spatial_radius_km=0.2)
>>> # print the difference in points between original and filtered TrajDataFrame
>>> print('Points of the original trajectory:\t%s'%len(tdf))
>>> print('Points of the compressed trajectory:\t%s'%len(ctdf))
Points of the original trajectory:	217653
Points of the compressed trajectory:	6281

流动性措施

文献中提出了几种措施来捕捉个人和集体层面的人类流动模式。个体测量总结了单个移动物体的流动模式,而集体测量总结了整个人口的流动模式。scikit-mobility 提供了一组广泛的移动性措施,每个措施都作为一个函数实现,该函数接受输入 aTrajDataFrame并输出 pandas DataFrame。单独和集体措施分别skmob.measure.individual在模块和skmob.measures.collective模块中实施。

例如,以下代码计算a的回转半径跳跃长度和起始位置TrajDataFrame

>>> from skmob.measures.individual import jump_lengths, radius_of_gyration, home_location
>>> # load a TrajDataFrame from an URL
>>> url = "https://snap.stanford.edu/data/loc-brightkite_totalCheckins.txt.gz"
>>> df = pd.read_csv(url, sep='\t', header=0, nrows=100000,
     names=['user', 'check-in_time', 'latitude', 'longitude', 'location id'])
>>> tdf = skmob.TrajDataFrame(df, latitude='latitude', longitude='longitude', datetime='check-in_time', user_id='user')
>>> # compute the radius of gyration for each individual
>>> rg_df = radius_of_gyration(tdf)
>>> print(rg_df)
   uid  radius_of_gyration
0    0         1564.436792
1    1         2467.773523
2    2         1439.649774
3    3         1752.604191
4    4         5380.503250
>>> # compute the jump lengths for each individual
>>> jl_df = jump_lengths(tdf.sort_values(by='datetime'))
>>> print(jl_df.head())
   uid                                       jump_lengths
0    0  [19.640467328877936, 0.0, 0.0, 1.7434311010381...
1    1  [6.505330424378251, 46.75436600375988, 53.9284...
2    2  [0.0, 0.0, 0.0, 0.0, 3.6410097195943507, 0.0, ...
3    3  [3861.2706300798827, 4.061631313492122, 5.9163...
4    4  [15511.92758595804, 0.0, 15511.92758595804, 1....

请注意,对于某些度量,例如jump_lengthTrajDataFrame必须按列按升序排列datetime(请参阅需要此条件的度量的文档https://scikit-mobility.github.io/scikit-mobility/reference/measures。 html ).

>>> # compute the home location for each individual
>>> hl_df = home_location(tdf)
>>> print(hl_df.head())
   uid        lat         lng
0    0  39.891077 -105.068532
1    1  37.630490 -122.411084
2    2  39.739154 -104.984703
3    3  37.748170 -122.459192
4    4  60.180171   24.949728
>>> # now let's visualize a cloropleth map of the home locations 
>>> import folium
>>> from folium.plugins import HeatMap
>>> m = folium.Map(tiles = 'openstreetmap', zoom_start=12, control_scale=True)
>>> HeatMap(hl_df[['lat', 'lng']].values).add_to(m)
>>> m

Cloropleth 地图家庭位置

集体生成模型

集体生成模型估计一组离散位置之间的空间流。使用集体生成模型估计的空间流示例包括社区之间的通勤旅行、城市之间的迁移流、州之间的货运以及区域之间的电话。

在 scikit-mobility 中,集体生成模型接受空间镶嵌,即 geopandas 的输入GeoDataFrame。要成为集体模型的有效输入,空间镶嵌应该包含两列,geometryrelevance,这是计算集体算法使用的两个变量所必需的:瓦片之间的距离和每个瓦片的重要性(也称为“吸引力”)。集体算法产生一个FlowDataFrame包含生成的流和空间镶嵌的。scikit-mobility 实现了最常见的集体生成算法:

  • Gravity模型;
  • Radiation模型。

重力模型

实现 Gravity 模型的类Gravity有两个主要方法:

  • fit,它使用 校准模型的参数FlowDataFrame
  • generate,它在给定的空间细分上生成流。

在 a 中加载空间镶嵌和真实流的数据集FlowDataFrame

>>> from skmob.utils import utils, constants
>>> import geopandas as gpd
>>> from skmob.models import Gravity
>>> import numpy as np
>>> # load a spatial tessellation
>>> url_tess = 'https://raw.githubusercontent.com/scikit-mobility/scikit-mobility/master/tutorial/data/NY_counties_2011.geojson'
>>> tessellation = gpd.read_file(url_tess).rename(columns={'tile_id': 'tile_ID'})
>>> # download the file with the real fluxes from: https://raw.githubusercontent.com/scikit-mobility/scikit-mobility/master/tutorial/data/NY_commuting_flows_2011.csv
>>> fdf = skmob.FlowDataFrame.from_file("NY_commuting_flows_2011.csv",
					tessellation=tessellation,
					tile_id='tile_ID',
					sep=",")
>>> # compute the total outflows from each location of the tessellation (excluding self loops)
>>> tot_outflows = fdf[fdf['origin'] != fdf['destination']].groupby(by='origin', axis=0)['flow'].sum().fillna(0).values
>>> tessellation[constants.TOT_OUTFLOW] = tot_outflows

实例化重力模型对象并生成合成流:

>>> # instantiate a singly constrained Gravity model
>>> gravity_singly = Gravity(gravity_type='singly constrained')
>>> print(gravity_singly)
Gravity(name="Gravity model", deterrence_func_type="power_law", deterrence_func_args=[-2.0], origin_exp=1.0, destination_exp=1.0, gravity_type="singly constrained")
>>> # start the generation of the synthetic flows
>>> np.random.seed(0)
>>> synth_fdf = gravity_singly.generate(tessellation,
				   tile_id_column='tile_ID',
				   tot_outflows_column='tot_outflow',
				   relevance_column= 'population',
				   out_format='flows')
>>> # print a portion of the synthetic flows
>>> print(synth_fdf.head())
  origin destination  flow
0  36019       36101   101
1  36019       36107    66
2  36019       36059  1041
3  36019       36011   151
4  36019       36123    33

拟合重力模型的参数FlowDataFrame并生成合成流:

>>> # instantiate a Gravity object (with default parameters)
>>> gravity_singly_fitted = Gravity(gravity_type='singly constrained')
>>> print(gravity_singly_fitted)
Gravity(name="Gravity model", deterrence_func_type="power_law", deterrence_func_args=[-2.0], origin_exp=1.0, destination_exp=1.0, gravity_type="singly constrained")
>>> # fit the parameters of the Gravity from the FlowDataFrame
>>> gravity_singly_fitted.fit(fdf, relevance_column='population')
>>> print(gravity_singly_fitted)
Gravity(name="Gravity model", deterrence_func_type="power_law", deterrence_func_args=[-1.9947152031914186], origin_exp=1.0, destination_exp=0.6471759552223144, gravity_type="singly constrained")
>>> # generate the synthetics flows
>>> np.random.seed(0)
>>> synth_fdf_fitted = gravity_singly_fitted.generate(tessellation,
							tile_id_column='tile_ID',
							tot_outflows_column='tot_outflow',
							relevance_column= 'population',
							out_format='flows')
>>> # print a portion of the synthetic flows
>>> print(synth_fdf_fitted.head())
  origin destination  flow
0  36019       36101   102
1  36019       36107    66
2  36019       36059  1044
3  36019       36011   152
4  36019       36123    33

绘制真实流和合成流:

>>> m = fdf.plot_flows(min_flow=100, flow_exp=0.01, flow_color='blue')
>>> synth_fdf_fitted.plot_flows(min_flow=1000, flow_exp=0.01, map_f=m)

重力模型:真实流动与合成流动

辐射模型

辐射模型是无参数的,只有一种方法:generate. 给定空间镶嵌,可以使用Radiation如下类生成合成流:

>>> from skmob.models import Radiation
>>> # instantiate a Radiation object
>>> radiation = Radiation()
>>> # start the simulation
>>> np.random.seed(0)
>>> rad_flows = radiation.generate(tessellation, 
				tile_id_column='tile_ID',  
				tot_outflows_column='tot_outflow', 
				relevance_column='population', 
				out_format='flows_sample')
>>> # print a portion of the synthetic flows
>>> print(rad_flows.head())
  origin destination   flow
0  36019       36033  11648
1  36019       36031   4232
2  36019       36089   5598
3  36019       36113   1596
4  36019       36041    117

个体生成模型

人类流动性的个体生成模型的目标是创建一个代理群体,其流动模式在统计上与真实个体的流动模式无法区分。假设一个对象独立于其他对象,单个生成模型通常会生成对应于单个移动对象的合成轨迹。

scikit-mobility 实现了最常见的个人生成模型,例如探索和优先回报模型及其变体,以及DITRAS。每个生成模型都是一个带有公共方法的 python 类generate,它开始生成合成轨迹。

以下代码使用该DensityEPR模型生成合成轨迹:

>>> from skmob.models.epr import DensityEPR
>>> # load a spatial tesellation on which to perform the simulation
>>> url = 'https://raw.githubusercontent.com/scikit-mobility/scikit-mobility/master/tutorial/data/NY_counties_2011.geojson'
>>> tessellation = gpd.read_file(url)
>>> # starting and end times of the simulation
>>> start_time = pd.to_datetime('2019/01/01 08:00:00')
>>> end_time = pd.to_datetime('2019/01/14 08:00:00')
>>> # instantiate a DensityEPR object
>>> depr = DensityEPR()
>>> # start the simulation
>>> tdf = depr.generate(start_time, end_time, tessellation, relevance_column='population', n_agents=100, verbose=True)
>>> print(tdf.head())
   uid                   datetime        lat        lng
0    1 2019-01-01 08:00:00.000000  42.452018 -76.473618
1    1 2019-01-01 08:32:30.108708