# Nutrition and Exercise Habits – Basic Analysis

The below code will import previously manicured data to offer quantitative advice regarding dietary and exercise logs, as well as draw some descriptive conclusions about our general habits. (see https://josetorres.us/data-science/jefit-etl-with-python/ and https://josetorres.us/data-science/scraping-myfitnesspal-with-python/ for data acquisition, cleaning, and warehousing steps.)

In [1]:
```%matplotlib inline
import sqlite3 # importing databases
import numpy as np  # helpful shorthanding and nans
import pandas as pd  # workhorse dataframes
import datetime  # timedelta offsets
import matplotlib.pyplot as plt  # basic plotting
import matplotlib.ticker # for aligning twin y axes
import matplotlib.style as style
import statsmodels.api as sm  # basic regression analysis
import sympy  # for pratio integrals
from scipy.optimize import curve_fit  # for fitting p-ratio
from fractions import Fraction # for expressing floats as rational for sympy polydiviserror
import warnings # to ignore warning messages due to deprecation etc over time
warnings.filterwarnings('ignore')
# mpl.style.use({'figure.figsize':[16,10], 'font.size':14.0, 'axes.titlesize':'large',
#          'axes.labelsize': 'medium', 'legend.fontsize': 'large', 'xtick.labelsize': 'medium',
#           'ytick.labelsize' : 'medium', 'axes.grid' : True, 'grid.linewidth' : 0.75,
#           'grid.linestyle' : '-', 'grid.color' : 'white'})
style.use({'figure.figsize':[16,10]})
style.use('fivethirtyeight') # some plot styling
```

## Importing data from MyFitnessPal database and merging it with any potential weight, bodyfat, and circumferential measurements from Jefit (Created in https://josetorres.us/data-science/scraping-myfitnesspal-with-python/)

In [2]:
```def load_mfp(mfp_db = "mfp_clean.db", jefit_db = "jefit_clean.db", merge_jefit=True):
conn = sqlite3.connect(mfp_db)
daily = pd.read_sql_query("SELECT * FROM mfp_data", conn, index_col="Date")
daily.index = pd.to_datetime(daily.index)

# creating daily macronutrient ratios to compare to goals
daily['PMacro'] = (daily.Protein*4)/daily.Calories*100.0
daily['FMacro'] = (daily.Fat*9)/daily.Calories*100.0
daily['CMacro'] = (daily.Carbs*4)/daily.Calories*100.0

profile = pd.read_sql_query("SELECT * FROM mfp_profile", conn).drop(["index"], axis=1)
profile = profile.iloc[0]

conn.close()

if merge_jefit:
conn = sqlite3.connect(jefit_db)
weight_df = pd.read_sql_query("SELECT * FROM weight", conn, index_col="Date")
weight_df.index = pd.to_datetime(weight_df.index)
bodyfat_df = pd.read_sql_query("SELECT * FROM bodyfat", conn, index_col="Date")
bodyfat_df.index = pd.to_datetime(bodyfat_df.index)
measurements = pd.read_sql_query("SELECT * FROM measurements", conn, index_col="Date")
measurements.index = pd.to_datetime(measurements.index)
cols_mfp = daily.columns.tolist()
cols_meas = measurements.columns.tolist()
totcols = cols_mfp + cols_meas
daily = weight_df.combine_first(daily)
daily = bodyfat_df.combine_first(daily)
daily = measurements.combine_first(daily)[totcols]
conn.close()

# get rid of days with no data
daily.replace('NaN', 0, inplace=True)
daily = daily.loc[(daily != 0).any(axis=1)]
daily.replace(0, np.nan, inplace=True)

# check for duplicate indices and keep only the last
daily = daily[~daily.index.duplicated(keep='last')]

return profile, daily
```

## Importing data from database created in https://josetorres.us/data-science/jefit-etl-with-python/

In [3]:
```def load_jefit(jefit_db = "jefit_clean.db"):
conn = sqlite3.connect(jefit_db)
lifts_df = pd.read_sql_query("SELECT * FROM lifts", conn, index_col="Date")
lifts_df.index = pd.to_datetime(lifts_df.index)
conn.close()
return lifts_df
```

## Putting it all together in an easily accessible class

In [4]:
```class FitnessLogs(object):
'''
Includes methods for viewing and predicting multiple factors including health, performance, and future estimates.

Include jefit=True if jefit data exists, start and end dates optional
'''

def __init__(self, mfp_db = "mfp_clean.db", jefit_db = "jefit_clean.db", jefit=True, start_date=None, end_date=None):
self._profile, self._daily = load_mfp(mfp_db, jefit_db, merge_jefit=jefit)

# load jefit data if jefit
if jefit:

# slice data using start and end date if given
if start_date:
start_date = pd.to_datetime(start_date)
self._daily = self._daily.loc[start_date:]
self._lifts = self._lifts.loc[start_date:]
if end_date:
end_date = pd.to_datetime(end_date)
self._daily = self._daily.loc[:end_date]
self._lifts = self._lifts.loc[:end_date]

# check how much data is missing, will greatly affect accuracy especially total estimate
days_total = (self._daily.index[-1] - self._daily.index[0]).days + 1
days_missing = days_total - len(self._daily)
if days_missing/float(days_total) > 0.25:
print('There are ' + str(days_missing) + ' days missing from your data spanning ' + str(days_total) + ' days (' + str(int(100*days_missing/float(days_total))) + '%).')
print('This greatly affects the value of the predicted result which is based on all history; missing weight or caloric logs cannot be accurately estimated (as missing days are often a result of going off diet). It is recommended to use start and end dates without such large gaps.')
print("Data will be imputed based on current limits in the meantime")
print('')

# filling na values first with ffill, then mean, 0 if no vals at all
self._daily.fillna(method='ffill', inplace=True)
self._daily.fillna(self._daily.mean(), inplace=True)
self._daily.fillna(0, inplace=True)

# reindexing to fill in missing dates while imputing
date_range = pd.date_range(start = self._daily.index[0], end = self._daily.index[-1])
self._daily = self._daily.reindex(date_range, method = 'ffill')

# creating timeframe dataframes in advance
self._weekly, self._monthly, self._yearly = self._create_means(self._daily)

# creating jefit timeframe dataframes in advance
if jefit:
self._historical_performance = self._create_historical_performance()
self._max_prs = self._calc_max_prs()
self._lifts_daily, self._lifts_daily_no_body = self._calc_lifts_daily()
self._lifts_weekly, self._lifts_weekly_no_body = self._calc_lifts_weekly()
self._lifts_monthly, self._lifts_monthly_no_body = self._calc_lifts_monthly()
self._lifts_yearly, self._lifts_yearly_no_body = self._calc_lifts_yearly()

self._calGoal = int(self._profile.CalGoal)
self._proRatio = self._profile.ProteinRatio
self._fatRatio = self._profile.FatRatio
self._carbRatio = self._profile.CarbRatio
self._weeklyChange = self._profile.WeeklyChange
self._activity = str(self._profile.Activity)
self._gender = str(self._profile.Gender)
self._goalWeight = self._profile.GoalWeight
self._goalBodyfat = None
self._imperial = bool(self._profile.Imperial)

if self._imperial:
self._wgtunit = 'lbs'
self._hgtunit = 'inches'
else:
self._wgtunit = 'kgs'
self._hgtunit = 'cm'

self._height = self._profile.Height
self._age = self._profile.Age

# finding most recent entries for weight and bodyfat
try:
self._weight = self._daily.loc[self._daily.Weight.last_valid_index()].Weight
except:
self._weight = 400
print('No weight logs found. Please use the set_weight method to enter one manually. Defaulting to 400.')
try:
self._bodyfat = self._daily.loc[self._daily.Bodyfat.last_valid_index()].Bodyfat
except:
self._bodyfat = 35
print('No bodyfat logs found. Please use the set_bodyfat method to enter one manually. Defaulting to 35.')
self._fatlbs = round(self._weight * self._bodyfat/100, 2)

self._tdee = self._calc_tdee()
self._otdee = self._calc_otdee('daily', 7)
self._calc_activity_level()
self._calc_calories()
self.set_macros(self._proRatio, self._fatRatio, self._carbRatio)

# save default p-ratio values and resultant constants
self._pratio_male_def, self._pratio_female_def = [0, 100.0/3], [-7.0/3, 100.0/3]
self._pratio_curve_def = np.array([[.60, 0.05],
[.40, 0.075],
[.30, 0.1],
[.25, 0.12],
[.20, 0.15],
[.15, 0.2],
[.10, 0.3],
[.05, 0.6],
[.03, 1]])

# set default p-ratio constants for user
self._const_loss = 20
self._pratio_curve = self._pratio_curve_def
if self._gender == 'male':
self._pratio_const = self._pratio_male_def
else:
self._pratio_const = self._pratio_female_def

def _create_means(self, mfp_daily):
# maybe useless fn
# weekly means, indexed by start of week i.e. 6/14 Sunday includes 6/14-6/20(Sun-Sat)
offset = datetime.timedelta(days=1)
mfp_weekly = mfp_daily.resample('W-SAT', how='mean', label='left', loffset=offset)

# monthly means, indexed by start of month i.e. 6/2015 covers 6/1/15-6/30/15
mfp_monthly = mfp_daily.resample('MS', how='mean')

# annual means, indexed by start of year i.e. 2015/1/1 includes all of 2015 data
mfp_yearly = mfp_daily.resample('AS', how='mean')

return mfp_weekly, mfp_monthly, mfp_yearly

def _check_timeframe(self, timeframe):
'''
Translates an input timeframe to output correctly and catch edgecases which will default to daily
'''
timeframedict = {'daily': self._daily, 'weekly': self._weekly, 'monthly': self._monthly, 'yearly': self._yearly}
timeframe = timeframe.lower()
if timeframe not in timeframedict.keys():
print('Timeframe not recognized as daily/weekly/monthly/yearly. Defaulting to daily, please try again.')
return timeframedict['daily']
return timeframedict[timeframe].copy()

def _calc_tdee(self, summary=True):
'''
Calculates theoretical TDEE based on research formula given weight, gender, height, and activity level
'''
activity_mult = {'sedentary': 1.2, 'light': 1.375, 'moderate': 1.55, 'heavy': 1.7, 'extreme': 1.9}
if self._gender.lower() == 'male':
tdee = (9.99 * self._weight + 6.25 * self._height - 4.92 * self._age + 5) * activity_mult[self._activity]
else:
tdee = (9.99 * self._weight + 6.25 * self._height - 4.92 * self._age - 161) * activity_mult[self._activity]

if summary:
print('Predicted TDEE for a ' + self._gender.lower() + ' weighing ' + str(self._weight) + ' ' + self._wgtunit +
' and measuring ' + str(self._height) + ' ' + self._hgtunit + ' at age', self._age,  'with an activity level '
'described as ' + self._activity.lower() + ' is ' + str(int(tdee)) + '.')
print('')

return int(tdee)

def _calc_calories(self):
'''
Provides survey information during initialization including whether set values are in range using profile information
'''
if not self._imperial:
calsfat = 68.8
mult = 3500.0/0.4536
cals_week = self._weeklyChange*mult
tdee_loss = (self._tdee - self._calGoal)*7/mult
otdee_loss = (self._otdee - self._calGoal)*7/mult
else:
calsfat = 31.4
mult = 3500.0
cals_week = self._weeklyChange*mult
tdee_loss = (self._tdee - self._calGoal)*7/mult
otdee_loss = (self._otdee - self._calGoal)*7/mult

calc_goal = int(self._otdee - cals_week/7)
max_deficit = int(self._fatlbs * calsfat)

print('Your caloric goal is currently set to', str(self._calGoal) + '.')

if abs(calc_goal - self._calGoal) < 150:
print('This value is reasonably close to our recommendation of', str(calc_goal), 'daily calories for a '
'weekly loss of', self._weeklyChange, self._wgtunit + '.')
else:
print('For a weekly change of ' + str(self._weeklyChange) + ' ' + self._wgtunit + ', it is recommended you '
'change your daily caloric intake to ' + str(calc_goal) + '.')

print('')
print('With a theoretical TDEE of', str(self._tdee), 'you will lose', str(tdee_loss), self._wgtunit,
'per week.')
print('With the empirically calculated oTDEE of', self._otdee, 'you will lose', otdee_loss, self._wgtunit,
'per week.')
print('')
print('The maximal daily caloric deficit for fat loss is approximately ' + str(calsfat) + ' cals per ' +
self._wgtunit[:2] + ' of fat. (See http://www.ncbi.nlm.nih.gov/pubmed/15615615)')
print('With ' + str(self._fatlbs) + ' ' + self._wgtunit + ' of fat, you can afford a maximum deficit of ' +
str(max_deficit) + ' cals/day, or ' + str(max_deficit*7/mult) + ' ' + self._wgtunit + '/week.')
print('')

if cals_week/7 > max_deficit:
print('Caloric deficit required to lose ' + str(self._weeklyChange) + ' ' + self._wgtunit + ' per week is greater than what would reasonably come from fat stores.')
print('As such, you will likely be losing lean body mass (muscle), hindering your efforts to lower bodyfat aka "tone".')
print('')
print('It is recommended to lower the weekly weight loss goal to a maximum of ' + str(max_deficit*7/mult) + ' ' + self._wgtunit + '.')
else:
pass

def _calc_otdee(self, timeframe, window_size):
'''
Calculating observed TDEE using empirical data. Will warn if missing > 25% data
Sets TDEE value, oTDEE dataframe,
'''
mfp_df = self._check_timeframe(timeframe)
# copy df so we don't modify it outside the fn
mfp_df = mfp_df[['Weight', 'NetCals']]

# attempt to solve missing data in either cals or weight
mfp_df.dropna(axis=0, how='any', inplace=True)

# calculate difference between weights separated by window_size then use that to estimate calorie
# deficit or surplus per day assuming 3500cal/lb difference
if self._imperial:
mult = 3500
else:
mult = 3500/0.4536
mfp_df['WeightDiffCals'] = mfp_df.Weight.diff(window_size)*mult/window_size
#print(mfp_df)

# shift weight results up one day so calories of a day/period corresponds to weight of the morning after
mfp_df[['Weight', 'WeightDiffCals']] = mfp_df[['Weight', 'WeightDiffCals']].shift(-1)

# calculate moving average of cals with given window size on any given date
mfp_df['MeanCals'] = pd.rolling_mean(mfp_df.NetCals, window_size)

# estimate otdee for all overlapping periods
mfp_df['oTDEE'] = mfp_df.MeanCals - mfp_df.WeightDiffCals

# then take the average of those averages THIS IS FOR MORE SMOOTHING
# mfp_df['MeanoTDEE'] = pd.rolling_mean(mfp_df.oTDEE, smoothing_window)
# mfp_df.MeanoTDEE = mfp_df.MeanoTDEE.shift(-smoothing_window+1
# shift all data so periods line up with their starting dates
# mfp_df = mfp_df.shift(-window_size+1)

# clears data from before 7 day window value returned
mfp_df = mfp_df.dropna()

# accurately count days since start of data

# regress over all data
fit1 = sm.OLS(mfp_df.oTDEE, X).fit()

# regress over last 14 days
fit2 = sm.OLS(mfp_df.oTDEE[-14:], X2).fit()

# currentotdee = int(fit1.predict()[-1]) #  change to predicted otdee, use 14 day val instead
self._otdeeDF = mfp_df
self._otdeeFit = fit1
self._otdeeFit2 = fit2

mean_last14 = int(self._otdeeDF.oTDEE[-14:].mean())

return mean_last14

def _calc_activity_level(self):
'''
Estimates your actual activity level and compares to self-reported value
'''
activity_mult = {'sedentary': 1.2, 'light': 1.375, 'moderate': 1.55, 'heavy': 1.7, 'extreme': 1.9}
bmr = self._tdee / activity_mult[self._activity]
avg_mult = round(self._otdee / bmr,2)
nearest = 5
for key, val in activity_mult.items():
if abs(val-avg_mult) < nearest:
nearest = abs(val-avg_mult)
nearkey = key

if nearkey != self._activity:
print('From your oTDEE, your activity level more closely resembles ' + nearkey + ' activity. Consider '

def _calc_pratio_constant(self):
'''
Calculates constants to be used in P-Ratio formula f(BF)
'''
def func(B, a, b):
return 1/(a+b*B)

pratio = self._pratio_curve
pratio = np.c_[pratio, 1.0/pratio[:, -1]]
pratio = pd.DataFrame(pratio, columns=['Bodyfat', 'P-Ratio', 'PInverse'])

#print(pratio)
x = pratio.iloc[:, 0]
y = pratio.iloc[:, 1]

popt, pcov = curve_fit(func, x, y)
self._pratio_const = popt

return popt

def _calc_pratio_weight(self, final_bodyfat, init_bodyfat, init_weight):
'''
Uses input p-ratio curve to calculate at what weight reach your goal bodyfat
'''
B = sympy.Symbol("B")
a, b = self._pratio_const
a, b = Fraction(a).limit_denominator(), Fraction(b).limit_denominator()
a, b = float(a), float(b)
a, b = sympy.sympify(a), sympy.sympify(b)

try:
integral = sympy.integrate((1-B-1/(a+b*B))**(-1), (B, round(init_bodyfat/100.0,2), round(final_bodyfat/100.0,2)))
integral = np.float(integral)
except:
# most accurate numbers failed PolyDivisError, round hard
a, b = round(a,0), round(b,0)
integral = sympy.integrate((1-B-1/(a+b*B))**(-1), (B, round(init_bodyfat/100.0,2), round(final_bodyfat/100.0,2)))
integral = np.float(integral)

final_weight = np.exp(integral + np.log(init_weight))

return final_weight

def set_macros(self, pratio, fratio, cratio):
'''
Manually set goal macronutrient ratios
'''
if pratio+fratio+cratio == 100:
mult = 1
elif pratio+fratio+cratio == 1:
mult = 100
else:
print('')
return
self._proRatio = pratio*mult
self._fatRatio = fratio*mult
self._carbRatio = cratio*mult
self._proGoal = int(self._calGoal * self._proRatio/(100.0*4))
self._fatGoal = int(self._calGoal * self._fatRatio/(100.0*9))
self._carbGoal = int(self._calGoal * self._carbRatio/(100.0*4))

# print('Macronutrient goals have been modified. Recomputing macronutrient totals.')
# print('')
print('Current macronutrient goals are', str(self._proGoal) + "/" + str(self._fatGoal) + '/' +
str(self._carbGoal), 'P/F/C in grams, with proportions', str(self._proRatio) + '/' + str(self._fatRatio)
+ '/' + str(self._carbRatio) + '.')

if self._imperial and self._proGoal/self._weight > 0.82:
print('Your protein goal is at least 0.82g/lb of body weight, appropriate according to modern research. (See http://www.ncbi.nlm.nih.gov/pubmed/22150425)')
elif not self._imperial and self._proGoal/self._weight > 1.8:
print('Your protein goal is at least 1.8g/kg of body weight, appropriate according to modern research. (See http://www.ncbi.nlm.nih.gov/pubmed/22150425)')
else:
print('Your protein goal is lower than 0.82g/lb or 1.8g/kg, which may inhibit muscle growth or lean body'
'mass retention. It is recommended to increase protein intake. (See http://www.ncbi.nlm.nih.gov/pubmed/22150425)')
print('')

def set_goal_calories(self, calories):
'''
Manually set goal calories
'''
self._calGoal = calories

print('Caloric goal has been modified. Recomputing macronutrient totals and caloric recommendations.')
print('')

self.set_macros(self._proRatio, self._fatRatio, self._carbRatio)

self._calc_calories()

def set_weeklychange(self, weeklychange):
'''
Manually set goal weight loss per week
'''
self._weeklyChange = weeklychange

print('Weekly weight change has been modified. Recomputing caloric recommendations.')
print('')

self._calc_calories()

def set_activitylevel(self, activitylevel):
'''
Manually set an activity level. Options and their multiplicative valuesactivity = {'sedentary': 1.2, 'light': 1.375, 'moderate': 1.55, 'heavy': 1.7, 'extreme': 1.9}
'''
self._activity = activitylevel.lower()

print('Activity level has been modified. Recomputing caloric recommendations.')
print('')

self._tdee = self._calc_tdee()

self._calc_calories()

def set_otdee(self, calories):
'''
in the case one would like to use a custom otdee value, i.e. the mean oTDEE or last 14 days oTDEE instead of
the calculated linear regression oTDEE
'''
self._otdee = calories

print('Observed TDEE has been modified. Recomputing caloric recommendations.')
print('')

self._calc_calories()

def set_goal_weight(self, goalweight):
'''
Change GOAL weight from imported value
'''
self._goalWeight = goalweight

print('Goal Weight has been modified. Recomputing approximate goal date and weight change plot.')
print('')

self.plot_weight('daily', showgoal=True)

def set_bodyfat(self, bodyfat):
'''
Change CURRENT body fat from imported value
'''
self._bodyfat = bodyfat

self._fatlbs = round(self._weight * self._bodyfat/100, 2)

print('Bodyfat has been modified.')
print('')

def set_pratio_curve(self, defvals=None):
'''
Set your own p-value estimates, entered in matrix form where
column 1 = bodyfat in %
column 2 = ratio of loss in lbm per change in weight

np.array([[.60, 0.05],
[.40, 0.075],
[.30, 0.1],
[.25, 0.12],
[.20, 0.15],
[.15, 0.2],
[.10, 0.3],
[.05, 0.6],
[.03, 1]])

Will recompute constants and plot new custom curve
'''
if defvals == None:
defvals = self._pratio_curve_def
self._pratio_curve = defvals

self._pratio_const = self._calc_pratio_constant()

print('P-Ratio values have been modified.')
print('')

def set_goal_bodyfat(self, bodyfat, init_bodyfat=None, init_weight=None, const_loss=None):
'''
Change GOAL body fat from imported value
'''
self._goalBodyfat = bodyfat

if not const_loss:
const_loss = self._const_loss

def present_results(pratio, const):
pratio.insert(0, round(init_weight - pratio[0], 2))  # total weight lost
pratio.append(round(pratio[1] * (1 - bodyfat/100), 2))  # final lbm
pratio.append(round(pratio[2] - init_weight * (1 - init_bodyfat/100), 2))  # change in lbm
pratio.append(round(abs(pratio[3]*100/pratio[0]), 2))  # effective % lbm loss

const.insert(0, round(init_weight - const[0], 2))  # total weight lost
const.append(round(const[1] * (1 - bodyfat/100), 2))  # final lbm
const.append(round(const[2] - init_weight * (1 - init_bodyfat/100), 2))  # change in lbm
const.append(const_loss)  # effective % lbm loss

no_loss = [round((init_weight * (1 - init_bodyfat/100))/(1 - bodyfat/100), 2)]
no_loss.append(init_weight - no_loss[0])

self.plot_pratio()

print('Goal: To drop from ' + str(init_weight) + ' ' + self._wgtunit + ' and ' + str(init_bodyfat) + '% bodyfat to ' + str(bodyfat) + '% bodyfat')
print('')
print('Using the P-Ratio curve to calculate LBM loss during your diet:')
print('Total Weight Lost: ' + str(pratio[0]))
print('Final Weight: ' + str(pratio[1]))
print('Final LBM: ' + str(pratio[2]))
print('Change in LBM: ' + str(pratio[3]))
print('Effective Rate of LBM Loss (%): ' + str(pratio[4]))
print('')
print('Using a flat 20% loss rate to calculate LBM loss during your diet:')
print('Total Weight Lost: ' + str(const[0]))
print('Final Weight: ' + str(const[1]))
print('Final LBM: ' + str(const[2]))
print('Change in LBM: ' + str(const[3]))
print('Effective Rate of LBM Loss (%): ' + str(const[4]))
print('')
print('If you can manage to lose 100% fat, congratulations! You will reach your goal in only ' +
str(no_loss[1]) + ' ' + self._wgtunit + ' at a final weight of ' + str(no_loss[0]) + ' ' +
self._wgtunit + '.')
print('')

if not init_bodyfat:
init_bodyfat = self._bodyfat
if not init_weight:
init_weight = self._weight

# p-ratio method, answer gets rounded in pratio weight fn
pratio_final_weight = [self._calc_pratio_weight(bodyfat, init_bodyfat, init_weight)]

# const loss method
const_final_weight = [round(init_weight - init_weight*(bodyfat/100-init_bodyfat/100)/(bodyfat/100-1+const_loss/100), 2)]

present_results(pratio_final_weight, const_final_weight)

'''
Optionally include a new goal weight to base calculations on
'''
if not goalweight:
goalweight = self._goalWeight
end_date = datetime.datetime.strptime(date, '%Y-%m-%d').date()

if end_date < datetime.date.today():
print("Please choose an end date in the future relative to today")
return None

# days_til - 1 to discount day of and the final day, so plan to start diet tomorrow and wake up goal weight morning of
days_til = (end_date - datetime.date.today()).days - 1

if self._imperial:
mult = 3500.0
else:
mult = 3500/0.4536
daily_deficit = int((self._weight - goalweight) * mult / days_til)

weight_start = self._weight
self._weight = goalweight
tdee_final = self._calc_tdee(summary=False)
self._weight = weight_start
delta_tdee = self._tdee - tdee_final

print('------------------------------------------------------------')
print('Goal: Lose ' + str(self._weight - goalweight) + ' ' + self._wgtunit + ' by ' + date + '.')
print('')
print('The following calculations assume you begin the caloric deficit tomorrow, and wake up on deadline '
print('Please keep in mind that there can be major fluctuations in weight on any particular day up to and '
'including the goal date due to water retention and other cyclical reasons. If "weight" and not '
'"weight loss" is of prime importance, begin eating lower carbohydrates in the final week.')
print('')
print('You require an initial daily deficit of ' + str(daily_deficit) + ' calories (this is NOT how much to eat).')
print('Using your theoretical TDEE of ' + str(self._tdee) + ', you should start with a daily goal of under '
+ str(self._tdee - daily_deficit) + ' calories, tapering down to ' + str(self._tdee - daily_deficit
- delta_tdee) + ' by the final day.')
if self._otdee:
print('Using your observed TDEE of ' + str(self._otdee) + ', you should start with a daily goal of under '
+ str(self._otdee - daily_deficit) + ' calories, tapering down to ' + str(self._otdee - daily_deficit
- delta_tdee) + ' by the final day.')
print('------------------------------------------------------------')
print('')

# plot predicted cals/weight over time
x = [datetime.datetime.today() + datetime.timedelta(days=1) + datetime.timedelta(days=x) for x in range(days_til+1)]
yotdee = np.linspace(self._otdee + daily_deficit, self._otdee + daily_deficit - delta_tdee, days_til+1)
ytdee = np.linspace(self._tdee + daily_deficit, self._tdee + daily_deficit - delta_tdee, days_til+1)
ywgt = np.linspace(self._weight, goalweight, days_til+1)

# add noise to weight signal based on std of empirical data
# np.random.seed(0)
ywgt[1:-1] += np.random.normal(loc = 0, scale = self._daily.Weight.std(), size = days_til - 1)

fig = plt.figure()
ax1.plot(x, ywgt, 'black', lw=3)
ax1.set_xlabel('Date')
ax1.set_ylabel('Weight (' + self._wgtunit + ')')
plt.xticks(rotation=45)
ax2 = ax1.twinx()

ax2.set_ylabel('Calorie Goal')
ax2.set_xlim(xmin=x[0], xmax=x[-1])
ax2.set_ylim(ymin=(min(ytdee[-1], yotdee[-1]) - 100), ymax=(max(ytdee[0], yotdee[0]) + 100))

# aligning twin axes
nticks = 11
ax1.yaxis.set_major_locator(matplotlib.ticker.LinearLocator(nticks))
ax2.yaxis.set_major_locator(matplotlib.ticker.LinearLocator(nticks))

ax2.plot(0, 0, color = 'black', lw=3, label='Predicted Weight Change')
ax2.plot(x, yotdee, 'blue', lw=2, alpha=0.5, label='Prescribed Calorie Goal via oTDEE')
ax2.plot(x, ytdee, 'red', lw=2, alpha=0.5, label='Prescribed Calorie Goal via TDEE')
ax2.legend()
plt.show()

def set_weight(self, weight):
'''
Change CURRENT weight from imported value
'''
self._weight = weight

print('Weight has been modified.')
print('')

def plot_calories(self, timeframe='daily', window_size=7):
'''
Plot caloric intake over time based on input timeframe and compare with your goal,
empirical mean, and a running average of size window_size
'''
mfp_df = self._check_timeframe(timeframe)
mv_avg = pd.stats.moments.rolling_mean(mfp_df.Calories, window=window_size)
mv_avg_label = 'Moving Average W = ' + str(window_size)
calcmean = int(mfp_df['Calories'].mean())
mean_label = "Mean = " + str(calcmean)
goal_label = "Goal = " + str(self._calGoal)
plt.bar(mfp_df.index, mfp_df.Calories, alpha = 0.2, width=1.0)
plt.ylim(ymin=500)
plt.axhline(calcmean, color='r', label=mean_label, linewidth=3)
plt.axhline(self._calGoal, color='g', label=goal_label, linewidth=3)
plt.plot(mv_avg.index, mv_avg, 'b-', linewidth=3, label=mv_avg_label)
plt.legend(loc=8)
plt.xlabel('Date')
plt.ylabel('Calories')
title_label = ('Caloric Intake from ' + str(mfp_df.index[0].month) + '/' + str(mfp_df.index[0].year)
+ ' to ' + str(mfp_df.index[-1].month) + '/' + str(mfp_df.index[-1].year))
plt.title(title_label)
plt.xticks(rotation=45)
plt.show()

def plot_bodyfat(self, timeframe = 'daily', window_size=7):
'''
Plot body fat over time based on input timeframe and compare with your goal,
empirical mean, and a running average of size window_size
'''
if self._goalBodyfat == None:
self._goalBodyfat = self._bodyfat
mfp_df = self._check_timeframe(timeframe)
mv_avg = pd.stats.moments.rolling_mean(mfp_df.Bodyfat, window=window_size)
mv_avg_label = 'Moving Average W = ' + str(window_size)
calcmean = int(mfp_df['Bodyfat'].mean())
mean_label = "Mean = " + str(calcmean)
goal_label = "Goal = " + str(self._goalBodyfat)
plt.bar(mfp_df.index, mfp_df.Bodyfat, alpha = 0.2, width = 1.0)
plt.axhline(calcmean, color='r', label=mean_label, linewidth=3)
plt.axhline(self._goalBodyfat, color='g', label=goal_label, linewidth=3)
plt.ylim(ymin=(max([3, self._goalBodyfat - 5])))
plt.plot(mv_avg.index, mv_avg, 'b-', linewidth=3, label=mv_avg_label)
plt.legend(loc=8)
plt.xlabel('Date')
plt.ylabel('Bodyfat')
title_label = ('Bodyfat from ' + str(mfp_df.index[0].month) + '/' + str(mfp_df.index[0].year)
+ ' to ' + str(mfp_df.index[-1].month) + '/' + str(mfp_df.index[-1].year))
plt.title(title_label)
plt.xticks(rotation=45)
plt.show()

def plot_macro(self, timeframe='daily', macro='Protein', window_size=7):
'''
Plot macronutrient intake for a particular macro over time based on input timeframe
and compare with your goal, empirical mean, and a running average of size window_size

Valid macros = 'protein', 'carbs', 'fat'
'''
mfp_df = self._check_timeframe(timeframe)
macro = macro.lower().capitalize()
try:
mv_avg = pd.stats.moments.rolling_mean(mfp_df[macro], window=window_size)
if macro == 'Carbs':
goalVal = self._carbGoal
elif macro == 'Fat':
goalVal = self._fatGoal
else:
goalVal = self._proGoal
except:
macro = 'Protein'
goalVal = self._proGoal
mv_avg = pd.stats.moments.rolling_mean(mfp_df[macro], window=window_size)
mv_avg_label = 'Moving Average W = ' + str(window_size)
calcmean = int(mfp_df[macro].mean())
mean_label = "Mean = " + str(calcmean) + 'g'
goal_label = "Goal = " + str(goalVal) + 'g'
plt.bar(mfp_df.index, mfp_df[macro], alpha=0.2, width=1.0)
plt.axhline(calcmean, color='r', label=mean_label, linewidth=3)
plt.axhline(goalVal, color='g', label=goal_label, linewidth=3)
plt.plot(mv_avg.index, mv_avg, 'b-', linewidth=3, label=mv_avg_label)
plt.legend(loc=8)
plt.xlabel('Date')
plt.ylabel(macro + ' (g)')

title_label = (macro + ' Intake from ' + str(mfp_df.index[0].month) + '/' + str(mfp_df.index[0].year)
+ ' to ' + str(mfp_df.index[-1].month) + '/' + str(mfp_df.index[-1].year))
plt.title(title_label)
plt.xticks(rotation=45)
plt.show()

def plot_macro_pie(self, timeframe='monthly', date=None):
'''
Plot macronutrient pie showing % of caloric intake over given time period
Dates must be "first of" i.e. if timeframe = 'monthly', date must be the first of the month
'''
mfp_df = self._check_timeframe(timeframe)
if date == None:
date = pd.datetime(self._daily.index[-1].year, self._daily.index[-1].month, 1)
try:
slices = [mfp_df.PMacro.loc[date], mfp_df.FMacro.loc[date], mfp_df.CMacro.loc[date]]
except:
print('No data catalogued under ' + date + '. If using a timeframe larger than daily, the data may be '
'indexed by the start of the week/month/year.')
return
slicecols = ['lightskyblue', 'lightcoral', 'gold']
plt.figure(figsize=(10,10))
pie = plt.pie(slices, labels=['Protein', 'Fat', 'Carbs'], autopct='%1.1f%%', colors=slicecols, startangle=90)
for pie_label in pie[1]:
pie_label.set_fontsize(15)
for pie_wedge in pie[0]:
pie_wedge.set_edgecolor('white')
datedict = {'daily': '', 'weekly': 'week starting ', 'monthly': 'month starting ', 'yearly': 'year starting '}
plt.title('Macros on ' + datedict[timeframe.lower()] + date)
plt.show()

def plot_weight(self, timeframe='daily', showgoal=False):
'''
Plot weight over time based on input timeframe, optionally include and
adjust axis limits to show goal weight
Compares raw data to regressions over multiple time periods
'''
mfp_df = self._check_timeframe(timeframe)
# make copy of df so we don't modify the version outside of the fn
mfp_df = pd.DataFrame(mfp_df.Weight)

# attempt to remedy missing weight value... needs testing, especially extradays stuff
mfp_df.dropna(axis=0, how='any', inplace=True)

# plot weight vs date and set up axes
plt.plot(mfp_df.index, mfp_df.Weight, lw=2, label='Raw Weight Data')
plt.ylabel('Weight')
plt.xlabel('Date')
plt.title('Weight over Time')
plt.xticks(rotation=45)

# plot goal weight line if desired and set proper ylim
if showgoal:
plt.axhline(y=self._goalWeight, label=('Goal Weight: ' + str(self._goalWeight) + ' ' + self._wgtunit))
if self._goalWeight > mfp_df.Weight[-1]:
plt.ylim(ymax=self._goalWeight+5)
else:
plt.ylim(ymin=self._goalWeight-5)

# calculations to predict 1/3 of the graph into the future

# pandas doesn't automatically convert time series to integer predictor...

# pandas also doesn't allow model predicting with new data... so... do it manually
numdays = np.arange((mfp_df.index[-1] - mfp_df.index[0]).days + extradays)
y_pred = numdays*regress.beta[0]+ regress.beta[1]
y_pred_index = [mfp_df.index[0] + datetime.timedelta(days=x) for x in range(len(numdays))]
plt.plot(y_pred_index, y_pred, label=('All data: ' + str(round(regress.beta[0]*7.0, 2)) + self._wgtunit + '/week'))

# repeat regression for last 14 days data

plt.plot(y2_pred_index, y2_pred, label=('Last 14: ' + str(round(regress2.beta[0]*7.0, 2)) + self._wgtunit + '/week'))

print('Predicted Weight in ' + str(extradays) + ' days (' + str(y_pred_index[-1].date()) + '):')
print(str(round(y_pred[-1], 1)) + ' based on all data')
print(str(round(y2_pred[-1], 1)) + ' based on last 14 days')

print('Total weight lost since ' + str(mfp_df.index[0].date()) + ': ' + str(mfp_df.Weight[-1] - mfp_df.Weight[0])
+ ' ' + self._wgtunit + '.')

goalwgtdays = int((self._goalWeight - regress2.beta[1]) / regress2.beta[0])
goalwgtday = mfp_df.index[0] + datetime.timedelta(days=goalwgtdays)

print('If your weight change proceeds consistent with the last 14 days, you will reach your goal weight of',
str(self._goalWeight), self._wgtunit, 'in approximately', str((goalwgtday - mfp_df.index[-1]).days), 'days ('
+ str(goalwgtday.date()) + ').')

plt.legend()
plt.show()

def plot_macro_weight(self, timeframe='daily', macro='carbs'):
'''
Plots the immediate effect of a particular macronutrient intake on the next day's weight
timeframe: str, 'daily', 'weekly', 'monthly', 'yearly'
macro: str, 'carbs', 'protein', 'fat', 'sodium', ...
'''
mfp_df = self._check_timeframe(timeframe)
macro = macro.lower().capitalize()

mfp_df = mfp_df[['Weight', macro]]
mfp_df['WeightDiff'] = mfp_df.Weight.diff(1)

# shift weight results up one day so calories of a day/period corresponds to weight of the morning after
mfp_df[['Weight', 'WeightDiff']] = mfp_df[['Weight', 'WeightDiff']].shift(-1)

# attempt to remedy missing weight value... needs testing
mfp_df.dropna(axis=0, how='any', subset=['WeightDiff', macro], inplace=True)

fig = plt.figure()
ax1.scatter(mfp_df.index, mfp_df.WeightDiff, color='black', alpha = 1)
ax1.plot(mfp_df.index, mfp_df.WeightDiff, color='red', lw=5, alpha = 0.5, label = "Change in Weight")
ax1.set_xlabel('Date')
ax1.set_ylabel('Weight')
plt.xticks(rotation=45)
plt.xlim(xmin=mfp_df.index[0], xmax=mfp_df.index[-1])
ax2 = ax1.twinx()

# aligning twin axes
nticks = 11
ax1.yaxis.set_major_locator(matplotlib.ticker.LinearLocator(nticks))
ax2.yaxis.set_major_locator(matplotlib.ticker.LinearLocator(nticks))

# checking if macro input a legitimate input
try:
ax2.bar(mfp_df.index, mfp_df[macro], alpha=0.4, label = "Macro Intake")
except:
macro = 'Protein'
ax2.bar(mfp_df.index, mfp_df[macro], alpha=0.4)
ax2.set_ylabel(macro + ' (g)')
ax2.set_xlim([mfp_df.index[0], mfp_df.index[-1]])

corr = str(round(mfp_df[macro].corr(mfp_df['WeightDiff']), 2))

plt.title('Effect of ' + macro + ' on Immediate Weight, Correlation = ' + corr)
plt.legend()
plt.show()

def plot_normalized_macros(self, timeframe='weekly'):
'''
Delivers the ratio of actual intake/goal intake
'''
mfp_df = self._check_timeframe(timeframe)
# make copy so we don't modify df outside fn
mfp_df = mfp_df.copy()

# normalize macros actual/goal values
mfp_df['ProNorm'] = mfp_df['Protein']/self._proGoal
mfp_df['FatNorm'] = mfp_df['Fat']/self._fatGoal
mfp_df['CarbsNorm'] = mfp_df['Carbs']/self._carbGoal

# to arrange bars side by side
bar_width = 0.2
x_axis = np.arange(len(mfp_df.index))

# aligning x labels with middle bar placement
if len(mfp_df) > 40:
plt.xticks((x_axis+2*bar_width)*5, mfp_df.index.date[::5], rotation=90)
else:
plt.xticks((x_axis+2*bar_width), mfp_df.index.date, rotation=45)

plt.bar(x_axis, mfp_df.ProNorm, width=bar_width, color='lightskyblue', label='Protein')
plt.bar(x_axis + bar_width, mfp_df.FatNorm, width=bar_width, color='lightcoral', label='Fat')
plt.bar(x_axis + 2*bar_width, mfp_df.CarbsNorm, width=bar_width, color='gold', label='Carbs')
plt.axhline(y=1, color='g', lw=4, alpha = 0.5)
plt.xlabel('Date')
plt.ylabel('Normalized Intake of Macro')
plt.title('Ratio Intake/Goal Intake, ideal = 1')
plt.legend(loc=9)
plt.show()

def plot_otdee(self, window_size=7): #timeframe
'''
Plots observed TDEE over time and compares with the mean over several time periods
as well as a smoothed line by window_size
'''
#mfp_df = self._check_timeframe(timeframe)
otdeelabel = 'oTDEE, ' + str(window_size) + ' day window'
plt.plot(self._otdeeDF.index, self._otdeeDF.oTDEE, label=otdeelabel, color = 'c')
plt.xticks(rotation=45)
meanotdee = int(self._otdeeDF.oTDEE.mean())
meanotdee14 = int(self._otdeeDF.oTDEE[-14:].mean())
plt.plot(self._otdeeDF.index, self._otdeeFit.predict(), color='k', lw=3, label=(str(int(self._otdeeFit.predict()[-1])) + ': Regression, all data'))
plt.plot(self._otdeeDF.index[-14:], self._otdeeFit2.predict(), color='r', lw=3, label=(str(int(self._otdeeFit2.predict()[-1])) + ': Regression, last 14'))
plt.axhline(y=meanotdee, label=(str(meanotdee) + ': Mean oTDEE, all data'), color='m', lw=2)
plt.axhline(y=meanotdee14, label=(str(meanotdee14) + ': Mean oTDEE, last 14 days'), color='y', lw=2)
plt.xlabel('Date')
plt.ylabel('Observed TDEE')
plt.title('Observed TDEE using ' + str(window_size) + ' day window')
plt.legend()
plt.show()

def plot_pratio(self):
'''
Plot P-ratio curves, includes defalut female, male, constant, and user input custom model
'''
def func(B, a, b):
return 1/(a+b*B/100)

# male default function
xm = np.linspace((self._pratio_curve_def[:, 0]*100)[0],(self._pratio_curve_def[:, 0]*100)[-1], 100)
ym = func(xm, *self._pratio_male_def)

# female default function
xf = np.linspace(((self._pratio_curve_def[:, 0] + 0.07)*100)[0], ((self._pratio_curve_def[:, 0] + 0.07)*100)[-1], 100)
yf = func(xf, *self._pratio_female_def)

# user input function (using set_pratio_curve
xcustom = np.linspace((self._pratio_curve[:, 0]*100)[0], (self._pratio_curve[:, 0]*100)[-1], 100)
ycustom = func(xcustom, *self._pratio_const)

xcustompts = (self._pratio_curve[:, 0]*100)
ycustompts = func(xcustompts, *self._pratio_const)

# plotting all 3 to compare
plt.plot(xm, ym, lw=3, color='black', label="Default Male Model")
plt.plot(xf, yf, lw=3, color='red', label="Default Female Model")
plt.plot(xcustom, ycustom, lw=3, color='green', label="Current User Model")
plt.scatter(xcustompts, ycustompts, s=150, marker='+', color='green')
plt.axhline(y=self._const_loss/100, color='blue', lw=3, label='Constant ' + str(self._const_loss) + '% Loss')
plt.xlim(1, 45)
plt.ylim(ymax=1.05, ymin=0)
plt.xlabel('Bodyfat %')
plt.ylabel('P-Ratio aka % of weight loss that will be LBM')
plt.title("P-Ratio LBM Loss Curves")
plt.legend()

plt.show()

####### JEFIT SPECIFIC #######

def get_exercise_list(self):
print(self._lifts.ExerciseName.unique())

def _check_timeframe_jefit(self, timeframe, body=False):
timeframe = timeframe.lower()
if body:
timeframedict = {'daily': self._lifts_daily, 'weekly': self._lifts_weekly, 'monthly': self._lifts_monthly, 'yearly': self._lifts_yearly}
else:
timeframedict = {'daily': self._lifts_daily_no_body, 'weekly': self._lifts_weekly_no_body, 'monthly': self._lifts_monthly_no_body, 'yearly': self._lifts_yearly_no_body}
if timeframe not in timeframedict.keys():
print('Timeframe not recognized as daily/weekly/monthly/yearly. Defaulting to daily, please try again.')
return timeframedict['daily']
return timeframedict[timeframe].copy()

def _create_historical_performance(self):
historical_performance = self._lifts.reset_index().groupby(['Date', 'ExerciseName']).apply(max).drop(['Date',
'Weight', 'Reps', 'Sets', 'ExerciseName', 'Bodypart', 'Set1Time', 'Set2Time', 'CardioCalsBurned', 'CardioDistance',
'CardioSpeed', 'CardioTime'], axis=1)

return historical_performance

def _calc_max_prs(self):
# has all time max for each exercise and date achieved
max_prs = self._lifts.reset_index().groupby(['ExerciseName']).agg({'1RM': 'max'}).rename(columns={'1RM':'1RMRecord'})
max_prs = pd.merge(self._lifts.reset_index()[['Date','ExerciseName','1RM']], max_prs.reset_index(), how='left', on=['ExerciseName'])
max_prs = max_prs[max_prs['1RM'] == max_prs['1RMRecord']].drop_duplicates('ExerciseName')
max_prs = max_prs.drop(['1RMRecord'], axis=1).set_index(['Date']).sort('ExerciseName')

return max_prs

def _calc_lifts_daily(self):
lifts_daily = self._lifts.reset_index().groupby(['Date', 'Bodypart']).aggregate({'Volume': 'sum', 'Sets': 'sum', 'Reps': 'sum'})
lifts_daily_no_body = self._lifts.reset_index().groupby(['Date']).aggregate({'Volume': 'sum', 'Sets': 'sum', 'Reps': 'sum'})

return lifts_daily, lifts_daily_no_body

def _calc_lifts_weekly(self):
lifts_weekly = self._lifts.copy().reset_index()
lifts_weekly['Date'] = pd.to_datetime(lifts_weekly['Date'])

# for future index/groupby purposes
lifts_weekly['Date2'] = lifts_weekly['Date']

# index by start-date of week i.e Sunday 6/2 would include 6/2 to Saturday 6/8
lifts_weekly = lifts_weekly.set_index(['Date']).groupby(['Date2', 'Bodypart']).resample('W', how='sum', label='left')

# cleaning up some columns

lifts_weekly = lifts_weekly.reset_index().set_index(['Date']).drop(['Date2', 'Weight', '1RM'], axis=1)

lifts_weekly = lifts_weekly.reset_index().groupby(['Date', 'Bodypart']).aggregate(sum)

lifts_weekly_no_body = lifts_weekly.reset_index().groupby(['Date']).aggregate(sum)

return lifts_weekly, lifts_weekly_no_body

def _calc_lifts_monthly(self):
lifts_monthly = self._lifts.copy().reset_index()
lifts_monthly['Date'] = pd.to_datetime(lifts_monthly['Date'])

# for future index/groupby purposes
lifts_monthly['Date2'] = lifts_monthly['Date']

# index by start-date of month i.e. 6/1 includes 6/1-6/30 data
lifts_monthly = lifts_monthly.set_index(['Date']).groupby(['Date2', 'Bodypart']).resample('MS', how='sum', label='left')

# cleaning up some columns
lifts_monthly = lifts_monthly.reset_index().set_index(['Date']).drop(['Date2', 'Weight', '1RM'], axis=1)

lifts_monthly = lifts_monthly.reset_index().groupby(['Date', 'Bodypart']).aggregate(sum)

lifts_monthly_no_body = lifts_monthly.reset_index().groupby(['Date']).aggregate(sum)

return lifts_monthly, lifts_monthly_no_body

def _calc_lifts_yearly(self):
lifts_yearly = self._lifts.copy().reset_index()
lifts_yearly['Date'] = pd.to_datetime(lifts_yearly['Date'])

# for future index/groupby purposes
lifts_yearly['Date2'] = lifts_yearly['Date']

# index by start-date of year i.e. 2014 includes 1/1/14 - 12/31/14
lifts_yearly = lifts_yearly.set_index(['Date']).groupby(['Date2', 'Bodypart']).resample('AS', how='sum', label='left')

# cleaning up some columns
lifts_yearly = lifts_yearly.reset_index().set_index(['Date']).drop(['Date2', 'Weight', '1RM'], axis=1)

lifts_yearly = lifts_yearly.reset_index().groupby(['Date', 'Bodypart']).aggregate(sum)

lifts_yearly_no_body = lifts_yearly.reset_index().groupby(['Date']).aggregate(sum)

return lifts_yearly, lifts_yearly_no_body

def plot_jefit_historical_performance(self, exercisename, show_weight=True):
# has maxes for each exercise on each day
historical_df = self._historical_performance
exercise_history = pd.DataFrame(historical_df.loc[historical_df.index.get_level_values('ExerciseName') == exercisename])
date_index = exercise_history.index.get_level_values('Date')
all_time_pr, all_time_date = round(exercise_history['1RM'].max(),2), exercise_history['1RM'].idxmax()[0]

fig = plt.figure()
ax1.scatter(date_index, exercise_history['1RM'], color='red', lw=3, label='1RM Performance')
plt.xticks(rotation=45)
ax1.set_xlim(xmax=date_index[-1], xmin=date_index[0])
ax1.set_ylim(ymax=all_time_pr + 5, ymin=exercise_history['1RM'].min() - 5)
ax1.set_ylabel('Calculated 1 RM')
ax1.set_xlabel('Date')

if show_weight:
ax2 = ax1.twinx()
ax2.plot(self._daily.index, self._daily.Weight, color='black', label='Weight', lw=2)
ax2.set_ylabel('Weight')
ax2.set_xlim(xmax=date_index[-1], xmin=date_index[0])
ax2.set_ylim(ymax=self._daily.Weight.max(), ymin=self._daily.Weight.min())

# aligning twin axes
nticks = 11
ax1.yaxis.set_major_locator(matplotlib.ticker.LinearLocator(nticks))
ax2.yaxis.set_major_locator(matplotlib.ticker.LinearLocator(nticks))
ax1.axhline(y=all_time_pr, lw=3, color='green', label=('1RM ' + str(all_time_pr) + ' set on ' + str(all_time_date.date())))
plt.title('Historical Performance on ' + exercisename + ' (1RM)')
ax1.legend(loc=8)
ax2.legend(loc=9)
plt.show()

def plot_jefit_bodypart_pie(self, timeframe='monthly', date=None):
'''
Plots a pie chart distribution of exercises done by body part. Requires a date
corresponding to Sunday if weekly timeframe, first of the month if monthly,
and Jan 1st if annually
'''
if date == None:
date = pd.datetime(self._daily.index[-1].year, self._daily.index[-1].month, 1)
def make_autopct(values):
# displays % (sets) for each slice in pie
def my_autopct(pct):
total = sum(values)
val = int((pct*total/100.0) + 0.5)
return '{p:.2f}%  ({v:d})'.format(p=pct,v=val)
return my_autopct

lifts_df = self._check_timeframe_jefit(timeframe, body=True)
day = lifts_df.loc[(lifts_df.index.get_level_values('Date') == date)]
if day.empty:
print('Body part pie chart failed. No exercise data on this date. Please try again.')
return
day = day.reset_index()
slices = day.Sets
plt.figure(figsize=(10,10))
cmap = plt.cm.Pastel1_r
colors = cmap(np.linspace(0., 1., len(slices)))
labels = day.Bodypart
pie = plt.pie(slices, colors=colors, labels=labels, autopct=make_autopct(slices))
for pie_label in pie[1]:
pie_label.set_fontsize(15)
for pie_wedge in pie[0]:
pie_wedge.set_edgecolor('white')
datedict = {'daily': '', 'weekly': 'week starting ', 'monthly': 'month starting ', 'yearly': 'year starting '}
plt.title('Sets per body part on ' + datedict[timeframe.lower()] + date)
plt.show()

def plot_jefit_volume_sets(self, timeframe='weekly'):
'''
Compares volume and sets over time.
'''
lifts = self._check_timeframe_jefit(timeframe)
width = 0.33
idx = list(range(len(lifts.index)))
idx2 = [num+width for num in idx]

fig, ax1 = plt.subplots()

rects1 = ax1.bar(idx, lifts.Volume, width, label='Total Volume')
ax2 = plt.twinx()
rects2 = ax2.bar([num+width for num in idx], lifts.Sets, width, color='red', label = 'Total Sets')

ax1.set_ylabel("Total Volume")
ax1.set_title('Volume/Sets vs Time over ' + timeframe.capitalize() + ' Timeframe')

ax2.set_ylabel('Total Sets')

# aligning twin axes
nticks = 11
ax1.yaxis.set_major_locator(matplotlib.ticker.LinearLocator(nticks))
ax2.yaxis.set_major_locator(matplotlib.ticker.LinearLocator(nticks))

plt.legend((rects1[0], rects2[0]), ('Volume', 'Sets'), loc=0)
ax1.set_xticklabels(lifts.index.date)

fig.autofmt_xdate()
plt.show()
```

## List of the variables contained within the class, methods exist to access and modify those necessary

In [6]:
```variables = pd.Series(list(user.__dict__.keys())).sort_values(inplace=False)
print(variables)
```
```22                  _activity
38                       _age
20                   _bodyfat
42                   _calGoal
35                  _carbGoal
12                 _carbRatio
37                _const_loss
1                      _daily
5                    _fatGoal
14                  _fatRatio
17                    _fatlbs
26                    _gender
6                _goalBodyfat
29                _goalWeight
46                    _height
11                   _hgtunit
18    _historical_performance
24                  _imperial
23                     _lifts
32               _lifts_daily
40       _lifts_daily_no_body
4              _lifts_monthly
0      _lifts_monthly_no_body
3               _lifts_weekly
31      _lifts_weekly_no_body
45              _lifts_yearly
41      _lifts_yearly_no_body
44                   _max_prs
9                    _monthly
34                     _otdee
21                   _otdeeDF
33                  _otdeeFit
39                 _otdeeFit2
27              _pratio_const
13              _pratio_curve
36          _pratio_curve_def
8          _pratio_female_def
2            _pratio_male_def
15                   _proGoal
25                  _proRatio
28                   _profile
43                      _tdee
10                    _weekly
30              _weeklyChange
16                    _weight
7                    _wgtunit
19                    _yearly
dtype: object
```

## List of internal methods calculated upon initialization or upon using set methods

In [7]:
```fns = pd.Series(list(vars(FitnessLogs))).sort(inplace=False)
print(fns[5:20])
```
```38              _calc_activity_level
18                    _calc_calories
9                  _calc_lifts_daily
31               _calc_lifts_monthly
12                _calc_lifts_weekly
25                _calc_lifts_yearly
20                     _calc_max_prs
21                       _calc_otdee
29             _calc_pratio_constant
41               _calc_pratio_weight
0                         _calc_tdee
14                  _check_timeframe
40            _check_timeframe_jefit
15    _create_historical_performance
22                     _create_means
dtype: object
```

## Exploratory plot methods available

In [8]:
```print(fns[21:33])
```
```6                          plot_bodyfat
13                        plot_calories
24              plot_jefit_bodypart_pie
16    plot_jefit_historical_performance
7                plot_jefit_volume_sets
1                            plot_macro
4                        plot_macro_pie
36                    plot_macro_weight
3                plot_normalized_macros
32                           plot_otdee
19                          plot_pratio
39                          plot_weight
dtype: object
```

## Methods to set or change values imported from Jefit or MyFitnessPal

In [9]:
```print(fns[33:])
```
```5     set_activitylevel
27          set_bodyfat
42     set_goal_bodyfat
17    set_goal_calories
28      set_goal_weight
34           set_macros
43            set_otdee
37     set_pratio_curve
26     set_weeklychange
2            set_weight
dtype: object
```

## And any methods to get relevant lists of potential inputs

In [10]:
```print(fns.iloc[20])
```
```get_exercise_list
```

## Fitness class will initialize with a brief summary regarding your goals and current stats

In [11]:
```user = FitnessLogs(jefit=True, start_date='2013-01-01', end_date='2015-08-18')
```
```There are 388 days missing from your data spanning 823 days (47%).
This greatly affects the value of the predicted result which is based on all history; missing weight or caloric logs cannot be accurately estimated (as missing days are often a result of going off diet). It is recommended to use start and end dates without such large gaps.
Data will be imputed based on current limits in the meantime

Predicted TDEE for a male weighing 160.4 lbs and measuring 68.0 inches at age 30.0 with an activity level described as sedentary is 2261.

Your caloric goal is currently set to 1500.
This value is reasonably close to our recommendation of 1369 daily calories for a weekly loss of 2.0 lbs.

With a theoretical TDEE of 2261 you will lose 1.522 lbs per week.
With the empirically calculated oTDEE of 2369 you will lose 1.738 lbs per week.

The maximal daily caloric deficit for fat loss is approximately 31.4 cals per lb of fat. (See http://www.ncbi.nlm.nih.gov/pubmed/15615615)
With 26.15 lbs of fat, you can afford a maximum deficit of 821 cals/day, or 1.642 lbs/week.

Caloric deficit required to lose 2.0 lbs per week is greater than what would reasonably come from fat stores.
As such, you will likely be losing lean body mass (muscle), hindering your efforts to lower bodyfat aka "tone".

It is recommended to lower the weekly weight loss goal to a maximum of 1.642 lbs.
Current macronutrient goals are 168/41/112 P/F/C in grams, with proportions 45.0/25.0/30.0.
Your protein goal is at least 0.82g/lb of body weight, appropriate according to modern research. (See http://www.ncbi.nlm.nih.gov/pubmed/22150425)

```

## Plot Caloric intake over time by chosen timeframe, apply optional smoothing window, in current date range there is a lot of missing data that had to be imputed, better to try with a smaller range

In [12]:
```user.plot_calories('daily', window_size = 7)
```

## Smaller date range with less missing data

In [13]:
```user = FitnessLogs(jefit=True, start_date='2015-06-18', end_date='2015-08-18')
```
```Predicted TDEE for a male weighing 160.4 lbs and measuring 68.0 inches at age 30.0 with an activity level described as sedentary is 2261.

Your caloric goal is currently set to 1500.
This value is reasonably close to our recommendation of 1369 daily calories for a weekly loss of 2.0 lbs.

With a theoretical TDEE of 2261 you will lose 1.522 lbs per week.
With the empirically calculated oTDEE of 2369 you will lose 1.738 lbs per week.

The maximal daily caloric deficit for fat loss is approximately 31.4 cals per lb of fat. (See http://www.ncbi.nlm.nih.gov/pubmed/15615615)
With 26.15 lbs of fat, you can afford a maximum deficit of 821 cals/day, or 1.642 lbs/week.

Caloric deficit required to lose 2.0 lbs per week is greater than what would reasonably come from fat stores.
As such, you will likely be losing lean body mass (muscle), hindering your efforts to lower bodyfat aka "tone".

It is recommended to lower the weekly weight loss goal to a maximum of 1.642 lbs.
Current macronutrient goals are 168/41/112 P/F/C in grams, with proportions 45.0/25.0/30.0.
Your protein goal is at least 0.82g/lb of body weight, appropriate according to modern research. (See http://www.ncbi.nlm.nih.gov/pubmed/22150425)

```

## Supposing imported body fat or weight was erroneous, or you wanted to change your goal stats:

In [14]:
```user.set_weight(175)
user.set_bodyfat(16)
```
```Weight has been modified.

Bodyfat has been modified.

```

## If you had a particular deadline to meet (say you want to hit your college weight by a wedding date) you can use set_deadline. Note that noise is artificially added based on standard deviation of your empirical weight logs.

In [15]:
```college_wgt = 145
wedding_date = '2018-12-31'
# you can get the same plot through the set_goal_weight or  plot_weight methods
```
```------------------------------------------------------------
Goal: Lose 30 lbs by 2018-12-31.

The following calculations assume you begin the caloric deficit tomorrow, and wake up on deadline morning weighing your goal weight.
Please keep in mind that there can be major fluctuations in weight on any particular day up to and including the goal date due to water retention and other cyclical reasons. If "weight" and not "weight loss" is of prime importance, begin eating lower carbohydrates in the final week.

You require an initial daily deficit of 157 calories (this is NOT how much to eat).
Using your theoretical TDEE of 2261, you should start with a daily goal of under 2104 calories, tapering down to 1920 by the final day.
Using your observed TDEE of 2369, you should start with a daily goal of under 2212 calories, tapering down to 2028 by the final day.
------------------------------------------------------------

```

## If you wanted to calculate based off body fat at a given weight using P-Ratio, you could try out a custom model (More info on P-Ratios in https://josetorres.us/data-science/using-p-ratio-to-plan-a-diet-with-python-excel/)

In [16]:
```user.set_pratio_curve(np.array([[.60, .5],
[.12, 1]]))
user.set_goal_bodyfat(10)
# you can get the same plot through the plot_pratio method
```
```P-Ratio values have been modified.

```
```Goal: To drop from 175 lbs and 16% bodyfat to 10% bodyfat

Using the P-Ratio curve to calculate LBM loss during your diet:
Total Weight Lost: 95.98
Final Weight: 79.0234375
Final LBM: 71.12
Change in LBM: -75.88
Effective Rate of LBM Loss (%): 79.06

Using a flat 20% loss rate to calculate LBM loss during your diet:
Total Weight Lost: 15.0
Final Weight: 160.0
Final LBM: 144.0
Change in LBM: -3.0
Effective Rate of LBM Loss (%): 20

If you can manage to lose 100% fat, congratulations! You will reach your goal in only 11.669999999999987 lbs at a final weight of 163.33 lbs.

```

## To get an estimate of your actual TDEE (total daily energy expenditure) aka maintenance calories using empirical data

In [17]:
```user.plot_otdee(window_size = 7)
```

## To examine body fat over time

In [18]:
```user.plot_bodyfat(timeframe='daily', window_size=7)
```

## For some more detailed nutritional analysis, you can examine a particular macronutrient over time

In [19]:
```user.plot_macro(timeframe='daily', macro='carbs', window_size=7)
```

## Or how well you’ve followed your goal macronutrient ratios (IIFYM, anyone?)

In [20]:
```user.plot_macro_pie(timeframe='monthly', date='2015-07-01')
user.plot_normalized_macros(timeframe='weekly')
```

## If you’re trying to optimize weight on a particular day, you can check what immediate effect a particular macro has on your weight (consider water retention from extra carbs, etc). In this case we find very little correlation, a lot of interesting reasons why that may be the case, whether cyclical (hormones), digestive tract retention, sodium, overall water intake, etc.

In [21]:
```user.plot_macro_weight(timeframe='daily', macro='carbs')
```