Fix bugs that occur when switching to mg/dL units.

This commit is contained in:
Timothy Allen 2018-01-03 02:45:45 +02:00
parent 9ed89e7f8f
commit 7087b5299a

View File

@ -9,7 +9,7 @@ __license__ = 'MIT'
# TODO: comments -- unicode # TODO: comments -- unicode
# TODO: prettify # TODO: prettify
# TODO: weekly graph with each day's figures as a different-coloured line # TODO: weekly graph with each day's figures as a different-coloured line
# TODO: verify either set of units (mmol,mg/dl) works with the data # TODO: verify either set of units (mmol/L,mg/dl) works with the data
import argparse import argparse
import csv import csv
@ -40,16 +40,19 @@ VALID_UNITS = [UNIT_MGDL, UNIT_MMOLL]
INTERVAL = 15 INTERVAL = 15
# Maximum gluclose value to display (TODO: mmol/mg) # Maximum gluclose value to display (TODO: mmol/mg)
GRAPH_MAX = 21 GRAPH_MAX = 21
GRAPH_MIN = 1
DEFAULT_HIGH = 8
DEFAULT_LOW = 4
# Set colour for below-target maxmins # Colour for below-target maxmins
RED = '#d71920' RED = '#d71920'
# Set colour for above-target maxmins # Colour for above-target maxmins
YELLOW = '#f1b80e' YELLOW = '#f1b80e'
# Set colour for graph lines # Colour for graph lines
BLUE = '#02538f' BLUE = '#02538f'
# Set colour for median glucose box # Colour for median glucose box
GREEN = '#009e73' GREEN = '#009e73'
# Set colour for median A1c box # Colour for median A1c box
BOXYELLOW = '#e69f00' BOXYELLOW = '#e69f00'
def main(): def main():
@ -68,6 +71,18 @@ def main():
for row in rows: for row in rows:
row = parse_entry(row, args.icons) row = parse_entry(row, args.icons)
# If we're on the default values for units, highs and lows, check that the average
# value is under 35 (assuming that average mmol/L < 35 and average mg/dL > 35)
if args.units == UNIT_MMOLL and (args.high == DEFAULT_HIGH or args.low == DEFAULT_LOW):
mean = round(np.mean([l['value'] for l in rows]), 1)
if mean > 35:
args.units = UNIT_MGDL
args.high = convert_glucose_unit(args.high, UNIT_MMOLL)
args.low = convert_glucose_unit(args.low, UNIT_MMOLL)
args.graph_max = convert_glucose_unit(args.graph_max, UNIT_MMOLL)
args.graph_min = convert_glucose_unit(args.graph_min, UNIT_MMOLL)
''' Fill in gaps that might exist in the data, in order to smooth the curves and fills ''' ''' Fill in gaps that might exist in the data, in order to smooth the curves and fills '''
''' We're using 8 minute gaps in order to have more accurate fills ''' ''' We're using 8 minute gaps in order to have more accurate fills '''
rows = fill_gaps(rows, interval=dt.timedelta(minutes=10)) rows = fill_gaps(rows, interval=dt.timedelta(minutes=10))
@ -140,7 +155,7 @@ def main():
generate_plot(data, generate_plot(data,
ax=ax, ax=ax,
transforms={'bezier':True, 'avga1c':a_median, \ transforms={'bezier':True, 'avga1c':a_median, \
'color':[RED, BLUE, RED], 'boundaries':[0, args.low, args.high, GRAPH_MAX]}, 'color':[RED, BLUE, RED], 'boundaries':[args.graph_min, args.low, args.high, args.graph_max]},
args=args, args=args,
color=BLUE, color=BLUE,
) )
@ -211,7 +226,7 @@ def main():
generate_plot(data, generate_plot(data,
ax=ax, ax=ax,
transforms={'bezier':True, \ transforms={'bezier':True, \
'color':[RED, BLUE, RED], 'boundaries':[0, args.low, args.high, GRAPH_MAX]}, 'color':[RED, BLUE, RED], 'boundaries':[args.graph_min, args.low, args.high, args.graph_max]},
args=args, args=args,
color=BLUE, color=BLUE,
) )
@ -308,13 +323,17 @@ def generate_plot(data, ax=None, transforms={}, args=[], **plot_args):
x_min = mdates.date2num(firstminute) x_min = mdates.date2num(firstminute)
x_max = mdates.date2num(lastminute) x_max = mdates.date2num(lastminute)
ax.set_xlim(x_min, x_max) ax.set_xlim(x_min, x_max)
ax.set_ylim(0, GRAPH_MAX) ax.set_ylim(args.graph_min, args.graph_max)
''' Calculate the time intervals in 2 hour segments ''' ''' Calculate the time intervals in 2 hour segments '''
xtimes = [] xtimes = []
time = firstminute time = firstminute
while time < lastminute: while time < lastminute:
xtimes.append(time) xtimes.append(time)
time += dt.timedelta(hours=2) time += dt.timedelta(hours=2)
if args.units == UNIT_MMOLL:
y_tick_freq = 2
else:
y_tick_freq = convert_glucose_unit(2, UNIT_MMOLL)
''' Formatting for axis labels, using date calculations from above ''' ''' Formatting for axis labels, using date calculations from above '''
ax.set_xlabel('Time', fontsize=9) ax.set_xlabel('Time', fontsize=9)
@ -327,9 +346,9 @@ def generate_plot(data, ax=None, transforms={}, args=[], **plot_args):
tick.label1.set_horizontalalignment('left') tick.label1.set_horizontalalignment('left')
ax.set_ylabel('Blood Glucose (' + args.units + ')', fontsize=9) ax.set_ylabel('Blood Glucose (' + args.units + ')', fontsize=9)
ax.set_ybound(0, GRAPH_MAX) ax.set_ybound(args.graph_min, args.graph_max)
ax.grid(axis='y', color = '#d0d0d0', linestyle = (1,(0.5,2)), zorder=1) ax.grid(axis='y', color = '#d0d0d0', linestyle = (1,(0.5,2)), zorder=1)
ax.set_yticks([a for a in range(0, GRAPH_MAX, 2)]) ax.set_yticks([a for a in range(int(args.graph_min), int(args.graph_max), int(y_tick_freq))])
ax.yaxis.set_major_formatter(mticker.FormatStrFormatter("%d")) ax.yaxis.set_major_formatter(mticker.FormatStrFormatter("%d"))
ax.yaxis.set_ticks_position('none') ax.yaxis.set_ticks_position('none')
@ -350,15 +369,13 @@ def generate_plot(data, ax=None, transforms={}, args=[], **plot_args):
elif transform == 'spline' and transforms[transform] is True: elif transform == 'spline' and transforms[transform] is True:
''' Use SciPy's UnivariateSpline for transforming (s is transforming factor) ''' ''' Use SciPy's UnivariateSpline for transforming (s is transforming factor) '''
if args.units == UNIT_MMOLL:
s = 8
else:
s = convert_glucose_unit(12, UNIT_MMOLL)
if not maxmin: if not maxmin:
curve = interpolate.UnivariateSpline(x=x, y=y, k=3, s=8) curve = interpolate.UnivariateSpline(x=x, y=y, k=3, s=s)
y = curve(x) y = curve(x)
#else:
# TODO Apply spline to each curve?
#curve1 = interpolate.UnivariateSpline(x=x, y=y, k=3, s=5)
#curve2 = interpolate.UnivariateSpline(x=x, y=z, k=3, s=5)
#y = curve1(x)
#z = curve2(x)
elif transform == 'bezier' and transforms[transform] is True: elif transform == 'bezier' and transforms[transform] is True:
''' Create bezier function for transforming (s is transforming factor) ''' ''' Create bezier function for transforming (s is transforming factor) '''
@ -374,8 +391,6 @@ def generate_plot(data, ax=None, transforms={}, args=[], **plot_args):
For large arrays, get a smaller slice of the full array. For large arrays, get a smaller slice of the full array.
Do this by removing every nth element from the array ''' Do this by removing every nth element from the array '''
n = 5 n = 5
while len(p) > 1000:
p = np.delete(p, np.arange(0, len(p), n), axis=0)
while len(x) > 1000: while len(x) > 1000:
x = np.delete(x, np.arange(0, len(x), n), axis=0) x = np.delete(x, np.arange(0, len(x), n), axis=0)
y = np.delete(y, np.arange(0, len(y), n), axis=0) y = np.delete(y, np.arange(0, len(y), n), axis=0)
@ -383,18 +398,15 @@ def generate_plot(data, ax=None, transforms={}, args=[], **plot_args):
if not maxmin: if not maxmin:
curve = np.array([c for _, c in bezier(np.array([x,y]).T, 250)]) curve = np.array([c for _, c in bezier(np.array([x,y]).T, 250)])
(x, y) = (curve[:,0], curve[:,1]) (x, y) = (curve[:,0], curve[:,1])
#else:
# TODO Apply bezier to each curve? Will this work for x?
#while len(q) > 1000:
# q = np.delete(q, np.arange(0, len(q), n), axis=0)
#curve1 = np.array([c for _, c in bezier(p[:], 250)])
#curve2 = np.array([c for _, c in bezier(q[:], 250)])
#(x, y) = (curve1[:,0], curve1[:,1])
#(x, z) = (curve2[:,0], curve2[:,1])
''' Add the mean or median glucose and A1c values ''' ''' Add the mean or median glucose and A1c values '''
if transform == 'avgglucose' and isinstance(transforms[transform], (int, float)): if transform == 'avgglucose' and isinstance(transforms[transform], (int, float)):
ax.annotate('Median glucose: %.1f%s' % (round(transforms['avgglucose'], 1), args.units), fontsize=9, \ if args.units == UNIT_MMOLL:
gmtext = 'Median glucose: %.1f%s' % (round(transforms['avgglucose'], 1), args.units)
else:
gmtext = 'Median glucose: %.0f%s' % (round(transforms['avgglucose'], 1), args.units)
ax.annotate(gmtext, fontsize=9, \
xy=(0.95, 0.85), xycoords='axes fraction', verticalalignment='top', horizontalalignment='right', \ xy=(0.95, 0.85), xycoords='axes fraction', verticalalignment='top', horizontalalignment='right', \
zorder=40, bbox=dict(facecolor=GREEN, edgecolor='#009e73', alpha=0.7, pad=8), \ zorder=40, bbox=dict(facecolor=GREEN, edgecolor='#009e73', alpha=0.7, pad=8), \
) )
@ -413,7 +425,7 @@ def generate_plot(data, ax=None, transforms={}, args=[], **plot_args):
#print(label) #print(label)
ax.annotate( ax.annotate(
label, label,
xy=(a, GRAPH_MAX-6), xy=(a, args.graph_max-6),
rotation=45, rotation=45,
zorder=25, zorder=25,
) )
@ -511,6 +523,9 @@ def parse_entry(data, icons, fmt='%Y-%m-%d %H:%M:%S'):
''' Convert value from string to float ''' ''' Convert value from string to float '''
data['value'] = float(data.get('value')) data['value'] = float(data.get('value'))
# XXX convert everything to mg/dL for testing
#data['value'] = float(round(data.get('value') * 18.0, 0))
return data return data
def list_days_and_weeks(data, trim_weeks=192): def list_days_and_weeks(data, trim_weeks=192):
@ -726,16 +741,22 @@ def parse_arguments():
default='mmol/L', choices=(UNIT_MGDL, UNIT_MMOLL), default='mmol/L', choices=(UNIT_MGDL, UNIT_MMOLL),
help=('The measurement units used (mmol/L or mg/dL).')) help=('The measurement units used (mmol/L or mg/dL).'))
parser.add_argument( parser.add_argument(
'--low', action='store', required=False, type=float, default=4, '--low', action='store', required=False, type=float, default=DEFAULT_LOW,
help=('Minimum of target glucose range.')) help=('Minimum of target glucose range.'))
parser.add_argument( parser.add_argument(
'--high', action='store', required=False, type=float, default=8, '--high', action='store', required=False, type=float, default=DEFAULT_HIGH,
help=('Maximum of target glucose range.')) help=('Maximum of target glucose range.'))
args = parser.parse_args() args = parser.parse_args()
args.pagesize = verify_pagesize(args.pagesize) args.pagesize = verify_pagesize(args.pagesize)
args.units = verify_units(args.units, args.high, args.low) args.units = verify_units(args.units, args.high, args.low)
if args.units == UNIT_MMOLL:
args.graph_max = GRAPH_MAX
args.graph_min = GRAPH_MIN
else:
args.graph_max = convert_glucose_unit(GRAPH_MAX, UNIT_MMOLL)
args.graph_min = convert_glucose_unit(GRAPH_MIN, UNIT_MMOLL)
''' Ensure we have a valid number of graphs_per_page ''' ''' Ensure we have a valid number of graphs_per_page '''
if not isinstance(args.graphs_per_page, int) or args.graphs_per_page < 1: if not isinstance(args.graphs_per_page, int) or args.graphs_per_page < 1:
@ -753,6 +774,34 @@ def from_csv(csv_file, newline=''):
rows.append(item) rows.append(item)
return rows return rows
def convert_glucose_unit(value, from_unit, to_unit=None):
"""Convert the given value of glucose level between units.
Args:
value: The value of glucose in the current unit
from_unit: The unit value is currently expressed in
to_unit: The unit to conver the value to: the other if empty.
Returns:
The converted representation of the blood glucose level.
Raises:
exceptions.InvalidGlucoseUnit: If the parameters are incorrect.
"""
if from_unit not in VALID_UNITS:
raise exceptions.InvalidGlucoseUnit(from_unit)
if from_unit == to_unit:
return value
if to_unit is not None:
if to_unit not in VALID_UNITS:
raise exceptions.InvalidGlucoseUnit(to_unit)
if from_unit is UNIT_MGDL:
return round(value / 18.0, 2)
else:
return round(value * 18.0, 0)
if __name__ == "__main__": if __name__ == "__main__":