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: prettify
# 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 csv
@ -40,16 +40,19 @@ VALID_UNITS = [UNIT_MGDL, UNIT_MMOLL]
INTERVAL = 15
# Maximum gluclose value to display (TODO: mmol/mg)
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'
# Set colour for above-target maxmins
# Colour for above-target maxmins
YELLOW = '#f1b80e'
# Set colour for graph lines
# Colour for graph lines
BLUE = '#02538f'
# Set colour for median glucose box
# Colour for median glucose box
GREEN = '#009e73'
# Set colour for median A1c box
# Colour for median A1c box
BOXYELLOW = '#e69f00'
def main():
@ -68,6 +71,18 @@ def main():
for row in rows:
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 '''
''' We're using 8 minute gaps in order to have more accurate fills '''
rows = fill_gaps(rows, interval=dt.timedelta(minutes=10))
@ -140,7 +155,7 @@ def main():
generate_plot(data,
ax=ax,
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,
color=BLUE,
)
@ -211,7 +226,7 @@ def main():
generate_plot(data,
ax=ax,
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,
color=BLUE,
)
@ -308,13 +323,17 @@ def generate_plot(data, ax=None, transforms={}, args=[], **plot_args):
x_min = mdates.date2num(firstminute)
x_max = mdates.date2num(lastminute)
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 '''
xtimes = []
time = firstminute
while time < lastminute:
xtimes.append(time)
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 '''
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')
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.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_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:
''' 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:
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)
#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:
''' 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.
Do this by removing every nth element from the array '''
n = 5
while len(p) > 1000:
p = np.delete(p, np.arange(0, len(p), n), axis=0)
while len(x) > 1000:
x = np.delete(x, np.arange(0, len(x), 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:
curve = np.array([c for _, c in bezier(np.array([x,y]).T, 250)])
(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 '''
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', \
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)
ax.annotate(
label,
xy=(a, GRAPH_MAX-6),
xy=(a, args.graph_max-6),
rotation=45,
zorder=25,
)
@ -511,6 +523,9 @@ def parse_entry(data, icons, fmt='%Y-%m-%d %H:%M:%S'):
''' Convert value from string to float '''
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
def list_days_and_weeks(data, trim_weeks=192):
@ -726,16 +741,22 @@ def parse_arguments():
default='mmol/L', choices=(UNIT_MGDL, UNIT_MMOLL),
help=('The measurement units used (mmol/L or mg/dL).'))
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.'))
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.'))
args = parser.parse_args()
args.pagesize = verify_pagesize(args.pagesize)
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 '''
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)
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__":