diff --git a/NEWS.md b/NEWS.md index 30745755d..1db1b2c34 100644 --- a/NEWS.md +++ b/NEWS.md @@ -14,6 +14,7 @@ - New methods `$str$head()` and `$str$tail()` (#1074). - New S3 methods `nanoarrow::as_nanoarrow_array_stream()` and `nanoarrow::infer_nanoarrow_schema()` for `RPolarsSeries` (#1076). +- New method `$dt$is_leap_year()` (#1077). ## Polars R Package 0.16.3 diff --git a/R/expr__datetime.R b/R/expr__datetime.R index 03a863373..abcf84aeb 100644 --- a/R/expr__datetime.R +++ b/R/expr__datetime.R @@ -35,7 +35,7 @@ #' df ExprDT_truncate = function(every, offset = NULL) { .pr$Expr$dt_truncate(self, every, offset) |> - unwrap("in dt$truncate()") + unwrap("in $dt$truncate()") } #' Round datetime @@ -83,7 +83,7 @@ ExprDT_truncate = function(every, offset = NULL) { #' df ExprDT_round = function(every, offset = NULL) { .pr$Expr$dt_round(self, every, offset) |> - unwrap("in dt$round()") + unwrap("in $dt$round()") } # ExprDT_combine = function(self, tm: time | pli.RPolarsExpr, tu: TimeUnit = "us") -> pli.RPolarsExpr: @@ -547,7 +547,7 @@ ExprDT_epoch = function(tu = c("us", "ns", "ms", "s", "d")) { or_else = Err( paste("tu must be one of 'ns', 'us', 'ms', 's', 'd', got", str_string(tu)) ) - ) |> map_err(\(err) paste("in dt$epoch:", err)) + ) |> map_err(\(err) paste("in $dt$epoch:", err)) unwrap(expr_result) } @@ -575,7 +575,7 @@ ExprDT_epoch = function(tu = c("us", "ns", "ms", "s", "d")) { #' ) ExprDT_timestamp = function(tu = c("ns", "us", "ms")) { .pr$Expr$timestamp(self, tu[1]) |> - map_err(\(err) paste("in dt$timestamp:", err)) |> + map_err(\(err) paste("in $dt$timestamp:", err)) |> unwrap() } @@ -604,7 +604,7 @@ ExprDT_timestamp = function(tu = c("ns", "us", "ms")) { #' ) ExprDT_with_time_unit = function(tu = c("ns", "us", "ms")) { .pr$Expr$dt_with_time_unit(self, tu[1]) |> - map_err(\(err) paste("in dt$with_time_unit:", err)) |> + map_err(\(err) paste("in $dt$with_time_unit:", err)) |> unwrap() } @@ -634,7 +634,7 @@ ExprDT_with_time_unit = function(tu = c("ns", "us", "ms")) { #' ) ExprDT_cast_time_unit = function(tu = c("ns", "us", "ms")) { .pr$Expr$dt_cast_time_unit(self, tu[1]) |> - map_err(\(err) paste("in dt$cast_time_unit:", err)) |> + map_err(\(err) paste("in $dt$cast_time_unit:", err)) |> unwrap() } @@ -661,7 +661,7 @@ ExprDT_cast_time_unit = function(tu = c("ns", "us", "ms")) { ExprDT_convert_time_zone = function(time_zone) { check_tz_to_result(time_zone) |> and_then(\(valid_tz) .pr$Expr$dt_convert_time_zone(self, valid_tz)) |> - map_err(\(err) paste("in dt$convert_time_zone:", err)) |> + map_err(\(err) paste("in $dt$convert_time_zone:", err)) |> unwrap("in $convert_time_zone():") } @@ -947,5 +947,19 @@ ExprDT_offset_by = function(by) { #' df$with_columns(times = pl$col("dates")$dt$time()) ExprDT_time = function() { .pr$Expr$dt_time(self) |> - unwrap("in dt$time()") + unwrap("in $dt$time()") +} + +#' Determine whether the year of the underlying date is a leap year +#' +#' @return An Expr of datatype Bool +#' +#' @examples +#' df = pl$DataFrame(date = as.Date(c("2000-01-01", "2001-01-01", "2002-01-01"))) +#' +#' df$with_columns( +#' leap_year = pl$col("date")$dt$is_leap_year() +#' ) +ExprDT_is_leap_year = function() { + .pr$Expr$dt_is_leap_year(self) } diff --git a/R/extendr-wrappers.R b/R/extendr-wrappers.R index 61d1a68c2..80ae2629d 100644 --- a/R/extendr-wrappers.R +++ b/R/extendr-wrappers.R @@ -852,6 +852,8 @@ RPolarsExpr$dt_total_nanoseconds <- function() .Call(wrap__RPolarsExpr__dt_total RPolarsExpr$dt_offset_by <- function(by) .Call(wrap__RPolarsExpr__dt_offset_by, self, by) +RPolarsExpr$dt_is_leap_year <- function() .Call(wrap__RPolarsExpr__dt_is_leap_year, self) + RPolarsExpr$repeat_by <- function(by) .Call(wrap__RPolarsExpr__repeat_by, self, by) RPolarsExpr$log10 <- function() .Call(wrap__RPolarsExpr__log10, self) diff --git a/man/ExprDT_is_leap_year.Rd b/man/ExprDT_is_leap_year.Rd new file mode 100644 index 000000000..19338dce0 --- /dev/null +++ b/man/ExprDT_is_leap_year.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/expr__datetime.R +\name{ExprDT_is_leap_year} +\alias{ExprDT_is_leap_year} +\title{Determine whether the year of the underlying date is a leap year} +\usage{ +ExprDT_is_leap_year() +} +\value{ +An Expr of datatype Bool +} +\description{ +Determine whether the year of the underlying date is a leap year +} +\examples{ +df = pl$DataFrame(date = as.Date(c("2000-01-01", "2001-01-01", "2002-01-01"))) + +df$with_columns( + leap_year = pl$col("date")$dt$is_leap_year() +) +} diff --git a/src/rust/src/lazy/dsl.rs b/src/rust/src/lazy/dsl.rs index d5c3b19fa..610c8ce8b 100644 --- a/src/rust/src/lazy/dsl.rs +++ b/src/rust/src/lazy/dsl.rs @@ -16,8 +16,7 @@ use crate::CONFIG; use extendr_api::{extendr, prelude::*, rprintln, Deref, DerefMut}; use pl::PolarsError as pl_error; use pl::{ - Duration, DurationMethods, IntoSeries, RollingGroupOptions, SetOperation, StringNameSpaceImpl, - TemporalMethods, + Duration, IntoSeries, RollingGroupOptions, SetOperation, StringNameSpaceImpl, TemporalMethods, }; use polars::lazy::dsl; use polars::prelude as pl; @@ -1469,80 +1468,35 @@ impl RPolarsExpr { } pub fn dt_total_days(&self) -> RResult { - Ok(self - .0 - .clone() - .map( - |s| Ok(Some(s.duration()?.days().into_series())), - pl::GetOutput::from_type(pl::DataType::Int64), - ) - .into()) + Ok(self.0.clone().dt().total_days().into()) } pub fn dt_total_hours(&self) -> RResult { - Ok(self - .0 - .clone() - .map( - |s| Ok(Some(s.duration()?.hours().into_series())), - pl::GetOutput::from_type(pl::DataType::Int64), - ) - .into()) + Ok(self.0.clone().dt().total_hours().into()) } pub fn dt_total_minutes(&self) -> RResult { - Ok(self - .0 - .clone() - .map( - |s| Ok(Some(s.duration()?.minutes().into_series())), - pl::GetOutput::from_type(pl::DataType::Int64), - ) - .into()) + Ok(self.0.clone().dt().total_minutes().into()) } pub fn dt_total_seconds(&self) -> RResult { - Ok(self - .0 - .clone() - .map( - |s| Ok(Some(s.duration()?.seconds().into_series())), - pl::GetOutput::from_type(pl::DataType::Int64), - ) - .into()) + Ok(self.0.clone().dt().total_seconds().into()) } pub fn dt_total_milliseconds(&self) -> RResult { - Ok(self - .0 - .clone() - .map( - |s| Ok(Some(s.duration()?.milliseconds().into_series())), - pl::GetOutput::from_type(pl::DataType::Int64), - ) - .into()) + Ok(self.0.clone().dt().total_milliseconds().into()) } pub fn dt_total_microseconds(&self) -> RResult { - Ok(self - .0 - .clone() - .map( - |s| Ok(Some(s.duration()?.microseconds().into_series())), - pl::GetOutput::from_type(pl::DataType::Int64), - ) - .into()) + Ok(self.0.clone().dt().total_microseconds().into()) } pub fn dt_total_nanoseconds(&self) -> RResult { - Ok(self - .0 - .clone() - .map( - |s| Ok(Some(s.duration()?.nanoseconds().into_series())), - pl::GetOutput::from_type(pl::DataType::Int64), - ) - .into()) + Ok(self.0.clone().dt().total_nanoseconds().into()) } pub fn dt_offset_by(&self, by: Robj) -> RResult { Ok(self.clone().0.dt().offset_by(robj_to!(PLExpr, by)?).into()) } + pub fn dt_is_leap_year(&self) -> Self { + self.clone().0.dt().is_leap_year().into() + } + pub fn repeat_by(&self, by: &RPolarsExpr) -> Self { self.clone().0.repeat_by(by.0.clone()).into() } diff --git a/tests/testthat/_snaps/after-wrappers.md b/tests/testthat/_snaps/after-wrappers.md index 032a74347..adc76eee3 100644 --- a/tests/testthat/_snaps/after-wrappers.md +++ b/tests/testthat/_snaps/after-wrappers.md @@ -318,128 +318,129 @@ [75] "dt_cast_time_unit" "dt_combine" [77] "dt_convert_time_zone" "dt_day" [79] "dt_epoch_seconds" "dt_hour" - [81] "dt_iso_year" "dt_microsecond" - [83] "dt_millisecond" "dt_minute" - [85] "dt_month" "dt_nanosecond" - [87] "dt_offset_by" "dt_ordinal_day" - [89] "dt_quarter" "dt_replace_time_zone" - [91] "dt_round" "dt_second" - [93] "dt_strftime" "dt_time" - [95] "dt_total_days" "dt_total_hours" - [97] "dt_total_microseconds" "dt_total_milliseconds" - [99] "dt_total_minutes" "dt_total_nanoseconds" - [101] "dt_total_seconds" "dt_truncate" - [103] "dt_week" "dt_weekday" - [105] "dt_with_time_unit" "dt_year" - [107] "dtype_cols" "entropy" - [109] "eq" "eq_missing" - [111] "ewm_mean" "ewm_std" - [113] "ewm_var" "exclude" - [115] "exclude_dtype" "exp" - [117] "explode" "extend_constant" - [119] "fill_nan" "fill_null" - [121] "fill_null_with_strategy" "filter" - [123] "first" "flatten" - [125] "floor" "floor_div" - [127] "forward_fill" "gather" - [129] "gather_every" "gt" - [131] "gt_eq" "hash" - [133] "head" "implode" - [135] "interpolate" "is_between" - [137] "is_duplicated" "is_finite" - [139] "is_first_distinct" "is_in" - [141] "is_infinite" "is_last_distinct" - [143] "is_nan" "is_not_nan" - [145] "is_not_null" "is_null" - [147] "is_unique" "kurtosis" - [149] "last" "len" - [151] "list_all" "list_any" - [153] "list_arg_max" "list_arg_min" - [155] "list_contains" "list_diff" - [157] "list_eval" "list_gather" - [159] "list_gather_every" "list_get" - [161] "list_join" "list_len" - [163] "list_max" "list_mean" - [165] "list_min" "list_n_unique" - [167] "list_reverse" "list_set_operation" - [169] "list_shift" "list_slice" - [171] "list_sort" "list_sum" - [173] "list_to_struct" "list_unique" - [175] "lit" "log" - [177] "log10" "lower_bound" - [179] "lt" "lt_eq" - [181] "map_batches" "map_batches_in_background" - [183] "map_elements_in_background" "max" - [185] "mean" "median" - [187] "meta_eq" "meta_has_multiple_outputs" - [189] "meta_is_regex_projection" "meta_output_name" - [191] "meta_pop" "meta_roots" - [193] "meta_tree_format" "meta_undo_aliases" - [195] "min" "mode" - [197] "mul" "n_unique" - [199] "name_keep" "name_map" - [201] "name_prefix" "name_prefix_fields" - [203] "name_suffix" "name_suffix_fields" - [205] "name_to_lowercase" "name_to_uppercase" - [207] "nan_max" "nan_min" - [209] "neq" "neq_missing" - [211] "new_first" "new_last" - [213] "new_len" "not" - [215] "null_count" "or" - [217] "over" "pct_change" - [219] "peak_max" "peak_min" - [221] "pow" "print" - [223] "product" "qcut" - [225] "qcut_uniform" "quantile" - [227] "rank" "rechunk" - [229] "reinterpret" "rem" - [231] "rep" "repeat_by" - [233] "replace" "reshape" - [235] "reverse" "rle" - [237] "rle_id" "rolling" - [239] "rolling_corr" "rolling_cov" - [241] "rolling_max" "rolling_mean" - [243] "rolling_median" "rolling_min" - [245] "rolling_quantile" "rolling_skew" - [247] "rolling_std" "rolling_sum" - [249] "rolling_var" "round" - [251] "sample_frac" "sample_n" - [253] "search_sorted" "shift" - [255] "shift_and_fill" "shrink_dtype" - [257] "shuffle" "sign" - [259] "sin" "sinh" - [261] "skew" "slice" - [263] "sort_by" "sort_with" - [265] "std" "str_base64_decode" - [267] "str_base64_encode" "str_concat" - [269] "str_contains" "str_contains_any" - [271] "str_count_matches" "str_ends_with" - [273] "str_explode" "str_extract" - [275] "str_extract_all" "str_extract_groups" - [277] "str_find" "str_head" - [279] "str_hex_decode" "str_hex_encode" - [281] "str_json_decode" "str_json_path_match" - [283] "str_len_bytes" "str_len_chars" - [285] "str_pad_end" "str_pad_start" - [287] "str_replace" "str_replace_all" - [289] "str_replace_many" "str_reverse" - [291] "str_slice" "str_split" - [293] "str_split_exact" "str_splitn" - [295] "str_starts_with" "str_strip_chars" - [297] "str_strip_chars_end" "str_strip_chars_start" - [299] "str_tail" "str_to_date" - [301] "str_to_datetime" "str_to_integer" - [303] "str_to_lowercase" "str_to_time" - [305] "str_to_titlecase" "str_to_uppercase" - [307] "str_zfill" "struct_field_by_name" - [309] "struct_rename_fields" "sub" - [311] "sum" "tail" - [313] "tan" "tanh" - [315] "timestamp" "to_physical" - [317] "top_k" "unique" - [319] "unique_counts" "unique_stable" - [321] "upper_bound" "value_counts" - [323] "var" "xor" + [81] "dt_is_leap_year" "dt_iso_year" + [83] "dt_microsecond" "dt_millisecond" + [85] "dt_minute" "dt_month" + [87] "dt_nanosecond" "dt_offset_by" + [89] "dt_ordinal_day" "dt_quarter" + [91] "dt_replace_time_zone" "dt_round" + [93] "dt_second" "dt_strftime" + [95] "dt_time" "dt_total_days" + [97] "dt_total_hours" "dt_total_microseconds" + [99] "dt_total_milliseconds" "dt_total_minutes" + [101] "dt_total_nanoseconds" "dt_total_seconds" + [103] "dt_truncate" "dt_week" + [105] "dt_weekday" "dt_with_time_unit" + [107] "dt_year" "dtype_cols" + [109] "entropy" "eq" + [111] "eq_missing" "ewm_mean" + [113] "ewm_std" "ewm_var" + [115] "exclude" "exclude_dtype" + [117] "exp" "explode" + [119] "extend_constant" "fill_nan" + [121] "fill_null" "fill_null_with_strategy" + [123] "filter" "first" + [125] "flatten" "floor" + [127] "floor_div" "forward_fill" + [129] "gather" "gather_every" + [131] "gt" "gt_eq" + [133] "hash" "head" + [135] "implode" "interpolate" + [137] "is_between" "is_duplicated" + [139] "is_finite" "is_first_distinct" + [141] "is_in" "is_infinite" + [143] "is_last_distinct" "is_nan" + [145] "is_not_nan" "is_not_null" + [147] "is_null" "is_unique" + [149] "kurtosis" "last" + [151] "len" "list_all" + [153] "list_any" "list_arg_max" + [155] "list_arg_min" "list_contains" + [157] "list_diff" "list_eval" + [159] "list_gather" "list_gather_every" + [161] "list_get" "list_join" + [163] "list_len" "list_max" + [165] "list_mean" "list_min" + [167] "list_n_unique" "list_reverse" + [169] "list_set_operation" "list_shift" + [171] "list_slice" "list_sort" + [173] "list_sum" "list_to_struct" + [175] "list_unique" "lit" + [177] "log" "log10" + [179] "lower_bound" "lt" + [181] "lt_eq" "map_batches" + [183] "map_batches_in_background" "map_elements_in_background" + [185] "max" "mean" + [187] "median" "meta_eq" + [189] "meta_has_multiple_outputs" "meta_is_regex_projection" + [191] "meta_output_name" "meta_pop" + [193] "meta_roots" "meta_tree_format" + [195] "meta_undo_aliases" "min" + [197] "mode" "mul" + [199] "n_unique" "name_keep" + [201] "name_map" "name_prefix" + [203] "name_prefix_fields" "name_suffix" + [205] "name_suffix_fields" "name_to_lowercase" + [207] "name_to_uppercase" "nan_max" + [209] "nan_min" "neq" + [211] "neq_missing" "new_first" + [213] "new_last" "new_len" + [215] "not" "null_count" + [217] "or" "over" + [219] "pct_change" "peak_max" + [221] "peak_min" "pow" + [223] "print" "product" + [225] "qcut" "qcut_uniform" + [227] "quantile" "rank" + [229] "rechunk" "reinterpret" + [231] "rem" "rep" + [233] "repeat_by" "replace" + [235] "reshape" "reverse" + [237] "rle" "rle_id" + [239] "rolling" "rolling_corr" + [241] "rolling_cov" "rolling_max" + [243] "rolling_mean" "rolling_median" + [245] "rolling_min" "rolling_quantile" + [247] "rolling_skew" "rolling_std" + [249] "rolling_sum" "rolling_var" + [251] "round" "sample_frac" + [253] "sample_n" "search_sorted" + [255] "shift" "shift_and_fill" + [257] "shrink_dtype" "shuffle" + [259] "sign" "sin" + [261] "sinh" "skew" + [263] "slice" "sort_by" + [265] "sort_with" "std" + [267] "str_base64_decode" "str_base64_encode" + [269] "str_concat" "str_contains" + [271] "str_contains_any" "str_count_matches" + [273] "str_ends_with" "str_explode" + [275] "str_extract" "str_extract_all" + [277] "str_extract_groups" "str_find" + [279] "str_head" "str_hex_decode" + [281] "str_hex_encode" "str_json_decode" + [283] "str_json_path_match" "str_len_bytes" + [285] "str_len_chars" "str_pad_end" + [287] "str_pad_start" "str_replace" + [289] "str_replace_all" "str_replace_many" + [291] "str_reverse" "str_slice" + [293] "str_split" "str_split_exact" + [295] "str_splitn" "str_starts_with" + [297] "str_strip_chars" "str_strip_chars_end" + [299] "str_strip_chars_start" "str_tail" + [301] "str_to_date" "str_to_datetime" + [303] "str_to_integer" "str_to_lowercase" + [305] "str_to_time" "str_to_titlecase" + [307] "str_to_uppercase" "str_zfill" + [309] "struct_field_by_name" "struct_rename_fields" + [311] "sub" "sum" + [313] "tail" "tan" + [315] "tanh" "timestamp" + [317] "to_physical" "top_k" + [319] "unique" "unique_counts" + [321] "unique_stable" "upper_bound" + [323] "value_counts" "var" + [325] "xor" # public and private methods of each class When diff --git a/tests/testthat/test-expr_datetime.R b/tests/testthat/test-expr_datetime.R index 6f76871bd..381e8cc7e 100644 --- a/tests/testthat/test-expr_datetime.R +++ b/tests/testthat/test-expr_datetime.R @@ -772,3 +772,19 @@ test_that("$dt$time()", { c(0.00e+00, 2.16e+13, 4.32e+13, 6.48e+13, 0.00e+00) ) }) + +test_that("$dt$is_leap_year()", { + df = pl$DataFrame( + date = as.Date(c("2000-01-01", "2001-01-01", "2002-01-01")), + datetime = pl$datetime_range(as.Date("2000-01-01"), as.Date("2002-01-01"), "1y") + ) + + expect_equal( + df$select(leap_year = pl$col("date")$dt$is_leap_year())$to_list(), + list(leap_year = c(TRUE, FALSE, FALSE)) + ) + expect_equal( + df$select(leap_year = pl$col("datetime")$dt$is_leap_year())$to_list(), + list(leap_year = c(TRUE, FALSE, FALSE)) + ) +})