diff --git a/tutorials/IC2S2-2025/[3]stop-detection.ipynb b/tutorials/IC2S2-2025/[3]stop-detection.ipynb deleted file mode 100644 index 21593e27..00000000 --- a/tutorials/IC2S2-2025/[3]stop-detection.ipynb +++ /dev/null @@ -1,742 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "92838936", - "metadata": {}, - "source": [ - "# **Tutorial 3: Stop detection in trajectories**\n", - "\n", - "In this notebook we will explore some stop detection algorithms implemented in `nomad`. We will learn the differences between stop detection and traditional clustering algorithms, and the types of errors we need to watch out for. " - ] - }, - { - "cell_type": "markdown", - "id": "e1f26154", - "metadata": {}, - "source": [ - "### Download the data\n", - "Download the file [IC2S2-2025.zip](https://drive.google.com/file/d/1wk3nrNsmAiBoTtWznHjjjPkmWAZfxk0P/view?usp=drive_link) and extract it to this folder to obtain the sample trajectory data used in this tutorial." - ] - }, - { - "cell_type": "markdown", - "id": "14d265f4", - "metadata": {}, - "source": [ - "## Load data " - ] - }, - { - "cell_type": "markdown", - "id": "7baab3bd", - "metadata": {}, - "source": [ - "Let's use the same 3 week sample of data as in the previous notebook. Initially we will focus on data from a user in just one day, which we can filter at read time. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "19184dee", - "metadata": {}, - "outputs": [], - "source": [ - "import nomad.io.base as loader\n", - "import geopandas as gpd\n", - "from shapely.geometry import Polygon, box, Point\n", - "\n", - "\n", - "city = gpd.read_file(\"garden_city.geojson\").to_crs('EPSG:3857')\n", - "outer_box = box(*city.total_bounds).buffer(15, join_style='mitre')\n", - "\n", - "filepath_root = 'gc_data_long/'\n", - "tc = {\n", - " \"user_id\": \"gc_identifier\",\n", - " \"timestamp\": \"unix_ts\",\n", - " \"x\": \"dev_x\",\n", - " \"y\": \"dev_y\",\n", - " \"ha\":\"ha\",\n", - " \"date\":\"date\"}\n", - "\n", - "users = ['admiring_brattain']\n", - "traj = loader.sample_from_file(filepath_root, format='parquet', users=users, filters = ('date','==', '2024-01-01'), traj_cols=tc)" - ] - }, - { - "cell_type": "markdown", - "id": "036947ae", - "metadata": {}, - "source": [ - "Understanding the time component of the trajectory is important since the notion of a \"stop\" or a \"visit\" requires finding pings that indicate **stationary behavior**. Naturally this depends on the **pings being spatially close to one another, but also in the same period of time**. Let's try to visualize this temporal component on the map. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bae98d82", - "metadata": {}, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "import matplotlib.pyplot as plt\n", - "import matplotlib.animation as animation\n", - "import shapely.plotting as shp_plt\n", - "from IPython.display import HTML\n", - "\n", - "# small utility\n", - "from nomad.stop_detection.viz import adjust_zoom, plot_stops_barcode, plot_pings, plot_stops, plot_time_barcode" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5b2e792f", - "metadata": {}, - "outputs": [], - "source": [ - "fig, (ax_map, ax_barcode) = plt.subplots(2, 1, figsize=(6,6.5), gridspec_kw={'height_ratios':[10,1]})\n", - "\n", - "# Map\n", - "shp_plt.plot_polygon(outer_box, ax=ax_map, add_points=False, color='#0e0e0e')\n", - "city.plot(ax=ax_map, column='type', edgecolor='black', linewidth=0.75, cmap='viridis')\n", - "ax_map.scatter(traj['dev_x'], traj['dev_y'], s=10, color='darkblue', alpha=0.75)\n", - "adjust_zoom(traj['dev_x'], traj['dev_y'], buffer=0.8, ax=ax_map)\n", - "ax_map.set_axis_off()\n", - "\n", - "plot_time_barcode(traj['unix_ts'], ax_barcode)\n", - "\n", - "plt.tight_layout(pad=0.1)\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "id": "cc7380ba", - "metadata": {}, - "source": [ - "We can see that the pings are very dense for this user and correspond (apparently) to **4 visits to close by locations**. We would like to have a clustering algorithm that can recover:\n", - "1. **Where** those stops took place (with a centroid for example)\n", - "2. **When** the stop started, and how long it lasted\n", - "\n", - "*Expand the cell bellow to see a small animation for the image above!" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c400af2c", - "metadata": {}, - "outputs": [], - "source": [ - "fig, (ax_map, ax_barcode) = plt.subplots(2, 1, figsize=(6,6.5), gridspec_kw={'height_ratios':[10,1]})\n", - "\n", - "# Map base\n", - "shp_plt.plot_polygon(outer_box, ax=ax_map, add_points=False, color='#0e0e0e')\n", - "city.plot(ax=ax_map, column='type', edgecolor='black', linewidth=0.75, cmap='Accent')\n", - "adjust_zoom(traj['dev_x'], traj['dev_y'], buffer=0.8, ax=ax_map)\n", - "ax_map.set_axis_off()\n", - "points = ax_map.scatter(traj['dev_x'], traj['dev_y'], s=10, color='darkblue', alpha=0.0)\n", - "current = ax_map.scatter([traj['dev_x'].iloc[0]], [traj['dev_y'].iloc[0]], s=50, color='red', alpha=1.0)\n", - "\n", - "def animate(i):\n", - " points.set_alpha([1.0 if j < i else 0.0 for j in range(len(traj))])\n", - " current.set_offsets([[traj['dev_x'].iloc[i], traj['dev_y'].iloc[i]]])\n", - " ax_barcode.cla() # Clear barcode axis\n", - " plot_time_barcode(traj['unix_ts'], ax_barcode, current_idx=i) # Your function, highlights current in red\n", - " return points, current\n", - "\n", - "ani = animation.FuncAnimation(fig, animate, frames=len(traj), interval=120, blit=False)\n", - "plt.tight_layout(pad=0.1)\n", - "\n", - "html = ani.to_html5_video() # capture the animation\n", - "plt.close(fig)\n", - "# finally display the video\n", - "HTML(html)" - ] - }, - { - "cell_type": "markdown", - "id": "c6d760b0", - "metadata": {}, - "source": [ - "## Stop detection algorithms" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fcc66678", - "metadata": {}, - "outputs": [], - "source": [ - "import nomad.stop_detection.dbscan as DBSCAN\n", - "import nomad.stop_detection.lachesis as LACHESIS\n", - "import nomad.stop_detection.grid_based as GRID_BASED\n", - "import nomad.filters as filters " - ] - }, - { - "cell_type": "markdown", - "id": "5f276f4e", - "metadata": {}, - "source": [ - "### Sequential stop detection" - ] - }, - { - "cell_type": "markdown", - "id": "8b269cf8", - "metadata": {}, - "source": [ - "The first stop detection algorithm implemented in ```nomad``` is a sequential algorithm insipired by the one in _Project Lachesis: Parsing and Modeling Location Histories_ (Hariharan & Toyama). This algorithm for extracting stays is dependent on two parameters: the roaming distance and the stay duration. \n", - "\n", - "dur_min\n", - "* `delta_roam` is the Roaming distance ($\\Delta_{\\texttt{max}}$) represents the maximum distance an object can move away from a point location and still be considered to be staying at that location.\n", - "* `dur_min` is a minimum stop duration below which we consider the stop to be (potentially) spurious\n", - "* ```dt_max```: Maximum time gap permitted between consecutive pings in a stay in minutes (dt_max should be greater than dur_min).\n", - "\n", - "The algorithm identifies stops as contiguous sequences of pings that have a diameter less than `delta_roam` for at least the duration of the stop duration." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e80327e9", - "metadata": {}, - "outputs": [], - "source": [ - "LACHESIS.lachesis(traj, delta_roam=20, dt_max = 60, dur_min=5, traj_cols=tc) # Try passing keep_col_names = False" - ] - }, - { - "cell_type": "markdown", - "id": "ac1dc896", - "metadata": {}, - "source": [ - "Optionally, we can get some useful statistics about each stop with the argument `complete_output`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "46d9a1d7", - "metadata": {}, - "outputs": [], - "source": [ - "stops = LACHESIS.lachesis(traj, delta_roam=20, dt_max = 60, dur_min=5, complete_output=True, keep_col_names=True, traj_cols=tc)\n", - "stops" - ] - }, - { - "cell_type": "markdown", - "id": "bab83326", - "metadata": {}, - "source": [ - "The diameter column can give us a notion of the extent of the stop. Larger stops reveal less precise information and could be evidence of **merging** two true stops. We can visualize them as circles with radius `stops['diameter']/2`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "570b6103", - "metadata": {}, - "outputs": [], - "source": [ - "fig, (ax_map, ax_barcode) = plt.subplots(2, 1, figsize=(6,6.5),\n", - " gridspec_kw={'height_ratios':[10,1]})\n", - "\n", - "shp_plt.plot_polygon(outer_box, ax=ax_map, add_points=False, color='#0e0e0e')\n", - "city.plot(ax=ax_map, edgecolor='white', linewidth=1, color='#2c353c')\n", - "\n", - "plot_stops(stops, ax=ax_map, cmap='Reds', x='dev_x', y='dev_y')\n", - "plot_pings(traj, ax=ax_map, s=6, point_color='black', cmap='twilight', traj_cols=tc)\n", - "\n", - "adjust_zoom(stops['dev_x'], stops['dev_y'], buffer=1.4, ax=ax_map)\n", - "ax_map.set_axis_off()\n", - "\n", - "plot_time_barcode(traj[tc['timestamp']], ax=ax_barcode, set_xlim=True)\n", - "plot_stops_barcode(stops, ax=ax_barcode, cmap='Reds', set_xlim=False, x='x', y='y', timestamp='unix_ts')\n", - "\n", - "plt.tight_layout(pad=0.1)\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "id": "b0bcc565", - "metadata": {}, - "source": [ - "### Flexibility in input formats \n", - "\n", - "The stop detection algorithms implemented in `nomad` support different combinations of input formats that are common in commercial datasets, detecting default names when possible\n", - "- timestamps in `datetime64[ns, tz]` or as unix seconds in integers\n", - "- geographic coordinates (`lon`, `lat`) which use the Haversine distance or projected coordinates (`x`, `y`) using meters and euclidean distance.\n", - "- Alternatively, if locations are only given through a spatial index like H3 or geohash, there is a **grid_based** clustering algorithm requiring no coordinates. \n", - "\n", - "The algorithms work with the same call, provided there is at least a pair of coordinates (or a location/spatial index) as well as at least a temporal column." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "01482356-bda1-472c-8875-bd1260679da3", - "metadata": {}, - "outputs": [], - "source": [ - "traj['h3_cell'] = filters.to_tessellation(traj, index=\"h3\", res=10, x='dev_x', y='dev_y', data_crs='EPSG:3857')\n", - "traj['h3_cell']" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8f4c8666", - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "import pandas as pd\n", - "pd.set_option(\"mode.copy_on_write\", True)\n", - "import h3\n", - "\n", - "traj[['longitude','latitude']] = np.column_stack(\n", - " filters.to_projection(traj, x='dev_x', y='dev_y', data_crs='EPSG:3857', crs_to='EPSG:4326')\n", - ")\n", - "\n", - "traj['zoned_datetime'] = pd.to_datetime(traj['unix_ts'], unit='s', utc=True).dt.tz_convert('Etc/GMT-1')\n", - "traj['h3_cell'] = filters.to_tessellation(traj, index=\"h3\", res=10, latitude='latitude', longitude='longitude')\n", - "\n", - "# Alternate datasets \n", - "traj_geog = traj[['latitude', 'longitude', 'zoned_datetime']]\n", - "traj_h3 = traj[['gc_identifier', 'unix_ts', 'h3_cell']]\n", - "\n", - "# Very similar call\n", - "stops_lac = LACHESIS.lachesis(traj_geog, delta_roam=25, dt_max = 45, dur_min=3, complete_output=True, latitude='latitude', longitude='longitude', datetime='zoned_datetime')\n", - "traj_geog['cluster'] = LACHESIS.lachesis_labels(traj_geog, delta_roam=25, dt_max = 45, dur_min=3, latitude='latitude', longitude='longitude', datetime='zoned_datetime')\n", - "\n", - "stops_h3 = GRID_BASED.grid_based(traj_h3, time_thresh=180, dur_min=3, complete_output=True, timestamp='unix_ts', location_id='h3_cell')\n", - "traj_h3['cluster'] = GRID_BASED.grid_based_labels(traj_h3, time_thresh=180, dur_min=3, timestamp='unix_ts', location_id='h3_cell')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "62ae8bc8", - "metadata": {}, - "outputs": [], - "source": [ - "traj_h3[['dev_x','dev_y']] = traj[['dev_x','dev_y']]\n", - "\n", - "fig, (ax_map, ax_barcode) = plt.subplots(2, 1, figsize=(6.5,7),\n", - " gridspec_kw={'height_ratios':[10,1]})\n", - "\n", - "shp_plt.plot_polygon(outer_box, ax=ax_map, add_points=False, color='#0e0e0e')\n", - "city.plot(ax=ax_map, edgecolor='white', linewidth=1, color='#2c353c')\n", - "\n", - "plot_stops(stops_h3, ax=ax_map, cmap='rainbow', location_id='h3_cell', crs='EPSG:3857', edge_only=True)\n", - "#plot_pings(traj_h3, ax=ax_map, point_color='black', cmap=\"rainbow\", radius=2, s=5, circle_alpha=0.5, traj_cols=tc)\n", - "\n", - "adjust_zoom(stops['dev_x'], stops['dev_y'], buffer=3, ax=ax_map)\n", - "ax_map.set_axis_off()\n", - "\n", - "#plot_time_barcode(traj_h3[tc['timestamp']], ax=ax_barcode, set_xlim=True)\n", - "plot_stops_barcode(stops_h3, ax=ax_barcode, cmap='viridis', set_xlim=False, timestamp='unix_ts')\n", - "\n", - "fig.suptitle(\"Stops detected from just H3_cells and unix timestamps\")\n", - "plt.tight_layout(pad=0.1)\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "id": "5a2d1d9f", - "metadata": {}, - "source": [ - "**Fig 2. Possible problems when applying stop detection to be aware of. How to parameterize optimally?**\n", - " \n", - "![title](figures/stop-detection-problems.png)" - ] - }, - { - "cell_type": "markdown", - "id": "3cf66c42", - "metadata": {}, - "source": [ - "### Density based stop detection (Temporal DBSCAN)" - ] - }, - { - "cell_type": "markdown", - "id": "f641e8bc", - "metadata": {}, - "source": [ - "The second stop detection algorithm implemented in ```nomad``` is an adaptation of DBSCAN. Unlike in plain DBSCAN, we also incorporate the time dimension to determine if two pings are \"neighbors\". This implementation relies on 3 parameters\n", - "\n", - "* `time_thresh` defines the maximum time difference (in minutes) between two consecutive pings for them to be considered neighbors within the same cluster.\n", - "* `dist_thresh` specifies the maximum spatial distance (in meters) between two pings for them to be considered neighbors.\n", - "* `min_pts` sets the minimum number of neighbors required for a ping to form a cluster.\n", - "\n", - "Notice that this method also works with **geographic coordinates** (lon, lat), using Haversine distance. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a55c2b49", - "metadata": {}, - "outputs": [], - "source": [ - "users = ['confident_aryabhata']\n", - "traj = loader.sample_from_file(filepath_root, format='parquet', users=users, filters = ('date','<=', '2024-01-03'), traj_cols=tc)\n", - "traj[['longitude','latitude']] = np.column_stack(\n", - " filters.to_projection(traj, x='dev_x', y='dev_y', data_crs='EPSG:3857', crs_to='EPSG:4326')\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "79e4c618", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "stops_dbscan = DBSCAN.ta_dbscan(traj,\n", - " time_thresh=720,\n", - " dist_thresh=15,\n", - " min_pts=3,\n", - " complete_output=True,\n", - " traj_cols=tc)\n", - "\n", - "fig, ax_barcode = plt.subplots(figsize=(10,1.5))\n", - "\n", - "plot_time_barcode(traj['unix_ts'], ax=ax_barcode, set_xlim=True)\n", - "plot_stops_barcode(stops_dbscan, ax=ax_barcode, stop_color='red', set_xlim=False, timestamp='unix_ts')\n", - "fig.suptitle(\"Overlapping stops\")\n", - "plt.tight_layout()\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d42f772e", - "metadata": {}, - "outputs": [], - "source": [ - "import nomad.stop_detection.postprocessing as post\n", - "\n", - "post.invalid_stops(stops_dbscan, print_stops=True, **tc)\n", - "stops_dbscan.loc[stops_dbscan.unix_ts.isin([1704197861, 1704242618])]" - ] - }, - { - "cell_type": "markdown", - "id": "b2c79720", - "metadata": {}, - "source": [ - "## Spatial-only algorithms can produce (temporally) invalid stop tables\n", - "\n", - "While the parameter `time_thresh` helps mitigate this issue (this would be *pre-processing*). It seems like some post-processing is necessary to \"break up\" temporally overlapping stops. " - ] - }, - { - "cell_type": "markdown", - "id": "ec1063cf", - "metadata": {}, - "source": [ - "One way is to use a **sequential location-based algorithm**, using DBSCAN's cluster labels **as \"locations\"**. For this we need the **disaggregated output** of the stop-detection algorithm" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "eb41efc0", - "metadata": {}, - "outputs": [], - "source": [ - "traj[\"cluster\"] = DBSCAN.ta_dbscan_labels(traj,\n", - " time_thresh=720,\n", - " dist_thresh=15,\n", - " min_pts=3,\n", - " traj_cols=tc)\n", - "\n", - "## Uncomment to see label counts\n", - "# traj.cluster.value_counts()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3b62b203-21ca-422c-aa64-0b2cbe7d69b6", - "metadata": {}, - "outputs": [], - "source": [ - "traj" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1d633ed4", - "metadata": {}, - "outputs": [], - "source": [ - "from nomad.stop_detection.utils import summarize_stop\n", - "\n", - "new_cluster = GRID_BASED.grid_based_labels(\n", - " data=traj.loc[traj.cluster!=-1], # except noise pings\n", - " time_thresh=720,\n", - " min_cluster_size=3,\n", - " location_id=\"cluster\", # grid_based requires a location column\n", - " traj_cols=tc)\n", - "\n", - "traj.loc[traj.cluster!=-1, 'cluster'] = new_cluster" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a071a061", - "metadata": {}, - "outputs": [], - "source": [ - "post_processed_stops = [\n", - " summarize_stop(\n", - " grouped_data=group,\n", - " keep_col_names=True,\n", - " complete_output=True,\n", - " traj_cols=tc,\n", - " passthrough_cols = ['cluster', 'gc_identifier']\n", - " )\n", - " for _, group in traj.loc[traj.cluster!=-1].groupby('cluster', as_index=False, sort=False)\n", - " ]\n", - "post_processed_stops = pd.DataFrame(post_processed_stops)\n", - "\n", - "print(\"Invalid stops?\")\n", - "post.invalid_stops(post_processed_stops, print_stops=True, **tc)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "97e0f353", - "metadata": {}, - "outputs": [], - "source": [ - "fig, ax_barcode = plt.subplots(figsize=(10,1.5))\n", - "\n", - "plot_time_barcode(traj['unix_ts'], ax=ax_barcode, set_xlim=True)\n", - "plot_stops_barcode(post_processed_stops, ax=ax_barcode, stop_color='blue', set_xlim=False, timestamp='unix_ts')\n", - "fig.suptitle(\"DBSCAN stops with post-processing\")\n", - "plt.tight_layout()\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "id": "b2ac75ac-6372-4268-8d2e-5bb9d8c6251b", - "metadata": {}, - "source": [ - "This post-processing is also wrapped in the method `nomad.stop_detection.postprocessing.remove_overlaps`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8a6eb8cd-f8e5-483a-9a92-f805eaf634c9", - "metadata": {}, - "outputs": [], - "source": [ - "# post_processed_stops = post.remove_overlaps(traj, time_thresh=720, method='cluster', traj_cols=tc)" - ] - }, - { - "cell_type": "markdown", - "id": "2de448bd", - "metadata": {}, - "source": [ - "## Which algorithm to choose? which parameters\n", - "\n", - "This is not a trivial problem and researchers often rely on \"trial and error\" or, worse, default parameters. A semi-informed choice could depend on:\n", - "\n", - "\n", - "| Factor | Implication |\n", - "| --- | --- |\n", - "| Signal sparsity | Denser data → increase DBSCAN `min_pts` to prevent over‑clustering |\n", - "| Noise level | Higher noise (low accuracy) → use larger distance thresholds |\n", - "| Building proximity | Dense areas → use smaller distance thresholds |\n", - "| Scalability | More robust methods → longer execution time |\n", - "\n", - "We could do **a parameter search** by keeping track of some statistics of the output of several stop detection algorithms" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "dc99ecd5", - "metadata": {}, - "outputs": [], - "source": [ - "traj = loader.sample_from_file(filepath_root, frac_users=0.1, format='parquet', traj_cols=tc, seed=10) # try frac_users = 0.1\n", - "\n", - "# H3 cells for grid_based stop detection method\n", - "traj['h3_cell'] = filters.to_tessellation(traj, index=\"h3\", res=10, x='dev_x', y='dev_y', data_crs='EPSG:3857')\n", - "pings_per_user = traj['gc_identifier'].value_counts()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4609ebe7", - "metadata": {}, - "outputs": [], - "source": [ - "import time\n", - "from tqdm import tqdm\n", - "import nomad.stop_detection.hdbscan as HDBSCAN\n", - "\n", - "# Approximately 5 minutes for 40 users\n", - "results = []\n", - "for user, n_pings in tqdm(pings_per_user.items(), total=len(pings_per_user)):\n", - " user_data = traj.query(\"gc_identifier == @user\")\n", - "\n", - " # For location based\n", - " start_time = time.time()\n", - " stops_gb = GRID_BASED.grid_based(user_data, time_thresh=240, complete_output=True, timestamp='unix_ts', location_id='h3_cell')\n", - " execution_time = time.time() - start_time\n", - " results += [pd.Series({'user':user, 'algo':'grid_based', 'execution_time':execution_time, 'total_dwell':stops_gb.duration.sum(), 'avg_diameter':52, 'n_pings':n_pings})]\n", - " \n", - " # For Lachesis\n", - " start_time = time.time()\n", - " stops_lac = LACHESIS.lachesis(user_data, delta_roam=30, dt_max=240, complete_output=True, traj_cols=tc)\n", - " execution_time = time.time() - start_time\n", - " results += [pd.Series({'user':user, 'algo':'lachesis', 'execution_time':execution_time, 'total_dwell':stops_lac.duration.sum(), 'avg_diameter':stops_lac.diameter.mean(), 'n_pings':n_pings})]\n", - "\n", - " # For TADbscan\n", - " start_time = time.time()\n", - " user_data_tadb = user_data.assign(cluster=DBSCAN.ta_dbscan_labels(user_data, time_thresh=240, dist_thresh=15, min_pts=3, traj_cols=tc))\n", - " # - post-processing\n", - " stops_tadb = post.remove_overlaps(user_data_tadb, time_thresh=240, method='cluster', traj_cols=tc, min_cluster_size=2)\n", - " execution_time = time.time() - start_time\n", - " results += [pd.Series({'user':user, 'algo':'tadbscan', 'execution_time':execution_time, 'total_dwell':stops_tadb.duration.sum(), 'avg_diameter':stops_tadb.diameter.mean(), 'n_pings':n_pings})]\n", - "\n", - " # For HDBSCAN\n", - " start_time = time.time()\n", - " user_data_hdb = user_data.assign(cluster=HDBSCAN.st_hdbscan_labels(user_data, time_thresh=240, min_pts=3, min_cluster_size=2, traj_cols=tc))\n", - " # - post-processing\n", - " stops_hdb = post.remove_overlaps(user_data_hdb, time_thresh=240, method='cluster', traj_cols=tc) \n", - " execution_time = time.time() - start_time\n", - " results += [pd.Series({'user':user, 'algo':'hdbscan', 'execution_time':execution_time, 'total_dwell':stops_hdb.duration.sum(), 'avg_diameter':stops_hdb.diameter.mean(), 'n_pings':n_pings})]\n", - "\n", - "results = pd.DataFrame(results)" - ] - }, - { - "cell_type": "markdown", - "id": "b329c036-1a08-44ef-8a18-688240087a29", - "metadata": {}, - "source": [ - "### Use **completeness to normalize** ('hrs with data' / 'total hrs')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "da1a8b6a", - "metadata": {}, - "outputs": [], - "source": [ - "completeness_per_user = filters.completeness(traj, timestamp='unix_ts', user_id='gc_identifier')\n", - "dwell_scaling = 1/completeness_per_user\n", - "dwell_scaling.name = 'dwell_scaling'\n", - "\n", - "metrics = pd.merge(results, dwell_scaling, left_on='user', right_index=True)\n", - "metrics['rescaled_total_dwell'] = (metrics['total_dwell']/60)*metrics['dwell_scaling'] # in hours" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "da7e4506", - "metadata": {}, - "outputs": [], - "source": [ - "import seaborn as sns\n", - "\n", - "algos = ['grid_based', 'lachesis', 'tadbscan', 'hdbscan']\n", - "palette = dict(zip(algos, sns.color_palette(n_colors=len(algos))))\n", - "\n", - "fig, axes = plt.subplots(1, 3, figsize=(15, 5))\n", - "\n", - "# scatter: n_pings vs execution_time\n", - "sns.scatterplot(data=metrics, x='n_pings', y='execution_time', hue='algo', ax=axes[0])\n", - "axes[0].set_title('n_pings vs execution_time')\n", - "\n", - "# density: avg_diameter\n", - "sns.kdeplot(data=metrics, x='avg_diameter', hue='algo', ax=axes[1], common_norm=False, bw_adjust=2)\n", - "for algo, color in palette.items():\n", - " med = metrics.loc[metrics.algo == algo, 'avg_diameter'].median()\n", - " axes[1].axvline(med, color=color, linestyle='--')\n", - " \n", - "axes[1].set_title('average stop diameter (m)')\n", - "\n", - "# density: rescaled_total_dwell\n", - "sns.kdeplot(data=metrics, x='rescaled_total_dwell', hue='algo', ax=axes[2], common_norm=False)\n", - "axes[2].set_title('total dwell time (h) rescaled')\n", - "\n", - "plt.tight_layout()\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "id": "65f9bb45-f5d5-4eea-8455-288d5e12583c", - "metadata": {}, - "source": [ - "## Export stops for next notebook" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "25ea48f6-42ac-4db7-9cf1-ed6ed3392ff8", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "%%time\n", - "# takes approximately 6 min\n", - "traj = loader.from_file(filepath_root, format='parquet', traj_cols=tc)\n", - "\n", - "all_stops = LACHESIS.lachesis_per_user(traj, delta_roam=30, dt_max=240, complete_output=True, keep_col_names=False, passthrough_cols=['tz_offset'], traj_cols=tc)\n", - "\n", - "loader.to_file(all_stops, \"gc_data_stops/\", format=\"parquet\", partition_by=[\"gc_identifier\"], existing_data_behavior='overwrite_or_ignore', user_id='gc_identifier')" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 313 (nomad-venv)", - "language": "python", - "name": "nomad-venv" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.6" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/tutorials/IC2S2-2025/[3]stop_detection.ipynb b/tutorials/IC2S2-2025/[3]stop_detection.ipynb new file mode 100644 index 00000000..066de1b9 --- /dev/null +++ b/tutorials/IC2S2-2025/[3]stop_detection.ipynb @@ -0,0 +1,2515 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "92838936", + "metadata": { + "id": "92838936" + }, + "source": [ + "# **Tutorial 3: Stop detection in trajectories**\n", + "\n", + "In this notebook we will explore some stop detection algorithms implemented in `nomad`. We will learn the differences between stop detection and traditional clustering algorithms, and the types of errors we need to watch out for." + ] + }, + { + "cell_type": "markdown", + "id": "e1f26154", + "metadata": { + "id": "e1f26154" + }, + "source": [ + "### Download the data\n", + "Download the file [IC2S2-2025.zip](https://drive.google.com/file/d/1wk3nrNsmAiBoTtWznHjjjPkmWAZfxk0P/view?usp=drive_link) and extract it to this folder to obtain the sample trajectory data used in this tutorial." + ] + }, + { + "cell_type": "markdown", + "id": "14d265f4", + "metadata": { + "id": "14d265f4" + }, + "source": [ + "## Load data" + ] + }, + { + "cell_type": "markdown", + "id": "7baab3bd", + "metadata": { + "id": "7baab3bd" + }, + "source": [ + "Let's use the same 3 week sample of data as in the previous notebook. Initially we will focus on data from a user in just one day, which we can filter at read time." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "19184dee", + "metadata": { + "id": "19184dee" + }, + "outputs": [], + "source": [ + "import nomad.io.base as loader\n", + "import geopandas as gpd\n", + "from shapely.geometry import Polygon, box, Point\n", + "\n", + "filepath_root = '/content/drive/MyDrive/Colab Notebooks/garden_city.geojson'\n", + "city = gpd.read_file(filepath_root).to_crs('EPSG:3857')\n", + "outer_box = box(*city.total_bounds).buffer(15, join_style='mitre')\n", + "\n", + "filepath_root = '/content/drive/MyDrive/Colab Notebooks/gc_data_long'\n", + "tc = {\n", + " \"user_id\": \"gc_identifier\",\n", + " \"timestamp\": \"unix_ts\",\n", + " \"x\": \"dev_x\",\n", + " \"y\": \"dev_y\",\n", + " \"ha\":\"ha\",\n", + " \"date\":\"date\"}\n", + "\n", + "users = ['admiring_brattain']\n", + "traj = loader.sample_from_file(filepath_root, format='parquet', users=users, filters = ('date','==', '2024-01-01'), traj_cols=tc)" + ] + }, + { + "cell_type": "markdown", + "id": "036947ae", + "metadata": { + "id": "036947ae" + }, + "source": [ + "Understanding the time component of the trajectory is important since the notion of a \"stop\" or a \"visit\" requires finding pings that indicate **stationary behavior**. Naturally this depends on the **pings being spatially close to one another, but also in the same period of time**. Let's try to visualize this temporal component on the map." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "bae98d82", + "metadata": { + "id": "bae98d82" + }, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "import matplotlib.pyplot as plt\n", + "import matplotlib.animation as animation\n", + "import shapely.plotting as shp_plt\n", + "from IPython.display import HTML\n", + "from nomad.stop_detection.viz import plot_stops_barcode, plot_pings, plot_stops, plot_time_barcode" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "5b2e792f", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 684 + }, + "id": "5b2e792f", + "outputId": "3feb1749-dc06-4206-9f2d-672b35401f79" + }, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "
" + ], + "image/png": "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0fNO/vnof09W91R1lfNaT/X+KVUl3nOltlVK+5O1MdPXs5RtTvS7ct/Dpfad8X5e7t9X7n6djlL69Uy26fLzptx+U+oxKu6MtdZO9aC+vj7NmzdPR48eVUtLSzXqAgAAmHXKyVRMdwIAAHiIkAYAAOAhQhoAAICHCGkAAAAeIqQBAAB4iJAGAADgIUIaAACAhwhpAAAAHippxYHC/W77+vqcFgMAADCbFbJUCWsJlBbSjh07Jklqb2+fQVkAAACQRrLVvHnzJn1MSctCRVGkvXv3qrm5WcaYihVYKX19fWpvb1dXVxfLVs1h9ANI9AOMoB9A8rMfWGt17NgxLVu2TEEw+VlnJY2kBUGgFStWVKQ4l1paWrx5EVA79ANI9AOMoB9A8q8fTDWCVsCFAwAAAB4ipAEAAHhoVoS0+vp63X333aqvr691Kagh+gEk+gFG0A8gxb8flHThAAAAAKprVoykAQAAzDaENAAAAA8R0gAAADxESAMAAPBQzULa97//fV1zzTVatmyZjDF64oknTnnMY489piuuuEILFy6UMUbPP/98ye2/8sorqqur05o1a8b9vTFGxhj927/925ifDw0NFbf37LPPlvEXYTqm6gfZbFYf//jHdf755yudTmvZsmV6z3veo717907a7vve977ia1xXV6czzzxTn/nMZ5TL5SRJzz77rIwxWrBggTKZzJjn/vSnPy0+F9Vz33336fTTT1cqldIll1yin/zkJ2N+/5WvfEWXXXaZWlpaZIzRkSNHpmyTfhA/k/WD3t5efeQjH9Fv/dZvqaGhQR0dHfrjP/5jHT16dNI2L7vssuJrmUqldO655+r+++8v/v7BBx+UMUbnnHPOKc/dvHmzjDE6/fTTK/Y3YmpTHQ8+9KEPadWqVWpoaFBbW5uuu+467dy5c9I249gPahbS+vv79drXvlb33XffpI9505vepM997nNlt//ggw/q+uuvV19fn3784x+P+5j29nY98MADY372+OOPq6mpqeztYXqm6gcDAwPaunWr/uIv/kJbt27VY489phdffFHXXnvtlG1feeWV2rdvn375y1/qYx/7mD796U/rb//2b8c8prm5WY8//viYn23atEkdHR3T/6NQtkceeUQf/ehHdffdd2vr1q167Wtfq7e//e06ePBg8TEDAwO68sor9alPfaqstukH8TFVP9i7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\n" + }, + "metadata": {} + } + ], + "source": [ + "fig, (ax_map, ax_barcode) = plt.subplots(2, 1, figsize=(6,6.5), gridspec_kw={'height_ratios':[10,1]})\n", + "\n", + "# Map\n", + "shp_plt.plot_polygon(outer_box, ax=ax_map, add_points=False, color='#0e0e0e')\n", + "city.plot(ax=ax_map, column='type', edgecolor='black', linewidth=0.75, cmap='viridis')\n", + "ax_map.scatter(traj['dev_x'], traj['dev_y'], s=10, color='darkblue', alpha=0.75)\n", + "ax_map.set_axis_off()\n", + "\n", + "plot_time_barcode(traj['unix_ts'], ax_barcode)\n", + "\n", + "plt.tight_layout(pad=0.1)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "cc7380ba", + "metadata": { + "id": "cc7380ba" + }, + "source": [ + "We can see that the pings are very dense for this user and correspond (apparently) to **4 visits to close by locations**. We would like to have a clustering algorithm that can recover:\n", + "1. **Where** those stops took place (with a centroid for example)\n", + "2. **When** the stop started, and how long it lasted\n", + "\n", + "*Expand the cell bellow to see a small animation for the image above!" + ] + }, + { + "cell_type": "markdown", + "id": "c6d760b0", + "metadata": { + "id": "c6d760b0" + }, + "source": [ + "## Stop detection algorithms" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "fcc66678", + "metadata": { + "id": "fcc66678" + }, + "outputs": [], + "source": [ + "import nomad.stop_detection.dbscan as DBSCAN\n", + "import nomad.stop_detection.lachesis as LACHESIS\n", + "import nomad.stop_detection.grid_based as GRID_BASED\n", + "import nomad.filters as filters" + ] + }, + { + "cell_type": "markdown", + "id": "5f276f4e", + "metadata": { + "id": "5f276f4e" + }, + "source": [ + "### Sequential stop detection" + ] + }, + { + "cell_type": "markdown", + "id": "8b269cf8", + "metadata": { + "id": "8b269cf8" + }, + "source": [ + "The first stop detection algorithm implemented in ```nomad``` is a sequential algorithm insipired by the one in _Project Lachesis: Parsing and Modeling Location Histories_ (Hariharan & Toyama). This algorithm for extracting stays is dependent on two parameters: the roaming distance and the stay duration.\n", + "\n", + "dur_min\n", + "* `delta_roam` is the Roaming distance ($\\Delta_{\\texttt{max}}$) represents the maximum distance an object can move away from a point location and still be considered to be staying at that location.\n", + "* `dur_min` is a minimum stop duration below which we consider the stop to be (potentially) spurious\n", + "* ```dt_max```: Maximum time gap permitted between consecutive pings in a stay in minutes (dt_max should be greater than dur_min).\n", + "\n", + "The algorithm identifies stops as contiguous sequences of pings that have a diameter less than `delta_roam` for at least the duration of the stop duration." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "e80327e9", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 289 + }, + "id": "e80327e9", + "outputId": "287d9d4a-2e60-4ead-91aa-92f9a8c762f0" + }, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + " cluster x y unix_ts ha diameter \\\n", + "0 0 -4.265482e+06 4.393153e+06 1704107777 11.006086 14.820705 \n", + "1 1 -4.265478e+06 4.393120e+06 1704112028 11.038392 16.458197 \n", + "2 2 -4.265482e+06 4.393117e+06 1704112843 12.200451 18.663269 \n", + "3 3 -4.265475e+06 4.393110e+06 1704113482 9.792796 8.638411 \n", + "4 4 -4.265449e+06 4.393130e+06 1704114942 11.163325 17.188732 \n", + "5 5 -4.265433e+06 4.393109e+06 1704117714 11.582069 19.549541 \n", + "6 6 -4.265435e+06 4.393109e+06 1704119274 11.059295 13.624470 \n", + "\n", + " n_pings end_timestamp duration max_gap gc_identifier \n", + "0 3 1704108899 18 12 admiring_brattain \n", + "1 6 1704112657 10 3 admiring_brattain \n", + "2 4 1704113143 5 3 admiring_brattain \n", + "3 3 1704113875 6 6 admiring_brattain \n", + "4 6 1704116038 18 7 admiring_brattain \n", + "5 5 1704118232 8 2 admiring_brattain \n", + "6 3 1704119938 11 5 admiring_brattain " + ], + "text/html": [ + "\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
clusterxyunix_tshadiametern_pingsend_timestampdurationmax_gapgc_identifier
00-4.265482e+064.393153e+06170410777711.00608614.820705317041088991812admiring_brattain
11-4.265478e+064.393120e+06170411202811.03839216.45819761704112657103admiring_brattain
22-4.265482e+064.393117e+06170411284312.20045118.6632694170411314353admiring_brattain
33-4.265475e+064.393110e+0617041134829.7927968.6384113170411387566admiring_brattain
44-4.265449e+064.393130e+06170411494211.16332517.18873261704116038187admiring_brattain
55-4.265433e+064.393109e+06170411771411.58206919.5495415170411823282admiring_brattain
66-4.265435e+064.393109e+06170411927411.05929513.62447031704119938115admiring_brattain
\n", + "
\n", + "
\n", + "\n", + "
\n", + " \n", + "\n", + " \n", + "\n", + " \n", + "
\n", + "\n", + "\n", + "
\n", + " \n", + "\n", + "\n", + "\n", + " \n", + "
\n", + "\n", + "
\n", + "
\n" + ], + "application/vnd.google.colaboratory.intrinsic+json": { + "type": "dataframe", + "summary": "{\n \"name\": \"LACHESIS\",\n \"rows\": 7,\n \"fields\": [\n {\n \"column\": \"cluster\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 2,\n \"min\": 0,\n \"max\": 6,\n \"num_unique_values\": 7,\n \"samples\": [\n 0,\n 1,\n 5\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"x\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 22.343188321748283,\n \"min\": -4265482.42108973,\n \"max\": -4265432.711557724,\n \"num_unique_values\": 7,\n \"samples\": [\n -4265482.42108973,\n -4265478.230315227,\n -4265432.711557724\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"y\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 16.00024360924574,\n \"min\": 4393108.5680012135,\n \"max\": 4393153.055636459,\n \"num_unique_values\": 7,\n \"samples\": [\n 4393153.055636459,\n 4393120.180699908,\n 4393108.5680012135\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"unix_ts\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 3801,\n \"min\": 1704107777,\n \"max\": 1704119274,\n \"num_unique_values\": 7,\n \"samples\": [\n 1704107777,\n 1704112028,\n 1704117714\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"ha\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.7265845914523488,\n \"min\": 9.792795870578788,\n \"max\": 12.200451455776323,\n \"num_unique_values\": 7,\n \"samples\": [\n 11.006086277686535,\n 11.038391866852985,\n 11.582068506847309\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"diameter\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 3.6789355036855724,\n \"min\": 8.63841051326716,\n \"max\": 19.549540971782243,\n \"num_unique_values\": 7,\n \"samples\": [\n 14.82070501101346,\n 16.458197144690676,\n 19.549540971782243\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"n_pings\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 1,\n \"min\": 3,\n \"max\": 6,\n \"num_unique_values\": 4,\n \"samples\": [\n 6,\n 5,\n 3\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"end_timestamp\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 3711,\n \"min\": 1704108899,\n \"max\": 1704119938,\n \"num_unique_values\": 7,\n \"samples\": [\n 1704108899,\n 1704112657,\n 1704118232\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"duration\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 5,\n \"min\": 5,\n \"max\": 18,\n \"num_unique_values\": 6,\n \"samples\": [\n 18,\n 10,\n 11\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"max_gap\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 3,\n \"min\": 2,\n \"max\": 12,\n \"num_unique_values\": 6,\n \"samples\": [\n 12,\n 3,\n 5\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"gc_identifier\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"admiring_brattain\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}" + } + }, + "metadata": {}, + "execution_count": 8 + } + ], + "source": [ + "LACHESIS.lachesis(traj, delta_roam=20, dt_max = 60, dur_min=5, complete_output=True, keep_col_names=True, traj_cols=tc)" + ] + }, + { + "cell_type": "markdown", + "id": "ac1dc896", + "metadata": { + "id": "ac1dc896" + }, + "source": [ + "Optionally, we can get some useful statistics about each stop with the argument `complete_output`" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "46d9a1d7", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 289 + }, + "id": "46d9a1d7", + "outputId": "7b1083a3-26dd-4bf9-abc0-6eb6ff9719f6" + }, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + " cluster x y unix_ts ha diameter \\\n", + "0 0 -4.265482e+06 4.393153e+06 1704107777 11.006086 14.820705 \n", + "1 1 -4.265478e+06 4.393120e+06 1704112028 11.038392 16.458197 \n", + "2 2 -4.265482e+06 4.393117e+06 1704112843 12.200451 18.663269 \n", + "3 3 -4.265475e+06 4.393110e+06 1704113482 9.792796 8.638411 \n", + "4 4 -4.265449e+06 4.393130e+06 1704114942 11.163325 17.188732 \n", + "5 5 -4.265433e+06 4.393109e+06 1704117714 11.582069 19.549541 \n", + "6 6 -4.265435e+06 4.393109e+06 1704119274 11.059295 13.624470 \n", + "\n", + " n_pings end_timestamp duration max_gap gc_identifier \n", + "0 3 1704108899 18 12 admiring_brattain \n", + "1 6 1704112657 10 3 admiring_brattain \n", + "2 4 1704113143 5 3 admiring_brattain \n", + "3 3 1704113875 6 6 admiring_brattain \n", + "4 6 1704116038 18 7 admiring_brattain \n", + "5 5 1704118232 8 2 admiring_brattain \n", + "6 3 1704119938 11 5 admiring_brattain " + ], + "text/html": [ + "\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
clusterxyunix_tshadiametern_pingsend_timestampdurationmax_gapgc_identifier
00-4.265482e+064.393153e+06170410777711.00608614.820705317041088991812admiring_brattain
11-4.265478e+064.393120e+06170411202811.03839216.45819761704112657103admiring_brattain
22-4.265482e+064.393117e+06170411284312.20045118.6632694170411314353admiring_brattain
33-4.265475e+064.393110e+0617041134829.7927968.6384113170411387566admiring_brattain
44-4.265449e+064.393130e+06170411494211.16332517.18873261704116038187admiring_brattain
55-4.265433e+064.393109e+06170411771411.58206919.5495415170411823282admiring_brattain
66-4.265435e+064.393109e+06170411927411.05929513.62447031704119938115admiring_brattain
\n", + "
\n", + "
\n", + "\n", + "
\n", + " \n", + "\n", + " \n", + "\n", + " \n", + "
\n", + "\n", + "\n", + "
\n", + " \n", + "\n", + "\n", + "\n", + " \n", + "
\n", + "\n", + "
\n", + " \n", + " \n", + " \n", + "
\n", + "\n", + "
\n", + "
\n" + ], + "application/vnd.google.colaboratory.intrinsic+json": { + "type": "dataframe", + "variable_name": "stops", + "summary": "{\n \"name\": \"stops\",\n \"rows\": 7,\n \"fields\": [\n {\n \"column\": \"cluster\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 2,\n \"min\": 0,\n \"max\": 6,\n \"num_unique_values\": 7,\n \"samples\": [\n 0,\n 1,\n 5\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"x\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 22.343188321748283,\n \"min\": -4265482.42108973,\n \"max\": -4265432.711557724,\n \"num_unique_values\": 7,\n \"samples\": [\n -4265482.42108973,\n -4265478.230315227,\n -4265432.711557724\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"y\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 16.00024360924574,\n \"min\": 4393108.5680012135,\n \"max\": 4393153.055636459,\n \"num_unique_values\": 7,\n \"samples\": [\n 4393153.055636459,\n 4393120.180699908,\n 4393108.5680012135\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"unix_ts\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 3801,\n \"min\": 1704107777,\n \"max\": 1704119274,\n \"num_unique_values\": 7,\n \"samples\": [\n 1704107777,\n 1704112028,\n 1704117714\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"ha\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.7265845914523488,\n \"min\": 9.792795870578788,\n \"max\": 12.200451455776323,\n \"num_unique_values\": 7,\n \"samples\": [\n 11.006086277686535,\n 11.038391866852985,\n 11.582068506847309\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"diameter\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 3.6789355036855724,\n \"min\": 8.63841051326716,\n \"max\": 19.549540971782243,\n \"num_unique_values\": 7,\n \"samples\": [\n 14.82070501101346,\n 16.458197144690676,\n 19.549540971782243\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"n_pings\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 1,\n \"min\": 3,\n \"max\": 6,\n \"num_unique_values\": 4,\n \"samples\": [\n 6,\n 5,\n 3\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"end_timestamp\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 3711,\n \"min\": 1704108899,\n \"max\": 1704119938,\n \"num_unique_values\": 7,\n \"samples\": [\n 1704108899,\n 1704112657,\n 1704118232\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"duration\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 5,\n \"min\": 5,\n \"max\": 18,\n \"num_unique_values\": 6,\n \"samples\": [\n 18,\n 10,\n 11\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"max_gap\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 3,\n \"min\": 2,\n \"max\": 12,\n \"num_unique_values\": 6,\n \"samples\": [\n 12,\n 3,\n 5\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"gc_identifier\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"admiring_brattain\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}" + } + }, + "metadata": {}, + "execution_count": 9 + } + ], + "source": [ + "stops = LACHESIS.lachesis(traj, delta_roam=20, dt_max = 60, dur_min=5, complete_output=True, keep_col_names=True, traj_cols=tc)\n", + "stops" + ] + }, + { + "cell_type": "markdown", + "id": "bab83326", + "metadata": { + "id": "bab83326" + }, + "source": [ + "The diameter column can give us a notion of the extent of the stop. Larger stops reveal less precise information and could be evidence of **merging** two true stops. We can visualize them as circles with radius `stops['diameter']/2`" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "570b6103", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 684 + }, + "id": "570b6103", + "outputId": "8e32c0dd-4fe9-4c61-d340-d1e8e9468d15" + }, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "
" + ], + "image/png": "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tXjxYjU1Nemtt96atOY111wz/FnOnj1b73rXu7R79+7h3+/bt0+BQEDLly8f99of/ehHCgQCqqqqcu09YmpTrQ/++q//WldccYUKCwu1YMECfepTn1Jra+ukNf04DzwLaf39/XrPe96j7373u5M+Z+3atbrnnnvSrr9v3z6tX79ePT09OnjwYNLnLFq0SPfee++oxx599FEVFxenvTxkZqp5EI1G9eKLL2rLli168cUX9cgjj+jIkSP65Cc/OWXt66+/XqdPn9Yf/vAHfe1rX9O3vvUt/dM//dOo55SUlOjRRx8d9djevXu1ePHizN8U0vbAAw/oq1/9qr75zW/qxRdf1Hve8x597GMf06uvvjr8nGg0quuvv17/8A//kFZt5oF/TDUPurq61NXVpX/+53/Wyy+/rH379unJJ5/UHXfcMWXtL3zhCzp9+rR+//vfa/369friF7+o++67b/j3RUVFevXVV9Xc3DzqdcyD7EtlffC+971P9957rw4fPqynnnpKjuPouuuu0+Dg4KS1fTcPHAtIch599NEJf3/s2DFHkvPrX/86pXpDQ0NOdXW18+STTzqbNm1yvvCFLyRd5je+8Q2ntLTUiUajw49/9KMfdbZs2eJIcp599tk03wmmY6p5kHDo0CFHktPZ2Tnhcz73uc85n/rUp0Y99tGPftRZvXq14ziO8+yzzw7PgWuvvXb4OdFo1JkzZ87wHEB2rFy50vniF784/PPg4KBTXl7u3H333eOem/js3njjjSnrMg/8JZ15kPDggw86BQUFTiwWm/A59fX1zle+8pVRj73zne90brnlFsdxHOfee+915syZ43zpS19y/uqv/mr4OSdOnHBmzZrlbN682amsrMzsTSFtmcyD3/zmN44kp62tbcLn+HEe5OQxac8++6yi0aiuvfZa3Xbbbbr//vvV398/7nnve9/7VFVVpYcffliSdPz4cf385z/X7bffnu2WkYa33npLgUBA73jHO9J6XWFhoQYGBkY9dvvtt+v555/X8ePHJUkPP/ywqqqq9Cd/8idutYspDAwM6L//+7917bXXDj+Wl5ena6+9dtxfs25gHtgp03nw1ltvqbS0VMFgSreiHpZsHmzcuFEPPvigotGopIt7ZK6//npdeumladVG5jKZB/39/br33nu1ZMkSLVq0KK3l2T4PcjKk7d27V7fccovy8/O1YsUKVVdX60c/+lHS527cuFHf//73JV38IG688UYtWLAgm+0iDefPn9emTZu0YcOGlG+W6ziOnn76aT311FP60z/901G/u+SSS3TDDTdo3759kqTvf//72rhxo9ttYxKvv/66BgcHx60AL730Up05c8a15TAP7JbJPHj99de1fft2NTQ0pLycwcFB/fCHP9Rvf/vbcfPgve99r6qrq/XQQw/JcRzt27ePeZBl6cyD3bt3q7i4WMXFxfrJT36in/3sZyooKEhpOX6ZBzkX0t5880098sgjuu2224Yfu+2227R3796kz7/tttvU3Nys9vZ2vpCWi8ViWr9+vRzH0fe+970pn//jH/9YxcXFmj17tm644QbdfPPN+ta3vjXueRs3btS+ffvU3t6u5uZm3XrrrQa6h1eYB7mpp6dHf/7nf653vetdST/PsRL/oBcWFuoLX/iC/u7v/k5/+7d/O+55Gzdu1L333qtIJKL+/n7deOONBrqHG2699Vb9+te/ViQS0dKlS7V+/fpxJwCN5bd5kN72YR/4j//4D50/f16rVq0afsxxHA0NDeno0aNaunTpqOeXlZXp4x//uO644w6dP39eN9xwg3p7e7PdNqaQCGidnZ36r//6r5S2on3kIx/R9773PRUUFKi8vHzC3SE33HCDGhoadMcdd+gTn/iEysrK3G4fk5g/f77y8/P1yiuvjHr8lVde0WWXXTbt+swDf0hnHvT29ur6668fPuEjFApNWf/WW2/VXXfdpcLCQi1cuFB5ecm3Udx66636+7//e33rW9/S7bffnvZuVExPOvNgzpw5mjNnjt75zndq9erVmjt3rh599FFt2LBhwvp+mwc5tyVt7969+trXvqaWlpbh/37zm9/oQx/60PBuzbE2btyo5557Tp/97GeVn5+f5Y4xlURA+8Mf/qCnn3465X88i4qKVFNTo8WLF0/6BQsGg/rsZz+r5557ji2pHigoKND73vc+PfPMM8OPDQ0N6ZlnntGaNWumXZ954A+pzoOenh5dd911Kigo0OOPP67Zs2enVH/OnDmqqalRRUXFhP8wS9K8efP0yU9+UpFIhHnggUzXB47jyHEcXbhwYdL6fpsHnv2J0NfXp7a2tuGfjx07ppaWFs2bN2/4NNezZ8/q+PHjw9fEOnLkiCTpsssuS/oXdktLi1588UX9+7//u2pra0f9bsOGDdq2bZv+8R//cdyK+vrrr9drr72W8jFOcM9U8yAWi2ndunV68cUX9eMf/1iDg4PDxyXMmzcv5eMPprJ9+3Z9/etfZ+uJR7761a/qc5/7nK6++mqtXLlS3/nOd9Tf36+//Mu/HH7OmTNndObMmeH58tJLL6mkpESLFy/WvHnzXOmDeeCtqeZBIqBFo1H98Ic/VE9Pj3p6eiRJCxYscO2P7H379mn37t3MA49MNQ/a29v1wAMP6LrrrtOCBQt08uRJ7dy5U4WFha7ulrRhHngW0n71q1/pIx/5yPDPX/3qVyVJn/vc54YP3n388cdHraRvueUWSdI3v/nNpMcg7N27V+9617vGBTRJ+sxnPqMvfelLeuKJJ8ZdYysQCGj+/PnTfUvIwFTz4NSpU3r88cclSXV1daNe++yzz+qaa65xpY+CggLmgIduvvlmvfbaa9q6davOnDmjuro6Pfnkk6MOHv7Xf/1Xffvb3x7++cMf/rAk6d5779XnP/95V/pgHnhrqnnw4osvDl/3sqamZtRrjx075tqFRgsLC1VYWOhKLaRvqnkwe/ZsPf/88/rOd76jN954Q5deeqk+/OEP6xe/+IUuueQS1/qwYR4EHMdxPO0AAAAA4+TcMWkAAAC5gJAGAABgIUIaAACAhQhpAAAAFiKkAQAAWIiQBgAAYCFCGgAAgIUIaQAAABYipAEAAFiIkAYAAGAhQhoAAICFCGkAAAAW+v8Bhd5LrN8mR+cAAAAASUVORK5CYII=\n" + }, + "metadata": {} + } + ], + "source": [ + "fig, (ax_map, ax_barcode) = plt.subplots(2, 1, figsize=(6,6.5),\n", + " gridspec_kw={'height_ratios':[10,1]})\n", + "\n", + "shp_plt.plot_polygon(outer_box, ax=ax_map, add_points=False, color='#0e0e0e')\n", + "city.plot(ax=ax_map, edgecolor='white', linewidth=1, color='#2c353c')\n", + "\n", + "plot_stops(stops, ax=ax_map, cmap='Reds', x='dev_x', y='dev_y')\n", + "plot_pings(traj, ax=ax_map, s=6, point_color='black', cmap='twilight', traj_cols=tc)\n", + "\n", + "ax_map.set_axis_off()\n", + "\n", + "plot_time_barcode(traj[tc['timestamp']], ax=ax_barcode, set_xlim=True)\n", + "plot_stops_barcode(stops, ax=ax_barcode, cmap='Reds', set_xlim=False, x='x', y='y', timestamp='unix_ts')\n", + "\n", + "plt.tight_layout(pad=0.1)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "b0bcc565", + "metadata": { + "id": "b0bcc565" + }, + "source": [ + "### Flexibility in input formats\n", + "\n", + "The stop detection algorithms implemented in `nomad` support different combinations of input formats that are common in commercial datasets, detecting default names when possible\n", + "- timestamps in `datetime64[ns, tz]` or as unix seconds in integers\n", + "- geographic coordinates (`lon`, `lat`) which use the Haversine distance or projected coordinates (`x`, `y`) using meters and euclidean distance.\n", + "- Alternatively, if locations are only given through a spatial index like H3 or geohash, there is a **grid_based** clustering algorithm requiring no coordinates.\n", + "\n", + "The algorithms work with the same call, provided there is at least a pair of coordinates (or a location/spatial index) as well as at least a temporal column." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "01482356-bda1-472c-8875-bd1260679da3", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 458 + }, + "id": "01482356-bda1-472c-8875-bd1260679da3", + "outputId": "f0a5c78b-aeea-4c19-a77d-ea92dee58b7d" + }, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "0 8a3593605ad7fff\n", + "1 8a3593605ad7fff\n", + "2 8a3593605ad7fff\n", + "3 8a3593605ad7fff\n", + "4 8a3593605ad7fff\n", + " ... \n", + "91 8a3593605adffff\n", + "92 8a3593605adffff\n", + "93 8a3593605adffff\n", + "94 8a3593605adffff\n", + "95 8a3593605adffff\n", + "Name: h3_cell, Length: 96, dtype: object" + ], + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
h3_cell
08a3593605ad7fff
18a3593605ad7fff
28a3593605ad7fff
38a3593605ad7fff
48a3593605ad7fff
......
918a3593605adffff
928a3593605adffff
938a3593605adffff
948a3593605adffff
958a3593605adffff
\n", + "

96 rows × 1 columns

\n", + "

" + ] + }, + "metadata": {}, + "execution_count": 8 + } + ], + "source": [ + "traj['h3_cell'] = filters.to_tessellation(traj, index=\"h3\", res=10, x='dev_x', y='dev_y', data_crs='EPSG:3857')\n", + "traj['h3_cell']" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "8f4c8666", + "metadata": { + "id": "8f4c8666" + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "import pandas as pd\n", + "pd.set_option(\"mode.copy_on_write\", True)\n", + "import h3\n", + "\n", + "traj[['longitude','latitude']] = np.column_stack(\n", + " filters.to_projection(traj, x='dev_x', y='dev_y', data_crs='EPSG:3857', crs_to='EPSG:4326')\n", + ")\n", + "\n", + "traj['zoned_datetime'] = pd.to_datetime(traj['unix_ts'], unit='s', utc=True).dt.tz_convert('Etc/GMT-1')\n", + "traj['h3_cell'] = filters.to_tessellation(traj, index=\"h3\", res=10, latitude='latitude', longitude='longitude')\n", + "\n", + "# Alternate datasets\n", + "traj_geog = traj[['latitude', 'longitude', 'zoned_datetime']]\n", + "traj_h3 = traj[['gc_identifier', 'unix_ts', 'h3_cell']]\n", + "\n", + "# Very similar call\n", + "stops_lac = LACHESIS.lachesis(traj_geog, delta_roam=25, dt_max = 45, dur_min=3, complete_output=True, latitude='latitude', longitude='longitude', datetime='zoned_datetime')\n", + "traj_geog['cluster'] = LACHESIS.lachesis_labels(traj_geog, delta_roam=25, dt_max = 45, dur_min=3, latitude='latitude', longitude='longitude', datetime='zoned_datetime')\n", + "\n", + "stops_h3 = GRID_BASED.grid_based(traj_h3, time_thresh=180, dur_min=3, complete_output=True, timestamp='unix_ts', location_id='h3_cell')\n", + "traj_h3['cluster'] = GRID_BASED.grid_based_labels(traj_h3, time_thresh=180, dur_min=3, timestamp='unix_ts', location_id='h3_cell')" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "62ae8bc8", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 182 + }, + "id": "62ae8bc8", + "outputId": "6f8c57ba-3c49-42c0-cf10-0cebba1fc6cd" + }, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "
" + ], + "image/png": "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2SQiIiIi2TDZJCIiIiLZMNkkIiIiItkw2SQiIiIi2TDZJCIiIiLZ/COSzXNXDxqVnTj3h8HfpXl4e8m6lva11N7jUNb+TJ07U7777jt89913JsvNvbbFw32Yayflx38blZ0+9rvZ1yVdPP3gvF07vBcAcP7AbxbHZ6nOtcN7sWHd2lLH/nD5zT2G1+3ib8bjLVmnZBu2uJ5yAJd/Lj1WLuzYa1WZuTrXUw4AMB7jX9uO48+tR6XXR7YcxpEth822a2m7qXrW7mPK9hnJAEzHecnXKRuuAgB2/HgOO348Z7Hth+vM+2i30Tb99pLtmWq75P7m6toyrkdtw1ybpbVn6zmz9Lq0c1IWlsb48DWztR1z+1hzzmy9TtbWL4/rDzz69ZDruMqyr7XX2dI++s+Mp9k/Itk8f81Esnl+n8HfpXl4e8m6lva11N7jUNb+TJ07UypSspl2/bRR2elju82+LumyPtk88uDv8wfMJ6fW1Ll25A9sWm9dsnlrr2Hid9lEslmyTsk2bHEj5QAu/7K/1O3lkWzeKCXZPLHtL8Nkc+thHNl62Gy7lrabqmftPqac3H4SgHXJ5s6ND35w/LzhHH7eYPmH0MN1dm6+YLRNv71ke6baLrm/ubq2jOtR2zDXZmnt2XrOLL0u7ZyUhaUxPnzNbG3H3D7WnDNbr5O19cvj+gOPfj3kOq6y7Gvtdba0j/4z42n2j0g2iYiIiOjJYLJJRERERLJhsklEREREsmGySURERESyYbJJRERERLJhsklEREREsmGySURERESyYbJJRERERLJhsklEREREsmGySURERESyYbJJRERERLJhsklEREREsmGySURERESyYbJJRERERLJhsklEREREsmGySURERESyYbJJRERERLJhsklEREREsmGySURERESyYbJJRERERLJhsklEREREsmGySURERESyYbJJRERERLJhsklEREREsmGySURERESyYbJJRERERLJhsklEREREsmGySURERESyYbJJRERERLJhsklEREREsmGySURERESyYbJJRERERLJhsklEREREsmGySURERESyYbJJRERERLJhsklEREREsmGySURERESyYbJJRERERLJhsklEREREsmGySURERESyYbJJRERERLJhsklEREREsmGySURERESyYbJJRERERLJhsklEREREsmGySURERESyYbJJRERERLJhsklEREREsmGySURERESyYbJJRERERLJhsklEREREsmGySURERESyYbJJRERERLJhsklEREREsmGySURERESyYbJJRERERLJhsklEREREsmGySURERESyYbJJRERERLJhsklEREREsmGySURERESycXjSA3gcgnyeNyprGNTC4O/SPLy9ZF1L+1pq73Eoa3+mzp0p/fr1s6q8tHq29mGuHS/vekZl9Rq/aPZ1SbXrhQEAfJ598HdQs5csjs9SHZ9nw9CtbTN4urmY3P7wMdVoGWY4nlbGbZesU7INW9Rq1wxq+9I/DgI7tLSqzFydWu2aATAeY8PoECjtlNLrZzs3tdiuNXVK1rN2H1MadGwAwPT5LVnWtrsvACCiR7BVbT9cr23XwFK3lWzPVPsl9zdX15ZxPWob5tosrT1r+rE0NnPntDxYGqN+u7X1rG3bmnNm63V6lDgti0e9HnIdV1n2tfY6W9pH/5nxNFMIIYSlSpmZmXB3d0dGRgbc3Nwex7jKzbVLtzFlzDdw0ajgpHJ80sOpVAryi5CTnY+p/x0AH//qT3o4Vrly/R7en7keLmolVE6Pdr3zC4qQk1eIMSMi8OnmFGiclFApyxY7+YVFyC4oxPy4HvD3rGK27oX79zBs03q4Kp2gdjDdb15xEbIKC7CsW08Eephvz5KL2Xcwcs/3cHV0gspenvdIvrYIWUUFWPhCDAI01aTym/m3MO/0f6G2VxsknBVJoa4Qedo8jK03BjVVNczWzS68hN03x8JRoYG9neoxjZCIKiutLh9FIhsv1pwHjdL/SQ/HJrbkhnyMTkRERESyYbJJRERERLJhsklEREREsmGySURERESyYbJJRERERLJhsklEREREsmGySURERESyYbJJRERERLJhsklEREREsmGySURERESyYbJJRERERLJhsklEREREsmGySURERESyYbJJRERERLJhsklEREREsmGySURERESyYbJJRERERLJxsKaSEAIAkJmZKetg5JCVlYXConyI7CIUFFl1uPR/igqKUVSkRVZWFjIzlU96OFbJyspEUWEesrWFKCywf6Q2Cou0KNZqkZOdhaL8PGQXFaLIoWy/lxUU61Ck1SIrKxOZTubHlZWZieK8POTkF5Xab2GxDsWiGFmZmci0e7TjlPrLzkJRTh6y7QpRZC/Pe6RA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\n" + }, + "metadata": {} + } + ], + "source": [ + "traj_h3[['dev_x','dev_y']] = traj[['dev_x','dev_y']]\n", + "\n", + "fig, ax_barcode = plt.subplots(figsize=(6.5,1.5))\n", + "\n", + "plot_time_barcode(traj_h3[tc['timestamp']], ax=ax_barcode, set_xlim=True)\n", + "plot_stops_barcode(stops_h3, ax=ax_barcode, cmap='viridis', set_xlim=False, timestamp='unix_ts')\n", + "\n", + "fig.suptitle(\"Stops detected from just H3_cells and unix timestamps\")\n", + "plt.tight_layout(pad=0.1)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "3cf66c42", + "metadata": { + "id": "3cf66c42" + }, + "source": [ + "### Density based stop detection (Temporal DBSCAN)" + ] + }, + { + "cell_type": "markdown", + "id": "f641e8bc", + "metadata": { + "id": "f641e8bc" + }, + "source": [ + "The second stop detection algorithm implemented in ```nomad``` is an adaptation of DBSCAN. Unlike in plain DBSCAN, we also incorporate the time dimension to determine if two pings are \"neighbors\". This implementation relies on 3 parameters\n", + "\n", + "* `time_thresh` defines the maximum time difference (in minutes) between two consecutive pings for them to be considered neighbors within the same cluster.\n", + "* `dist_thresh` specifies the maximum spatial distance (in meters) between two pings for them to be considered neighbors.\n", + "* `min_pts` sets the minimum number of neighbors required for a ping to form a cluster.\n", + "\n", + "Notice that this method also works with **geographic coordinates** (lon, lat), using Haversine distance." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "a55c2b49", + "metadata": { + "id": "a55c2b49" + }, + "outputs": [], + "source": [ + "users = ['confident_aryabhata']\n", + "traj = loader.sample_from_file(filepath_root, format='parquet', users=users, filters = ('date','<=', '2024-01-03'), traj_cols=tc)\n", + "traj[['longitude','latitude']] = np.column_stack(\n", + " filters.to_projection(traj, x='dev_x', y='dev_y', data_crs='EPSG:3857', crs_to='EPSG:4326')\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "79e4c618", + "metadata": { + "scrolled": true, + "colab": { + "base_uri": "https://localhost:8080/", + "height": 150 + }, + "id": "79e4c618", + "outputId": "5996ac91-23c3-4fff-e577-6fcc11d284d5" + }, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "
" + ], + "image/png": "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q1cvl7pLlixxeRv98uXLlZOTo6FDh/o0V2exsbGKj4/XkiVL9OOPPzrK169fr6+++qrS9oWFheVeHe/Zs6ckOd4+3rBhQ0llr/o7a9mypXr27KnFixe7HNu9e7c+/fRTDRs2rNx4r732msvtV155RZIca3H8+PFybdznAwBATeKt4wCAGtO5c2ctXrxYY8eOVffu3TVx4kS1b99e2dnZSklJUX5+vtLS0tSxY0eXdvXr19ddd92lpUuX6tSpU3r++efL9f3aa6+pX79+6t69uyZNmqQOHTro2LFj2rRpk7777jvt3Lmz2vPt06ePmjRpovvuu0+//vWvZbPZ9Pe//93rW7RjY2P15z//WdnZ2erSpYvS09OVlZWlhQsXOn52y65p06bq16+fHnjgAR07dkwvv/yyOnXqpEmTJlV7np48++yzGjlypPr27asHHnhAhYWFevXVVxUfH++SfHuyePFizZ8/X6NGjVLHjh1VXFysN998U5GRkY5EOSwsTNdcc43S09PVpUsXNW3aVPHx8YqPj9e8efM0dOhQ3XzzzZo4caLj572ioqJcfmvb7vDhwxoxYoSGDBmiTZs26e2339Yvf/lLXXfddZKk2bNna8OGDRo+fLjatm2r3NxczZ8/X61atVK/fv0sWS8AAC5JDX7jOQAAxhhjdu3aZZKSkkzLli1N/fr1TYsWLUxSUpL56quvvLZZvXq1kWRsNps5cuSIxzrffPONmTBhgmnRooWpX7++iYuLMwkJCWb58uWOOvaf8PL001Weft7riy++MDfddJMJCwszsbGx5vHHHzeffPKJkWTWrVvnqDdgwABz7bXXmq1bt5qbb77ZNGjQwLRt29a8+uqrLmPYf94rLS3NPPXUUyYmJsaEhYWZ4cOHm2+//dalrref95o3b165ucvDT20tXbrUdO3a1YSGhpr4+HjzwQcfmLvvvtt07drV4/rZbd++3SQlJZk2bdqY0NBQExMTYxISEszWrVtd6v3rX/8yvXr1MiEhIeXG/+yzz0zfvn1NWFiYiYyMNHfccYfZu3evS3v7z3vt3bvXjB492jRq1Mg0adLEPPLII+b06dOOemvWrDEjR440sbGxJiQkxMTGxpqkpCSzf//+Cs8DAIBAsRlj0TelAAAAh4EDByo/P1+7d++usF5mZqZuueUWZWRkaPTo0QGa3U969uyp6Oho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\n" + }, + "metadata": {} + } + ], + "source": [ + "stops_dbscan = DBSCAN.ta_dbscan(traj,\n", + " time_thresh=720,\n", + " dist_thresh=15,\n", + " min_pts=3,\n", + " complete_output=True,\n", + " traj_cols=tc)\n", + "\n", + "fig, ax_barcode = plt.subplots(figsize=(10,1.5))\n", + "\n", + "plot_time_barcode(traj['unix_ts'], ax=ax_barcode, set_xlim=True)\n", + "plot_stops_barcode(stops_dbscan, ax=ax_barcode, stop_color='red', set_xlim=False, timestamp='unix_ts')\n", + "fig.suptitle(\"Overlapping stops\")\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "d42f772e", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 132 + }, + "id": "d42f772e", + "outputId": "d738ecd0-f417-43e4-8518-cb183bb28fd2" + }, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + " cluster x y unix_ts ha diameter \\\n", + "1 1 -4.265513e+06 4.393055e+06 1704197861 12.44818 43.731772 \n", + "2 2 -4.265490e+06 4.393198e+06 1704242618 12.96571 48.313154 \n", + "\n", + " n_pings end_timestamp duration max_gap gc_identifier \n", + "1 28 1704225293 457 294 confident_aryabhata \n", + "2 39 1704260592 299 54 confident_aryabhata " + ], + "text/html": [ + "\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
clusterxyunix_tshadiametern_pingsend_timestampdurationmax_gapgc_identifier
11-4.265513e+064.393055e+06170419786112.4481843.731772281704225293457294confident_aryabhata
22-4.265490e+064.393198e+06170424261812.9657148.31315439170426059229954confident_aryabhata
\n", + "
\n", + "
\n", + "\n", + "
\n", + " \n", + "\n", + " \n", + "\n", + " \n", + "
\n", + "\n", + "\n", + "
\n", + " \n", + "\n", + "\n", + "\n", + " \n", + "
\n", + "\n", + "
\n", + "
\n" + ], + "application/vnd.google.colaboratory.intrinsic+json": { + "type": "dataframe", + "summary": "{\n \"name\": \"stops_dbscan\",\n \"rows\": 2,\n \"fields\": [\n {\n \"column\": \"cluster\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0,\n \"min\": 1,\n \"max\": 2,\n \"num_unique_values\": 2,\n \"samples\": [\n 2,\n 1\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"x\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 16.49092078529291,\n \"min\": -4265513.323511388,\n \"max\": -4265490.001827558,\n \"num_unique_values\": 2,\n \"samples\": [\n -4265490.001827558,\n -4265513.323511388\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"y\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 100.70823213641619,\n \"min\": 4393055.400394569,\n \"max\": 4393197.823342299,\n \"num_unique_values\": 2,\n \"samples\": [\n 4393197.823342299,\n 4393055.400394569\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"unix_ts\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 31647,\n \"min\": 1704197861,\n \"max\": 1704242618,\n \"num_unique_values\": 2,\n \"samples\": [\n 1704242618,\n 1704197861\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"ha\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.3659487562194804,\n \"min\": 12.448179807940974,\n \"max\": 12.965709502120129,\n \"num_unique_values\": 2,\n \"samples\": [\n 12.965709502120129,\n 12.448179807940974\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"diameter\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 3.239526335100597,\n \"min\": 43.731771947035156,\n \"max\": 48.31315402579923,\n \"num_unique_values\": 2,\n \"samples\": [\n 48.31315402579923,\n 43.731771947035156\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"n_pings\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 7,\n \"min\": 28,\n \"max\": 39,\n \"num_unique_values\": 2,\n \"samples\": [\n 39,\n 28\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"end_timestamp\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 24960,\n \"min\": 1704225293,\n \"max\": 1704260592,\n \"num_unique_values\": 2,\n \"samples\": [\n 1704260592,\n 1704225293\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"duration\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 111,\n \"min\": 299,\n \"max\": 457,\n \"num_unique_values\": 2,\n \"samples\": [\n 299,\n 457\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"max_gap\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 169,\n \"min\": 54,\n \"max\": 294,\n \"num_unique_values\": 2,\n \"samples\": [\n 54,\n 294\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"gc_identifier\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"confident_aryabhata\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}" + } + }, + "metadata": {}, + "execution_count": 18 + } + ], + "source": [ + "import nomad.stop_detection.postprocessing as post\n", + "\n", + "post.invalid_stops(stops_dbscan, print_stops=True, **tc)\n", + "stops_dbscan.loc[stops_dbscan.unix_ts.isin([1704197861, 1704242618])]" + ] + }, + { + "cell_type": "markdown", + "id": "b2c79720", + "metadata": { + "id": "b2c79720" + }, + "source": [ + "## Spatial-only algorithms can produce (temporally) invalid stop tables\n", + "\n", + "While the parameter `time_thresh` helps mitigate this issue (this would be *pre-processing*). It seems like some post-processing is necessary to \"break up\" temporally overlapping stops." + ] + }, + { + "cell_type": "markdown", + "id": "ec1063cf", + "metadata": { + "id": "ec1063cf" + }, + "source": [ + "One way is to use a **sequential location-based algorithm**, using DBSCAN's cluster labels **as \"locations\"**. For this we need the **disaggregated output** of the stop-detection algorithm" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "eb41efc0", + "metadata": { + "id": "eb41efc0" + }, + "outputs": [], + "source": [ + "traj[\"cluster\"] = DBSCAN.ta_dbscan_labels(traj,\n", + " time_thresh=720,\n", + " dist_thresh=15,\n", + " min_pts=3,\n", + " traj_cols=tc)\n", + "\n", + "## Uncomment to see label counts\n", + "# traj.cluster.value_counts()" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "3b62b203-21ca-422c-aa64-0b2cbe7d69b6", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 617 + }, + "id": "3b62b203-21ca-422c-aa64-0b2cbe7d69b6", + "outputId": "5cc2bc9d-0a14-4b01-cb9b-2dc7a14b6c84" + }, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + " gc_identifier dev_x dev_y unix_ts ha \\\n", + "0 confident_aryabhata -4.265516e+06 4.393040e+06 1704111151 9.940171 \n", + "1 confident_aryabhata -4.265521e+06 4.393051e+06 1704111275 12.917403 \n", + "2 confident_aryabhata -4.265521e+06 4.393061e+06 1704111660 9.529149 \n", + "3 confident_aryabhata -4.265510e+06 4.393068e+06 1704111704 10.096338 \n", + "4 confident_aryabhata -4.265505e+06 4.393050e+06 1704111741 13.339782 \n", + ".. ... ... ... ... ... \n", + "396 confident_aryabhata -4.265566e+06 4.393104e+06 1704338918 15.048051 \n", + "397 confident_aryabhata -4.265562e+06 4.393088e+06 1704339010 9.201426 \n", + "398 confident_aryabhata -4.265554e+06 4.393088e+06 1704339350 8.434094 \n", + "399 confident_aryabhata -4.265569e+06 4.393087e+06 1704340058 8.223668 \n", + "400 confident_aryabhata -4.265561e+06 4.393097e+06 1704340275 8.210428 \n", + "\n", + " tz_offset date longitude latitude cluster \n", + "0 -14400 2024-01-01 -38.317783 36.668785 0 \n", + "1 -14400 2024-01-01 -38.317828 36.668866 0 \n", + "2 -14400 2024-01-01 -38.317826 36.668937 0 \n", + "3 -14400 2024-01-01 -38.317727 36.668989 0 \n", + "4 -14400 2024-01-01 -38.317688 36.668856 0 \n", + ".. ... ... ... ... ... \n", + "396 -14400 2024-01-03 -38.318228 36.669250 1 \n", + "397 -14400 2024-01-03 -38.318194 36.669136 1 \n", + "398 -14400 2024-01-03 -38.318120 36.669136 1 \n", + "399 -14400 2024-01-03 -38.318262 36.669123 1 \n", + "400 -14400 2024-01-03 -38.318186 36.669199 1 \n", + "\n", + "[401 rows x 10 columns]" + ], + "text/html": [ + "\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
gc_identifierdev_xdev_yunix_tshatz_offsetdatelongitudelatitudecluster
0confident_aryabhata-4.265516e+064.393040e+0617041111519.940171-144002024-01-01-38.31778336.6687850
1confident_aryabhata-4.265521e+064.393051e+06170411127512.917403-144002024-01-01-38.31782836.6688660
2confident_aryabhata-4.265521e+064.393061e+0617041116609.529149-144002024-01-01-38.31782636.6689370
3confident_aryabhata-4.265510e+064.393068e+06170411170410.096338-144002024-01-01-38.31772736.6689890
4confident_aryabhata-4.265505e+064.393050e+06170411174113.339782-144002024-01-01-38.31768836.6688560
.................................
396confident_aryabhata-4.265566e+064.393104e+06170433891815.048051-144002024-01-03-38.31822836.6692501
397confident_aryabhata-4.265562e+064.393088e+0617043390109.201426-144002024-01-03-38.31819436.6691361
398confident_aryabhata-4.265554e+064.393088e+0617043393508.434094-144002024-01-03-38.31812036.6691361
399confident_aryabhata-4.265569e+064.393087e+0617043400588.223668-144002024-01-03-38.31826236.6691231
400confident_aryabhata-4.265561e+064.393097e+0617043402758.210428-144002024-01-03-38.31818636.6691991
\n", + "

401 rows × 10 columns

\n", + "
\n", + "
\n", + "\n", + "
\n", + " \n", + "\n", + " \n", + "\n", + " \n", + "
\n", + "\n", + "\n", + "
\n", + " \n", + "\n", + "\n", + "\n", + " \n", + "
\n", + "\n", + "
\n", + " \n", + " \n", + " \n", + "
\n", + "\n", + "
\n", + "
\n" + ], + "application/vnd.google.colaboratory.intrinsic+json": { + "type": "dataframe", + "variable_name": "traj", + "summary": "{\n \"name\": \"traj\",\n \"rows\": 401,\n \"fields\": [\n {\n \"column\": \"gc_identifier\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"confident_aryabhata\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"dev_x\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 41.420774823690984,\n \"min\": -4265686.75801807,\n \"max\": -4265393.907162332,\n \"num_unique_values\": 401,\n \"samples\": [\n -4265596.055766985\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"dev_y\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 49.557254832234875,\n \"min\": 4392990.665978055,\n \"max\": 4393345.934079478,\n \"num_unique_values\": 401,\n \"samples\": [\n 4393110.798585053\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"unix_ts\",\n \"properties\": {\n \"dtype\": \"Int64\",\n \"num_unique_values\": 401,\n \"samples\": [\n 1704277206\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"ha\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 15.926127108169064,\n \"min\": 8.008025494011061,\n \"max\": 154.62373675273687,\n \"num_unique_values\": 401,\n \"samples\": [\n 19.574932200217052\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"tz_offset\",\n \"properties\": {\n \"dtype\": \"Int64\",\n \"num_unique_values\": 1,\n \"samples\": [\n -14400\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"date\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": \"2024-01-01\",\n \"max\": \"2024-01-03\",\n \"num_unique_values\": 3,\n \"samples\": [\n \"2024-01-01\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"longitude\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.000372089151041898,\n \"min\": -38.319316119938826,\n \"max\": -38.31668539594206,\n \"num_unique_values\": 401,\n \"samples\": [\n -38.318501327754284\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"latitude\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.00035707696200259565,\n \"min\": 36.6684317184759,\n \"max\": 36.67099153532932,\n \"num_unique_values\": 401,\n \"samples\": [\n 36.669297320695755\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"cluster\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0,\n \"min\": -1,\n \"max\": 3,\n \"num_unique_values\": 5,\n \"samples\": [\n -1\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}" + } + }, + "metadata": {}, + "execution_count": 25 + } + ], + "source": [ + "traj" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "1d633ed4", + "metadata": { + "id": "1d633ed4" + }, + "outputs": [], + "source": [ + "from nomad.stop_detection.utils import summarize_stop\n", + "\n", + "new_cluster = GRID_BASED.grid_based_labels(\n", + " data=traj.loc[traj.cluster!=-1], # except noise pings\n", + " time_thresh=720,\n", + " min_cluster_size=3,\n", + " location_id=\"cluster\", # grid_based requires a location column\n", + " traj_cols=tc)\n", + "\n", + "traj.loc[traj.cluster!=-1, 'cluster'] = new_cluster" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "a071a061", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "a071a061", + "outputId": "d1a2f947-1829-495a-e7fb-32c771477131" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Invalid stops?\n" + ] + }, + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "False" + ] + }, + "metadata": {}, + "execution_count": 27 + } + ], + "source": [ + "post_processed_stops = [\n", + " summarize_stop(\n", + " grouped_data=group,\n", + " keep_col_names=True,\n", + " complete_output=True,\n", + " traj_cols=tc,\n", + " passthrough_cols = ['cluster', 'gc_identifier']\n", + " )\n", + " for _, group in traj.loc[traj.cluster!=-1].groupby('cluster', as_index=False, sort=False)\n", + " ]\n", + "post_processed_stops = pd.DataFrame(post_processed_stops)\n", + "\n", + "print(\"Invalid stops?\")\n", + "post.invalid_stops(post_processed_stops, print_stops=True, **tc)" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "97e0f353", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 150 + }, + "id": "97e0f353", + "outputId": "b17284c9-9f15-4bdf-b5be-1c984d664f8c" + }, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "
" + ], + "image/png": "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507l5uYqJiZGJSUl2rhxo+6//36//bRv317Tpk3T//7v/+r+++9XSkqKJOnGG290tTl16pT69eunwYMHa+jQoVq6dKl+//vfq2PHjkpPT68w3oHE4JtvvlFKSopiYmL05JNPqkGDBpo7d6569+6ttWvXuv6joDrHriLTp0+XzWbT73//ex0/flwvvvii+vTpox07dnh8C+TkyZNKT0/X8OHDNXLkSDVr1kwFBQXq3bu39uzZowkTJujyyy9XVlaWRo8erdOnT+uhhx5yrX/vvfdqwYIFSk9P17hx41RUVKT169fr888/d30bZfr06Xrqqac0dOhQjRs3Tjk5OZo1a5Z69uyp7du3Ky4uTufPn1daWpoKCws1ceJExcfH6/Dhw/rggw90+vRpxcbG6ptvvtGAAQN09dVXa9q0aQoPD9eePXv02WefVRiP4uJipaWlqVu3bnrhhRf0ySefaMaMGWrdurUeeOABSaU549Zbb9XmzZv1wAMPqF27dlq2bJnuueeeSsUeAFBNBgBQI+bPn28kmS1btpTZJjY21lxzzTWu+08//bSR5HOLi4szH330kce6K1asMHa73djtdtO9e3fz5JNPmo8//ticP3/eo90jjzxiJJnt27cHPPYvv/zSSDIrV640xhhTUlJiWrZsaR566CGftpLM+PHjjTHGpKammvj4eJOfnx9wDIwxplOnTqZ///7lthk/frzx97L13nvvGUnmmWee8Vg+ZMgQY7PZzJ49ezzGKsl8+eWXrmUHDhwwERER5vbbb3cti42Nde1TZfTv39907drVdX/w4MFm8ODBxm63m+XLlxtjjNm2bZuRZJYtW+Zq16tXL9OrVy/X/S1bthhJZv78+T7b6NWrl5Fk3njjDdeywsJCEx8fb+64444KxxhoDAYNGmTCwsLM3r17Xcuys7NNdHS06dmzp2tZdY5dWVavXm0kmRYtWpjc3FzX8rfeestIMi+99JJrmTMer7zyikcfL774opFkFi5c6Fp2/vx50717d+NwOFz9rlq1ykgykyZN8hlHSUmJMcaY/fv3G7vdbqZPn+7x+L/+9S8TGhrqWr59+3YjyWRlZZW5bzNnzjSSTE5OTplt9u3b53P877nnHiPJTJs2zaPtNddcY7p06eK6//bbbxtJ5sUXX3QtKy4uNjfffHOZcwoAYD2+Og4AtcjhcPi9+vjbb7+tlStXasWKFZo/f77atm2rO+64Qxs3bnS16du3rzZt2qSBAwdq586deu6555SWlqYWLVp4fH05NzdXkhQdHR3wuDIzM9WsWTOlpqZKKv3K77Bhw7R48WKfr6m6y8jI0NGjR/XKK68EvC1JiouL0zfffKPdu3dXaj2p9EJbdrtdkyZN8lj+2GOPyRij5cuXeyzv3r27unTp4rp/2WWX6bbbbtPHH3/s2re4uDh98cUXys7OrtRYUlJStG3bNp07d06StGHDBt1yyy3q3Lmz1q9fL6n0U26bzaYePXpUel+dHA6HRo4c6bofFhamrl276scffwxo/YpiUFxcrBUrVmjQoEH61a9+5WrXvHlz/fa3v9WGDRtc86o6x64id999t8e8HTJkiJo3b64PP/zQo114eLjGjBnjsezDDz9UfHy8RowY4VrWoEEDTZo0SWfPntXatWsllT7XbDab3+sPOL/i/s4776ikpERDhw7ViRMnXLf4+HglJydr9erVkqTY2FhJ0scff1zmhQGdp3osW7ZMJSUllQmHpNJvW7hLSUnxOO4fffSRGjRo4PEtmZCQEI0fP77S2wIAVB2FNgDUorNnz/otgHv27Kk+ffqob9++Gj16tD799FNFR0e7rlrudP311+udd97RqVOntHnzZk2ePFl5eXkaMmSIvv32W0lSTEyMJPkt6P0pLi7W4sWLlZqaqn379mnPnj3as2ePunXrpmPHjunTTz8tc92ePXsqNTW10udqT5s2TadPn1bbtm3VsWNHPfHEE2Vekd3bgQMHlJCQ4BPH9u3bux53l5yc7NNH27ZtlZ+fr5ycHEnSc889p6+//lqtWrVS165dlZGREVARm5KSoqKiIm3atEnff/+9jh8/rpSUFPXs2dOj0L7yyivVqFGjgPbPn5YtW/qc53zJJZfo1KlTAa1fUQxycnKUn5+vK664wqdd+/btVVJS4jo3uTrHLicnR0ePHnXdzp49W+44bTab2rRpo/3793ssb9Gihc8F5Q4cOKDk5GSFhHi+1fGeF3v37lVCQkK5x2P37t0yxig5OVlNmjTxuH333Xeuiw9efvnlevTRR/Xqq6+qcePGSktL08svv+xxfvawYcN00003ady4cWrWrJmGDx+ut956K6Ci23n+uDvv437gwAE1b97c52Jwbdq0qbB/AIB1KLQBoJYcOnRIZ86cCegNsMPhULdu3Tw+LXUXFham66+/Xn/60580Z84c/fLLL8rKypIktWvXTlLpb24HYtWqVTpy5IgWL16s5ORk1815fnh5F0WTSq9ifvToUc2dOzeg7UmlBfrevXv12muvqUOHDnr11Vd17bXX6tVXXw24DysNHTpUP/74o2bNmqWEhAQ9//zzuuqqq3w+Hfd23XXXKSIiQuvWrdP69evVtGlTtW3bVikpKdq8ebMKCwu1fv1613nXVWW32/0uN14XfwuG6hy766+/Xs2bN3fdXnjhhSqNwfuq/VYrKSmRzWbTRx99pJUrV/rc3Of6jBkz9NVXX2nKlCkqKCjQpEmTdNVVV7l+Pi8yMlLr1q3TJ598olGjRumrr77SsGHD1Ldv33K/LSKVfdwBAHUPhTYA1BLnhaHS0tICal9UVCRJPp/6eXNevOnIkSOSSq9wbbfbA7q6uVRaSDdt2lRZWVk+txEjRujdd98t99PqXr16qXfv3vrzn/9cqU+1GzVqpDFjxmjRokU6ePCgrr76amVkZLgeL+tK1YmJicrOzvb5xH7Xrl2ux935+4rzDz/8oIYNG3p8Wti8eXM9+OCDeu+997Rv3z5deumlmj59ern74PwK9/r16z0K6pSUFBUWFiozM1PHjh1Tz549y+2noqtyV1dFMWjSpIkaNmyo77//3qfdrl27FBISolatWrmWVfXYZWZmehSsd999d7njNMZoz549SkpKqnAfExMTtXv3bp9Pir3nRevWrZWdne26AKE/rVu3ljFGl19+ufr06eNzu+GGGzzad+zYUX/4wx9c/+Fy+PBhj9MpQkJC9Otf/1p/+ctf9O2332r69OlatWqV6yvo1ZGYmKgjR474fHV9z5491e4bABA4Cm0AqAWrVq3SH//4R11++eW66667Kmz/008/aePGjYqPj3f9dNfq1av9foLpPH/V+bXfVq1a6b777tOKFSs0a9Ysn/YlJSWaMWOGDh06pIKCAr3zzjsaMGCAhgwZ4nObMGGC8vLyPM4B98d5rvbf//73CvdNKr1qtDuHw6E2bdqosLDQtSwqKkqSdPr0aY+2t9xyi4qLi/W3v/3NY/nMmTNls9l8rsK9adMmj5/pOnjwoJYtW6bf/OY3stvtKi4u9viqr1T6c2kJCQke4ylLSkqKvvjiC61evdpVaDdu3Fjt27fXn//8Z1eb8pS1r1apKAZ2u12/+c1vtGzZMo+vaR87dkz//Oc/1aNHD9cpCdU5djfddJNHwep+PrgkvfHGGx7/gbJ06VIdOXIkoCur33LLLTp69KiWLFniWlZUVKRZs2bJ4XCoV69ekqQ77rhDxhhNnTrVpw/n82vw4MGy2+2aOnWqz3POGOOKQW5urus/xJw6duyokJAQVzz8FfSdO3eWpIDmV0XS0tL0yy+/6B//+IdrWUlJiesn1gAAwcHPewFADVu+fLl27dqloqIiHTt2TKtWrdLKlSuVmJio999/XxERET7rLF26VA6HQ8YYZWdna968eTp16pReeeUV16eDEydOVH5+vm6//Xa1a9dO58+f18aNG7VkyRIlJSV5XBxqxowZ2rt3ryZNmuQqpC+55BL9+9//VlZWlnbt2qXhw4fr/fffV15enuvnqbzdcMMNatKkiTIzMzVs2LAy97lXr17q1auX64JTFbnyyivVu3dvdenSRY0aNdKXX37p+nktJ+fFuyZNmqS0tDTZ7XYNHz5ct956q1JTU/U///M/2r9/vzp16qQVK1Zo2bJlevjhh9W6dWuPbXXo0EFpaWkeP20lyVVo5eXlqWXLlhoyZIg6deokh8OhTz75RFu2bNGMGTMq3JeUlBRNnz5dBw8e9Cioe/bsqblz5yopKcnnN8u9tW7dWnFxcXrllVcUHR2tqKgodevWTZdffnlA8axIRTGQpGeeecb1e88PPvigQkNDNXfuXBUWFnr8Znd1jl1FGjVqpB49emjMmDE6duyYXnzxRbVp08bn5/D8uf/++zV37lyNHj1aW7duVVJSkpYuXarPPvtML774ouuc/tTUVI0aNUp//etftXv3bvXr108lJSVav369UlNTNWHCBLVu3VrPPPOMJk+erP3792vQoEGKjo7Wvn379O677+r+++/X448/rlWrVmnChAm688471bZtWxUVFenNN9+U3W7XHXfcIan0nPZ169apf//+SkxM1PHjxzV79my1bNmyWhfIcxo0aJC6du2qxx57THv27FG7du30/vvvuwr8mv62BADg/9XS1c4BoN5z/rSV8xYWFmbi4+NN3759zUsvveTxs0VO/n7eKyoqynTv3t289dZbHm2XL19uxo4da9q1a2ccDocJCwszbdq0MRMnTjTHjh3z6buoqMi8+uqrJiUlxcTGxpoGDRqYxMREM2bM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qy7gQvvEiRMqLi72mTTNmjXT0aNHLRuQMUaffPKJPv74Y918880ejzVt2lTp6elasGCBJOm1117T2LFjLdt2XVCVOM+bN0/t27fXjTfeGPB2iouLtXDhQn311Vc+cb7mmmv0q1/9SkuXLpUxRgsWLKhXca7puTxv3jyNHDlSUulXu86cOaO1a9f6tGvQoIEroUil83nkyJFq0KBBtcdQF9TkXC4pKdGCBQtccR4+fLg2bNigffv2+bSt73mD3Bwc5OaaR24ODnJzcJCbg4PcXPPIzVVXZ646/sEHH8jhcCgiIkLp6ekaNmyYMjIyfNqNHTtWCxYs0I8//qhNmzbprrvuCv5g65CCggL985//DPh/5WbPni2Hw6HIyEjdd999euSRR/TAAw/4tBs7dqzmz5+vtWvX6ty5c7rlllusHnq99P3332vz5s0aMWKEJCk0NFTDhg0r83/0x44dq6ysLB09elRZWVn1KjFXVmXm8sqVKz3mZePGjdW3b19X8vVG3qg6cnPVkJvrFnJz1ZGb6yZyc9WQm+uW+p6bAy60GzduLLvdrmPHjnksP3bsmOLj46s9kNTUVO3YsUO7d+9WQUGBXn/9dUVFRfm0S09PV0FBge69917deuutuvTSS6u97bqksnFeunSp8vPzdffddwfU/1133aUdO3Zo3759OnfunP7yl78oJMR3Gtx11136/PPPlZGRoVGjRik0NLRqO1QH1eRcnjdvnoqKipSQkKDQ0FCFhoZqzpw5evvtt3XmzBmf9h07dlS7du00YsQItW/fXh06dKjW9uuSmpzL8+bN008//aTIyEhXnD/88EO9/vrrKikp8Wlfn/MGuTk4yM01j9wcHOTm4CA3Bwe5ueaRm6su4EI7LCxMXbp00aeffupaVlJSok8//VTdu3ev9kCioqLUpk0bXXbZZeVOztDQUN19991as2ZNnf9fjKqobJznzZungQMHqkmTJgH1HxsbqzZt2qhFixZ+E4VTo0aNNHDgQK1du7bexbmm5nJRUZHeeOMNzZgxQzt27HDddu7cqYSEBI+rVLobO3ZsvZzPNTWXT548qWXLlmnx4sUecd6+fbtOnTqlFStW+KxTn/MGuTk4yM01j9wcHOTm4CA3Bwe5ueaRm6uhMldOW7x4sQkPDzcLFiww3377rbn//vtNXFycOXr0qKvNkSNHzPbt280//vEPI8msW7fObN++3Zw8ebLMfv1dPdGd99XrCgsLTU5OjikpKTHGGPPuu+/Wq6snBhJnY4zZvXu3sdlsZvny5QH16+/qie6cV090ys/PNydOnHDdnzlzZr25emJNzOV3333XhIWFmdOnT/s89uSTT5rrrrvOGPOfqydu377dGGPML7/8YnJycswvv/xijDFm+/btdfbqiZVVE3N55syZpnnz5q7nv7uhQ4eaIUOGGGMurrxBbg4OcnPNIzcHB7k5OMjNwUFurnnk5qqp9LNs1qxZ5rLLLjNhYWGma9eu5vPPP/d4/OmnnzaSfG7z588vs8/KJgxv9S1hGFNxnI0xZvLkyaZVq1amuLg4oD4rmzC81aeEYYz1c3nAgAHmlltu8fvYF198YSSZnTt3+iQMb3U5YVSF1XO5Y8eO5sEHH/T72JIlS0xYWJjJycm56PIGuTk4yM01j9wcHOTm4CA3Bwe5ueaRmyvPZoyf66cDAAAAAIAqqTNXHQcAAAAAoD6g0AYAAAAAwEIU2gAAAAAAWIhCGwAAAAAAC1FoAwAAAABgIQptAAAAAAAsRKENAAAAAICFKLQBAAAAALAQhTYAAAAAABai0AYAAAAAwEIU2gAAAAAAWIhCGwAAAAAAC/0fOf4gxA2VMW8AAAAASUVORK5CYII=\n" + }, + "metadata": {} + } + ], + "source": [ + "fig, ax_barcode = plt.subplots(figsize=(10,1.5))\n", + "\n", + "plot_time_barcode(traj['unix_ts'], ax=ax_barcode, set_xlim=True)\n", + "plot_stops_barcode(post_processed_stops, ax=ax_barcode, stop_color='blue', set_xlim=False, timestamp='unix_ts')\n", + "fig.suptitle(\"DBSCAN stops with post-processing\")\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "b2ac75ac-6372-4268-8d2e-5bb9d8c6251b", + "metadata": { + "id": "b2ac75ac-6372-4268-8d2e-5bb9d8c6251b" + }, + "source": [ + "This post-processing is also wrapped in the method `nomad.stop_detection.postprocessing.remove_overlaps`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8a6eb8cd-f8e5-483a-9a92-f805eaf634c9", + "metadata": { + "id": "8a6eb8cd-f8e5-483a-9a92-f805eaf634c9" + }, + "outputs": [], + "source": [ + "# post_processed_stops = post.remove_overlaps(traj, time_thresh=720, method='cluster', traj_cols=tc)" + ] + }, + { + "cell_type": "markdown", + "id": "2de448bd", + "metadata": { + "id": "2de448bd" + }, + "source": [ + "## Which algorithm to choose? which parameters\n", + "\n", + "This is not a trivial problem and researchers often rely on \"trial and error\" or, worse, default parameters. A semi-informed choice could depend on:\n", + "\n", + "\n", + "| Factor | Implication |\n", + "| --- | --- |\n", + "| Signal sparsity | Denser data → increase DBSCAN `min_pts` to prevent over‑clustering |\n", + "| Noise level | Higher noise (low accuracy) → use larger distance thresholds |\n", + "| Building proximity | Dense areas → use smaller distance thresholds |\n", + "| Scalability | More robust methods → longer execution time |\n", + "\n", + "We could do **a parameter search** by keeping track of some statistics of the output of several stop detection algorithms" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "dc99ecd5", + "metadata": { + "id": "dc99ecd5" + }, + "outputs": [], + "source": [ + "traj = loader.sample_from_file(filepath_root, frac_users=0.1, format='parquet', traj_cols=tc, seed=10) # try frac_users = 0.1\n", + "\n", + "# Rename 'gc_identifier' to 'user_id'\n", + "traj = traj.rename(columns={'gc_identifier': 'user_id'})\n", + "\n", + "# H3 cells for grid_based stop detection method\n", + "traj['h3_cell'] = filters.to_tessellation(traj, index=\"h3\", res=10, x='dev_x', y='dev_y', data_crs='EPSG:3857')\n", + "pings_per_user = traj['user_id'].value_counts() # Updated to use 'user_id'\n", + "tc['user_id'] = 'user_id'" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "4609ebe7", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "4609ebe7", + "outputId": "ddee6390-7535-4946-c5cb-dbfa1ef1cc15" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stderr", + "text": [ + "100%|██████████| 35/35 [06:38<00:00, 11.38s/it]\n" + ] + } + ], + "source": [ + "import time\n", + "from tqdm import tqdm\n", + "import nomad.stop_detection.hdbscan as HDBSCAN\n", + "import pandas as pd\n", + "\n", + "# Approximately 5 minutes for 40 users\n", + "results = []\n", + "for user, n_pings in tqdm(pings_per_user.items(), total=len(pings_per_user)):\n", + " user_data = traj.query(\"user_id == @user\") # Updated to use 'user_id'\n", + "\n", + " # For location based\n", + " start_time = time.time()\n", + " stops_gb = GRID_BASED.grid_based_per_user(user_data, time_thresh=240, complete_output=True, timestamp='unix_ts', location_id='h3_cell')\n", + " execution_time = time.time() - start_time\n", + " results += [pd.Series({'user':user, 'algo':'grid_based', 'execution_time':execution_time, 'n_pings':n_pings})]\n", + "\n", + " # For Lachesis\n", + " start_time = time.time()\n", + " stops_lac = LACHESIS.lachesis(user_data, delta_roam=30, dt_max=240, complete_output=True, traj_cols=tc)\n", + " execution_time = time.time() - start_time\n", + " results += [pd.Series({'user':user, 'algo':'lachesis', 'execution_time':execution_time, 'n_pings':n_pings})]\n", + "\n", + " # For TADbscan\n", + " start_time = time.time()\n", + " user_data_tadb = user_data.assign(cluster=DBSCAN.ta_dbscan_labels(user_data, time_thresh=240, dist_thresh=15, min_pts=3, traj_cols=tc))\n", + " # - post-processing\n", + " stops_tadb = post.remove_overlaps(user_data_tadb, time_thresh=240, method='cluster', traj_cols=tc, min_cluster_size=2)\n", + " execution_time = time.time() - start_time\n", + " results += [pd.Series({'user':user, 'algo':'tadbscan', 'execution_time':execution_time, 'n_pings':n_pings})]\n", + "\n", + " # For HDBSCAN\n", + " start_time = time.time()\n", + " user_data_hdb = HDBSCAN.st_hdbscan_per_user(user_data, time_thresh=720, dist_thresh=15, min_pts=3, complete_output=True, traj_cols=tc)\n", + " # - post-processing\n", + " stops_hdb = post.remove_overlaps(user_data_hdb, time_thresh=240, method='cluster', traj_cols=tc)\n", + " execution_time = time.time() - start_time\n", + " results += [pd.Series({'user':user, 'algo':'hdbscan', 'execution_time':execution_time, 'n_pings':n_pings})]\n", + "\n", + "results = pd.DataFrame(results)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "base", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.5" + }, + "colab": { + "provenance": [] + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file