core-lens 0.1.dev74__tar.gz → 0.1.dev86__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 (74) hide show
  1. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/PKG-INFO +1 -1
  2. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/SKILLS.md +2 -2
  3. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/docs/source/queries.md +4 -2
  4. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/docs/source/stats.md +19 -9
  5. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/examples/demo_mws.py +1 -9
  6. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/examples/demo_tehsil.py +1 -1
  7. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/src/core_lens/_version.py +2 -2
  8. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/src/core_lens/base/namespaces/plot.py +35 -13
  9. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/src/core_lens/base/namespaces/stats.py +154 -65
  10. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/src/core_lens/base/view.py +21 -13
  11. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/tests/unit/test_plot.py +2 -1
  12. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/tests/unit/test_stats.py +79 -41
  13. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/tests/unit/test_view.py +13 -11
  14. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/uv.lock +325 -311
  15. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/.github/pull_request_template.md +0 -0
  16. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/.github/workflows/ci.yml +0 -0
  17. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/.github/workflows/gh-pages.yml +0 -0
  18. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/.github/workflows/pre-release.yml +0 -0
  19. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/.github/workflows/release.yml +0 -0
  20. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/.gitignore +0 -0
  21. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/.gitmessage +0 -0
  22. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/.pre-commit-config.yaml +0 -0
  23. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/.python-version +0 -0
  24. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/CONTRIBUTING.md +0 -0
  25. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/LICENSE +0 -0
  26. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/README.md +0 -0
  27. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/docs/Makefile +0 -0
  28. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/docs/make.bat +0 -0
  29. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/docs/source/concepts.md +0 -0
  30. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/docs/source/conf.py +0 -0
  31. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/docs/source/index.rst +0 -0
  32. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/docs/source/intro.md +0 -0
  33. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/docs/source/plots.md +0 -0
  34. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/docs/source/plugins.md +0 -0
  35. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/docs/source/quickstart.md +0 -0
  36. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/hooks/mypy.sh +0 -0
  37. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/hooks/no-parquet-outside-fixtures.sh +0 -0
  38. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/hooks/pytest.sh +0 -0
  39. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/pyproject.toml +0 -0
  40. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/src/core_lens/__init__.py +0 -0
  41. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/src/core_lens/__main__.py +0 -0
  42. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/src/core_lens/aoi.py +0 -0
  43. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/src/core_lens/base/__init__.py +0 -0
  44. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/src/core_lens/base/entity.py +0 -0
  45. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/src/core_lens/base/namespaces/__init__.py +0 -0
  46. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/src/core_lens/base/result.py +0 -0
  47. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/src/core_lens/entities/__init__.py +0 -0
  48. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/src/core_lens/entities/mws.py +0 -0
  49. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/src/core_lens/entities/tehsil.py +0 -0
  50. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/src/core_lens/export/__init__.py +0 -0
  51. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/src/core_lens/export/formats.py +0 -0
  52. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/src/core_lens/py.typed +0 -0
  53. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/src/core_lens/schema/__init__.py +0 -0
  54. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/src/core_lens/schema/detection.py +0 -0
  55. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/src/core_lens/schema/profile.py +0 -0
  56. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/src/core_lens/utils/__init__.py +0 -0
  57. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/src/core_lens/utils/polars_utils.py +0 -0
  58. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/src/core_lens/utils/season.py +0 -0
  59. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/src/core_lens/utils/spatial.py +0 -0
  60. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/tests/fixtures/generate_fixtures.py +0 -0
  61. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/tests/unit/conftest.py +0 -0
  62. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/tests/unit/test_aoi.py +0 -0
  63. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/tests/unit/test_entities.py +0 -0
  64. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/tests/unit/test_entity.py +0 -0
  65. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/tests/unit/test_export.py +0 -0
  66. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/tests/unit/test_main.py +0 -0
  67. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/tests/unit/test_polars_utils.py +0 -0
  68. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/tests/unit/test_profile.py +0 -0
  69. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/tests/unit/test_result.py +0 -0
  70. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/tests/unit/test_schema_detection.py +0 -0
  71. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/tests/unit/test_schema_profile.py +0 -0
  72. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/tests/unit/test_season.py +0 -0
  73. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/tests/unit/test_season_config.py +0 -0
  74. {core_lens-0.1.dev74 → core_lens-0.1.dev86}/tests/unit/test_spatial.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: core-lens
3
- Version: 0.1.dev74
3
+ Version: 0.1.dev86
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
@@ -91,7 +91,7 @@ Calculates correlation between columns either across entities or across time. Su
91
91
  result.stats.correlate(
92
92
  columns=["ndvi", "rainfall", "temperature"],
93
93
  method="pearson",
94
- across="mws"
94
+ across="entity"
95
95
  )
96
96
  ```
97
97
 
@@ -112,7 +112,7 @@ result.stats.change(column="ndvi", from_period=2010, to_period=2023, method="tre
112
112
  ```
113
113
 
114
114
  ### Anomaly Detection
115
- Identifies anomalies cross-sectionally (vs other entities) or over time.
115
+ Identifies anomalies cross-sectionally (vs other entities) or over time.
116
116
  - **Cross-sectional methods:** `"zscore"`, `"iqr"`, `"percentile"`, `"threshold"`.
117
117
  - **Time-series methods:** `"stl"`, `"cusum"`, `"mad"`.
118
118
  ```python
@@ -9,10 +9,12 @@ Filter time-series data using exact dates or agronomic seasons:
9
9
  annual_data = aoi.mws.between("2010-01-01", "2023-12-31").annual
10
10
 
11
11
  # Season-based filtering (defaults to Kharif, Rabi, Zaid)
12
- kharif_2020 = aoi.mws.between(season="kharif", year=2020).fortnightly
12
+ from core_lens.base.view import Season
13
+
14
+ kharif_2020 = aoi.mws.between(season=Season.KHARIF, year=2020).fortnightly
13
15
 
14
16
  # Current season based on today's date
15
- current = aoi.mws.between(season="current").fortnightly
17
+ current = aoi.mws.between(season=Season.CURRENT).fortnightly
16
18
  ```
17
19
 
18
20
  ## Aggregations & Derived Columns
@@ -17,10 +17,12 @@ desc_entity = res.stats.describe(by="entity")
17
17
  Find temporal or spatial correlations between variables:
18
18
 
19
19
  ```python
20
+ from core_lens.base.namespaces.stats import CorrelateMethod
21
+
20
22
  corr = res.stats.correlate(
21
23
  columns=["ndvi", "rainfall", "temperature"],
22
- method="pearson", # or "spearman", "kendall"
23
- across="mws" # correlate across entities or time
24
+ method=CorrelateMethod.PEARSON, # or SPEARMAN, KENDALL
25
+ across="entity" # correlate across entities or time
24
26
  )
25
27
  ```
26
28
 
@@ -29,18 +31,20 @@ corr = res.stats.correlate(
29
31
  Test for significant differences between groups or periods:
30
32
 
31
33
  ```python
34
+ from core_lens.base.namespaces.stats import TestMethod
35
+
32
36
  # Group-based testing
33
37
  test_res = res.stats.test(
34
38
  column="cropping_intensity",
35
39
  groups="temperature_zone",
36
- method="mann-whitney"
40
+ method=TestMethod.MANN_WHITNEY
37
41
  )
38
42
 
39
43
  # Period-based testing
40
44
  test_period = res.stats.test(
41
45
  column="ndvi",
42
46
  periods=[(2010, 2015), (2016, 2023)],
43
- method="t-test"
47
+ method=TestMethod.T_TEST
44
48
  )
45
49
  ```
46
50
 
@@ -49,12 +53,14 @@ test_period = res.stats.test(
49
53
  Analyse absolute, percentage, or trend changes over time:
50
54
 
51
55
  ```python
56
+ from core_lens.base.namespaces.stats import ChangeMethod
57
+
52
58
  # Trend over time
53
59
  trend = res.stats.change(
54
60
  column="ndvi",
55
61
  from_period=2010,
56
62
  to_period=2023,
57
- method="trend"
63
+ method=ChangeMethod.TREND
58
64
  )
59
65
 
60
66
  # Absolute or percentage change
@@ -62,7 +68,7 @@ pct_change = res.stats.change(
62
68
  column="tree_cover",
63
69
  from_period=2018,
64
70
  to_period=2023,
65
- method="percentage"
71
+ method=ChangeMethod.PERCENTAGE
66
72
  )
67
73
  ```
68
74
 
@@ -71,11 +77,13 @@ pct_change = res.stats.change(
71
77
  Identify anomalies against a historical baseline or cross-sectionally:
72
78
 
73
79
  ```python
80
+ from core_lens.base.namespaces.stats import AnomalyTsMethod, AnomalyCrossMethod
81
+
74
82
  # Timeseries anomaly against its own history
75
83
  ts_anomalies = res.stats.anomaly(
76
84
  column="ndvi",
77
85
  mode="timeseries",
78
- method="stl",
86
+ method=AnomalyTsMethod.STL,
79
87
  baseline=(2010, 2018)
80
88
  )
81
89
 
@@ -83,7 +91,7 @@ ts_anomalies = res.stats.anomaly(
83
91
  cross_anomalies = res.stats.anomaly(
84
92
  column="ndvi",
85
93
  mode="cross_sectional",
86
- method="zscore",
94
+ method=AnomalyCrossMethod.ZSCORE,
87
95
  baseline=(2010, 2020)
88
96
  )
89
97
  ```
@@ -93,13 +101,15 @@ cross_anomalies = res.stats.anomaly(
93
101
  Find entities similar to a target entity across multiple dimensions:
94
102
 
95
103
  ```python
104
+ from core_lens.base.namespaces.stats import SimilarityMethod
105
+
96
106
  similar = res.stats.similarity(
97
107
  target="13_551",
98
108
  columns={
99
109
  "rainfall": ("annual", {"year": 2018}),
100
110
  "ndvi": ("fortnightly", {"season": "kharif", "year": 2020})
101
111
  },
102
- method="euclidean",
112
+ method=SimilarityMethod.EUCLIDEAN,
103
113
  top_n=10
104
114
  )
105
115
  ```
@@ -171,7 +171,7 @@ print(desc_annual_by_entity.df().head(5))
171
171
  corr = result_fn.stats.correlate(
172
172
  columns=["df_precipitation", "df_et"],
173
173
  method="pearson",
174
- across="mws",
174
+ across="entity",
175
175
  )
176
176
  print("\nCorrelation (Precipitation vs ET):")
177
177
  print(corr.df())
@@ -279,14 +279,6 @@ except Exception:
279
279
 
280
280
  # ── 23. Export ───────────────────────────────────────────────────────────────
281
281
 
282
- # Tabular (JSON or standard Parquet) — drop geometry first
283
- # The static table might have 'geom' or 'geometry' as the geometry column.
284
- # mws.py specifies geometry_col as "geometry".
285
- geom_col = MWSEntity().schema_profile.geometry_col
286
- pl_df = result_static_derived.df().drop(geom_col)
287
- pl_df.write_parquet("output_mws.parquet")
288
- print("\nParquet written: output_mws.parquet")
289
-
290
282
  # GeoParquet — preserves geometry
291
283
  geoparquet(result_static_derived, "output_mws.geoparquet")
292
284
  print("GeoParquet written: output_mws.geoparquet")
@@ -164,7 +164,7 @@ print(desc_by_entity.df().head(5))
164
164
  corr = result_with_area.stats.correlate(
165
165
  columns=["area_km2", "Shape_Leng", "compactness"],
166
166
  method="spearman",
167
- across="mws",
167
+ across="entity",
168
168
  )
169
169
  print("\nCorrelation (Spearman):")
170
170
  print(corr.df())
@@ -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.dev74'
22
- __version_tuple__ = version_tuple = (0, 1, 'dev74')
21
+ __version__ = version = '0.1.dev86'
22
+ __version_tuple__ = version_tuple = (0, 1, 'dev86')
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -2,8 +2,29 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from enum import Enum
5
6
  from typing import TYPE_CHECKING, Any
6
7
 
8
+
9
+ class SubplotOn(Enum):
10
+ """Temporal dimensions to split plot data across.
11
+
12
+ Used to specify how data should be partitioned into subplots or map layers
13
+ based on temporal columns added during materialisation.
14
+
15
+ Attributes:
16
+ YEAR: Split data by year.
17
+ MONTH: Split data by month.
18
+ SEASON: Split data by meteorological season.
19
+ SEASON_YEAR: Split data by season and year.
20
+ """
21
+
22
+ YEAR = "year"
23
+ MONTH = "month"
24
+ SEASON = "season"
25
+ SEASON_YEAR = "season_year"
26
+
27
+
7
28
  if TYPE_CHECKING:
8
29
  import lonboard
9
30
  from core_lens.base.result import Result
@@ -50,7 +71,9 @@ class PlotNamespace:
50
71
  def __init__(self, result: "Result") -> None:
51
72
  self.result = result
52
73
 
53
- def choropleth(self, column: str, subplot_on: str | None = None) -> "lonboard.Map":
74
+ def choropleth(
75
+ self, column: str, subplot_on: SubplotOn | None = None
76
+ ) -> "lonboard.Map":
54
77
  """Render an interactive choropleth map using Lonboard.
55
78
 
56
79
  If the Result does not already have geometry, it will be attached
@@ -59,8 +82,7 @@ class PlotNamespace:
59
82
  Args:
60
83
  column: The column to use for colour mapping.
61
84
  subplot_on: Optional temporal dimension to split data across.
62
- Valid values: ``\"year\"``, ``\"month\"``, ``\"season\"``,
63
- ``\"season_year\"``. When set, one layer is rendered per unique
85
+ A :class:`~core_lens.base.namespaces.plot.SubplotOn` enum value. When set, one layer is rendered per unique
64
86
  value of ``subplot_on`` in the data.
65
87
 
66
88
  Returns:
@@ -74,23 +96,23 @@ class PlotNamespace:
74
96
  import lonboard
75
97
  from lonboard.colormap import apply_continuous_cmap
76
98
 
77
- _VALID_SUBPLOT_ON = {"year", "month", "season", "season_year"}
78
-
79
- if subplot_on is not None and subplot_on not in _VALID_SUBPLOT_ON:
99
+ if subplot_on is not None and not isinstance(subplot_on, SubplotOn):
80
100
  raise ValueError(
81
- f"PlotNamespace.choropleth: Unknown subplot_on={subplot_on!r}. "
82
- f"Valid options: {sorted(_VALID_SUBPLOT_ON)}."
101
+ f"PlotNamespace.choropleth: subplot_on must be a SubplotOn enum. "
102
+ f"Valid options: {[e.name for e in SubplotOn]}."
83
103
  )
84
104
 
105
+ subplot_col = subplot_on.value if subplot_on is not None else None
106
+
85
107
  res = self.result if self.result.has_geometry else self.result.with_geometry()
86
108
  gdf = res.gdf()
87
109
 
88
110
  if column not in gdf.columns:
89
111
  raise ValueError(f"Column {column!r} not found in Result.")
90
112
 
91
- if subplot_on is not None and subplot_on not in gdf.columns:
113
+ if subplot_col is not None and subplot_col not in gdf.columns:
92
114
  raise ValueError(
93
- f"PlotNamespace.choropleth: subplot_on column {subplot_on!r} not found "
115
+ f"PlotNamespace.choropleth: subplot_on column {subplot_col!r} not found "
94
116
  "in Result. Ensure fortnightly data is materialised and temporal columns "
95
117
  "are present (they are added automatically by the materialisation layer)."
96
118
  )
@@ -120,13 +142,13 @@ class PlotNamespace:
120
142
  import matplotlib as mpl
121
143
  import numpy as np
122
144
 
123
- if subplot_on is not None:
145
+ if subplot_col is not None:
124
146
  # Render the most-recent/first unique value of subplot_on so that the
125
147
  # map is still useful. True multi-panel subplots are not supported by
126
148
  # Lonboard's single-Map API.
127
- unique_vals = sorted(gdf[subplot_on].dropna().unique().tolist())
149
+ unique_vals = sorted(gdf[subplot_col].dropna().unique().tolist())
128
150
  if unique_vals:
129
- gdf = gdf[gdf[subplot_on] == unique_vals[-1]].copy()
151
+ gdf = gdf[gdf[subplot_col] == unique_vals[-1]].copy()
130
152
 
131
153
  values = gdf[column].to_numpy()
132
154
  v_min, v_max = np.nanmin(values), np.nanmax(values)