Ctrl K
ComunidadGuías…

timeseries/timeseries.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package timeseries

import (
	"encoding/json"
	"fmt"
	"math"
	"strings"

	"github.com/vmihailenco/msgpack/v5"
)

var NaN = float32(math.NaN())

func IsNaN(v float32) bool {
	return v != v
}

func IsInf(f float32, sign int) bool {
	return sign >= 0 && f > math.MaxFloat32 || sign <= 0 && f < -math.MaxFloat32
}

type TimeSeries struct {
	from Time
	step Duration
	data []float32
	last float32
}

func New(from Time, pointsCount int, step Duration) *TimeSeries {
	data := make([]float32, pointsCount)
	for i := range data {
		data[i] = NaN
	}
	return NewWithData(from, step, data)
}

func NewWithData(from Time, step Duration, data []float32) *TimeSeries {
	ts := &TimeSeries{
		from: from,
		step: step,
		data: data,
		last: data[len(data)-1],
	}
	return ts
}

func (ts *TimeSeries) Len() int {
	if ts.IsEmpty() {
		return 0
	}
	return len(ts.data)
}

func (ts *TimeSeries) MarshalJSON() ([]byte, error) {
	if ts.IsEmpty() {
		return json.Marshal(nil)
	}
	vs := make([]Value, 0, ts.Len())
	iter := ts.Iter()
	for iter.Next() {
		_, v := iter.Value()
		vs = append(vs, Value(v))
	}
	if len(vs) == 0 {
		return json.Marshal(nil)
	}
	d, err := json.Marshal(vs)
	return d, err
}

func (ts *TimeSeries) UnmarshalJSON(data []byte) error {
	if string(data) == "null" {
		ts.data = nil
		ts.last = NaN
		return nil
	}

	var raw []*float32
	if err := json.Unmarshal(data, &raw); err != nil {
		return err
	}

	values := make([]float32, len(raw))
	for i, v := range raw {
		if v == nil {
			values[i] = NaN
		} else {
			values[i] = *v
		}
	}

	ts.data = values
	if len(values) > 0 {
		ts.last = values[len(values)-1]
	} else {
		ts.last = NaN
	}

	return nil
}

type Msgpack struct {
	From Time     `msgpack:"from"`
	Step Duration `msgpack:"step"`
	Data []byte   `msgpack:"data"`
}

func (ts *TimeSeries) EncodeMsgpack(enc *msgpack.Encoder) error {
	m := Msgpack{
		From: ts.from,
		Step: ts.step,
		Data: asBytes32(ts.data),
	}
	return enc.Encode(m)
}

func (ts *TimeSeries) DecodeMsgpack(dec *msgpack.Decoder) error {
	var m Msgpack
	err := dec.Decode(&m)
	if err != nil {
		return err
	}
	ts.from = m.From
	ts.step = m.Step
	ts.last = NaN
	if len(m.Data) > 0 {
		ts.data = asFloats32(m.Data)
		ts.last = ts.data[len(ts.data)-1]
	}
	return nil
}

func (ts *TimeSeries) String() string {
	if ts.IsEmpty() {
		return "TimeSeries(nil)"
	}
	values := make([]string, 0, ts.Len())
	iter := ts.Iter()
	for iter.Next() {
		_, v := iter.Value()
		values = append(values, Value(v).String())
	}
	return fmt.Sprintf("TimeSeries(%d, %d, %d, [%s])", ts.from, ts.Len(), ts.step, strings.Join(values, " "))
}

func (ts *TimeSeries) Get() *TimeSeries {
	return ts
}

func (ts *TimeSeries) Set(t Time, v float32) {
	t = t.Truncate(ts.step)
	if t < ts.from {
		return
	}
	idx := int((t - ts.from) / Time(ts.step))
	l := len(ts.data) - 1
	if idx <= l {
		ts.data[idx] = v
		if idx == l {
			ts.last = v
		}
	}
}

type FillFunc func(ts *TimeSeries, from Time, step Duration, data []float32) bool

func FillAny(ts *TimeSeries, from Time, step Duration, data []float32) bool {
	changed := false
	maxIndex := len(ts.data) - 1
	tSrc, iSrc := from, 0
	if ts.from.Sub(tSrc) >= ts.step {
		tSrc = tSrc.Add(ts.from.Sub(tSrc.Truncate(ts.step)).Truncate(ts.step))
		if tSrc > ts.from {
			tSrc = tSrc.Add(-ts.step)
		}
		iSrc = int((tSrc - from) / Time(step))
	}
	tDst, iDst := ts.from, 0
	if tSrc > tDst {
		tDst = tSrc.Truncate(ts.step)
		if tDst < tSrc {
			tDst = tDst.Add(ts.step)
		}
		iDst = int((tDst - ts.from) / Time(ts.step))
	}
	vv := NaN
	for _, v := range data[iSrc:] {
		if tSrc > tDst {
			ts.data[iDst] = vv
			vv = NaN
			iDst++
			if iDst > maxIndex {
				break
			}
			tDst += Time(ts.step)
		}
		if !IsNaN(v) {
			vv = v
			changed = true
		}
		tSrc += Time(step)
	}
	if iDst <= maxIndex {
		ts.data[iDst] = vv
	}
	ts.last = ts.data[maxIndex]
	return changed
}

func FillSum(ts *TimeSeries, from Time, step Duration, data []float32) bool {
	changed := false
	maxIndex := len(ts.data) - 1
	tSrc, iSrc := from, 0
	if ts.from.Sub(tSrc) >= ts.step {
		tSrc = tSrc.Add(ts.from.Sub(tSrc.Truncate(ts.step)).Truncate(ts.step))
		if tSrc > ts.from {
			tSrc = tSrc.Add(-ts.step)
		}
		iSrc = int((tSrc - from) / Time(step))
	}
	tDst, iDst := ts.from, 0
	if tSrc > tDst {
		tDst = tSrc.Truncate(ts.step)
		if tDst < tSrc {
			tDst = tDst.Add(ts.step)
		}
		iDst = int((tDst - ts.from) / Time(ts.step))
	}
	vv := float32(0)
	if !IsNaN(ts.data[iDst]) {
		vv = ts.data[iDst]
	}
	for _, v := range data[iSrc:] {
		if tSrc > tDst {
			ts.data[iDst] = vv
			vv = 0
			iDst++
			if iDst > maxIndex {
				break
			}
			tDst += Time(ts.step)
		}
		if !IsNaN(v) {
			vv += v
			changed = true
		}
		tSrc += Time(step)
	}
	if iDst <= maxIndex {
		ts.data[iDst] = vv
	}
	ts.last = ts.data[maxIndex]
	return changed
}

func FillAvg(ts *TimeSeries, from Time, step Duration, data []float32) bool {
	changed := false
	maxIndex := len(ts.data) - 1
	tSrc, iSrc := from, 0
	if ts.from.Sub(tSrc) >= ts.step {
		tSrc = tSrc.Add(ts.from.Sub(tSrc.Truncate(ts.step)).Truncate(ts.step))
		if tSrc > ts.from {
			tSrc = tSrc.Add(-ts.step)
		}
		iSrc = int((tSrc - from) / Time(step))
	}
	tDst, iDst := ts.from, 0
	if tSrc > tDst {
		tDst = tSrc.Truncate(ts.step)
		if tDst < tSrc {
			tDst = tDst.Add(ts.step)
		}
		iDst = int((tDst - ts.from) / Time(ts.step))
	}
	vv := float32(0)
	cc := float32(0)
	if !IsNaN(ts.data[iDst]) {
		vv = ts.data[iDst]
		cc = 1
	}
	for _, v := range data[iSrc:] {
		if tSrc > tDst {
			if cc > 0 {
				ts.data[iDst] = vv / cc
			}
			vv = 0
			cc = 0
			iDst++
			if iDst > maxIndex {
				break
			}
			tDst += Time(ts.step)
		}
		if !IsNaN(v) {
			vv += v
			cc++
			changed = true
		}
		tSrc += Time(step)
	}
	if iDst <= maxIndex {
		if cc > 0 {
			ts.data[iDst] = vv / cc
		}
	}
	ts.last = ts.data[maxIndex]
	return changed
}

func (ts *TimeSeries) Iter() *Iterator {
	if ts.IsEmpty() {
		return &Iterator{data: nil}
	}
	return &Iterator{
		step: ts.step,
		data: ts.data,
		idx:  -1,
		t:    ts.from.Add(-ts.step),
	}
}

func (ts *TimeSeries) IterFrom(from Time) *Iterator {
	if ts.IsEmpty() {
		return &Iterator{data: nil}
	}
	if from.Before(ts.from) {
		from = ts.from
	}
	to := ts.from.Add(ts.step * Duration(len(ts.data)-1))
	if from.After(to) {
		return &Iterator{data: nil}
	}
	from = from.Truncate(ts.step)
	return &Iterator{
		step: ts.step,
		data: ts.data,
		idx:  int(from.Sub(ts.from)/ts.step) - 1,
		t:    from.Add(-ts.step),
	}
}

func (ts *TimeSeries) IsEmpty() bool {
	return ts == nil
}

func (ts *TimeSeries) Last() float32 {
	if ts.IsEmpty() {
		return NaN
	}
	return ts.last
}

func (ts *TimeSeries) TailIsEmpty() bool {
	if ts.IsEmpty() {
		return true
	}
	if !IsNaN(ts.last) {
		return false
	}
	l := len(ts.data)
	if l >= 2 && !IsNaN(ts.data[l-2]) {
		return false
	}
	if l >= 3 && !IsNaN(ts.data[l-3]) {
		return false
	}
	return true
}

func (ts *TimeSeries) Reduce(f F) float32 {
	if ts.IsEmpty() {
		return NaN
	}
	accumulator := NaN
	iter := ts.Iter()
	for iter.Next() {
		t, v := iter.Value()
		accumulator = f(t, accumulator, v)
	}
	return accumulator
}

func (ts *TimeSeries) ReduceFrom(from Time, f F) float32 {
	if ts.IsEmpty() {
		return NaN
	}
	accumulator := NaN
	iter := ts.IterFrom(from)
	for iter.Next() {
		t, v := iter.Value()
		accumulator = f(t, accumulator, v)
	}
	return accumulator
}

func (ts *TimeSeries) Map(f func(t Time, v float32) float32) *TimeSeries {
	if ts.IsEmpty() {
		return nil
	}

	data := make([]float32, ts.Len())
	iter := ts.Iter()
	i := 0
	for iter.Next() {
		t, v := iter.Value()
		data[i] = f(t, v)
		i++
	}
	return NewWithData(ts.from, ts.step, data)
}

func (ts *TimeSeries) MapInPlace(f func(t Time, v float32) float32) *TimeSeries {
	if ts.IsEmpty() {
		return nil
	}

	t := ts.from
	for i, v := range ts.data {
		ts.data[i] = f(t, v)
		t = t.Add(ts.step)
	}
	ts.last = ts.data[len(ts.data)-1]
	return ts
}

func (ts *TimeSeries) WithNewValue(newValue float32) *TimeSeries {
	if ts.IsEmpty() {
		return nil
	}

	data := make([]float32, ts.Len())
	for i := range data {
		data[i] = newValue
	}
	return NewWithData(ts.from, ts.step, data)
}

func (ts *TimeSeries) NewWithData(data []float32) *TimeSeries {
	if ts.IsEmpty() {
		return nil
	}
	return NewWithData(ts.from, ts.step, data)
}

func (ts *TimeSeries) LastNotNull() (Time, float32) {
	if ts.IsEmpty() {
		return 0, NaN
	}
	var vv float32
	var tt Time
	iter := ts.Iter()
	for iter.Next() {
		t, v := iter.Value()
		if !IsNaN(v) {
			vv = v
			tt = t
		}
	}
	return tt, vv
}

func (ts *TimeSeries) LastNAvg(nPoints int, defaultValue float32) float32 {
	if ts.IsEmpty() || len(ts.data) < nPoints {
		return defaultValue
	}
	sum := float32(0)
	count := 0
	for _, v := range ts.data[len(ts.data)-nPoints:] {
		if !IsNaN(v) {
			sum += v
			count++
		}
	}
	if count > 0 {
		return sum / float32(count)
	}
	return defaultValue
}

func (ts *TimeSeries) LastNMax(nPoints int, defaultValue float32) float32 {
	if ts.IsEmpty() || len(ts.data) < nPoints {
		return defaultValue
	}
	res := defaultValue
	found := false
	for _, v := range ts.data[len(ts.data)-nPoints:] {
		if IsNaN(v) {
			continue
		}
		if !found || v > res {
			res, found = v, true
		}
	}
	return res
}

func (ts *TimeSeries) Average() float32 {
	if ts.IsEmpty() {
		return NaN
	}
	sum, count := float32(0), 0
	iter := ts.Iter()
	for iter.Next() {
		if _, v := iter.Value(); !IsNaN(v) {
			sum += v
			count++
		}
	}
	if count == 0 {
		return NaN
	}
	return sum / float32(count)
}

func Increase(x, status *TimeSeries) *TimeSeries {
	if x.IsEmpty() || status.IsEmpty() {
		return nil
	}
	data := make([]float32, 0, x.Len())
	prev, prevStatus := NaN, NaN
	iter := x.Iter()
	statusIter := status.Iter()
	var d float32
	for iter.Next() && statusIter.Next() {
		_, v := iter.Value()
		d = NaN
		switch {
		case !IsNaN(v) && !IsNaN(prev):
			if v-prev >= 0 {
				d = v - prev
			} else {
				d = v
			}
		case IsNaN(prev) && prevStatus == 1:
			d = v
		}
		prev = v
		_, prevStatus = statusIter.Value()
		data = append(data, d)
	}
	return NewWithData(x.from, x.step, data)
}

func Aggregate2(x, y *TimeSeries, f func(x, y float32) float32) *TimeSeries {
	if x.IsEmpty() || y.IsEmpty() {
		return nil
	}
	data := make([]float32, x.Len())
	xIter := x.Iter()
	yIter := y.Iter()
	i := 0
	for xIter.Next() && yIter.Next() {
		_, xv := xIter.Value()
		_, yv := yIter.Value()
		data[i] = f(xv, yv)
		i++
	}
	return NewWithData(x.from, x.step, data)
}

func Mul(x, y *TimeSeries) *TimeSeries {
	return Aggregate2(x, y, func(x, y float32) float32 { return x * y })
}

func Div(x, y *TimeSeries) *TimeSeries {
	return Aggregate2(x, y, func(x, y float32) float32 { return x / y })
}

func Sub(x, y *TimeSeries) *TimeSeries {
	return Aggregate2(x, y, func(x, y float32) float32 { return x - y })
}

func Sum(x, y *TimeSeries) *TimeSeries {
	return Aggregate2(x, y, func(x, y float32) float32 { return x + y })
}