core-lens 0.1.dev113__tar.gz → 0.1.dev117__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 (77) hide show
  1. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/PKG-INFO +1 -1
  2. core_lens-0.1.dev117/docs/source/export.md +47 -0
  3. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/docs/source/index.rst +1 -0
  4. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/docs/source/intro.md +2 -1
  5. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/docs/source/plots.md +1 -1
  6. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/src/core_lens/_version.py +2 -2
  7. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/src/core_lens/aoi.py +16 -16
  8. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/src/core_lens/base/entity.py +18 -18
  9. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/src/core_lens/base/namespaces/plot.py +25 -25
  10. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/src/core_lens/base/namespaces/stats.py +25 -25
  11. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/src/core_lens/base/result.py +16 -10
  12. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/src/core_lens/base/view.py +39 -19
  13. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/src/core_lens/entities/mws.py +15 -0
  14. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/src/core_lens/entities/tehsil.py +12 -0
  15. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/src/core_lens/entities/waterbody.py +12 -0
  16. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/src/core_lens/export/formats.py +15 -15
  17. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/src/core_lens/schema/detection.py +6 -6
  18. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/src/core_lens/schema/profile.py +2 -2
  19. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/src/core_lens/utils/polars_utils.py +10 -7
  20. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/src/core_lens/utils/season.py +117 -47
  21. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/src/core_lens/utils/spatial.py +28 -28
  22. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/tests/unit/test_entity.py +65 -0
  23. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/tests/unit/test_plot.py +35 -0
  24. core_lens-0.1.dev117/tests/unit/test_polars_utils.py +87 -0
  25. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/tests/unit/test_spatial.py +104 -0
  26. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/tests/unit/test_stats.py +219 -11
  27. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/tests/unit/test_view.py +76 -0
  28. core_lens-0.1.dev113/tests/unit/test_polars_utils.py +0 -14
  29. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/.github/pull_request_template.md +0 -0
  30. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/.github/workflows/ci.yml +0 -0
  31. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/.github/workflows/gh-pages.yml +0 -0
  32. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/.github/workflows/pre-release.yml +0 -0
  33. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/.github/workflows/release.yml +0 -0
  34. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/.gitignore +0 -0
  35. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/.gitmessage +0 -0
  36. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/.pre-commit-config.yaml +0 -0
  37. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/.python-version +0 -0
  38. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/CONTRIBUTING.md +0 -0
  39. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/LICENSE +0 -0
  40. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/README.md +0 -0
  41. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/SKILLS.md +0 -0
  42. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/docs/Makefile +0 -0
  43. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/docs/make.bat +0 -0
  44. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/docs/source/concepts.md +0 -0
  45. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/docs/source/conf.py +0 -0
  46. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/docs/source/plugins.md +0 -0
  47. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/docs/source/queries.md +0 -0
  48. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/docs/source/quickstart.md +0 -0
  49. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/docs/source/stats.md +0 -0
  50. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/examples/demo_mws.py +0 -0
  51. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/examples/demo_tehsil.py +0 -0
  52. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/hooks/mypy.sh +0 -0
  53. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/hooks/no-parquet-outside-fixtures.sh +0 -0
  54. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/hooks/pytest.sh +0 -0
  55. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/pyproject.toml +0 -0
  56. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/src/core_lens/__init__.py +0 -0
  57. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/src/core_lens/__main__.py +0 -0
  58. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/src/core_lens/base/__init__.py +0 -0
  59. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/src/core_lens/base/namespaces/__init__.py +0 -0
  60. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/src/core_lens/entities/__init__.py +0 -0
  61. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/src/core_lens/export/__init__.py +0 -0
  62. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/src/core_lens/py.typed +0 -0
  63. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/src/core_lens/schema/__init__.py +0 -0
  64. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/src/core_lens/utils/__init__.py +0 -0
  65. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/tests/fixtures/generate_fixtures.py +0 -0
  66. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/tests/unit/conftest.py +0 -0
  67. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/tests/unit/test_aoi.py +0 -0
  68. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/tests/unit/test_entities.py +0 -0
  69. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/tests/unit/test_export.py +0 -0
  70. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/tests/unit/test_main.py +0 -0
  71. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/tests/unit/test_profile.py +0 -0
  72. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/tests/unit/test_result.py +0 -0
  73. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/tests/unit/test_schema_detection.py +0 -0
  74. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/tests/unit/test_schema_profile.py +0 -0
  75. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/tests/unit/test_season.py +0 -0
  76. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/tests/unit/test_season_config.py +0 -0
  77. {core_lens-0.1.dev113 → core_lens-0.1.dev117}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: core-lens
3
- Version: 0.1.dev113
3
+ Version: 0.1.dev117
4
4
  Summary: Query, analyse, and visualise CoreStack's microwatershed and Earth science data through a clean, composable Python API.
5
5
  Project-URL: Homepage, https://github.com/ApoorvaKashyap/core-lens
6
6
  Project-URL: Issues, https://github.com/ApoorvaKashyap/core-lens/issues
@@ -0,0 +1,47 @@
1
+ # Data Export
2
+
3
+ CoreLens provides a unified `core_lens.export` module for saving `Result` objects to various file formats. Standard tabular formats use Polars for high-speed writes, while geospatial formats leverage DuckDB's spatial extensions for robust geometry encoding.
4
+
5
+ ## Standard Formats
6
+
7
+ Export non-spatial results to standard formats. Note that these formats cannot encode geometry columns; if your `Result` has geometry, these functions will raise a `TypeError`.
8
+
9
+ ```python
10
+ from core_lens.export import parquet, csv, json
11
+
12
+ res = aoi.mws.annual
13
+
14
+ # Export to Parquet with ZSTD compression
15
+ parquet(res, "output.parquet", compression="zstd")
16
+
17
+ # Export to CSV
18
+ csv(res, "output.csv", separator=",")
19
+
20
+ # Export to JSON
21
+ json(res, "output.json", pretty=True)
22
+ ```
23
+
24
+ ## Geospatial Formats
25
+
26
+ Export spatial data to GeoParquet and GeoJSON. The result must have geometry attached (e.g., by calling `.with_geometry()`).
27
+
28
+ ```python
29
+ from core_lens.export import geoparquet, geojson
30
+
31
+ # Make sure geometry is attached!
32
+ spatial_res = aoi.mws.annual.with_geometry()
33
+
34
+ # Export to GeoParquet with DuckDB COPY options (e.g., partitioning)
35
+ geoparquet(
36
+ spatial_res,
37
+ "output.geoparquet",
38
+ compression="ZSTD",
39
+ partition_by="year"
40
+ )
41
+
42
+ # Export to GeoJSON
43
+ geojson(spatial_res, "output.json")
44
+ ```
45
+
46
+ > [!NOTE]
47
+ > Exporting to geospatial formats automatically uses DuckDB to cast WKB geometries to proper spatial types via the `spatial` extension. CoreLens handles the installation and loading of the extension seamlessly under the hood.
@@ -21,5 +21,6 @@ Provides a unified interface over microwatershed hydrology, administrative bound
21
21
  queries
22
22
  stats
23
23
  plots
24
+ export
24
25
  plugins
25
26
  autoapi/index
@@ -12,4 +12,5 @@ CoreLens provides a unified interface over microwatersheds, administrative bound
12
12
  - **Pluggable Entities**: Built-in support for standard units (MWS, Tehsil) with a simple plugin architecture for adding new domain entities.
13
13
  - **Temporal & Seasonal Awareness**: Native support for agronomic seasons (Kharif, Rabi, Zaid) and time-range filtering.
14
14
  - **Spatial Statistics & Analysis**: Built-in methods for anomaly detection, spatial similarity, temporal correlation, and hypothesis testing.
15
- - **Interactive Visualisation**: Generate interactive maps using Lonboard and timeseries/scatter plots using Bokeh directly from results.
15
+ - **Interactive Visualisation**: Generate interactive maps using Lonboard with zero-copy GeoArrow rendering directly from Polars, and timeseries/scatter plots using Bokeh.
16
+ - **High-Performance Exports**: Export results directly to Parquet, JSON, CSV, or spatially-enabled GeoParquet and GeoJSON using integrated DuckDB spatial extensions.
@@ -1,6 +1,6 @@
1
1
  # Visualisation and Plots
2
2
 
3
- The `result.plot` namespace provides integrated plotting capabilities. Spatial plots use **Lonboard** for high-performance interactive maps, while charts and timeseries use **Bokeh**.
3
+ The `result.plot` namespace provides integrated plotting capabilities. Spatial plots use **Lonboard** for high-performance interactive maps (achieved via zero-copy GeoArrow memory mapping directly from Polars, bypassing GeoPandas), while charts and timeseries use **Bokeh**.
4
4
 
5
5
  Plot methods return plot objects (like a Bokeh Figure or Lonboard Map) which can be displayed using `bokeh.io.show` or exported via `bokeh.io.save`.
6
6
 
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.1.dev113'
22
- __version_tuple__ = version_tuple = (0, 1, 'dev113')
21
+ __version__ = version = '0.1.dev117'
22
+ __version_tuple__ = version_tuple = (0, 1, 'dev117')
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -67,10 +67,10 @@ class SeasonConfig:
67
67
  """Return the season name for a given date.
68
68
 
69
69
  Args:
70
- d: The date to classify.
70
+ d (date): The date to classify.
71
71
 
72
72
  Returns:
73
- ``"kharif"``, ``"rabi"``, or ``"zaid"``.
73
+ str: ``"kharif"``, ``"rabi"``, or ``"zaid"``.
74
74
  """
75
75
  md = f"{d.month:02d}-{d.day:02d}"
76
76
  for name in ("kharif", "rabi", "zaid"):
@@ -155,16 +155,16 @@ class AoI:
155
155
  """Resolve the AoI boundary and scope all registered entities.
156
156
 
157
157
  Args:
158
- data_root: Path to the root data directory. Prepended to every
158
+ data_root (str): Path to the root data directory. Prepended to every
159
159
  relative entity path that does not start with ``/`` or a URI
160
160
  scheme.
161
- bbox: Bounding box as ``(minx, miny, maxx, maxy)`` in WGS-84.
161
+ bbox (tuple[float, float, float, float] | None, optional): Bounding box as ``(minx, miny, maxx, maxy)`` in WGS-84.
162
162
  Mutually exclusive with ``geometry`` and ``entity_kwargs``.
163
- geometry: Arbitrary Shapely geometry. Used as-is. Mutually
163
+ geometry (shapely.Geometry | None, optional): Arbitrary Shapely geometry. Used as-is. Mutually
164
164
  exclusive with ``bbox`` and ``entity_kwargs``.
165
- seasons: :class:`SeasonConfig` override. Defaults to the library
165
+ seasons (SeasonConfig | None, optional): :class:`SeasonConfig` override. Defaults to the library
166
166
  agronomic defaults.
167
- **entity_kwargs: Named filter pairs that identify the boundary,
167
+ **entity_kwargs (str | list[str]): Named filter pairs that identify the boundary,
168
168
  e.g. ``tehsil="Pangi"``, ``district="Chamba"``,
169
169
  ``state="Himachal Pradesh"``, ``mws_id="13_551"``.
170
170
  Mutually exclusive with ``bbox`` and ``geometry``.
@@ -219,7 +219,7 @@ class AoI:
219
219
  """The season name for today's date under the AoI's SeasonConfig.
220
220
 
221
221
  Returns:
222
- ``"kharif"``, ``"rabi"``, or ``"zaid"``.
222
+ str: ``"kharif"``, ``"rabi"``, or ``"zaid"``.
223
223
  """
224
224
  return self.seasons.season_for(date.today())
225
225
 
@@ -228,7 +228,7 @@ class AoI:
228
228
  """The current calendar year.
229
229
 
230
230
  Returns:
231
- Current year as an integer.
231
+ int: Current year as an integer.
232
232
  """
233
233
  return date.today().year
234
234
 
@@ -236,11 +236,11 @@ class AoI:
236
236
  """Render an interactive Lonboard map of the AoI and its entity layers.
237
237
 
238
238
  Args:
239
- overlay: An optional :class:`~core_lens.base.result.Result` to
239
+ overlay (Result | None, optional): An optional :class:`~core_lens.base.result.Result` to
240
240
  overlay on the map.
241
241
 
242
242
  Returns:
243
- A Lonboard Map object.
243
+ Any: A Lonboard Map object.
244
244
  """
245
245
  import lonboard
246
246
  import geopandas as gpd
@@ -309,10 +309,10 @@ class AoI:
309
309
  the unique matching row).
310
310
 
311
311
  Args:
312
- entity_kwargs: Column–value pairs used to identify the boundary.
312
+ entity_kwargs (dict[str, str | list[str]]): Column–value pairs used to identify the boundary.
313
313
 
314
314
  Returns:
315
- The union of all matching entity geometries as a Shapely object.
315
+ shapely.Geometry: The union of all matching entity geometries as a Shapely object.
316
316
 
317
317
  Raises:
318
318
  :class:`~core_lens.base.EntityValidationError`: If no registered entity can satisfy the filters.
@@ -391,7 +391,7 @@ class AoI:
391
391
  :class:`AoI` is instantiated (when ``data_root`` is known).
392
392
 
393
393
  Args:
394
- entity_cls: A concrete subclass of
394
+ entity_cls (type[BaseEntity]): A concrete subclass of
395
395
  :class:`~core_lens.base.entity.BaseEntity`.
396
396
 
397
397
  Raises:
@@ -417,7 +417,7 @@ class AoI:
417
417
  Primarily useful in tests where a clean registry is needed between runs.
418
418
 
419
419
  Args:
420
- entity_cls: The entity class to remove.
420
+ entity_cls (type[BaseEntity]): The entity class to remove.
421
421
  """
422
422
  name = _entity_name(entity_cls)
423
423
  _REGISTRY.pop(name, None)
@@ -427,7 +427,7 @@ class AoI:
427
427
  """Return the names of all currently registered entities.
428
428
 
429
429
  Returns:
430
- A sorted list of entity name strings.
430
+ list[str]: A sorted list of entity name strings.
431
431
  """
432
432
  return sorted(_REGISTRY)
433
433
 
@@ -84,7 +84,7 @@ class BaseEntity(ABC):
84
84
  """Initialise the entity with an optional data root directory.
85
85
 
86
86
  Args:
87
- data_root: Absolute path to the root data directory. When
87
+ data_root (pathlib.Path | None, optional): Absolute path to the root data directory. When
88
88
  supplied, relative :attr:`static_path`, :attr:`annual_path`,
89
89
  and :attr:`fortnightly_path` values are resolved against this
90
90
  directory. Defaults to ``None``, in which case relative paths
@@ -100,10 +100,10 @@ class BaseEntity(ABC):
100
100
  otherwise against the current working directory.
101
101
 
102
102
  Args:
103
- path: A filesystem path, absolute or relative.
103
+ path (str): A filesystem path, absolute or relative.
104
104
 
105
105
  Returns:
106
- An absolute path string.
106
+ str: An absolute path string.
107
107
 
108
108
  Raises:
109
109
  FileNotFoundError: If the resolved path does not exist.
@@ -131,7 +131,7 @@ class BaseEntity(ABC):
131
131
  ``[\"mws_id\"]``), but the contract allows composite keys for plugins.
132
132
 
133
133
  Returns:
134
- A list of column name strings present in the static file.
134
+ list[str]: A list of column name strings present in the static file.
135
135
  """
136
136
 
137
137
  @property
@@ -143,7 +143,7 @@ class BaseEntity(ABC):
143
143
  (WKB bytes, WKT string, or a native geometry column).
144
144
 
145
145
  Returns:
146
- The column name as a string.
146
+ str: The column name as a string.
147
147
  """
148
148
 
149
149
  @property
@@ -156,7 +156,7 @@ class BaseEntity(ABC):
156
156
  ``FileNotFoundError`` is raised if the file does not exist.
157
157
 
158
158
  Returns:
159
- A path string.
159
+ str: A path string.
160
160
  """
161
161
 
162
162
  @property
@@ -168,7 +168,7 @@ class BaseEntity(ABC):
168
168
  :class:`EntityValidationError` is raised.
169
169
 
170
170
  Returns:
171
- A path string, or ``None`` if the entity has no annual data.
171
+ str | None: A path string, or ``None`` if the entity has no annual data.
172
172
  """
173
173
  return None
174
174
 
@@ -181,7 +181,7 @@ class BaseEntity(ABC):
181
181
  :class:`EntityValidationError` is raised.
182
182
 
183
183
  Returns:
184
- A path string, or ``None`` if the entity has no fortnightly data.
184
+ str | None: A path string, or ``None`` if the entity has no fortnightly data.
185
185
  """
186
186
  return None
187
187
 
@@ -194,7 +194,7 @@ class BaseEntity(ABC):
194
194
  instead of relying on detection.
195
195
 
196
196
  Returns:
197
- A fully-validated :class:`~core_lens.schema.profile.SchemaProfile`.
197
+ SchemaProfile: A fully-validated :class:`~core_lens.schema.profile.SchemaProfile`.
198
198
  """
199
199
  if not hasattr(self, "_schema_profile"):
200
200
  from core_lens.schema.detection import detect
@@ -247,7 +247,7 @@ class BaseEntity(ABC):
247
247
  are resolved as entity-name lookups.
248
248
 
249
249
  Returns:
250
- A lazy :class:`~core_lens.base.view.View` with resolved key pairs.
250
+ View: A lazy :class:`~core_lens.base.view.View` with resolved key pairs.
251
251
 
252
252
  Raises:
253
253
  ValueError: If a kwarg cannot be resolved as either an attribute
@@ -354,21 +354,21 @@ class BaseEntity(ABC):
354
354
  refines with a Shapely STRtree exact-relationship check.
355
355
 
356
356
  Args:
357
- geometry: A Shapely geometry representing the spatial extent.
358
- bbox: Bounding box as ``(minx, miny, maxx, maxy)`` in WGS-84.
357
+ geometry (shapely.Geometry | None, optional): A Shapely geometry representing the spatial extent.
358
+ bbox (tuple[float, float, float, float] | None, optional): Bounding box as ``(minx, miny, maxx, maxy)`` in WGS-84.
359
359
  Converted to a ``shapely.geometry.box`` internally.
360
- relationship: Spatial relationship mode.
360
+ relationship (str, optional): Spatial relationship mode.
361
361
 
362
362
  * ``"centroid"`` (default) — entity centroid must lie within
363
363
  the geometry.
364
364
  * ``"area"`` — intersection area / entity area must exceed
365
365
  ``threshold``.
366
366
 
367
- threshold: Area coverage threshold for ``"area"`` mode (0–1).
367
+ threshold (float, optional): Area coverage threshold for ``"area"`` mode (0–1).
368
368
  Default 0.5.
369
369
 
370
370
  Returns:
371
- A lazy :class:`~core_lens.base.view.View` scoped to the given
371
+ View: A lazy :class:`~core_lens.base.view.View` scoped to the given
372
372
  spatial extent.
373
373
 
374
374
  Raises:
@@ -411,15 +411,15 @@ class BaseEntity(ABC):
411
411
  Joined columns are namespaced as ``{entity_name}_{column_name}``.
412
412
 
413
413
  Args:
414
- other: The secondary :class:`BaseEntity` whose columns will be
414
+ other (BaseEntity): The secondary :class:`BaseEntity` whose columns will be
415
415
  joined and aggregated onto ``self``.
416
- agg: Mapping of ``{column: aggregation}`` specifying which columns
416
+ agg (dict[str, str]): Mapping of ``{column: aggregation}`` specifying which columns
417
417
  from ``other`` to bring in and how to aggregate them. Valid
418
418
  aggregation strings are ``\"area\"``, ``\"count\"``, ``\"mean\"``,
419
419
  ``\"sum\"``, ``\"min\"``, and ``\"max\"``.
420
420
 
421
421
  Returns:
422
- A lazy :class:`~core_lens.base.view.View` with the join spec
422
+ View: A lazy :class:`~core_lens.base.view.View` with the join spec
423
423
  recorded for deferred execution.
424
424
  """
425
425
  from core_lens.base.view import View
@@ -220,13 +220,13 @@ class PlotNamespace:
220
220
  automatically via :meth:`~core_lens.base.result.Result.with_geometry`.
221
221
 
222
222
  Args:
223
- column: The column to use for colour mapping.
224
- subplot_on: Optional temporal dimension to split data across.
223
+ column (str): The column to use for colour mapping.
224
+ subplot_on (SubplotOn | None, optional): Optional temporal dimension to split data across.
225
225
  A :class:`~core_lens.base.namespaces.plot.SubplotOn` enum value. When set, one layer is rendered per unique
226
226
  value of ``subplot_on`` in the data.
227
227
 
228
228
  Returns:
229
- A Lonboard Map object.
229
+ lonboard.Map: A Lonboard Map object.
230
230
 
231
231
  Raises:
232
232
  ValueError: If ``column`` or ``subplot_on`` column not found in data.
@@ -311,16 +311,16 @@ class PlotNamespace:
311
311
  plain :class:`bokeh.plotting.figure`.
312
312
 
313
313
  Args:
314
- x: The temporal column to plot on the x-axis.
315
- y: The value column(s) to plot on the y-axis.
314
+ x (str | None, optional): The temporal column to plot on the x-axis.
315
+ y (str | list[str] | None, optional): The value column(s) to plot on the y-axis.
316
316
  If ``None``, all numeric columns (except ``x``) are used.
317
- subplot_on: Optional temporal dimension to split data across.
317
+ subplot_on (SubplotOn | str | None, optional): Optional temporal dimension to split data across.
318
318
  A :class:`~core_lens.base.namespaces.plot.SubplotOn` enum value or string.
319
- top_n: Maximum number of entities rendered in per-entity view.
320
- aggregate: If ``True``, renders only the aggregated mean view.
319
+ top_n (int, optional): Maximum number of entities rendered in per-entity view.
320
+ aggregate (bool, optional): If ``True``, renders only the aggregated mean view.
321
321
 
322
322
  Returns:
323
- A Bokeh Tabs object (or a single Figure when ``aggregate=True``).
323
+ Any: A Bokeh Tabs object (or a single Figure when ``aggregate=True``).
324
324
  """
325
325
  import polars as pl
326
326
  import polars.selectors as cs
@@ -486,12 +486,12 @@ class PlotNamespace:
486
486
  """Render a scatter plot using Bokeh.
487
487
 
488
488
  Args:
489
- x: The column to plot on the x-axis.
490
- y: The column(s) to plot on the y-axis. If None, auto-selects all numeric.
491
- top_n: Maximum number of entities to plot.
489
+ x (str | None, optional): The column to plot on the x-axis.
490
+ y (str | list[str] | None, optional): The column(s) to plot on the y-axis. If None, auto-selects all numeric.
491
+ top_n (int, optional): Maximum number of entities to plot.
492
492
 
493
493
  Returns:
494
- A Bokeh Figure or Tabs object.
494
+ Any: A Bokeh Figure or Tabs object.
495
495
  """
496
496
  import polars as pl
497
497
  import polars.selectors as cs
@@ -562,11 +562,11 @@ class PlotNamespace:
562
562
  """Render a distribution (histogram) plot using Bokeh.
563
563
 
564
564
  Args:
565
- x: The column(s) to plot the distribution for. If None, auto-selects all numeric.
566
- top_n: Maximum number of entities to include.
565
+ x (str | list[str] | None, optional): The column(s) to plot the distribution for. If None, auto-selects all numeric.
566
+ top_n (int, optional): Maximum number of entities to include.
567
567
 
568
568
  Returns:
569
- A Bokeh Figure or Tabs object.
569
+ Any: A Bokeh Figure or Tabs object.
570
570
  """
571
571
  import numpy as np
572
572
  import pandas as pd
@@ -631,11 +631,11 @@ class PlotNamespace:
631
631
  """Render a correlation heatmap using Bokeh.
632
632
 
633
633
  Args:
634
- columns: Optional list of columns to correlate. Defaults to all numeric.
635
- top_n: Maximum number of entities to include (currently unused).
634
+ columns (list[str] | None, optional): Optional list of columns to correlate. Defaults to all numeric.
635
+ top_n (int, optional): Maximum number of entities to include (currently unused).
636
636
 
637
637
  Returns:
638
- A Bokeh Figure object.
638
+ Any: A Bokeh Figure object.
639
639
  """
640
640
  import polars.selectors as cs
641
641
  from bokeh.models import BasicTicker, ColumnDataSource, LinearColorMapper
@@ -708,13 +708,13 @@ class PlotNamespace:
708
708
  """Render a heatmap using Bokeh.
709
709
 
710
710
  Args:
711
- x: The column for the x-axis.
712
- y: The column for the y-axis.
713
- value: The column for the colour values.
714
- top_n: Maximum number of entities to include (currently unused).
711
+ x (str | None, optional): The column for the x-axis.
712
+ y (str | None, optional): The column for the y-axis.
713
+ value (str | None, optional): The column for the colour values.
714
+ top_n (int, optional): Maximum number of entities to include (currently unused).
715
715
 
716
716
  Returns:
717
- A Bokeh Figure object.
717
+ Any: A Bokeh Figure object.
718
718
  """
719
719
  from bokeh.models import (
720
720
  BasicTicker,
@@ -789,7 +789,7 @@ class PlotNamespace:
789
789
  """Render a scatter matrix (pairs plot) using Bokeh.
790
790
 
791
791
  Returns:
792
- A Bokeh ``gridplot`` object.
792
+ Any: A Bokeh ``gridplot`` object.
793
793
  """
794
794
  import polars.selectors as cs
795
795
  from bokeh.layouts import gridplot
@@ -170,12 +170,12 @@ class StatsNamespace:
170
170
  """Per-column or per-entity descriptive statistics.
171
171
 
172
172
  Args:
173
- columns: Numeric columns to describe. Defaults to all numeric cols.
174
- by: ``"column"`` (one row per column, design default) or
173
+ columns (list[str] | None, optional): Numeric columns to describe. Defaults to all numeric cols.
174
+ by (str, optional): ``"column"`` (one row per column, design default) or
175
175
  ``"entity"`` (one row per entity with mean/std per column).
176
176
 
177
177
  Returns:
178
- New :class:`~core_lens.base.result.Result` with descriptive stats.
178
+ Result: New :class:`~core_lens.base.result.Result` with descriptive stats.
179
179
 
180
180
  Raises:
181
181
  ValueError: If ``by`` is not ``\"column\"`` or ``\"entity\"``.
@@ -227,12 +227,12 @@ class StatsNamespace:
227
227
  """Pairwise correlations between columns.
228
228
 
229
229
  Args:
230
- columns: At least 2 column names.
231
- method: A :class:`~core_lens.base.namespaces.stats.CorrelateMethod` enum value.
232
- across: ``"entity"`` or ``"time"`` — recorded in metadata only.
230
+ columns (list[str]): At least 2 column names.
231
+ method (CorrelateMethod, optional): A :class:`~core_lens.base.namespaces.stats.CorrelateMethod` enum value.
232
+ across (str, optional): ``"entity"`` or ``"time"`` — recorded in metadata only.
233
233
 
234
234
  Returns:
235
- Result whose data has columns
235
+ Result: Result whose data has columns
236
236
  ``column_a | column_b | correlation | p_value``.
237
237
 
238
238
  Raises:
@@ -296,16 +296,16 @@ class StatsNamespace:
296
296
  """Hypothesis test in three modes: group-based, period-based, single-sample.
297
297
 
298
298
  Args:
299
- column: Numeric column to test.
300
- groups: Categorical column to split groups on.
301
- periods: List of ``(from_year, to_year)`` period tuples.
302
- against: Reference value for a one-sample test.
303
- method: A :class:`~core_lens.base.namespaces.stats.TestMethod` enum value.
299
+ column (str): Numeric column to test.
300
+ groups (str | None, optional): Categorical column to split groups on.
301
+ periods (list[tuple[int, int]] | None, optional): List of ``(from_year, to_year)`` period tuples.
302
+ against (float | None, optional): Reference value for a one-sample test.
303
+ method (TestMethod | None, optional): A :class:`~core_lens.base.namespaces.stats.TestMethod` enum value.
304
304
  Auto-selected via Shapiro-Wilk if ``None``.
305
- significance_level: Alpha level for ``significant`` flag (default 0.05).
305
+ significance_level (float, optional): Alpha level for ``significant`` flag (default 0.05).
306
306
 
307
307
  Returns:
308
- Result whose data has per-group descriptive stats
308
+ Result: Result whose data has per-group descriptive stats
309
309
  ``group | n | mean | std | median`` and
310
310
  ``metadata`` with ``statistic``, ``p_value``, ``significant``.
311
311
 
@@ -447,13 +447,13 @@ class StatsNamespace:
447
447
  """Change between two time periods per entity.
448
448
 
449
449
  Args:
450
- column: Value column to compute change for.
451
- from_period: Start year/period integer.
452
- to_period: End year/period integer.
453
- method: A :class:`~core_lens.base.namespaces.stats.ChangeMethod` enum value.
450
+ column (str): Value column to compute change for.
451
+ from_period (int): Start year/period integer.
452
+ to_period (int): End year/period integer.
453
+ method (ChangeMethod, optional): A :class:`~core_lens.base.namespaces.stats.ChangeMethod` enum value.
454
454
 
455
455
  Returns:
456
- For *absolute* / *percentage*: data has
456
+ Result: For *absolute* / *percentage*: data has
457
457
  ``key_col | value_from | value_to | change | pct_change``.
458
458
  For *trend*: ``key_col | slope | r_squared | direction``.
459
459
 
@@ -550,15 +550,15 @@ class StatsNamespace:
550
550
  """Anomaly detection in cross-sectional or timeseries mode.
551
551
 
552
552
  Args:
553
- column: Value column to analyse.
554
- mode: ``"cross_sectional"`` or ``"timeseries"``.
555
- method: An :class:`~core_lens.base.namespaces.stats.AnomalyCrossMethod` or :class:`~core_lens.base.namespaces.stats.AnomalyTsMethod` enum value.
556
- baseline: ``(from_year, to_year)`` inclusive. Required for
553
+ column (str): Value column to analyse.
554
+ mode (str): ``"cross_sectional"`` or ``"timeseries"``.
555
+ method (AnomalyCrossMethod | AnomalyTsMethod): An :class:`~core_lens.base.namespaces.stats.AnomalyCrossMethod` or :class:`~core_lens.base.namespaces.stats.AnomalyTsMethod` enum value.
556
+ baseline (tuple[int, int] | None, optional): ``(from_year, to_year)`` inclusive. Required for
557
557
  timeseries; optional for cross-sectional.
558
- threshold: Sigma / score threshold for anomaly flag (default 2.0).
558
+ threshold (float, optional): Sigma / score threshold for anomaly flag (default 2.0).
559
559
 
560
560
  Returns:
561
- Result whose data has ``key_col | anomaly_score | is_anomaly``
561
+ Result: Result whose data has ``key_col | anomaly_score | is_anomaly``
562
562
  (cross-sectional) or ``key_col | year | anomaly_score | is_anomaly``
563
563
  (timeseries, baseline period excluded).
564
564
 
@@ -78,7 +78,7 @@ class Result:
78
78
  """Return the underlying ``pl.DataFrame``.
79
79
 
80
80
  Returns:
81
- The materialised data frame.
81
+ pl.DataFrame: The materialised data frame.
82
82
  """
83
83
  return self.data
84
84
 
@@ -89,7 +89,7 @@ class Result:
89
89
  :meth:`with_geometry` first on non-static results.
90
90
 
91
91
  Returns:
92
- A ``geopandas.GeoDataFrame`` built from :attr:`data`.
92
+ gpd.GeoDataFrame: A ``geopandas.GeoDataFrame`` built from :attr:`data`.
93
93
 
94
94
  Raises:
95
95
  TypeError: If :attr:`has_geometry` is ``False``.
@@ -118,7 +118,7 @@ class Result:
118
118
  are responsible for collecting and wrapping the output themselves.
119
119
 
120
120
  Returns:
121
- A lazy frame backed by :attr:`data`.
121
+ pl.LazyFrame: A lazy frame backed by :attr:`data`.
122
122
  """
123
123
  return self.data.lazy()
124
124
 
@@ -134,7 +134,7 @@ class Result:
134
134
  ``result.plot.choropleth()``.
135
135
 
136
136
  Returns:
137
- A new :class:`Result` with the geometry column merged in and
137
+ Result: A new :class:`Result` with the geometry column merged in and
138
138
  ``has_geometry=True``. If ``has_geometry`` is already ``True``,
139
139
  returns ``self`` unchanged.
140
140
  """
@@ -164,11 +164,11 @@ class Result:
164
164
  .derive("drought_flag", pl.when(pl.col("rainfall") < 500).then(1).otherwise(0))
165
165
 
166
166
  Args:
167
- name: Name for the new column.
168
- expr: A Polars expression that evaluates to the column values.
167
+ name (str): Name for the new column.
168
+ expr (pl.Expr): A Polars expression that evaluates to the column values.
169
169
 
170
170
  Returns:
171
- A new :class:`Result` with ``name`` appended to :attr:`data`.
171
+ Result: A new :class:`Result` with ``name`` appended to :attr:`data`.
172
172
  """
173
173
  new_data = self.data.with_columns(expr.alias(name))
174
174
  return self._replace(data=new_data)
@@ -201,9 +201,9 @@ class Result:
201
201
  - ✅
202
202
 
203
203
  Args:
204
- *exprs: One or more Polars aggregation expressions
204
+ *exprs (pl.Expr): One or more Polars aggregation expressions
205
205
  (e.g. ``pl.mean("ndvi")``, ``pl.max("rainfall")``).
206
- by: Grouping dimension. ``None`` collapses all rows to one.
206
+ by (str | None, optional): Grouping dimension. ``None`` collapses all rows to one.
207
207
  ``"year"`` groups by entity + year and is valid for both
208
208
  annual and fortnightly resolution.
209
209
  Other temporal groupings (``"month"``, ``"year_month"``,
@@ -211,7 +211,7 @@ class Result:
211
211
  ``resolution="fortnightly"``.
212
212
 
213
213
  Returns:
214
- A new :class:`Result` whose ``data`` is the aggregated frame.
214
+ Result: A new :class:`Result` whose ``data`` is the aggregated frame.
215
215
 
216
216
  Raises:
217
217
  ValueError: If ``by`` is incompatible with :attr:`resolution`, or
@@ -257,6 +257,9 @@ class Result:
257
257
  result.stats.describe()
258
258
  result.stats.correlate(["ndvi", "rainfall"], method="spearman")
259
259
  result.stats.anomaly("ndvi", mode="cross_sectional", method="zscore")
260
+
261
+ Returns:
262
+ StatsNamespace: The statistical analysis namespace.
260
263
  """
261
264
  from core_lens.base.namespaces.stats import StatsNamespace
262
265
 
@@ -272,6 +275,9 @@ class Result:
272
275
 
273
276
  result.plot.choropleth("ndvi")
274
277
  result.plot.timeseries(x="year", y="rainfall")
278
+
279
+ Returns:
280
+ PlotNamespace: The visualisation namespace.
275
281
  """
276
282
  from core_lens.base.namespaces.plot import PlotNamespace
277
283