Skip to content

Instantly share code, notes, and snippets.

@kmcallister
Created July 28, 2019 17:11
Show Gist options
  • Save kmcallister/19c9898fea0d76156fa2f1925ace2164 to your computer and use it in GitHub Desktop.
Save kmcallister/19c9898fea0d76156fa2f1925ace2164 to your computer and use it in GitHub Desktop.

Revisions

  1. kmcallister created this gist Jul 28, 2019.
    17 changes: 17 additions & 0 deletions calc
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,17 @@
    #!/bin/sh

    #set -e

    ./ewma.py < log > smoothed
    echo
    ./progress.py "$@" < smoothed
    echo

    gnuplot < weight-graph.gpl
    #./maintain.py < log
    #echo

    ./rate.py < smoothed > rate
    gnuplot < rate-graph.gpl

    cp log backup/$(tail -n 1 log | awk '{print $1}')
    48 changes: 48 additions & 0 deletions ewma.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,48 @@
    #!/usr/bin/python
    import sys
    import argparse
    import time
    import datetime

    argparser = argparse.ArgumentParser()
    argparser.add_argument('--verbose', '-v', action='store_true')
    argparser.add_argument('--alpha', '-a', type=float, default=0.1)
    args = argparser.parse_args()

    if args.alpha < 0.0 or args.alpha > 1.0:
    raise ValueError, "you suck"

    ewma = 0.0
    def update(weight):
    global ewma
    ewma = args.alpha*weight + (1.0 - args.alpha)*ewma

    last_ordinal = None
    last_weight = None
    for ln in sys.stdin:
    date, weight = ln.split()
    date = datetime.datetime.strptime(date, '%Y-%m-%d')
    weight = float(weight)
    ordinal = date.toordinal()

    if args.verbose:
    print (date.toordinal(), weight)

    if last_ordinal is None:
    last_ordinal = ordinal
    last_weight = weight
    ewma = weight
    else:
    last_ordinal += 1
    while last_ordinal < ordinal:
    if args.verbose:
    print 'Missing day, using', last_weight
    update(last_weight)
    last_ordinal += 1

    if args.verbose:
    print 'Update with', weight
    update(weight)
    last_weight = weight

    print date.strftime('%Y-%m-%d'), ewma
    32 changes: 32 additions & 0 deletions maintain.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,32 @@
    #!/usr/bin/env python
    import datetime
    import sys
    import math
    import argparse
    import numpy
    from scipy import stats

    goal = 234
    window = 30
    points = []
    confidence = 0.05

    for ln in sys.stdin:
    date, weight = ln.split()
    date = datetime.datetime.strptime(date, '%Y-%m-%d')
    weight = float(weight)
    points.append((date, weight))
    points = points[-window:]

    weights = [p[1] for p in points]
    minw, maxw, avgw = (min(weights), max(weights), numpy.average(weights))

    t_stat, p_value = stats.ttest_1samp(weights, goal)

    print 'Target: %6.2f lbs' % (goal,)
    print 'Range: %6.2f lbs' % (maxw-minw,)
    print 'Std deviation: %6.2f lbs' % (numpy.std(weights),)
    print 'Min/avg/max: %6.2f / %6.2f / %6.2f' % (minw, avgw, maxw)
    print 'Deltas: %6.2f / %6.2f / %6.2f' % (minw-goal, avgw-goal, maxw-goal)
    print
    print 'Verdict: ', 'Maintaining!' if p_value > confidence else 'Not maintaining', '(p = %.2f)' % (p_value,)
    104 changes: 104 additions & 0 deletions progress.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,104 @@
    #!/usr/bin/env python
    import datetime
    import sys
    import math
    import argparse
    from scipy import stats

    argparser = argparse.ArgumentParser()
    argparser.add_argument('--reddit', '-r', action='store_true')
    args = argparser.parse_args()

    parse_date = lambda s: datetime.datetime.strptime(s, "%Y-%m-%d")

    goal = 225
    goal_date = parse_date("2019-06-01")
    window = 30
    height = 1.91 # meters

    kg_per_lb = 0.453592
    points = []

    def parse(ln):
    date, weight = ln.split()
    date = datetime.datetime.strptime(date, '%Y-%m-%d')
    weight = float(weight)
    return (date, weight)

    lines = list(sys.stdin)
    start_date, start = parse(lines[0])

    points = map(parse, lines)
    num_points = len(points)
    num_decrease = sum(1 for p, q in zip(points, points[1:]) if p[1] > q[1])

    points = points[-window:]

    first = points[0]
    last = points[-1]

    reg_x = [p[0].toordinal() for p in points]
    reg_y = [p[1] for p in points]

    slope, intercept, r_value, p_value, stderr = stats.linregress(reg_x, reg_y)

    #rate = (first[1] - last[1]) / (last[0] - first[0]).days
    rate = -slope

    if rate <= 0.0:
    print "Sorry, you're getting fatter at %.2f lb/wk (r^2 = %.2f)" % (-7.0*rate, r_value**2.0)
    sys.exit(1)

    def output_stat(label, value):
    if args.reddit:
    print '**' + label + '**|' + value.lstrip()
    else:
    print (label + ':').ljust(17) + value

    done = start - last[1]
    days_done = (last[0] - start_date).days
    remainder = last[1] - goal
    remaining_time = datetime.timedelta(days = remainder / rate)
    finish_date = last[0] + remaining_time
    margin = (goal_date - finish_date).days + 1

    required_rate = remainder / (goal_date - last[0]).days
    excess_rate = rate - required_rate

    def project(date):
    projection = last[1] - (rate * (parse_date(date) - last[0]).days)
    print 'Weight on %s: %6.2f lbs = %6.2f lbs lost' % (date, projection, start - projection)


    if args.reddit:
    print 'Stat|Value'
    print ':-|:-'
    output_stat('Current weight', '%6.2f lbs' % (last[1],))
    #output_stat('BMI', '%6.2f' % ((kg_per_lb * last[1]) / (height ** 2.0),))
    output_stat('Lost so far', '%6.2f lbs = %5.2f%% of starting weight' % (done, 100.0 * done / start))
    output_stat('Remaining', '%6.2f lbs = %5.2f%% of current weight' % (remainder, 100.0 * remainder / last[1]))
    output_stat('Progress', '%6.2f %%' % (done / (start - goal) * 100.0,))
    output_stat('Required rate', '%6.2f lbs / wk' % (7.0 * required_rate,))
    output_stat('Actual rate', '%6.2f lbs / wk = %.2f%% per week (r^2 = %4.2f)' % (7.0*rate, 100*7.0*rate / last[1], r_value**2.0))
    #output_stat('Excess rate', '%6.2f lbs / wk' % (7.0*excess_rate,))
    output_stat('Deficit', ' %4d kcal / day' % (rate*3500,))
    #output_stat('Excess deficit', ' %4d kcal/day' % (excess_rate*3500,))
    output_stat('All-time rate', '%6.2f lbs / wk' % (7.0 * done / days_done,))
    output_stat('Average drops', '%6.2f %% of the time (%d out of %d points)' % (100.0 * num_decrease / float(num_points), num_decrease, num_points))
    print
    print 'Goal will be reached on %s' % (finish_date.strftime('%Y-%m-%d'),),
    if margin > 0:
    print '(%d days early)' % (margin,)
    else:
    print '(%d days late)' % (-margin,)
    if args.reddit:
    print
    print days_done, 'days done, only', remaining_time.days, 'left to go! Keep it up, you\'re doing great!'

    print
    project('2019-06-01')

    with open('regression_line', 'w') as f:
    line_out = lambda pt: f.write(pt[0].strftime('%Y-%m-%d') + ' ' + str(intercept + slope*pt[0].toordinal()) + '\n')
    line_out(points[0])
    line_out(points[-1])
    14 changes: 14 additions & 0 deletions rate-graph.gpl
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,14 @@
    set title "Weight loss rate"
    set ylabel "Pounds per week"
    #set y2label "r^2"

    set term png size 1400,900
    set output "rate-graph.png"
    set timefmt "%Y-%m-%d"
    set xdata time
    set format x "%b\n%d"

    #set xrange ["2018-02-18":]

    plot "rate" using 1:2 with lines lw 3 title "Rate", \
    #"rate" using 1:3 with lines lw 1 title "r^2" axes x1y2
    26 changes: 26 additions & 0 deletions rate.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,26 @@
    #!/usr/bin/env python
    import datetime
    import sys
    import math
    import argparse
    from scipy import stats

    window = 14
    points = []

    for ln in sys.stdin:
    date, weight = ln.split()
    date = datetime.datetime.strptime(date, '%Y-%m-%d')
    weight = float(weight)
    points.append((date, weight))
    points = points[-window:]

    if len(points) < window:
    continue

    reg_x = [p[0].toordinal() for p in points]
    reg_y = [p[1] for p in points]

    slope, intercept, r_value, p_value, stderr = stats.linregress(reg_x, reg_y)

    print date.strftime("%Y-%m-%d"), -7.0*slope, r_value**2.0
    16 changes: 16 additions & 0 deletions weight-graph.gpl
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,16 @@
    set title "Weight loss progress"
    set ylabel "Weight (lbs)"

    set term png size 1400,900
    set output "weight-graph.png"
    set timefmt "%Y-%m-%d"
    set xdata time
    set format x "%b\n%d"

    #set xrange ["2018-08-01":]
    #set yrange [240:290]

    plot "log" using 1:2 with linespoints pointtype 6 title "Daily weight", \
    "smoothed" using 1:2 with lines lw 3 title "Moving average", \
    "regression_line" using 1:2 title "Trend line" with lines lw 2 lt rgb "#c01f1f", \
    # "raw_line" using 1:2 title "Raw trend line" with lines lw 2 lt rgb "blue"