{ "cells": [ { "cell_type": "markdown", "id": "f51d618d", "metadata": {}, "source": [ "# Relating Events\n", "\n", "This notebook covers events-based conflation, aggregation, geometry creation, and dataset integration." ] }, { "cell_type": "code", "execution_count": 1, "id": "cbf0451f", "metadata": { "execution": { "iopub.execute_input": "2026-05-29T16:42:51.697896Z", "iopub.status.busy": "2026-05-29T16:42:51.697381Z", "iopub.status.idle": "2026-05-29T16:42:52.297111Z", "shell.execute_reply": "2026-05-29T16:42:52.296594Z" } }, "outputs": [], "source": [ "import linref as lr\n", "\n", "# Load sample datasets with LRS pre-configured\n", "roadways = lr.datasets.load('roadways', set_lrs=True)\n", "crashes = lr.datasets.load('crashes', set_lrs=True)\n", "pavement = lr.datasets.load('pavement', set_lrs=True)" ] }, { "cell_type": "code", "execution_count": 2, "id": "0f9c1f8b", "metadata": { "execution": { "iopub.execute_input": "2026-05-29T16:42:52.298813Z", "iopub.status.busy": "2026-05-29T16:42:52.298555Z", "iopub.status.idle": "2026-05-29T16:42:52.313770Z", "shell.execute_reply": "2026-05-29T16:42:52.313324Z" } }, "outputs": [], "source": [ "# Prepare data: dissolve and resegment roadways\n", "dissolved = roadways.lr.dissolve()\n", "resegmented = dissolved.lr.resegment(length=5, fill='cut')" ] }, { "cell_type": "markdown", "id": "245bf6d5", "metadata": {}, "source": [ "## Conflating *Point* Data onto *Linear* Data\n", "\n", "An important functionality of linearly referenced data is events-based conflation. Linref makes this easy with the `relate` method, which builds dynamic relationships between two linearly referenced dataframes that can then be aggregated with a variety of methods." ] }, { "cell_type": "code", "execution_count": 3, "id": "2009109f", "metadata": { "execution": { "iopub.execute_input": "2026-05-29T16:42:52.315471Z", "iopub.status.busy": "2026-05-29T16:42:52.315142Z", "iopub.status.idle": "2026-05-29T16:42:52.325689Z", "shell.execute_reply": "2026-05-29T16:42:52.325183Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1 2 1 4 3 5 4 0]\n", " route beg end crash_counts\n", "0 I-5 0.0 5.0 1\n", "1 I-5 5.0 10.0 2\n", "2 I-5 10.0 12.0 1\n" ] } ], "source": [ "# Conflating point data onto linear data\n", "# left_df right_df <-- relations use terminology of left and right\n", "relation = resegmented.lr.relate(crashes)\n", "\n", "# Dataframe-level aggregations like count are easy\n", "print(relation.count()) # Count the number of crashes on each roadway segment\n", "\n", "# Aggregator results are designed to be assigned as new columns to the left dataframe\n", "resegmented['crash_counts'] = relation.count()\n", "print(resegmented.head(3)[['route', 'beg', 'end', 'crash_counts']])" ] }, { "cell_type": "markdown", "id": "bdb5f733", "metadata": {}, "source": [ "Column-level aggregators can be accessed using column indexing on the `relation` instance, using a syntax similar to the pandas `groupby` syntax:" ] }, { "cell_type": "code", "execution_count": 4, "id": "abd24449", "metadata": { "execution": { "iopub.execute_input": "2026-05-29T16:42:52.327267Z", "iopub.status.busy": "2026-05-29T16:42:52.327002Z", "iopub.status.idle": "2026-05-29T16:42:52.336447Z", "shell.execute_reply": "2026-05-29T16:42:52.335999Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " route beg end crash_ids severities Fatal Injury \\\n", "0 I-5 0.0 5.0 [3] [Injury] 0.0 1.0 \n", "1 I-5 5.0 10.0 [5, 6] [Fatal, Injury] 1.0 1.0 \n", "2 I-5 10.0 12.0 [13] [Injury] 0.0 1.0 \n", "\n", " Property Damage Only \n", "0 0.0 \n", "1 0.0 \n", "2 0.0 \n" ] } ], "source": [ "# List of unique values (creates a single column)\n", "resegmented['crash_ids'] = relation['crash_id'].list()\n", "# Apply an aggregator to multiple columns at once\n", "resegmented[['crash_ids', 'severities']] = relation[['crash_id', 'severity']].list()\n", "# Counts of unique values (creates a number of columns equal to the number of unique values)\n", "severities = ['Fatal', 'Injury', 'Property Damage Only']\n", "resegmented[severities] = relation['severity'].value_counts()[severities] # Returned as a dataframe\n", "\n", "print(resegmented.head(3)[['route', 'beg', 'end', 'crash_ids', 'severities'] + severities])" ] }, { "cell_type": "markdown", "id": "24431715", "metadata": {}, "source": [ "## Conflating *Linear* Data onto *Linear* Data" ] }, { "cell_type": "code", "execution_count": 5, "id": "075aa008", "metadata": { "execution": { "iopub.execute_input": "2026-05-29T16:42:52.337961Z", "iopub.status.busy": "2026-05-29T16:42:52.337713Z", "iopub.status.idle": "2026-05-29T16:42:52.356125Z", "shell.execute_reply": "2026-05-29T16:42:52.355598Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " route beg end condition_rating condition_ratings\n", "0 I-5 0.0 5.0 81.900 [84, 77]\n", "1 I-5 5.0 10.0 81.086 [77, 83, 82]\n", "2 I-5 10.0 12.0 82.000 [82]\n", " route beg end surface_type surface_types\n", "0 I-5 0.0 5.0 Asphalt [Asphalt, Concrete]\n", "1 I-5 5.0 10.0 Concrete [Concrete, Asphalt, Concrete]\n", "2 I-5 10.0 12.0 Concrete [Concrete]\n" ] } ], "source": [ "# Conflating linear data onto linear data\n", "relation = resegmented.lr.relate(pavement)\n", "\n", "# Length-weighted mean of numerical data\n", "resegmented['condition_rating'] = relation['condition_rating'].mean().round(3)\n", "resegmented['condition_ratings'] = relation['condition_rating'].list()\n", "\n", "print(resegmented.head(3)[['route', 'beg', 'end', 'condition_rating', 'condition_ratings']])\n", "\n", "# Length-weighted mode of categorical data\n", "resegmented['surface_type'] = relation['surface_type'].mode()\n", "resegmented['surface_types'] = relation['surface_type'].list()\n", "\n", "print(resegmented.head(3)[['route', 'beg', 'end', 'surface_type', 'surface_types']])" ] }, { "cell_type": "markdown", "id": "877750ed", "metadata": {}, "source": [ "## Creating New Geometries from Relations\n", "\n", "We can create new geometries for linearly referenced point or linear dataframes by relating them to linearly referenced dataframes containing m-enabled geometries. When dissolving the roadways layer earlier, that method automatically created m-enabled geometries to retain dissolved event information. We can create them manually for other layers using the `add_geom_m` method, which applies event boundaries to the existing geometries." ] }, { "cell_type": "code", "execution_count": 6, "id": "5b0ef697", "metadata": { "execution": { "iopub.execute_input": "2026-05-29T16:42:52.358043Z", "iopub.status.busy": "2026-05-29T16:42:52.357717Z", "iopub.status.idle": "2026-05-29T16:42:52.374251Z", "shell.execute_reply": "2026-05-29T16:42:52.373794Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[, , ]\n", "[, , ]\n", "[LINESTRING M (0.0 0.0 0.0, 2.5 0.5 2.5) # linref compatibility approximation, LINESTRING M (2.5 0.5 2.5, 5.0 1.2 5.0) # linref compatibility approximation, LINESTRING M (5.0 1.2 5.0, 7.8 2.0 7.8) # linref compatibility approximation]\n", "[LINESTRING M (0.0 0.0 0.0, 2.5 0.5 2.5) # linref compatibility approximation, LINESTRING M (2.5 0.5 2.5, 5.0 1.2 5.0) # linref compatibility approximation, LINESTRING M (5.0 1.2 5.0, 7.8 2.0 7.8) # linref compatibility approximation]\n" ] } ], "source": [ "# Add m-enabled geometries to the roadways layer\n", "roadways = roadways.lr.add_geom_m()\n", "\n", "# Interpolate new crash geometries based on their LRS information\n", "interpolated = crashes.lr.relate(roadways).interpolate()\n", "print(list(crashes.geometry)[:3])\n", "print(list(interpolated)[:3])\n", "\n", "# Cut new roadway geometries from the parent LRS layer\n", "cut = roadways.lr.relate(dissolved).cut()\n", "print(list(roadways.geometry_m)[:3])\n", "print(list(cut)[:3])" ] }, { "cell_type": "markdown", "id": "37d69cf4", "metadata": {}, "source": [ "## Integrating Multiple Datasets\n", "\n", "We can combine multiple event datasets into a single, unified dataframe using the `integrate` function. This analyzes a list of passed linearly referenced dataframes, creating a single version featuring the least common event intervals among them. New events can be tabularly joined back to each of their source dataframes using the created `integrated_index_[#]` columns." ] }, { "cell_type": "code", "execution_count": 7, "id": "724af0bd", "metadata": { "execution": { "iopub.execute_input": "2026-05-29T16:42:52.375815Z", "iopub.status.busy": "2026-05-29T16:42:52.375551Z", "iopub.status.idle": "2026-05-29T16:42:52.394426Z", "shell.execute_reply": "2026-05-29T16:42:52.394016Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " route beg end integrated_index_0 integrated_index_1\n", "4 SR-1 0.0 1.6 4.0 6.0\n", "5 SR-1 1.6 3.2 4.0 7.0\n", "6 SR-1 3.2 6.5 5.0 8.0\n", "7 SR-1 6.5 9.8 6.0 9.0\n" ] } ], "source": [ "# Combine multiple datasets with a matching LRS\n", "integrated = lr.integrate([roadways, pavement])\n", "\n", "print(integrated.lr.get_group('SR-1'))" ] }, { "cell_type": "markdown", "id": "1838b6fe", "metadata": {}, "source": [ "New geometries can be cut for these intervals by relating the integrated dataframe back to spatial dataframe on the same LRS." ] }, { "cell_type": "code", "execution_count": 8, "id": "ac10d1a3", "metadata": { "execution": { "iopub.execute_input": "2026-05-29T16:42:52.395798Z", "iopub.status.busy": "2026-05-29T16:42:52.395569Z", "iopub.status.idle": "2026-05-29T16:42:52.411597Z", "shell.execute_reply": "2026-05-29T16:42:52.411074Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Index(['route', 'beg', 'end', 'integrated_index_0', 'integrated_index_1',\n", " 'geometry_m', 'geometry'],\n", " dtype='object')\n" ] } ], "source": [ "# Relate the integrated dataset back to the roadways layer\n", "integrated['geometry_m'] = integrated.lr.relate(roadways).cut()\n", "\n", "# A short-hand version of this pattern is available with the cut_from method\n", "# This retrieves both M-enabled and non-M-enabled geometries in a single line\n", "integrated.lr.cut_from(roadways, inplace=True)\n", "\n", "print(integrated.columns)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "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.12.3" } }, "nbformat": 4, "nbformat_minor": 5 }