From 75c49fe88f19392ca181521b21fcc19d37eadc04 Mon Sep 17 00:00:00 2001 From: tim Date: Mon, 18 Dec 2017 21:46:26 +0200 Subject: [PATCH] Add a weekly average graph. --- glucometer_graphs.pl | 182 +++++++++++++++++++++++-------------------- 1 file changed, 96 insertions(+), 86 deletions(-) diff --git a/glucometer_graphs.pl b/glucometer_graphs.pl index 1734de3..6cc155e 100644 --- a/glucometer_graphs.pl +++ b/glucometer_graphs.pl @@ -134,6 +134,13 @@ foreach my $row ( @filelines ) { $seen_days{$date}++; } } +# Remove weeks for which there is less than a day of results in that week +# (In a full day, assuming a reading is taken every 15 minutes, there will be 96 readings) +foreach my $year ( sort keys %seen_weeks ) { + foreach my $week ( sort keys %{$seen_weeks{$year}} ) { + delete $seen_weeks{$year}{$week} if ( scalar $seen_weeks{$year}{$week} < 96 ); + } +} $total_day_graphs = scalar keys %seen_days; $total_week_graphs = scalar keys %seen_weeks; @@ -157,7 +164,7 @@ foreach my $d ( sort keys %seen_days ) { push @sortedlines, qq($row); } } -# @sortedlines = map { " $_" } @sortedlines; # indent data structure + @sortedlines = map { " $_" } @sortedlines; # indent data structure push @data, join "\n", sort @sortedlines; push @data, qq(EOD); } @@ -172,7 +179,7 @@ foreach my $row (@filelines) { push @sortedlines, qq($1$2); } } -#@sortedlines = map { " $_" } @sortedlines; # indent data structure +@sortedlines = map { " $_" } @sortedlines; # indent data structure push @data, join "\n", sort @sortedlines; push @data, qq(EOD); @@ -184,7 +191,7 @@ push @data, qq( foreach my $time ( sort keys %$intervals ) { push @sortedlines, qq("$time","$intervals->{$time}->{max}","$intervals->{$time}->{min}"); } -#@sortedlines = map { " $_" } @sortedlines; # indent data structure +@sortedlines = map { " $_" } @sortedlines; # indent data structure push @data, join "\n", sort @sortedlines; push @data, qq(EOD); @@ -193,14 +200,14 @@ push @data, qq(EOD); foreach my $year ( sort keys %seen_weeks ) { foreach my $week ( sort keys %{$seen_weeks{$year}} ) { my $time = Time::Piece->strptime( $year, "%Y" ); - my $mon = $time + ( ONE_WEEK * ( $week - 1 ) ); - my $sun = $time + ( ONE_WEEK * ( $week - 1 ) ) + ( ONE_DAY * 6 ); + my $mon = $time + ( ONE_WEEK * ( $week - 1 ) ) + ( ONE_DAY ); + my $sun = $time + ( ONE_WEEK * ( $week - 1 ) ) + ( ONE_DAY * 7 ); my $label = $mon->strftime("%Y%m%d"); # Select data from the week in question my @weeklines; foreach my $row (@filelines) { - foreach my $dow ( 0 .. 7 ) { + foreach my $dow ( 1 .. 7 ) { my $day = $mon + ( ONE_DAY * $dow ); my $d = $day->strftime("%Y-%m-%d"); if ( $row =~ m#^"$d .*$#ms ) { @@ -209,23 +216,9 @@ foreach my $year ( sort keys %seen_weeks ) { } } - push @data, qq( -\$DataWeek$label << EOD -"timestamp","blood glucose","meal","method","comment"); - @sortedlines = (); - foreach my $row (@weeklines) { - if ($row =~ m#^(")\d{4}-\d{2}-\d{2} (.*)$# ) { - push @sortedlines, qq($1$2); - } - } -# @sortedlines = map { " $_" } @sortedlines; # indent data structure - push @data, join "\n", sort @sortedlines; - push @data, qq(EOD); - - push @data, qq( \$DataWeekAvg$label << EOD -"timestamp","blood glucose","meal","method","comment"); + "timestamp","blood glucose","meal","method","comment"); @sortedlines = (); foreach my $row (@weeklines) { if ($row =~ m#^(")\d{4}-\d{2}-\d{2} (.*)$# ) { @@ -233,19 +226,19 @@ foreach my $year ( sort keys %seen_weeks ) { } } my $week_intervals = calculate_max_min( 'lines' => \@sortedlines, 'format' => '"%H:%M:%S"' ); -# @sortedlines = map { " $_" } @sortedlines; # indent data structure + @sortedlines = map { " $_" } @sortedlines; # indent data structure push @data, join "\n", sort @sortedlines; push @data, qq(EOD); push @data, qq( -\$DataMaxMin$label << EOD -"timestamp","max","min"); +\$DataWeekMaxMin$label << EOD + "timestamp","max","min"); @sortedlines = (); foreach my $time ( sort keys %{$week_intervals} ) { push @sortedlines, qq("$time","$intervals->{$time}->{max}","$intervals->{$time}->{min}"); } -# @sortedlines = map { " $_" } @sortedlines; # indent data structure + @sortedlines = map { " $_" } @sortedlines; # indent data structure push @data, sort @sortedlines; push @data, qq(EOD); } @@ -296,7 +289,8 @@ set samples 10000 set xdata stats \$DataAvg using 2 -MeanTotal = STATS_mean +MedianTotal = STATS_median +MeanTotal = STATS_mean set xdata time @@ -305,7 +299,18 @@ plot \$DataAvg using 1:2 smooth mcsplines unset table set table \$SmoothDataAvg +# Use bezier smoothing plot \$DataAvg using 1:2 smooth bezier +# +## Alternate: Try a five-point average using data_feedback.dem +## This is more responsive to outlier points than the bezier, so bezier serves our purposes better +#samples(x) = \$1 > 4 ? 5 : (\$1+1) +#avg5(x) = (shift5(x), (back1+back2+back3+back4+back5)/samples(\$1)) +#shift5(x) = (back5 = back4, back4 = back3, back3 = back2, back2 = back1, back1 = x) +## Initialize a running sum +#init(x) = (back1 = back2 = back3 = back4 = back5 = sum = 0) +#plot sum = init(0), \$DataAvg using 1:(avg5(\$2)) every 2 smooth mcsplines, \$DataAvg using 1:2 smooth bezier +# unset table @@ -316,7 +321,43 @@ unset table ); +# Sample the average $interval values by week into a smoothed plot, and store each in a new table +foreach my $year ( sort keys %seen_weeks ) { + foreach my $week ( sort keys %{$seen_weeks{$year}} ) { + my $time = Time::Piece->strptime( "$year", "%Y" ); + my $mon = $time + ( ONE_WEEK * ( $week - 1 ) ) + ( ONE_DAY ); + my $label = $mon->strftime("%Y%m%d"); + push @data, qq( +set datafile separator "," +set timefmt "%H:%M:%S" +set format x "%s" timedate +set format y "%.2f" numeric +set samples 10000 +set xdata +print "DataWeekAvg$label" +stats \$DataWeekAvg$label using 2 +MedianTotal$label = STATS_mean +MeanTotal$label = STATS_mean + +set xdata time + +set table \$DataWeekAvgTable$label +plot \$DataWeekAvg$label using 1:2 smooth mcsplines +unset table + +set table \$SmoothDataWeekAvg$label +plot \$DataWeekAvg$label using 1:2 smooth bezier +unset table + +# Convert DataWeekMaxMin from CSV to table +set table \$DataWeekMaxMinTable$label +plot \$DataWeekMaxMin$label using 1:2:3 with table +unset table +); + + } +} @@ -338,7 +379,6 @@ unset table # and the new $SmoothData, which contains a table, needs a whitespace separator push @data, qq( # ensure separator handles tables -#reset set datafile separator whitespace set key off @@ -384,7 +424,7 @@ set grid ytics ls 100 front set object 1 rect from graph 0, first $min_glucose to graph 1,first $max_glucose fc ls 6 fs solid 0.2 back AVG = Mean$label -AVG_LABEL = gprintf("Average glucose: %.2f", AVG) +AVG_LABEL = gprintf("Median glucose: %.2f", AVG) set object 2 rect at graph 0.9, graph 0.9 fc ls 2 fs transparent solid 0.5 front size char strlen(AVG_LABEL), char 3 set label 2 AVG_LABEL at graph 0.9, graph 0.9 front center @@ -451,25 +491,25 @@ set grid ytics ls 100 front set object 1 rect from graph 0, first $min_glucose to graph 1,first $max_glucose fc ls 6 fs solid 0.05 back -AVG = MeanTotal -AVG_LABEL = gprintf("Average glucose: %.2f", AVG) +AVG = MedianTotal +AVG_LABEL = gprintf("Median glucose: %.2f", AVG) set object 2 rect at graph 0.9, graph 0.9 fc ls 2 fs transparent solid 0.5 front size char strlen(AVG_LABEL), char 3 set label 2 AVG_LABEL at graph 0.9, graph 0.9 front center A1C = 0 if (A1C == 0 && '$units' eq 'mg/dL') { - A1C = (MeanTotal + 46.7) / 28.7 + A1C = (MedianTotal + 46.7) / 28.7 } if (A1C == 0 && '$units' eq 'mmol/L') { - A1C = (MeanTotal + 2.59) / 1.59 + A1C = (MedianTotal + 2.59) / 1.59 } # mg/dL numbers tend to be higher than 35 -if (A1C == 0 && MeanTotal >= 35) { - A1C = (MeanTotal + 46.7) / 28.7 +if (A1C == 0 && MedianTotal >= 35) { + A1C = (MedianTotal + 46.7) / 28.7 } # mmol/L numbers tend to be lower than 35 -if (A1C == 0 && MeanTotal < 35) { - A1C = (MeanTotal + 2.59) / 1.59 +if (A1C == 0 && MedianTotal < 35) { + A1C = (MedianTotal + 2.59) / 1.59 } A1C_LABEL = gprintf("Average A1c: %.1f", A1C) @@ -494,45 +534,8 @@ plot 1/0 - - -=cut - -foreach my $year ( sort keys %seen_weeks ) { - foreach my $week ( sort keys %{$seen_weeks{$year}} ) { - my $time = Time::Piece->strptime( "$year", "%Y" ); - my $mon = $time + ( ONE_WEEK * ( $week - 1 ) ); - my $sun = $time + ( ONE_WEEK * ( $week - 1 ) ) + ( ONE_DAY * 6 ); - my $label = $mon->strftime("%Y-%m-%d"); - my $title = $mon->strftime("%Y-%m-%d") . " to " . $sun->strftime("%Y-%m-%d"); - push @data, qq( -#reset -set datafile separator "," - -set timefmt "%H:%M:%S" -set format x "%s" timedate -set format y "%.2f" numeric -set samples 10000 -set xdata -stats \$DataAvg using 2 -MeanTotal = STATS_mean - -set xdata time - -set table \$DataAvgTable -plot \$DataAvg using 1:2 smooth mcsplines -unset table - -set table \$SmoothDataAvg -plot \$DataAvg using 1:2 smooth bezier -unset table - -# Convert DataMaxMin from CSV to table -set table \$DataMaxMinTable -plot \$DataMaxMin using 1:2:3 with table -unset table - -reset +# Plot and display a graph with the average glucose values for every $interval for recorded days in a given week +push @data, qq( set datafile separator whitespace set key off @@ -557,7 +560,15 @@ set tmargin 5 set bmargin 5 set multiplot title layout $graphs_per_page,1 - +); +foreach my $year ( sort keys %seen_weeks ) { + foreach my $week ( sort keys %{$seen_weeks{$year}} ) { + my $time = Time::Piece->strptime( "$year", "%Y" ); + my $mon = $time + ( ONE_WEEK * ( $week - 1 ) ) + ( ONE_DAY ); + my $sun = $time + ( ONE_WEEK * ( $week - 1 ) ) + ( ONE_DAY * 7 ); + my $title = $mon->strftime("%A, %d %B %Y") . " to " . $sun->strftime("%A, %d %B %Y"); + my $label = $mon->strftime("%Y%m%d"); + push @data, qq( set title "Average Daily Glucose from $title" font "Calibri,18" set xlabel "Time" offset 0,-0.25 set ylabel "Blood glucose" @@ -567,32 +578,32 @@ set grid ytics ls 100 front set object 1 rect from graph 0, first $min_glucose to graph 1,first $max_glucose fc ls 6 fs solid 0.05 back -AVG = MeanTotal -AVG_LABEL = gprintf("Average glucose: %.2f", AVG) +AVG = MedianTotal$label +AVG_LABEL = gprintf("Median glucose: %.2f", AVG) set object 2 rect at graph 0.9, graph 0.9 fc ls 2 fs transparent solid 0.5 front size char strlen(AVG_LABEL), char 3 set label 2 AVG_LABEL at graph 0.9, graph 0.9 front center A1C = 0 if (A1C == 0 && '$units' eq 'mg/dL') { - A1C = (MeanTotal + 46.7) / 28.7 + A1C = (MedianTotal$label + 46.7) / 28.7 } if (A1C == 0 && '$units' eq 'mmol/L') { - A1C = (MeanTotal + 2.59) / 1.59 + A1C = (MedianTotal$label + 2.59) / 1.59 } # mg/dL numbers tend to be higher than 35 -if (A1C == 0 && MeanTotal >= 35) { - A1C = (MeanTotal + 46.7) / 28.7 +if (A1C == 0 && MedianTotal$label >= 35) { + A1C = (MedianTotal$label + 46.7) / 28.7 } # mmol/L numbers tend to be lower than 35 -if (A1C == 0 && MeanTotal < 35) { - A1C = (MeanTotal + 2.59) / 1.59 +if (A1C == 0 && MedianTotal$label < 35) { + A1C = (MedianTotal$label + 2.59) / 1.59 } A1C_LABEL = gprintf("Average A1c: %.1f", A1C) set object 3 rect at graph 0.07, graph 0.9 fc ls 4 fs transparent solid 0.5 front size char strlen(A1C_LABEL), char 3 set label 3 A1C_LABEL at graph 0.07, graph 0.9 front center -plot \$DataMaxMinTable using (strftime("%H:%M:%S", \$1)):2:3 with filledcurves lc 111, \$SmoothDataAvg using (strftime("%H:%M:%S", \$1)):2:( \$2 > $max_glucose || \$2 < $min_glucose ? 110 : 1 ) with lines lw 3 lc variable +plot \$DataWeekMaxMinTable$label using (strftime("%H:%M:%S", \$1)):2:3 with filledcurves lc 111, \$SmoothDataWeekAvg$label using (strftime("%H:%M:%S", \$1)):2:( \$2 > $max_glucose || \$2 < $min_glucose ? 110 : 1 ) with lines lw 3 lc variable # Add an x grid set multiplot previous @@ -614,7 +625,6 @@ plot 1/0 } } } -=cut push @data, qq( unset multiplot