sameer-graph-lib
sameer-graph-lib is an editable Python library for building H3-based route affinity graphs with NetworkX.
It turns H3 arrays, latitude/longitude sequences, and encoded polylines into connected hex chains, inserts them into a weighted graph, extracts high-affinity corridors, and decomposes the graph into a main trunk plus minor branches.
Editable install
python -m pip install -e ".[dev,plot]"
Because the install is editable, changes you make inside src/sameer_graph_lib are picked up immediately by Python without reinstalling.
If your machine uses uv, run commands through the managed environment:
uv run --extra dev --extra plot python -c "import sameer_graph_lib; print(sameer_graph_lib.__version__)"
Install
From PyPI after publication:
pip install sameer-graph-lib
With optional plotting and geospatial extras:
pip install "sameer-graph-lib[plot,geo]"
For the route flow graph and the DataFrame plotter (pandas, numpy, xarray, matplotlib):
pip install "sameer-graph-lib[analysis]"
Quick start
from sameer_graph_lib import HexGraph
graph = HexGraph(hex_resolution=9)
route = graph.add_latlng_sequence([
(12.9716, 77.5946),
(12.9760, 77.5990),
])
print(graph.get_graph_stats())
selected = graph.get_appropriate_hexes(cutoff=0.8)
fig = graph.visualize_graph(title="80% compact cluster", highlight_hexes=selected)
print(graph.decompose_branches())
Main APIs
SpatialIngestor: converts H3 arrays, lat/lng sequences, and encoded polylines into contiguous H3 chains.AffinityGraph: NetworkX wrapper for array-based insertion, nearest attachment, affinity scoring, editing, and JSON persistence.CorridorExtractor: uses exact all-node Dijkstra selection to extract the most compact cluster covering a target percentage of graph traversal volume.TopologyAnalyzer: separates the main branch from residual minor branches.HexGraph: backwards-compatible convenience class for your original code style.RouteSchema/RouteTensor/RouteGraph: grain-aware pickup -> drop graph where each edge carries a metric cube (see Route flow graphs).RouteExplorer: the one-liner front door to the route graph - build from a DataFrame, rank partners, and plot the flow around a cluster.plotter: stateless DataFrame plotting - multi-column, multi-axis, multi-group, plus distribution checks.
Graph creation follows the original per-node procedure: H3 arrays are normalized, then each hex is inserted with add_node/add_hex. Lat/lng sequences and encoded polylines are first converted into H3 arrays at the requested resolution, then inserted the same way.
For QC, use:
fig = graph.visualize_graph(highlight_hexes=selected)
fig.savefig("graph_qc.png", dpi=150, bbox_inches="tight")
fig = graph.visualize_step_by_step(route[:5], labels=["A", "B", "C", "D", "E"])
fig.savefig("insertion_steps.png", dpi=150, bbox_inches="tight")
To plot actual H3 hex boundaries as geospatial polygons:
from sameer_graph_lib import plot_h3_cells, plot_h3_cells_map
fig = plot_h3_cells("88618c4f29fffff", label_full_hex=True)
fig.savefig("single_h3_cell.png", dpi=150, bbox_inches="tight")
fig = graph.plot_h3_cells(highlight_hexes=selected, show_labels=False)
fig.savefig("h3_cell_footprint.png", dpi=150, bbox_inches="tight")
fig = plot_h3_cells_map(route, selected_cells=selected)
fig.savefig("h3_cell_basemap.png", dpi=150, bbox_inches="tight")
plot_h3_cells_map uses GeoPandas + Contextily. Install it with:
python -m pip install -e ".[plot,geo]"
uv run --extra plot --extra geo python -c "from sameer_graph_lib import plot_h3_cells_map"
To get H3 centers and a convex hull:
from sameer_graph_lib import getLatLng, h3_convex_hull
points = getLatLng(route) # [(lat, lng), ...]
hull = h3_convex_hull(route) # Shapely geometry in (lng, lat)
graph_hull = graph.convex_hull() # Same, using graph nodes
Route flow graphs (pickup -> drop)
The second half of the library works on tabular route data rather than H3
chains: one row per pickup_cluster x drop_cluster x week_period x hour, with
whatever metric columns you export. Every edge stores a tensor - a dense
cube over the grain dimensions - so sums stay exact, averages stay correctly
weighted, and two tensors can be merged without re-reading the source rows.
The metric columns are yours: the schema reads whatever numeric columns the
frame has. Names that look like averages (avg_, _rate, pct, ratio, ...)
are weighted-averaged, the rest are summed, and grain/id/categorical columns are
never treated as metrics. Pass sum_metrics= / mean_metrics= to override the
split, and metric= anywhere to choose the variable; with nothing named, calls
fall back to schema.default_metric (the first sum metric).
Install the extra:
python -m pip install -e ".[analysis]" # pandas, numpy, xarray, matplotlib
Quick start
from sameer_graph_lib import RouteExplorer
ex = RouteExplorer(df, metric="orders") # metric= is optional
ex.sources("A1", top=5) # where A1 orders come from
ex.drops("A1", top=5) # where A1 orders go
ex.route("A1", "B1") # every metric for one route
ex.summary("A1") # every metric for one cluster
print(ex.describe())
where and using return a new view that shares the same graph, so you can
narrow the question without rebuilding anything:
peak = ex.where(week_period="weekday", hour=[8, 9, 10]).using("speed")
peak.drops("A1", top=5)
Plotting the flow around a cluster
plot puts the focus clusters in the middle, the clusters that feed them on
the left, and the clusters they feed on the right. The per-level top-k is the
main control: pass an int for one level, or a list for one number per level.
# top 5 sources, then the top 3 sources of each of those, then the top 2;
# and on the other side, the top 4 drops and the top 2 of each.
fig = ex.plot("A1", upstream=[5, 3, 2], downstream=[4, 2])
# several focus clusters, plus the routes between the selected set
ex.plot(["A1", "C1"], upstream=3, downstream=3, include_cross_edges=True)
# rank by one variable, show several: every node and edge carries a table
ex.plot("A1", metric="orders",
node_metrics=["orders", "accepted_orders", "requests", "speed"],
edge_metrics=["orders", "speed", "avg_distance"],
hour=[8, 9, 10])
node_metrics and edge_metrics take any number of variables. They are drawn
as a small aligned table under each node and on each edge, with the metric
names on the left and the values right-aligned; every table in one figure
shares its column widths, and the figure sizes itself so they do not collide.
Numbers are formatted per value by default (62,709 and 0.273 both read
correctly); pass value_format="{:,.1f}" for one format, or a dict such as
value_format={"speed": "{:.3f}"} per metric.
Other options: size_metric= (which variable drives node size),
show_metric_names=False (values only), per_parent=False (rank a whole level
globally instead of per parent), min_value=, layout="layered" | "geo" | "spring" (geo places H3 cluster ids at their real coordinates),
show_edge_values, label_cross_edges, curve, figsize, and the colours.
The same expansion is available without plotting:
ex.flow("A1", upstream=[5, 3], downstream=4) # tidy DataFrame
ex.subgraph("A1", upstream=2, downstream=2) # nx.DiGraph, levels on nodes
print(ex.tree("A1", upstream=[5, 3], downstream=4))
A1 (orders)
sources (orders coming in)
|-- <- C1 [8,523.4]
| |-- <- D3 [8,802.4]
| +-- <- D2 [3,646.5]
+-- <- C3 [7,041.1]
drops (orders going out)
|-- -> B4 [4,574.9]
+-- -> B5 [4,446.4]
Other route views
| Call | What you get |
|---|---|
ex.plot_partners("A1", top=10) |
top sources and top drops as back-to-back bars on one shared scale |
ex.plot_profile(cluster="A1") |
week_period x hour heatmap for a cluster or a route |
ex.plot_matrix(top=15) |
pickup x drop heatmap |
ex.plot_graph(top_routes=30) |
the whole graph as a ranked ring, sized and coloured by a metric |
ex.clusters_frame() |
per cluster inbound vs outbound volume and net balance |
ex.top_routes(20) |
routes ranked by any metric |
ex.matrix() / ex.frame() |
pivot table / long per-bucket frame |
The lower-level classes are public too: RouteSchema (grains and metric
columns), RouteTensor (one route's metric cube, with summary, totals,
profile, merge) and RouteGraph (the NetworkX graph, with partners,
node_tensor, shortest_route, flow_subgraph, as_dataset).
DataFrame plotting
sameer_graph_lib.plotter is a stateless plotting layer for any DataFrame -
each function takes a frame and returns a figure.
from sameer_graph_lib import plotter
# n columns, one panel each, test vs control overlaid in every panel
plotter.plot_columns(df, ["orders", "speed", "aor"], x="hour", group="variant",
agg="mean", kind="line", mode="grid")
# columns with different scales on one chart, one y-axis each
plotter.plot_multi_axis(df, ["orders", "speed"], x="hour", agg="mean")
# distribution of a column, with count/mean/median/sd/skew in the corner
plotter.plot_distribution(df, "speed", kind="hist", bins=40)
# the same distribution per group, one colour per line, on one chart
plotter.compare_distributions(df, "speed", group="variant", kind="kde", with_box=True)
# A/B readout: aggregate per group, optionally relative to a baseline
plotter.compare_groups(df, ["orders", "speed"], group="variant", agg="mean",
normalize_to="control")
plotter.describe_distribution(df, "speed", group="variant") # stats table
plotter.plot_correlation(df, ["orders", "speed", "distance"])
mode is grid (one panel per column), overlay (one shared axes) or twin
(one y-axis per column). kind is line, step, area, bar, barh,
scatter, hist, kde, box, violin or ecdf. group splits every
series by a categorical column and gives each level its own colour. KDE and
ECDF are computed with numpy, so scipy is not required.
The explorer exposes the same helpers over its own long frame:
ex.plot_columns(["orders", "speed"], x="hour", group="week_period", agg="mean")
ex.compare_distributions("speed", "week_period")
Useful commands
python -m pytest
python -m build
uv run --extra dev pytest -q
uv run --extra dev python -m build
uv run --extra dev python -m twine check dist/*
Build artifacts will appear in dist/ after python -m build.
Publish To PyPI
- Build the package:
uv run --extra dev python -m build
- Validate the package metadata:
uv run --extra dev python -m twine check dist/*
- Upload to PyPI:
uv run --extra dev python -m twine upload dist/*
After upload, users can install it with:
pip install sameer-graph-lib
Publish From GitHub
This repo also includes a Trusted Publishing workflow in .github/workflows/publish.yml.
To finish that setup:
- Create the project on PyPI, or reserve the name
sameer-graph-lib. - On PyPI, open the project settings and add a Trusted Publisher for:
owner:iams31repository:sameer_graph_libworkflow:publish.ymlenvironment:pypi - Create a GitHub Release, or run the workflow manually from the Actions tab.
After that, GitHub Actions can publish without storing a long-lived PyPI token.
Official references:
- PyPI Trusted Publishing: https://docs.pypi.org/trusted-publishers/
- Packaging guide upload flow: https://packaging.python.org/tutorials/packaging-projects/
Metadata
Release files for sameer-graph-lib 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| sameer_graph_lib-0.3.0.tar.gz | 70.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| sameer_graph_lib-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 133.6 kB
Release files / sameer_graph_lib-0.3.0.tar.gz
| Download URL | sameer_graph_lib-0.3.0.tar.gz |
|---|---|
| Size | 70.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
480d2f6cd70b722fb017f9898c00c73b6151f3df3fcd51663354491e86128fda
|
|
BLAKE2b-256 checksum How to use checksums |
f03136f87abc8cfa5e7db9d0fc6042f6208da34b7ae4be6485c8f50b985dfcb3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 13, 2026.
Transparency logRelease files / sameer_graph_lib-0.3.0-py3-none-any.whl
| Download URL | sameer_graph_lib-0.3.0-py3-none-any.whl |
|---|---|
| Size | 63.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5abae8b69017e15513b0239dbea8b125c148eb6ca61f84f6d31c25596e5375c1
|
|
BLAKE2b-256 checksum How to use checksums |
082737ca61b85344917b7d60b83936982001f178aee826485254b7bf5d3065d4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 13, 2026.
Transparency log