Skip to content

Examples

For a complete, runnable visualization, see the map example.

Missing edge costs

model = SpatialDeformer(
    cost="travel_time_s",
    missing_cost="median",
    impute_by="road_class",
)
warped = model.fit_transform(edges)

print(model.imputed_edges_)
print(model.costs_)

The map example colors imputed segments separately so data quality remains visible in exported figures.

Cost column or y

by_column = SpatialDeformer(cost="travel_time_s").fit(edges)
by_array = SpatialDeformer(cost="unused").fit(edges, y=travel_times)

Endpoint inference

model = SpatialDeformer(
    source=None,
    target=None,
    cost="minutes",
    node_precision=7,
)
warped = model.fit_transform(edge_geodataframe)

Local spatial weighting

model = SpatialDeformer(
    backend="mds",
    spatial_weight="gaussian",
    spatial_bandwidth=10_000,
    geo_weight=0.05,
)

Directed travel costs

model = SpatialDeformer(
    directed=True,
    directed_policy="mean",
    cost="directed_time_s",
)

mean represents typical reciprocal travel. min favors the quicker direction; max favors the slower direction. All three produce a symmetric distance matrix because ordinary 2D Euclidean distance is symmetric.

Transform another geometry layer

After fitting, the learned displacement field can transform another compatible line GeoDataFrame in the same coordinate region:

model.fit(network_edges)
warped_routes = model.transform(route_geometries)

The second GeoDataFrame does not need cost columns. It must contain supported line geometries and use a compatible CRS.

Export fitted nodes

nodes_wgs84 = model.get_nodes(crs="EPSG:4326")
nodes_wgs84.to_file("deformed_nodes.geojson", driver="GeoJSON")

Spatial readability is partly qualitative, but a small parameter study can generate candidates:

from sklearn.base import clone

base = SpatialDeformer(cost="travel_time_s")
candidates = []

for geo_weight in [0.01, 0.05, 0.1, 0.5]:
    for spatial_weight in ["uniform", "gaussian"]:
        model = clone(base).set_params(
            geo_weight=geo_weight,
            spatial_weight=spatial_weight,
        )
        model.fit(edges)
        candidates.append((model.normalized_stress_, model))

for stress, model in sorted(candidates, key=lambda item: item[0]):
    print(stress, model.get_params())

Do not select solely by stress. Review geographic recognizability and line crossing behavior as well.