Skip to content

Commit 2ed5020

Browse files
committed
core: update datetime handling
1 parent 9f7d81c commit 2ed5020

4 files changed

Lines changed: 64 additions & 24 deletions

File tree

src/damast/core/data_description.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
"""
44
from __future__ import annotations
55

6+
import datetime as dt
7+
import logging
68
import math
79
from abc import ABC, abstractmethod
810
from typing import Any, Dict, List
@@ -13,6 +15,7 @@
1315

1416
__all__ = ["CyclicMinMax", "DataElement", "DataRange", "ListOfValues", "MinMax"]
1517

18+
logger = logging.getLogger(__name__)
1619

1720
class DataElement:
1821
"""
@@ -27,14 +30,17 @@ def create(cls, value, dtype):
2730
:param value: value of the DataElement
2831
:param dtype: datatype of the value
2932
"""
30-
if isinstance(dtype, pl.datatypes.classes.DataTypeClass):
31-
dtype = dtype.to_python()
32-
elif isinstance(dtype, pl.datatypes.Datetime):
33-
if type(value) is str:
34-
return pl.Series([value]).str.to_datetime(time_unit=dtype.time_unit, time_zone=dtype.time_zone)[0]
35-
return pl.Series([value]).cast(dtype)[0]
36-
37-
return dtype(value)
33+
try:
34+
if isinstance(dtype, pl.datatypes.classes.DataTypeClass):
35+
dtype = dtype.to_python()
36+
elif isinstance(dtype, pl.datatypes.Datetime) or (dtype is dt.datetime):
37+
if type(value) is str:
38+
return pl.Series([value]).str.to_datetime(time_unit=dtype.time_unit, time_zone=dtype.time_zone)[0]
39+
return pl.Series([value]).cast(dtype)[0]
40+
return dtype(value)
41+
except Exception as e:
42+
logger.warning(f"DataElement.create: {value=} {dtype=} failed -- {e}")
43+
raise
3844

3945

4046
class DataRange(ABC):
@@ -201,7 +207,7 @@ def merge(self, other: ListOfValues | MinMax) -> ListOfValues:
201207
if type(other) is MinMax:
202208
this_min_max = MinMax(min(self.values), max(self.values))
203209
return this_min_max.merge(other)
204-
elif type(other) is ListOfValues:
210+
elif type(other) is not ListOfValues:
205211
raise ValueError(f"ListOfValues.merge: cannot merge with other (type: {type(other)})")
206212

207213
self.values = list(set(self.values + other.values))
@@ -263,6 +269,10 @@ def is_in_range(self, value: Any) -> bool:
263269
except Exception:
264270
pass
265271

272+
if type(value) is float:
273+
epsilon = 1.0E-07
274+
return self.min - epsilon <= value <= self.max + epsilon
275+
266276
return self.min <= value <= self.max
267277

268278
@classmethod

src/damast/core/metadata.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -654,13 +654,12 @@ def update_datarange_and_stats(self, df, column_name: str):
654654
min_value, max_value = df.compat.minmax(column_name)
655655
if min_value is not None and max_value is not None:
656656
self.value_range = MinMax(min_value, max_value)
657-
elif df.compat.is_numeric(column_name):
657+
elif df.compat.is_numeric(column_name) or df.compat.is_datetime(column_name):
658658
try:
659659
min_value, max_value = df.compat.minmax(column_name)
660660
if min_value is not None and max_value is not None:
661661
logger.debug(f"Setting value range: MinMax for {column_name}")
662662
self.value_range = MinMax(min_value, max_value)
663-
664663
results = df.compat.minmax_stats([column_name])
665664

666665
logger.debug(f"Setting value stats for {column_name}")
@@ -796,26 +795,26 @@ def merge(self, other: DataSpecification, strategy: MergeStrategy | None = None)
796795
this_value = getattr(self, key.value)
797796
other_value = getattr(other, key.value)
798797

799-
if key == self.Key.representation_type:
800-
if hasattr(this_value, "to_python"):
801-
this_value = this_value.to_python()
802-
if hasattr(other_value, "to_python"):
803-
other_value = other_value.to_python()
804-
805-
if this_value is None:
798+
if this_value == other_value:
799+
setattr(ds, key.value, this_value)
800+
elif this_value is None:
806801
setattr(ds, key.value, other_value)
807802
elif other_value is None:
808803
setattr(ds, key.value, this_value)
809-
elif this_value == other_value:
810-
setattr(ds, key.value, this_value)
811804
else:
805+
if key == self.Key.representation_type:
806+
if hasattr(this_value, "to_python"):
807+
this_value = this_value.to_python()
808+
if hasattr(other_value, "to_python"):
809+
other_value = other_value.to_python()
810+
812811
try:
813812
if hasattr(this_value, "merge"):
814813
merged_value = this_value.merge(other_value)
815814
setattr(ds, key.value, merged_value)
816815
return ds
817-
except Exception:
818-
logger.debug("Merge failed: {e}")
816+
except Exception as e:
817+
logger.warning(f"Merge failed: {e}")
819818

820819
if not strategy:
821820
raise ValueError(

src/damast/core/polars_dataframe.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,15 @@ def types(cls) -> dict[str, any]:
6161

6262
@classmethod
6363
def resolve_type(cls, type_txt: str):
64+
if type_txt == "datetime":
65+
type_txt = "Datetime"
66+
elif type_txt == "str":
67+
type_txt = "String"
68+
elif type_txt == "int":
69+
type_txt = "Int64"
70+
elif type_txt == "float":
71+
type_txt = "Float64"
72+
6473
return eval(type_txt, cls.types())
6574

6675

@@ -92,6 +101,12 @@ def is_string(self, column_name: str) -> bool:
92101
def is_numeric(self, column_name: str) -> bool:
93102
return self.dtype(column_name).is_numeric()
94103

104+
def is_datetime(self, column_name: str) -> bool:
105+
return type(self.dtype(column_name)) is polars.Datetime
106+
107+
def is_date(self, column_name: str) -> bool:
108+
return type(self.dtype(column_name)) is polars.Date
109+
95110
def __getitem__(self, column_name: str):
96111
"""
97112
Make dataframe subscriptable and behave more like the :class:`pandas.DataFrame`.
@@ -194,12 +209,21 @@ def minmax_stats(self, column_names: list[str]) -> dict[str, dict[str, any]]:
194209
fields.extend([
195210
polars.col(column).min().alias(f"{column}_min_value"),
196211
polars.col(column).max().alias(f"{column}_max_value"),
197-
polars.col(column).mean().alias(f"{column}_mean"),
198-
polars.col(column).std().alias(f"{column}_stddev"),
199212
polars.col(column).count().alias(f"{column}_total_count"),
200213
polars.col(column).null_count().alias(f"{column}_null_count")
201214
])
202215

216+
if self.is_datetime(column):
217+
fields.extend([
218+
(polars.col(column).dt.timestamp("us").mean() / 1_000_000).alias(f"{column}_mean"),
219+
(polars.col(column).dt.timestamp("us").std() / 1_000_000).alias(f"{column}_stddev")
220+
])
221+
else:
222+
fields.extend([
223+
polars.col(column).mean().alias(f"{column}_mean"),
224+
polars.col(column).std().alias(f"{column}_stddev"),
225+
])
226+
203227
result = self._dataframe.select(
204228
fields
205229
).collect()

tests/damast/core/test_metadata.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,13 @@ def test_data_specification_read_write(name, category, is_optional,
154154
category=DataCategory.DYNAMIC,
155155
representation_type=int),
156156
DataSpecification.MergeStrategy.THIS, None
157+
],
158+
[DataSpecification(name="a",
159+
representation_type=float,
160+
unit='deg'),
161+
DataSpecification(name="a",
162+
unit='rad'),
163+
DataSpecification.MergeStrategy.OTHER, None
157164
]
158165
])
159166
def test_data_specification_merge(dataspec, other_dataspec, merge_strategy, error_msg):

0 commit comments

Comments
 (0)