Added year-on-year transform.

[?]
CrEcTsRjb1hHQjHuumqRfqdbVV4X58iLEubi4noaDPFa
Jul 21, 2021, 12:19 AM
TD7KX2PIGP2TCZ2Q7XXNBBDJIQ2KXEIFWLBMWXTJE3OEXVV4L7MAC

Dependencies

  • [2] YMV7RPQ5 Improved csv reading and cleanup up serialization.
  • [3] P6XNTWNX Added GPL2 license.
  • [4] IYW574EK Created RegularTimeSeriesIter.
  • [5] XIWTRGR6 Prepare to make external iterator for TimeSeries.
  • [6] XCCNAGMZ Created with_range() fn on RegularTimeSeries.
  • [7] QYLGEDIV First record.

Change contents

  • file move: src (dxwrxwrx-r)src (d--r------)
    [1.0]
    [4.6]
  • file move: lib.rs (-xw-xw-x--)lib.rs (----------)
    [4.6]
    [4.16]
  • edit in src/lib.rs at line 1
    [4.16]
    [4.0]
    #![deny(missing_docs)]
  • replacement in src/lib.rs at line 6
    [4.46][4.46:78](),[4.78][2.0:1]()
    #![feature(min_const_generics)]
    [4.46]
    [2.1]
    ///
  • edit in src/lib.rs at line 25
    [4.246]
    [4.358]
    /// 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
    [4.59][4.567:602](),[4.527][4.567:602](),[4.567][4.567:602]()
    pub struct MonthlyDate(pub usize);
    [4.527]
    [4.602]
    pub struct MonthlyDate(pub isize);
  • edit in src/lib.rs at line 61
    [4.603]
    [4.603]
    // Currently only checked for positive inner value.
  • replacement in src/lib.rs at line 64
    [4.657][4.657:691]()
    pub fn year(&self) -> usize {
    [4.657]
    [4.691]
    pub fn year(&self) -> isize {
  • replacement in src/lib.rs at line 70
    [4.789][2.39:63]()
    self.0 % 12 + 1
    [4.789]
    [4.809]
    (self.0 % 12 + 1_isize) as usize
  • replacement in src/lib.rs at line 75
    [4.933][4.933:968]()
    pub fn inner(&self) -> usize {
    [4.933]
    [4.968]
    pub fn as_isize(&self) -> isize {
  • replacement in src/lib.rs at line 80
    [4.1043][4.1043:1133]()
    pub fn ym(year: usize, month: usize) -> Self {
    MonthlyDate(year * 12 + month)
    [4.1043]
    [4.1133]
    pub fn ym(year: isize, month: usize) -> Self {
    MonthlyDate(year * 12 + month as isize)
  • replacement in src/lib.rs at line 110
    [4.1490][4.1490:1517]()
    impl Add for MonthlyDate {
    [4.1490]
    [4.1517]
    impl Add<Duration> for MonthlyDate {
  • replacement in src/lib.rs at line 113
    [4.1549][4.1549:1639]()
    fn add(self, other: Self) -> Self {
    MonthlyDate(self.inner() + other.inner())
    [4.1549]
    [4.1639]
    fn add(self, duration: Duration) -> Self {
    MonthlyDate(self.as_isize() + duration.0)
  • replacement in src/lib.rs at line 118
    [4.1648][4.1648:1675]()
    impl Sub for MonthlyDate {
    [4.1648]
    [4.1675]
    impl Sub<Duration> for MonthlyDate {
  • replacement in src/lib.rs at line 121
    [4.1707][4.1707:1797]()
    fn sub(self, other: Self) -> Self {
    MonthlyDate(self.inner() - other.inner())
    [4.1707]
    [4.1797]
    fn sub(self, duration: Duration) -> Self {
    MonthlyDate(self.as_isize() - duration.0)
  • replacement in src/lib.rs at line 220
    [4.3969][4.3969:4253]()
    /// 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
    })
    }
    [4.3969]
    [4.4253]
    // /// 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
    [4.4319][4.4319:4384]()
    pub fn first_duration(&self) -> Result<MonthlyDate, Error> {
    [4.4319]
    [2.1523]
    pub fn first_duration(&self) -> Result<Duration, Error> {
  • replacement in src/lib.rs at line 236
    [2.1671][4.4510:4562](),[4.4510][4.4510:4562]()
    Ok((self.0)[1].date() - (self.0)[0].date())
    [2.1671]
    [4.4562]
    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
    [4.786]
    [4.786]
    // 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
    [4.2430][4.2430:2493]()
    let date_range = DateRange::new(Some(date2), Some(date4));
    [4.2430]
    [4.2493]
    let date_range = DateRange::new(&Some(date2), &Some(date4));
  • replacement in src/lib.rs at line 330
    [4.4995][4.4995:5184]()
    /// A `RegularTimeSeries` has an additional requirements over `TimeSeries` in
    /// the time interval between successive `DatePoints` has the same `Duration`.
    /// This also ensures ordering.
    [4.4995]
    [2.1672]
    /// 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
    [4.5259][4.5259:5288]()
    duration: MonthlyDate,
    [4.5259]
    [4.5288]
    duration: Duration,
  • replacement in src/lib.rs at line 352
    [4.525][4.525:591]()
    /// the result is empty, return an empty `RegularTimeSeries`.
    [4.525]
    [4.2727]
    /// result has less than two data points then fail.
  • replacement in src/lib.rs at line 365
    [4.1117][4.1117:1193]()
    let date_range = DateRange::new(Some(first_date), Some(last_date));
    [4.1117]
    [4.1193]
    let date_range = DateRange::new(&Some(first_date), &Some(last_date));
  • edit in src/lib.rs at line 376
    [4.1488]
    [4.1488]
  • replacement in src/lib.rs at line 414
    [4.1099][4.1099:1162]()
    let date_range = DateRange::new(Some(date2), Some(date4));
    [4.1099]
    [4.1162]
    let date_range = DateRange::new(&Some(date2), &Some(date4));
  • replacement in src/lib.rs at line 418
    [4.1198][4.1198:1255]()
    let mut iter = rts.iter(DateRange::new(None, None));
    [4.1198]
    [4.1255]
    let mut iter = rts.iter(DateRange::new(&None, &None));
  • edit in src/lib.rs at line 426
    [4.5366]
    [4.1550]
    /// 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
    [4.1551]
    [4.2864]
    Ok(self.ts.0[index as usize])
    }
    /// Iterate over the data points in a `RegularTimeSeries`.
  • replacement in src/lib.rs at line 484
    [4.5410][4.5410:5454]()
    pub fn duration(&self) -> MonthlyDate {
    [4.5410]
    [4.5454]
    pub fn duration(&self) -> Duration {
  • replacement in src/lib.rs at line 502
    [4.6201][4.6201:6292]()
    let x = self.ts.0.iter().map(|dp| dp.date().inner() as f64).collect::<Vec<f64>>();
    [4.6201]
    [4.6292]
    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
    [4.6463][4.6463:6535]()
    for i in self.first_date().inner()..=self.last_date().inner() {
    [4.6463]
    [4.6535]
    for i in self.first_date().as_isize()..=self.last_date().as_isize() {
  • edit in src/lib.rs at line 514
    [4.6712]
    [4.6714]
    /// 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
    [4.6715]
    [4.6759]
    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
    [4.1949][4.7650:7673](),[4.7650][4.7650:7673]()
    #[derive(Clone, Copy)]
    [4.1949]
    [4.7673]
    #[derive(Clone, Copy, Debug)]
  • replacement in src/lib.rs at line 596
    [4.1951][4.1951:2083]()
    pub fn new(start_date: Option<MonthlyDate>, end_date: Option<MonthlyDate>) -> Self {
    DateRange { start_date, end_date }
    [4.1951]
    [4.2083]
    /// 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
    [4.7875][4.7875:7899]()
    self.start_date
    [4.7875]
    [4.7899]
    self.start_date.map(|md| MonthlyDate(md.0))
  • replacement in src/lib.rs at line 611
    [4.7990][4.7990:8012]()
    self.end_date
    [4.7990]
    [4.8012]
    self.end_date.map(|md| MonthlyDate(md.0))
  • edit in src/lib.rs at line 625
    [2.2081]
    [4.8317]
    // 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()),
    }
    [4.8362]
    [4.8652]
    Ok(RegularTimeSeries::<N> { duration, ts })
  • file move: error.rs (-xw-xw-x--)error.rs (----------)
    [4.6]
    [2.2497]
  • edit in src/error.rs at line 11
    [2.2713]
    [2.2713]
    }
    ///
    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
    [2.2720]
    [2.2720]
    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
    [2.3349]
    [2.3349]
    ///
  • edit in src/error.rs at line 64
    [2.3489]
    [2.3489]
    ///
  • file move: LICENSE (-xw-xw-x--)LICENSE (----------)
    [1.0]
    [3.258]
  • file move: Cargo.toml (-xw-xw-x--)Cargo.toml (----------)
    [1.0]
    [4.10483]