Oxford Oberon : running average
Due to a mild inconvenience I took up measuring my blood pressure again. I record all my readings in a text file called 'pols'. I plot the data with gnuplot and that works just fine. Below is an excerpt from file 'pols':
# x syst diast pulse # -- ----- ----- ----- 1 170 101 64 2 152 93 63 3 133 87 65 4 161 99 57 5 131 84 56 149 134 86 60 # 10:30 150 148 93 68 # 12:15 151 131 84 63 # 12:30 152 141 91 70 # 15:30 157 128 84 81 # 14:45 naar ETZ-e geweest op de fiets 158 129 83 68 # 18:15 Met de bus naar ETZ-e geweest 159 172 95 68 # 20:00 Stil gezeten laptop aan 174 141 91 66 # 11:30 175 120 76 86 # 17:30 3 uur door de stad geslenterd 176 156 97 60 # 01:30 24-02 177 123 80 72 # 12:00 182 140 75 86 # 15:15 183 120 78 74 # 16:00 184 127 88 103 # 17:45 16 km @ 21 kpu 185 143 93 84 # 18:30 186 # 187 # ... 200 #Each line obeys to the formula:
Plotting is done with gnuplot:
gnuplot -p polplot1where 'polplot1' is a file containing instructions for gnuplot to execute. To the right is a typical gnuplot output.
set grid plot \ "pols" using 1:2 with lines title 'systolic', \ "pols" using 1:3 with linespoints title 'diastolic', \ "pols" using 1:4 with lines title 'pulse'Now, that's all very nice but I want a trendline plotted in. I tried to have it done by gnuplot but I didn't succeed. So I decided to make a program that can do the following:
set grid plot \ "pols" using 1:2 with lines title 'Systolic', \ "pols.avg" using 1:2 with lines, \ "pols" using 1:3 with linespoints title 'Diastolic', \ "pols.avg" using 1:3 with lines, \ "pols.avg" using 1:4 with lines, \ "pols" using 1:4 with lines title 'pulse'The resulting plot is shown below. The trendline shows progress instead of raw data. Blood pressure readings vary quite a lot. I use an old Braun BP 6054 wrist style bloodpressure meter and a newer Omron RS7 IntelliSens meter. Both show comparable readings. Not identical but close enough. The Omron measures while inflating. That's kind of neat.
Movavg : calculate running averages
Below is the source of the obc executable movav1.mod:
MODULE movav1;
(* Moving averages
ver date does
--- ----- -----------------------------------------------------------------
0 22-02 Averages over range, no command line options
1 Add commandline options
22-02 Bigger data array, more symbols
*)
IMPORT Args, Conv, Err, Out, Files, Strings;
CONST LineFeed = 0AX;
Tab = 09X;
MaxRow = 1024;
MaxCol = 16;
DefaultFields = 4;
DefSummit = 10; (* average over this amount *)
TYPE String = ARRAY 32 OF CHAR;
DataStore = ARRAY MaxRow OF ARRAY MaxCol OF INTEGER;
VAR records, fields, last,
index, summit : INTEGER;
outfile : String;
inF : Files.File;
Done, Showit : BOOLEAN;
numbers, sigma : DataStore;
PROCEDURE FirstLine; (* First line is expected to have fields list like in *)
(* # ix upper lower pulse *)
VAR ch : CHAR;
BEGIN
Files.ReadChar (inF, ch);
IF ch # "#" THEN RETURN END;
Files.ReadChar (inF, ch);
LOOP
WHILE (ch = " ") OR (ch = Tab) DO Files.ReadChar (inF, ch) END; (* Skip over WS *)
IF ch = LineFeed THEN EXIT END;
WHILE ch > " " DO Files.ReadChar (inF, ch) END; (* n-th field found *)
INC (fields)
END;
Out.Int (fields, 2); Out.String (" fields found. Expecting field 1 as sequence number."); Out.Ln
END FirstLine;
PROCEDURE ReadNumber (VAR num : INTEGER); (* Read a number from file, skip over comments *)
VAR i : INTEGER;
ch : CHAR;
str : String;
BEGIN
i := 0;
str := "";
Files.ReadChar (inF, ch);
WHILE ch <= ' ' DO Files.ReadChar (inF, ch) END; (* Skip over WS *)
WHILE ch = '#' DO
REPEAT Files.ReadChar (inF, ch) UNTIL ch = LineFeed; (* Skip over comment *)
Files.ReadChar (inF, ch)
END;
WHILE ch <= ' ' DO Files.ReadChar (inF, ch) END; (* Skip over WS *)
IF Files.Eof (inF) THEN
Done := TRUE;
num := -1;
ELSE;
REPEAT
str [i] := ch;
INC (i);
Files.ReadChar (inF, ch)
UNTIL ch <= ' ';
num := Conv.IntVal (str)
END
END ReadNumber;
PROCEDURE ReadLine; (* Read one data line (probably ending in a comment) *)
VAR n, m : INTEGER;
BEGIN
n := 0;
WHILE n < fields DO
ReadNumber (m);
IF Files.Eof (inF) THEN RETURN END;
IF m = -1 THEN RETURN END;
numbers [records, n] := m; (* Store in array *)
INC (n)
END;
INC (records)
END ReadLine;
PROCEDURE Process; (* Average over 'summit' samples and write to disk *)
VAR ix, dx, n, col : INTEGER;
outF : Files.File;
BEGIN
outF := Files.Open (outfile, "w");
IF outF = NIL THEN
Err.String ("Could not open file "); Err.String (outfile); Err.String (" for writing. Aborting");
ShutDown;
HALT (3)
END;
FOR ix := summit - 1 TO records - 1 DO (* First 'summit' records get lost *)
sigma [ix, 0] := numbers [ix, 0]; (* Copy the index number *)
FOR col := 1 TO fields - 1 DO (* For each field, do the math *)
sigma [ix, col] := 0;
FOR dx := 0 TO summit - 1 DO (* Sum previous fields plus this one *)
sigma [ix, col] := sigma [ix, col] + numbers [ix - dx, col]
END;
sigma [ix, col] := (sigma [ix, col] + summit DIV 2) DIV summit (* Rounded avg *)
END
END;
FOR ix := summit - 1 TO records - 1 DO (* Write results to disk *)
FOR n := 0 TO fields - 1 DO
Files.WriteInt (outF, sigma [ix, n], 8)
END;
Files.WriteLn (outF)
END;
Files.Close (outF);
Out.String ("Averages saved to file "); Out.String (outfile); Out.Ln
END Process;
PROCEDURE Init;
VAR option : String;
args, n : INTEGER;
BEGIN
Done := FALSE; Showit := FALSE;
summit := DefSummit;
fields := 0; last := MaxRow;
records := 0; index := 0;
args := Args.argc;
IF args < 2 THEN
Err.String ("Usage : movavg datafile. Aborting."); Err.Ln;
HALT (1)
END;
Args.GetArg (1, option);
inF := Files.Open (option, "r");
IF inF = NIL THEN
Err.String ("Cannot open file "); Err.String (option); Err.String (". Aborting");
Err.Ln;
HALT (2)
END;
outfile := option;
Strings.Append (".avg", outfile);
n := 2; (* argument counter *)
WHILE n < args DO
Args.GetArg (n, option); INC (n);
IF option = "sum" THEN
Args.GetArg (n, option); INC (n);
summit := Conv.IntVal (option)
ELSIF option = "last" THEN
Args.GetArg (n, option); INC (n);
last := Conv.IntVal (option)
ELSIF option = "fields" THEN
Args.GetArg (n, option); INC (n);
fields := Conv.IntVal (option)
ELSIF option = "index" THEN
Args.GetArg (n, option); INC (n);
index := Conv.IntVal (option)
ELSIF option = "show" THEN
Showit := TRUE
END
END
END Init;
PROCEDURE ShowIt; (* Dump output array to screen *)
VAR m, n : INTEGER;
BEGIN
FOR n := summit TO records - 1 DO
FOR m := 0 TO fields - 1 DO
Out.Int (sigma [n, m], 5)
END;
Out.Ln
END;
Out.Ln
END ShowIt;
PROCEDURE ShutDown;
BEGIN
IF inF # NIL THEN Files.Close (inF) END;
Out.Ln; Out.String ("Records processed = "); Out.Int (records, 4); Out.Ln
END ShutDown;
BEGIN
Init;
IF fields = 0 THEN FirstLine END;
IF fields = 0 THEN
fields := 4;
Err.String ("Expected field definitions in line 1"); Err.Ln;
Err.String ("Using 4 fields by default"); Err.Ln
END;
REPEAT ReadLine UNTIL Done OR (records = last);
Process;
IF Showit THEN ShowIt END;
ShutDown;
END movav1.
No need to explain I guess. It's all quite straighforward. Just download
the source
from this site and compile it with
obc -o movavg movav2.modAn example commandline would be:
movavg pols last 185 sum 12for averaging 12 samples on the run and ending at record nr 185.