Addition to `smooth` node

Could the smooth node also have average.

mean isn't quite what I am wanting.

Mathematically speaking, mean and average are the same. Just different terms used in different contexts.

Don't confuse mean with median though, those are actually calculated differently.

What do you want to calculate?

1 Like

I'm getting values coming in and want the average.
Looking at the smooth and only seeing mean, I got confused.

Ok, mean == average (ok: probably the wrong symbol)

Since I'm here and we have cleared that.

Yeah, I know I've asked: but......

I want a MAX, MIN and AVG value of an input stream which also allows resetting.

smooth does all 3 but that means 3 iterations of it in the 3 modes.

And not to be difficult. I'm just asking - again.

What would be wrong with this in a function node:

var MAX = context.get("MAX") || 0;
var COUNTER = context.get("COUNTER") || 0;
var MIN = context.get("MIN") || null;
var AVG = context.get("AVG") || null;
var TOTAL = context.get("TOTAL") || null;
var NEW = msg.payload;

msg1 = {};
msg2 = {};

if (msg.reset == true)
{
    //  Reset all things
    node.warn("RESET");
    context.set("MAX",0);
    context.set("COUNTER",0);
    context.set("MIN",null);
    context.set("AVG",null);
    context.set("TOTAL",null);
    return;
}

if (COUNTER == 0)
{
    //  First pass
    MIN = msg.payload;
}

NEW = msg.payload;

if (NEW > MAX)
{
    MAX = NEW;
    context.set("MAX",NEW);
}

if (NEW < MIN)
{
	MIN = NEW;
	context.set("MIN",NEW);
}

COUNTER = COUNTER + 1;
context.set("COUNTER",COUNTER);
TOTAL = TOTAL + NEW;
context.set("TOTAL",TOTAL);
AVG = parseInt((TOTAL / COUNTER));

msg.payload = "My calculated average is " + AVG;
msg1.payload = MAX;
msg2.payload = MIN;
return [msg,msg1,msg2];

It is difficult to see the amount of memory needed for this vs what is needed for 3 smooth nodes.

In terms of concept then nothing wrong at all, that is how I would do it as I am happy with writing javascript. Others without the experience may well prefer to use core nodes as you can then be 99% certain that there is not a bug in the code.

In terms of your specific code though.

  1. I would not use capital letters for variable names as that is generally used for constants.
  2. Use let rather than var, it is good practice to use the new features of the language.
  3. Don't initialise max to zero, otherwise it won't work if all the values are negative. Initialise them both to msg.payload when counter is 0 (that should be if (counter === 0 ).
  4. The line
    AVG = parseInt((TOTAL / COUNTER))
    can be
    avg = total/counter
    they are both numbers so there is no string to parse. If you want to convert it to an integer then use round()
1 Like

Thanks. Capital letters are an old bad (?) habit. I use them only to highlight them as being variable names but only in this case.

Though reading a bit more of your reply..... constants.
I am still not fully understanding them and how to use them.

I tried once - wrongly - to use one and it was painful. As it is/was a .... constant. (Go figure, huh) :wink:

Yeah, let. Again it is an old habit and I joined when it was var.
I will have to go through all my codes and change the var to let.
3: Yeah. Ok. But I am measuring room temperatures. But understood.
4: Thanks. I only did it that way because I didn't know of round().
(edit)
all I can find is Math.round()..... Shrug :confused:

Again: (just to declare) I am doing this to learn and improve my understanding of how to write code and get rid of these/those old bad habits.

Thanks @Colin

What does Math.round() do?

I just tried it and ... seemingly: nothing.

Which is weird as I found it on this link:
Site

example form page:

var a = Math.round(2.60);
var b = Math.round(2.50);
var c = Math.round(2.49);
var d = Math.round(-2.60);
var e = Math.round(-2.50);
var f = Math.round(-2.49);

So just round()?

This isn't working either though.

[{"id":"c901c5b6032b2c98","type":"inject","z":"7e987ddf260bdf0d","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"32.1572","payloadType":"num","x":220,"y":970,"wires":[["784b89bddbd42fb0","88c3f21ec03cd3e3"]]},{"id":"784b89bddbd42fb0","type":"function","z":"7e987ddf260bdf0d","name":"","func":"let x = round(msg.payload);\nx = msg.payload;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":380,"y":970,"wires":[["d442289f90810600"]]},{"id":"88c3f21ec03cd3e3","type":"debug","z":"7e987ddf260bdf0d","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":390,"y":1010,"wires":[]},{"id":"d442289f90810600","type":"debug","z":"7e987ddf260bdf0d","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":570,"y":970,"wires":[]}]

Be it Math.round() or just round()

round() gives me:
ReferenceError: round is not defined (line 1, col 9)

In the flow you have

let x = round(msg.payload);
x = msg.payload;
return msg;

You have the second line the wrong way round, and it is Math.round(). I did not say that initially as I thought it was obvious, as Math.round() says it does exactly what you want.

ARGH! Yeah, I just noticed that myself.

Sorry.

Yeah, but still that error I posted if I use only round()

I have to use Math.round() to get it to make it an integer.

I didn't know I had to qualify round() with Math.
Working now.
Thanks.

That is telling javascript that it is in the Math library, the same as Math.max and Math.min that were referenced earlier.

I would have used Math.max() and min() and just written out the values to context without testing for a change, but that is a matter of choice.

I think this is how I would have coded it, if you are interested

let data = context.get("data") || { count: 0, total: 0, min: Number.MAX_VALUE, max: -Number.MAX_VALUE}
let msgs = null

if (msg.reset) {
    data = { count: 0, total: 0, min: Number.MAX_VALUE, max: -Number.MAX_VALUE }
} else {
    data.count++
    data.min = Math.min( data.min, msg.payload)
    data.max = Math.max( data.max, msg.payload)
    data.total += msg.payload
    msgs = [
        {payload: data.total / data.count},
        {payload: data.max},
        {payload: data.min}
    ]
}
context.set("data", data)
return msgs;
1 Like

Colin, depending on the payload and frequency, total could potentially reach Number.MAX_SAFE_INTEGER (9007199254740991)

A simple moving average or exponential moving average might be better. As for the window size, that would depend on how reactive you want it to be.

Yeah, ok.

Again, it is knowing such commands exist.

I'll look at it a bit more and try to learn a bit more from it.

May even use it as it is tidier than my effort and promotes better structure.

Though taking on what Steve said, I may do the average by totalising the vale each time how I did in my code originally.

I was just posting a solution to @Trying_to_learn's requirement.

In principle you are correct, the total may get too large to keep the precision required. However, for example, if the values being accumulated had average value 100, and the node were passed values 1000 times every second, then it would be 2854 years before it overflowed. I doubt whether the OP intends to use it in a way such that it leaves it for 3000 years before sending a Reset message into the node.

On the other hand you are correct in that a windowed average, with reset, like is rarely the best solution. The (mathematically) ideal solution is a true low pass filter. The Smooth node makes an attempt at this but it relies on regular values coming in and it is not clear exactly what time constant the node simulates under any particular situation. Where I need smoothing I use this reasonably accurate simulation of a low pass filter.

// Applies a simple RC low pass filter to incoming payload values
var tc = 20*60*1000;       // time constant in milliseconds

var lastValue = context.get('lastValue');
if (typeof lastValue == "undefined") lastValue = msg.payload;
var lastTime = context.get('lastTime') || null;
var now = new Date();
var currentValue = Number(msg.payload);
// ignore if not a number
if (!isNaN(currentValue) && isFinite(currentValue)) {
    var newValue;
    if (lastTime === null) {
        // first time through
        newValue = currentValue;
    } else {
        var dt = now - lastTime;
        if (dt > 0) {
            var dtotc = dt / tc;
            newValue = lastValue * (1 - dtotc) + currentValue * dtotc;
        } else {
            // no time has elapsed leave output the same as last time
            newValue = lastValue;
        }
    }
    context.set('lastValue', newValue);
    context.set('lastTime', now);
    msg.payload = newValue;
} else {
    // not a number so ignore it
    msg = null;
}
return msg;

Yes, I know I shouldn't use var, I wrote this a long time ago.

2 Likes

I know I'm being pedantic but average and mean are NOT the same. The mean is AN average. The mean, median and mode (and several others) are all 'averages' as an average is a 'measure of location' used to compare 2 data sets. The term 'arithmetic mean' is the correct one for the sum of the values divided by the number of values. The old 2.4 children example explains why the mean is not the best 'average' to describe family size as the better one (for house builders) would be the mode.

Yes, strictly you are correct. However, in common usage, when the term 'the average' is used this is taken to mean the arithmetic mean. In this particular case @Trying_to_learn used the word average where he meant arithmetic mean, he just did not know that is what he meant.

Yes, but he was asking for ‘average’ to be added to the Smooth node (in addition to ‘mean’). In the Smooth node, technically, both Max and Min may be also be classed as ‘averages’ as they can be used to compare 2 datasets – and, in fact, probably are being used so. Common usage (when incorrect) causes confusion and spreads it further. In programming correct usage is important – as the comment about using capitals to signify constants illustrated. Common usage has changed the meaning of the word ‘sophisticated’ to an almost exact opposite meaning (intelligent) to its original meaning (false) because of common usage. That’s OK provided that everyone knows what is meant but not everybody does.