Skip to content

chore: support casting floats and list-likes to timedelta series #1362

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

Merged
merged 7 commits into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions bigframes/operations/timedelta_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ class ToTimedeltaOp(base_ops.UnaryOp):
unit: typing.Literal["us", "ms", "s", "m", "h", "d", "W"]

def output_type(self, *input_types):
if input_types[0] is not dtypes.INT_DTYPE:
raise TypeError("expected integer input")
return dtypes.TIMEDELTA_DTYPE
if input_types[0] in (dtypes.INT_DTYPE, dtypes.FLOAT_DTYPE):
return dtypes.TIMEDELTA_DTYPE
raise TypeError("expected integer or float input")
20 changes: 13 additions & 7 deletions bigframes/pandas/core/tools/timedeltas.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,26 @@
timedeltas as vendored_pandas_timedeltas,
)
import pandas as pd
import pandas.api.types as pdtypes

from bigframes import operations as ops
from bigframes import series
from bigframes import series, session


def to_timedelta(
arg: typing.Union[series.Series, str, int, float],
arg,
unit: typing.Optional[vendored_pandas_timedeltas.UnitChoices] = None,
) -> typing.Union[series.Series, pd.Timedelta]:
if not isinstance(arg, series.Series):
return pd.to_timedelta(arg, unit)
*,
session: typing.Optional[session.Session] = None,
):
if isinstance(arg, series.Series):
canonical_unit = "us" if unit is None else _canonicalize_unit(unit)
return arg._apply_unary_op(ops.ToTimedeltaOp(canonical_unit))

canonical_unit = "us" if unit is None else _canonicalize_unit(unit)
return arg._apply_unary_op(ops.ToTimedeltaOp(canonical_unit))
if pdtypes.is_list_like(arg):
return to_timedelta(series.Series(arg), unit, session=session)

return pd.to_timedelta(arg, unit)


to_timedelta.__doc__ = vendored_pandas_timedeltas.to_timedelta.__doc__
Expand Down
38 changes: 37 additions & 1 deletion tests/system/small/test_pandas.py
Original file line number Diff line number Diff line change
Expand Up @@ -763,7 +763,7 @@ def test_to_datetime_timestamp_inputs(arg, utc, output_in_utc):
"micros",
],
)
def test_to_timedelta_with_bf_series(session, unit):
def test_to_timedelta_with_bf_integer_series(session, unit):
bf_series = bpd.Series([1, 2, 3], session=session)
pd_series = pd.Series([1, 2, 3])

Expand All @@ -779,6 +779,42 @@ def test_to_timedelta_with_bf_series(session, unit):
)


def test_to_timedelta_with_bf_float_series_value_rounded_down(session):
bf_series = bpd.Series([1.2, 2.9], session=session)

actual_result = (
typing.cast(bpd.Series, bpd.to_timedelta(bf_series, "us"))
.to_pandas()
.astype("timedelta64[ns]")
)

expected_result = pd.Series([pd.Timedelta(1, "us"), pd.Timedelta(2, "us")])
pd.testing.assert_series_equal(
actual_result, expected_result, check_index_type=False
)


@pytest.mark.parametrize(
"input",
[
pytest.param([1, 2, 3], id="list"),
pytest.param((1, 2, 3), id="tuple"),
pytest.param(pd.Series([1, 2, 3]), id="pandas-series"),
],
)
def test_to_timedelta_with_list_like_input(session, input):
actual_result = (
typing.cast(bpd.Series, bpd.to_timedelta(input, "s", session=session))
.to_pandas()
.astype("timedelta64[ns]")
)

expected_result = pd.Series(pd.to_timedelta(input, "s"))
pd.testing.assert_series_equal(
actual_result, expected_result, check_index_type=False
)


@pytest.mark.parametrize(
"unit",
["Y", "M", "whatever"],
Expand Down