diff --git a/R/000-wrappers.R b/R/000-wrappers.R index b976ab142..36d0f33a0 100644 --- a/R/000-wrappers.R +++ b/R/000-wrappers.R @@ -1406,9 +1406,9 @@ class(`PlRLazyGroupBy`) <- "PlRLazyGroupBy__bundle" } `PlRSeries_to_r_vector` <- function(self) { - function(`ensure_vector`, `int64`, `struct`, `as_clock_class`, `ambiguous`, `non_existent`, `local_time_zone`) { + function(`ensure_vector`, `int64`, `struct`, `decimal`, `as_clock_class`, `ambiguous`, `non_existent`, `local_time_zone`) { `ambiguous` <- .savvy_extract_ptr(`ambiguous`, "PlRExpr") - .Call(savvy_PlRSeries_to_r_vector__impl, `self`, `ensure_vector`, `int64`, `struct`, `as_clock_class`, `ambiguous`, `non_existent`, `local_time_zone`) + .Call(savvy_PlRSeries_to_r_vector__impl, `self`, `ensure_vector`, `int64`, `struct`, `decimal`, `as_clock_class`, `ambiguous`, `non_existent`, `local_time_zone`) } } diff --git a/R/dataframe-s3-base.R b/R/dataframe-s3-base.R index b9cc14c75..62e5b2127 100644 --- a/R/dataframe-s3-base.R +++ b/R/dataframe-s3-base.R @@ -38,6 +38,7 @@ as.list.polars_data_frame <- function( as_series = FALSE, int64 = "double", struct = "dataframe", + decimal = "double", as_clock_class = FALSE, ambiguous = "raise", non_existent = "raise") { @@ -47,6 +48,7 @@ as.list.polars_data_frame <- function( x$to_r_list( int64 = int64, struct = struct, + decimal = decimal, as_clock_class = as_clock_class, ambiguous = ambiguous, non_existent = non_existent @@ -69,6 +71,7 @@ as.list.polars_data_frame <- function( as.data.frame.polars_data_frame <- function( x, ..., int64 = "double", + decimal = "double", as_clock_class = FALSE, ambiguous = "raise", non_existent = "raise") { @@ -76,6 +79,7 @@ as.data.frame.polars_data_frame <- function( ensure_vector = FALSE, int64 = int64, struct = "dataframe", + decimal = decimal, as_clock_class = as_clock_class, ambiguous = ambiguous, non_existent = non_existent diff --git a/R/dataframe-s3-tibble.R b/R/dataframe-s3-tibble.R index 1525fe82d..45932bb7e 100644 --- a/R/dataframe-s3-tibble.R +++ b/R/dataframe-s3-tibble.R @@ -25,6 +25,7 @@ as_tibble.polars_data_frame <- function( x, ..., .name_repair = "check_unique", int64 = "double", + decimal = "double", as_clock_class = FALSE, ambiguous = "raise", non_existent = "raise") { @@ -32,6 +33,7 @@ as_tibble.polars_data_frame <- function( ensure_vector = FALSE, int64 = int64, struct = "tibble", + decimal = decimal, as_clock_class = as_clock_class, ambiguous = ambiguous, non_existent = non_existent diff --git a/R/dataframe-to_r_list.R b/R/dataframe-to_r_list.R index 56ce2a95c..bd1806279 100644 --- a/R/dataframe-to_r_list.R +++ b/R/dataframe-to_r_list.R @@ -22,6 +22,7 @@ dataframe__to_r_list <- function( int64 = "double", as_clock_class = FALSE, struct = "dataframe", + decimal = "double", ambiguous = "raise", non_existent = "raise") { wrap({ @@ -30,8 +31,9 @@ dataframe__to_r_list <- function( self$to_struct()$to_r_vector( ensure_vector = TRUE, int64 = int64, - as_clock_class = as_clock_class, struct = struct, + decimal = decimal, + as_clock_class = as_clock_class, ambiguous = ambiguous, non_existent = non_existent ) diff --git a/R/series-to_r_vector.R b/R/series-to_r_vector.R index e1ee012c7..3e7d64774 100644 --- a/R/series-to_r_vector.R +++ b/R/series-to_r_vector.R @@ -53,6 +53,10 @@ #' - `"dataframe"` (default): Convert to the R's [data.frame] class. #' - `"tibble"`: Convert to the [tibble][tibble::tbl_df] class. #' If the [tibble][tibble::tibble-package] package is not installed, a warning will be shown. +#' @param decimal Determine how to convert Polars' Decimal type values to R type. +#' One of the followings: +#' - `"double"` (default): Convert to the R's [double] type. +#' - `"character"`: Convert to the R's [character] type. #' @param as_clock_class A logical value indicating whether to export datetimes and duration as #' the [clock][clock::clock] package's classes. #' - `FALSE` (default): Duration values are exported as [difftime] @@ -157,6 +161,7 @@ series__to_r_vector <- function( ensure_vector = FALSE, int64 = "double", struct = "dataframe", + decimal = "double", as_clock_class = FALSE, ambiguous = "raise", non_existent = "raise") { @@ -185,6 +190,7 @@ series__to_r_vector <- function( ensure_vector = ensure_vector, int64 = int64, struct = struct, + decimal = decimal, as_clock_class = as_clock_class, ambiguous = ambiguous, non_existent = non_existent, diff --git a/man/dataframe__to_r_list.Rd b/man/dataframe__to_r_list.Rd index 333b321de..1d07f0390 100644 --- a/man/dataframe__to_r_list.Rd +++ b/man/dataframe__to_r_list.Rd @@ -9,6 +9,7 @@ dataframe__to_r_list( int64 = "double", as_clock_class = FALSE, struct = "dataframe", + decimal = "double", ambiguous = "raise", non_existent = "raise" ) @@ -50,6 +51,13 @@ One of the followings: If the \link[tibble:tibble-package]{tibble} package is not installed, a warning will be shown. }} +\item{decimal}{Determine how to convert Polars' Decimal type values to R type. +One of the followings: +\itemize{ +\item \code{"double"} (default): Convert to the R's \link{double} type. +\item \code{"character"}: Convert to the R's \link{character} type. +}} + \item{ambiguous}{Determine how to deal with ambiguous datetimes. Only applicable when \code{as_clock_class} is set to \code{FALSE} and datetime without timezone values are exported as \link{POSIXct}. diff --git a/man/s3_as.data.frame.Rd b/man/s3_as.data.frame.Rd index bcf9c2f6f..6bac22ffe 100644 --- a/man/s3_as.data.frame.Rd +++ b/man/s3_as.data.frame.Rd @@ -8,6 +8,7 @@ x, ..., int64 = "double", + decimal = "double", as_clock_class = FALSE, ambiguous = "raise", non_existent = "raise" @@ -31,6 +32,13 @@ The \link[bit64:bit64-package]{bit64} package must be installed. If the value is out of the range of \link[bit64:bit64-package]{bit64::integer64}, export as \link[bit64:as.integer64.character]{bit64::NA_integer64_}. }} +\item{decimal}{Determine how to convert Polars' Decimal type values to R type. +One of the followings: +\itemize{ +\item \code{"double"} (default): Convert to the R's \link{double} type. +\item \code{"character"}: Convert to the R's \link{character} type. +}} + \item{as_clock_class}{A logical value indicating whether to export datetimes and duration as the \link[clock:clock-package]{clock} package's classes. \itemize{ diff --git a/man/s3_as.list.Rd b/man/s3_as.list.Rd index 58e44b7f5..2a98d8cd5 100644 --- a/man/s3_as.list.Rd +++ b/man/s3_as.list.Rd @@ -10,6 +10,7 @@ as_series = FALSE, int64 = "double", struct = "dataframe", + decimal = "double", as_clock_class = FALSE, ambiguous = "raise", non_existent = "raise" @@ -44,6 +45,13 @@ One of the followings: If the \link[tibble:tibble-package]{tibble} package is not installed, a warning will be shown. }} +\item{decimal}{Determine how to convert Polars' Decimal type values to R type. +One of the followings: +\itemize{ +\item \code{"double"} (default): Convert to the R's \link{double} type. +\item \code{"character"}: Convert to the R's \link{character} type. +}} + \item{as_clock_class}{A logical value indicating whether to export datetimes and duration as the \link[clock:clock-package]{clock} package's classes. \itemize{ diff --git a/man/s3_as_tibble.Rd b/man/s3_as_tibble.Rd index 0ade2f788..9d239f6de 100644 --- a/man/s3_as_tibble.Rd +++ b/man/s3_as_tibble.Rd @@ -9,6 +9,7 @@ ..., .name_repair = "check_unique", int64 = "double", + decimal = "double", as_clock_class = FALSE, ambiguous = "raise", non_existent = "raise" @@ -48,6 +49,13 @@ The \link[bit64:bit64-package]{bit64} package must be installed. If the value is out of the range of \link[bit64:bit64-package]{bit64::integer64}, export as \link[bit64:as.integer64.character]{bit64::NA_integer64_}. }} +\item{decimal}{Determine how to convert Polars' Decimal type values to R type. +One of the followings: +\itemize{ +\item \code{"double"} (default): Convert to the R's \link{double} type. +\item \code{"character"}: Convert to the R's \link{character} type. +}} + \item{as_clock_class}{A logical value indicating whether to export datetimes and duration as the \link[clock:clock-package]{clock} package's classes. \itemize{ diff --git a/man/series__to_r_vector.Rd b/man/series__to_r_vector.Rd index 5da2add9f..7f12e59ee 100644 --- a/man/series__to_r_vector.Rd +++ b/man/series__to_r_vector.Rd @@ -9,6 +9,7 @@ series__to_r_vector( ensure_vector = FALSE, int64 = "double", struct = "dataframe", + decimal = "double", as_clock_class = FALSE, ambiguous = "raise", non_existent = "raise" @@ -43,6 +44,13 @@ One of the followings: If the \link[tibble:tibble-package]{tibble} package is not installed, a warning will be shown. }} +\item{decimal}{Determine how to convert Polars' Decimal type values to R type. +One of the followings: +\itemize{ +\item \code{"double"} (default): Convert to the R's \link{double} type. +\item \code{"character"}: Convert to the R's \link{character} type. +}} + \item{as_clock_class}{A logical value indicating whether to export datetimes and duration as the \link[clock:clock-package]{clock} package's classes. \itemize{ diff --git a/src/init.c b/src/init.c index 32ea1b796..7aa6870dc 100644 --- a/src/init.c +++ b/src/init.c @@ -874,8 +874,8 @@ SEXP savvy_PlRSeries_new_i64_from_clock_pair__impl(SEXP c_arg__name, SEXP c_arg_ return handle_result(res); } -SEXP savvy_PlRSeries_to_r_vector__impl(SEXP self__, SEXP c_arg__ensure_vector, SEXP c_arg__int64, SEXP c_arg__struct, SEXP c_arg__as_clock_class, SEXP c_arg__ambiguous, SEXP c_arg__non_existent, SEXP c_arg__local_time_zone) { - SEXP res = savvy_PlRSeries_to_r_vector__ffi(self__, c_arg__ensure_vector, c_arg__int64, c_arg__struct, c_arg__as_clock_class, c_arg__ambiguous, c_arg__non_existent, c_arg__local_time_zone); +SEXP savvy_PlRSeries_to_r_vector__impl(SEXP self__, SEXP c_arg__ensure_vector, SEXP c_arg__int64, SEXP c_arg__struct, SEXP c_arg__decimal, SEXP c_arg__as_clock_class, SEXP c_arg__ambiguous, SEXP c_arg__non_existent, SEXP c_arg__local_time_zone) { + SEXP res = savvy_PlRSeries_to_r_vector__ffi(self__, c_arg__ensure_vector, c_arg__int64, c_arg__struct, c_arg__decimal, c_arg__as_clock_class, c_arg__ambiguous, c_arg__non_existent, c_arg__local_time_zone); return handle_result(res); } @@ -1139,7 +1139,7 @@ static const R_CallMethodDef CallEntries[] = { {"savvy_PlRSeries_new_binary__impl", (DL_FUNC) &savvy_PlRSeries_new_binary__impl, 2}, {"savvy_PlRSeries_new_series_list__impl", (DL_FUNC) &savvy_PlRSeries_new_series_list__impl, 3}, {"savvy_PlRSeries_new_i64_from_clock_pair__impl", (DL_FUNC) &savvy_PlRSeries_new_i64_from_clock_pair__impl, 4}, - {"savvy_PlRSeries_to_r_vector__impl", (DL_FUNC) &savvy_PlRSeries_to_r_vector__impl, 8}, + {"savvy_PlRSeries_to_r_vector__impl", (DL_FUNC) &savvy_PlRSeries_to_r_vector__impl, 9}, {"savvy_PlRSeries_print__impl", (DL_FUNC) &savvy_PlRSeries_print__impl, 1}, {"savvy_PlRSeries_struct_unnest__impl", (DL_FUNC) &savvy_PlRSeries_struct_unnest__impl, 1}, {"savvy_PlRSeries_struct_fields__impl", (DL_FUNC) &savvy_PlRSeries_struct_fields__impl, 1}, diff --git a/src/rust/api.h b/src/rust/api.h index 3d30da5da..9cdfb7cbd 100644 --- a/src/rust/api.h +++ b/src/rust/api.h @@ -182,7 +182,7 @@ SEXP savvy_PlRSeries_new_single_binary__ffi(SEXP c_arg__name, SEXP c_arg__values SEXP savvy_PlRSeries_new_binary__ffi(SEXP c_arg__name, SEXP c_arg__values); SEXP savvy_PlRSeries_new_series_list__ffi(SEXP c_arg__name, SEXP c_arg__values, SEXP c_arg__strict); SEXP savvy_PlRSeries_new_i64_from_clock_pair__ffi(SEXP c_arg__name, SEXP c_arg__left, SEXP c_arg__right, SEXP c_arg__precision); -SEXP savvy_PlRSeries_to_r_vector__ffi(SEXP self__, SEXP c_arg__ensure_vector, SEXP c_arg__int64, SEXP c_arg__struct, SEXP c_arg__as_clock_class, SEXP c_arg__ambiguous, SEXP c_arg__non_existent, SEXP c_arg__local_time_zone); +SEXP savvy_PlRSeries_to_r_vector__ffi(SEXP self__, SEXP c_arg__ensure_vector, SEXP c_arg__int64, SEXP c_arg__struct, SEXP c_arg__decimal, SEXP c_arg__as_clock_class, SEXP c_arg__ambiguous, SEXP c_arg__non_existent, SEXP c_arg__local_time_zone); SEXP savvy_PlRSeries_print__ffi(SEXP self__); SEXP savvy_PlRSeries_struct_unnest__ffi(SEXP self__); SEXP savvy_PlRSeries_struct_fields__ffi(SEXP self__); diff --git a/src/rust/src/series/export.rs b/src/rust/src/series/export.rs index 96fddbd8d..b967f8fac 100644 --- a/src/rust/src/series/export.rs +++ b/src/rust/src/series/export.rs @@ -20,6 +20,13 @@ enum StructConversion { Tibble, } +#[derive(Debug, Clone, EnumString)] +#[strum(serialize_all = "lowercase")] +enum DecimalConversion { + Character, + Double, +} + // `vctrs::unspecified` like function fn vctrs_unspecified_sexp(n: usize) -> Sexp { let mut sexp = OwnedLogicalSexp::new(n).unwrap(); @@ -51,6 +58,7 @@ impl PlRSeries { ensure_vector: bool, int64: &str, r#struct: &str, + decimal: &str, as_clock_class: bool, ambiguous: &PlRExpr, non_existent: &str, @@ -69,6 +77,11 @@ impl PlRSeries { "Argument `struct` must be one of ('dataframe', 'tibble')".to_string(), ) })?; + let decimal = DecimalConversion::try_from(decimal).map_err(|_| { + savvy::Error::from( + "Argument `decimal` must be one of ('character', 'double')".to_string(), + ) + })?; let ambiguous = ambiguous.inner.clone(); let non_existent = >::try_from(non_existent)?.0; @@ -77,6 +90,7 @@ impl PlRSeries { ensure_vector: bool, int64: Int64Conversion, r#struct: StructConversion, + decimal: DecimalConversion, as_clock_class: bool, ambiguous: Expr, non_existent: NonExistent, @@ -128,6 +142,7 @@ impl PlRSeries { false, int64.clone(), r#struct.clone(), + decimal.clone(), as_clock_class, ambiguous.clone(), non_existent, @@ -145,6 +160,7 @@ impl PlRSeries { false, int64.clone(), r#struct.clone(), + decimal.clone(), as_clock_class, ambiguous.clone(), non_existent, @@ -168,6 +184,7 @@ impl PlRSeries { false, int64.clone(), r#struct.clone(), + decimal.clone(), as_clock_class, ambiguous.clone(), non_existent, @@ -185,6 +202,7 @@ impl PlRSeries { false, int64.clone(), r#struct.clone(), + decimal.clone(), as_clock_class, ambiguous.clone(), non_existent, @@ -237,9 +255,14 @@ impl PlRSeries { } } } - DataType::Decimal(_, _) => Ok(::from(Wrap( - series.cast(&DataType::Float64).unwrap().f64().unwrap(), - ))), + DataType::Decimal(_, _) => match decimal { + DecimalConversion::Character => Ok(::from(Wrap( + series.cast(&DataType::String).unwrap().str().unwrap(), + ))), + DecimalConversion::Double => Ok(::from(Wrap( + series.cast(&DataType::Float64).unwrap().f64().unwrap(), + ))), + }, DataType::String => Ok(::from(Wrap(series.str().unwrap()))), DataType::Struct(_) => { let df = series @@ -269,6 +292,7 @@ impl PlRSeries { false, int64.clone(), r#struct.clone(), + decimal.clone(), as_clock_class, ambiguous.clone(), non_existent, @@ -297,6 +321,7 @@ impl PlRSeries { ensure_vector, int64, r#struct, + decimal, as_clock_class, ambiguous, non_existent, diff --git a/tests/testthat/test-series-to_r_vector.R b/tests/testthat/test-series-to_r_vector.R index c59c8eec0..8cb65e4f1 100644 --- a/tests/testthat/test-series-to_r_vector.R +++ b/tests/testthat/test-series-to_r_vector.R @@ -119,6 +119,34 @@ test_that("struct argument warning and error", { ) }) +patrick::with_parameters_test_that( + "decimal conversion", + .cases = { + tibble::tribble( + ~.test_name, ~type, ~expected_out, + "double", "double", c(NA, 1, 0.1), + "character", "character", c(NA, "1.000", "0.100"), + ) + }, + code = { + series_decimal <- as_polars_series(c(NA, 1, 0.1))$cast(pl$Decimal(5, 3)) + + out <- series_decimal$to_r_vector(decimal = type) + expect_identical(out, expected_out) + } +) + +test_that("decimal argument error", { + expect_error( + as_polars_series(1)$to_r_vector(decimal = TRUE), + r"(Argument `decimal` must be character)" + ) + expect_error( + as_polars_series(1)$to_r_vector(decimal = "foo"), + r"(must be one of \('character', 'double'\))" + ) +}) + patrick::with_parameters_test_that("datetime conversion to clock classes", .cases = { skip_if_not_installed("clock")