diff --git a/lib/explorer/backend/lazy_series.ex b/lib/explorer/backend/lazy_series.ex index bb8e5480c..44ca2105f 100644 --- a/lib/explorer/backend/lazy_series.ex +++ b/lib/explorer/backend/lazy_series.ex @@ -72,6 +72,7 @@ defmodule Explorer.Backend.LazySeries do max: 1, mean: 1, median: 1, + mode: 1, n_distinct: 1, variance: 1, standard_deviation: 1, @@ -99,6 +100,7 @@ defmodule Explorer.Backend.LazySeries do :max, :mean, :median, + :mode, :variance, :standard_deviation, :count, diff --git a/lib/explorer/backend/series.ex b/lib/explorer/backend/series.ex index 7015d5050..366a2d4fd 100644 --- a/lib/explorer/backend/series.ex +++ b/lib/explorer/backend/series.ex @@ -53,6 +53,7 @@ defmodule Explorer.Backend.Series do @callback max(s) :: number() | Date.t() | NaiveDateTime.t() | lazy_s() | nil @callback mean(s) :: float() | lazy_s() | nil @callback median(s) :: float() | lazy_s() | nil + @callback mode(s) :: number() | lazy_s() | nil @callback variance(s) :: float() | lazy_s() | nil @callback standard_deviation(s) :: float() | lazy_s() | nil @callback quantile(s, float()) :: number | Date.t() | NaiveDateTime.t() | lazy_s() | nil diff --git a/lib/explorer/polars_backend/expression.ex b/lib/explorer/polars_backend/expression.ex index 4c3718c7b..eaad59256 100644 --- a/lib/explorer/polars_backend/expression.ex +++ b/lib/explorer/polars_backend/expression.ex @@ -40,6 +40,7 @@ defmodule Explorer.PolarsBackend.Expression do mean: 1, median: 1, min: 1, + mode: 1, multiply: 2, n_distinct: 1, nil_count: 1, diff --git a/lib/explorer/polars_backend/series.ex b/lib/explorer/polars_backend/series.ex index 69e083248..f06e01893 100644 --- a/lib/explorer/polars_backend/series.ex +++ b/lib/explorer/polars_backend/series.ex @@ -148,6 +148,9 @@ defmodule Explorer.PolarsBackend.Series do @impl true def median(series), do: Shared.apply_series(series, :s_median) + @impl true + def mode(series), do: Shared.apply_series(series, :s_mode) + @impl true def variance(series), do: Shared.apply_series(series, :s_variance) diff --git a/lib/explorer/series.ex b/lib/explorer/series.ex index 627a7e596..d59f34e72 100644 --- a/lib/explorer/series.ex +++ b/lib/explorer/series.ex @@ -1241,6 +1241,27 @@ defmodule Explorer.Series do def mean(%Series{dtype: dtype}), do: dtype_error("mean/1", dtype, [:integer, :float]) + @doc """ + Gets the mode value of the series. + + ## Supported dtypes + + * `:integer` + * `:float` + + ## Examples + + iex> s = Explorer.Series.from_list([1, 2, 2, 3]) + iex> Explorer.Series.mode(s) + 2 + """ + @doc type: :aggregation + @spec mode(series :: Series.t()) :: float() | nil + def mode(%Series{dtype: dtype} = series) when numeric_dtype?(dtype), + do: Shared.apply_impl(series, :mode) + + def mode(%Series{dtype: dtype}), do: dtype_error("mode/1", dtype, [:integer, :float]) + @doc """ Gets the median value of the series. diff --git a/native/explorer/Cargo.toml b/native/explorer/Cargo.toml index 8b3f6ac29..6580aaa89 100644 --- a/native/explorer/Cargo.toml +++ b/native/explorer/Cargo.toml @@ -54,6 +54,7 @@ features = [ "to_dummies", "is_in", "strings", + "mode", ] [dependencies.polars-ops] diff --git a/native/explorer/src/expressions.rs b/native/explorer/src/expressions.rs index e887e0910..4a15b6506 100644 --- a/native/explorer/src/expressions.rs +++ b/native/explorer/src/expressions.rs @@ -598,3 +598,10 @@ pub fn expr_trim_trailing(expr: ExExpr) -> ExExpr { let expr: Expr = expr.resource.0.clone(); ExExpr::new(expr.str().rstrip(None)) } + +#[rustler::nif] +pub fn expr_mode(expr: ExExpr) -> ExExpr { + let expr: Expr = expr.resource.0.clone(); + + ExExpr::new(expr.mode()) +} diff --git a/native/explorer/src/lib.rs b/native/explorer/src/lib.rs index f08e8a4f5..9b2d28a14 100644 --- a/native/explorer/src/lib.rs +++ b/native/explorer/src/lib.rs @@ -181,6 +181,7 @@ rustler::init!( expr_mean, expr_median, expr_min, + expr_mode, expr_n_distinct, expr_nil_count, expr_quantile, @@ -258,6 +259,7 @@ rustler::init!( s_mean, s_median, s_min, + s_mode, s_multiply, s_n_distinct, s_name, diff --git a/native/explorer/src/series.rs b/native/explorer/src/series.rs index a7b8b98aa..e69c043e0 100644 --- a/native/explorer/src/series.rs +++ b/native/explorer/src/series.rs @@ -879,3 +879,12 @@ pub fn s_trim_trailing(data: ExSeries) -> Result { // There are no eager strip functions. Ok(ExSeries::new(s1.utf8()?.replace(r#"[ \s]+$"#, "")?.into())) } + +#[rustler::nif(schedule = "DirtyCpu")] +pub fn s_mode(env: Env, data: ExSeries) -> Result { + let s = &data.resource.0; + match s.dtype() { + DataType::UInt32 | DataType::Int64 | DataType::Float64 => Ok(s.mode()?.encode(env)), + dt => panic!("mode/1 not implemented for {:?}", dt), + } +}