Datafilter vectors - smooth()

.. part of a few updates to add some smoothing algorithms to apply
   to vector data, this first one is just a simple moving average.

.. smooth(list, sma, centered|forward|backward, window) will apply
   smoothing to the list. Note that centered doesn't use multiple
   windows when the window size is even, so recommend always using
   an odd number for this parameter.

.. will add ewma and maybe some others over the next few commits.
This commit is contained in:
Mark Liversedge
2020-03-15 20:22:42 +00:00
parent 3f9774b2c2
commit 0f5f44053f
3 changed files with 134 additions and 0 deletions

View File

@@ -166,8 +166,11 @@ static struct {
{ "sapply", 2 }, // sapply(vector, expr) - returns a vector where expr has been applied to every element. x and i
// are both available in the expr for element value and index position.
{ "lr", 2 }, // lr(xlist, ylist) - linear regression on x,y co-ords returns vector [slope, intercept, r2, see]
{ "smooth", 0 }, // smooth(list, algorithm, ... parameters) - returns smoothed data.
// add new ones above this line
{ "", -1 }
};
@@ -1220,6 +1223,7 @@ void Leaf::validateFilter(Context *context, DataFilterRuntime *df, Leaf *leaf)
QRegExp constValidSymbols("^(e|pi)$", Qt::CaseInsensitive); // just do basics for now
QRegExp dateRangeValidSymbols("^(start|stop)$", Qt::CaseInsensitive); // date range
QRegExp pmcValidSymbols("^(stress|lts|sts|sb|rr|date)$", Qt::CaseInsensitive);
QRegExp smoothAlgos("^(sma|ewma)$", Qt::CaseInsensitive);
if (leaf->series) { // old way of hand crafting each function in the lexer including support for literal parameter e.g. (power, 1)
@@ -1468,6 +1472,49 @@ void Leaf::validateFilter(Context *context, DataFilterRuntime *df, Leaf *leaf)
}
}
} else if (leaf->function == "smooth") {
if (leaf->fparms.count() < 2 || leaf->fparms[1]->type != Leaf::Symbol) {
leaf->inerror = true;
DataFiltererrors << QString(tr("smooth(list, algorithm [,parameters]) need at least 2 parameters."));
} else {
QString algo = *(leaf->fparms[1]->lvalue.n);
if (!smoothAlgos.exactMatch(algo)) {
leaf->inerror = true;
DataFiltererrors << QString(tr("smoothing algorithm '%1' not available").arg(algo));
} else {
if (algo == "sma") {
// smooth(list, sma, centred|forward|backward, window)
if (leaf->fparms.count() != 4 || leaf->fparms[2]->type != Leaf::Symbol) {
leaf->inerror = true;
DataFiltererrors << QString(tr("smooth(list, sma, forward|centered|backward, windowsize"));
} else {
QRegExp parms("^(forward|centered|backward)$");
QString parm1 = *(leaf->fparms[2]->lvalue.n);
if (!parms.exactMatch(parm1)) {
leaf->inerror = true;
DataFiltererrors << QString(tr("smooth(list, sma, forward|centered|backward, windowsize"));
}
// check list and windowsize
validateFilter(context, df, leaf->fparms[0]);
validateFilter(context, df, leaf->fparms[3]);
}
} else if (algo == "ewma") {
// smooth(list, ewma, alpha)
// TODO
}
}
}
} else if (leaf->function == "lr") {
if (leaf->fparms.count() != 2) {
@@ -2654,6 +2701,33 @@ Result Leaf::eval(DataFilterRuntime *df, Leaf *leaf, float x, long it, RideItem
return returning;
}
// smooth
if (leaf->function == "smooth") {
Result returning(0);
// moving average
if (*(leaf->fparms[1]->lvalue.n) == "sma") {
QString type = *(leaf->fparms[2]->lvalue.n);
int window = eval(df,leaf->fparms[3],x, it, m, p, c, s, d).number;
Result data = eval(df,leaf->fparms[0],x, it, m, p, c, s, d);
int pos=2; // fallback
if (type=="backward") pos=0;
if (type=="forward") pos=1;
if (type=="centered") pos=2;
// smooth in different ways....
returning.vector = Utils::smooth_sma(data.vector, pos, window);
// sum. ugh.
for(int i=0; i<returning.vector.count(); i++) returning.number += returning.vector[i];
}
return returning;
}
// linear regression
if (leaf->function == "lr") {
Result returning(0);

View File

@@ -297,5 +297,60 @@ argsort(QVector<double> &v, bool ascending)
return returning;
}
// simple moving average
static double mean(QVector<double>&data, int start, int end)
{
double sum=0;
double count=0;
// add em up and handle out of bounds
for (int i=start; i<end; i++) {
if (i < 0) sum += data[0];
else if (i>=data.count()) sum += data[data.count()-1];
else sum += data[i];
count ++;
}
return sum/count;
}
QVector<double>
smooth_sma(QVector<double>&data, int pos, int window)
{
QVector<double> returning;
int window_start=0, window_end=0;
int index=0;
double ma=0;
// window is offset from index depending upon the forward/backward/centred position
switch (pos) {
case GC_SMOOTH_FORWARD:
window_start=0;
window_end=window;
break;
case GC_SMOOTH_BACKWARD:
window_end=0;
window_start=window *-1;
break;
case GC_SMOOTH_CENTERED: // we should handle odd/even size better
window_start = (window*-1)/2;
window_end = (window)/2;
}
while (index < data.count()) {
returning << mean(data, window_start, window_end);
index ++;
window_start++;
window_end++;
}
return returning;
}
};

View File

@@ -30,6 +30,10 @@
class QString;
class QStringList;
#define GC_SMOOTH_FORWARD 0
#define GC_SMOOTH_BACKWARD 1
#define GC_SMOOTH_CENTERED 2
namespace Utils
{
QString xmlprotect(const QString &string);
@@ -39,6 +43,7 @@ namespace Utils
QStringList searchPath(QString path, QString binary, bool isexec=true);
QString removeDP(QString);
QVector<int> argsort(QVector<double>&, bool ascending=false);
QVector<double> smooth_sma(QVector<double>&, int pos, int window);
};