sameer-graph-lib 0.2.0__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. sameer_graph_lib-0.3.0/PKG-INFO +376 -0
  2. sameer_graph_lib-0.3.0/README.md +329 -0
  3. {sameer_graph_lib-0.2.0 → sameer_graph_lib-0.3.0}/pyproject.toml +45 -43
  4. sameer_graph_lib-0.3.0/src/sameer_graph_lib/__init__.py +130 -0
  5. sameer_graph_lib-0.3.0/src/sameer_graph_lib/plotter.py +780 -0
  6. sameer_graph_lib-0.3.0/src/sameer_graph_lib/route_explorer.py +318 -0
  7. sameer_graph_lib-0.3.0/src/sameer_graph_lib/route_graph.py +1019 -0
  8. sameer_graph_lib-0.3.0/src/sameer_graph_lib/route_viz.py +777 -0
  9. sameer_graph_lib-0.3.0/src/sameer_graph_lib.egg-info/PKG-INFO +376 -0
  10. {sameer_graph_lib-0.2.0 → sameer_graph_lib-0.3.0}/src/sameer_graph_lib.egg-info/SOURCES.txt +7 -0
  11. {sameer_graph_lib-0.2.0 → sameer_graph_lib-0.3.0}/src/sameer_graph_lib.egg-info/requires.txt +11 -0
  12. sameer_graph_lib-0.3.0/tests/test_plotter.py +169 -0
  13. sameer_graph_lib-0.3.0/tests/test_route_graph.py +246 -0
  14. sameer_graph_lib-0.3.0/tests/test_route_viz.py +224 -0
  15. sameer_graph_lib-0.2.0/PKG-INFO +0 -201
  16. sameer_graph_lib-0.2.0/README.md +0 -163
  17. sameer_graph_lib-0.2.0/src/sameer_graph_lib/__init__.py +0 -53
  18. sameer_graph_lib-0.2.0/src/sameer_graph_lib.egg-info/PKG-INFO +0 -201
  19. {sameer_graph_lib-0.2.0 → sameer_graph_lib-0.3.0}/LICENSE +0 -0
  20. {sameer_graph_lib-0.2.0 → sameer_graph_lib-0.3.0}/setup.cfg +0 -0
  21. {sameer_graph_lib-0.2.0 → sameer_graph_lib-0.3.0}/src/sameer_graph_lib/_h3.py +0 -0
  22. {sameer_graph_lib-0.2.0 → sameer_graph_lib-0.3.0}/src/sameer_graph_lib/affinity_graph.py +0 -0
  23. {sameer_graph_lib-0.2.0 → sameer_graph_lib-0.3.0}/src/sameer_graph_lib/corridor_extractor.py +0 -0
  24. {sameer_graph_lib-0.2.0 → sameer_graph_lib-0.3.0}/src/sameer_graph_lib/geometry.py +0 -0
  25. {sameer_graph_lib-0.2.0 → sameer_graph_lib-0.3.0}/src/sameer_graph_lib/hex_graph.py +0 -0
  26. {sameer_graph_lib-0.2.0 → sameer_graph_lib-0.3.0}/src/sameer_graph_lib/motion.py +0 -0
  27. {sameer_graph_lib-0.2.0 → sameer_graph_lib-0.3.0}/src/sameer_graph_lib/plotting.py +0 -0
  28. {sameer_graph_lib-0.2.0 → sameer_graph_lib-0.3.0}/src/sameer_graph_lib/plylinedecoding.py +0 -0
  29. {sameer_graph_lib-0.2.0 → sameer_graph_lib-0.3.0}/src/sameer_graph_lib/spatial_ingestor.py +0 -0
  30. {sameer_graph_lib-0.2.0 → sameer_graph_lib-0.3.0}/src/sameer_graph_lib/topology_analyzer.py +0 -0
  31. {sameer_graph_lib-0.2.0 → sameer_graph_lib-0.3.0}/src/sameer_graph_lib.egg-info/dependency_links.txt +0 -0
  32. {sameer_graph_lib-0.2.0 → sameer_graph_lib-0.3.0}/src/sameer_graph_lib.egg-info/top_level.txt +0 -0
  33. {sameer_graph_lib-0.2.0 → sameer_graph_lib-0.3.0}/tests/test_geometry.py +0 -0
  34. {sameer_graph_lib-0.2.0 → sameer_graph_lib-0.3.0}/tests/test_h3_cell_plotting.py +0 -0
  35. {sameer_graph_lib-0.2.0 → sameer_graph_lib-0.3.0}/tests/test_h3_cells_map_plotting.py +0 -0
  36. {sameer_graph_lib-0.2.0 → sameer_graph_lib-0.3.0}/tests/test_h3_grid_distance.py +0 -0
  37. {sameer_graph_lib-0.2.0 → sameer_graph_lib-0.3.0}/tests/test_plotting.py +0 -0
  38. {sameer_graph_lib-0.2.0 → sameer_graph_lib-0.3.0}/tests/test_smoke.py +0 -0
@@ -0,0 +1,376 @@
1
+ Metadata-Version: 2.4
2
+ Name: sameer-graph-lib
3
+ Version: 0.3.0
4
+ Summary: H3 and NetworkX based route affinity, route-flow and plotting toolkit.
5
+ Author-email: Sameer <sameerkumarroy073@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/iams31/sameer_graph_lib
8
+ Project-URL: Repository, https://github.com/iams31/sameer_graph_lib
9
+ Project-URL: Issues, https://github.com/iams31/sameer_graph_lib/issues
10
+ Keywords: h3,networkx,geospatial,routing,graph,visualization,pandas
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Scientific/Engineering :: GIS
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: h3>=4.0.0
22
+ Requires-Dist: networkx>=3.0
23
+ Requires-Dist: polyline>=2.0
24
+ Provides-Extra: plot
25
+ Requires-Dist: matplotlib>=3.7; extra == "plot"
26
+ Provides-Extra: geo
27
+ Requires-Dist: contextily>=1.5; extra == "geo"
28
+ Requires-Dist: geopandas>=0.14; extra == "geo"
29
+ Requires-Dist: shapely>=2.0; extra == "geo"
30
+ Provides-Extra: motion
31
+ Requires-Dist: numpy>=1.24; extra == "motion"
32
+ Requires-Dist: pyproj>=3.5; extra == "motion"
33
+ Provides-Extra: route
34
+ Requires-Dist: pandas>=2.0; extra == "route"
35
+ Requires-Dist: numpy>=1.24; extra == "route"
36
+ Requires-Dist: xarray>=2023.1; extra == "route"
37
+ Provides-Extra: analysis
38
+ Requires-Dist: pandas>=2.0; extra == "analysis"
39
+ Requires-Dist: numpy>=1.24; extra == "analysis"
40
+ Requires-Dist: xarray>=2023.1; extra == "analysis"
41
+ Requires-Dist: matplotlib>=3.7; extra == "analysis"
42
+ Provides-Extra: dev
43
+ Requires-Dist: pytest>=8.0; extra == "dev"
44
+ Requires-Dist: build>=1.2; extra == "dev"
45
+ Requires-Dist: twine>=5.1; extra == "dev"
46
+ Dynamic: license-file
47
+
48
+ # sameer-graph-lib
49
+
50
+ `sameer-graph-lib` is an editable Python library for building H3-based route affinity graphs with NetworkX.
51
+
52
+ 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.
53
+
54
+ ## Editable install
55
+
56
+ ```powershell
57
+ python -m pip install -e ".[dev,plot]"
58
+ ```
59
+
60
+ Because the install is editable, changes you make inside `src/sameer_graph_lib` are picked up immediately by Python without reinstalling.
61
+
62
+ If your machine uses `uv`, run commands through the managed environment:
63
+
64
+ ```powershell
65
+ uv run --extra dev --extra plot python -c "import sameer_graph_lib; print(sameer_graph_lib.__version__)"
66
+ ```
67
+
68
+ ## Install
69
+
70
+ From PyPI after publication:
71
+
72
+ ```powershell
73
+ pip install sameer-graph-lib
74
+ ```
75
+
76
+ With optional plotting and geospatial extras:
77
+
78
+ ```powershell
79
+ pip install "sameer-graph-lib[plot,geo]"
80
+ ```
81
+
82
+ For the route flow graph and the DataFrame plotter (pandas, numpy, xarray, matplotlib):
83
+
84
+ ```powershell
85
+ pip install "sameer-graph-lib[analysis]"
86
+ ```
87
+
88
+ ## Quick start
89
+
90
+ ```python
91
+ from sameer_graph_lib import HexGraph
92
+
93
+ graph = HexGraph(hex_resolution=9)
94
+
95
+ route = graph.add_latlng_sequence([
96
+ (12.9716, 77.5946),
97
+ (12.9760, 77.5990),
98
+ ])
99
+
100
+ print(graph.get_graph_stats())
101
+ selected = graph.get_appropriate_hexes(cutoff=0.8)
102
+ fig = graph.visualize_graph(title="80% compact cluster", highlight_hexes=selected)
103
+ print(graph.decompose_branches())
104
+ ```
105
+
106
+ ## Main APIs
107
+
108
+ - `SpatialIngestor`: converts H3 arrays, lat/lng sequences, and encoded polylines into contiguous H3 chains.
109
+ - `AffinityGraph`: NetworkX wrapper for array-based insertion, nearest attachment, affinity scoring, editing, and JSON persistence.
110
+ - `CorridorExtractor`: uses exact all-node Dijkstra selection to extract the most compact cluster covering a target percentage of graph traversal volume.
111
+ - `TopologyAnalyzer`: separates the main branch from residual minor branches.
112
+ - `HexGraph`: backwards-compatible convenience class for your original code style.
113
+ - `RouteSchema` / `RouteTensor` / `RouteGraph`: grain-aware pickup -> drop graph where each edge carries a metric cube (see [Route flow graphs](#route-flow-graphs-pickup---drop)).
114
+ - `RouteExplorer`: the one-liner front door to the route graph - build from a DataFrame, rank partners, and plot the flow around a cluster.
115
+ - `plotter`: stateless DataFrame plotting - multi-column, multi-axis, multi-group, plus distribution checks.
116
+
117
+ 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.
118
+
119
+ For QC, use:
120
+
121
+ ```python
122
+ fig = graph.visualize_graph(highlight_hexes=selected)
123
+ fig.savefig("graph_qc.png", dpi=150, bbox_inches="tight")
124
+
125
+ fig = graph.visualize_step_by_step(route[:5], labels=["A", "B", "C", "D", "E"])
126
+ fig.savefig("insertion_steps.png", dpi=150, bbox_inches="tight")
127
+ ```
128
+
129
+ To plot actual H3 hex boundaries as geospatial polygons:
130
+
131
+ ```python
132
+ from sameer_graph_lib import plot_h3_cells, plot_h3_cells_map
133
+
134
+ fig = plot_h3_cells("88618c4f29fffff", label_full_hex=True)
135
+ fig.savefig("single_h3_cell.png", dpi=150, bbox_inches="tight")
136
+
137
+ fig = graph.plot_h3_cells(highlight_hexes=selected, show_labels=False)
138
+ fig.savefig("h3_cell_footprint.png", dpi=150, bbox_inches="tight")
139
+
140
+ fig = plot_h3_cells_map(route, selected_cells=selected)
141
+ fig.savefig("h3_cell_basemap.png", dpi=150, bbox_inches="tight")
142
+ ```
143
+
144
+ `plot_h3_cells_map` uses GeoPandas + Contextily. Install it with:
145
+
146
+ ```powershell
147
+ python -m pip install -e ".[plot,geo]"
148
+ uv run --extra plot --extra geo python -c "from sameer_graph_lib import plot_h3_cells_map"
149
+ ```
150
+
151
+ To get H3 centers and a convex hull:
152
+
153
+ ```python
154
+ from sameer_graph_lib import getLatLng, h3_convex_hull
155
+
156
+ points = getLatLng(route) # [(lat, lng), ...]
157
+ hull = h3_convex_hull(route) # Shapely geometry in (lng, lat)
158
+ graph_hull = graph.convex_hull() # Same, using graph nodes
159
+ ```
160
+
161
+ ## Route flow graphs (pickup -> drop)
162
+
163
+ The second half of the library works on tabular route data rather than H3
164
+ chains: one row per `pickup_cluster x drop_cluster x week_period x hour`, with
165
+ whatever metric columns you export. Every edge stores a **tensor** - a dense
166
+ cube over the grain dimensions - so sums stay exact, averages stay correctly
167
+ weighted, and two tensors can be merged without re-reading the source rows.
168
+
169
+ The metric columns are **yours**: the schema reads whatever numeric columns the
170
+ frame has. Names that look like averages (`avg_`, `_rate`, `pct`, `ratio`, ...)
171
+ are weighted-averaged, the rest are summed, and grain/id/categorical columns are
172
+ never treated as metrics. Pass `sum_metrics=` / `mean_metrics=` to override the
173
+ split, and `metric=` anywhere to choose the variable; with nothing named, calls
174
+ fall back to `schema.default_metric` (the first sum metric).
175
+
176
+ Install the extra:
177
+
178
+ ```powershell
179
+ python -m pip install -e ".[analysis]" # pandas, numpy, xarray, matplotlib
180
+ ```
181
+
182
+ ### Quick start
183
+
184
+ ```python
185
+ from sameer_graph_lib import RouteExplorer
186
+
187
+ ex = RouteExplorer(df, metric="orders") # metric= is optional
188
+
189
+ ex.sources("A1", top=5) # where A1 orders come from
190
+ ex.drops("A1", top=5) # where A1 orders go
191
+ ex.route("A1", "B1") # every metric for one route
192
+ ex.summary("A1") # every metric for one cluster
193
+ print(ex.describe())
194
+ ```
195
+
196
+ `where` and `using` return a new view that shares the same graph, so you can
197
+ narrow the question without rebuilding anything:
198
+
199
+ ```python
200
+ peak = ex.where(week_period="weekday", hour=[8, 9, 10]).using("speed")
201
+ peak.drops("A1", top=5)
202
+ ```
203
+
204
+ ### Plotting the flow around a cluster
205
+
206
+ `plot` puts the focus clusters in the middle, the clusters that feed them on
207
+ the left, and the clusters they feed on the right. The per-level top-k is the
208
+ main control: pass an int for one level, or a list for one number per level.
209
+
210
+ ```python
211
+ # top 5 sources, then the top 3 sources of each of those, then the top 2;
212
+ # and on the other side, the top 4 drops and the top 2 of each.
213
+ fig = ex.plot("A1", upstream=[5, 3, 2], downstream=[4, 2])
214
+
215
+ # several focus clusters, plus the routes between the selected set
216
+ ex.plot(["A1", "C1"], upstream=3, downstream=3, include_cross_edges=True)
217
+
218
+ # rank by one variable, show several: every node and edge carries a table
219
+ ex.plot("A1", metric="orders",
220
+ node_metrics=["orders", "accepted_orders", "requests", "speed"],
221
+ edge_metrics=["orders", "speed", "avg_distance"],
222
+ hour=[8, 9, 10])
223
+ ```
224
+
225
+ `node_metrics` and `edge_metrics` take any number of variables. They are drawn
226
+ as a small aligned table under each node and on each edge, with the metric
227
+ names on the left and the values right-aligned; every table in one figure
228
+ shares its column widths, and the figure sizes itself so they do not collide.
229
+ Numbers are formatted per value by default (62,709 and 0.273 both read
230
+ correctly); pass `value_format="{:,.1f}"` for one format, or a dict such as
231
+ `value_format={"speed": "{:.3f}"}` per metric.
232
+
233
+ Other options: `size_metric=` (which variable drives node size),
234
+ `show_metric_names=False` (values only), `per_parent=False` (rank a whole level
235
+ globally instead of per parent), `min_value=`, `layout="layered" | "geo" |
236
+ "spring"` (`geo` places H3 cluster ids at their real coordinates),
237
+ `show_edge_values`, `label_cross_edges`, `curve`, `figsize`, and the colours.
238
+
239
+ The same expansion is available without plotting:
240
+
241
+ ```python
242
+ ex.flow("A1", upstream=[5, 3], downstream=4) # tidy DataFrame
243
+ ex.subgraph("A1", upstream=2, downstream=2) # nx.DiGraph, levels on nodes
244
+ print(ex.tree("A1", upstream=[5, 3], downstream=4))
245
+ ```
246
+
247
+ ```text
248
+ A1 (orders)
249
+ sources (orders coming in)
250
+ |-- <- C1 [8,523.4]
251
+ | |-- <- D3 [8,802.4]
252
+ | +-- <- D2 [3,646.5]
253
+ +-- <- C3 [7,041.1]
254
+ drops (orders going out)
255
+ |-- -> B4 [4,574.9]
256
+ +-- -> B5 [4,446.4]
257
+ ```
258
+
259
+ ### Other route views
260
+
261
+ | Call | What you get |
262
+ | --- | --- |
263
+ | `ex.plot_partners("A1", top=10)` | top sources and top drops as back-to-back bars on one shared scale |
264
+ | `ex.plot_profile(cluster="A1")` | week_period x hour heatmap for a cluster or a route |
265
+ | `ex.plot_matrix(top=15)` | pickup x drop heatmap |
266
+ | `ex.plot_graph(top_routes=30)` | the whole graph as a ranked ring, sized and coloured by a metric |
267
+ | `ex.clusters_frame()` | per cluster inbound vs outbound volume and net balance |
268
+ | `ex.top_routes(20)` | routes ranked by any metric |
269
+ | `ex.matrix()` / `ex.frame()` | pivot table / long per-bucket frame |
270
+
271
+ The lower-level classes are public too: `RouteSchema` (grains and metric
272
+ columns), `RouteTensor` (one route's metric cube, with `summary`, `totals`,
273
+ `profile`, `merge`) and `RouteGraph` (the NetworkX graph, with `partners`,
274
+ `node_tensor`, `shortest_route`, `flow_subgraph`, `as_dataset`).
275
+
276
+ ## DataFrame plotting
277
+
278
+ `sameer_graph_lib.plotter` is a stateless plotting layer for any DataFrame -
279
+ each function takes a frame and returns a figure.
280
+
281
+ ```python
282
+ from sameer_graph_lib import plotter
283
+
284
+ # n columns, one panel each, test vs control overlaid in every panel
285
+ plotter.plot_columns(df, ["orders", "speed", "aor"], x="hour", group="variant",
286
+ agg="mean", kind="line", mode="grid")
287
+
288
+ # columns with different scales on one chart, one y-axis each
289
+ plotter.plot_multi_axis(df, ["orders", "speed"], x="hour", agg="mean")
290
+
291
+ # distribution of a column, with count/mean/median/sd/skew in the corner
292
+ plotter.plot_distribution(df, "speed", kind="hist", bins=40)
293
+
294
+ # the same distribution per group, one colour per line, on one chart
295
+ plotter.compare_distributions(df, "speed", group="variant", kind="kde", with_box=True)
296
+
297
+ # A/B readout: aggregate per group, optionally relative to a baseline
298
+ plotter.compare_groups(df, ["orders", "speed"], group="variant", agg="mean",
299
+ normalize_to="control")
300
+
301
+ plotter.describe_distribution(df, "speed", group="variant") # stats table
302
+ plotter.plot_correlation(df, ["orders", "speed", "distance"])
303
+ ```
304
+
305
+ `mode` is `grid` (one panel per column), `overlay` (one shared axes) or `twin`
306
+ (one y-axis per column). `kind` is `line`, `step`, `area`, `bar`, `barh`,
307
+ `scatter`, `hist`, `kde`, `box`, `violin` or `ecdf`. `group` splits every
308
+ series by a categorical column and gives each level its own colour. KDE and
309
+ ECDF are computed with numpy, so scipy is not required.
310
+
311
+ The explorer exposes the same helpers over its own long frame:
312
+
313
+ ```python
314
+ ex.plot_columns(["orders", "speed"], x="hour", group="week_period", agg="mean")
315
+ ex.compare_distributions("speed", "week_period")
316
+ ```
317
+
318
+ ## Useful commands
319
+
320
+ ```powershell
321
+ python -m pytest
322
+ python -m build
323
+ uv run --extra dev pytest -q
324
+ uv run --extra dev python -m build
325
+ uv run --extra dev python -m twine check dist/*
326
+ ```
327
+
328
+ Build artifacts will appear in `dist/` after `python -m build`.
329
+
330
+ ## Publish To PyPI
331
+
332
+ 1. Build the package:
333
+
334
+ ```powershell
335
+ uv run --extra dev python -m build
336
+ ```
337
+
338
+ 2. Validate the package metadata:
339
+
340
+ ```powershell
341
+ uv run --extra dev python -m twine check dist/*
342
+ ```
343
+
344
+ 3. Upload to PyPI:
345
+
346
+ ```powershell
347
+ uv run --extra dev python -m twine upload dist/*
348
+ ```
349
+
350
+ After upload, users can install it with:
351
+
352
+ ```powershell
353
+ pip install sameer-graph-lib
354
+ ```
355
+
356
+ ## Publish From GitHub
357
+
358
+ This repo also includes a Trusted Publishing workflow in
359
+ [.github/workflows/publish.yml](C:/Users/rrran/Desktop/sameer_graph_lib/.github/workflows/publish.yml:1).
360
+
361
+ To finish that setup:
362
+
363
+ 1. Create the project on PyPI, or reserve the name `sameer-graph-lib`.
364
+ 2. On PyPI, open the project settings and add a Trusted Publisher for:
365
+ `owner`: `iams31`
366
+ `repository`: `sameer_graph_lib`
367
+ `workflow`: `publish.yml`
368
+ `environment`: `pypi`
369
+ 3. Create a GitHub Release, or run the workflow manually from the Actions tab.
370
+
371
+ After that, GitHub Actions can publish without storing a long-lived PyPI token.
372
+
373
+ Official references:
374
+
375
+ - PyPI Trusted Publishing: https://docs.pypi.org/trusted-publishers/
376
+ - Packaging guide upload flow: https://packaging.python.org/tutorials/packaging-projects/
@@ -0,0 +1,329 @@
1
+ # sameer-graph-lib
2
+
3
+ `sameer-graph-lib` is an editable Python library for building H3-based route affinity graphs with NetworkX.
4
+
5
+ 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.
6
+
7
+ ## Editable install
8
+
9
+ ```powershell
10
+ python -m pip install -e ".[dev,plot]"
11
+ ```
12
+
13
+ Because the install is editable, changes you make inside `src/sameer_graph_lib` are picked up immediately by Python without reinstalling.
14
+
15
+ If your machine uses `uv`, run commands through the managed environment:
16
+
17
+ ```powershell
18
+ uv run --extra dev --extra plot python -c "import sameer_graph_lib; print(sameer_graph_lib.__version__)"
19
+ ```
20
+
21
+ ## Install
22
+
23
+ From PyPI after publication:
24
+
25
+ ```powershell
26
+ pip install sameer-graph-lib
27
+ ```
28
+
29
+ With optional plotting and geospatial extras:
30
+
31
+ ```powershell
32
+ pip install "sameer-graph-lib[plot,geo]"
33
+ ```
34
+
35
+ For the route flow graph and the DataFrame plotter (pandas, numpy, xarray, matplotlib):
36
+
37
+ ```powershell
38
+ pip install "sameer-graph-lib[analysis]"
39
+ ```
40
+
41
+ ## Quick start
42
+
43
+ ```python
44
+ from sameer_graph_lib import HexGraph
45
+
46
+ graph = HexGraph(hex_resolution=9)
47
+
48
+ route = graph.add_latlng_sequence([
49
+ (12.9716, 77.5946),
50
+ (12.9760, 77.5990),
51
+ ])
52
+
53
+ print(graph.get_graph_stats())
54
+ selected = graph.get_appropriate_hexes(cutoff=0.8)
55
+ fig = graph.visualize_graph(title="80% compact cluster", highlight_hexes=selected)
56
+ print(graph.decompose_branches())
57
+ ```
58
+
59
+ ## Main APIs
60
+
61
+ - `SpatialIngestor`: converts H3 arrays, lat/lng sequences, and encoded polylines into contiguous H3 chains.
62
+ - `AffinityGraph`: NetworkX wrapper for array-based insertion, nearest attachment, affinity scoring, editing, and JSON persistence.
63
+ - `CorridorExtractor`: uses exact all-node Dijkstra selection to extract the most compact cluster covering a target percentage of graph traversal volume.
64
+ - `TopologyAnalyzer`: separates the main branch from residual minor branches.
65
+ - `HexGraph`: backwards-compatible convenience class for your original code style.
66
+ - `RouteSchema` / `RouteTensor` / `RouteGraph`: grain-aware pickup -> drop graph where each edge carries a metric cube (see [Route flow graphs](#route-flow-graphs-pickup---drop)).
67
+ - `RouteExplorer`: the one-liner front door to the route graph - build from a DataFrame, rank partners, and plot the flow around a cluster.
68
+ - `plotter`: stateless DataFrame plotting - multi-column, multi-axis, multi-group, plus distribution checks.
69
+
70
+ 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.
71
+
72
+ For QC, use:
73
+
74
+ ```python
75
+ fig = graph.visualize_graph(highlight_hexes=selected)
76
+ fig.savefig("graph_qc.png", dpi=150, bbox_inches="tight")
77
+
78
+ fig = graph.visualize_step_by_step(route[:5], labels=["A", "B", "C", "D", "E"])
79
+ fig.savefig("insertion_steps.png", dpi=150, bbox_inches="tight")
80
+ ```
81
+
82
+ To plot actual H3 hex boundaries as geospatial polygons:
83
+
84
+ ```python
85
+ from sameer_graph_lib import plot_h3_cells, plot_h3_cells_map
86
+
87
+ fig = plot_h3_cells("88618c4f29fffff", label_full_hex=True)
88
+ fig.savefig("single_h3_cell.png", dpi=150, bbox_inches="tight")
89
+
90
+ fig = graph.plot_h3_cells(highlight_hexes=selected, show_labels=False)
91
+ fig.savefig("h3_cell_footprint.png", dpi=150, bbox_inches="tight")
92
+
93
+ fig = plot_h3_cells_map(route, selected_cells=selected)
94
+ fig.savefig("h3_cell_basemap.png", dpi=150, bbox_inches="tight")
95
+ ```
96
+
97
+ `plot_h3_cells_map` uses GeoPandas + Contextily. Install it with:
98
+
99
+ ```powershell
100
+ python -m pip install -e ".[plot,geo]"
101
+ uv run --extra plot --extra geo python -c "from sameer_graph_lib import plot_h3_cells_map"
102
+ ```
103
+
104
+ To get H3 centers and a convex hull:
105
+
106
+ ```python
107
+ from sameer_graph_lib import getLatLng, h3_convex_hull
108
+
109
+ points = getLatLng(route) # [(lat, lng), ...]
110
+ hull = h3_convex_hull(route) # Shapely geometry in (lng, lat)
111
+ graph_hull = graph.convex_hull() # Same, using graph nodes
112
+ ```
113
+
114
+ ## Route flow graphs (pickup -> drop)
115
+
116
+ The second half of the library works on tabular route data rather than H3
117
+ chains: one row per `pickup_cluster x drop_cluster x week_period x hour`, with
118
+ whatever metric columns you export. Every edge stores a **tensor** - a dense
119
+ cube over the grain dimensions - so sums stay exact, averages stay correctly
120
+ weighted, and two tensors can be merged without re-reading the source rows.
121
+
122
+ The metric columns are **yours**: the schema reads whatever numeric columns the
123
+ frame has. Names that look like averages (`avg_`, `_rate`, `pct`, `ratio`, ...)
124
+ are weighted-averaged, the rest are summed, and grain/id/categorical columns are
125
+ never treated as metrics. Pass `sum_metrics=` / `mean_metrics=` to override the
126
+ split, and `metric=` anywhere to choose the variable; with nothing named, calls
127
+ fall back to `schema.default_metric` (the first sum metric).
128
+
129
+ Install the extra:
130
+
131
+ ```powershell
132
+ python -m pip install -e ".[analysis]" # pandas, numpy, xarray, matplotlib
133
+ ```
134
+
135
+ ### Quick start
136
+
137
+ ```python
138
+ from sameer_graph_lib import RouteExplorer
139
+
140
+ ex = RouteExplorer(df, metric="orders") # metric= is optional
141
+
142
+ ex.sources("A1", top=5) # where A1 orders come from
143
+ ex.drops("A1", top=5) # where A1 orders go
144
+ ex.route("A1", "B1") # every metric for one route
145
+ ex.summary("A1") # every metric for one cluster
146
+ print(ex.describe())
147
+ ```
148
+
149
+ `where` and `using` return a new view that shares the same graph, so you can
150
+ narrow the question without rebuilding anything:
151
+
152
+ ```python
153
+ peak = ex.where(week_period="weekday", hour=[8, 9, 10]).using("speed")
154
+ peak.drops("A1", top=5)
155
+ ```
156
+
157
+ ### Plotting the flow around a cluster
158
+
159
+ `plot` puts the focus clusters in the middle, the clusters that feed them on
160
+ the left, and the clusters they feed on the right. The per-level top-k is the
161
+ main control: pass an int for one level, or a list for one number per level.
162
+
163
+ ```python
164
+ # top 5 sources, then the top 3 sources of each of those, then the top 2;
165
+ # and on the other side, the top 4 drops and the top 2 of each.
166
+ fig = ex.plot("A1", upstream=[5, 3, 2], downstream=[4, 2])
167
+
168
+ # several focus clusters, plus the routes between the selected set
169
+ ex.plot(["A1", "C1"], upstream=3, downstream=3, include_cross_edges=True)
170
+
171
+ # rank by one variable, show several: every node and edge carries a table
172
+ ex.plot("A1", metric="orders",
173
+ node_metrics=["orders", "accepted_orders", "requests", "speed"],
174
+ edge_metrics=["orders", "speed", "avg_distance"],
175
+ hour=[8, 9, 10])
176
+ ```
177
+
178
+ `node_metrics` and `edge_metrics` take any number of variables. They are drawn
179
+ as a small aligned table under each node and on each edge, with the metric
180
+ names on the left and the values right-aligned; every table in one figure
181
+ shares its column widths, and the figure sizes itself so they do not collide.
182
+ Numbers are formatted per value by default (62,709 and 0.273 both read
183
+ correctly); pass `value_format="{:,.1f}"` for one format, or a dict such as
184
+ `value_format={"speed": "{:.3f}"}` per metric.
185
+
186
+ Other options: `size_metric=` (which variable drives node size),
187
+ `show_metric_names=False` (values only), `per_parent=False` (rank a whole level
188
+ globally instead of per parent), `min_value=`, `layout="layered" | "geo" |
189
+ "spring"` (`geo` places H3 cluster ids at their real coordinates),
190
+ `show_edge_values`, `label_cross_edges`, `curve`, `figsize`, and the colours.
191
+
192
+ The same expansion is available without plotting:
193
+
194
+ ```python
195
+ ex.flow("A1", upstream=[5, 3], downstream=4) # tidy DataFrame
196
+ ex.subgraph("A1", upstream=2, downstream=2) # nx.DiGraph, levels on nodes
197
+ print(ex.tree("A1", upstream=[5, 3], downstream=4))
198
+ ```
199
+
200
+ ```text
201
+ A1 (orders)
202
+ sources (orders coming in)
203
+ |-- <- C1 [8,523.4]
204
+ | |-- <- D3 [8,802.4]
205
+ | +-- <- D2 [3,646.5]
206
+ +-- <- C3 [7,041.1]
207
+ drops (orders going out)
208
+ |-- -> B4 [4,574.9]
209
+ +-- -> B5 [4,446.4]
210
+ ```
211
+
212
+ ### Other route views
213
+
214
+ | Call | What you get |
215
+ | --- | --- |
216
+ | `ex.plot_partners("A1", top=10)` | top sources and top drops as back-to-back bars on one shared scale |
217
+ | `ex.plot_profile(cluster="A1")` | week_period x hour heatmap for a cluster or a route |
218
+ | `ex.plot_matrix(top=15)` | pickup x drop heatmap |
219
+ | `ex.plot_graph(top_routes=30)` | the whole graph as a ranked ring, sized and coloured by a metric |
220
+ | `ex.clusters_frame()` | per cluster inbound vs outbound volume and net balance |
221
+ | `ex.top_routes(20)` | routes ranked by any metric |
222
+ | `ex.matrix()` / `ex.frame()` | pivot table / long per-bucket frame |
223
+
224
+ The lower-level classes are public too: `RouteSchema` (grains and metric
225
+ columns), `RouteTensor` (one route's metric cube, with `summary`, `totals`,
226
+ `profile`, `merge`) and `RouteGraph` (the NetworkX graph, with `partners`,
227
+ `node_tensor`, `shortest_route`, `flow_subgraph`, `as_dataset`).
228
+
229
+ ## DataFrame plotting
230
+
231
+ `sameer_graph_lib.plotter` is a stateless plotting layer for any DataFrame -
232
+ each function takes a frame and returns a figure.
233
+
234
+ ```python
235
+ from sameer_graph_lib import plotter
236
+
237
+ # n columns, one panel each, test vs control overlaid in every panel
238
+ plotter.plot_columns(df, ["orders", "speed", "aor"], x="hour", group="variant",
239
+ agg="mean", kind="line", mode="grid")
240
+
241
+ # columns with different scales on one chart, one y-axis each
242
+ plotter.plot_multi_axis(df, ["orders", "speed"], x="hour", agg="mean")
243
+
244
+ # distribution of a column, with count/mean/median/sd/skew in the corner
245
+ plotter.plot_distribution(df, "speed", kind="hist", bins=40)
246
+
247
+ # the same distribution per group, one colour per line, on one chart
248
+ plotter.compare_distributions(df, "speed", group="variant", kind="kde", with_box=True)
249
+
250
+ # A/B readout: aggregate per group, optionally relative to a baseline
251
+ plotter.compare_groups(df, ["orders", "speed"], group="variant", agg="mean",
252
+ normalize_to="control")
253
+
254
+ plotter.describe_distribution(df, "speed", group="variant") # stats table
255
+ plotter.plot_correlation(df, ["orders", "speed", "distance"])
256
+ ```
257
+
258
+ `mode` is `grid` (one panel per column), `overlay` (one shared axes) or `twin`
259
+ (one y-axis per column). `kind` is `line`, `step`, `area`, `bar`, `barh`,
260
+ `scatter`, `hist`, `kde`, `box`, `violin` or `ecdf`. `group` splits every
261
+ series by a categorical column and gives each level its own colour. KDE and
262
+ ECDF are computed with numpy, so scipy is not required.
263
+
264
+ The explorer exposes the same helpers over its own long frame:
265
+
266
+ ```python
267
+ ex.plot_columns(["orders", "speed"], x="hour", group="week_period", agg="mean")
268
+ ex.compare_distributions("speed", "week_period")
269
+ ```
270
+
271
+ ## Useful commands
272
+
273
+ ```powershell
274
+ python -m pytest
275
+ python -m build
276
+ uv run --extra dev pytest -q
277
+ uv run --extra dev python -m build
278
+ uv run --extra dev python -m twine check dist/*
279
+ ```
280
+
281
+ Build artifacts will appear in `dist/` after `python -m build`.
282
+
283
+ ## Publish To PyPI
284
+
285
+ 1. Build the package:
286
+
287
+ ```powershell
288
+ uv run --extra dev python -m build
289
+ ```
290
+
291
+ 2. Validate the package metadata:
292
+
293
+ ```powershell
294
+ uv run --extra dev python -m twine check dist/*
295
+ ```
296
+
297
+ 3. Upload to PyPI:
298
+
299
+ ```powershell
300
+ uv run --extra dev python -m twine upload dist/*
301
+ ```
302
+
303
+ After upload, users can install it with:
304
+
305
+ ```powershell
306
+ pip install sameer-graph-lib
307
+ ```
308
+
309
+ ## Publish From GitHub
310
+
311
+ This repo also includes a Trusted Publishing workflow in
312
+ [.github/workflows/publish.yml](C:/Users/rrran/Desktop/sameer_graph_lib/.github/workflows/publish.yml:1).
313
+
314
+ To finish that setup:
315
+
316
+ 1. Create the project on PyPI, or reserve the name `sameer-graph-lib`.
317
+ 2. On PyPI, open the project settings and add a Trusted Publisher for:
318
+ `owner`: `iams31`
319
+ `repository`: `sameer_graph_lib`
320
+ `workflow`: `publish.yml`
321
+ `environment`: `pypi`
322
+ 3. Create a GitHub Release, or run the workflow manually from the Actions tab.
323
+
324
+ After that, GitHub Actions can publish without storing a long-lived PyPI token.
325
+
326
+ Official references:
327
+
328
+ - PyPI Trusted Publishing: https://docs.pypi.org/trusted-publishers/
329
+ - Packaging guide upload flow: https://packaging.python.org/tutorials/packaging-projects/