core-lens 0.1.dev83__tar.gz → 0.1.dev88__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.dev83 → core_lens-0.1.dev88}/PKG-INFO +1 -1
  2. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/src/core_lens/_version.py +2 -2
  3. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/src/core_lens/base/namespaces/plot.py +12 -0
  4. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/src/core_lens/base/namespaces/stats.py +58 -3
  5. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/src/core_lens/base/result.py +2 -2
  6. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/src/core_lens/utils/season.py +20 -1
  7. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/tests/unit/test_season.py +30 -0
  8. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/uv.lock +325 -311
  9. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/.github/pull_request_template.md +0 -0
  10. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/.github/workflows/ci.yml +0 -0
  11. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/.github/workflows/gh-pages.yml +0 -0
  12. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/.github/workflows/pre-release.yml +0 -0
  13. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/.github/workflows/release.yml +0 -0
  14. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/.gitignore +0 -0
  15. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/.gitmessage +0 -0
  16. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/.pre-commit-config.yaml +0 -0
  17. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/.python-version +0 -0
  18. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/CONTRIBUTING.md +0 -0
  19. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/LICENSE +0 -0
  20. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/README.md +0 -0
  21. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/SKILLS.md +0 -0
  22. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/docs/Makefile +0 -0
  23. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/docs/make.bat +0 -0
  24. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/docs/source/concepts.md +0 -0
  25. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/docs/source/conf.py +0 -0
  26. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/docs/source/index.rst +0 -0
  27. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/docs/source/intro.md +0 -0
  28. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/docs/source/plots.md +0 -0
  29. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/docs/source/plugins.md +0 -0
  30. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/docs/source/queries.md +0 -0
  31. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/docs/source/quickstart.md +0 -0
  32. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/docs/source/stats.md +0 -0
  33. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/examples/demo_mws.py +0 -0
  34. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/examples/demo_tehsil.py +0 -0
  35. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/hooks/mypy.sh +0 -0
  36. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/hooks/no-parquet-outside-fixtures.sh +0 -0
  37. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/hooks/pytest.sh +0 -0
  38. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/pyproject.toml +0 -0
  39. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/src/core_lens/__init__.py +0 -0
  40. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/src/core_lens/__main__.py +0 -0
  41. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/src/core_lens/aoi.py +0 -0
  42. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/src/core_lens/base/__init__.py +0 -0
  43. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/src/core_lens/base/entity.py +0 -0
  44. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/src/core_lens/base/namespaces/__init__.py +0 -0
  45. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/src/core_lens/base/view.py +0 -0
  46. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/src/core_lens/entities/__init__.py +0 -0
  47. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/src/core_lens/entities/mws.py +0 -0
  48. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/src/core_lens/entities/tehsil.py +0 -0
  49. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/src/core_lens/export/__init__.py +0 -0
  50. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/src/core_lens/export/formats.py +0 -0
  51. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/src/core_lens/py.typed +0 -0
  52. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/src/core_lens/schema/__init__.py +0 -0
  53. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/src/core_lens/schema/detection.py +0 -0
  54. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/src/core_lens/schema/profile.py +0 -0
  55. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/src/core_lens/utils/__init__.py +0 -0
  56. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/src/core_lens/utils/polars_utils.py +0 -0
  57. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/src/core_lens/utils/spatial.py +0 -0
  58. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/tests/fixtures/generate_fixtures.py +0 -0
  59. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/tests/unit/conftest.py +0 -0
  60. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/tests/unit/test_aoi.py +0 -0
  61. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/tests/unit/test_entities.py +0 -0
  62. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/tests/unit/test_entity.py +0 -0
  63. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/tests/unit/test_export.py +0 -0
  64. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/tests/unit/test_main.py +0 -0
  65. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/tests/unit/test_plot.py +0 -0
  66. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/tests/unit/test_polars_utils.py +0 -0
  67. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/tests/unit/test_profile.py +0 -0
  68. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/tests/unit/test_result.py +0 -0
  69. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/tests/unit/test_schema_detection.py +0 -0
  70. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/tests/unit/test_schema_profile.py +0 -0
  71. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/tests/unit/test_season_config.py +0 -0
  72. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/tests/unit/test_spatial.py +0 -0
  73. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/tests/unit/test_stats.py +0 -0
  74. {core_lens-0.1.dev83 → core_lens-0.1.dev88}/tests/unit/test_view.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: core-lens
3
- Version: 0.1.dev83
3
+ Version: 0.1.dev88
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.dev83'
22
- __version_tuple__ = version_tuple = (0, 1, 'dev83')
21
+ __version__ = version = '0.1.dev88'
22
+ __version_tuple__ = version_tuple = (0, 1, 'dev88')
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -7,6 +7,18 @@ from typing import TYPE_CHECKING, Any
7
7
 
8
8
 
9
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
+
10
22
  YEAR = "year"
11
23
  MONTH = "month"
12
24
  SEASON = "season"
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import warnings
5
6
  from enum import Enum
6
7
  from itertools import combinations
7
8
  from typing import TYPE_CHECKING, Any, cast
@@ -14,12 +15,30 @@ if TYPE_CHECKING:
14
15
 
15
16
 
16
17
  class CorrelateMethod(Enum):
18
+ """Correlation methods.
19
+
20
+ Attributes:
21
+ PEARSON: Pearson correlation coefficient.
22
+ SPEARMAN: Spearman rank correlation.
23
+ KENDALL: Kendall Tau correlation.
24
+ """
25
+
17
26
  PEARSON = "pearson"
18
27
  SPEARMAN = "spearman"
19
28
  KENDALL = "kendall"
20
29
 
21
30
 
22
31
  class TestMethod(Enum):
32
+ """Statistical hypothesis testing methods.
33
+
34
+ Attributes:
35
+ T_TEST: Student's t-test.
36
+ MANN_WHITNEY: Mann-Whitney U test.
37
+ WILCOXON: Wilcoxon signed-rank test.
38
+ KS: Kolmogorov-Smirnov test.
39
+ CHI_SQUARE: Chi-square test.
40
+ """
41
+
23
42
  __test__ = False
24
43
  T_TEST = "t-test"
25
44
  MANN_WHITNEY = "mann-whitney"
@@ -29,12 +48,29 @@ class TestMethod(Enum):
29
48
 
30
49
 
31
50
  class ChangeMethod(Enum):
51
+ """Methods for calculating change over time.
52
+
53
+ Attributes:
54
+ ABSOLUTE: Absolute difference between periods.
55
+ PERCENTAGE: Percentage change between periods.
56
+ TREND: Linear trend over time.
57
+ """
58
+
32
59
  ABSOLUTE = "absolute"
33
60
  PERCENTAGE = "percentage"
34
61
  TREND = "trend"
35
62
 
36
63
 
37
64
  class AnomalyCrossMethod(Enum):
65
+ """Methods for detecting cross-sectional anomalies.
66
+
67
+ Attributes:
68
+ ZSCORE: Z-score method.
69
+ IQR: Interquartile range method.
70
+ PERCENTILE: Percentile-based method.
71
+ THRESHOLD: Fixed threshold method.
72
+ """
73
+
38
74
  ZSCORE = "zscore"
39
75
  IQR = "iqr"
40
76
  PERCENTILE = "percentile"
@@ -42,12 +78,29 @@ class AnomalyCrossMethod(Enum):
42
78
 
43
79
 
44
80
  class AnomalyTsMethod(Enum):
81
+ """Methods for detecting time-series anomalies.
82
+
83
+ Attributes:
84
+ STL: Seasonal-Trend decomposition using LOESS.
85
+ CUSUM: Cumulative sum control chart.
86
+ MAD: Median Absolute Deviation.
87
+ """
88
+
45
89
  STL = "stl"
46
90
  CUSUM = "cusum"
47
91
  MAD = "mad"
48
92
 
49
93
 
50
94
  class SimilarityMethod(Enum):
95
+ """Methods for calculating similarity or distance.
96
+
97
+ Attributes:
98
+ EUCLIDEAN: Euclidean distance.
99
+ COSINE: Cosine similarity.
100
+ MAHALANOBIS: Mahalanobis distance.
101
+ MANHATTAN: Manhattan distance.
102
+ """
103
+
51
104
  EUCLIDEAN = "euclidean"
52
105
  COSINE = "cosine"
53
106
  MAHALANOBIS = "mahalanobis"
@@ -871,9 +924,11 @@ class StatsNamespace:
871
924
  tidx = ids.index(target)
872
925
 
873
926
  # z-score normalise before computing distances.
874
- means = np.nanmean(mat, axis=0)
875
- stds = np.nanstd(mat, axis=0, ddof=1)
876
- stds[stds == 0] = 1.0
927
+ with warnings.catch_warnings():
928
+ warnings.simplefilter("ignore", category=RuntimeWarning)
929
+ means = np.nanmean(mat, axis=0)
930
+ stds = np.nanstd(mat, axis=0, ddof=1)
931
+ stds[np.isnan(stds) | (stds == 0)] = 1.0
877
932
  norm = (mat - means) / stds
878
933
  tvec = norm[tidx]
879
934
 
@@ -235,12 +235,12 @@ class Result:
235
235
  )
236
236
 
237
237
  if by is None:
238
- new_data = self.data.group_by(self.key_cols).agg(exprs)
238
+ new_data = self.data.group_by(self.key_cols).agg(*exprs)
239
239
  else:
240
240
  # Temporal grouping columns are expected to already exist on the
241
241
  # frame (added by the materialisation layer from the time column).
242
242
  group_cols = self.key_cols + [by]
243
- new_data = self.data.group_by(group_cols).agg(exprs)
243
+ new_data = self.data.group_by(group_cols).agg(*exprs)
244
244
 
245
245
  return self._replace(data=new_data)
246
246
 
@@ -81,7 +81,26 @@ def _date_range_expr(time_col: str, start: str, end: str) -> pl.Expr:
81
81
  start_date = datetime.date.fromisoformat(start)
82
82
  end_date = datetime.date.fromisoformat(end)
83
83
  col = pl.col(time_col)
84
- return col.is_between(pl.lit(start_date), pl.lit(end_date))
84
+
85
+ # We use cast to string to avoid ComputeError when the column is an integer,
86
+ # but still allow exact date comparison.
87
+ # For integer year columns (e.g., 2020), cast to string yields "2020".
88
+ # For date columns, cast to string yields "2020-01-01".
89
+ # So we compare strings directly! Lexicographical string comparison works perfectly for ISO-8601 dates and 4-digit years.
90
+ # For year integers, "2020" >= "2020-01-01" is False.
91
+ # Wait, "2020" < "2020-01-01". So if it's an integer, "2020" will not be between "2020-01-01" and "2023-12-31" because "2020" is less than "2020-01-01".
92
+ # To fix this, we can extract the year for the integer comparison using a regex or length check.
93
+ # Better: check the length of the casted string. If 4, it's a year.
94
+ s_col = col.cast(pl.String)
95
+ is_year_int = s_col.str.len_bytes() == 4
96
+
97
+ # Use strict=False so "2020-01-01" casts to null instead of raising ComputeError
98
+ year_expr = s_col.cast(pl.Int32, strict=False).is_between(
99
+ start_date.year, end_date.year
100
+ )
101
+ date_expr = s_col.is_between(pl.lit(start), pl.lit(end))
102
+
103
+ return pl.when(is_year_int).then(year_expr).otherwise(date_expr)
85
104
 
86
105
 
87
106
  def _season_expr(
@@ -31,6 +31,36 @@ class TestResolveTimeFilter:
31
31
  assert "date" in str(expr)
32
32
  # We can't strictly assert string contents, but we know it's built successfully
33
33
 
34
+ def test_date_range_evaluation_on_types(self, season_config: SeasonConfig) -> None:
35
+ df = pl.DataFrame(
36
+ {
37
+ "year_int": [2019, 2020, 2021],
38
+ "date_col": [
39
+ datetime.date(2019, 6, 1),
40
+ datetime.date(2020, 1, 15),
41
+ datetime.date(2021, 1, 1),
42
+ ],
43
+ }
44
+ )
45
+
46
+ # Test integer year column
47
+ expr_int = resolve_time_filter(
48
+ {"start": "2020-01-01", "end": "2020-12-31"},
49
+ time_col="year_int",
50
+ season_config=season_config,
51
+ )
52
+ res_int = df.filter(expr_int)
53
+ assert res_int["year_int"].to_list() == [2020]
54
+
55
+ # Test Date column
56
+ expr_date = resolve_time_filter(
57
+ {"start": "2020-01-01", "end": "2020-12-31"},
58
+ time_col="date_col",
59
+ season_config=season_config,
60
+ )
61
+ res_date = df.filter(expr_date)
62
+ assert res_date["year_int"].to_list() == [2020]
63
+
34
64
  def test_season_no_year(self, season_config: SeasonConfig) -> None:
35
65
  expr = resolve_time_filter(
36
66
  {"season": "kharif"},