Using P-Ratio to Plan a Diet with Python and Excel

[To access the P-Ratio Excel spreadsheet associated with this, click here.]

As a very goal oriented person, it helps me to define a structure and a finish line, especially when it comes to something as mentally difficult as dieting.

When we estimate how much weight we want to lose, we ballpark without concern to the muscle that will usually decrease along with it. Many people find they reach their “goal weight” and still don’t achieve the “toned” look, because they neglected to factor this in and adjust the goal accordingly.

The P-Ratio (or partitioning ratio), as I simplify it, defines what portion of weight loss is lean body mass as a function of current body fat (see Lyle McDonald’s article for a great overview and a more precise definition).

With the P-Ratio, given initial inputs for weight and body fat, you can calculate what weight you will reach your goal body fat at.

Because the P-Ratio is a function of body fat, and body fat is changing as we diet, we need a little calculus to help. With body fat as BF weight as W, fat as F, and lean body mass as L:

(1)   \begin{equation*}  W = F + L \end{equation*}

(2)   \begin{equation*}  BF = \frac{F}{W} = \frac{F}{F+L} \end{equation*}

Taking the derivative of 2 using the quotient rule:

    \[\dot{BF} = \frac{(F+L)\dot{F} - (\dot{F}+\dot{L})F}{(F+L)^2} = \frac{F\dot{F} + L\dot{F} - F\dot{F} - F\dot{L}}{(F+L)^2} = \frac{L\dot{F} - F\dot{L}}{(F+L)^2}\]

Which we write as

(3)   \begin{equation*}  \frac{dBF}{dW} = \frac{\frac{dF}{dW}L - \frac{dL}{dW}F}{(F+L)^2} \end{equation*}

But from equation 1 we find

(4)   \begin{equation*}  \frac{dF}{dW} + \frac{dL}{dW} = 1 \end{equation*}

Rearranging equations 1 and 4 we substitute

    \[\frac{dBF}{dW} = \frac{(1-\frac{dL}{dW})L - \frac{dL}{dW}(W-L)}{W^2} = \frac{L - \frac{dL}{dW}L - \frac{dL}{dW}W + \frac{dL}{dW}L}{W^2} = \frac{L - \frac{dL}{dW}W}{W^2}\]

Using 1 and 2 we rearrange for L

(5)   \begin{equation*}  L = W - BF(W) = W(1-BF) \end{equation*}

and plug in to the above

    \[\frac{dBF}{dW} = \frac{W(1-BF)}{W^2} - \frac{W\frac{dL}{dW}}{W^2}\]

Multiplying both sides by dW and canceling the W’s in the numerator

    \[dBF = \frac{1 - BF - \frac{dL}{dW}}{W}dW\]

    \[\frac{1}{1 - BF - \frac{dL}{dW}}dBF = \frac{1}{W}dW\]

But we’ve defined \frac{dL}{dW}, how much LBM changes per change in weight, to be the P-Ratio, which is simply a function of BF f(BF). This lets us sum up the math neatly:

(6)   \begin{equation*}  \int_{BF_0}^{BF_F} \frac{1}{1 - BF - f(BF)} dBF = \int_{W_0}^{W_F}\frac{1}{W}dW \end{equation*}

What is our P-Ratio function?

Ideally one could gather enough data to perform regression and find f(BF), but accurate body fat measurements are notoriously difficult to source (see a great meta analysis here), and beyond that, there is evidence that P-Ratio varies by individual (think of this variance as a modern take on endo-meso-ecto body types).

I initially threw together a few data points based on anecdotal reports and compared a few different models.

Based on simplicity of model, I chose to use the linear model, where

(7)   \begin{equation*}  f(BF) = \frac{1}{a+b(BF)} \end{equation*}

Using Python to Optimize and Integrate

I entered the anecdotal P-Ratio values as below, with the female values shifted by 7% (approximating a man’s 10% being equivalent to a woman’s 17%, for example).

In [4]:
import pandas as pd
import numpy as np
from scipy.optimize import curve_fit
import matplotlib.pyplot as plt
import seaborn as sns
sns.set()
%matplotlib inline
plt.rcParams["figure.figsize"] = 12,8
In [5]:
defvals = 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]])

male = np.copy(defvals)

female = np.copy(defvals)
female[:, 0] += 0.07

maledf = pd.DataFrame(male, columns = ['Bodyfat', 'P-Ratio'])
femaledf = pd.DataFrame(female, columns = ['Bodyfat', 'P-Ratio'])
In [6]:
print(maledf)
   Bodyfat  P-Ratio
0     0.60    0.050
1     0.40    0.075
2     0.30    0.100
3     0.25    0.120
4     0.20    0.150
5     0.15    0.200
6     0.10    0.300
7     0.05    0.600
8     0.03    1.000

These values can be interpreted best with an example: At 30% body fat (0.30), 10% of each pound lost will be lean body mass. This number increases as you become leaner, to the point that at 5% body fat, over half of each pound of weight lost will be muscle (60%).

In [7]:
print(femaledf)
   Bodyfat  P-Ratio
0     0.67    0.050
1     0.47    0.075
2     0.37    0.100
3     0.32    0.120
4     0.27    0.150
5     0.22    0.200
6     0.17    0.300
7     0.12    0.600
8     0.10    1.000

I used SciPy’s curve_fit functionality to minimize least squares error and calculate the coefficients a and b given the P-ratio values input.

In [8]:
def func(x, a, b): # our chosen P-ratio model
    return 1/(a+b*x)

x=male[:,0] # body fat as input
y=male[:,1] # our model p-ratio as output

xf = female[:,0] 
yf = female[:,1]

popt, pcov = curve_fit(func, x, y)
poptf, pcovf = curve_fit(func, xf, yf)
In [9]:
print("Male Coefficients:", popt)
print("Female Coefficients:", poptf)
Male Coefficients: [  5.17506793e-09   3.33333332e+01]
Female Coefficients: [ -2.33333333  33.33333333]

To double check our figures, I plotted the predicted formula against our original data points

In [10]:
plt.plot(x*100, y, 'ko', label="Original Data")
x_pred = np.linspace(x[0]*100, x[-1]*100, 1000)
plt.plot(x_pred, func(x_pred/100, *popt), 'r-', label="Fitted Curve")
plt.xlabel("Body Fat %")
plt.ylabel("% LBM Lost Per Change in Weight")
plt.title("Estimated Male P-Ratio")
plt.legend()
plt.show()

With those figures confirmed, we are ready to actually calculate our goal weight based on initial inputs! SymPy does a great job of integrating 6 for us and we solve for Goal Weight.

In [11]:
import sympy
from fractions import Fraction

BF = sympy.Symbol("BF")
a, b = popt

# to fix polynomialdivisionerror in SymPy
a, b = float(Fraction(a).limit_denominator()), float(Fraction(b).limit_denominator())

a, b = sympy.sympify(a), sympy.sympify(b)

init_bodyfat = 20
init_weight = 160
final_bodyfat = 10

integral = sympy.integrate((1-BF-1/(a+b*BF))**(-1), (BF, round(init_bodyfat/100.0,2), 
                                                     round(final_bodyfat/100.0,2)))
integral = np.float(integral)

final_weight = round(np.exp(integral + np.log(init_weight)),1)
print("With an initial weight and body fat of", init_weight, "lbs and", 
      init_bodyfat, "% body fat, you will need to reach", 
      final_weight, "lbs to hit", final_bodyfat, "% body fat.")

“With an initial weight and body fat of 160 lbs and 20 % body fat, you will need to reach 136.9 lbs to hit 10 % body fat.”

I included the above calculations with totally customizable starting parameters in my “fitness suite” that scrapes and uses your own data from popular fitness apps to deliver customized results.

In the future, I’d like to expand it into an app that can aggregate anonymized user data to calculate an empirical value for the P-ratio.

In the meantime, I put together this same material in an Excel spreadsheet for easy distribution and modification.

Google Docs Spreadsheet

Leave a Reply

Your email address will not be published. Required fields are marked *