Relating Events
This notebook covers events-based conflation, aggregation, geometry creation, and dataset integration.
[1]:
import linref as lr
# Load sample datasets with LRS pre-configured
roadways = lr.datasets.load('roadways', set_lrs=True)
crashes = lr.datasets.load('crashes', set_lrs=True)
pavement = lr.datasets.load('pavement', set_lrs=True)
[2]:
# Prepare data: dissolve and resegment roadways
dissolved = roadways.lr.dissolve()
resegmented = dissolved.lr.resegment(length=5, fill='cut')
Conflating Point Data onto Linear Data
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.
[3]:
# Conflating point data onto linear data
# left_df right_df <-- relations use terminology of left and right
relation = resegmented.lr.relate(crashes)
# Dataframe-level aggregations like count are easy
print(relation.count()) # Count the number of crashes on each roadway segment
# Aggregator results are designed to be assigned as new columns to the left dataframe
resegmented['crash_counts'] = relation.count()
print(resegmented.head(3)[['route', 'beg', 'end', 'crash_counts']])
[1 2 1 4 3 5 4 0]
route beg end crash_counts
0 I-5 0.0 5.0 1
1 I-5 5.0 10.0 2
2 I-5 10.0 12.0 1
Column-level aggregators can be accessed using column indexing on the relation instance, using a syntax similar to the pandas groupby syntax:
[4]:
# List of unique values (creates a single column)
resegmented['crash_ids'] = relation['crash_id'].list()
# Apply an aggregator to multiple columns at once
resegmented[['crash_ids', 'severities']] = relation[['crash_id', 'severity']].list()
# Counts of unique values (creates a number of columns equal to the number of unique values)
severities = ['Fatal', 'Injury', 'Property Damage Only']
resegmented[severities] = relation['severity'].value_counts()[severities] # Returned as a dataframe
print(resegmented.head(3)[['route', 'beg', 'end', 'crash_ids', 'severities'] + severities])
route beg end crash_ids severities Fatal Injury \
0 I-5 0.0 5.0 [3] [Injury] 0.0 1.0
1 I-5 5.0 10.0 [5, 6] [Fatal, Injury] 1.0 1.0
2 I-5 10.0 12.0 [13] [Injury] 0.0 1.0
Property Damage Only
0 0.0
1 0.0
2 0.0
Conflating Linear Data onto Linear Data
[5]:
# Conflating linear data onto linear data
relation = resegmented.lr.relate(pavement)
# Length-weighted mean of numerical data
resegmented['condition_rating'] = relation['condition_rating'].mean().round(3)
resegmented['condition_ratings'] = relation['condition_rating'].list()
print(resegmented.head(3)[['route', 'beg', 'end', 'condition_rating', 'condition_ratings']])
# Length-weighted mode of categorical data
resegmented['surface_type'] = relation['surface_type'].mode()
resegmented['surface_types'] = relation['surface_type'].list()
print(resegmented.head(3)[['route', 'beg', 'end', 'surface_type', 'surface_types']])
route beg end condition_rating condition_ratings
0 I-5 0.0 5.0 81.900 [84, 77]
1 I-5 5.0 10.0 81.086 [77, 83, 82]
2 I-5 10.0 12.0 82.000 [82]
route beg end surface_type surface_types
0 I-5 0.0 5.0 Asphalt [Asphalt, Concrete]
1 I-5 5.0 10.0 Concrete [Concrete, Asphalt, Concrete]
2 I-5 10.0 12.0 Concrete [Concrete]
Creating New Geometries from Relations
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.
[6]:
# Add m-enabled geometries to the roadways layer
roadways = roadways.lr.add_geom_m()
# Interpolate new crash geometries based on their LRS information
interpolated = crashes.lr.relate(roadways).interpolate()
print(list(crashes.geometry)[:3])
print(list(interpolated)[:3])
# Cut new roadway geometries from the parent LRS layer
cut = roadways.lr.relate(dissolved).cut()
print(list(roadways.geometry_m)[:3])
print(list(cut)[:3])
[<POINT (2.56 0.52)>, <POINT (8.18 2.15)>, <POINT (4.47 11.13)>]
[<POINT (2.56 0.517)>, <POINT (8.18 2.155)>, <POINT (4.47 11.132)>]
[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]
[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]
Integrating Multiple Datasets
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.
[7]:
# Combine multiple datasets with a matching LRS
integrated = lr.integrate([roadways, pavement])
print(integrated.lr.get_group('SR-1'))
route beg end integrated_index_0 integrated_index_1
4 SR-1 0.0 1.6 4.0 6.0
5 SR-1 1.6 3.2 4.0 7.0
6 SR-1 3.2 6.5 5.0 8.0
7 SR-1 6.5 9.8 6.0 9.0
New geometries can be cut for these intervals by relating the integrated dataframe back to spatial dataframe on the same LRS.
[8]:
# Relate the integrated dataset back to the roadways layer
integrated['geometry_m'] = integrated.lr.relate(roadways).cut()
# A short-hand version of this pattern is available with the cut_from method
# This retrieves both M-enabled and non-M-enabled geometries in a single line
integrated.lr.cut_from(roadways, inplace=True)
print(integrated.columns)
Index(['route', 'beg', 'end', 'integrated_index_0', 'integrated_index_1',
'geometry_m', 'geometry'],
dtype='object')