Skip to content

Instantly share code, notes, and snippets.

@john-science
Last active February 14, 2024 15:07
Show Gist options
  • Save john-science/29445b6ee7adfb20ab59a83f5f6a5337 to your computer and use it in GitHub Desktop.
Save john-science/29445b6ee7adfb20ab59a83f5f6a5337 to your computer and use it in GitHub Desktop.
Python script for plotting my weight over time
""" plot_my_weight.py
This is a simple scatter-and-line plot of my body weight at various times. The challenge is that
the data is very irregular. This program reads in a CSV of this form:
datetime,weight(lbs),Steps,Meals,Calories,Run (km),anything,else...
2010-02-18,165
2010-02-25,156.6
2010-03-11,154.4
2022-06-22,159.1,10000,2,1500,5,...
"""
from datetime import datetime, timedelta
import matplotlib as mpl
import numpy as np
import sys
from matplotlib import style
style.use("ggplot")
# set the matplotlib backend
gui_env = ['TKAgg','GTKAgg','Qt4Agg','WXAgg']
for gui in gui_env:
try:
mpl.use(gui, warn=False, force=True)
from matplotlib import pyplot as plt
break
except:
continue
print(f"Using: {mpl.get_backend()}")
from matplotlib import pyplot as plt
# CONFIG
START_DATE = datetime(1981, 6, 22)
END_DATE = datetime(2200, 1, 1)
WEIGHT_FILE_PATH = "/home/john/Recordings/Personal/Code/ops/my_weight.csv"
# CONSTANTS
DAY_FORMAT = "%Y-%m-%d"
HOUR_FORMAT = "%Y-%m-%d-%H"
MIN_FORMAT = "%Y-%m-%d-%H-%M"
def main():
""" A simple command-line parser to make this script easier to use. """
# Constants
DAY_FORMAT = "%Y-%m-%d"
start_date = START_DATE
end_date = END_DATE
weight_file = WEIGHT_FILE_PATH
try:
if len(sys.argv) > 1:
start_date = datetime.strptime(sys.argv[1], DAY_FORMAT)
if len(sys.argv) > 2:
end_date = datetime.strptime(sys.argv[2], DAY_FORMAT)
if len(sys.argv) > 3:
weight_file = sys.argv[3]
except ValueError:
usage()
if len(sys.argv) > 4:
usage()
plot_my_weight(start_date, end_date, weight_file)
def usage():
""" This is just a simple print-to-screen if the users throw nonsense at the command line. """
print("\nUsage:\n\npython weights.py\n\nOR\n")
print("python plot_my_weight.py 2014-01-01")
print("python plot_my_weight.py 2014-01-01 2015-12-31")
print("python plot_my_weight.py 2014-01-01 2015-12-31 /path/to/my_weight.csv\n")
exit()
def plot_my_weight(start_date, end_date, weight_file):
"""This is the meat of the script.
Create a quick matplotlib plot of weights and display it.
"""
# read weight file
f = open(weight_file, "r")
_ = f.readline()
lines = f.readlines()
f.close()
# ingest weight data
dates = []
weights = []
# loop through all lines in file
for line in lines:
# split line into columns
bits = line.strip().split(",")
date_str = bits[0].strip()
weight = bits[1].strip()
if not weight:
continue
# parse dates
if len(date_str) == 10:
date = datetime.strptime(date_str, DAY_FORMAT)
elif len(date_str) == 13:
date = datetime.strptime(date_str, HOUR_FORMAT)
elif len(date_str) == 16:
date = datetime.strptime(date_str, MIN_FORMAT)
# make sure date is in range
if date >= start_date and date <= end_date:
dates.append(date)
else:
continue
# parse weight
weights.append(float(weight))
# convert data to NumPy array
x = np.array(dates)
y = np.array(weights)
# build better weekly average
# 1. find the smallest value in each day
sdays = {}
for i, d in enumerate(dates):
yr = d.year
jday = d.timetuple().tm_yday
sday = (yr, jday)
if sday not in sdays or weights[i] < sdays[sday]:
sdays[sday] = weights[i]
# 2. build the weekly data we need to average a week
weekly = {}
for sday, min_val in sdays.items():
yr, jday = sday
d = datetime.strptime(str(yr) + str(jday), "%Y%j").date()
monday = d - timedelta(days=d.weekday())
if monday not in weekly:
weekly[monday] = []
weekly[monday].append(min_val)
# 3. do the weekly averages
weekly_dates = []
weekly_weights_min = []
weekly_weights_avg = []
weekly_weights_max = []
for monday in sorted(weekly.keys()):
weekly_dates.append(monday + timedelta(days=3))
avg = sum(weekly[monday]) / len(weekly[monday])
weekly_weights_avg.append(avg)
weekly_weights_min.append(min(weekly[monday]))
weekly_weights_max.append(max(weekly[monday]))
# 4. create plot of raw data and weekly averages
plt.plot(weekly_dates, weekly_weights_avg, color="0.4", zorder=5, linewidth=6)
plt.scatter(x, y, c=y/max(y), s=25, zorder=3, linewidth=0, cmap="rainbow")
#plt.plot(weekly_dates, weekly_weights_avg, color="0.8", zorder=1, linewidth=24)
plt.plot(weekly_dates, weekly_weights_min, color="0.8", zorder=2, linewidth=2)
plt.plot(weekly_dates, weekly_weights_max, color="0.8", zorder=2, linewidth=2)
plt.ylim(ymin=min(y) - 5)
min_yr = min([d.year for d in dates])
max_yr = max([d.year for d in dates])
plt.xticks([datetime(yr, 1, 1) for yr in range(min_yr, max_yr + 2)], rotation=45)
# 5. add vertical lines for when I lived where
plt.axvline(datetime(2018, 4, 1), color="gray", label="Boston")
plt.axvline(datetime(2021, 7, 4), color="gray", label="Seattle")
plt.axvline(datetime(2022, 7, 15), color="gray", label="Spokane")
plt.show()
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment