Skip to content

Commit

Permalink
add cumprod
Browse files Browse the repository at this point in the history
  • Loading branch information
ritchie46 committed Oct 18, 2021
1 parent 96dcc54 commit 85aa974
Show file tree
Hide file tree
Showing 12 changed files with 110 additions and 1 deletion.
39 changes: 38 additions & 1 deletion polars/polars-core/src/chunked_array/ops/cum_agg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::prelude::*;
use crate::utils::CustomIterTools;
use itertools::__std_iter::FromIterator;
use num::Bounded;
use std::ops::{Add, AddAssign};
use std::ops::{Add, AddAssign, Mul};

fn det_max<T>(state: &mut T, v: Option<T>) -> Option<Option<T>>
where
Expand Down Expand Up @@ -51,6 +51,23 @@ where
}
}

fn det_prod<T>(state: &mut Option<T>, v: Option<T>) -> Option<Option<T>>
where
T: Copy + PartialOrd + Mul<Output = T>,
{
match (*state, v) {
(Some(state_inner), Some(v)) => {
*state = Some(state_inner * v);
Some(*state)
}
(None, Some(v)) => {
*state = Some(v);
Some(*state)
}
(_, None) => Some(None),
}
}

impl<T> ChunkCumAgg<T> for ChunkedArray<T>
where
T: PolarsNumericType,
Expand Down Expand Up @@ -115,6 +132,26 @@ where
ca.rename(self.name());
ca
}

fn cumprod(&self, reverse: bool) -> ChunkedArray<T> {
let init = None;
let mut ca: Self = match reverse {
false => self
.into_iter()
.scan(init, det_prod)
.trust_my_length(self.len())
.collect_trusted(),
true => self
.into_iter()
.rev()
.scan(init, det_prod)
.trust_my_length(self.len())
.collect_reversed(),
};

ca.rename(self.name());
ca
}
}

#[cfg(test)]
Expand Down
4 changes: 4 additions & 0 deletions polars/polars-core/src/chunked_array/ops/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ pub trait ChunkCumAgg<T> {
fn cumsum(&self, _reverse: bool) -> ChunkedArray<T> {
panic!("operation cumsum not supported for this dtype")
}
/// Get an array with the cumulative product computed at every element
fn cumprod(&self, _reverse: bool) -> ChunkedArray<T> {
panic!("operation cumprod not supported for this dtype")
}
}

/// Traverse and collect every nth element
Expand Down
5 changes: 5 additions & 0 deletions polars/polars-core/src/series/implementations/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,11 @@ macro_rules! impl_dyn_series {
self.0.cumsum(reverse).into_series()
}

#[cfg(feature = "cum_agg")]
fn _cumprod(&self, reverse: bool) -> Series {
self.0.cumprod(reverse).into_series()
}

#[cfg(feature = "asof_join")]
fn join_asof(&self, other: &Series) -> Result<Vec<Option<u32>>> {
self.0.join_asof(other)
Expand Down
16 changes: 16 additions & 0 deletions polars/polars-core/src/series/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,22 @@ impl Series {
}
}

/// Get an array with the cumulative product computed at every element
#[cfg_attr(docsrs, doc(cfg(feature = "cum_agg")))]
pub fn cumprod(&self, _reverse: bool) -> Series {
#[cfg(feature = "cum_agg")]
{
match self.dtype() {
DataType::Boolean => self.cast(&DataType::UInt32).unwrap()._cumprod(_reverse),
_ => self._cumprod(_reverse),
}
}
#[cfg(not(feature = "cum_agg"))]
{
panic!("activate 'cum_agg' feature")
}
}

/// Apply a rolling variance to a Series. See:
#[cfg_attr(docsrs, doc(cfg(feature = "rolling_window")))]
pub fn rolling_var(&self, _options: RollingOptions) -> Result<Series> {
Expand Down
6 changes: 6 additions & 0 deletions polars/polars-core/src/series/series_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,12 @@ pub(crate) mod private {
panic!("operation cumsum not supported for this dtype")
}

/// Get an array with the cumulative sum computed at every element
#[cfg(feature = "cum_agg")]
fn _cumprod(&self, _reverse: bool) -> Series {
panic!("operation cumprod not supported for this dtype")
}

#[cfg(feature = "asof_join")]
fn join_asof(&self, _other: &Series) -> Result<Vec<Option<u32>>> {
unimplemented!()
Expand Down
9 changes: 9 additions & 0 deletions polars/polars-lazy/src/dsl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,15 @@ impl Expr {
)
}

/// Get an array with the cumulative product computed at every element
#[cfg_attr(docsrs, doc(cfg(feature = "cum_agg")))]
pub fn cumprod(self, reverse: bool) -> Self {
self.apply(
move |s: Series| Ok(s.cumprod(reverse)),
GetOutput::same_type(),
)
}

/// Get an array with the cumulative min computed at every element
#[cfg_attr(docsrs, doc(cfg(feature = "cum_agg")))]
pub fn cummin(self, reverse: bool) -> Self {
Expand Down
1 change: 1 addition & 0 deletions py-polars/docs/source/reference/expression.rst
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ Computations
Expr.cumsum
Expr.cummin
Expr.cummax
Expr.cumprod
Expr.dot
Expr.mode
Expr.n_unique
Expand Down
1 change: 1 addition & 0 deletions py-polars/docs/source/reference/series.rst
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ Computations
Series.cumsum
Series.cummin
Series.cummax
Series.cumprod
Series.arg_sort
Series.arg_true
Series.arg_unique
Expand Down
11 changes: 11 additions & 0 deletions py-polars/polars/eager/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -980,6 +980,17 @@ def cummax(self, reverse: bool = False) -> "Series":
"""
return wrap_s(self._s.cummax(reverse))

def cumprod(self, reverse: bool = False) -> "Series":
"""
Get an array with the cumulative product computed at every element.
Parameters
----------
reverse
reverse the operation.
"""
return wrap_s(self._s.cumprod(reverse))

def limit(self, num_elements: int = 10) -> "Series":
"""
Take n elements from this Series.
Expand Down
11 changes: 11 additions & 0 deletions py-polars/polars/lazy/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,17 @@ def cumsum(self, reverse: bool = False) -> "Expr":
"""
return wrap_expr(self._pyexpr.cumsum(reverse))

def cumprod(self, reverse: bool = False) -> "Expr":
"""
Get an array with the cumulative product computed at every element.
Parameters
----------
reverse
Reverse the operation.
"""
return wrap_expr(self._pyexpr.cumprod(reverse))

def cummin(self, reverse: bool = False) -> "Expr":
"""
Get an array with the cumulative min computed at every element.
Expand Down
4 changes: 4 additions & 0 deletions py-polars/src/lazy/dsl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,10 @@ impl PyExpr {
self.clone().inner.cummin(reverse).into()
}

pub fn cumprod(&self, reverse: bool) -> PyExpr {
self.clone().inner.cumprod(reverse).into()
}

pub fn str_parse_date(&self, fmt: Option<String>) -> PyExpr {
let function = move |s: Series| {
let ca = s.utf8()?;
Expand Down
4 changes: 4 additions & 0 deletions py-polars/src/series.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,10 @@ impl PySeries {
self.series.cummin(reverse).into()
}

pub fn cumprod(&self, reverse: bool) -> Self {
self.series.cumprod(reverse).into()
}

pub fn chunk_lengths(&self) -> Vec<usize> {
self.series.chunk_lengths().collect()
}
Expand Down

0 comments on commit 85aa974

Please sign in to comment.