Skip to content

Commit 0479763

Browse files
authored
feat: add isocalendar() for dt accessor" (#1717)
1 parent 18780b4 commit 0479763

File tree

6 files changed

+96
-4
lines changed

6 files changed

+96
-4
lines changed

bigframes/core/compile/scalar_op_compiler.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,22 @@ def date_op_impl(x: ibis_types.Value):
667667
return typing.cast(ibis_types.TimestampValue, x).date()
668668

669669

670+
@scalar_op_compiler.register_unary_op(ops.iso_day_op)
671+
def iso_day_op_impl(x: ibis_types.Value):
672+
# Plus 1 because iso day of week uses 1-based indexing
673+
return dayofweek_op_impl(x) + 1
674+
675+
676+
@scalar_op_compiler.register_unary_op(ops.iso_week_op)
677+
def iso_week_op_impl(x: ibis_types.Value):
678+
return typing.cast(ibis_types.TimestampValue, x).week_of_year()
679+
680+
681+
@scalar_op_compiler.register_unary_op(ops.iso_year_op)
682+
def iso_year_op_impl(x: ibis_types.Value):
683+
return typing.cast(ibis_types.TimestampValue, x).iso_year()
684+
685+
670686
@scalar_op_compiler.register_unary_op(ops.dayofweek_op)
671687
def dayofweek_op_impl(x: ibis_types.Value):
672688
return (

bigframes/operations/__init__.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@
4343
day_op,
4444
dayofweek_op,
4545
dayofyear_op,
46+
iso_day_op,
47+
iso_week_op,
48+
iso_year_op,
4649
month_op,
4750
quarter_op,
4851
year_op,
@@ -260,11 +263,14 @@
260263
# Date ops
261264
"date_diff_op",
262265
"day_op",
263-
"month_op",
264-
"year_op",
265266
"dayofweek_op",
266267
"dayofyear_op",
268+
"iso_day_op",
269+
"iso_week_op",
270+
"iso_year_op",
271+
"month_op",
267272
"quarter_op",
273+
"year_op",
268274
# Time ops
269275
"hour_op",
270276
"minute_op",

bigframes/operations/date_ops.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,20 @@
3434
type_signature=op_typing.DATELIKE_ACCESSOR,
3535
)
3636

37+
iso_day_op = base_ops.create_unary_op(
38+
name="iso_day", type_signature=op_typing.DATELIKE_ACCESSOR
39+
)
40+
41+
iso_week_op = base_ops.create_unary_op(
42+
name="iso_weeek",
43+
type_signature=op_typing.DATELIKE_ACCESSOR,
44+
)
45+
46+
iso_year_op = base_ops.create_unary_op(
47+
name="iso_year",
48+
type_signature=op_typing.DATELIKE_ACCESSOR,
49+
)
50+
3751
dayofweek_op = base_ops.create_unary_op(
3852
name="dayofweek",
3953
type_signature=op_typing.DATELIKE_ACCESSOR,

bigframes/operations/datetimes.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@
2121
import bigframes_vendored.pandas.core.indexes.accessor as vendordt
2222
import pandas
2323

24-
from bigframes import dtypes
24+
from bigframes import dataframe, dtypes, series
2525
from bigframes.core import log_adapter
26+
from bigframes.core.reshape import concat
2627
import bigframes.operations as ops
2728
import bigframes.operations.base
28-
import bigframes.series as series
2929

3030
_ONE_DAY = pandas.Timedelta("1d")
3131
_ONE_SECOND = pandas.Timedelta("1s")
@@ -69,6 +69,15 @@ def year(self) -> series.Series:
6969
def month(self) -> series.Series:
7070
return self._apply_unary_op(ops.month_op)
7171

72+
def isocalendar(self) -> dataframe.DataFrame:
73+
years = self._apply_unary_op(ops.iso_year_op)
74+
weeks = self._apply_unary_op(ops.iso_week_op)
75+
days = self._apply_unary_op(ops.iso_day_op)
76+
77+
result = concat.concat([years, weeks, days], axis=1)
78+
result.columns = pandas.Index(["year", "week", "day"])
79+
return result
80+
7281
# Time accessors
7382
@property
7483
def hour(self) -> series.Series:

tests/system/small/operations/test_datetimes.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,21 @@ def test_dt_year(scalars_dfs, col_name):
229229
)
230230

231231

232+
def test_dt_isocalendar(session):
233+
# We don't re-use the exisintg scalars_dfs fixture because iso calendar
234+
# get tricky when a new year starts, but the dataset `scalars_dfs` does not cover
235+
# this case.
236+
pd_s = pd.Series(pd.date_range("2009-12-25", "2010-01-07", freq="d"))
237+
bf_s = session.read_pandas(pd_s)
238+
239+
actual_result = bf_s.dt.isocalendar().to_pandas()
240+
241+
expected_result = pd_s.dt.isocalendar()
242+
testing.assert_frame_equal(
243+
actual_result, expected_result, check_dtype=False, check_index_type=False
244+
)
245+
246+
232247
@pytest.mark.parametrize(
233248
("col_name",),
234249
DATETIME_COL_NAMES,

third_party/bigframes_vendored/pandas/core/indexes/accessor.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,38 @@ def month(self):
199199

200200
raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE)
201201

202+
def isocalendar(self):
203+
"""
204+
Calculate year, week, and day according to the ISO 8601 standard.
205+
206+
**Examples:**
207+
>>> import pandas as pd
208+
>>> import bigframes.pandas as bpd
209+
>>> bpd.options.display.progress_bar = None
210+
>>> s = bpd.Series(
211+
... pd.date_range('2009-12-27', '2010-01-04', freq='d').to_series()
212+
... )
213+
>>> s.dt.isocalendar()
214+
year week day
215+
2009-12-27 00:00:00 2009 52 7
216+
2009-12-28 00:00:00 2009 53 1
217+
2009-12-29 00:00:00 2009 53 2
218+
2009-12-30 00:00:00 2009 53 3
219+
2009-12-31 00:00:00 2009 53 4
220+
2010-01-01 00:00:00 2009 53 5
221+
2010-01-02 00:00:00 2009 53 6
222+
2010-01-03 00:00:00 2009 53 7
223+
2010-01-04 00:00:00 2010 1 1
224+
<BLANKLINE>
225+
[9 rows x 3 columns]
226+
227+
228+
Returns: DataFrame
229+
With columns year, week and day.
230+
231+
232+
"""
233+
202234
@property
203235
def second(self):
204236
"""The seconds of the datetime.

0 commit comments

Comments
 (0)