This commit is contained in:
Thies Lennart Alff 2025-05-03 20:54:38 +02:00
parent 5379004d1c
commit e53878f665
Signed by: lennartalff
GPG key ID: 4EC67D34D594104D
4 changed files with 223 additions and 25 deletions

104
lactate-eval.py Normal file
View file

@ -0,0 +1,104 @@
import numpy as np
from scipy import optimize
import matplotlib.pyplot as plt
x0 = np.array(
[
102,
128,
143,
161,
176,
191,
207,
222,
237,
255,
270,
]
)
y0 = np.array(
[
1.5,
1.3,
1.1,
1.3,
1.5,
1.6,
2.3,
2.9,
4.2,
5.9,
7.7,
]
)
x1 = np.array(
[
100,
120,
140,
160,
180,
200,
220,
240,
260,
]
)
y1 = np.array(
[
1.4,
1,
1.4,
1.5,
1.7,
2.9,
2.8,
3,
6.2,
]
)
def get_poly_fit(x, y, remove_first_n=1, remove_last_n=1, n_points=100):
coeffs = np.polyfit(x, y, 3)
poly = np.poly1d(coeffs)
x_start = x[remove_first_n]
x_end = x[-1-remove_last_n]
x_poly = np.linspace(x_start, x_end, n_points)
y_poly = poly(x_poly)
return x_poly, y_poly
def get_log_log_threshold(x, y):
x_log = np.log(x)
y_log = np.log(y)
p, e = optimize.curve_fit(piecewise_linear, x_log, y_log, p0=[np.mean(x_log), np.mean(y_log), 0.5, 4.0])
return np.exp(p[0])
def piecewise_linear(x, x0, y0, k1, k2):
return np.piecewise(
x, [x < x0], [lambda x: k1 * x + y0 - k1 * x0, lambda x: k2 * x + y0 - k2 * x0]
)
def main():
first_x, first_y = get_poly_fit(x0, y0)
second_x, second_y = get_poly_fit(x1, y1)
first_lt1 = get_log_log_threshold(first_x, first_y)
second_lt1 = get_log_log_threshold(second_x, second_y)
plt.plot(first_x, first_y, color='C0')
plt.scatter(x0, y0, color='C0')
plt.plot(second_x, second_y, color='C1')
plt.scatter(x1, y1, color='C1')
plt.axvline(first_lt1, color='C0', linestyle='dashed')
plt.axvline(second_lt1, color='C1', linestyle='dashed')
plt.grid(True, which='major')
major_ticks = np.arange(0, 8, 1)
minor_ticks = np.arange(0, 8, 0.1)
plt.yticks(major_ticks)
plt.yticks(minor_ticks, minor=True)
plt.show()
if __name__ == '__main__':
main()

View file

@ -1,31 +1,86 @@
import numpy as np
from scipy import optimize
metrics = GC.activityMetrics(compare=True) metrics = GC.activityMetrics(compare=True)
n_activities = len(metrics) n_activities = len(metrics)
GC.setChart(title='Lactate-Ramp',
def piecewise_linear(x, x0, y0, k1, k2):
def fun0(x):
k1 * x + y0 - k1 * x0
def fun1(x):
k2 * x + y0 - k2 * x0
return np.piecewise(x, [x < x0], [fun0, fun1])
GC.setChart(
title="Lactate-Ramp",
type=GC.CHART_LINE, type=GC.CHART_LINE,
animate=False, animate=False,
legpos=GC_ALIGN_TOP, legpos=GC_ALIGN_TOP,
stack=False) stack=False,
)
for i, metric in enumerate(metrics): for i, metric in enumerate(metrics):
date = metric[0]['date'] date = metric[0]["date"]
color = metric[1] color = metric[1]
print(color) print(color)
x = list(GC.xdataSeries('Lactate-Ramp', 'Power', compareindex=i)) x = list(GC.xdataSeries("Lactate-Ramp", "Power", compareindex=i))
y = list(GC.xdataSeries('Lactate-Ramp', 'Lactate', compareindex=i)) y = list(GC.xdataSeries("Lactate-Ramp", "Lactate", compareindex=i))
if not (x and y): if not (x and y):
continue continue
GC.addCurve(name=str(date), GC.addCurve(
name=str(date),
x=x, x=x,
y=y, y=y,
xaxis='Power', xaxis="Power",
yaxis='Lactate', yaxis="Lactate",
color=color, color=color,
line=GC_LINE_SOLID, line=GC_LINE_SOLID,
symbol=GC_SYMBOL_CIRCLE, symbol=GC_SYMBOL_CIRCLE,
size=3, size=3,
opacity=100, opacity=100,
opengl=False) opengl=False,
)
coeffs = np.polyfit(x, y, 3)
poly = np.poly1d(coeffs)
x_poly = np.linspace(x[0], x[-1], 51)
y_poly = poly(x_poly)
GC.addCurve(
name="Fit",
x=x_poly,
y=y_poly,
xaxis="Power",
yaxis="Lactate",
color=color,
line=GC_LINE_DASH,
symbol=GC_SYMBOL_NONE,
size=3,
opacity=100,
opengl=False,
)
x_poly = np.linspace(x[0], x[-2], 51)
x_poly = np.array(x)
y_poly = poly(x_poly)
p, e = optimize.curve_fit(piecewise_linear, np.log(x_poly), np.log(y_poly), p0=[np.mean(x_poly), np.mean(y_poly), 0, 0])
# p, e = optimize.curve_fit(piecewise_linear, x_poly, y_poly)
print(p)
GC.addCurve(
name="log-log",
x=x_poly,
y=piecewise_linear(x_poly, *p),
xaxis="Power",
yaxis="Lactate",
line=GC_LINE_DASH,
symbol=GC_SYMBOL_NONE,
size=1,
opacity=100,
opengl=False,
)
print(piecewise_linear(x_poly, *p))
GC.setAxis('Power', min=90, max=350, type=GC.AXIS_CONTINUOUS)
GC.setAxis('Lactate', min=0.0, max=10.0, type=GC.AXIS_CONTINUOUS) GC.setAxis("Power", min=90, max=350, type=GC.AXIS_CONTINUOUS)
GC.setAxis("Lactate", min=0.0, max=10.0, type=GC.AXIS_CONTINUOUS)

View file

@ -1,2 +1,5 @@
plotly plotly
pandas pandas
scipy
matplotlib
pwlf

View file

@ -4,18 +4,25 @@ import numpy as np
WEIGHT_RANGE = [75, 85] WEIGHT_RANGE = [75, 85]
def to_date(date):
return (date - pd.to_datetime("1900-01-01").date()).days
def get_weight_measures(): def get_weight_measures():
# query non-zero # query non-zero
data = pd.DataFrame(GC.seasonMeasures(group="Body")).query("WEIGHTKG != 0.0")[ data = pd.DataFrame(GC.seasonMeasures(group="Body")).query("WEIGHTKG != 0.0")[
["date", "WEIGHTKG"] ["date", "WEIGHTKG"]
] ]
data.reset_index(inplace=True)
print(data)
weight = data["WEIGHTKG"].to_numpy().flatten() weight = data["WEIGHTKG"].to_numpy().flatten()
weight_shifted = data["WEIGHTKG"].shift(periods=1) weight_shifted = data["WEIGHTKG"].shift(periods=1).to_numpy().flatten()
weight_shifted.loc[0] = 0.0 weight_shifted[0] = 0.0
weight_shifted = weight_shifted.to_numpy().flatten()
change = weight - weight_shifted change = weight - weight_shifted
indices = np.argwhere(change != 0.0).flatten() indices = np.argwhere(change != 0.0).flatten()
weight = weight[indices] weight = weight[indices]
print(data["date"])
print(indices)
date = ( date = (
data["date"].loc[indices] - pd.to_datetime("1900-01-01").date() data["date"].loc[indices] - pd.to_datetime("1900-01-01").date()
).dt.days.to_numpy() ).dt.days.to_numpy()
@ -25,7 +32,26 @@ def get_weight_measures():
) )
def trendline(weight_data, start, end):
weight = weight_data["WEIGHTKG"]
date = weight_data["date"]
y = weight.to_numpy()
x = date.to_numpy()
coeffs = np.polyfit(x[1:], y[1:], 1)
poly = np.poly1d(coeffs)
trend = poly([start, end])
trend_x = np.array([start, end])
return pd.DataFrame(
np.hstack([trend_x.reshape([-1, 1]), trend.reshape([-1, 1])]),
columns=["date", "trend"],
), (trend[0], (trend[-1] - trend[0]) / (trend_x[-1] - trend_x[0]))
season = GC.season()
t_min = to_date(season["start"][0])
t_max = to_date(season["end"][0])
weight_data = get_weight_measures() weight_data = get_weight_measures()
trend_data, trend_coeffs = trendline(weight_data, t_min, t_max)
GC.setChart( GC.setChart(
type=GC.CHART_LINE, type=GC.CHART_LINE,
orientation=GC_VERTICAL, orientation=GC_VERTICAL,
@ -44,7 +70,16 @@ settings = {
"opengl": False, "opengl": False,
} }
GC.addCurve(name="Weight", x=weight_data["date"], y=weight_data["WEIGHTKG"], **settings) GC.addCurve(name="Weight", x=weight_data["date"], y=weight_data["WEIGHTKG"], **settings)
GC.setAxis("Date", type=GC.AXIS_DATE) settings["symbol"] = GC_SYMBOL_NONE
settings["line"] = GC_LINE_SOLID
settings["color"] = "yellow"
GC.addCurve(
name=f"Trend: {trend_coeffs[0]:.2f} {trend_coeffs[1]*7:+.2f}/week",
x=trend_data["date"],
y=trend_data["trend"],
**settings,
)
GC.setAxis("Date", type=GC.AXIS_DATE, min=t_min, max=t_max)
GC.setAxis( GC.setAxis(
"Weight", "Weight",
type=GC.AXIS_CONTINUOUS, type=GC.AXIS_CONTINUOUS,
@ -52,3 +87,4 @@ GC.setAxis(
max=WEIGHT_RANGE[1], max=WEIGHT_RANGE[1],
color="red", color="red",
) )