From 6c2b854356aa968d8f3bf29f4e66a09752af3e3e Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Sun, 19 Jan 2025 19:52:09 +0100 Subject: [PATCH 01/27] docs: add an example to the class docstring --- src/safeds/data/tabular/query/_math_operations.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/safeds/data/tabular/query/_math_operations.py b/src/safeds/data/tabular/query/_math_operations.py index 7f7b208a7..124db825e 100644 --- a/src/safeds/data/tabular/query/_math_operations.py +++ b/src/safeds/data/tabular/query/_math_operations.py @@ -12,6 +12,21 @@ class MathOperations(ABC): Namespace for mathematical operations. This class cannot be instantiated directly. It can only be accessed using the `math` attribute of a cell. + + Examples + -------- + >>> from safeds.data.tabular.containers import Column + >>> column = Column("a", [-1, 0, 1]) + >>> column.transform(lambda cell: cell.math.abs()) + +-----+ + | a | + | --- | + | i64 | + +=====+ + | 1 | + | 0 | + | 1 | + +-----+ """ # ------------------------------------------------------------------------------------------------------------------ From 3003aee91d4e80f0a323b85d7f0f51a80a35d804 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Sun, 19 Jan 2025 20:05:53 +0100 Subject: [PATCH 02/27] feat: rounding methods and `sign` --- .../tabular/query/_lazy_math_operations.py | 9 ++ .../data/tabular/query/_math_operations.py | 130 ++++++++++++++++++ 2 files changed, 139 insertions(+) diff --git a/src/safeds/data/tabular/query/_lazy_math_operations.py b/src/safeds/data/tabular/query/_lazy_math_operations.py index b141fe143..f9613479a 100644 --- a/src/safeds/data/tabular/query/_lazy_math_operations.py +++ b/src/safeds/data/tabular/query/_lazy_math_operations.py @@ -51,3 +51,12 @@ def ceil(self) -> Cell: def floor(self) -> Cell: return _LazyCell(self._expression.floor()) + + def round_to_decimal_places(self, decimal_places: int) -> Cell: + return _LazyCell(self._expression.round(decimal_places)) + + def round_to_significant_figures(self, significant_figures: int) -> Cell: + return _LazyCell(self._expression.round_sig_figs(significant_figures)) + + def sign(self) -> Cell: + return _LazyCell(self._expression.sign()) diff --git a/src/safeds/data/tabular/query/_math_operations.py b/src/safeds/data/tabular/query/_math_operations.py index 124db825e..cf62697f2 100644 --- a/src/safeds/data/tabular/query/_math_operations.py +++ b/src/safeds/data/tabular/query/_math_operations.py @@ -57,6 +57,11 @@ def abs(self) -> Cell: """ Get the absolute value. + Returns + ------- + cell: + The absolute value. + Examples -------- >>> from safeds.data.tabular.containers import Column @@ -78,6 +83,11 @@ def ceil(self) -> Cell: """ Round up to the nearest integer. + Returns + ------- + cell: + The rounded value. + Examples -------- >>> from safeds.data.tabular.containers import Column @@ -99,6 +109,11 @@ def floor(self) -> Cell: """ Round down to the nearest integer. + Returns + ------- + cell: + The rounded value. + Examples -------- >>> from safeds.data.tabular.containers import Column @@ -114,3 +129,118 @@ def floor(self) -> Cell: | null | +---------+ """ + + @abstractmethod + def round_to_decimal_places(self, decimal_places: int) -> Cell: + """ + Round to the specified number of decimal places. + + Parameters + ---------- + decimal_places: + The number of decimal places to round to. + + Returns + ------- + cell: + The rounded value. + + Examples + -------- + >>> from safeds.data.tabular.containers import Column + >>> column = Column("a", [0.999, 1.123, 3.456, None]) + >>> column.transform(lambda cell: cell.math.round_to_decimal_places(0)) + +---------+ + | a | + | --- | + | f64 | + +=========+ + | 1.00000 | + | 1.00000 | + | 3.00000 | + | null | + +---------+ + + >>> column.transform(lambda cell: cell.math.round_to_decimal_places(2)) + +---------+ + | a | + | --- | + | f64 | + +=========+ + | 1.00000 | + | 1.12000 | + | 3.46000 | + | null | + +---------+ + """ + + @abstractmethod + def round_to_significant_figures(self, significant_figures: int) -> Cell: + """ + Round to the specified number of significant figures. + + Parameters + ---------- + significant_figures: + The number of significant figures to round to. + + Returns + ------- + cell: + The rounded value. + + Examples + -------- + >>> from safeds.data.tabular.containers import Column + >>> column = Column("a", [0.999, 1.123, 3.456, None]) + >>> column.transform(lambda cell: cell.math.round_to_significant_figures(1)) + +---------+ + | a | + | --- | + | f64 | + +=========+ + | 1.00000 | + | 1.00000 | + | 3.00000 | + | null | + +---------+ + + >>> column.transform(lambda cell: cell.math.round_to_significant_figures(2)) + +---------+ + | a | + | --- | + | f64 | + +=========+ + | 1.00000 | + | 1.10000 | + | 3.50000 | + | null | + +---------+ + """ + + @abstractmethod + def sign(self) -> Cell: + """ + Get the sign (-1 for negative numbers, 0 for zero, and 1 for positive numbers). + + Returns + ------- + cell: + The sign. + + Examples + -------- + >>> from safeds.data.tabular.containers import Column + >>> column = Column("a", [-2, 0, 2, None]) + >>> column.transform(lambda cell: cell.math.sign()) + +------+ + | a | + | --- | + | i64 | + +======+ + | -1 | + | 0 | + | 1 | + | null | + +------+ + """ From 3eb952ea68f3b2b738bf194cea7e8c8c9b783f72 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Sun, 19 Jan 2025 20:12:16 +0100 Subject: [PATCH 03/27] feat: `arccos`, `arcsin`, `arctan` --- .../tabular/query/_lazy_math_operations.py | 9 +++ .../data/tabular/query/_math_operations.py | 81 +++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/src/safeds/data/tabular/query/_lazy_math_operations.py b/src/safeds/data/tabular/query/_lazy_math_operations.py index f9613479a..2498e7d4c 100644 --- a/src/safeds/data/tabular/query/_lazy_math_operations.py +++ b/src/safeds/data/tabular/query/_lazy_math_operations.py @@ -46,6 +46,15 @@ def __str__(self) -> str: def abs(self) -> Cell: return _LazyCell(self._expression.__abs__()) + def arccos(self) -> Cell: + return _LazyCell(self._expression.arccos()) + + def arcsin(self) -> Cell: + return _LazyCell(self._expression.arcsin()) + + def arctan(self) -> Cell: + return _LazyCell(self._expression.arctan()) + def ceil(self) -> Cell: return _LazyCell(self._expression.ceil()) diff --git a/src/safeds/data/tabular/query/_math_operations.py b/src/safeds/data/tabular/query/_math_operations.py index cf62697f2..1df7c01d0 100644 --- a/src/safeds/data/tabular/query/_math_operations.py +++ b/src/safeds/data/tabular/query/_math_operations.py @@ -78,6 +78,87 @@ def abs(self) -> Cell: +------+ """ + @abstractmethod + def arccos(self) -> Cell: + """ + Get the inverse cosine. + + Returns + ------- + cell: + The inverse cosine. + + Examples + -------- + >>> from safeds.data.tabular.containers import Column + >>> column = Column("a", [-1, 0, 1, None]) + >>> column.transform(lambda cell: cell.math.arccos()) + +---------+ + | a | + | --- | + | f64 | + +=========+ + | 3.14159 | + | 1.57080 | + | 0.00000 | + | null | + +---------+ + """ + + @abstractmethod + def arcsin(self) -> Cell: + """ + Get the inverse sine. + + Returns + ------- + cell: + The inverse sine. + + Examples + -------- + >>> from safeds.data.tabular.containers import Column + >>> column = Column("a", [-1, 0, 1, None]) + >>> column.transform(lambda cell: cell.math.arcsin()) + +----------+ + | a | + | --- | + | f64 | + +==========+ + | -1.57080 | + | 0.00000 | + | 1.57080 | + | null | + +----------+ + """ + + @abstractmethod + def arctan(self) -> Cell: + """ + Get the inverse tangent. + + Returns + ------- + cell: + The inverse tangent. + + Examples + -------- + >>> from safeds.data.tabular.containers import Column + >>> column = Column("a", [-1, 0, 1, None]) + >>> column.transform(lambda cell: cell.math.arctan()) + +----------+ + | a | + | --- | + | f64 | + +==========+ + | -0.78540 | + | 0.00000 | + | 0.78540 | + | null | + +----------+ + """ + @abstractmethod def ceil(self) -> Cell: """ From b06487a86ba6527f186c56e15add8304e496142c Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Sun, 19 Jan 2025 20:21:42 +0100 Subject: [PATCH 04/27] feat: `cos`, `sin`, `tan` --- .../tabular/query/_lazy_math_operations.py | 9 ++ .../data/tabular/query/_math_operations.py | 107 +++++++++++++++++- 2 files changed, 114 insertions(+), 2 deletions(-) diff --git a/src/safeds/data/tabular/query/_lazy_math_operations.py b/src/safeds/data/tabular/query/_lazy_math_operations.py index 2498e7d4c..f4df75761 100644 --- a/src/safeds/data/tabular/query/_lazy_math_operations.py +++ b/src/safeds/data/tabular/query/_lazy_math_operations.py @@ -58,6 +58,9 @@ def arctan(self) -> Cell: def ceil(self) -> Cell: return _LazyCell(self._expression.ceil()) + def cos(self) -> Cell: + return _LazyCell(self._expression.cos()) + def floor(self) -> Cell: return _LazyCell(self._expression.floor()) @@ -69,3 +72,9 @@ def round_to_significant_figures(self, significant_figures: int) -> Cell: def sign(self) -> Cell: return _LazyCell(self._expression.sign()) + + def sin(self) -> Cell: + return _LazyCell(self._expression.sin()) + + def tan(self) -> Cell: + return _LazyCell(self._expression.tan()) diff --git a/src/safeds/data/tabular/query/_math_operations.py b/src/safeds/data/tabular/query/_math_operations.py index 1df7c01d0..7a6ffbe69 100644 --- a/src/safeds/data/tabular/query/_math_operations.py +++ b/src/safeds/data/tabular/query/_math_operations.py @@ -185,6 +185,35 @@ def ceil(self) -> Cell: +---------+ """ + @abstractmethod + def cos(self) -> Cell: + """ + Get the cosine. + + Returns + ------- + cell: + The cosine. + + Examples + -------- + >>> import math + >>> from safeds.data.tabular.containers import Column + >>> column = Column("a", [0, math.pi / 2, math.pi, 3 * math.pi / 2, None]) + >>> column.transform(lambda cell: cell.math.cos()) + +----------+ + | a | + | --- | + | f64 | + +==========+ + | 1.00000 | + | 0.00000 | + | -1.00000 | + | -0.00000 | + | null | + +----------+ + """ + @abstractmethod def floor(self) -> Cell: """ @@ -304,6 +333,9 @@ def sign(self) -> Cell: """ Get the sign (-1 for negative numbers, 0 for zero, and 1 for positive numbers). + Note that IEEE 754 defines a negative zero (-0) and a positive zero (+0). This method return a negative zero + for -0 and a positive zero for +0. + Returns ------- cell: @@ -312,8 +344,8 @@ def sign(self) -> Cell: Examples -------- >>> from safeds.data.tabular.containers import Column - >>> column = Column("a", [-2, 0, 2, None]) - >>> column.transform(lambda cell: cell.math.sign()) + >>> column1 = Column("a", [-1, 0, 1, None]) + >>> column1.transform(lambda cell: cell.math.sign()) +------+ | a | | --- | @@ -324,4 +356,75 @@ def sign(self) -> Cell: | 1 | | null | +------+ + + >>> column2 = Column("a", [-1.0, -0.0, +0.0, 1.0, None]) + >>> column2.transform(lambda cell: cell.math.sign()) + +----------+ + | a | + | --- | + | f64 | + +==========+ + | -1.00000 | + | -0.00000 | + | 0.00000 | + | 1.00000 | + | null | + +----------+ + """ + + @abstractmethod + def sin(self) -> Cell: + """ + Get the sine. + + Returns + ------- + cell: + The sine. + + Examples + -------- + >>> import math + >>> from safeds.data.tabular.containers import Column + >>> column = Column("a", [0, math.pi / 2, math.pi, 3 * math.pi / 2, None]) + >>> column.transform(lambda cell: cell.math.sin()) + +----------+ + | a | + | --- | + | f64 | + +==========+ + | 0.00000 | + | 1.00000 | + | 0.00000 | + | -1.00000 | + | null | + +----------+ + """ + + @abstractmethod + def tan(self) -> Cell: + """ + Get the tangent. + + Returns + ------- + cell: + The tangent. + + Examples + -------- + >>> import math + >>> from safeds.data.tabular.containers import Column + >>> column = Column("a", [0, math.pi / 4, 3 * math.pi / 4, None]) + >>> column.transform(lambda cell: cell.math.tan()) + +----------+ + | a | + | --- | + | f64 | + +==========+ + | 0.00000 | + | 1.00000 | + | -1.00000 | + | null | + +----------+ """ From c2d28dcbc52ff04bda4d8994392b628f7bb554f3 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Sun, 19 Jan 2025 20:31:12 +0100 Subject: [PATCH 05/27] feat: `cosh`, `sinh`, `tanh` --- .../tabular/query/_lazy_math_operations.py | 9 ++ .../data/tabular/query/_math_operations.py | 84 +++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/src/safeds/data/tabular/query/_lazy_math_operations.py b/src/safeds/data/tabular/query/_lazy_math_operations.py index f4df75761..f1c713f25 100644 --- a/src/safeds/data/tabular/query/_lazy_math_operations.py +++ b/src/safeds/data/tabular/query/_lazy_math_operations.py @@ -61,6 +61,9 @@ def ceil(self) -> Cell: def cos(self) -> Cell: return _LazyCell(self._expression.cos()) + def cosh(self) -> Cell: + return _LazyCell(self._expression.cosh()) + def floor(self) -> Cell: return _LazyCell(self._expression.floor()) @@ -76,5 +79,11 @@ def sign(self) -> Cell: def sin(self) -> Cell: return _LazyCell(self._expression.sin()) + def sinh(self) -> Cell: + return _LazyCell(self._expression.sinh()) + def tan(self) -> Cell: return _LazyCell(self._expression.tan()) + + def tanh(self) -> Cell: + return _LazyCell(self._expression.tanh()) diff --git a/src/safeds/data/tabular/query/_math_operations.py b/src/safeds/data/tabular/query/_math_operations.py index 7a6ffbe69..a17369ba1 100644 --- a/src/safeds/data/tabular/query/_math_operations.py +++ b/src/safeds/data/tabular/query/_math_operations.py @@ -214,6 +214,34 @@ def cos(self) -> Cell: +----------+ """ + @abstractmethod + def cosh(self) -> Cell: + """ + Get the hyperbolic cosine. + + Returns + ------- + cell: + The hyperbolic cosine. + + Examples + -------- + >>> import math + >>> from safeds.data.tabular.containers import Column + >>> column = Column("a", [-1, 0, 1, None]) + >>> column.transform(lambda cell: cell.math.cosh()) + +---------+ + | a | + | --- | + | f64 | + +=========+ + | 1.54308 | + | 1.00000 | + | 1.54308 | + | null | + +---------+ + """ + @abstractmethod def floor(self) -> Cell: """ @@ -401,6 +429,34 @@ def sin(self) -> Cell: +----------+ """ + @abstractmethod + def sinh(self) -> Cell: + """ + Get the hyperbolic sine. + + Returns + ------- + cell: + The hyperbolic sine. + + Examples + -------- + >>> import math + >>> from safeds.data.tabular.containers import Column + >>> column = Column("a", [-1, 0, 1, None]) + >>> column.transform(lambda cell: cell.math.sinh()) + +----------+ + | a | + | --- | + | f64 | + +==========+ + | -1.17520 | + | 0.00000 | + | 1.17520 | + | null | + +----------+ + """ + @abstractmethod def tan(self) -> Cell: """ @@ -428,3 +484,31 @@ def tan(self) -> Cell: | null | +----------+ """ + + @abstractmethod + def tanh(self) -> Cell: + """ + Get the hyperbolic tangent. + + Returns + ------- + cell: + The hyperbolic tangent. + + Examples + -------- + >>> import math + >>> from safeds.data.tabular.containers import Column + >>> column = Column("a", [-1, 0, 1, None]) + >>> column.transform(lambda cell: cell.math.tanh()) + +----------+ + | a | + | --- | + | f64 | + +==========+ + | -0.76159 | + | 0.00000 | + | 0.76159 | + | null | + +----------+ + """ From e4f1ab003e30d132ac4b19965ce9aac3b11197ad Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Sun, 19 Jan 2025 20:36:47 +0100 Subject: [PATCH 06/27] feat: `arccosh`, `arcsinh`, `arctanh` --- .../tabular/query/_lazy_math_operations.py | 9 +++ .../data/tabular/query/_math_operations.py | 81 +++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/src/safeds/data/tabular/query/_lazy_math_operations.py b/src/safeds/data/tabular/query/_lazy_math_operations.py index f1c713f25..dbf8ebcbd 100644 --- a/src/safeds/data/tabular/query/_lazy_math_operations.py +++ b/src/safeds/data/tabular/query/_lazy_math_operations.py @@ -49,12 +49,21 @@ def abs(self) -> Cell: def arccos(self) -> Cell: return _LazyCell(self._expression.arccos()) + def arccosh(self) -> Cell: + return _LazyCell(self._expression.arccosh()) + def arcsin(self) -> Cell: return _LazyCell(self._expression.arcsin()) + def arcsinh(self) -> Cell: + return _LazyCell(self._expression.arcsinh()) + def arctan(self) -> Cell: return _LazyCell(self._expression.arctan()) + def arctanh(self) -> Cell: + return _LazyCell(self._expression.arctanh()) + def ceil(self) -> Cell: return _LazyCell(self._expression.ceil()) diff --git a/src/safeds/data/tabular/query/_math_operations.py b/src/safeds/data/tabular/query/_math_operations.py index a17369ba1..790adef4d 100644 --- a/src/safeds/data/tabular/query/_math_operations.py +++ b/src/safeds/data/tabular/query/_math_operations.py @@ -105,6 +105,33 @@ def arccos(self) -> Cell: +---------+ """ + @abstractmethod + def arccosh(self) -> Cell: + """ + Get the inverse hyperbolic cosine. + + Returns + ------- + cell: + The inverse hyperbolic cosine. + + Examples + -------- + >>> from safeds.data.tabular.containers import Column + >>> column = Column("a", [-1, 0, 1, None]) + >>> column.transform(lambda cell: cell.math.arccosh()) + +---------+ + | a | + | --- | + | f64 | + +=========+ + | NaN | + | NaN | + | 0.00000 | + | null | + +---------+ + """ + @abstractmethod def arcsin(self) -> Cell: """ @@ -132,6 +159,33 @@ def arcsin(self) -> Cell: +----------+ """ + @abstractmethod + def arcsinh(self) -> Cell: + """ + Get the inverse hyperbolic sine. + + Returns + ------- + cell: + The inverse hyperbolic sine. + + Examples + -------- + >>> from safeds.data.tabular.containers import Column + >>> column = Column("a", [-1, 0, 1, None]) + >>> column.transform(lambda cell: cell.math.arcsinh()) + +----------+ + | a | + | --- | + | f64 | + +==========+ + | -0.88137 | + | 0.00000 | + | 0.88137 | + | null | + +----------+ + """ + @abstractmethod def arctan(self) -> Cell: """ @@ -159,6 +213,33 @@ def arctan(self) -> Cell: +----------+ """ + @abstractmethod + def arctanh(self) -> Cell: + """ + Get the inverse hyperbolic tangent. + + Returns + ------- + cell: + The inverse hyperbolic tangent. + + Examples + -------- + >>> from safeds.data.tabular.containers import Column + >>> column = Column("a", [-1, 0, 1, None]) + >>> column.transform(lambda cell: cell.math.arctanh()) + +---------+ + | a | + | --- | + | f64 | + +=========+ + | -inf | + | 0.00000 | + | inf | + | null | + +---------+ + """ + @abstractmethod def ceil(self) -> Cell: """ From 8c04972380c0171eb13e3ecb11e0031b9274ff3f Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Sun, 19 Jan 2025 20:42:44 +0100 Subject: [PATCH 07/27] feat: `exp`, `sqrt` --- .../tabular/query/_lazy_math_operations.py | 6 +++ .../data/tabular/query/_math_operations.py | 53 +++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/src/safeds/data/tabular/query/_lazy_math_operations.py b/src/safeds/data/tabular/query/_lazy_math_operations.py index dbf8ebcbd..72a59a771 100644 --- a/src/safeds/data/tabular/query/_lazy_math_operations.py +++ b/src/safeds/data/tabular/query/_lazy_math_operations.py @@ -73,6 +73,9 @@ def cos(self) -> Cell: def cosh(self) -> Cell: return _LazyCell(self._expression.cosh()) + def exp(self) -> Cell: + return _LazyCell(self._expression.exp()) + def floor(self) -> Cell: return _LazyCell(self._expression.floor()) @@ -91,6 +94,9 @@ def sin(self) -> Cell: def sinh(self) -> Cell: return _LazyCell(self._expression.sinh()) + def sqrt(self) -> Cell: + return _LazyCell(self._expression.sqrt()) + def tan(self) -> Cell: return _LazyCell(self._expression.tan()) diff --git a/src/safeds/data/tabular/query/_math_operations.py b/src/safeds/data/tabular/query/_math_operations.py index 790adef4d..5977d6335 100644 --- a/src/safeds/data/tabular/query/_math_operations.py +++ b/src/safeds/data/tabular/query/_math_operations.py @@ -323,6 +323,33 @@ def cosh(self) -> Cell: +---------+ """ + @abstractmethod + def exp(self) -> Cell: + """ + Get the exponential. + + Returns + ------- + cell: + The exponential. + + Examples + -------- + >>> from safeds.data.tabular.containers import Column + >>> column = Column("a", [-1, 0, 1, None]) + >>> column.transform(lambda cell: cell.math.exp()) + +---------+ + | a | + | --- | + | f64 | + +=========+ + | 0.36788 | + | 1.00000 | + | 2.71828 | + | null | + +---------+ + """ + @abstractmethod def floor(self) -> Cell: """ @@ -538,6 +565,32 @@ def sinh(self) -> Cell: +----------+ """ + @abstractmethod + def sqrt(self) -> Cell: + """ + Get the square root. + + Returns + ------- + cell: + The square root. + + Examples + -------- + >>> from safeds.data.tabular.containers import Column + >>> column = Column("a", [1, 4, None]) + >>> column.transform(lambda cell: cell.math.sqrt()) + +---------+ + | a | + | --- | + | f64 | + +=========+ + | 1.00000 | + | 2.00000 | + | null | + +---------+ + """ + @abstractmethod def tan(self) -> Cell: """ From 5284199ef0af2751bbb1086bd30933821dbb8b09 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Sun, 19 Jan 2025 20:46:54 +0100 Subject: [PATCH 08/27] feat: `degrees_to_radians` and `radians_to_degrees` --- .../tabular/query/_lazy_math_operations.py | 6 ++ .../data/tabular/query/_math_operations.py | 58 +++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/src/safeds/data/tabular/query/_lazy_math_operations.py b/src/safeds/data/tabular/query/_lazy_math_operations.py index 72a59a771..d527811bb 100644 --- a/src/safeds/data/tabular/query/_lazy_math_operations.py +++ b/src/safeds/data/tabular/query/_lazy_math_operations.py @@ -73,12 +73,18 @@ def cos(self) -> Cell: def cosh(self) -> Cell: return _LazyCell(self._expression.cosh()) + def degrees_to_radians(self) -> Cell: + return _LazyCell(self._expression.radians()) + def exp(self) -> Cell: return _LazyCell(self._expression.exp()) def floor(self) -> Cell: return _LazyCell(self._expression.floor()) + def radians_to_degrees(self) -> Cell: + return _LazyCell(self._expression.degrees()) + def round_to_decimal_places(self, decimal_places: int) -> Cell: return _LazyCell(self._expression.round(decimal_places)) diff --git a/src/safeds/data/tabular/query/_math_operations.py b/src/safeds/data/tabular/query/_math_operations.py index 5977d6335..05db131f9 100644 --- a/src/safeds/data/tabular/query/_math_operations.py +++ b/src/safeds/data/tabular/query/_math_operations.py @@ -323,6 +323,35 @@ def cosh(self) -> Cell: +---------+ """ + @abstractmethod + def degrees_to_radians(self) -> Cell: + """ + Convert degrees to radians. + + Returns + ------- + cell: + The value in radians. + + Examples + -------- + >>> import math + >>> from safeds.data.tabular.containers import Column + >>> column = Column("a", [0, 90, 180, 270, None]) + >>> column.transform(lambda cell: cell.math.degrees_to_radians()) + +---------+ + | a | + | --- | + | f64 | + +=========+ + | 0.00000 | + | 1.57080 | + | 3.14159 | + | 4.71239 | + | null | + +---------+ + """ + @abstractmethod def exp(self) -> Cell: """ @@ -376,6 +405,35 @@ def floor(self) -> Cell: +---------+ """ + @abstractmethod + def radians_to_degrees(self) -> Cell: + """ + Convert radians to degrees. + + Returns + ------- + cell: + The value in degrees. + + Examples + -------- + >>> import math + >>> from safeds.data.tabular.containers import Column + >>> column = Column("a", [0, math.pi / 2, math.pi, 3 * math.pi / 2, None]) + >>> column.transform(lambda cell: cell.math.radians_to_degrees()) + +-----------+ + | a | + | --- | + | f64 | + +===========+ + | 0.00000 | + | 90.00000 | + | 180.00000 | + | 270.00000 | + | null | + +-----------+ + """ + @abstractmethod def round_to_decimal_places(self, decimal_places: int) -> Cell: """ From f0e78b6447c8f4172435adc362e8c32586b4ef10 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Sun, 19 Jan 2025 21:00:53 +0100 Subject: [PATCH 09/27] feat: `log` --- .../tabular/query/_lazy_math_operations.py | 9 ++++ .../data/tabular/query/_math_operations.py | 52 +++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/src/safeds/data/tabular/query/_lazy_math_operations.py b/src/safeds/data/tabular/query/_lazy_math_operations.py index d527811bb..0056e7171 100644 --- a/src/safeds/data/tabular/query/_lazy_math_operations.py +++ b/src/safeds/data/tabular/query/_lazy_math_operations.py @@ -1,8 +1,10 @@ from __future__ import annotations +import math from typing import TYPE_CHECKING from safeds._utils import _structural_hash +from safeds._validation import _check_bounds, _OpenBound from safeds.data.tabular.containers._lazy_cell import _LazyCell from safeds.data.tabular.query._math_operations import MathOperations @@ -82,6 +84,13 @@ def exp(self) -> Cell: def floor(self) -> Cell: return _LazyCell(self._expression.floor()) + def log(self, base: float = math.e) -> Cell: + _check_bounds("base", base, lower_bound=_OpenBound(0)) + if base == 1: + raise ValueError("The base of the logarithm must not be 1.") + + return _LazyCell(self._expression.log(base)) + def radians_to_degrees(self) -> Cell: return _LazyCell(self._expression.degrees()) diff --git a/src/safeds/data/tabular/query/_math_operations.py b/src/safeds/data/tabular/query/_math_operations.py index 05db131f9..b4bd2f2ee 100644 --- a/src/safeds/data/tabular/query/_math_operations.py +++ b/src/safeds/data/tabular/query/_math_operations.py @@ -1,5 +1,6 @@ from __future__ import annotations +import math from abc import ABC, abstractmethod from typing import TYPE_CHECKING @@ -405,6 +406,57 @@ def floor(self) -> Cell: +---------+ """ + @abstractmethod + def log(self, base: float = math.e) -> Cell: + """ + Get the logarithm to the specified base. By default, the natural logarithm is computed. + + Parameters + ---------- + base: + The base of the logarithm. Must be positive and not equal to 1. + + Returns + ------- + cell: + The logarithm. + + Raises + ------ + ValueError + If the base is less than or equal to 0 or equal to 1. + + Examples + -------- + >>> import math + >>> from safeds.data.tabular.containers import Column + >>> column1 = Column("a", [0, 1, math.e, None]) + >>> column1.transform(lambda cell: cell.math.log()) + +---------+ + | a | + | --- | + | f64 | + +=========+ + | -inf | + | 0.00000 | + | 1.00000 | + | null | + +---------+ + + >>> column2 = Column("a", [0, 1, 10, None]) + >>> column2.transform(lambda cell: cell.math.log(10)) + +---------+ + | a | + | --- | + | f64 | + +=========+ + | -inf | + | 0.00000 | + | 1.00000 | + | null | + +---------+ + """ + @abstractmethod def radians_to_degrees(self) -> Cell: """ From f3e1802acea708512a826eb306eee001b7556053 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Sun, 19 Jan 2025 21:03:21 +0100 Subject: [PATCH 10/27] docs: minor change --- .../tabular/transformation/_functional_table_transformer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/safeds/data/tabular/transformation/_functional_table_transformer.py b/src/safeds/data/tabular/transformation/_functional_table_transformer.py index 7c4add23c..eed68c3b4 100644 --- a/src/safeds/data/tabular/transformation/_functional_table_transformer.py +++ b/src/safeds/data/tabular/transformation/_functional_table_transformer.py @@ -77,7 +77,7 @@ def transform(self, table: Table) -> Table: Parameters ---------- table: - The table on which on which the callable is executed. + The table on which the callable is executed. Returns ------- From 7a6717009dfa88c246a1e6cf4d9c540a7b61a9a0 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 20 Jan 2025 10:27:45 +0100 Subject: [PATCH 11/27] test: `sqrt` --- tests/helpers/_assertions.py | 8 +++++- .../query/_lazy_math_operations/test_sqrt.py | 25 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 tests/safeds/data/tabular/query/_lazy_math_operations/test_sqrt.py diff --git a/tests/helpers/_assertions.py b/tests/helpers/_assertions.py index 5846c28f9..6d4333eb9 100644 --- a/tests/helpers/_assertions.py +++ b/tests/helpers/_assertions.py @@ -1,3 +1,4 @@ +import math from collections.abc import Callable from typing import Any @@ -87,7 +88,12 @@ def assert_cell_operation_works( column = Column("a", [value], type=type_) transformed_column = column.transform(transformer) actual = transformed_column[0] - assert actual == expected, f"Expected {expected}, but got {actual}." + + # NaN != NaN; isnan cannot handle None + if expected is not None and math.isnan(expected): + assert math.isnan(actual), f"Expected {expected}, but got {actual}." + else: + assert actual == expected, f"Expected {expected}, but got {actual}." def assert_row_operation_works( diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_sqrt.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_sqrt.py new file mode 100644 index 000000000..da7190e5a --- /dev/null +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_sqrt.py @@ -0,0 +1,25 @@ +import math + +import pytest +from helpers import assert_cell_operation_works + + +@pytest.mark.parametrize( + ("value", "expected"), + [ + (-1, math.nan), + (0, 0), + (1, 1), + (4, 2), + (None, None), + ], + ids=[ + "negative", + "zero", + "one", + "perfect square", + "None", + ], +) +def test_should_return_square_root(value: int | None, expected: int | None) -> None: + assert_cell_operation_works(value, lambda cell: cell.math.sqrt(), expected) From 6e13577b99fa0df713abb8892e355145fbe1b2f7 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 20 Jan 2025 10:29:53 +0100 Subject: [PATCH 12/27] test: `sign` --- .../query/_lazy_math_operations/test_sign.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tests/safeds/data/tabular/query/_lazy_math_operations/test_sign.py diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_sign.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_sign.py new file mode 100644 index 000000000..98b0de7f0 --- /dev/null +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_sign.py @@ -0,0 +1,29 @@ +import pytest +from helpers import assert_cell_operation_works + +from safeds.data.tabular.typing import ColumnType + + +@pytest.mark.parametrize( + ("value", "expected"), + [ + (-2, -1), + (-1, -1), + (-0, -0), + (0, 0), + (1, 1), + (2, 1), + (None, None), + ], + ids=[ + "-2", + "-1", + "-0", + "0", + "1", + "2", + "None", + ], +) +def test_should_return_sign(value: int | None, expected: int | None) -> None: + assert_cell_operation_works(value, lambda cell: cell.math.sign(), expected, type_if_none=ColumnType.float64()) From 8563ac4ce2eeac05ae5e3ea42450abc4ebb02e64 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 20 Jan 2025 10:32:41 +0100 Subject: [PATCH 13/27] test: `degrees_to_radians` --- .../test_degrees_to_radians.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 tests/safeds/data/tabular/query/_lazy_math_operations/test_degrees_to_radians.py diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_degrees_to_radians.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_degrees_to_radians.py new file mode 100644 index 000000000..2d3cfae5b --- /dev/null +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_degrees_to_radians.py @@ -0,0 +1,36 @@ +import math + +import pytest +from helpers import assert_cell_operation_works + +from safeds.data.tabular.typing import ColumnType + + +@pytest.mark.parametrize( + ("value", "expected"), + [ + (0, 0), + (90, math.pi / 2), + (180, math.pi), + (270, 3 * math.pi / 2), + (360, 2 * math.pi), + (720, 4 * math.pi), + (None, None), + ], + ids=[ + "0°", + "90°", + "180°", + "270°", + "360°", + "720°", + "None", + ], +) +def test_should_convert_degrees_to_radians(value: int | None, expected: int | None) -> None: + assert_cell_operation_works( + value, + lambda cell: cell.math.degrees_to_radians(), + expected, + type_if_none=ColumnType.float64(), + ) From e45c4d26f7ffada7df325c197928fea8f417a757 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 20 Jan 2025 10:34:07 +0100 Subject: [PATCH 14/27] test: `radians_to_degrees` --- .../test_radians_to_degrees.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 tests/safeds/data/tabular/query/_lazy_math_operations/test_radians_to_degrees.py diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_radians_to_degrees.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_radians_to_degrees.py new file mode 100644 index 000000000..32b1d8897 --- /dev/null +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_radians_to_degrees.py @@ -0,0 +1,36 @@ +import math + +import pytest +from helpers import assert_cell_operation_works + +from safeds.data.tabular.typing import ColumnType + + +@pytest.mark.parametrize( + ("value", "expected"), + [ + (0, 0), + (math.pi / 2, 90), + (math.pi, 180), + (3 * math.pi / 2, 270), + (2 * math.pi, 360), + (4 * math.pi, 720), + (None, None), + ], + ids=[ + "0°", + "90°", + "180°", + "270°", + "360°", + "720°", + "None", + ], +) +def test_should_convert_radians_to_degrees(value: int | None, expected: int | None) -> None: + assert_cell_operation_works( + value, + lambda cell: cell.math.radians_to_degrees(), + expected, + type_if_none=ColumnType.float64(), + ) From 6c7b699a52e1db78483faa8f308c1d28f9fae79a Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 20 Jan 2025 10:53:51 +0100 Subject: [PATCH 15/27] test: `sin`, `cos`, `tan` --- .../data/tabular/query/_math_operations.py | 15 ++++++++-- tests/helpers/_assertions.py | 17 ++++++++--- .../query/_lazy_math_operations/test_cos.py | 29 +++++++++++++++++++ .../test_degrees_to_radians.py | 16 +++++----- .../test_radians_to_degrees.py | 16 +++++----- .../query/_lazy_math_operations/test_sign.py | 10 +++---- .../query/_lazy_math_operations/test_sin.py | 29 +++++++++++++++++++ .../query/_lazy_math_operations/test_sqrt.py | 6 ++-- .../query/_lazy_math_operations/test_tan.py | 29 +++++++++++++++++++ 9 files changed, 140 insertions(+), 27 deletions(-) create mode 100644 tests/safeds/data/tabular/query/_lazy_math_operations/test_cos.py create mode 100644 tests/safeds/data/tabular/query/_lazy_math_operations/test_sin.py create mode 100644 tests/safeds/data/tabular/query/_lazy_math_operations/test_tan.py diff --git a/src/safeds/data/tabular/query/_math_operations.py b/src/safeds/data/tabular/query/_math_operations.py index b4bd2f2ee..27078af50 100644 --- a/src/safeds/data/tabular/query/_math_operations.py +++ b/src/safeds/data/tabular/query/_math_operations.py @@ -6,6 +6,7 @@ if TYPE_CHECKING: from safeds.data.tabular.containers import Cell + from safeds.exceptions import OutOfBoundsError # noqa: F401 class MathOperations(ABC): @@ -494,13 +495,18 @@ def round_to_decimal_places(self, decimal_places: int) -> Cell: Parameters ---------- decimal_places: - The number of decimal places to round to. + The number of decimal places to round to. Must be greater than or equal to 0. Returns ------- cell: The rounded value. + Raises + ------ + OutOfBoundsError + If `decimal_places` is less than 0. + Examples -------- >>> from safeds.data.tabular.containers import Column @@ -538,13 +544,18 @@ def round_to_significant_figures(self, significant_figures: int) -> Cell: Parameters ---------- significant_figures: - The number of significant figures to round to. + The number of significant figures to round to. Must be greater than or equal to 1. Returns ------- cell: The rounded value. + Raises + ------ + OutOfBoundsError + If `significant_figures` is less than 1. + Examples -------- >>> from safeds.data.tabular.containers import Column diff --git a/tests/helpers/_assertions.py b/tests/helpers/_assertions.py index 6d4333eb9..cc8efb821 100644 --- a/tests/helpers/_assertions.py +++ b/tests/helpers/_assertions.py @@ -69,6 +69,7 @@ def assert_cell_operation_works( expected: Any, *, type_if_none: ColumnType | None = None, + ignore_float_imprecision: bool = True, ) -> None: """ Assert that a cell operation works as expected. @@ -83,17 +84,25 @@ def assert_cell_operation_works( The expected value of the transformed cell. type_if_none: The type of the column if the value is `None`. + ignore_float_imprecision: + If False, check if floating point values match EXACTLY. """ type_ = type_if_none if value is None else None column = Column("a", [value], type=type_) transformed_column = column.transform(transformer) actual = transformed_column[0] - # NaN != NaN; isnan cannot handle None - if expected is not None and math.isnan(expected): - assert math.isnan(actual), f"Expected {expected}, but got {actual}." + message = f"Expected {expected}, but got {actual}." + + if expected is None: + assert actual is None, message + elif math.isnan(expected): + # NaN != NaN + assert math.isnan(actual), message + elif ignore_float_imprecision: + assert math.isclose(actual, expected, abs_tol=1e-15), message else: - assert actual == expected, f"Expected {expected}, but got {actual}." + assert actual == expected, message def assert_row_operation_works( diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_cos.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_cos.py new file mode 100644 index 000000000..2cd923d43 --- /dev/null +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_cos.py @@ -0,0 +1,29 @@ +import math + +import pytest +from helpers import assert_cell_operation_works + +from safeds.data.tabular.typing import ColumnType + + +@pytest.mark.parametrize( + ("value", "expected"), + [ + (0, 1), + (math.pi / 2, 0), + (math.pi, -1), + (3 * math.pi / 2, 0), + (2 * math.pi, 1), + (None, None), + ], + ids=[ + "0 deg", + "90 deg", + "180 deg", + "270 deg", + "360 deg", + "None", + ], +) +def test_should_return_cosine(value: float | None, expected: float | None) -> None: + assert_cell_operation_works(value, lambda cell: cell.math.cos(), expected, type_if_none=ColumnType.float64()) diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_degrees_to_radians.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_degrees_to_radians.py index 2d3cfae5b..75a111933 100644 --- a/tests/safeds/data/tabular/query/_lazy_math_operations/test_degrees_to_radians.py +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_degrees_to_radians.py @@ -10,6 +10,7 @@ ("value", "expected"), [ (0, 0), + (22.5, math.pi / 8), (90, math.pi / 2), (180, math.pi), (270, 3 * math.pi / 2), @@ -18,16 +19,17 @@ (None, None), ], ids=[ - "0°", - "90°", - "180°", - "270°", - "360°", - "720°", + "0 deg", + "22.5 deg", + "90 deg", + "180 deg", + "270 deg", + "360 deg", + "720 deg", "None", ], ) -def test_should_convert_degrees_to_radians(value: int | None, expected: int | None) -> None: +def test_should_convert_degrees_to_radians(value: float | None, expected: float | None) -> None: assert_cell_operation_works( value, lambda cell: cell.math.degrees_to_radians(), diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_radians_to_degrees.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_radians_to_degrees.py index 32b1d8897..fcf8a8dd4 100644 --- a/tests/safeds/data/tabular/query/_lazy_math_operations/test_radians_to_degrees.py +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_radians_to_degrees.py @@ -10,6 +10,7 @@ ("value", "expected"), [ (0, 0), + (math.pi / 8, 22.5), (math.pi / 2, 90), (math.pi, 180), (3 * math.pi / 2, 270), @@ -18,16 +19,17 @@ (None, None), ], ids=[ - "0°", - "90°", - "180°", - "270°", - "360°", - "720°", + "0 deg", + "22.5 deg", + "90 deg", + "180 deg", + "270 deg", + "360 deg", + "720 deg", "None", ], ) -def test_should_convert_radians_to_degrees(value: int | None, expected: int | None) -> None: +def test_should_convert_radians_to_degrees(value: float | None, expected: float | None) -> None: assert_cell_operation_works( value, lambda cell: cell.math.radians_to_degrees(), diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_sign.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_sign.py index 98b0de7f0..077c2a894 100644 --- a/tests/safeds/data/tabular/query/_lazy_math_operations/test_sign.py +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_sign.py @@ -7,23 +7,23 @@ @pytest.mark.parametrize( ("value", "expected"), [ - (-2, -1), + (-2.5, -1), (-1, -1), (-0, -0), (0, 0), (1, 1), - (2, 1), + (2.5, 1), (None, None), ], ids=[ - "-2", + "-2.5", "-1", "-0", "0", "1", - "2", + "2.5", "None", ], ) -def test_should_return_sign(value: int | None, expected: int | None) -> None: +def test_should_return_sign(value: float | None, expected: float | None) -> None: assert_cell_operation_works(value, lambda cell: cell.math.sign(), expected, type_if_none=ColumnType.float64()) diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_sin.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_sin.py new file mode 100644 index 000000000..6726c9e6b --- /dev/null +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_sin.py @@ -0,0 +1,29 @@ +import math + +import pytest +from helpers import assert_cell_operation_works + +from safeds.data.tabular.typing import ColumnType + + +@pytest.mark.parametrize( + ("value", "expected"), + [ + (0, 0), + (math.pi / 2, 1), + (math.pi, 0), + (3 * math.pi / 2, -1), + (2 * math.pi, 0), + (None, None), + ], + ids=[ + "0 deg", + "90 deg", + "180 deg", + "270 deg", + "360 deg", + "None", + ], +) +def test_should_return_sine(value: float | None, expected: float | None) -> None: + assert_cell_operation_works(value, lambda cell: cell.math.sin(), expected, type_if_none=ColumnType.float64()) diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_sqrt.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_sqrt.py index da7190e5a..fe5aabd10 100644 --- a/tests/safeds/data/tabular/query/_lazy_math_operations/test_sqrt.py +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_sqrt.py @@ -10,6 +10,7 @@ (-1, math.nan), (0, 0), (1, 1), + (2.25, 1.5), (4, 2), (None, None), ], @@ -17,9 +18,10 @@ "negative", "zero", "one", - "perfect square", + "square of flot", + "square of int", "None", ], ) -def test_should_return_square_root(value: int | None, expected: int | None) -> None: +def test_should_return_square_root(value: float | None, expected: float | None) -> None: assert_cell_operation_works(value, lambda cell: cell.math.sqrt(), expected) diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_tan.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_tan.py new file mode 100644 index 000000000..a46e32d8d --- /dev/null +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_tan.py @@ -0,0 +1,29 @@ +import math + +import pytest +from helpers import assert_cell_operation_works + +from safeds.data.tabular.typing import ColumnType + + +@pytest.mark.parametrize( + ("value", "expected"), + [ + (0, 0), + (math.pi / 4, 1), + (math.pi, 0), + (3 * math.pi / 4, -1), + (2 * math.pi, 0), + (None, None), + ], + ids=[ + "0 deg", + "45 deg", + "180 deg", + "135 deg", + "360 deg", + "None", + ], +) +def test_should_return_tangent(value: float | None, expected: float | None) -> None: + assert_cell_operation_works(value, lambda cell: cell.math.tan(), expected, type_if_none=ColumnType.float64()) From 3024624454e457249d6bd389d64a2d222226b867 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 20 Jan 2025 11:01:59 +0100 Subject: [PATCH 16/27] test: `arcsin`, `arccos`, `arctan` --- .../_lazy_math_operations/test_arccos.py | 25 +++++++++++++++++++ .../_lazy_math_operations/test_arcsin.py | 25 +++++++++++++++++++ .../_lazy_math_operations/test_arctan.py | 25 +++++++++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 tests/safeds/data/tabular/query/_lazy_math_operations/test_arccos.py create mode 100644 tests/safeds/data/tabular/query/_lazy_math_operations/test_arcsin.py create mode 100644 tests/safeds/data/tabular/query/_lazy_math_operations/test_arctan.py diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_arccos.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_arccos.py new file mode 100644 index 000000000..7c73047fc --- /dev/null +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_arccos.py @@ -0,0 +1,25 @@ +import math + +import pytest +from helpers import assert_cell_operation_works + +from safeds.data.tabular.typing import ColumnType + + +@pytest.mark.parametrize( + ("value", "expected"), + [ + (1.0, 0), + (0, math.pi / 2), + (-1, math.pi), + (None, None), + ], + ids=[ + "0 deg", + "90 deg", + "180 deg", + "None", + ], +) +def test_should_return_inverse_cosine(value: float | None, expected: float | None) -> None: + assert_cell_operation_works(value, lambda cell: cell.math.arccos(), expected, type_if_none=ColumnType.float64()) diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_arcsin.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_arcsin.py new file mode 100644 index 000000000..19b724865 --- /dev/null +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_arcsin.py @@ -0,0 +1,25 @@ +import math + +import pytest +from helpers import assert_cell_operation_works + +from safeds.data.tabular.typing import ColumnType + + +@pytest.mark.parametrize( + ("value", "expected"), + [ + (-1.0, -math.pi / 2), + (0, 0), + (1, math.pi / 2), + (None, None), + ], + ids=[ + "-90 deg", + "0 deg", + "90 deg", + "None", + ], +) +def test_should_return_inverse_sine(value: float | None, expected: float | None) -> None: + assert_cell_operation_works(value, lambda cell: cell.math.arcsin(), expected, type_if_none=ColumnType.float64()) diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_arctan.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_arctan.py new file mode 100644 index 000000000..1585242e7 --- /dev/null +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_arctan.py @@ -0,0 +1,25 @@ +import math + +import pytest +from helpers import assert_cell_operation_works + +from safeds.data.tabular.typing import ColumnType + + +@pytest.mark.parametrize( + ("value", "expected"), + [ + (-1.0, -math.pi / 4), + (0, 0), + (1, math.pi / 4), + (None, None), + ], + ids=[ + "-45 deg", + "0 deg", + "45 deg", + "None", + ], +) +def test_should_return_inverse_tangent(value: float | None, expected: float | None) -> None: + assert_cell_operation_works(value, lambda cell: cell.math.arctan(), expected, type_if_none=ColumnType.float64()) From 3fded103e74a23f0fe3ee77c664d8b4b82948ae7 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 20 Jan 2025 11:36:48 +0100 Subject: [PATCH 17/27] test: `sinh`, `cosh`, `tanh` --- .../query/_lazy_math_operations/test_cosh.py | 30 +++++++++++++++++++ .../query/_lazy_math_operations/test_sinh.py | 30 +++++++++++++++++++ .../query/_lazy_math_operations/test_tanh.py | 30 +++++++++++++++++++ 3 files changed, 90 insertions(+) create mode 100644 tests/safeds/data/tabular/query/_lazy_math_operations/test_cosh.py create mode 100644 tests/safeds/data/tabular/query/_lazy_math_operations/test_sinh.py create mode 100644 tests/safeds/data/tabular/query/_lazy_math_operations/test_tanh.py diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_cosh.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_cosh.py new file mode 100644 index 000000000..0d1fc2ba6 --- /dev/null +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_cosh.py @@ -0,0 +1,30 @@ +import math + +import pytest +from helpers import assert_cell_operation_works + +from safeds.data.tabular.typing import ColumnType + +E = math.e +PHI = (1 + math.sqrt(5)) / 2 + + +@pytest.mark.parametrize( + ("value", "expected"), + [ + (0, 1), + (-1, 0.5 * (E + 1 / E)), + (1, 0.5 * (E + 1 / E)), + (math.log(PHI), 0.5 * math.sqrt(5)), + (None, None), + ], + ids=[ + "0", + "-1", + "1", + "ln of golden ratio", + "None", + ], +) +def test_should_return_hyperbolic_cosine(value: float | None, expected: float | None) -> None: + assert_cell_operation_works(value, lambda cell: cell.math.cosh(), expected, type_if_none=ColumnType.float64()) diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_sinh.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_sinh.py new file mode 100644 index 000000000..44ff52c8d --- /dev/null +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_sinh.py @@ -0,0 +1,30 @@ +import math + +import pytest +from helpers import assert_cell_operation_works + +from safeds.data.tabular.typing import ColumnType + +E = math.e +PHI = (1 + math.sqrt(5)) / 2 + + +@pytest.mark.parametrize( + ("value", "expected"), + [ + (0, 0), + (-1, -0.5 * (E - 1 / E)), + (1, 0.5 * (E - 1 / E)), + (math.log(PHI), 0.5), + (None, None), + ], + ids=[ + "0", + "-1", + "1", + "ln of golden ratio", + "None", + ], +) +def test_should_return_hyperbolic_sine(value: float | None, expected: float | None) -> None: + assert_cell_operation_works(value, lambda cell: cell.math.sinh(), expected, type_if_none=ColumnType.float64()) diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_tanh.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_tanh.py new file mode 100644 index 000000000..bc132d04c --- /dev/null +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_tanh.py @@ -0,0 +1,30 @@ +import math + +import pytest +from helpers import assert_cell_operation_works + +from safeds.data.tabular.typing import ColumnType + +E = math.e +PHI = (1 + math.sqrt(5)) / 2 + + +@pytest.mark.parametrize( + ("value", "expected"), + [ + (0, 0), + (-1, -(E - 1 / E) / (E + 1 / E)), + (1, (E - 1 / E) / (E + 1 / E)), + (math.log(PHI), 1 / math.sqrt(5)), + (None, None), + ], + ids=[ + "0", + "-1", + "1", + "ln of golden ratio", + "None", + ], +) +def test_should_return_hyperbolic_tangent(value: float | None, expected: float | None) -> None: + assert_cell_operation_works(value, lambda cell: cell.math.tanh(), expected, type_if_none=ColumnType.float64()) From 46c6743d807ec71f2e4daf663c4554012f4f3825 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 20 Jan 2025 11:47:23 +0100 Subject: [PATCH 18/27] test: `arsinh`, `arcosh`, `artanh` --- .../tabular/query/_lazy_math_operations.py | 12 +-- .../data/tabular/query/_math_operations.py | 78 +++++++++---------- .../_lazy_math_operations/test_arcosh.py | 28 +++++++ .../_lazy_math_operations/test_arsinh.py | 30 +++++++ .../_lazy_math_operations/test_artanh.py | 30 +++++++ 5 files changed, 133 insertions(+), 45 deletions(-) create mode 100644 tests/safeds/data/tabular/query/_lazy_math_operations/test_arcosh.py create mode 100644 tests/safeds/data/tabular/query/_lazy_math_operations/test_arsinh.py create mode 100644 tests/safeds/data/tabular/query/_lazy_math_operations/test_artanh.py diff --git a/src/safeds/data/tabular/query/_lazy_math_operations.py b/src/safeds/data/tabular/query/_lazy_math_operations.py index 0056e7171..b5bd27ec1 100644 --- a/src/safeds/data/tabular/query/_lazy_math_operations.py +++ b/src/safeds/data/tabular/query/_lazy_math_operations.py @@ -51,19 +51,19 @@ def abs(self) -> Cell: def arccos(self) -> Cell: return _LazyCell(self._expression.arccos()) - def arccosh(self) -> Cell: - return _LazyCell(self._expression.arccosh()) - def arcsin(self) -> Cell: return _LazyCell(self._expression.arcsin()) - def arcsinh(self) -> Cell: - return _LazyCell(self._expression.arcsinh()) + def arcosh(self) -> Cell: + return _LazyCell(self._expression.arccosh()) def arctan(self) -> Cell: return _LazyCell(self._expression.arctan()) - def arctanh(self) -> Cell: + def arsinh(self) -> Cell: + return _LazyCell(self._expression.arcsinh()) + + def artanh(self) -> Cell: return _LazyCell(self._expression.arctanh()) def ceil(self) -> Cell: diff --git a/src/safeds/data/tabular/query/_math_operations.py b/src/safeds/data/tabular/query/_math_operations.py index 27078af50..578d9c573 100644 --- a/src/safeds/data/tabular/query/_math_operations.py +++ b/src/safeds/data/tabular/query/_math_operations.py @@ -108,7 +108,34 @@ def arccos(self) -> Cell: """ @abstractmethod - def arccosh(self) -> Cell: + def arcsin(self) -> Cell: + """ + Get the inverse sine. + + Returns + ------- + cell: + The inverse sine. + + Examples + -------- + >>> from safeds.data.tabular.containers import Column + >>> column = Column("a", [-1, 0, 1, None]) + >>> column.transform(lambda cell: cell.math.arcsin()) + +----------+ + | a | + | --- | + | f64 | + +==========+ + | -1.57080 | + | 0.00000 | + | 1.57080 | + | null | + +----------+ + """ + + @abstractmethod + def arcosh(self) -> Cell: """ Get the inverse hyperbolic cosine. @@ -121,7 +148,7 @@ def arccosh(self) -> Cell: -------- >>> from safeds.data.tabular.containers import Column >>> column = Column("a", [-1, 0, 1, None]) - >>> column.transform(lambda cell: cell.math.arccosh()) + >>> column.transform(lambda cell: cell.math.arcosh()) +---------+ | a | | --- | @@ -135,34 +162,34 @@ def arccosh(self) -> Cell: """ @abstractmethod - def arcsin(self) -> Cell: + def arctan(self) -> Cell: """ - Get the inverse sine. + Get the inverse tangent. Returns ------- cell: - The inverse sine. + The inverse tangent. Examples -------- >>> from safeds.data.tabular.containers import Column >>> column = Column("a", [-1, 0, 1, None]) - >>> column.transform(lambda cell: cell.math.arcsin()) + >>> column.transform(lambda cell: cell.math.arctan()) +----------+ | a | | --- | | f64 | +==========+ - | -1.57080 | + | -0.78540 | | 0.00000 | - | 1.57080 | + | 0.78540 | | null | +----------+ """ @abstractmethod - def arcsinh(self) -> Cell: + def arsinh(self) -> Cell: """ Get the inverse hyperbolic sine. @@ -175,7 +202,7 @@ def arcsinh(self) -> Cell: -------- >>> from safeds.data.tabular.containers import Column >>> column = Column("a", [-1, 0, 1, None]) - >>> column.transform(lambda cell: cell.math.arcsinh()) + >>> column.transform(lambda cell: cell.math.arsinh()) +----------+ | a | | --- | @@ -189,34 +216,7 @@ def arcsinh(self) -> Cell: """ @abstractmethod - def arctan(self) -> Cell: - """ - Get the inverse tangent. - - Returns - ------- - cell: - The inverse tangent. - - Examples - -------- - >>> from safeds.data.tabular.containers import Column - >>> column = Column("a", [-1, 0, 1, None]) - >>> column.transform(lambda cell: cell.math.arctan()) - +----------+ - | a | - | --- | - | f64 | - +==========+ - | -0.78540 | - | 0.00000 | - | 0.78540 | - | null | - +----------+ - """ - - @abstractmethod - def arctanh(self) -> Cell: + def artanh(self) -> Cell: """ Get the inverse hyperbolic tangent. @@ -229,7 +229,7 @@ def arctanh(self) -> Cell: -------- >>> from safeds.data.tabular.containers import Column >>> column = Column("a", [-1, 0, 1, None]) - >>> column.transform(lambda cell: cell.math.arctanh()) + >>> column.transform(lambda cell: cell.math.artanh()) +---------+ | a | | --- | diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_arcosh.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_arcosh.py new file mode 100644 index 000000000..c3c73ea75 --- /dev/null +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_arcosh.py @@ -0,0 +1,28 @@ +import math + +import pytest +from helpers import assert_cell_operation_works + +from safeds.data.tabular.typing import ColumnType + +E = math.e +PHI = (1 + math.sqrt(5)) / 2 + + +@pytest.mark.parametrize( + ("value", "expected"), + [ + (1, 0), + (0.5 * (E + 1 / E), 1), + (0.5 * math.sqrt(5), math.log(PHI)), + (None, None), + ], + ids=[ + "0", + "1", + "ln of golden ratio", + "None", + ], +) +def test_should_return_inverse_hyperbolic_cosine(value: float | None, expected: float | None) -> None: + assert_cell_operation_works(value, lambda cell: cell.math.arcosh(), expected, type_if_none=ColumnType.float64()) diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_arsinh.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_arsinh.py new file mode 100644 index 000000000..865fe9361 --- /dev/null +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_arsinh.py @@ -0,0 +1,30 @@ +import math + +import pytest +from helpers import assert_cell_operation_works + +from safeds.data.tabular.typing import ColumnType + +E = math.e +PHI = (1 + math.sqrt(5)) / 2 + + +@pytest.mark.parametrize( + ("value", "expected"), + [ + (0, 0), + (-0.5 * (E - 1 / E), -1), + (0.5 * (E - 1 / E), 1), + (0.5, math.log(PHI)), + (None, None), + ], + ids=[ + "0", + "-1", + "1", + "ln of golden ratio", + "None", + ], +) +def test_should_return_inverse_hyperbolic_sine(value: float | None, expected: float | None) -> None: + assert_cell_operation_works(value, lambda cell: cell.math.arsinh(), expected, type_if_none=ColumnType.float64()) diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_artanh.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_artanh.py new file mode 100644 index 000000000..f49c8ba12 --- /dev/null +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_artanh.py @@ -0,0 +1,30 @@ +import math + +import pytest +from helpers import assert_cell_operation_works + +from safeds.data.tabular.typing import ColumnType + +E = math.e +PHI = (1 + math.sqrt(5)) / 2 + + +@pytest.mark.parametrize( + ("value", "expected"), + [ + (0, 0), + (-(E - 1 / E) / (E + 1 / E), -1), + ((E - 1 / E) / (E + 1 / E), 1), + (1 / math.sqrt(5), math.log(PHI)), + (None, None), + ], + ids=[ + "0", + "-1", + "1", + "ln of golden ratio", + "None", + ], +) +def test_should_return_inverse_hyperbolic_tangent(value: float | None, expected: float | None) -> None: + assert_cell_operation_works(value, lambda cell: cell.math.artanh(), expected, type_if_none=ColumnType.float64()) From 57413734456d84a6762cf5f4c9c77cbf0eee4aba Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 20 Jan 2025 11:56:20 +0100 Subject: [PATCH 19/27] test: `exp` --- .../query/_lazy_math_operations/test_exp.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 tests/safeds/data/tabular/query/_lazy_math_operations/test_exp.py diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_exp.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_exp.py new file mode 100644 index 000000000..8196c5e87 --- /dev/null +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_exp.py @@ -0,0 +1,23 @@ +import math + +import pytest +from helpers import assert_cell_operation_works + +from safeds.data.tabular.typing import ColumnType + + +@pytest.mark.parametrize( + ("value", "expected"), + [ + (0, 1), + (1.0, math.e), + (None, None), + ], + ids=[ + "0", + "1", + "None", + ], +) +def test_should_return_exponential(value: float | None, expected: float | None) -> None: + assert_cell_operation_works(value, lambda cell: cell.math.exp(), expected, type_if_none=ColumnType.float64()) From 431d0960fbedc613a6d9df46fd1c021e197a508e Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 20 Jan 2025 12:17:34 +0100 Subject: [PATCH 20/27] test: `round_to_significant_figures` --- .../tabular/query/_lazy_math_operations.py | 4 +- .../_lazy_math_operations/test_arccos.py | 2 +- .../_lazy_math_operations/test_arcosh.py | 2 +- .../_lazy_math_operations/test_arcsin.py | 2 +- .../_lazy_math_operations/test_arctan.py | 2 +- .../_lazy_math_operations/test_arsinh.py | 2 +- .../_lazy_math_operations/test_artanh.py | 2 +- .../query/_lazy_math_operations/test_cos.py | 2 +- .../query/_lazy_math_operations/test_cosh.py | 2 +- .../test_degrees_to_radians.py | 2 +- .../query/_lazy_math_operations/test_exp.py | 2 +- .../test_radians_to_degrees.py | 2 +- .../test_round_to_significant_figures.py | 115 ++++++++++++++++++ .../query/_lazy_math_operations/test_sign.py | 2 +- .../query/_lazy_math_operations/test_sin.py | 2 +- .../query/_lazy_math_operations/test_sinh.py | 2 +- .../query/_lazy_math_operations/test_sqrt.py | 3 +- .../query/_lazy_math_operations/test_tan.py | 2 +- .../query/_lazy_math_operations/test_tanh.py | 2 +- 19 files changed, 136 insertions(+), 18 deletions(-) create mode 100644 tests/safeds/data/tabular/query/_lazy_math_operations/test_round_to_significant_figures.py diff --git a/src/safeds/data/tabular/query/_lazy_math_operations.py b/src/safeds/data/tabular/query/_lazy_math_operations.py index b5bd27ec1..5db83a445 100644 --- a/src/safeds/data/tabular/query/_lazy_math_operations.py +++ b/src/safeds/data/tabular/query/_lazy_math_operations.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING from safeds._utils import _structural_hash -from safeds._validation import _check_bounds, _OpenBound +from safeds._validation import _check_bounds, _ClosedBound, _OpenBound from safeds.data.tabular.containers._lazy_cell import _LazyCell from safeds.data.tabular.query._math_operations import MathOperations @@ -98,6 +98,8 @@ def round_to_decimal_places(self, decimal_places: int) -> Cell: return _LazyCell(self._expression.round(decimal_places)) def round_to_significant_figures(self, significant_figures: int) -> Cell: + _check_bounds("significant_figures", significant_figures, lower_bound=_ClosedBound(1)) + return _LazyCell(self._expression.round_sig_figs(significant_figures)) def sign(self) -> Cell: diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_arccos.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_arccos.py index 7c73047fc..827a1a929 100644 --- a/tests/safeds/data/tabular/query/_lazy_math_operations/test_arccos.py +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_arccos.py @@ -1,9 +1,9 @@ import math import pytest -from helpers import assert_cell_operation_works from safeds.data.tabular.typing import ColumnType +from tests.helpers import assert_cell_operation_works @pytest.mark.parametrize( diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_arcosh.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_arcosh.py index c3c73ea75..c1f826b43 100644 --- a/tests/safeds/data/tabular/query/_lazy_math_operations/test_arcosh.py +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_arcosh.py @@ -1,9 +1,9 @@ import math import pytest -from helpers import assert_cell_operation_works from safeds.data.tabular.typing import ColumnType +from tests.helpers import assert_cell_operation_works E = math.e PHI = (1 + math.sqrt(5)) / 2 diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_arcsin.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_arcsin.py index 19b724865..b8d083ed2 100644 --- a/tests/safeds/data/tabular/query/_lazy_math_operations/test_arcsin.py +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_arcsin.py @@ -1,9 +1,9 @@ import math import pytest -from helpers import assert_cell_operation_works from safeds.data.tabular.typing import ColumnType +from tests.helpers import assert_cell_operation_works @pytest.mark.parametrize( diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_arctan.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_arctan.py index 1585242e7..72fb4091f 100644 --- a/tests/safeds/data/tabular/query/_lazy_math_operations/test_arctan.py +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_arctan.py @@ -1,9 +1,9 @@ import math import pytest -from helpers import assert_cell_operation_works from safeds.data.tabular.typing import ColumnType +from tests.helpers import assert_cell_operation_works @pytest.mark.parametrize( diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_arsinh.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_arsinh.py index 865fe9361..3fe423579 100644 --- a/tests/safeds/data/tabular/query/_lazy_math_operations/test_arsinh.py +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_arsinh.py @@ -1,9 +1,9 @@ import math import pytest -from helpers import assert_cell_operation_works from safeds.data.tabular.typing import ColumnType +from tests.helpers import assert_cell_operation_works E = math.e PHI = (1 + math.sqrt(5)) / 2 diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_artanh.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_artanh.py index f49c8ba12..17aea30d2 100644 --- a/tests/safeds/data/tabular/query/_lazy_math_operations/test_artanh.py +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_artanh.py @@ -1,9 +1,9 @@ import math import pytest -from helpers import assert_cell_operation_works from safeds.data.tabular.typing import ColumnType +from tests.helpers import assert_cell_operation_works E = math.e PHI = (1 + math.sqrt(5)) / 2 diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_cos.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_cos.py index 2cd923d43..0c3255f41 100644 --- a/tests/safeds/data/tabular/query/_lazy_math_operations/test_cos.py +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_cos.py @@ -1,9 +1,9 @@ import math import pytest -from helpers import assert_cell_operation_works from safeds.data.tabular.typing import ColumnType +from tests.helpers import assert_cell_operation_works @pytest.mark.parametrize( diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_cosh.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_cosh.py index 0d1fc2ba6..6843c0049 100644 --- a/tests/safeds/data/tabular/query/_lazy_math_operations/test_cosh.py +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_cosh.py @@ -1,9 +1,9 @@ import math import pytest -from helpers import assert_cell_operation_works from safeds.data.tabular.typing import ColumnType +from tests.helpers import assert_cell_operation_works E = math.e PHI = (1 + math.sqrt(5)) / 2 diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_degrees_to_radians.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_degrees_to_radians.py index 75a111933..5a44485fb 100644 --- a/tests/safeds/data/tabular/query/_lazy_math_operations/test_degrees_to_radians.py +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_degrees_to_radians.py @@ -1,9 +1,9 @@ import math import pytest -from helpers import assert_cell_operation_works from safeds.data.tabular.typing import ColumnType +from tests.helpers import assert_cell_operation_works @pytest.mark.parametrize( diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_exp.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_exp.py index 8196c5e87..578868387 100644 --- a/tests/safeds/data/tabular/query/_lazy_math_operations/test_exp.py +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_exp.py @@ -1,9 +1,9 @@ import math import pytest -from helpers import assert_cell_operation_works from safeds.data.tabular.typing import ColumnType +from tests.helpers import assert_cell_operation_works @pytest.mark.parametrize( diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_radians_to_degrees.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_radians_to_degrees.py index fcf8a8dd4..6ab5e615a 100644 --- a/tests/safeds/data/tabular/query/_lazy_math_operations/test_radians_to_degrees.py +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_radians_to_degrees.py @@ -1,9 +1,9 @@ import math import pytest -from helpers import assert_cell_operation_works from safeds.data.tabular.typing import ColumnType +from tests.helpers import assert_cell_operation_works @pytest.mark.parametrize( diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_round_to_significant_figures.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_round_to_significant_figures.py new file mode 100644 index 000000000..c68301992 --- /dev/null +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_round_to_significant_figures.py @@ -0,0 +1,115 @@ +import pytest + +from safeds.data.tabular.containers import Column +from safeds.data.tabular.typing import ColumnType +from safeds.exceptions import OutOfBoundsError +from tests.helpers import assert_cell_operation_works + + +@pytest.mark.parametrize( + ("value", "significant_figures", "expected"), + [ + # Zero + (0, 1, 0), + (0.0, 1, 0), + # (0, 0.1) + (0.05, 1, 0.05), + (0.05, 2, 0.05), + (0.054, 1, 0.05), + (0.054, 2, 0.054), + (0.055, 1, 0.06), + (0.055, 2, 0.055), + # [0.1, 1) + (0.5, 1, 0.5), + (0.5, 2, 0.5), + (0.54, 1, 0.5), + (0.54, 2, 0.54), + (0.55, 1, 0.6), + (0.55, 2, 0.55), + # [1, 10) + (5, 1, 5), + (5, 2, 5), + (5.4, 1, 5), + (5.4, 2, 5.4), + (5.5, 1, 6), + (5.5, 2, 5.5), + # [10, 100) + (50, 1, 50), + (50, 2, 50), + (54, 1, 50), + (54, 2, 54), + (55, 1, 60), + (55, 2, 55), + # Overflow + (9.99, 1, 10), + (9.99, 2, 10), + # None + (None, 1, None), + ], + ids=[ + # Zero + "0", + "0.0", + # (0, 0.1) + "0.05 (1 sig fig)", + "0.05 (2 sig fig)", + "0.054 (1 sig fig)", + "0.054 (2 sig fig)", + "0.055 (1 sig fig)", + "0.055 (2 sig fig)", + # [0.1, 1) + "0.5 (1 sig fig)", + "0.5 (2 sig fig)", + "0.54 (1 sig fig)", + "0.54 (2 sig fig)", + "0.55 (1 sig fig)", + "0.55 (2 sig fig)", + # [1, 10) + "5 (1 sig fig)", + "5 (2 sig fig)", + "5.4 (1 sig fig)", + "5.4 (2 sig fig)", + "5.5 (1 sig fig)", + "5.5 (2 sig fig)", + # [10, 100) + "50 (1 sig fig)", + "50 (2 sig fig)", + "54 (1 sig fig)", + "54 (2 sig fig)", + "55 (1 sig fig)", + "55 (2 sig fig)", + # Overflow + "9.99 (1 sig fig)", + "9.99 (2 sig fig)", + # None + "None", + ], +) +def test_should_round_to_significant_figures( + value: float | None, + significant_figures: int, + expected: float | None, +) -> None: + assert_cell_operation_works( + value, + lambda cell: cell.math.round_to_significant_figures(significant_figures), + expected, + type_if_none=ColumnType.float64(), + ) + + +@pytest.mark.parametrize( + "significant_figures", + [ + -1, + 0, + ], + ids=[ + "negative", + "zero", + ], +) +def test_should_raise_if_parameter_is_out_of_bounds(significant_figures: int) -> None: + column = Column("a", [1]) + with pytest.raises(OutOfBoundsError): + column.transform(lambda cell: cell.math.round_to_significant_figures(significant_figures)) diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_sign.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_sign.py index 077c2a894..b6b93df3a 100644 --- a/tests/safeds/data/tabular/query/_lazy_math_operations/test_sign.py +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_sign.py @@ -1,7 +1,7 @@ import pytest -from helpers import assert_cell_operation_works from safeds.data.tabular.typing import ColumnType +from tests.helpers import assert_cell_operation_works @pytest.mark.parametrize( diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_sin.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_sin.py index 6726c9e6b..078e850d3 100644 --- a/tests/safeds/data/tabular/query/_lazy_math_operations/test_sin.py +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_sin.py @@ -1,9 +1,9 @@ import math import pytest -from helpers import assert_cell_operation_works from safeds.data.tabular.typing import ColumnType +from tests.helpers import assert_cell_operation_works @pytest.mark.parametrize( diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_sinh.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_sinh.py index 44ff52c8d..4540fd147 100644 --- a/tests/safeds/data/tabular/query/_lazy_math_operations/test_sinh.py +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_sinh.py @@ -1,9 +1,9 @@ import math import pytest -from helpers import assert_cell_operation_works from safeds.data.tabular.typing import ColumnType +from tests.helpers import assert_cell_operation_works E = math.e PHI = (1 + math.sqrt(5)) / 2 diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_sqrt.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_sqrt.py index fe5aabd10..051f98b73 100644 --- a/tests/safeds/data/tabular/query/_lazy_math_operations/test_sqrt.py +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_sqrt.py @@ -1,7 +1,8 @@ import math import pytest -from helpers import assert_cell_operation_works + +from tests.helpers import assert_cell_operation_works @pytest.mark.parametrize( diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_tan.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_tan.py index a46e32d8d..c70b4d773 100644 --- a/tests/safeds/data/tabular/query/_lazy_math_operations/test_tan.py +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_tan.py @@ -1,9 +1,9 @@ import math import pytest -from helpers import assert_cell_operation_works from safeds.data.tabular.typing import ColumnType +from tests.helpers import assert_cell_operation_works @pytest.mark.parametrize( diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_tanh.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_tanh.py index bc132d04c..255a792fa 100644 --- a/tests/safeds/data/tabular/query/_lazy_math_operations/test_tanh.py +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_tanh.py @@ -1,9 +1,9 @@ import math import pytest -from helpers import assert_cell_operation_works from safeds.data.tabular.typing import ColumnType +from tests.helpers import assert_cell_operation_works E = math.e PHI = (1 + math.sqrt(5)) / 2 From b330a4b56ecedc03b4bd105ad66bf960863234b2 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 20 Jan 2025 12:19:55 +0100 Subject: [PATCH 21/27] chore: normalize names of inverse trigonometric/hyperbolic functions --- .../tabular/query/_lazy_math_operations.py | 18 ++-- .../data/tabular/query/_math_operations.py | 82 +++++++++---------- .../{test_arccos.py => test_acos.py} | 2 +- .../{test_arcosh.py => test_acosh.py} | 2 +- .../{test_arcsin.py => test_asin.py} | 2 +- .../{test_arsinh.py => test_asinh.py} | 2 +- .../{test_arctan.py => test_atan.py} | 2 +- .../{test_artanh.py => test_atanh.py} | 2 +- 8 files changed, 56 insertions(+), 56 deletions(-) rename tests/safeds/data/tabular/query/_lazy_math_operations/{test_arccos.py => test_acos.py} (80%) rename tests/safeds/data/tabular/query/_lazy_math_operations/{test_arcosh.py => test_acosh.py} (82%) rename tests/safeds/data/tabular/query/_lazy_math_operations/{test_arcsin.py => test_asin.py} (80%) rename tests/safeds/data/tabular/query/_lazy_math_operations/{test_arsinh.py => test_asinh.py} (83%) rename tests/safeds/data/tabular/query/_lazy_math_operations/{test_arctan.py => test_atan.py} (80%) rename tests/safeds/data/tabular/query/_lazy_math_operations/{test_artanh.py => test_atanh.py} (83%) diff --git a/src/safeds/data/tabular/query/_lazy_math_operations.py b/src/safeds/data/tabular/query/_lazy_math_operations.py index 5db83a445..97757775f 100644 --- a/src/safeds/data/tabular/query/_lazy_math_operations.py +++ b/src/safeds/data/tabular/query/_lazy_math_operations.py @@ -48,22 +48,22 @@ def __str__(self) -> str: def abs(self) -> Cell: return _LazyCell(self._expression.__abs__()) - def arccos(self) -> Cell: + def acos(self) -> Cell: return _LazyCell(self._expression.arccos()) - def arcsin(self) -> Cell: - return _LazyCell(self._expression.arcsin()) - - def arcosh(self) -> Cell: + def acosh(self) -> Cell: return _LazyCell(self._expression.arccosh()) - def arctan(self) -> Cell: - return _LazyCell(self._expression.arctan()) + def asin(self) -> Cell: + return _LazyCell(self._expression.arcsin()) - def arsinh(self) -> Cell: + def asinh(self) -> Cell: return _LazyCell(self._expression.arcsinh()) - def artanh(self) -> Cell: + def atan(self) -> Cell: + return _LazyCell(self._expression.arctan()) + + def atanh(self) -> Cell: return _LazyCell(self._expression.arctanh()) def ceil(self) -> Cell: diff --git a/src/safeds/data/tabular/query/_math_operations.py b/src/safeds/data/tabular/query/_math_operations.py index 578d9c573..12469a4d4 100644 --- a/src/safeds/data/tabular/query/_math_operations.py +++ b/src/safeds/data/tabular/query/_math_operations.py @@ -81,7 +81,7 @@ def abs(self) -> Cell: """ @abstractmethod - def arccos(self) -> Cell: + def acos(self) -> Cell: """ Get the inverse cosine. @@ -94,7 +94,7 @@ def arccos(self) -> Cell: -------- >>> from safeds.data.tabular.containers import Column >>> column = Column("a", [-1, 0, 1, None]) - >>> column.transform(lambda cell: cell.math.arccos()) + >>> column.transform(lambda cell: cell.math.acos()) +---------+ | a | | --- | @@ -108,34 +108,7 @@ def arccos(self) -> Cell: """ @abstractmethod - def arcsin(self) -> Cell: - """ - Get the inverse sine. - - Returns - ------- - cell: - The inverse sine. - - Examples - -------- - >>> from safeds.data.tabular.containers import Column - >>> column = Column("a", [-1, 0, 1, None]) - >>> column.transform(lambda cell: cell.math.arcsin()) - +----------+ - | a | - | --- | - | f64 | - +==========+ - | -1.57080 | - | 0.00000 | - | 1.57080 | - | null | - +----------+ - """ - - @abstractmethod - def arcosh(self) -> Cell: + def acosh(self) -> Cell: """ Get the inverse hyperbolic cosine. @@ -148,7 +121,7 @@ def arcosh(self) -> Cell: -------- >>> from safeds.data.tabular.containers import Column >>> column = Column("a", [-1, 0, 1, None]) - >>> column.transform(lambda cell: cell.math.arcosh()) + >>> column.transform(lambda cell: cell.math.acosh()) +---------+ | a | | --- | @@ -162,34 +135,34 @@ def arcosh(self) -> Cell: """ @abstractmethod - def arctan(self) -> Cell: + def asin(self) -> Cell: """ - Get the inverse tangent. + Get the inverse sine. Returns ------- cell: - The inverse tangent. + The inverse sine. Examples -------- >>> from safeds.data.tabular.containers import Column >>> column = Column("a", [-1, 0, 1, None]) - >>> column.transform(lambda cell: cell.math.arctan()) + >>> column.transform(lambda cell: cell.math.asin()) +----------+ | a | | --- | | f64 | +==========+ - | -0.78540 | + | -1.57080 | | 0.00000 | - | 0.78540 | + | 1.57080 | | null | +----------+ """ @abstractmethod - def arsinh(self) -> Cell: + def asinh(self) -> Cell: """ Get the inverse hyperbolic sine. @@ -202,7 +175,7 @@ def arsinh(self) -> Cell: -------- >>> from safeds.data.tabular.containers import Column >>> column = Column("a", [-1, 0, 1, None]) - >>> column.transform(lambda cell: cell.math.arsinh()) + >>> column.transform(lambda cell: cell.math.asinh()) +----------+ | a | | --- | @@ -216,7 +189,34 @@ def arsinh(self) -> Cell: """ @abstractmethod - def artanh(self) -> Cell: + def atan(self) -> Cell: + """ + Get the inverse tangent. + + Returns + ------- + cell: + The inverse tangent. + + Examples + -------- + >>> from safeds.data.tabular.containers import Column + >>> column = Column("a", [-1, 0, 1, None]) + >>> column.transform(lambda cell: cell.math.atan()) + +----------+ + | a | + | --- | + | f64 | + +==========+ + | -0.78540 | + | 0.00000 | + | 0.78540 | + | null | + +----------+ + """ + + @abstractmethod + def atanh(self) -> Cell: """ Get the inverse hyperbolic tangent. @@ -229,7 +229,7 @@ def artanh(self) -> Cell: -------- >>> from safeds.data.tabular.containers import Column >>> column = Column("a", [-1, 0, 1, None]) - >>> column.transform(lambda cell: cell.math.artanh()) + >>> column.transform(lambda cell: cell.math.atanh()) +---------+ | a | | --- | diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_arccos.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_acos.py similarity index 80% rename from tests/safeds/data/tabular/query/_lazy_math_operations/test_arccos.py rename to tests/safeds/data/tabular/query/_lazy_math_operations/test_acos.py index 827a1a929..c65f11bde 100644 --- a/tests/safeds/data/tabular/query/_lazy_math_operations/test_arccos.py +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_acos.py @@ -22,4 +22,4 @@ ], ) def test_should_return_inverse_cosine(value: float | None, expected: float | None) -> None: - assert_cell_operation_works(value, lambda cell: cell.math.arccos(), expected, type_if_none=ColumnType.float64()) + assert_cell_operation_works(value, lambda cell: cell.math.acos(), expected, type_if_none=ColumnType.float64()) diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_arcosh.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_acosh.py similarity index 82% rename from tests/safeds/data/tabular/query/_lazy_math_operations/test_arcosh.py rename to tests/safeds/data/tabular/query/_lazy_math_operations/test_acosh.py index c1f826b43..d61a1b4a8 100644 --- a/tests/safeds/data/tabular/query/_lazy_math_operations/test_arcosh.py +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_acosh.py @@ -25,4 +25,4 @@ ], ) def test_should_return_inverse_hyperbolic_cosine(value: float | None, expected: float | None) -> None: - assert_cell_operation_works(value, lambda cell: cell.math.arcosh(), expected, type_if_none=ColumnType.float64()) + assert_cell_operation_works(value, lambda cell: cell.math.acosh(), expected, type_if_none=ColumnType.float64()) diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_arcsin.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_asin.py similarity index 80% rename from tests/safeds/data/tabular/query/_lazy_math_operations/test_arcsin.py rename to tests/safeds/data/tabular/query/_lazy_math_operations/test_asin.py index b8d083ed2..c2304c326 100644 --- a/tests/safeds/data/tabular/query/_lazy_math_operations/test_arcsin.py +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_asin.py @@ -22,4 +22,4 @@ ], ) def test_should_return_inverse_sine(value: float | None, expected: float | None) -> None: - assert_cell_operation_works(value, lambda cell: cell.math.arcsin(), expected, type_if_none=ColumnType.float64()) + assert_cell_operation_works(value, lambda cell: cell.math.asin(), expected, type_if_none=ColumnType.float64()) diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_arsinh.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_asinh.py similarity index 83% rename from tests/safeds/data/tabular/query/_lazy_math_operations/test_arsinh.py rename to tests/safeds/data/tabular/query/_lazy_math_operations/test_asinh.py index 3fe423579..39fde9be6 100644 --- a/tests/safeds/data/tabular/query/_lazy_math_operations/test_arsinh.py +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_asinh.py @@ -27,4 +27,4 @@ ], ) def test_should_return_inverse_hyperbolic_sine(value: float | None, expected: float | None) -> None: - assert_cell_operation_works(value, lambda cell: cell.math.arsinh(), expected, type_if_none=ColumnType.float64()) + assert_cell_operation_works(value, lambda cell: cell.math.asinh(), expected, type_if_none=ColumnType.float64()) diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_arctan.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_atan.py similarity index 80% rename from tests/safeds/data/tabular/query/_lazy_math_operations/test_arctan.py rename to tests/safeds/data/tabular/query/_lazy_math_operations/test_atan.py index 72fb4091f..e6078bbdc 100644 --- a/tests/safeds/data/tabular/query/_lazy_math_operations/test_arctan.py +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_atan.py @@ -22,4 +22,4 @@ ], ) def test_should_return_inverse_tangent(value: float | None, expected: float | None) -> None: - assert_cell_operation_works(value, lambda cell: cell.math.arctan(), expected, type_if_none=ColumnType.float64()) + assert_cell_operation_works(value, lambda cell: cell.math.atan(), expected, type_if_none=ColumnType.float64()) diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_artanh.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_atanh.py similarity index 83% rename from tests/safeds/data/tabular/query/_lazy_math_operations/test_artanh.py rename to tests/safeds/data/tabular/query/_lazy_math_operations/test_atanh.py index 17aea30d2..5fd72b305 100644 --- a/tests/safeds/data/tabular/query/_lazy_math_operations/test_artanh.py +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_atanh.py @@ -27,4 +27,4 @@ ], ) def test_should_return_inverse_hyperbolic_tangent(value: float | None, expected: float | None) -> None: - assert_cell_operation_works(value, lambda cell: cell.math.artanh(), expected, type_if_none=ColumnType.float64()) + assert_cell_operation_works(value, lambda cell: cell.math.atanh(), expected, type_if_none=ColumnType.float64()) From 284b96904e6c3f6096da789274f0c51538713c62 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 20 Jan 2025 12:33:27 +0100 Subject: [PATCH 22/27] feat: add `cbrt`, `ln`, and `log10` --- .../tabular/query/_lazy_math_operations.py | 12 ++- .../data/tabular/query/_math_operations.py | 89 ++++++++++++++++++- 2 files changed, 95 insertions(+), 6 deletions(-) diff --git a/src/safeds/data/tabular/query/_lazy_math_operations.py b/src/safeds/data/tabular/query/_lazy_math_operations.py index 97757775f..4fd5efc18 100644 --- a/src/safeds/data/tabular/query/_lazy_math_operations.py +++ b/src/safeds/data/tabular/query/_lazy_math_operations.py @@ -1,6 +1,5 @@ from __future__ import annotations -import math from typing import TYPE_CHECKING from safeds._utils import _structural_hash @@ -66,6 +65,9 @@ def atan(self) -> Cell: def atanh(self) -> Cell: return _LazyCell(self._expression.arctanh()) + def cbrt(self) -> Cell: + return _LazyCell(self._expression.cbrt()) + def ceil(self) -> Cell: return _LazyCell(self._expression.ceil()) @@ -84,13 +86,19 @@ def exp(self) -> Cell: def floor(self) -> Cell: return _LazyCell(self._expression.floor()) - def log(self, base: float = math.e) -> Cell: + def ln(self) -> Cell: + return _LazyCell(self._expression.log()) + + def log(self, base: float) -> Cell: _check_bounds("base", base, lower_bound=_OpenBound(0)) if base == 1: raise ValueError("The base of the logarithm must not be 1.") return _LazyCell(self._expression.log(base)) + def log10(self) -> Cell: + return _LazyCell(self._expression.log10()) + def radians_to_degrees(self) -> Cell: return _LazyCell(self._expression.degrees()) diff --git a/src/safeds/data/tabular/query/_math_operations.py b/src/safeds/data/tabular/query/_math_operations.py index 12469a4d4..0c33ccd69 100644 --- a/src/safeds/data/tabular/query/_math_operations.py +++ b/src/safeds/data/tabular/query/_math_operations.py @@ -1,6 +1,5 @@ from __future__ import annotations -import math from abc import ABC, abstractmethod from typing import TYPE_CHECKING @@ -242,6 +241,32 @@ def atanh(self) -> Cell: +---------+ """ + @abstractmethod + def cbrt(self) -> Cell: + """ + Get the cube root. + + Returns + ------- + cell: + The cube root. + + Examples + -------- + >>> from safeds.data.tabular.containers import Column + >>> column = Column("a", [1, 8, None]) + >>> column.transform(lambda cell: cell.math.cbrt()) + +---------+ + | a | + | --- | + | f64 | + +=========+ + | 1.00000 | + | 2.00000 | + | null | + +---------+ + """ + @abstractmethod def ceil(self) -> Cell: """ @@ -408,9 +433,37 @@ def floor(self) -> Cell: """ @abstractmethod - def log(self, base: float = math.e) -> Cell: + def ln(self) -> Cell: """ - Get the logarithm to the specified base. By default, the natural logarithm is computed. + Get the natural logarithm. + + Returns + ------- + cell: + The natural logarithm. + + Examples + -------- + >>> import math + >>> from safeds.data.tabular.containers import Column + >>> column = Column("a", [0, 1, math.e, None]) + >>> column.transform(lambda cell: cell.math.ln()) + +---------+ + | a | + | --- | + | f64 | + +=========+ + | -inf | + | 0.00000 | + | 1.00000 | + | null | + +---------+ + """ + + @abstractmethod + def log(self, base: float) -> Cell: + """ + Get the logarithm to the specified base. Parameters ---------- @@ -432,7 +485,7 @@ def log(self, base: float = math.e) -> Cell: >>> import math >>> from safeds.data.tabular.containers import Column >>> column1 = Column("a", [0, 1, math.e, None]) - >>> column1.transform(lambda cell: cell.math.log()) + >>> column1.transform(lambda cell: cell.math.log(math.e)) +---------+ | a | | --- | @@ -458,6 +511,34 @@ def log(self, base: float = math.e) -> Cell: +---------+ """ + @abstractmethod + def log10(self) -> Cell: + """ + Get the common logarithm (base 10). + + Returns + ------- + cell: + The common logarithm. + + Examples + -------- + >>> import math + >>> from safeds.data.tabular.containers import Column + >>> column = Column("a", [0, 1, 10, None]) + >>> column.transform(lambda cell: cell.math.log10()) + +---------+ + | a | + | --- | + | f64 | + +=========+ + | -inf | + | 0.00000 | + | 1.00000 | + | null | + +---------+ + """ + @abstractmethod def radians_to_degrees(self) -> Cell: """ From 6d5ef1826aa411dd8e868e1fd1d386ac8fb57073 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 20 Jan 2025 12:41:25 +0100 Subject: [PATCH 23/27] test: `round_to_decimal_places` --- .../tabular/query/_lazy_math_operations.py | 2 + .../test_round_to_decimal_places.py | 68 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 tests/safeds/data/tabular/query/_lazy_math_operations/test_round_to_decimal_places.py diff --git a/src/safeds/data/tabular/query/_lazy_math_operations.py b/src/safeds/data/tabular/query/_lazy_math_operations.py index 4fd5efc18..acd5e4bf0 100644 --- a/src/safeds/data/tabular/query/_lazy_math_operations.py +++ b/src/safeds/data/tabular/query/_lazy_math_operations.py @@ -103,6 +103,8 @@ def radians_to_degrees(self) -> Cell: return _LazyCell(self._expression.degrees()) def round_to_decimal_places(self, decimal_places: int) -> Cell: + _check_bounds("decimal_places", decimal_places, lower_bound=_ClosedBound(0)) + return _LazyCell(self._expression.round(decimal_places)) def round_to_significant_figures(self, significant_figures: int) -> Cell: diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_round_to_decimal_places.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_round_to_decimal_places.py new file mode 100644 index 000000000..fcc2d7573 --- /dev/null +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_round_to_decimal_places.py @@ -0,0 +1,68 @@ +import pytest + +from safeds.data.tabular.containers import Column +from safeds.data.tabular.typing import ColumnType +from safeds.exceptions import OutOfBoundsError +from tests.helpers import assert_cell_operation_works + + +@pytest.mark.parametrize( + ("value", "decimal_places", "expected"), + [ + # Zero + (0, 1, 0), + (0.0, 1, 0), + # Zero decimal places + (0.1, 0, 0), + (1, 0, 1), + (1.1, 0, 1), + # Rounding down + (0.14, 1, 0.1), + (0.104, 2, 0.1), + # Rounding up + (0.15, 1, 0.2), + (0.105, 2, 0.11), + # Overflow + (9.99, 1, 10), + (9.99, 2, 9.99), + # None + (None, 1, None), + ], + ids=[ + # Zero + "0", + "0.0", + # Zero decimal places + "0.1 (0 decimal places)", + "1 (0 decimal places)", + "1.1 (0 decimal places)", + # Rounding down + "0.14 (1 decimal places)", + "0.104 (2 decimal places)", + # Rounding up + "0.15 (1 decimal places)", + "0.105 (2 decimal places)", + # Overflow + "9.99 (1 decimal places)", + "9.99 (2 decimal places)", + # None + "None", + ], +) +def test_should_round_to_decimal_places( + value: float | None, + decimal_places: int, + expected: float | None, +) -> None: + assert_cell_operation_works( + value, + lambda cell: cell.math.round_to_decimal_places(decimal_places), + expected, + type_if_none=ColumnType.float64(), + ) + + +def test_should_raise_if_parameter_is_out_of_bounds() -> None: + column = Column("a", [1]) + with pytest.raises(OutOfBoundsError): + column.transform(lambda cell: cell.math.round_to_decimal_places(-1)) From 4f5502b49460ad9822dbab5c2fba7e231a87eb67 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 20 Jan 2025 12:43:28 +0100 Subject: [PATCH 24/27] test: `ln` and `log10` --- .../query/_lazy_math_operations/test_ln.py | 25 +++++++++++++++++ .../query/_lazy_math_operations/test_log10.py | 27 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 tests/safeds/data/tabular/query/_lazy_math_operations/test_ln.py create mode 100644 tests/safeds/data/tabular/query/_lazy_math_operations/test_log10.py diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_ln.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_ln.py new file mode 100644 index 000000000..63bc11424 --- /dev/null +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_ln.py @@ -0,0 +1,25 @@ +import math + +import pytest + +from safeds.data.tabular.typing import ColumnType +from tests.helpers import assert_cell_operation_works + + +@pytest.mark.parametrize( + ("value", "expected"), + [ + (0, -math.inf), + (1, 0), + (math.e, 1), + (None, None), + ], + ids=[ + "0", + "1", + "e", + "None", + ], +) +def test_should_return_natural_logarithm(value: float | None, expected: float | None) -> None: + assert_cell_operation_works(value, lambda cell: cell.math.ln(), expected, type_if_none=ColumnType.float64()) diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_log10.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_log10.py new file mode 100644 index 000000000..b784a9e21 --- /dev/null +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_log10.py @@ -0,0 +1,27 @@ +import math + +import pytest + +from safeds.data.tabular.typing import ColumnType +from tests.helpers import assert_cell_operation_works + + +@pytest.mark.parametrize( + ("value", "expected"), + [ + (0, -math.inf), + (1, 0), + (10, 1), + (100, 2), + (None, None), + ], + ids=[ + "0", + "1", + "10", + "100", + "None", + ], +) +def test_should_return_common_logarithm(value: float | None, expected: float | None) -> None: + assert_cell_operation_works(value, lambda cell: cell.math.log10(), expected, type_if_none=ColumnType.float64()) From 0be396f392080ba05f66454d6481153ba2241603 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 20 Jan 2025 12:46:01 +0100 Subject: [PATCH 25/27] test: `cbrt` --- .../query/_lazy_math_operations/test_cbrt.py | 26 +++++++++++++++++++ .../query/_lazy_math_operations/test_sqrt.py | 10 +++---- 2 files changed, 31 insertions(+), 5 deletions(-) create mode 100644 tests/safeds/data/tabular/query/_lazy_math_operations/test_cbrt.py diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_cbrt.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_cbrt.py new file mode 100644 index 000000000..32b6b01e6 --- /dev/null +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_cbrt.py @@ -0,0 +1,26 @@ +import pytest + +from tests.helpers import assert_cell_operation_works + + +@pytest.mark.parametrize( + ("value", "expected"), + [ + (-1, -1), + (0, 0), + (1, 1), + (3.375, 1.5), + (8, 2), + (None, None), + ], + ids=[ + "-1", + "0", + "1", + "cube of float", + "cube of int", + "None", + ], +) +def test_should_return_cube_root(value: float | None, expected: float | None) -> None: + assert_cell_operation_works(value, lambda cell: cell.math.cbrt(), expected) diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_sqrt.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_sqrt.py index 051f98b73..72472d707 100644 --- a/tests/safeds/data/tabular/query/_lazy_math_operations/test_sqrt.py +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_sqrt.py @@ -16,11 +16,11 @@ (None, None), ], ids=[ - "negative", - "zero", - "one", - "square of flot", - "square of int", + "-1", + "0", + "1", + "square of 1.5", + "square of 2", "None", ], ) From a0df4d03673bfb1e4248f64086213db0bb56ee29 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 20 Jan 2025 12:48:11 +0100 Subject: [PATCH 26/27] test: `log` --- .../query/_lazy_math_operations/test_log.py | 59 +++++++++++++++++++ .../test_round_to_decimal_places.py | 2 +- .../test_round_to_significant_figures.py | 2 +- 3 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 tests/safeds/data/tabular/query/_lazy_math_operations/test_log.py diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_log.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_log.py new file mode 100644 index 000000000..4ed6456eb --- /dev/null +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_log.py @@ -0,0 +1,59 @@ +import math + +import pytest + +from safeds.data.tabular.containers import Column +from safeds.data.tabular.typing import ColumnType +from tests.helpers import assert_cell_operation_works + + +@pytest.mark.parametrize( + ("value", "base", "expected"), + [ + # base e + (0, math.e, -math.inf), + (1, math.e, 0), + (math.e, math.e, 1), + # base 10 + (0, 10, -math.inf), + (1, 10, 0), + (10, 10, 1), + (100, 10, 2), + # None + (None, 10, None), + ], + ids=[ + # base e + "base e - 0", + "base e - 1", + "base e - e", + # base 10 + "base 10 - 0", + "base 10 - 1", + "base 10 - 10", + "base 10 - 100", + # None + "None", + ], +) +def test_should_return_logarithm_to_given_base(value: float | None, base: int, expected: float | None) -> None: + assert_cell_operation_works(value, lambda cell: cell.math.log(base), expected, type_if_none=ColumnType.float64()) + + +@pytest.mark.parametrize( + "base", + [ + -1, + 0, + 1, + ], + ids=[ + "negative", + "zero", + "one", + ], +) +def test_should_raise_if_base_is_out_of_bounds(base: int) -> None: + column = Column("a", [1]) + with pytest.raises(ValueError, match="base"): + column.transform(lambda cell: cell.math.log(base)) diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_round_to_decimal_places.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_round_to_decimal_places.py index fcc2d7573..7035adaf8 100644 --- a/tests/safeds/data/tabular/query/_lazy_math_operations/test_round_to_decimal_places.py +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_round_to_decimal_places.py @@ -62,7 +62,7 @@ def test_should_round_to_decimal_places( ) -def test_should_raise_if_parameter_is_out_of_bounds() -> None: +def test_should_raise_if_decimal_places_is_out_of_bounds() -> None: column = Column("a", [1]) with pytest.raises(OutOfBoundsError): column.transform(lambda cell: cell.math.round_to_decimal_places(-1)) diff --git a/tests/safeds/data/tabular/query/_lazy_math_operations/test_round_to_significant_figures.py b/tests/safeds/data/tabular/query/_lazy_math_operations/test_round_to_significant_figures.py index c68301992..3947c5d9f 100644 --- a/tests/safeds/data/tabular/query/_lazy_math_operations/test_round_to_significant_figures.py +++ b/tests/safeds/data/tabular/query/_lazy_math_operations/test_round_to_significant_figures.py @@ -109,7 +109,7 @@ def test_should_round_to_significant_figures( "zero", ], ) -def test_should_raise_if_parameter_is_out_of_bounds(significant_figures: int) -> None: +def test_should_raise_if_significant_figures_is_out_of_bounds(significant_figures: int) -> None: column = Column("a", [1]) with pytest.raises(OutOfBoundsError): column.transform(lambda cell: cell.math.round_to_significant_figures(significant_figures)) From 62e90084a95773d16c8aa8f8a9900e433a56baa0 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 20 Jan 2025 13:00:37 +0100 Subject: [PATCH 27/27] test: update assertion --- tests/helpers/_assertions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/helpers/_assertions.py b/tests/helpers/_assertions.py index cc8efb821..046ed61b6 100644 --- a/tests/helpers/_assertions.py +++ b/tests/helpers/_assertions.py @@ -1,6 +1,6 @@ import math from collections.abc import Callable -from typing import Any +from typing import Any, SupportsFloat from polars.testing import assert_frame_equal @@ -96,10 +96,10 @@ def assert_cell_operation_works( if expected is None: assert actual is None, message - elif math.isnan(expected): + elif isinstance(expected, SupportsFloat) and math.isnan(expected): # NaN != NaN assert math.isnan(actual), message - elif ignore_float_imprecision: + elif isinstance(expected, SupportsFloat) and ignore_float_imprecision: assert math.isclose(actual, expected, abs_tol=1e-15), message else: assert actual == expected, message