Added year-on-year transform.
[?]
CrEcTsRjb1hHQjHuumqRfqdbVV4X58iLEubi4noaDPFa
Jul 21, 2021, 12:19 AM
TD7KX2PIGP2TCZ2Q7XXNBBDJIQ2KXEIFWLBMWXTJE3OEXVV4L7MACDependencies
- [2]
YMV7RPQ5Improved csv reading and cleanup up serialization. - [3]
P6XNTWNXAdded GPL2 license. - [4]
IYW574EKCreated RegularTimeSeriesIter. - [5]
XIWTRGR6Prepare to make external iterator for TimeSeries. - [6]
XCCNAGMZCreated with_range() fn on RegularTimeSeries. - [7]
QYLGEDIVFirst record.
Change contents
- file move: src → src
- file move: lib.rs → lib.rs
- edit in src/lib.rs at line 1
#![deny(missing_docs)] - replacement in src/lib.rs at line 6
#![feature(min_const_generics)]/// - edit in src/lib.rs at line 25
/// A duration between two `MonthlyDates`. The value can be positive or negative.#[derive(Clone, Copy, Debug, Eq, PartialEq)]pub struct Duration(isize);impl Duration {fn months(n: isize) -> Self {Duration(n)}fn quarters(n: isize) -> Self {Duration(3 * n)}fn year(n: isize) -> Self {Duration(12 * n)}/// Return the duration between two dates.fn between(min: MonthlyDate, max: MonthlyDate) -> Self {Duration(max.as_isize() - min.as_isize())}fn is_not_positive(&self) -> bool {self.0 <= 0}} - replacement in src/lib.rs at line 59
pub struct MonthlyDate(pub usize);pub struct MonthlyDate(pub isize); - edit in src/lib.rs at line 61
// Currently only checked for positive inner value. - replacement in src/lib.rs at line 64
pub fn year(&self) -> usize {pub fn year(&self) -> isize { - replacement in src/lib.rs at line 70
self.0 % 12 + 1(self.0 % 12 + 1_isize) as usize - replacement in src/lib.rs at line 75
pub fn inner(&self) -> usize {pub fn as_isize(&self) -> isize { - replacement in src/lib.rs at line 80
pub fn ym(year: usize, month: usize) -> Self {MonthlyDate(year * 12 + month)pub fn ym(year: isize, month: usize) -> Self {MonthlyDate(year * 12 + month as isize) - replacement in src/lib.rs at line 110
impl Add for MonthlyDate {impl Add<Duration> for MonthlyDate { - replacement in src/lib.rs at line 113
fn add(self, other: Self) -> Self {MonthlyDate(self.inner() + other.inner())fn add(self, duration: Duration) -> Self {MonthlyDate(self.as_isize() + duration.0) - replacement in src/lib.rs at line 118
impl Sub for MonthlyDate {impl Sub<Duration> for MonthlyDate { - replacement in src/lib.rs at line 121
fn sub(self, other: Self) -> Self {MonthlyDate(self.inner() - other.inner())fn sub(self, duration: Duration) -> Self {MonthlyDate(self.as_isize() - duration.0) - replacement in src/lib.rs at line 220
/// Return true if the durations between Points are all equal.pub fn is_regular(&self, duration: &MonthlyDate) -> bool {self.0.as_slice().windows(2).all(|datapoint_pair| {datapoint_pair[1].date() - datapoint_pair[0].date() == *duration})}// /// Return true if the durations between Points are all equal.// pub fn is_regular(&self, duration: &MonthlyDate) -> bool {// if self.len < 2 {// return false// } else {// self.0.as_slice().windows(2).all(|datapoint_pair| {// datapoint_pair[1].date() - datapoint_pair[0].date() == *duration// })// }// } - replacement in src/lib.rs at line 232
pub fn first_duration(&self) -> Result<MonthlyDate, Error> {pub fn first_duration(&self) -> Result<Duration, Error> { - replacement in src/lib.rs at line 236
Ok((self.0)[1].date() - (self.0)[0].date())let first_date = self.0[0].date();let second_date = self.0[1].date();let duration = Duration::between(second_date, first_date);if duration.is_not_positive() {return Err(expected_positive_duration(&format!("{}-{}-01", first_date.year(), first_date.month()),&format!("{}-{}-01", second_date.year(), second_date.month()),))};Ok(duration) - edit in src/lib.rs at line 267
// The only way to construct a RegularTimeSeries is by try_into() from a// TimeSeries, because this checks sufficient length and consistent duration./// An iterator over a `RegularTimeSeries`. - replacement in src/lib.rs at line 322
let date_range = DateRange::new(Some(date2), Some(date4));let date_range = DateRange::new(&Some(date2), &Some(date4)); - replacement in src/lib.rs at line 330
/// A `RegularTimeSeries` has an additional requirements over `TimeSeries` in/// the time interval between successive `DatePoints` has the same `Duration`./// This also ensures ordering./// A `RegularTimeSeries` has an additional requirements over `TimeSeries` in the time interval/// between successive `DatePoints` has the same `Duration`. This also ensures ordering. A/// `RegularTimeSeries` is guaranteed to have two or more data points. - replacement in src/lib.rs at line 335
duration: MonthlyDate,duration: Duration, - replacement in src/lib.rs at line 352
/// the result is empty, return an empty `RegularTimeSeries`./// result has less than two data points then fail. - replacement in src/lib.rs at line 365
let date_range = DateRange::new(Some(first_date), Some(last_date));let date_range = DateRange::new(&Some(first_date), &Some(last_date)); - edit in src/lib.rs at line 376
- replacement in src/lib.rs at line 414
let date_range = DateRange::new(Some(date2), Some(date4));let date_range = DateRange::new(&Some(date2), &Some(date4)); - replacement in src/lib.rs at line 418
let mut iter = rts.iter(DateRange::new(None, None));let mut iter = rts.iter(DateRange::new(&None, &None)); - edit in src/lib.rs at line 426
/// Return the datapoint for the given date or error if that date is not in `Self`.pub fn datepoint_from_date(&self, date: MonthlyDate) -> Result<DatePoint::<N>, Error> {if date < self.first_date() || date > self.last_date(){return Err(date_not_in_timeseries(&format!("{}-{:02}-01",date.year(),date.month())))};let months_delta = date.as_isize() - self.first_date().as_isize();if months_delta % self.duration.0 != 0 {return Err(date_not_in_timeseries(&format!("{}-{:02}-01",date.year(),date.month())))};let index = date.as_isize() - self.first_date().as_isize() / self.duration.0; - edit in src/lib.rs at line 456
Ok(self.ts.0[index as usize])}/// Iterate over the data points in a `RegularTimeSeries`. - replacement in src/lib.rs at line 484
pub fn duration(&self) -> MonthlyDate {pub fn duration(&self) -> Duration { - replacement in src/lib.rs at line 502
let x = self.ts.0.iter().map(|dp| dp.date().inner() as f64).collect::<Vec<f64>>();let x = self.ts.0.iter().map(|dp| dp.date().as_isize() as f64).collect::<Vec<f64>>(); - replacement in src/lib.rs at line 508
for i in self.first_date().inner()..=self.last_date().inner() {for i in self.first_date().as_isize()..=self.last_date().as_isize() { - edit in src/lib.rs at line 514
/// Transform a `RegularTimeSeries` into quarterly data.pub fn to_quarterly(&self, n: usize) -> RegularTimeSeries<1> {let x = self.ts.0.iter().map(|dp| dp.date().as_isize() as f64).collect::<Vec<f64>>();let y = self.ts.0.iter().map(|dp| dp.value(n) as f64).collect::<Vec<f64>>(); - edit in src/lib.rs at line 521
let spline = CubicSpline::from_nodes(x, y);let (add_year, month) = match self.first_date().month() {1 => (0, 1),2 | 3 | 4 => (0, 4),5 | 6 | 7 => (0, 7),8 | 9 | 10 => (0, 10),11 | 12 => (1, 1),_ => panic!(),};let mut date = MonthlyDate::ym(self.first_date().year() + add_year, month);let mut v = Vec::new();while date <= self.last_date() {let dp = DatePoint::<1>::new(date, [spline.eval(date.as_isize() as f64) as f32]);v.push(dp);date = MonthlyDate(date.0 + 3);};TimeSeries::new(v).try_into().unwrap()}/// Transform a `RegularTimeSeries` into year-on-year percentage change over the previous year.pub fn to_year_on_year(&self, n: usize) -> Result<RegularTimeSeries<1>, Error> {let mut v = Vec::new();let mut date = self.first_date();while let Ok(dp2) = self.datepoint_from_date(date + Duration::year(1)) {let dp1 = self.datepoint_from_date(date).unwrap();let yoy = (dp2.value(0) - dp1.value(n)) * 100.0 / dp1.value(n);let dp = DatePoint::<1>::new(date + Duration::year(1), [yoy]);v.push(dp);date = date + self.duration;}TimeSeries::new(v).try_into()} - replacement in src/lib.rs at line 588
#[derive(Clone, Copy)]#[derive(Clone, Copy, Debug)] - replacement in src/lib.rs at line 596
pub fn new(start_date: Option<MonthlyDate>, end_date: Option<MonthlyDate>) -> Self {DateRange { start_date, end_date }/// Return a new `DateRange`.pub fn new(start_date: &Option<MonthlyDate>, end_date: &Option<MonthlyDate>) -> Self {DateRange {start_date: start_date.clone(),end_date: end_date.clone()} - replacement in src/lib.rs at line 606
self.start_dateself.start_date.map(|md| MonthlyDate(md.0)) - replacement in src/lib.rs at line 611
self.end_dateself.end_date.map(|md| MonthlyDate(md.0)) - edit in src/lib.rs at line 625
// This will fail if self has less that 2 datapoints. - replacement in src/lib.rs at line 628[4.8362]→[4.8362:8597](∅→∅),[4.8597]→[2.2082:2140](∅→∅),[2.2140]→[4.8642:8652](∅→∅),[4.8642]→[4.8642:8652](∅→∅)
match ts.is_regular(&duration) {true => {Ok(RegularTimeSeries::<N> {duration: duration.clone(),ts: ts,})},false => Err(expected_regular_time_series()),}Ok(RegularTimeSeries::<N> { duration, ts }) - file move: error.rs → error.rs
- edit in src/error.rs at line 11
}///pub fn date_not_in_timeseries(date: &str) -> Error {Error(format!("[time_series:01] Date {} not in time-series.",date)) - edit in src/error.rs at line 22
pub fn expected_positive_duration(date1: &str, date2: &str) -> Error {Error(format!("[time_series:01] Expected positive duration between {} and {}.",date1,date2))}/// - edit in src/error.rs at line 57
/// - edit in src/error.rs at line 64
/// - file move: LICENSE → LICENSE
- file move: Cargo.toml → Cargo.toml