Skip to content

Commit

Permalink
Add pct change (#2194)
Browse files Browse the repository at this point in the history
  • Loading branch information
mhconradt committed Dec 28, 2021
1 parent f272292 commit d6eeac2
Show file tree
Hide file tree
Showing 13 changed files with 140 additions and 0 deletions.
1 change: 1 addition & 0 deletions polars/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ interpolate = ["polars-core/interpolate", "polars-lazy/interpolate"]
list = ["polars-core/list", "polars-lazy/list"]
rank = ["polars-core/rank", "polars-lazy/rank"]
diff = ["polars-core/diff", "polars-lazy/diff"]
pct_change = ["polars-core/pct_change", "polars-lazy/pct_change"]
moment = ["polars-core/moment", "polars-lazy/moment"]
arange = ["polars-lazy/arange"]
true_div = ["polars-lazy/true_div"]
Expand Down
1 change: 1 addition & 0 deletions polars/polars-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ interpolate = []
list = []
rank = []
diff = []
pct_change = ["diff"]
moment = []
diagonal_concat = []
horizontal_concat = []
Expand Down
2 changes: 2 additions & 0 deletions polars/polars-core/src/series/ops/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ mod extend;
#[cfg(feature = "moment")]
pub mod moment;
mod null;
#[cfg(feature = "pct_change")]
pub mod pct_change;
mod to_list;

#[derive(Copy, Clone)]
Expand Down
49 changes: 49 additions & 0 deletions polars/polars-core/src/series/ops/pct_change.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use crate::prelude::*;
use crate::series::ops::NullBehavior;

impl Series {
#[cfg_attr(docsrs, doc(cfg(feature = "pct_change")))]
pub fn pct_change(&self, n: usize) -> Result<Series> {
match self.dtype() {
DataType::Float64 | DataType::Float32 => {}
_ => return self.cast(&DataType::Float64)?.pct_change(n),
}
let nn = self.fill_null(FillNullStrategy::Forward)?;
nn.diff(n, NullBehavior::Ignore).divide(&nn.shift(n as i64))
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_nulls() -> Result<()> {
let s = Series::new("", &[Some(1), None, Some(2), None, Some(3)]);
assert_eq!(
s.pct_change(1)?,
Series::new("", &[None, Some(0.0f64), Some(1.0), Some(0.), Some(0.5)])
);
Ok(())
}

#[test]
fn test_same() -> Result<()> {
let s = Series::new("", &[Some(1), Some(1), Some(1)]);
assert_eq!(
s.pct_change(1)?,
Series::new("", &[None, Some(0.0f64), Some(0.0)])
);
Ok(())
}

#[test]
fn test_two_periods() -> Result<()> {
let s = Series::new("", &[Some(1), Some(2), Some(4), Some(8), Some(16)]);
assert_eq!(
s.pct_change(2)?,
Series::new("", &[None, None, Some(3.0f64), Some(3.0), Some(3.0)])
);
Ok(())
}
}
1 change: 1 addition & 0 deletions polars/polars-lazy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ interpolate = ["polars-core/interpolate"]
rolling_window = ["polars-core/rolling_window"]
rank = ["polars-core/rank"]
diff = ["polars-core/diff"]
pct_change = ["polars-core/pct_change"]
moment = ["polars-core/moment"]
list = ["polars-core/list"]
abs = ["polars-core/abs"]
Expand Down
13 changes: 13 additions & 0 deletions polars/polars-lazy/src/dsl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1672,6 +1672,19 @@ impl Expr {
)
}

#[cfg(feature = "pct_change")]
#[cfg_attr(docsrs, doc(cfg(feature = "pct_change")))]
pub fn pct_change(self, n: usize) -> Expr {
use DataType::*;
self.apply(
move |s| s.pct_change(n),
GetOutput::map_dtype(|dt| match dt {
Float64 | Float32 => dt.clone(),
_ => Float64,
}),
)
}

#[cfg(feature = "moment")]
#[cfg_attr(docsrs, doc(cfg(feature = "moment")))]
pub fn skew(self, bias: bool) -> Expr {
Expand Down
1 change: 1 addition & 0 deletions py-polars/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ features = [
"list",
"rank",
"diff",
"pct_change",
"moment",
"arange",
"true_div",
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 @@ -141,6 +141,7 @@ Computations
Expr.abs
Expr.rank
Expr.diff
Expr.pct_change
Expr.skew
Expr.kurtosis
Expr.sqrt
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 @@ -115,6 +115,7 @@ Computations
Series.abs
Series.rank
Series.diff
Series.pct_change
Series.skew
Series.kurtosis
Series.sqrt
Expand Down
14 changes: 14 additions & 0 deletions py-polars/polars/internals/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -1794,6 +1794,20 @@ def diff(self, n: int = 1, null_behavior: str = "ignore") -> "Expr":
"""
return wrap_expr(self._pyexpr.diff(n, null_behavior))

def pct_change(self, n: int = 1) -> "Expr":
"""
Percentage change (as fraction) between current element and most-recent
non-null element at least n period(s) before the current element.
Computes the change from the previous row by default.
Parameters
----------
n
periods to shift for forming percent change.
"""
return wrap_expr(self._pyexpr.pct_change(n))

def skew(self, bias: bool = True) -> "Expr":
r"""Compute the sample skewness of a data set.
For normally distributed data, the skewness should be about zero. For
Expand Down
46 changes: 46 additions & 0 deletions py-polars/polars/internals/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -2976,6 +2976,52 @@ def diff(self, n: int = 1, null_behavior: str = "ignore") -> "Series":
"""
return wrap_s(self._s.diff(n, null_behavior))

def pct_change(self, n: int = 1) -> "Series":
"""
Percentage change (as fraction) between current element and most-recent
non-null element at least n period(s) before the current element.
Computes the change from the previous row by default.
Parameters
----------
n
periods to shift for forming percent change.
>>> pl.Series(range(10)).pct_change()
shape: (10,)
Series: '' [f64]
[
null
inf
1
0.5
0.3333333333333333
0.25
0.2
0.16666666666666666
0.14285714285714285
0.125
]
>>> pl.Series([1, 2, 4, 8, 16, 32, 64, 128, 256, 512]).pct_change(2)
shape: (10,)
Series: '' [f64]
[
null
null
3
3
3
3
3
3
3
3
]
"""
return self.to_frame().select(pli.col(self.name).pct_change(n)).to_series()

def skew(self, bias: bool = True) -> Optional[float]:
r"""Compute the sample skewness of a data set.
For normally distributed data, the skewness should be about zero. For
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 @@ -976,6 +976,10 @@ impl PyExpr {
self.inner.clone().diff(n, null_behavior).into()
}

fn pct_change(&self, n: usize) -> Self {
self.inner.clone().pct_change(n).into()
}

fn skew(&self, bias: bool) -> Self {
self.inner.clone().skew(bias).into()
}
Expand Down
6 changes: 6 additions & 0 deletions py-polars/tests/test_series.py
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,12 @@ def test_diff_dispatch() -> None:
)


def test_pct_change_dispatch() -> None:
s = pl.Series("a", [1, 2, 4, 8, 16, 32, 64])
expected = pl.Series("a", [None, None, float("inf"), 3.0, 3.0, 3.0, 3.0])
verify_series_and_expr_api(s, expected, "pct_change", 2)


def test_skew_dispatch() -> None:
s = pl.Series("a", [1, 2, 3, 2, 2, 3, 0])

Expand Down

0 comments on commit d6eeac2

Please sign in to comment.