Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
65b47e9
Add between_time for Series/DataFrame
darshan0548 Sep 1, 2026
1b7e890
Add between_time and at_time methods
darshan0548 Sep 1, 2026
684efcc
Add at_time method
darshan0548 Sep 1, 2026
4256146
Merge branch 'main' into fix-between-time
darshan0548 Sep 1, 2026
71a301e
[pre-commit.ci] auto code formatting
pre-commit-ci[bot] Sep 1, 2026
500f785
Merge branch 'main' into fix-between-time
darshan0548 Sep 2, 2026
355ccdb
Fix int16 overflow in between_time/at_time row_secs calculation
darshan0548 Sep 2, 2026
f58617c
[pre-commit.ci] auto code formatting
pre-commit-ci[bot] Sep 2, 2026
4c86d6a
Fix fractional-second precision in between_time/at_time; add axis=1 g…
darshan0548 Sep 2, 2026
c42cadf
Remove leftover merge conflict markers
darshan0548 Sep 2, 2026
bb4fdda
[pre-commit.ci] auto code formatting
pre-commit-ci[bot] Sep 2, 2026
a73cf2a
Add unit tests for between_time and at_time
darshan0548 Sep 2, 2026
e1f6026
Fill NaT rows with sentinel value to fix between_time null handling
darshan0548 Sep 2, 2026
ab70d06
[pre-commit.ci] auto code formatting
pre-commit-ci[bot] Sep 2, 2026
de6a15d
Merge branch 'main' into fix-between-time
darshan0548 Sep 2, 2026
7f08859
Reuse indexer_between_time; support axis=None per pandas signature
darshan0548 Sep 2, 2026
b2e2494
Remove obsolete between_time skip entries now that it's implemented
darshan0548 Sep 2, 2026
3e424ee
Merge branch 'main' into fix-between-time
darshan0548 Sep 2, 2026
17d6857
[pre-commit.ci] auto code formatting
pre-commit-ci[bot] Sep 2, 2026
4ce1cf4
Merge branch 'main' into fix-between-time
darshan0548 Sep 3, 2026
2dc59fb
Merge branch 'main' into fix-between-time
darshan0548 Sep 3, 2026
063cc4a
Fix indentation bug in between_time method
darshan0548 Sep 3, 2026
07c7f4b
Add Series tests for between_time/at_time; convert DataFrame test hel…
darshan0548 Sep 3, 2026
fa5feb3
[pre-commit.ci] auto code formatting
pre-commit-ci[bot] Sep 3, 2026
b298c1e
Clear DatetimeIndex freq after between_time/at_time filtering to matc…
darshan0548 Sep 4, 2026
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
127 changes: 127 additions & 0 deletions python/cudf/cudf/core/indexed_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -2422,6 +2422,133 @@ def truncate(self, before=None, after=None, axis=0, copy=True):
slicer[axis] = slice(before, after)
return self.loc[tuple(slicer)].copy()

@_performance_tracking
def between_time(
self,
start_time,
end_time,
inclusive: str = "both",
axis: Axis | None = None,
) -> Self:
"""
Select values between particular times of the day (e.g., 9:00-9:30 AM).

By setting ``start_time`` to be later than ``end_time``, you can get
the times that are *not* between the two times.

Parameters
----------
start_time : datetime.time or str
Initial time as a time filter limit.
end_time : datetime.time or str
End time as a time filter limit.
inclusive : {"both", "neither", "left", "right"}, default "both"
Include boundaries; whether to set each bound as closed or open.
axis : {0 or 'index'}, None, default None
Axis on which to select. Only axis=0/'index' (rows) is supported.

Returns
-------
Series or DataFrame
Data from the original object filtered to the specified
time range.

Raises
------
TypeError
If the index is not a :class:`~cudf.DatetimeIndex`.

Examples
--------
>>> import cudf
>>> i = cudf.date_range('2018-04-09', periods=4, freq='1D20min')
>>> ts = cudf.DataFrame({'A': [1, 2, 3, 4]}, index=i)
>>> ts
A
2018-04-09 00:00:00 1
2018-04-10 00:20:00 2
2018-04-11 00:40:00 3
2018-04-12 01:00:00 4
>>> ts.between_time('0:15', '0:45')
A
2018-04-10 00:20:00 2
2018-04-11 00:40:00 3
"""
if axis is not None and axis not in (0, "index"):
raise NotImplementedError("Only axis=0 is supported.")

if not isinstance(self.index, cudf.DatetimeIndex):
raise TypeError("Index must be DatetimeIndex")

if inclusive not in {"both", "neither", "left", "right"}:
raise ValueError(
"Inclusive has to be either 'both', 'neither', "
"'left' or 'right'"
)
include_start = inclusive in {"both", "left"}
include_end = inclusive in {"both", "right"}

indexer = self.index.indexer_between_time(
start_time,
end_time,
include_start=include_start,
include_end=include_end,
)
result = self.iloc[indexer]
if isinstance(result.index, cudf.DatetimeIndex):
result.index._freq = None
return result

@_performance_tracking
def at_time(self, time, axis: Axis | None = None) -> Self:
"""
Select values at particular time of day (e.g., 9:30AM).

Parameters
----------
time : datetime.time or str
axis : {0 or 'index'}, None, default None
Axis on which to select. Only axis=0/'index' (rows) is supported.

Returns
-------
Series or DataFrame

Raises
------
TypeError
If the index is not a :class:`~cudf.DatetimeIndex`.

Examples
--------
>>> import cudf
>>> i = cudf.date_range('2018-04-09', periods=4, freq='12h')
>>> ts = cudf.DataFrame({'A': [1, 2, 3, 4]}, index=i)
>>> ts
A
2018-04-09 00:00:00 1
2018-04-09 12:00:00 2
2018-04-10 00:00:00 3
2018-04-10 12:00:00 4
>>> ts.at_time('12:00')
A
2018-04-09 12:00:00 2
2018-04-10 12:00:00 4
"""
if axis is not None and axis not in (0, "index"):
raise NotImplementedError("Only axis=0 is supported.")

if not isinstance(self.index, cudf.DatetimeIndex):
raise TypeError("Index must be DatetimeIndex")

indexer = self.index.indexer_between_time(
time, time, include_start=True, include_end=True
)
result = self.iloc[indexer]
if isinstance(result.index, cudf.DatetimeIndex):
result.index._freq = None
return result

@property
def loc(self):
"""Select rows and columns by label or boolean mask.
Expand Down
2 changes: 0 additions & 2 deletions python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -550,8 +550,6 @@ def pytest_unconfigure(config):
"tests/copy_view/test_methods.py::test_align_with_series_copy_false": "TODO: Add a reason for failure",
"tests/copy_view/test_methods.py::test_assign_drop_duplicates[assign]": "TODO: Add a reason for failure",
"tests/copy_view/test_methods.py::test_assign_drop_duplicates[drop_duplicates]": "TODO: Add a reason for failure",
"tests/copy_view/test_methods.py::test_between_time[obj0]": "TODO: Add a reason for failure",
"tests/copy_view/test_methods.py::test_between_time[obj1]": "TODO: Add a reason for failure",
"tests/copy_view/test_methods.py::test_chained_methods[reset_index]": "TODO: Add a reason for failure",
"tests/copy_view/test_methods.py::test_chained_where_mask[mask]": "TODO: Add a reason for failure",
"tests/copy_view/test_methods.py::test_chained_where_mask[where]": "TODO: Add a reason for failure",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

import pytest

import cudf
from cudf.testing import assert_eq


@pytest.fixture
def gdf_pdf():
dates = cudf.date_range("2018-04-09", periods=4, freq="1D20min")
df = cudf.DataFrame({"A": [1, 2, 3, 4]}, index=dates)
return df, df.to_pandas()


def test_between_time_basic(gdf_pdf):
gdf, pdf = gdf_pdf
expected = pdf.between_time("0:15", "0:45")
actual = gdf.between_time("0:15", "0:45")
assert_eq(actual, expected)


@pytest.mark.parametrize("inclusive", ["both", "neither", "left", "right"])
def test_between_time_inclusive_modes(gdf_pdf, inclusive):
gdf, pdf = gdf_pdf
expected = pdf.between_time("0:20", "0:40", inclusive=inclusive)
actual = gdf.between_time("0:20", "0:40", inclusive=inclusive)
assert_eq(actual, expected)


def test_between_time_wraparound(gdf_pdf):
gdf, pdf = gdf_pdf
expected = pdf.between_time("0:45", "0:15")
actual = gdf.between_time("0:45", "0:15")
assert_eq(actual, expected)


def test_between_time_fractional_seconds():
dates = cudf.date_range("2021-01-01 00:00:00", periods=5, freq="500ms")
gdf = cudf.DataFrame({"A": range(5)}, index=dates)
pdf = gdf.to_pandas()

expected = pdf.between_time("00:00:00.250", "00:00:01.750")
actual = gdf.between_time("00:00:00.250", "00:00:01.750")
assert_eq(actual, expected)


def test_between_time_invalid_index():
gdf = cudf.DataFrame({"A": [1, 2, 3]})
with pytest.raises(TypeError):
gdf.between_time("0:15", "0:45")


def test_between_time_invalid_inclusive(gdf_pdf):
gdf, _ = gdf_pdf
with pytest.raises(ValueError):
gdf.between_time("0:15", "0:45", inclusive="oops")


def test_at_time_basic():
dates = cudf.date_range("2018-04-09", periods=4, freq="12h")
gdf = cudf.DataFrame({"A": [1, 2, 3, 4]}, index=dates)
pdf = gdf.to_pandas()

expected = pdf.at_time("12:00")
actual = gdf.at_time("12:00")
assert_eq(actual, expected)


def test_at_time_fractional_seconds():
dates = cudf.date_range("2021-01-01 00:00:00", periods=5, freq="500ms")
gdf = cudf.DataFrame({"A": range(5)}, index=dates)
pdf = gdf.to_pandas()

expected = pdf.at_time("00:00:01.500")
actual = gdf.at_time("00:00:01.500")
assert_eq(actual, expected)


def test_at_time_invalid_index():
gdf = cudf.DataFrame({"A": [1, 2, 3]})
with pytest.raises(TypeError):
gdf.at_time("12:00")


def test_at_time_invalid_axis():
dates = cudf.date_range("2018-04-09", periods=4, freq="12h")
gdf = cudf.DataFrame({"A": [1, 2, 3, 4]}, index=dates)
with pytest.raises(NotImplementedError):
gdf.at_time("12:00", axis=1)
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

import pytest

import cudf
from cudf.testing import assert_eq


@pytest.fixture
def gsr_psr():
dates = cudf.date_range("2018-04-09", periods=4, freq="1D20min")
gsr = cudf.Series([1, 2, 3, 4], index=dates)
return gsr, gsr.to_pandas()


def test_series_between_time_basic(gsr_psr):
gsr, psr = gsr_psr
expected = psr.between_time("0:20", "0:40")
actual = gsr.between_time("0:20", "0:40")
assert_eq(actual, expected)


def test_series_at_time_basic():
dates = cudf.date_range("2018-04-09", periods=4, freq="12h")
gsr = cudf.Series([1, 2, 3, 4], index=dates)
psr = gsr.to_pandas()

expected = psr.at_time("12:00")
actual = gsr.at_time("12:00")
assert_eq(actual, expected)
Loading