From e53878f66522d26f1868d6f06571d79d21b0d31a Mon Sep 17 00:00:00 2001 From: Thies Lennart Alff Date: Sat, 3 May 2025 20:54:38 +0200 Subject: [PATCH] update --- lactate-eval.py | 104 +++++++++++++++++++++++++++++++++++++++++++++++ lactate-ramp.py | 97 +++++++++++++++++++++++++++++++++---------- requirements.txt | 3 ++ weight_trend.py | 44 ++++++++++++++++++-- 4 files changed, 223 insertions(+), 25 deletions(-) create mode 100644 lactate-eval.py diff --git a/lactate-eval.py b/lactate-eval.py new file mode 100644 index 0000000..b82fc76 --- /dev/null +++ b/lactate-eval.py @@ -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() + + diff --git a/lactate-ramp.py b/lactate-ramp.py index 51b7c3d..486a041 100644 --- a/lactate-ramp.py +++ b/lactate-ramp.py @@ -1,31 +1,86 @@ +import numpy as np +from scipy import optimize + metrics = GC.activityMetrics(compare=True) n_activities = len(metrics) -GC.setChart(title='Lactate-Ramp', - type=GC.CHART_LINE, - animate=False, - legpos=GC_ALIGN_TOP, - stack=False) + +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, + animate=False, + legpos=GC_ALIGN_TOP, + stack=False, +) for i, metric in enumerate(metrics): - date = metric[0]['date'] + date = metric[0]["date"] color = metric[1] print(color) - x = list(GC.xdataSeries('Lactate-Ramp', 'Power', compareindex=i)) - y = list(GC.xdataSeries('Lactate-Ramp', 'Lactate', compareindex=i)) + x = list(GC.xdataSeries("Lactate-Ramp", "Power", compareindex=i)) + y = list(GC.xdataSeries("Lactate-Ramp", "Lactate", compareindex=i)) if not (x and y): continue - GC.addCurve(name=str(date), - x=x, - y=y, - xaxis='Power', - yaxis='Lactate', - color=color, - line=GC_LINE_SOLID, - symbol=GC_SYMBOL_CIRCLE, - size=3, - opacity=100, - opengl=False) + GC.addCurve( + name=str(date), + x=x, + y=y, + xaxis="Power", + yaxis="Lactate", + color=color, + line=GC_LINE_SOLID, + symbol=GC_SYMBOL_CIRCLE, + size=3, + opacity=100, + 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) diff --git a/requirements.txt b/requirements.txt index b68957b..03ff34a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,5 @@ plotly pandas +scipy +matplotlib +pwlf diff --git a/weight_trend.py b/weight_trend.py index cd23d7e..873d695 100644 --- a/weight_trend.py +++ b/weight_trend.py @@ -4,18 +4,25 @@ import numpy as np WEIGHT_RANGE = [75, 85] +def to_date(date): + return (date - pd.to_datetime("1900-01-01").date()).days + + def get_weight_measures(): # query non-zero data = pd.DataFrame(GC.seasonMeasures(group="Body")).query("WEIGHTKG != 0.0")[ ["date", "WEIGHTKG"] ] + data.reset_index(inplace=True) + print(data) weight = data["WEIGHTKG"].to_numpy().flatten() - weight_shifted = data["WEIGHTKG"].shift(periods=1) - weight_shifted.loc[0] = 0.0 - weight_shifted = weight_shifted.to_numpy().flatten() + weight_shifted = data["WEIGHTKG"].shift(periods=1).to_numpy().flatten() + weight_shifted[0] = 0.0 change = weight - weight_shifted indices = np.argwhere(change != 0.0).flatten() weight = weight[indices] + print(data["date"]) + print(indices) date = ( data["date"].loc[indices] - pd.to_datetime("1900-01-01").date() ).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() +trend_data, trend_coeffs = trendline(weight_data, t_min, t_max) GC.setChart( type=GC.CHART_LINE, orientation=GC_VERTICAL, @@ -44,7 +70,16 @@ settings = { "opengl": False, } 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( "Weight", type=GC.AXIS_CONTINUOUS, @@ -52,3 +87,4 @@ GC.setAxis( max=WEIGHT_RANGE[1], color="red", ) +