@@ -112,11 +112,9 @@ def __init__(
112
112
def __column_namespace__ (self ) -> Any :
113
113
return dataframe_api_compat .pandas_standard
114
114
115
- @property
116
115
def root_names (self ):
117
116
return sorted (set (self ._root_names ))
118
117
119
- @property
120
118
def output_name (self ):
121
119
return self ._output_name
122
120
@@ -128,12 +126,12 @@ def _record_call(
128
126
) -> PandasColumn :
129
127
calls = [* self ._calls , (func , self , rhs )]
130
128
if isinstance (rhs , PandasColumn ):
131
- root_names = self .root_names + rhs .root_names
129
+ root_names = self .root_names () + rhs .root_names ()
132
130
else :
133
- root_names = self .root_names
131
+ root_names = self .root_names ()
134
132
return PandasColumn (
135
133
root_names = root_names ,
136
- output_name = output_name or self .output_name ,
134
+ output_name = output_name or self .output_name () ,
137
135
extra_calls = calls ,
138
136
)
139
137
@@ -320,12 +318,12 @@ def func(ser, _rhs):
320
318
if ascending :
321
319
return (
322
320
ser .sort_values ()
323
- .index .to_series (name = self .output_name )
321
+ .index .to_series (name = self .output_name () )
324
322
.reset_index (drop = True )
325
323
)
326
324
return (
327
325
ser .sort_values ()
328
- .index .to_series (name = self .output_name )[::- 1 ]
326
+ .index .to_series (name = self .output_name () )[::- 1 ]
329
327
.reset_index (drop = True )
330
328
)
331
329
@@ -376,7 +374,7 @@ def func(ser, value):
376
374
ser = num / other
377
375
else :
378
376
ser = ser .fillna (value )
379
- return ser .rename (self .output_name )
377
+ return ser .rename (self .output_name () )
380
378
381
379
return self ._record_call (
382
380
lambda ser , _rhs : func (ser , value ),
@@ -413,6 +411,66 @@ def rename(self, name: str) -> PandasColumn:
413
411
)
414
412
return expr
415
413
414
+ @property
415
+ def dt (self ) -> ColumnDatetimeAccessor :
416
+ """
417
+ Return accessor with functions which work on temporal dtypes.
418
+ """
419
+ return ColumnDatetimeAccessor (self )
420
+
421
+
422
+ class ColumnDatetimeAccessor :
423
+ def __init__ (self , column : PandasColumn | PandasPermissiveColumn ) -> None :
424
+ if isinstance (column , PandasPermissiveColumn ):
425
+ self .eager = True
426
+ self .column = column ._to_expression ()
427
+ self ._api_version = column ._api_version
428
+ else :
429
+ self .eager = False
430
+ self .column = column
431
+
432
+ def _return (self , expr : PandasColumn ):
433
+ if not self .eager :
434
+ return expr
435
+ return (
436
+ PandasDataFrame (pd .DataFrame (), api_version = self ._api_version )
437
+ .select (expr )
438
+ .collect ()
439
+ .get_column_by_name (self .column .output_name ())
440
+ )
441
+
442
+ def year (self ) -> Column :
443
+ expr = self .column ._record_call (lambda ser , _rhs : ser .dt .year , None )
444
+ return self ._return (expr )
445
+
446
+ def month (self ) -> Column :
447
+ expr = self .column ._record_call (lambda ser , _rhs : ser .dt .month , None )
448
+ return expr
449
+
450
+ def day (self ) -> Column :
451
+ expr = self .column ._record_call (lambda ser , _rhs : ser .dt .day , None )
452
+ return expr
453
+
454
+ def hour (self ) -> Column :
455
+ expr = self .column ._record_call (lambda ser , _rhs : ser .dt .hour , None )
456
+ return expr
457
+
458
+ def minute (self ) -> Column :
459
+ expr = self .column ._record_call (lambda ser , _rhs : ser .dt .minute , None )
460
+ return expr
461
+
462
+ def second (self ) -> Column :
463
+ expr = self .column ._record_call (lambda ser , _rhs : ser .dt .second , None )
464
+ return expr
465
+
466
+ def microsecond (self ) -> Column :
467
+ expr = self .column ._record_call (lambda ser , _rhs : ser .dt .microsecond , None )
468
+ return expr
469
+
470
+ def iso_weekday (self ) -> Column :
471
+ expr = self .column ._record_call (lambda ser , _rhs : ser .dt .weekday + 1 , None )
472
+ return expr
473
+
416
474
417
475
class PandasGroupBy (GroupBy ):
418
476
def __init__ (self , df : pd .DataFrame , keys : Sequence [str ], api_version : str ) -> None :
@@ -518,6 +576,9 @@ def __init__(self, column: pd.Series[Any], api_version: str) -> None:
518
576
"Try updating dataframe-api-compat?"
519
577
)
520
578
579
+ def __repr__ (self ) -> str : # pragma: no cover
580
+ return self .column .__repr__ ()
581
+
521
582
def _to_expression (self ) -> PandasColumn :
522
583
return PandasColumn (
523
584
root_names = [],
@@ -737,6 +798,13 @@ def to_array_object(self, dtype: str) -> Any:
737
798
)
738
799
return self .column .to_numpy (dtype = dtype )
739
800
801
+ @property
802
+ def dt (self ) -> ColumnDatetimeAccessor :
803
+ """
804
+ Return accessor with functions which work on temporal dtypes.
805
+ """
806
+ return ColumnDatetimeAccessor (self )
807
+
740
808
741
809
class PandasDataFrame (DataFrame ):
742
810
# Not technically part of the standard
@@ -879,7 +947,7 @@ def _resolve_expression(
879
947
return expression
880
948
if not expression ._calls :
881
949
return expression ._base_call (self .dataframe )
882
- output_name = expression .output_name
950
+ output_name = expression .output_name ()
883
951
for func , lhs , rhs in expression ._calls :
884
952
lhs = self ._resolve_expression (lhs )
885
953
rhs = self ._resolve_expression (rhs )
0 commit comments