core-lens 0.1.dev74__tar.gz → 0.1.dev83__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.
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/PKG-INFO +1 -1
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/SKILLS.md +2 -2
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/docs/source/queries.md +4 -2
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/docs/source/stats.md +19 -9
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/examples/demo_mws.py +1 -9
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/examples/demo_tehsil.py +1 -1
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/src/core_lens/_version.py +2 -2
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/src/core_lens/base/namespaces/plot.py +23 -13
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/src/core_lens/base/namespaces/stats.py +96 -62
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/src/core_lens/base/view.py +21 -13
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/tests/unit/test_plot.py +2 -1
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/tests/unit/test_stats.py +79 -41
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/tests/unit/test_view.py +13 -11
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/.github/pull_request_template.md +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/.github/workflows/ci.yml +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/.github/workflows/gh-pages.yml +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/.github/workflows/pre-release.yml +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/.github/workflows/release.yml +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/.gitignore +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/.gitmessage +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/.pre-commit-config.yaml +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/.python-version +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/CONTRIBUTING.md +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/LICENSE +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/README.md +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/docs/Makefile +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/docs/make.bat +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/docs/source/concepts.md +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/docs/source/conf.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/docs/source/index.rst +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/docs/source/intro.md +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/docs/source/plots.md +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/docs/source/plugins.md +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/docs/source/quickstart.md +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/hooks/mypy.sh +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/hooks/no-parquet-outside-fixtures.sh +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/hooks/pytest.sh +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/pyproject.toml +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/src/core_lens/__init__.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/src/core_lens/__main__.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/src/core_lens/aoi.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/src/core_lens/base/__init__.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/src/core_lens/base/entity.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/src/core_lens/base/namespaces/__init__.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/src/core_lens/base/result.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/src/core_lens/entities/__init__.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/src/core_lens/entities/mws.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/src/core_lens/entities/tehsil.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/src/core_lens/export/__init__.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/src/core_lens/export/formats.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/src/core_lens/py.typed +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/src/core_lens/schema/__init__.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/src/core_lens/schema/detection.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/src/core_lens/schema/profile.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/src/core_lens/utils/__init__.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/src/core_lens/utils/polars_utils.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/src/core_lens/utils/season.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/src/core_lens/utils/spatial.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/tests/fixtures/generate_fixtures.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/tests/unit/conftest.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/tests/unit/test_aoi.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/tests/unit/test_entities.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/tests/unit/test_entity.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/tests/unit/test_export.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/tests/unit/test_main.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/tests/unit/test_polars_utils.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/tests/unit/test_profile.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/tests/unit/test_result.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/tests/unit/test_schema_detection.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/tests/unit/test_schema_profile.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/tests/unit/test_season.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/tests/unit/test_season_config.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/tests/unit/test_spatial.py +0 -0
- {core_lens-0.1.dev74 → core_lens-0.1.dev83}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: core-lens
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.dev83
|
|
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="
|
|
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
|
-
|
|
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=
|
|
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=
|
|
23
|
-
across="
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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="
|
|
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="
|
|
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.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 1, '
|
|
21
|
+
__version__ = version = '0.1.dev83'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 'dev83')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -2,8 +2,17 @@
|
|
|
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
|
+
YEAR = "year"
|
|
11
|
+
MONTH = "month"
|
|
12
|
+
SEASON = "season"
|
|
13
|
+
SEASON_YEAR = "season_year"
|
|
14
|
+
|
|
15
|
+
|
|
7
16
|
if TYPE_CHECKING:
|
|
8
17
|
import lonboard
|
|
9
18
|
from core_lens.base.result import Result
|
|
@@ -50,7 +59,9 @@ class PlotNamespace:
|
|
|
50
59
|
def __init__(self, result: "Result") -> None:
|
|
51
60
|
self.result = result
|
|
52
61
|
|
|
53
|
-
def choropleth(
|
|
62
|
+
def choropleth(
|
|
63
|
+
self, column: str, subplot_on: SubplotOn | None = None
|
|
64
|
+
) -> "lonboard.Map":
|
|
54
65
|
"""Render an interactive choropleth map using Lonboard.
|
|
55
66
|
|
|
56
67
|
If the Result does not already have geometry, it will be attached
|
|
@@ -59,8 +70,7 @@ class PlotNamespace:
|
|
|
59
70
|
Args:
|
|
60
71
|
column: The column to use for colour mapping.
|
|
61
72
|
subplot_on: Optional temporal dimension to split data across.
|
|
62
|
-
|
|
63
|
-
``\"season_year\"``. When set, one layer is rendered per unique
|
|
73
|
+
A :class:`~core_lens.base.namespaces.plot.SubplotOn` enum value. When set, one layer is rendered per unique
|
|
64
74
|
value of ``subplot_on`` in the data.
|
|
65
75
|
|
|
66
76
|
Returns:
|
|
@@ -74,23 +84,23 @@ class PlotNamespace:
|
|
|
74
84
|
import lonboard
|
|
75
85
|
from lonboard.colormap import apply_continuous_cmap
|
|
76
86
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if subplot_on is not None and subplot_on not in _VALID_SUBPLOT_ON:
|
|
87
|
+
if subplot_on is not None and not isinstance(subplot_on, SubplotOn):
|
|
80
88
|
raise ValueError(
|
|
81
|
-
f"PlotNamespace.choropleth:
|
|
82
|
-
f"Valid options: {
|
|
89
|
+
f"PlotNamespace.choropleth: subplot_on must be a SubplotOn enum. "
|
|
90
|
+
f"Valid options: {[e.name for e in SubplotOn]}."
|
|
83
91
|
)
|
|
84
92
|
|
|
93
|
+
subplot_col = subplot_on.value if subplot_on is not None else None
|
|
94
|
+
|
|
85
95
|
res = self.result if self.result.has_geometry else self.result.with_geometry()
|
|
86
96
|
gdf = res.gdf()
|
|
87
97
|
|
|
88
98
|
if column not in gdf.columns:
|
|
89
99
|
raise ValueError(f"Column {column!r} not found in Result.")
|
|
90
100
|
|
|
91
|
-
if
|
|
101
|
+
if subplot_col is not None and subplot_col not in gdf.columns:
|
|
92
102
|
raise ValueError(
|
|
93
|
-
f"PlotNamespace.choropleth: subplot_on column {
|
|
103
|
+
f"PlotNamespace.choropleth: subplot_on column {subplot_col!r} not found "
|
|
94
104
|
"in Result. Ensure fortnightly data is materialised and temporal columns "
|
|
95
105
|
"are present (they are added automatically by the materialisation layer)."
|
|
96
106
|
)
|
|
@@ -120,13 +130,13 @@ class PlotNamespace:
|
|
|
120
130
|
import matplotlib as mpl
|
|
121
131
|
import numpy as np
|
|
122
132
|
|
|
123
|
-
if
|
|
133
|
+
if subplot_col is not None:
|
|
124
134
|
# Render the most-recent/first unique value of subplot_on so that the
|
|
125
135
|
# map is still useful. True multi-panel subplots are not supported by
|
|
126
136
|
# Lonboard's single-Map API.
|
|
127
|
-
unique_vals = sorted(gdf[
|
|
137
|
+
unique_vals = sorted(gdf[subplot_col].dropna().unique().tolist())
|
|
128
138
|
if unique_vals:
|
|
129
|
-
gdf = gdf[gdf[
|
|
139
|
+
gdf = gdf[gdf[subplot_col] == unique_vals[-1]].copy()
|
|
130
140
|
|
|
131
141
|
values = gdf[column].to_numpy()
|
|
132
142
|
v_min, v_max = np.nanmin(values), np.nanmax(values)
|