core-lens 0.1.dev202__tar.gz → 0.1.dev203__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.dev202 → core_lens-0.1.dev203}/PKG-INFO +1 -1
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/src/core_lens/_version.py +2 -2
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/src/core_lens/base/view.py +35 -3
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/src/core_lens/utils/spatial.py +122 -12
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/tests/unit/test_spatial.py +4 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/.github/ISSUE_TEMPLATE/blank-proposal.yaml +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/.github/ISSUE_TEMPLATE/bug-report.yaml +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/.github/ISSUE_TEMPLATE/feature-request.yaml +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/.github/pull_request_template.md +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/.github/workflows/ci.yml +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/.github/workflows/gh-pages.yml +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/.github/workflows/pre-release.yml +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/.github/workflows/release.yml +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/.gitignore +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/.gitmessage +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/.pre-commit-config.yaml +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/.python-version +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/CONTRIBUTING.md +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/LICENSE +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/README.md +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/SKILLS.md +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/benchmarks/README.md +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/benchmarks/bench_aoi.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/benchmarks/bench_entity.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/benchmarks/bench_export.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/benchmarks/bench_polars_utils.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/benchmarks/bench_result.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/benchmarks/bench_schema.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/benchmarks/bench_season.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/benchmarks/bench_spatial.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/benchmarks/bench_view.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/benchmarks/run_all.sh +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/docs/Makefile +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/docs/make.bat +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/docs/source/concepts.md +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/docs/source/conf.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/docs/source/export.md +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/docs/source/index.rst +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/docs/source/intro.md +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/docs/source/logging.md +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/docs/source/plots.md +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/docs/source/plugins.md +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/docs/source/queries.md +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/docs/source/quickstart.md +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/docs/source/stats.md +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/examples/demo_mws.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/examples/demo_tehsil.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/hooks/mypy.sh +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/hooks/no-parquet-outside-fixtures.sh +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/hooks/pytest.sh +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/pyproject.toml +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/src/core_lens/__init__.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/src/core_lens/__main__.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/src/core_lens/aoi.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/src/core_lens/base/__init__.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/src/core_lens/base/entity.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/src/core_lens/base/namespaces/__init__.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/src/core_lens/base/namespaces/plot.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/src/core_lens/base/namespaces/stats.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/src/core_lens/base/result.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/src/core_lens/entities/__init__.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/src/core_lens/entities/farms.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/src/core_lens/entities/mws.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/src/core_lens/entities/tehsil.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/src/core_lens/entities/waterbody.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/src/core_lens/export/__init__.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/src/core_lens/export/formats.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/src/core_lens/py.typed +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/src/core_lens/schema/__init__.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/src/core_lens/schema/detection.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/src/core_lens/schema/profile.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/src/core_lens/utils/__init__.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/src/core_lens/utils/paths.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/src/core_lens/utils/polars_utils.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/src/core_lens/utils/season.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/tests/fixtures/generate_fixtures.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/tests/unit/conftest.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/tests/unit/test_aoi.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/tests/unit/test_entities.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/tests/unit/test_entity.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/tests/unit/test_export.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/tests/unit/test_main.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/tests/unit/test_plot.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/tests/unit/test_polars_utils.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/tests/unit/test_profile.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/tests/unit/test_result.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/tests/unit/test_schema_detection.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/tests/unit/test_schema_profile.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/tests/unit/test_season.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/tests/unit/test_season_config.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/tests/unit/test_stats.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/tests/unit/test_view.py +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/usage.md +0 -0
- {core_lens-0.1.dev202 → core_lens-0.1.dev203}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: core-lens
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.dev203
|
|
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
|
|
@@ -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.dev203'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 'dev203')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -234,7 +234,9 @@ class View:
|
|
|
234
234
|
season_config=self._season_config,
|
|
235
235
|
)
|
|
236
236
|
|
|
237
|
-
def spatial_join(
|
|
237
|
+
def spatial_join(
|
|
238
|
+
self, other: "BaseEntity | View", agg: dict[str, "str | tuple[str, str]"]
|
|
239
|
+
) -> "View":
|
|
238
240
|
"""Return a new View with a cross-entity join pending.
|
|
239
241
|
|
|
240
242
|
The join is recorded in the View's ``join_spec`` and computed only at
|
|
@@ -248,13 +250,43 @@ class View:
|
|
|
248
250
|
aoi.mws.spatial_join(aoi.waterbody, agg={"kharif": "mean"})
|
|
249
251
|
aoi.mws.spatial_join(aoi.waterbody.entity, agg={"kharif": "mean"})
|
|
250
252
|
|
|
253
|
+
Each ``agg`` value is either a plain aggregation string (column read
|
|
254
|
+
from the **static** file of ``other``) or a
|
|
255
|
+
``(aggregation, resolution)`` tuple when the column lives in a
|
|
256
|
+
temporal file::
|
|
257
|
+
|
|
258
|
+
# column from the static file — shorthand
|
|
259
|
+
aoi.mws.spatial_join(aoi.waterbody, agg={"area_in_ha": "sum"})
|
|
260
|
+
|
|
261
|
+
# column from the annual file
|
|
262
|
+
aoi.mws.spatial_join(
|
|
263
|
+
aoi.waterbody,
|
|
264
|
+
agg={"kharif": ("mean", "annual")},
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
# mix static and temporal in one call
|
|
268
|
+
aoi.mws.spatial_join(
|
|
269
|
+
aoi.waterbody,
|
|
270
|
+
agg={
|
|
271
|
+
"count": "count",
|
|
272
|
+
"area_in_ha": "sum",
|
|
273
|
+
"kharif": ("mean", "annual"),
|
|
274
|
+
"ndvi": ("mean", "sub_annual"),
|
|
275
|
+
},
|
|
276
|
+
)
|
|
277
|
+
|
|
251
278
|
Args:
|
|
252
279
|
other (BaseEntity | View): The secondary entity whose columns will
|
|
253
280
|
be joined and aggregated onto ``self``. Accepts either a
|
|
254
281
|
:class:`~core_lens.base.entity.BaseEntity` instance or a
|
|
255
282
|
:class:`View` (the latter is automatically unwrapped).
|
|
256
|
-
agg (dict[str, str]): Mapping of
|
|
257
|
-
|
|
283
|
+
agg (dict[str, str | tuple[str, str]]): Mapping of column to
|
|
284
|
+
aggregation. Each value is either a plain aggregation string
|
|
285
|
+
(``"count"``, ``"mean"``, ``"sum"``, ``"min"``, ``"max"``,
|
|
286
|
+
``"area"``, ``"first"``) — column assumed to be in the static
|
|
287
|
+
file — or a ``(aggregation, resolution)`` tuple where
|
|
288
|
+
resolution is one of ``"static"``, ``"annual"``,
|
|
289
|
+
``"sub_annual"``.
|
|
258
290
|
|
|
259
291
|
Returns:
|
|
260
292
|
View: A new lazy :class:`View` with the join spec recorded.
|
|
@@ -589,7 +589,7 @@ def execute_spatial_join(
|
|
|
589
589
|
primary_geom_col: str,
|
|
590
590
|
primary_geom_type: str,
|
|
591
591
|
other_entity: Any,
|
|
592
|
-
agg: dict[str, str],
|
|
592
|
+
agg: dict[str, str | tuple[str, str]],
|
|
593
593
|
other_entity_name: str,
|
|
594
594
|
) -> pl.DataFrame:
|
|
595
595
|
"""Materialise a cross-entity spatial join and return enriched DataFrame.
|
|
@@ -598,6 +598,23 @@ def execute_spatial_join(
|
|
|
598
598
|
``other_entity`` and aggregates the requested columns. Result columns are
|
|
599
599
|
named ``{other_entity_name}_{column_name}`` to avoid clashes.
|
|
600
600
|
|
|
601
|
+
Each entry in ``agg`` can be either:
|
|
602
|
+
|
|
603
|
+
* A plain string aggregation (column assumed to be in the static file)::
|
|
604
|
+
|
|
605
|
+
agg={"district": "first", "count": "count"}
|
|
606
|
+
|
|
607
|
+
* A ``(aggregation, resolution)`` tuple to pull the column from the
|
|
608
|
+
annual or sub_annual file of the other entity::
|
|
609
|
+
|
|
610
|
+
agg={"kharif": ("mean", "annual"), "ndvi": ("mean", "sub_annual")}
|
|
611
|
+
|
|
612
|
+
Valid resolution strings: ``"static"``, ``"annual"``, ``"sub_annual"``.
|
|
613
|
+
|
|
614
|
+
The geometry match is always performed against the static file (geometry
|
|
615
|
+
lives there). For temporal resolutions the matched keys are used to
|
|
616
|
+
filter the appropriate file before aggregation — no geometry I/O overhead.
|
|
617
|
+
|
|
601
618
|
Args:
|
|
602
619
|
primary_df (pl.DataFrame): The primary entity DataFrame (must contain
|
|
603
620
|
``primary_geom_col`` unless geometry is in a separate column).
|
|
@@ -606,10 +623,11 @@ def execute_spatial_join(
|
|
|
606
623
|
primary_geom_type (str): Geometry encoding — ``"wkb"`` or ``"wkt"``.
|
|
607
624
|
other_entity (BaseEntity): A :class:`~core_lens.base.entity.BaseEntity` instance
|
|
608
625
|
to join against.
|
|
609
|
-
agg (dict[str, str]): Mapping
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
``
|
|
626
|
+
agg (dict[str, str | tuple[str, str]]): Mapping of column to aggregation.
|
|
627
|
+
Each value is either a plain aggregation string (``"count"``,
|
|
628
|
+
``"mean"``, ``"sum"``, ``"min"``, ``"max"``, ``"area"``, ``"first"``)
|
|
629
|
+
or a ``(aggregation, resolution)`` tuple when the column lives in a
|
|
630
|
+
temporal file.
|
|
613
631
|
other_entity_name (str): Used to prefix result column names.
|
|
614
632
|
|
|
615
633
|
Returns:
|
|
@@ -666,9 +684,32 @@ def execute_spatial_join(
|
|
|
666
684
|
other_geom_col = other_profile.geometry_col
|
|
667
685
|
other_geom_type = other_profile.geometry_type
|
|
668
686
|
other_key_cols = other_entity.key_cols
|
|
687
|
+
other_so = getattr(other_entity, "_storage_options", None) or {}
|
|
688
|
+
|
|
689
|
+
# Normalise agg values: bare strings are treated as (fn, "static").
|
|
690
|
+
# Split by resolution so each file is read at most once.
|
|
691
|
+
_normalised: dict[str, tuple[str, str]] = {
|
|
692
|
+
col: (val if isinstance(val, tuple) else (val, "static"))
|
|
693
|
+
for col, val in agg.items()
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
# Columns that need to come from static (geometry + static agg cols).
|
|
697
|
+
static_agg_col_names = [
|
|
698
|
+
c
|
|
699
|
+
for c, (fn, res) in _normalised.items()
|
|
700
|
+
if res == "static" and c not in ("count", "area")
|
|
701
|
+
]
|
|
702
|
+
# Columns split by temporal resolution.
|
|
703
|
+
annual_agg_col_names = [
|
|
704
|
+
c for c, (fn, res) in _normalised.items() if res == "annual"
|
|
705
|
+
]
|
|
706
|
+
sub_annual_agg_col_names = [
|
|
707
|
+
c for c, (fn, res) in _normalised.items() if res == "sub_annual"
|
|
708
|
+
]
|
|
669
709
|
|
|
670
|
-
|
|
671
|
-
|
|
710
|
+
static_cols = list(
|
|
711
|
+
dict.fromkeys(other_key_cols + [other_geom_col] + static_agg_col_names)
|
|
712
|
+
)
|
|
672
713
|
|
|
673
714
|
if (
|
|
674
715
|
primary_total_box is not None
|
|
@@ -676,16 +717,15 @@ def execute_spatial_join(
|
|
|
676
717
|
):
|
|
677
718
|
# Pre-filter other_entity using its in-memory bounding box index
|
|
678
719
|
candidates = bbox_intersects_geometry(other_entity._index, primary_total_box)
|
|
679
|
-
other_so = getattr(other_entity, "_storage_options", None) or {}
|
|
680
720
|
if candidates.is_empty():
|
|
681
|
-
other_df = pl.DataFrame(schema={c: pl.String for c in
|
|
721
|
+
other_df = pl.DataFrame(schema={c: pl.String for c in static_cols})
|
|
682
722
|
else:
|
|
683
723
|
other_df = (
|
|
684
724
|
pl.scan_parquet(
|
|
685
725
|
other_static,
|
|
686
726
|
storage_options=other_so or None,
|
|
687
727
|
)
|
|
688
|
-
.select(
|
|
728
|
+
.select(static_cols)
|
|
689
729
|
.join(
|
|
690
730
|
candidates.select(other_key_cols).lazy(),
|
|
691
731
|
on=other_key_cols,
|
|
@@ -695,13 +735,83 @@ def execute_spatial_join(
|
|
|
695
735
|
.collect(engine="in-memory")
|
|
696
736
|
)
|
|
697
737
|
else:
|
|
698
|
-
other_so = getattr(other_entity, "_storage_options", None) or {}
|
|
699
738
|
other_df = pl.read_parquet(
|
|
700
739
|
other_static,
|
|
701
|
-
columns=
|
|
740
|
+
columns=static_cols,
|
|
702
741
|
storage_options=other_so or None,
|
|
703
742
|
)
|
|
704
743
|
|
|
744
|
+
# Load temporal agg columns (annual / sub_annual) keyed on other_key_cols,
|
|
745
|
+
# then left-join them onto other_df so the aggregation loop sees all values.
|
|
746
|
+
def _load_temporal_cols(
|
|
747
|
+
path: str | None, col_names: list[str]
|
|
748
|
+
) -> pl.DataFrame | None:
|
|
749
|
+
"""Read *col_names* from a temporal parquet, return None if unavailable."""
|
|
750
|
+
if not col_names or path is None:
|
|
751
|
+
return None
|
|
752
|
+
resolved = other_entity._resolve(path)
|
|
753
|
+
needed = list(dict.fromkeys(other_key_cols + col_names))
|
|
754
|
+
try:
|
|
755
|
+
return pl.read_parquet(
|
|
756
|
+
resolved,
|
|
757
|
+
columns=needed,
|
|
758
|
+
storage_options=other_so or None,
|
|
759
|
+
)
|
|
760
|
+
except Exception as exc:
|
|
761
|
+
logger.warning(
|
|
762
|
+
"execute_spatial_join: could not load temporal cols {} from {}: {}",
|
|
763
|
+
col_names,
|
|
764
|
+
resolved,
|
|
765
|
+
exc,
|
|
766
|
+
)
|
|
767
|
+
return None
|
|
768
|
+
|
|
769
|
+
annual_df = _load_temporal_cols(other_entity.annual_path, annual_agg_col_names)
|
|
770
|
+
sub_annual_df = _load_temporal_cols(
|
|
771
|
+
other_entity.sub_annual_path, sub_annual_agg_col_names
|
|
772
|
+
)
|
|
773
|
+
|
|
774
|
+
if annual_df is not None and not annual_df.is_empty():
|
|
775
|
+
# Aggregate temporal rows to one row per key before joining onto geometry.
|
|
776
|
+
_annual_agg_exprs = [
|
|
777
|
+
pl.col(c).mean().alias(c)
|
|
778
|
+
if _normalised[c][0] == "mean"
|
|
779
|
+
else pl.col(c).sum().alias(c)
|
|
780
|
+
if _normalised[c][0] == "sum"
|
|
781
|
+
else pl.col(c).min().alias(c)
|
|
782
|
+
if _normalised[c][0] == "min"
|
|
783
|
+
else pl.col(c).max().alias(c)
|
|
784
|
+
if _normalised[c][0] == "max"
|
|
785
|
+
else pl.col(c).first().alias(c)
|
|
786
|
+
for c in annual_agg_col_names
|
|
787
|
+
if c in annual_df.columns
|
|
788
|
+
]
|
|
789
|
+
if _annual_agg_exprs:
|
|
790
|
+
annual_df = annual_df.group_by(other_key_cols).agg(_annual_agg_exprs)
|
|
791
|
+
other_df = other_df.join(
|
|
792
|
+
annual_df, on=other_key_cols, how="left", maintain_order="left"
|
|
793
|
+
)
|
|
794
|
+
|
|
795
|
+
if sub_annual_df is not None and not sub_annual_df.is_empty():
|
|
796
|
+
_sub_agg_exprs = [
|
|
797
|
+
pl.col(c).mean().alias(c)
|
|
798
|
+
if _normalised[c][0] == "mean"
|
|
799
|
+
else pl.col(c).sum().alias(c)
|
|
800
|
+
if _normalised[c][0] == "sum"
|
|
801
|
+
else pl.col(c).min().alias(c)
|
|
802
|
+
if _normalised[c][0] == "min"
|
|
803
|
+
else pl.col(c).max().alias(c)
|
|
804
|
+
if _normalised[c][0] == "max"
|
|
805
|
+
else pl.col(c).first().alias(c)
|
|
806
|
+
for c in sub_annual_agg_col_names
|
|
807
|
+
if c in sub_annual_df.columns
|
|
808
|
+
]
|
|
809
|
+
if _sub_agg_exprs:
|
|
810
|
+
sub_annual_df = sub_annual_df.group_by(other_key_cols).agg(_sub_agg_exprs)
|
|
811
|
+
other_df = other_df.join(
|
|
812
|
+
sub_annual_df, on=other_key_cols, how="left", maintain_order="left"
|
|
813
|
+
)
|
|
814
|
+
|
|
705
815
|
if other_df.is_empty():
|
|
706
816
|
other_geoms = np.array([])
|
|
707
817
|
other_tree = shapely.STRtree([])
|
|
@@ -94,6 +94,8 @@ def test_execute_spatial_join(tmp_path: Any) -> None:
|
|
|
94
94
|
|
|
95
95
|
class DummyOther:
|
|
96
96
|
static_path = str(tmp_path / "other.parquet")
|
|
97
|
+
annual_path: str | None = None
|
|
98
|
+
sub_annual_path: str | None = None
|
|
97
99
|
key_cols = ["oid"]
|
|
98
100
|
schema_profile = SchemaProfile(
|
|
99
101
|
key_cols=["oid"],
|
|
@@ -202,6 +204,8 @@ def test_execute_spatial_join_no_match_and_agg(tmp_path: Any) -> None:
|
|
|
202
204
|
|
|
203
205
|
class DummyOther:
|
|
204
206
|
static_path = str(tmp_path / "other.parquet")
|
|
207
|
+
annual_path: str | None = None
|
|
208
|
+
sub_annual_path: str | None = None
|
|
205
209
|
key_cols = ["oid"]
|
|
206
210
|
schema_profile = SchemaProfile(
|
|
207
211
|
key_cols=["oid"],
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|