Last active
February 14, 2024 15:07
-
-
Save john-science/29445b6ee7adfb20ab59a83f5f6a5337 to your computer and use it in GitHub Desktop.
Python script for plotting my weight over time
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" 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