-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Support cudf-polars total_xxx datetime extraction methods
#18171
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
405b61f
36eb333
7eaaab2
a60d8e1
d4d8748
547ba6c
327e427
b994c6b
5065648
bd55421
3985014
a8c13b0
5a22a24
4b1ee24
1711b61
fcfd75e
bd573b7
0ce3e8e
65e527a
1fb02e7
0f8d495
3a8a4e2
5912548
7a25dbb
3d55b64
55995e7
bfbfbe3
a3e4b41
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,6 +28,15 @@ | |
| __all__ = ["TemporalFunction"] | ||
|
|
||
|
|
||
| _unit_to_nanoseconds_conversion = { | ||
| plc.TypeId.DURATION_NANOSECONDS: 1, | ||
| plc.TypeId.DURATION_MICROSECONDS: 1_000, | ||
| plc.TypeId.DURATION_MILLISECONDS: 1_000_000, | ||
| plc.TypeId.DURATION_SECONDS: 1_000_000_000, | ||
| plc.TypeId.DURATION_DAYS: 86_400_000_000_000, | ||
| } | ||
|
Matt711 marked this conversation as resolved.
|
||
|
|
||
|
|
||
| class TemporalFunction(Expr): | ||
| class Name(IntEnum): | ||
| """Internal and picklable representation of polars' `TemporalFunction`.""" | ||
|
|
@@ -114,6 +123,16 @@ def from_polars(cls, obj: polars._expr_nodes.TemporalFunction) -> Self: | |
| "ns": plc.datetime.RoundingFrequency.NANOSECOND, | ||
| } | ||
|
|
||
| # Number of nanoseconds represented by one unit of each ``total_*`` component. | ||
| _TOTAL_COMPONENT_NANOSECONDS: ClassVar[dict[Name, int]] = { | ||
| Name.TotalDays: 86_400_000_000_000, | ||
| Name.TotalHours: 3_600_000_000_000, | ||
| Name.TotalMinutes: 60_000_000_000, | ||
| Name.TotalSeconds: 1_000_000_000, | ||
| Name.TotalMilliseconds: 1_000_000, | ||
| Name.TotalMicroseconds: 1_000, | ||
| Name.TotalNanoseconds: 1, | ||
| } | ||
| _valid_ops: ClassVar[set[Name]] = { | ||
| *_COMPONENT_MAP.keys(), | ||
| Name.IsLeapYear, | ||
|
|
@@ -126,6 +145,7 @@ def from_polars(cls, obj: polars._expr_nodes.TemporalFunction) -> Self: | |
| Name.TimeStamp, | ||
| Name.CastTimeUnit, | ||
| Name.Truncate, | ||
| *_TOTAL_COMPONENT_NANOSECONDS.keys(), | ||
| } | ||
|
|
||
| def __init__( | ||
|
|
@@ -159,6 +179,34 @@ def do_evaluate( | |
| ) -> Column: | ||
| """Evaluate this expression given a dataframe for context.""" | ||
| columns = [child.evaluate(df, context=context) for child in self.children] | ||
| if self.name in self._TOTAL_COMPONENT_NANOSECONDS: | ||
| (column,) = columns | ||
| source_ns = _unit_to_nanoseconds_conversion[column.obj.type().id()] | ||
| target_ns = self._TOTAL_COMPONENT_NANOSECONDS[self.name] | ||
| # Reinterpret the duration's integer tick count as int64. | ||
| casted = column.astype(self.dtype, stream=df.stream) | ||
| if source_ns >= target_ns: | ||
| # Coarser (or equal) storage unit: exact integer multiply. | ||
| op = plc.binaryop.BinaryOperator.MUL | ||
| factor = source_ns // target_ns | ||
| else: | ||
| # Finer storage unit: integer divide. libcudf (like polars) | ||
| # truncates toward zero for signed integer division. | ||
| op = plc.binaryop.BinaryOperator.DIV | ||
| factor = target_ns // source_ns | ||
| if factor == 1: | ||
| # Storage unit already matches the requested unit. | ||
| return casted | ||
| result = plc.binaryop.binary_operation( | ||
|
brandon-b-miller marked this conversation as resolved.
|
||
| casted.obj, | ||
| plc.Scalar.from_py( | ||
| factor, plc.DataType(plc.TypeId.INT64), stream=df.stream | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: if |
||
| ), | ||
| op, | ||
| self.dtype.plc_type, | ||
| stream=df.stream, | ||
| ) | ||
| return Column(result, dtype=self.dtype) | ||
| if self.name is TemporalFunction.Name.TimeStamp: | ||
| (column,) = columns | ||
| (time_unit,) = self.options | ||
|
|
@@ -257,7 +305,6 @@ def do_evaluate( | |
| self.dtype.plc_type, | ||
| stream=df.stream, | ||
| ) | ||
|
|
||
| return Column(result, dtype=self.dtype) | ||
| elif self.name is TemporalFunction.Name.MonthEnd: | ||
| (column,) = columns | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| # SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. | ||
| # SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| from __future__ import annotations | ||
|
|
@@ -315,6 +315,24 @@ def test_astype_to_string(val, plc_tid, pl_type): | |
| assert result.dtype == target_dtype | ||
|
|
||
|
|
||
| def test_astype_duration_to_narrower_integer(): | ||
| stream = get_cuda_stream() | ||
| col = Column( | ||
| plc.unary.cast( | ||
| plc.Column.from_iterable_of_py( | ||
| [1, 2, -3], plc.DataType(plc.TypeId.INT64), stream=stream | ||
| ), | ||
| plc.DataType(plc.TypeId.DURATION_MICROSECONDS), | ||
| stream=stream, | ||
| ), | ||
| dtype=DataType(pl.Duration(time_unit="us")), | ||
| ) | ||
| target_dtype = DataType(pl.Int32()) | ||
| result = col.astype(target_dtype, stream=stream) | ||
| assert result.dtype == target_dtype | ||
| assert result.obj.type().id() == plc.TypeId.INT32 | ||
|
Comment on lines
+318
to
+333
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Test currently verifies metadata but not cast correctness of values At Line 332–333, the test only checks dtype/type-id. Please also assert the converted values (and add at least one edge case like null/empty/single-element), otherwise value corruption/regressions in As per coding guidelines: 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
|
|
||
|
|
||
| def test_astype_from_string_unsupported(): | ||
| stream = get_cuda_stream() | ||
| col = Column( | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.