Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Series.ewm_std/2 and Series.ewm_var/2 #778

Merged
merged 3 commits into from
Dec 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions lib/explorer/backend/lazy_series.ex
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ defmodule Explorer.Backend.LazySeries do
window_sum: 5,
window_standard_deviation: 5,
ewm_mean: 5,
ewm_standard_deviation: 6,
ewm_variance: 6,
# Transformation
column: 1,
reverse: 1,
Expand Down Expand Up @@ -662,6 +664,28 @@ defmodule Explorer.Backend.LazySeries do
Backend.Series.new(data, {:f, 64})
end

@impl true
def ewm_standard_deviation(%Series{} = series, alpha, adjust, bias, min_periods, ignore_nils) do
args = [lazy_series!(series), alpha, adjust, bias, min_periods, ignore_nils]

if aggregations?(args), do: raise_agg_inside_window(:ewm_standard_deviation)

data = new(:ewm_standard_deviation, args, {:f, 64}, false)

Backend.Series.new(data, {:f, 64})
end

@impl true
def ewm_variance(%Series{} = series, alpha, adjust, bias, min_periods, ignore_nils) do
args = [lazy_series!(series), alpha, adjust, bias, min_periods, ignore_nils]

if aggregations?(args), do: raise_agg_inside_window(:ewm_variance)

data = new(:ewm_variance, args, {:f, 64}, false)

Backend.Series.new(data, {:f, 64})
end

defp dtype_for_agg_operation(op, _) when op in [:count, :nil_count, :n_distinct], do: :integer

defp dtype_for_agg_operation(op, series)
Expand Down
18 changes: 18 additions & 0 deletions lib/explorer/backend/series.ex
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,24 @@ defmodule Explorer.Backend.Series do
ignore_nils :: boolean()
) :: s

@callback ewm_standard_deviation(
s,
alpha :: float(),
adjust :: boolean(),
bias :: boolean(),
min_periods :: integer(),
ignore_nils :: boolean()
) :: s

@callback ewm_variance(
s,
alpha :: float(),
adjust :: boolean(),
bias :: boolean(),
min_periods :: integer(),
ignore_nils :: boolean()
) :: s

# Nulls

@callback fill_missing_with_strategy(s, :backward | :forward | :min | :max | :mean) :: s
Expand Down
2 changes: 2 additions & 0 deletions lib/explorer/polars_backend/expression.ex
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ defmodule Explorer.PolarsBackend.Expression do
window_sum: 5,
window_standard_deviation: 5,
ewm_mean: 5,
ewm_standard_deviation: 6,
ewm_variance: 6,

# Conversions
strptime: 2,
Expand Down
2 changes: 2 additions & 0 deletions lib/explorer/polars_backend/native.ex
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,8 @@ defmodule Explorer.PolarsBackend.Native do
do: err()

def s_ewm_mean(_s, _alpha, _adjust, _min_periods, _ignore_nils), do: err()
def s_ewm_standard_deviation(_s, _alpha, _adjust, _bias, _min_periods, _ignore_nils), do: err()
def s_ewm_variance(_s, _alpha, _adjust, _bias, _min_periods, _ignore_nils), do: err()
def s_in(_s, _other), do: err()
def s_day_of_week(_s), do: err()
def s_day_of_year(_s), do: err()
Expand Down
14 changes: 14 additions & 0 deletions lib/explorer/polars_backend/series.ex
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,20 @@ defmodule Explorer.PolarsBackend.Series do
Shared.apply_series(series, :s_ewm_mean, [alpha, adjust, min_periods, ignore_nils])
end

@impl true
def ewm_standard_deviation(series, alpha, adjust, bias, min_periods, ignore_nils) do
Shared.apply_series(
series,
:s_ewm_standard_deviation,
[alpha, adjust, bias, min_periods, ignore_nils]
)
end

@impl true
def ewm_variance(series, alpha, adjust, bias, min_periods, ignore_nils) do
Shared.apply_series(series, :s_ewm_variance, [alpha, adjust, bias, min_periods, ignore_nils])
end

# Missing values

@impl true
Expand Down
110 changes: 110 additions & 0 deletions lib/explorer/series.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4848,6 +4848,116 @@ defmodule Explorer.Series do
])
end

@doc """
Calculate the exponentially weighted moving standard deviation, given smoothing factor alpha.

## Options

* `:alpha` - Optional smoothing factor which specifies the imporance given
to most recent observations. It is a value such that, 0 < alpha <= 1. Defaults to 0.5.

* `:adjust` - If set to true, it corrects the bias introduced by smoothing process.
Defaults to `true`.

* `:bias` - If set to false, it corrects the estimate to be statistically unbiased.
Defaults to `false`.

* `:min_periods` - The number of values in the window that should be non-nil
before computing a result. Defaults to `1`.

* `:ignore_nils` - If set to true, it ignore nulls in the calculation. Defaults to `true`.

## Examples

iex> s = 1..5 |> Enum.to_list() |> Explorer.Series.from_list()
iex> Explorer.Series.ewm_standard_deviation(s)
#Explorer.Series<
Polars[5]
f64 [0.0, 0.7071067811865476, 0.9636241116594314, 1.1771636613972951, 1.3452425132127066]
>

iex> s = 1..5 |> Enum.to_list() |> Explorer.Series.from_list()
iex> Explorer.Series.ewm_standard_deviation(s, alpha: 0.1)
#Explorer.Series<
Polars[5]
f64 [0.0, 0.7071067811865476, 0.9990770648702808, 1.2879021599718157, 1.5741638698820746]
>
"""
@doc type: :window
def ewm_standard_deviation(series, opts \\ []) do
opts =
Keyword.validate!(opts,
alpha: 0.5,
adjust: true,
bias: false,
min_periods: 1,
ignore_nils: true
)

apply_series(series, :ewm_standard_deviation, [
opts[:alpha],
opts[:adjust],
opts[:bias],
opts[:min_periods],
opts[:ignore_nils]
])
end

@doc """
Calculate the exponentially weighted moving variance, given smoothing factor alpha.

## Options

* `:alpha` - Optional smoothing factor which specifies the imporance given
to most recent observations. It is a value such that, 0 < alpha <= 1. Defaults to 0.5.

* `:adjust` - If set to true, it corrects the bias introduced by smoothing process.
Defaults to `true`.

* `:bias` - If set to false, it corrects the estimate to be statistically unbiased.
Defaults to `false`.

* `:min_periods` - The number of values in the window that should be non-nil
before computing a result. Defaults to `1`.

* `:ignore_nils` - If set to true, it ignore nulls in the calculation. Defaults to `true`.

## Examples

iex> s = 1..5 |> Enum.to_list() |> Explorer.Series.from_list()
iex> Explorer.Series.ewm_variance(s)
#Explorer.Series<
Polars[5]
f64 [0.0, 0.5, 0.9285714285714284, 1.385714285714286, 1.8096774193548393]
>

iex> s = 1..5 |> Enum.to_list() |> Explorer.Series.from_list()
iex> Explorer.Series.ewm_variance(s, alpha: 0.1)
#Explorer.Series<
Polars[5]
f64 [0.0, 0.5, 0.9981549815498153, 1.6586919736600685, 2.4779918892421087]
>
"""
@doc type: :window
def ewm_variance(series, opts \\ []) do
opts =
Keyword.validate!(opts,
alpha: 0.5,
adjust: true,
bias: false,
min_periods: 1,
ignore_nils: true
)

apply_series(series, :ewm_variance, [
opts[:alpha],
opts[:adjust],
opts[:bias],
opts[:min_periods],
opts[:ignore_nils]
])
end

# Missing values

@doc """
Expand Down
30 changes: 29 additions & 1 deletion native/explorer/src/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use polars::prelude::{
col, concat_str, cov, pearson_corr, spearman_rank_corr, when, IntoLazy, LiteralValue,
SortOptions,
};
use polars::prelude::{DataType, Expr, Literal, StrptimeOptions, TimeUnit};
use polars::prelude::{DataType, Expr, EWMOptions, Literal, StrptimeOptions, TimeUnit};

use crate::datatypes::{
ExCorrelationMethod, ExDate, ExDateTime, ExDuration, ExRankMethod, ExSeriesDtype, ExValidValue,
Expand Down Expand Up @@ -699,6 +699,34 @@ pub fn expr_ewm_mean(
ExExpr::new(expr.ewm_mean(opts))
}

#[rustler::nif]
pub fn expr_ewm_standard_deviation(
data: ExExpr,
alpha: f64,
adjust: bool,
bias: bool,
min_periods: usize,
ignore_nulls: bool,
) -> ExExpr {
let expr = data.clone_inner();
let opts = EWMOptions { alpha, adjust, bias, min_periods, ignore_nulls, ..Default::default() };
ExExpr::new(expr.ewm_std(opts))
}

#[rustler::nif]
pub fn expr_ewm_variance(
data: ExExpr,
alpha: f64,
adjust: bool,
bias: bool,
min_periods: usize,
ignore_nulls: bool,
) -> ExExpr {
let expr = data.clone_inner();
let opts = EWMOptions { alpha, adjust, bias, min_periods, ignore_nulls, ..Default::default() };
ExExpr::new(expr.ewm_var(opts))
}

#[rustler::nif]
pub fn expr_reverse(expr: ExExpr) -> ExExpr {
let expr = expr.clone_inner();
Expand Down
4 changes: 4 additions & 0 deletions native/explorer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,8 @@ rustler::init!(
expr_window_sum,
expr_window_standard_deviation,
expr_ewm_mean,
expr_ewm_standard_deviation,
expr_ewm_variance,
// inspect expressions
expr_describe_filter_plan,
// string expressions
Expand Down Expand Up @@ -461,6 +463,8 @@ rustler::init!(
s_window_sum,
s_window_standard_deviation,
s_ewm_mean,
s_ewm_standard_deviation,
s_ewm_variance,
s_in,
s_round,
s_floor,
Expand Down
28 changes: 28 additions & 0 deletions native/explorer/src/series.rs
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,34 @@ pub fn s_ewm_mean(
Ok(ExSeries::new(s1))
}

#[rustler::nif(schedule = "DirtyCpu")]
pub fn s_ewm_standard_deviation(
series: ExSeries,
alpha: f64,
adjust: bool,
bias: bool,
min_periods: usize,
ignore_nulls: bool,
) -> Result<ExSeries, ExplorerError> {
let opts = EWMOptions { alpha, adjust, bias, min_periods, ignore_nulls, ..Default::default() };
let s1 = polars_ops::prelude::ewm_std(&series, opts)?;
Ok(ExSeries::new(s1))
}

#[rustler::nif(schedule = "DirtyCpu")]
pub fn s_ewm_variance(
series: ExSeries,
alpha: f64,
adjust: bool,
bias: bool,
min_periods: usize,
ignore_nulls: bool,
) -> Result<ExSeries, ExplorerError> {
let opts = EWMOptions { alpha, adjust, bias, min_periods, ignore_nulls, ..Default::default() };
let s1 = polars_ops::prelude::ewm_var(&series, opts)?;
Ok(ExSeries::new(s1))
}

pub fn ewm_opts(alpha: f64, adjust: bool, min_periods: usize, ignore_nulls: bool) -> EWMOptions {
EWMOptions {
alpha,
Expand Down
Loading
Loading