From 8b724e1bf894aed8aec7bbc26a65dc33c59356c6 Mon Sep 17 00:00:00 2001 From: Shenyang Cai Date: Mon, 5 May 2025 22:31:56 +0000 Subject: [PATCH 1/3] implement dayofyear. tests to follow --- bigframes/core/compile/scalar_op_compiler.py | 9 +++++++++ bigframes/operations/__init__.py | 2 ++ bigframes/operations/date_ops.py | 5 +++++ bigframes/operations/datetimes.py | 4 ++++ 4 files changed, 20 insertions(+) diff --git a/bigframes/core/compile/scalar_op_compiler.py b/bigframes/core/compile/scalar_op_compiler.py index 8243627a91..601d6b55f5 100644 --- a/bigframes/core/compile/scalar_op_compiler.py +++ b/bigframes/core/compile/scalar_op_compiler.py @@ -676,6 +676,15 @@ def dayofweek_op_impl(x: ibis_types.Value): ) +@scalar_op_compiler.register_unary_op(ops.dayofyear_op) +def dayofyear_op_impl(x: ibis_types.Value): + return ( + typing.cast(ibis_types.TimestampValue, x) + .day_of_year() + .cast(ibis_dtypes.int64) + ) + + @scalar_op_compiler.register_unary_op(ops.hour_op) def hour_op_impl(x: ibis_types.Value): return typing.cast(ibis_types.TimestampValue, x).hour().cast(ibis_dtypes.int64) diff --git a/bigframes/operations/__init__.py b/bigframes/operations/__init__.py index 74ff5c0f98..c8ccaf2a25 100644 --- a/bigframes/operations/__init__.py +++ b/bigframes/operations/__init__.py @@ -42,6 +42,7 @@ date_diff_op, day_op, dayofweek_op, + dayofyear_op, month_op, quarter_op, year_op, @@ -261,6 +262,7 @@ "month_op", "year_op", "dayofweek_op", + "dayofyear_op", "quarter_op", # Time ops "hour_op", diff --git a/bigframes/operations/date_ops.py b/bigframes/operations/date_ops.py index 32d8eec118..9bcdddb8df 100644 --- a/bigframes/operations/date_ops.py +++ b/bigframes/operations/date_ops.py @@ -39,6 +39,11 @@ type_signature=op_typing.DATELIKE_ACCESSOR, ) +dayofyear_op = base_ops.create_unary_op( + name="dayofyear", + type_signature=op_typing.DATELIKE_ACCESSOR, +) + quarter_op = base_ops.create_unary_op( name="quarter", type_signature=op_typing.DATELIKE_ACCESSOR, diff --git a/bigframes/operations/datetimes.py b/bigframes/operations/datetimes.py index 7d25ac3622..032bd50390 100644 --- a/bigframes/operations/datetimes.py +++ b/bigframes/operations/datetimes.py @@ -43,6 +43,10 @@ def day(self) -> series.Series: def dayofweek(self) -> series.Series: return self._apply_unary_op(ops.dayofweek_op) + @property + def dayofyear(self) -> series.Series: + return self._apply_unary_op(ops.dayofyear_op) + @property def date(self) -> series.Series: return self._apply_unary_op(ops.date_op) From f923851cc191c201efa47ee36de7c78904027c43 Mon Sep 17 00:00:00 2001 From: Shenyang Cai Date: Mon, 5 May 2025 22:47:52 +0000 Subject: [PATCH 2/3] add tests --- bigframes/core/compile/scalar_op_compiler.py | 4 +--- tests/system/small/operations/test_datetimes.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/bigframes/core/compile/scalar_op_compiler.py b/bigframes/core/compile/scalar_op_compiler.py index 601d6b55f5..2b9208137b 100644 --- a/bigframes/core/compile/scalar_op_compiler.py +++ b/bigframes/core/compile/scalar_op_compiler.py @@ -679,9 +679,7 @@ def dayofweek_op_impl(x: ibis_types.Value): @scalar_op_compiler.register_unary_op(ops.dayofyear_op) def dayofyear_op_impl(x: ibis_types.Value): return ( - typing.cast(ibis_types.TimestampValue, x) - .day_of_year() - .cast(ibis_dtypes.int64) + typing.cast(ibis_types.TimestampValue, x).day_of_year().cast(ibis_dtypes.int64) ) diff --git a/tests/system/small/operations/test_datetimes.py b/tests/system/small/operations/test_datetimes.py index 0463124309..705439fd96 100644 --- a/tests/system/small/operations/test_datetimes.py +++ b/tests/system/small/operations/test_datetimes.py @@ -81,6 +81,20 @@ def test_dt_dayofweek(scalars_dfs, col_name): assert_series_equal(pd_result, bf_result, check_dtype=False) +@pytest.mark.parametrize( + ("col_name",), + DATE_COLUMNS, +) +def test_dt_dayofyear(scalars_dfs, col_name): + pytest.importorskip("pandas", minversion="2.0.0") + scalars_df, scalars_pandas_df = scalars_dfs + bf_series: bigframes.series.Series = scalars_df[col_name] + bf_result = bf_series.dt.dayofyear.to_pandas() + pd_result = scalars_pandas_df[col_name].dt.dayofyear + + assert_series_equal(pd_result, bf_result, check_dtype=False) + + @pytest.mark.parametrize( ("col_name",), DATETIME_COL_NAMES, From 1825c8ec9d46193a44a58cc5068b0202eb957370 Mon Sep 17 00:00:00 2001 From: Shenyang Cai Date: Tue, 6 May 2025 19:35:50 +0000 Subject: [PATCH 3/3] add Python doc --- .../pandas/core/indexes/accessor.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/third_party/bigframes_vendored/pandas/core/indexes/accessor.py b/third_party/bigframes_vendored/pandas/core/indexes/accessor.py index f34612cb11..a3c0d59e46 100644 --- a/third_party/bigframes_vendored/pandas/core/indexes/accessor.py +++ b/third_party/bigframes_vendored/pandas/core/indexes/accessor.py @@ -66,6 +66,35 @@ def dayofweek(self): raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) + @property + def dayofyear(self): + """The ordinal day of the year. + + **Examples:** + + >>> import pandas as pd + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None + >>> s = bpd.Series( + ... pd.date_range('2016-12-28', '2017-01-03', freq='D').to_series() + ... ) + >>> s.dt.dayofyear + 2016-12-28 00:00:00 363 + 2016-12-29 00:00:00 364 + 2016-12-30 00:00:00 365 + 2016-12-31 00:00:00 366 + 2017-01-01 00:00:00 1 + 2017-01-02 00:00:00 2 + 2017-01-03 00:00:00 3 + dtype: Int64 + dtype: Int64 + + Returns: + Series: Containing integers indicating the day number. + """ + + raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) + @property def date(self): """Returns a Series with the date part of Timestamps without time and