Skip to content

Commit 99d5bd8

Browse files
feat: Implement <expr>$is_close() (#1637)
1 parent a042672 commit 99d5bd8

File tree

12 files changed

+261
-21
lines changed

12 files changed

+261
-21
lines changed

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- `pl$collect_all()` to efficiently collect a list of LazyFrames (#1598).
88
- `<lazyframe>$remove()` and `<dataframe>$remove()` as a complement to
99
`$filter()` (#1632).
10+
- New method `<expr>$is_close()` (#1637).
1011

1112
## polars 1.5.0
1213

R/000-wrappers.R

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2062,6 +2062,13 @@ class(`PlRDataTypeExpr`) <- c("PlRDataTypeExpr__bundle", "savvy_polars__sealed")
20622062
}
20632063
}
20642064

2065+
`PlRExpr_is_close` <- function(self) {
2066+
function(`other`, `abs_tol`, `rel_tol`, `nans_equal`) {
2067+
`other` <- .savvy_extract_ptr(`other`, "PlRExpr")
2068+
.savvy_wrap_PlRExpr(.Call(savvy_PlRExpr_is_close__impl, `self`, `other`, `abs_tol`, `rel_tol`, `nans_equal`))
2069+
}
2070+
}
2071+
20652072
`PlRExpr_is_duplicated` <- function(self) {
20662073
function() {
20672074
.savvy_wrap_PlRExpr(.Call(savvy_PlRExpr_is_duplicated__impl, `self`))
@@ -3567,6 +3574,7 @@ class(`PlRDataTypeExpr`) <- c("PlRDataTypeExpr__bundle", "savvy_polars__sealed")
35673574
e$`interpolate_by` <- `PlRExpr_interpolate_by`(ptr)
35683575
e$`into_selector` <- `PlRExpr_into_selector`(ptr)
35693576
e$`is_between` <- `PlRExpr_is_between`(ptr)
3577+
e$`is_close` <- `PlRExpr_is_close`(ptr)
35703578
e$`is_duplicated` <- `PlRExpr_is_duplicated`(ptr)
35713579
e$`is_finite` <- `PlRExpr_is_finite`(ptr)
35723580
e$`is_first_distinct` <- `PlRExpr_is_first_distinct`(ptr)

R/expr-expr.R

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4759,3 +4759,31 @@ expr__index_of <- function(element) {
47594759
self$`_rexpr`$index_of(as_polars_expr(element, as_lit = TRUE)$`_rexpr`) |>
47604760
wrap()
47614761
}
4762+
4763+
#' Check if this expression is close, i.e. almost equal, to the other expression
4764+
#'
4765+
#' @description
4766+
#' Two values `a` and `b` are considered close if the following condition holds:
4767+
#' \eqn{|a - b| \le \max \{ \text{rel\_tol} \cdot \max \{ |a|, |b| \}, \text{abs\_tol} \}}
4768+
#'
4769+
#' @inheritParams rlang::args_dots_empty
4770+
#' @param other A literal or expression value to compare with.
4771+
#' @param abs_tol Absolute tolerance. This is the maximum allowed absolute
4772+
#' difference between two values. Must be non-negative.
4773+
#' @param rel_tol Relative tolerance. This is the maximum allowed difference
4774+
#' between two values, relative to the larger absolute value. Must be
4775+
#' non-negative.
4776+
#' @param nans_equal Whether `NaN` values should be considered equal.
4777+
#'
4778+
#' @inherit as_polars_expr return
4779+
#' @examples
4780+
#' df <- pl$DataFrame(a = c(1.5, 2.0, 2.5), b = c(1.55, 2.2, 3.0))
4781+
#' df$with_columns(
4782+
#' is_close = pl$col("a")$is_close("b", abs_tol = 0.1)
4783+
#' )
4784+
expr__is_close <- function(other, ..., abs_tol = 0, rel_tol = 1e-09, nans_equal = FALSE) {
4785+
wrap({
4786+
check_dots_empty0(...)
4787+
self$`_rexpr`$is_close(as_polars_expr(other)$`_rexpr`, abs_tol, rel_tol, nans_equal)
4788+
})
4789+
}

altdoc/mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ nav:
316316
- interpolate_by: man/expr__interpolate_by.md
317317
- interpolate: man/expr__interpolate.md
318318
- is_between: man/expr__is_between.md
319+
- is_close: man/expr__is_close.md
319320
- is_duplicated: man/expr__is_duplicated.md
320321
- is_finite: man/expr__is_finite.md
321322
- is_first_distinct: man/expr__is_first_distinct.md

man/expr__is_close.Rd

Lines changed: 35 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/init.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1519,6 +1519,11 @@ SEXP savvy_PlRExpr_is_between__impl(SEXP self__, SEXP c_arg__lower, SEXP c_arg__
15191519
return handle_result(res);
15201520
}
15211521

1522+
SEXP savvy_PlRExpr_is_close__impl(SEXP self__, SEXP c_arg__other, SEXP c_arg__abs_tol, SEXP c_arg__rel_tol, SEXP c_arg__nans_equal) {
1523+
SEXP res = savvy_PlRExpr_is_close__ffi(self__, c_arg__other, c_arg__abs_tol, c_arg__rel_tol, c_arg__nans_equal);
1524+
return handle_result(res);
1525+
}
1526+
15221527
SEXP savvy_PlRExpr_is_duplicated__impl(SEXP self__) {
15231528
SEXP res = savvy_PlRExpr_is_duplicated__ffi(self__);
15241529
return handle_result(res);
@@ -3678,6 +3683,7 @@ static const R_CallMethodDef CallEntries[] = {
36783683
{"savvy_PlRExpr_interpolate_by__impl", (DL_FUNC) &savvy_PlRExpr_interpolate_by__impl, 2},
36793684
{"savvy_PlRExpr_into_selector__impl", (DL_FUNC) &savvy_PlRExpr_into_selector__impl, 1},
36803685
{"savvy_PlRExpr_is_between__impl", (DL_FUNC) &savvy_PlRExpr_is_between__impl, 4},
3686+
{"savvy_PlRExpr_is_close__impl", (DL_FUNC) &savvy_PlRExpr_is_close__impl, 5},
36813687
{"savvy_PlRExpr_is_duplicated__impl", (DL_FUNC) &savvy_PlRExpr_is_duplicated__impl, 1},
36823688
{"savvy_PlRExpr_is_finite__impl", (DL_FUNC) &savvy_PlRExpr_is_finite__impl, 1},
36833689
{"savvy_PlRExpr_is_first_distinct__impl", (DL_FUNC) &savvy_PlRExpr_is_first_distinct__impl, 1},

src/rust/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ features = [
109109
"interpolate",
110110
"interpolate_by",
111111
"is_between",
112+
"is_close",
112113
"is_first_distinct",
113114
"is_in",
114115
"is_last_distinct",

src/rust/api.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,7 @@ SEXP savvy_PlRExpr_interpolate__ffi(SEXP self__, SEXP c_arg__method);
309309
SEXP savvy_PlRExpr_interpolate_by__ffi(SEXP self__, SEXP c_arg__by);
310310
SEXP savvy_PlRExpr_into_selector__ffi(SEXP self__);
311311
SEXP savvy_PlRExpr_is_between__ffi(SEXP self__, SEXP c_arg__lower, SEXP c_arg__upper, SEXP c_arg__closed);
312+
SEXP savvy_PlRExpr_is_close__ffi(SEXP self__, SEXP c_arg__other, SEXP c_arg__abs_tol, SEXP c_arg__rel_tol, SEXP c_arg__nans_equal);
312313
SEXP savvy_PlRExpr_is_duplicated__ffi(SEXP self__);
313314
SEXP savvy_PlRExpr_is_finite__ffi(SEXP self__);
314315
SEXP savvy_PlRExpr_is_first_distinct__ffi(SEXP self__);

src/rust/src/expr/general.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,4 +1035,18 @@ impl PlRExpr {
10351035
fn index_of(&self, element: &PlRExpr) -> Result<Self> {
10361036
Ok(self.inner.clone().index_of(element.inner.clone()).into())
10371037
}
1038+
1039+
fn is_close(
1040+
&self,
1041+
other: &PlRExpr,
1042+
abs_tol: f64,
1043+
rel_tol: f64,
1044+
nans_equal: bool,
1045+
) -> Result<Self> {
1046+
Ok(self
1047+
.inner
1048+
.clone()
1049+
.is_close(other.inner.clone(), abs_tol, rel_tol, nans_equal)
1050+
.into())
1051+
}
10381052
}

tests/testthat/_snaps/expr-expr.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,3 +589,83 @@
589589
Output
590590
col("foo")
591591

592+
# is_close works
593+
594+
Code
595+
df$select(is_close = pl$col("a")$is_close("b", abs_tol = -1))
596+
Condition
597+
Error in `df$select()`:
598+
! Evaluation failed in `$select()`.
599+
Caused by error:
600+
! Evaluation failed in `$collect()`.
601+
Caused by error:
602+
! `abs_tol` must be non-negative but got -1
603+
604+
---
605+
606+
Code
607+
df$select(is_close = pl$col("a")$is_close("b", rel_tol = -1))
608+
Condition
609+
Error in `df$select()`:
610+
! Evaluation failed in `$select()`.
611+
Caused by error:
612+
! Evaluation failed in `$collect()`.
613+
Caused by error:
614+
! `rel_tol` must be non-negative but got -1
615+
616+
---
617+
618+
Code
619+
df$select(is_close = pl$col("a")$is_close("b", abs_tol = "a"))
620+
Condition
621+
Error in `df$select()`:
622+
! Evaluation failed in `$select()`.
623+
Caused by error:
624+
! Evaluation failed in `$select()`.
625+
Caused by error:
626+
! Evaluation failed in `$is_close()`.
627+
Caused by error:
628+
! Argument `abs_tol` must be double, not character
629+
630+
---
631+
632+
Code
633+
df$select(is_close = pl$col("a")$is_close("b", rel_tol = "a"))
634+
Condition
635+
Error in `df$select()`:
636+
! Evaluation failed in `$select()`.
637+
Caused by error:
638+
! Evaluation failed in `$select()`.
639+
Caused by error:
640+
! Evaluation failed in `$is_close()`.
641+
Caused by error:
642+
! Argument `rel_tol` must be double, not character
643+
644+
---
645+
646+
Code
647+
df$select(is_close = pl$col("a")$is_close("b", abs_tol = c(1, 2)))
648+
Condition
649+
Error in `df$select()`:
650+
! Evaluation failed in `$select()`.
651+
Caused by error:
652+
! Evaluation failed in `$select()`.
653+
Caused by error:
654+
! Evaluation failed in `$is_close()`.
655+
Caused by error:
656+
! Argument `abs_tol` must be be length 1 of non-missing value
657+
658+
---
659+
660+
Code
661+
df$select(is_close = pl$col("a")$is_close("b", rel_tol = c(1, 2)))
662+
Condition
663+
Error in `df$select()`:
664+
! Evaluation failed in `$select()`.
665+
Caused by error:
666+
! Evaluation failed in `$select()`.
667+
Caused by error:
668+
! Evaluation failed in `$is_close()`.
669+
Caused by error:
670+
! Argument `rel_tol` must be be length 1 of non-missing value
671+

0 commit comments

Comments
 (0)