Fix bugs that occur when switching to mg/dL units.
This commit is contained in:
parent
9ed89e7f8f
commit
7087b5299a
@ -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
|
||||
@ -39,17 +39,20 @@ VALID_UNITS = [UNIT_MGDL, UNIT_MMOLL]
|
||||
# When averaging, set the period to this number of minutes
|
||||
INTERVAL = 15
|
||||
# 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'
|
||||
# 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__":
|
||||
|
Loading…
Reference in New Issue
Block a user