Hands-on!

Neste tutorial nós iremos mostrar como fazer o uso do PyMove para pré-processamento e visualização dos dados de trajetória!

MoveDataFrame

Para trabalhar com o PyMove é preciso que você importe os seus dados para a nossa estrutura de dados interna: MoveDataFrame.

O MoveDataFrame é uma abstração que instancia uma estrutura de dados que pode manipula diferentes bibliotecas para trabalhar com dados de trajetória.

Você precisa mandar uma flag no momento da criação do MoveDataFrame para indicar qual biblioteca você deseja uitlizar internamente para manipular o seu conjunto de dados. Atualmente a única opção disponível é o Pandas.

O MoveDataFrame deve conter as seguintes colunas:

  1. lat: representa a latitute de um ponto;
  2. lon: representa a longitude de um ponto;
  3. datatime: representa a data e o tempo da coleta dos dados do ponto

O usuário tem a liberdade para adicionar quaisquer outras colunas que desejar. Se uma coluna contendo o id das trajetórias não for indicado os pontos são considerados pertencentes a mesma trajetória.

Agora vamos iniciar o nosso tutorial!!

1. Leitura dos dados

Vamos criar um MoveDataFrame a partir de um arquivo csv. O conjunto de dados utilizado é o Geolife GPS trajectory dataset da microsoft.

In [1]:
import pymove
from pymove import MoveDataFrame
In [2]:
import pandas as pd

df = pd.read_csv('examples/geolife_sample.csv', parse_dates=['datetime'])
move_df = MoveDataFrame(data=df, latitude="lat", longitude="lon", datetime="datetime")

move_df.head()
Out[2]:
lat lon datetime id
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

Vamos utilizar a função "show_trajectories_info" para visualizar algumas informações sobre o conjunto de dados.

In [3]:
move_df.show_trajectories_info()
======================= INFORMATION ABOUT DATASET =======================

Number of Points: 217653

Number of IDs objects: 2

Start Date:2008-10-23 05:53:05     End Date:2009-03-19 05:46:37

Bounding Box:(22.147577, 113.54884299999999, 41.132062, 121.156224)


=========================================================================

2. Pré-processamento

Para o pré-processamento, o nosso objetivo é realizar a compressão de dados, mas para isso é preciso passar por um conjunto de etapas. Vamos realizar as seguintes atividades:

  1. Limpeza dos dados;
  2. Segmentação;
  3. Detecção dos pontos de parada;
  4. Compressão de dados.

2.1. Limpeza dos dados

O objetivo desta etapa é eliminar pontos que podem estar inconsistentes, duplicados ou contenham dados inválidos, a fim de contribuir para um melhor resultado nas etapas posteriores.

Primeiro vamos utiizar a função "clean_gps_jumps_by_distance" do módulo filters para eliminar outliers do conjunto de dados.

In [4]:
from pymove import filters
filters.clean_gps_jumps_by_distance(move_df)
Creating or updating distance features in meters...

...Sorting by id and datetime to increase performance

...Set id as index to increase attribution performance

(217653/217653) 100% in 00:00:00.077 - estimated end in 00:00:00.000
...Reset index

..Total Time: 0.07864189147949219

Cleaning gps jumps by distance to jump_coefficient 3.0...

...Filtring jumps 

...Dropping 383 rows of gps points

...Rows before: 217653, Rows after:217270, Sum drop:383


Cleaning gps jumps by distance to jump_coefficient 3.0...

...Filtring jumps 

383 GPS points were dropped

Em seguida vamos para eliminar pontos consecutivos com a mesma latitude e a mesma longitude e por fim eliminar trajetórias que possuem menos de 100 pontos, para isso vamos utilizar as funções "clean_consecutive_duplicates" e "clean_trajectories_with_few_points" do módulo filters respectivamente.

filters.clean_consecutive_duplicates(move_df)

In [5]:
filters.clean_trajectories_with_few_points(move_df)
Creating or updating tid feature...

...Sorting by id and datetime to increase performance


...tid feature was created...


...There are 4 ids with few points

...Tids before drop: 625

...Tids after drop: 621

...Shape - before drop: (217270, 8) - after drop: (217266, 8)

Creating or updating distance, time and speed features in meters by seconds

...Sorting by tid and datetime to increase performance

...Set tid as index to a higher peformance

(114/217266) 0% in 00:00:00.116 - estimated end in 00:03:42.658
(44041/217266) 20% in 00:00:00.137 - estimated end in 00:00:00.541
(87233/217266) 40% in 00:00:00.157 - estimated end in 00:00:00.234
(130426/217266) 60% in 00:00:00.175 - estimated end in 00:00:00.117
(173830/217266) 80% in 00:00:00.197 - estimated end in 00:00:00.049
(217266/217266) 100% in 00:00:00.218 - estimated end in 00:00:00.000
...Reset index...

..Total Time: 0.224

2.2. Segmentação

Agora que terminamos a filtragem de ruído, é necessário segmentar os dados. Uma das funções diponnives para isso é a "segmentation_by_max_dist" do módulo segmentation. A geometria foi o critério escolhido para realizar a segmentação. Foram segmentadas todas as trajetórias que possuíam uma distância superior a 200 metros entre os seus pontos adjacentes.

In [6]:
from pymove import segmentation
segmentation.by_max_dist(move_df, max_dist_between_adj_points = 200)
Split trajectories by max distance between adjacent points: 200
...setting id as index
(217266/217266) 100% in 00:00:00.318 - estimated end in 00:00:00.000
... Reseting index

Total Time: 0.32 seconds
------------------------------------------

/home/andreza/Área de Trabalho/Laboratorio/PyMove/pymove/preprocessing/segmentation.py:427: RuntimeWarning: invalid value encountered in greater
  dist = (move_data.at[idx, DIST_TO_PREV] > max_dist_between_adj_points)

2.3. Detecção de pontos de parada

Para realizar a compressão é necessário identificar os pontos de parada, para isso vamos utilizar a função "create_or_update_move_stop_by_disttime" do módulo stay point_detection.

In [7]:
from pymove import stay_point_detection
stay_point_detection.create_or_update_move_stop_by_dist_time(move_df, dist_radius=40, time_radius=1000)
Split trajectories by max distance between adjacent points: 40
...setting id as index
(217266/217266) 100% in 00:00:00.077 - estimated end in 00:00:00.000
... Reseting index

Total Time: 0.08 seconds
------------------------------------------


Creating or updating distance, time and speed features in meters by seconds

...Sorting by segment_stop and datetime to increase performance

...Set segment_stop as index to a higher peformance

(5/217266) 0% in 00:00:00.101 - estimated end in 01:13:40.117
(43977/217266) 20% in 00:00:00.129 - estimated end in 00:00:00.511
(88535/217266) 40% in 00:00:00.154 - estimated end in 00:00:00.224
(130502/217266) 60% in 00:00:00.186 - estimated end in 00:00:00.124
(173815/217266) 80% in 00:00:00.279 - estimated end in 00:00:00.069
...Reset index...

..Total Time: 0.352
Create or update stop as True or False
...Creating stop features as True or False using 1000 to time in seconds
True     173604
False     43662
Name: stop, dtype: int64

Total Time: 0.59 seconds
-----------------------------------------------------

2.4. Compressão das trajetórias

Agora que já realizamos todas as etapas necessárias vamos finalmente comprimir as trajetórias, para isso vamos utilizar a função "compress_segment_stop_to_point" do módulo compression.

In [8]:
from pymove import compression
compression.compress_segment_stop_to_point(move_df)
...setting mean to lat and lon...
...move segments will be dropped...
...get only segments stop...
(180/173604) 0% in 00:00:00.169 - estimated end in 00:02:42.845
(8780/173604) 5% in 00:00:00.283 - estimated end in 00:00:05.314
(17596/173604) 10% in 00:00:00.512 - estimated end in 00:00:04.543
(26517/173604) 15% in 00:00:00.818 - estimated end in 00:00:04.538
(34760/173604) 20% in 00:00:00.941 - estimated end in 00:00:03.759
(44270/173604) 25% in 00:00:01.112 - estimated end in 00:00:03.250
(52301/173604) 30% in 00:00:01.262 - estimated end in 00:00:02.927
(61560/173604) 35% in 00:00:01.484 - estimated end in 00:00:02.701
(70051/173604) 40% in 00:00:01.633 - estimated end in 00:00:02.415
(78346/173604) 45% in 00:00:01.929 - estimated end in 00:00:02.345
(95766/173604) 55% in 00:00:02.179 - estimated end in 00:00:01.771
(105404/173604) 60% in 00:00:02.447 - estimated end in 00:00:01.583
(113027/173604) 65% in 00:00:02.928 - estimated end in 00:00:01.569
(121669/173604) 70% in 00:00:03.447 - estimated end in 00:00:01.471
(130319/173604) 75% in 00:00:03.985 - estimated end in 00:00:01.323
(138960/173604) 80% in 00:00:04.329 - estimated end in 00:00:01.079
(147756/173604) 85% in 00:00:04.909 - estimated end in 00:00:00.858
(156650/173604) 90% in 00:00:05.144 - estimated end in 00:00:00.556
(165386/173604) 95% in 00:00:05.472 - estimated end in 00:00:00.271
(173604/173604) 100% in 00:00:05.856 - estimated end in 00:00:00.000

...Dropping 216566 points...
...Shape_before: 217266
...Current shape: 700
...Compression time: 5.960 seconds
-----------------------------------------------------

In [9]:
move_df.show_trajectories_info()
======================= INFORMATION ABOUT DATASET =======================

Number of Points: 700

Number of IDs objects: 2

Number of TIDs trajectory: 365

Start Date:2008-10-23 05:54:03     End Date:2009-03-19 04:35:37

Bounding Box:(22.147998, 113.54916599999999, 41.118873, 121.154734)

Gap time MAX:424105.0     Gap time MIN:1.0

Speed MAX:36.028    Speed MIN:0.0

Distance MAX:2001218.375    Distance MIN:0.0


=========================================================================

3. Visualização de dados

Neste trecho do tutorial, iremos fazer uma análise exploratória dos recursos que esse módulo dispõe para realizar as visualizações! Este módulo faz uso de bibliotecas como Matplotlib e Folium!


3.1. Importações

Para continuarmos, devemos importar o módulo de visualização.

In [10]:
from pymove.visualization import visualization

3.2. Gerando cores

Temos uma função que permite gerar cores aleatórias para que você possa ter mais opções de cores em suas visualizações!

In [12]:
visualization.generate_color()
Out[12]:
'#8A2BE2'

Caso queira, você também pode gerar tuplas que representam cores RGB, passando a intensidade de cada cor.

In [13]:
rgb = visualization.rgb([0.6, 0.2, 0.2])
rgb
Out[13]:
(51, 51, 153)

Com esta tupla RGB você pode gerar cores hexadecimais!

In [14]:
visualization.hex_rgb([0.6, 0.2, 0.2])
Out[14]:
'#333399'

3.3. Explorando as trajetórias!

Podemos gerar uma visão geral que contém a distribuição de dados por período, hora, data e dia da semana para entender melhor como os dados são distribuídos.

In [16]:
visualization.show_object_id_by_date(move_df)
Creating date features...
..Date features was created...


Creating or updating a feature for hour...

...Hour feature was created...


Creating or updating period feature
...Early morning from 0H to 6H
...Morning from 6H to 12H
...Afternoon from 12H to 18H
...Evening from 18H to 24H

...the period of day feature was created

Creating or updating day of the week feature...

...the day of the week feature was created...

/home/andreza/miniconda3/lib/python3.7/site-packages/pandas/plotting/_matplotlib/tools.py:298: MatplotlibDeprecationWarning: 
The rowNum attribute was deprecated in Matplotlib 3.2 and will be removed two minor releases later. Use ax.get_subplotspec().rowspan.start instead.
  layout[ax.rowNum, ax.colNum] = ax.get_visible()
/home/andreza/miniconda3/lib/python3.7/site-packages/pandas/plotting/_matplotlib/tools.py:298: MatplotlibDeprecationWarning: 
The colNum attribute was deprecated in Matplotlib 3.2 and will be removed two minor releases later. Use ax.get_subplotspec().colspan.start instead.
  layout[ax.rowNum, ax.colNum] = ax.get_visible()
/home/andreza/miniconda3/lib/python3.7/site-packages/pandas/plotting/_matplotlib/tools.py:304: MatplotlibDeprecationWarning: 
The rowNum attribute was deprecated in Matplotlib 3.2 and will be removed two minor releases later. Use ax.get_subplotspec().rowspan.start instead.
  if not layout[ax.rowNum + 1, ax.colNum]:
/home/andreza/miniconda3/lib/python3.7/site-packages/pandas/plotting/_matplotlib/tools.py:304: MatplotlibDeprecationWarning: 
The colNum attribute was deprecated in Matplotlib 3.2 and will be removed two minor releases later. Use ax.get_subplotspec().colspan.start instead.
  if not layout[ax.rowNum + 1, ax.colNum]:
/home/andreza/miniconda3/lib/python3.7/site-packages/pandas/plotting/_matplotlib/tools.py:298: MatplotlibDeprecationWarning: 
The rowNum attribute was deprecated in Matplotlib 3.2 and will be removed two minor releases later. Use ax.get_subplotspec().rowspan.start instead.
  layout[ax.rowNum, ax.colNum] = ax.get_visible()
/home/andreza/miniconda3/lib/python3.7/site-packages/pandas/plotting/_matplotlib/tools.py:298: MatplotlibDeprecationWarning: 
The colNum attribute was deprecated in Matplotlib 3.2 and will be removed two minor releases later. Use ax.get_subplotspec().colspan.start instead.
  layout[ax.rowNum, ax.colNum] = ax.get_visible()
/home/andreza/miniconda3/lib/python3.7/site-packages/pandas/plotting/_matplotlib/tools.py:304: MatplotlibDeprecationWarning: 
The rowNum attribute was deprecated in Matplotlib 3.2 and will be removed two minor releases later. Use ax.get_subplotspec().rowspan.start instead.
  if not layout[ax.rowNum + 1, ax.colNum]:
/home/andreza/miniconda3/lib/python3.7/site-packages/pandas/plotting/_matplotlib/tools.py:304: MatplotlibDeprecationWarning: 
The colNum attribute was deprecated in Matplotlib 3.2 and will be removed two minor releases later. Use ax.get_subplotspec().colspan.start instead.
  if not layout[ax.rowNum + 1, ax.colNum]:
/home/andreza/miniconda3/lib/python3.7/site-packages/pandas/plotting/_matplotlib/tools.py:298: MatplotlibDeprecationWarning: 
The rowNum attribute was deprecated in Matplotlib 3.2 and will be removed two minor releases later. Use ax.get_subplotspec().rowspan.start instead.
  layout[ax.rowNum, ax.colNum] = ax.get_visible()
/home/andreza/miniconda3/lib/python3.7/site-packages/pandas/plotting/_matplotlib/tools.py:298: MatplotlibDeprecationWarning: 
The colNum attribute was deprecated in Matplotlib 3.2 and will be removed two minor releases later. Use ax.get_subplotspec().colspan.start instead.
  layout[ax.rowNum, ax.colNum] = ax.get_visible()
/home/andreza/miniconda3/lib/python3.7/site-packages/pandas/plotting/_matplotlib/tools.py:304: MatplotlibDeprecationWarning: 
The rowNum attribute was deprecated in Matplotlib 3.2 and will be removed two minor releases later. Use ax.get_subplotspec().rowspan.start instead.
  if not layout[ax.rowNum + 1, ax.colNum]:
/home/andreza/miniconda3/lib/python3.7/site-packages/pandas/plotting/_matplotlib/tools.py:304: MatplotlibDeprecationWarning: 
The colNum attribute was deprecated in Matplotlib 3.2 and will be removed two minor releases later. Use ax.get_subplotspec().colspan.start instead.
  if not layout[ax.rowNum + 1, ax.colNum]:

Podemos plotar todos os dados de trajetória para termos uma ideia ou noção inicial (ou confundirmo-nos mais ainda, rs). O marcador verde indica o ponto inicial da trajetória e marcador vermelho indica o ponto final.

Observação: Para essa e as demais visualizações com mapas, utilizamos a biblioteca Folium. Quando essas visualizações incluem muitos dados, a visualização não consegue ser carregada em um notebook como este. Por isso, recomendamos que você salve esse tipo de visualização como um arquivo .html. Para isto basta setar o parâmetro save_as_html como True e indicar o nome do arquivo a ser salvo no parâmetro filename. Neste tutorial iremos limitar o número de dados para que possamos vislumbrar aqui as visualizações.

In [17]:
visualization.plot_trajectories_with_folium(move_df, n_rows=10000)
Out[17]:

Também podemos gerar visualizações dos pontos de trajetória filtrados por features de tempo, como:

  • Dia da semana com a função plot_trajectory_by_day_week

  • Período de tempo com data de início e data de término com a função plot_trajectory_by_date

  • Período de tempo com hora de início e hora de término com a função plot_trajectory_by_hour

  • Período de um dia com a função plot_trajectory_by_period

Aqui embaixo temos um exemplo utilizando o plot_trajectory_by_date.

In [20]:
visualization.plot_trajectory_by_date(move_df, '2008-10-23', '2008-10-23')
Out[20]:

Também podemos gerar IDs para as trajetórias e plotar!

Utilizando o matplotlib:

In [21]:
move_df.generate_tid_based_on_id_datatime()
move_df.plot_traj_id(move_df['tid'][0])
Creating or updating tid feature...

...Sorting by id and datetime to increase performance


...tid feature was created...

Out[21]:
(           lat         lon            datetime  id        date  hour  \
 0    39.984094  116.319236 2008-10-23 05:53:05   1  2008-10-23     5   
 1    39.984198  116.319322 2008-10-23 05:53:06   1  2008-10-23     5   
 2    39.984224  116.319402 2008-10-23 05:53:11   1  2008-10-23     5   
 3    39.984211  116.319389 2008-10-23 05:53:16   1  2008-10-23     5   
 4    39.984217  116.319422 2008-10-23 05:53:21   1  2008-10-23     5   
 ..         ...         ...                 ...  ..         ...   ...   
 109  39.979758  116.324513 2008-10-23 05:59:47   1  2008-10-23     5   
 110  39.979743  116.324668 2008-10-23 05:59:50   1  2008-10-23     5   
 111  39.979725  116.324794 2008-10-23 05:59:52   1  2008-10-23     5   
 112  39.979691  116.324938 2008-10-23 05:59:54   1  2008-10-23     5   
 113  39.979672  116.325033 2008-10-23 05:59:59   1  2008-10-23     5   
 
             period       day          tid  
 0    Early morning  Thursday  12008102305  
 1    Early morning  Thursday  12008102305  
 2    Early morning  Thursday  12008102305  
 3    Early morning  Thursday  12008102305  
 4    Early morning  Thursday  12008102305  
 ..             ...       ...          ...  
 109  Early morning  Thursday  12008102305  
 110  Early morning  Thursday  12008102305  
 111  Early morning  Thursday  12008102305  
 112  Early morning  Thursday  12008102305  
 113  Early morning  Thursday  12008102305  
 
 [114 rows x 9 columns],
 <Figure size 720x720 with 1 Axes>)

Ou o Folium!

In [26]:
visualization.plot_trajectory_by_id_with_folium(move_df, 1, n_rows=1000, color="black")
Out[26]:

Também podemos gerar visualizações com Mapa de Calor e visualizar quais as zonas quentes nos nossos dados!

In [23]:
visualization.heatmap(move_df, 10000)
Out[23]:

Também podemos plotar a trajetória como pontos utilizando marcadores!

In [24]:
visualization.plot_markers(move_df, n_rows=400)
Out[24]:

Ou, para não poluir tanto a visualização quando houver muitos dados, podemos utilizar a técnica de Cluster!

In [25]:
visualization.cluster(move_df, 1000)
Out[25]:

E com isso, chegamos ao final de nosso tutorial!

O PyMove permanece em construção e aprimoramento de suas funcionalidades. Para sugestões ou eventuais dúvidas entre em contato conosco!

Fim!