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: Comments run from the '#' pound sign upto end of line. The first line of the file should contain the fields in each record. Records can end at the pulse rate but they can also contain a comment.

Plotting is done with gnuplot:

gnuplot -p polplot1
where '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: The processed data is written to a new file (old filename with '.avg' appended to it) and then gnuplot is instructed to plot data from both files in one plot. I created a new plotting file, polplot:
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.mod
An example commandline would be:
movavg pols last 185 sum 12
for averaging 12 samples on the run and ending at record nr 185.

Page created 25 Feb 2019,