diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 593e6d6..ab17053 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,3 +17,8 @@ repos: hooks: - id: flake8 args: [--max-line-length=88, --extend-ignore=E203] # To make it compatible with black + + - repo: https://github.com/srstevenson/nb-clean + rev: 4.0.1 + hooks: + - id: nb-clean diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 7d82d65..7129b1d 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -34,7 +34,7 @@ To add a development dependency, run `uv add --dev `. ## Testing -To run tests, run either `uv run pytest`. +To run tests, run `uv run pytest`. @@ -43,7 +43,7 @@ To run tests, run either `uv run pytest`. - Never push to `main` directly - Create feature branches named `feature/{initials}_{feature_slug}` (e.g. `git checkout -b feature/sd_my_awesome_feature`) - Create a Pull Request to `main` when ready -- Try not to merge your own PRs (we might enforce stricter rules later but for now let's keep it simple and fast) +- Try not to merge your own PRs ## Pre-PR checklist diff --git a/README.md b/README.md index e5792fe..cfb709f 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,8 @@ A python package to analyse lactate based tresholds. Strongly inspired by [lacte docs can be found: https://dierickxsimon.github.io/lactopy/ -Still a work in progress... - -`lactopy` can be installed with pip: +`lactopy` can be installed with `pip`: ```bash pip install lactopy diff --git a/docs/assets/flexible-robust-diagram.png b/docs/assets/flexible-robust-diagram.png new file mode 100644 index 0000000..9bb0599 Binary files /dev/null and b/docs/assets/flexible-robust-diagram.png differ diff --git a/docs/assets/flexible-robust-graph.png b/docs/assets/flexible-robust-graph.png new file mode 100644 index 0000000..b4a509d Binary files /dev/null and b/docs/assets/flexible-robust-graph.png differ diff --git a/docs/css/custom_css.css b/docs/css/custom_css.css new file mode 100644 index 0000000..39d66f0 --- /dev/null +++ b/docs/css/custom_css.css @@ -0,0 +1,6 @@ +.center { + display: block; + margin-left: auto; + margin-right: auto; + text-align: center; +} diff --git a/docs/lactate_models/OBLA.md b/docs/lactate_models/OBLA.md new file mode 100644 index 0000000..5a3400a --- /dev/null +++ b/docs/lactate_models/OBLA.md @@ -0,0 +1 @@ +:::lactopy.lactate_models.OBLA diff --git a/docs/lactate_models/dmax.md b/docs/lactate_models/dmax.md new file mode 100644 index 0000000..0cd391a --- /dev/null +++ b/docs/lactate_models/dmax.md @@ -0,0 +1 @@ +:::lactopy.lactate_models.Dmax diff --git a/docs/lactate_models/general/general.md b/docs/lactate_models/general/general.md deleted file mode 100644 index f5bf265..0000000 --- a/docs/lactate_models/general/general.md +++ /dev/null @@ -1 +0,0 @@ -:::lactopy.lactate_models.general.OBLA.OBLA diff --git a/docs/lactate_models/second_threshold/dmax.md b/docs/lactate_models/second_threshold/dmax.md deleted file mode 100644 index d40477c..0000000 --- a/docs/lactate_models/second_threshold/dmax.md +++ /dev/null @@ -1 +0,0 @@ -:::lactopy.lactate_models.second_threshold.dmax.Dmax diff --git a/docs/overview.md b/docs/overview.md new file mode 100644 index 0000000..aaf7e32 --- /dev/null +++ b/docs/overview.md @@ -0,0 +1,6 @@ +# Overview + +In this overview we will give you a quick explanation of all the different models and how they can be used. + +![flexible-robust-diagram](assets/flexible-robust-diagram.png){.center} +![flexible-robust-diagram](assets/flexible-robust-graph.png){.center} diff --git a/docs/quickstart.ipynb b/docs/quickstart.ipynb index 782856c..27c5d90 100644 --- a/docs/quickstart.ipynb +++ b/docs/quickstart.ipynb @@ -2,24 +2,24 @@ "cells": [ { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "150ece86", "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "\n", - "from lactopy.lactate_models.general.OBLA import OBLA" + "from lactopy import OBLA" ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "id": "e0dd4c2e", "metadata": {}, "outputs": [], "source": [ - "df = pd.read_csv('../tests/test_data/lactate_test_data copy.csv')" + "df = pd.read_csv('../notebooks/data/test.csv')" ] }, { @@ -27,440 +27,11 @@ "execution_count": null, "id": "7f657183", "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
OBLA()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" - ], - "text/plain": [ - "OBLA()" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# fit model returns a scikit learn like model object\n", "obla = OBLA()\n", - "obla.fit(df['watt'], df['lactate'])" + "obla.fit(df['watt'], df['lactate'], method='spline')" ] }, { @@ -468,28 +39,7 @@ "execution_count": null, "id": "77b4e630", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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Vkz58uXT0aNHk8+TpAoVKjidV7hwYeXPnz/VZ9m5c2dy57arnT59Ot2f5UoVKlRQ48aNM/RaAEDaCEEA4IFmzpyp8PBwtW3bVgMHDlSRIkXk4+Oj4cOH69ChQ5n2c5599llNmTJFAwYMUJ06dRQcHCyHw6HOnTunqxlA0jkzZsxQsWLFUh3PmfPm/pp67LHHNHHiRL3xxhuqXr26KleufN3z0zPalV6JiYlq0qSJBg0alObx2267LdN+FgDg1hCCAMADLViwQGXLltXChQud/qF/9bS3cuXKacWKFTpz5sx1R4OuFRYWLFigHj16aNSoUcn7Ll++rHPnzqXr9UkNFooUKZIpox3169dXyZIltXr1ao0cOfKa55UqVUqJiYk6ePCgU+OAv/76S+fOnVOpUqWSz5PMKE/ZsmWTz/v7779TtdwuV66coqOjbR21ycxQBwCejHuCAMADJbWEvnJ62saNG7Vhwwan8x555BFZlqWhQ4emeo8rX5s7d+5UwSbp51x5niSNHz9eCQkJTvty584tSaneo1mzZgoKCtKwYcOcpuwl+fvvv9P4dNfmcDg0btw4vf766+rWrds1z0tafPTqjm2jR4+WJLVq1UqS1LhxY/n6+mr8+PFOnzOtTm+PPvqoNmzYoBUrVqQ6du7cOcXHx9/UZ8mIa33PAABnjAQBgJv6/PPP9e2336ba379/fz300ENauHCh2rVrp1atWunw4cOaOHGiKleurOjo6ORzGzVqpG7dumncuHE6ePCgmjdvrsTERK1bt06NGjVS3759JUm1atXS999/r9GjRys0NFRlypTRPffco4ceekgzZsxQcHCwKleurA0bNuj7779XwYIFnWq688475ePjo5EjRyoyMlL+/v564IEHVKRIEU2YMEHdunVTzZo11blzZxUuXFjHjh3T119/rXr16unDDz+8qe+lTZs2atOmzXXPqV69unr06KFJkybp3Llzuv/++/Xrr79q2rRpatu2rRo1aiTJ3Pvz4osvavjw4XrooYfUsmVLbdu2TcuXL3dalFWSBg4cqKVLl+qhhx5SeHi4atWqpQsXLmjXrl1asGCBjhw5kuo1me163zMA4Ao2dqYDAGRAUovsa23Hjx+3EhMTrWHDhlmlSpWy/P39rRo1aljLli2zevToYZUqVcrp/eLj46333nvPqlSpkuXn52cVLlzY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cD1cB9fCtXA9XJtbN0YAAAAAgJvFSBAAAAAAr0IIAgAAAOBVCEEAAAAAvAohCAAAAIBXIQR5qZMnT+rxxx9XwYIFFRgYqDvuuEObN29OPm5ZloYMGaKQkBAFBgaqcePGOnjwoI0Ve66EhAS99tprKlOmjAIDA1WuXDm99dZburJnCdcja6xdu1atW7dWaGioHA6HFi9e7HQ8Pd/7mTNn1LVrVwUFBSlfvnx64oknFB0dnY2fwnNc73rExcXppZde0h133KHcuXMrNDRU3bt3159//un0HlyPzHGjPxtXevrpp+VwODRmzBin/VyLzJOe67F37149/PDDCg4OVu7cuXXXXXfp2LFjyccvX76sPn36qGDBgsqTJ48eeeQR/fXXX9n4KTzDja5FdHS0+vbtqxIlSigwMFCVK1fWxIkTnc7hWrgGQpAXOnv2rOrVqydfX18tX75ce/bs0ahRo5Q/f/7kc959912NGzdOEydO1MaNG5U7d241a9ZMly9ftrFyzzRy5EhNmDBBH374ofbu3auRI0fq3Xff1fjx45PP4XpkjQsXLqh69er66KOP0jyenu+9a9eu+u2337Ry5UotW7ZMa9eu1X/+85/s+gge5XrX4+LFi9q6datee+01bd26VQsXLtT+/fv18MMPO53H9cgcN/qzkWTRokX65ZdfFBoamuoY1yLz3Oh6HDp0SPXr11elSpW0evVq7dy5U6+99poCAgKSz3nuuef01Vdfaf78+VqzZo3+/PNPtW/fPrs+gse40bV4/vnn9e2332rmzJnau3evBgwYoL59+2rp0qXJ53AtXIQFr/PSSy9Z9evXv+bxxMREq1ixYtZ7772XvO/cuXOWv7+/NWfOnOwo0au0atXK6tWrl9O+9u3bW127drUsi+uRXSRZixYtSn6enu99z549liRr06ZNyecsX77ccjgc1smTJ7Otdk909fVIy6+//mpJso4ePWpZFtcjq1zrWpw4ccIqXry4tXv3bqtUqVLWBx98kHyMa5F10roenTp1sh5//PFrvubcuXOWr6+vNX/+/OR9e/futSRZGzZsyKpSPV5a16JKlSrWm2++6bSvZs2a1v/93/9ZlsW1cCWMBHmhpUuXqnbt2urYsaOKFCmiGjVq6NNPP00+fvjwYZ06dUqNGzdO3hccHKx77rlHGzZssKNkj1a3bl398MMPOnDggCRpx44d+umnn9SiRQtJXA+7pOd737Bhg/Lly6fatWsnn9O4cWPlyJFDGzduzPaavU1kZKQcDofy5csnieuRnRITE9WtWzcNHDhQVapUSXWca5F9EhMT9fXXX+u2225Ts2bNVKRIEd1zzz1O07S2bNmiuLg4p/+eVapUSSVLluTvkUxWt25dLV26VCdPnpRlWVq1apUOHDigpk2bSuJauBJCkBf6448/NGHCBFWoUEErVqxQ79691a9fP02bNk2SdOrUKUlS0aJFnV5XtGjR5GPIPC+//LI6d+6sSpUqydfXVzVq1NCAAQPUtWtXSVwPu6Tnez916pSKFCnidDxnzpwqUKAA1yaLXb58WS+99JK6dOmioKAgSVyP7DRy5EjlzJlT/fr1S/M41yL7nD59WtHR0RoxYoSaN2+u7777Tu3atVP79u21Zs0aSeZ6+Pn5Jf8PgyT8PZL5xo8fr8qVK6tEiRLy8/NT8+bN9dFHH+m+++6TxLVwJTntLgDZLzExUbVr19awYcMkSTVq1NDu3bs1ceJE9ejRw+bqvM+8efM0a9YszZ49W1WqVNH27ds1YMAAhYaGcj2ANMTFxenRRx+VZVmaMGGC3eV4nS1btmjs2LHaunWrHA6H3eV4vcTERElSmzZt9Nxzz0mS7rzzTq1fv14TJ07U/fffb2d5Xmf8+PH65ZdftHTpUpUqVUpr165Vnz59FBoa6jT6A/sxEuSFQkJCVLlyZad9t99+e3IXmWLFiklSqk4lf/31V/IxZJ6BAwcmjwbdcccd6tatm5577jkNHz5cEtfDLun53osVK6bTp087HY+Pj9eZM2e4NlkkKQAdPXpUK1euTB4Fkrge2WXdunU6ffq0SpYsqZw5cypnzpw6evSoXnjhBZUuXVoS1yI7FSpUSDlz5rzh3+uxsbE6d+6c0zn8PZK5Ll26pFdeeUWjR49W69atVa1aNfXt21edOnXS+++/L4lr4UoIQV6oXr162r9/v9O+AwcOqFSpUpKkMmXKqFixYvrhhx+Sj0dFRWnjxo2qU6dOttbqDS5evKgcOZz/KPr4+CT/3z2uhz3S873XqVNH586d05YtW5LP+fHHH5WYmKh77rkn22v2dEkB6ODBg/r+++9VsGBBp+Ncj+zRrVs37dy5U9u3b0/eQkNDNXDgQK1YsUIS1yI7+fn56a677rru3+u1atWSr6+v03/P9u/fr2PHjvH3SCaKi4tTXFzcdf9O51q4ELs7MyD7/frrr1bOnDmtd955xzp48KA1a9YsK1euXNbMmTOTzxkxYoSVL18+a8mSJdbOnTutNm3aWGXKlLEuXbpkY+WeqUePHlbx4sWtZcuWWYcPH7YWLlxoFSpUyBo0aFDyOVyPrHH+/Hlr27Zt1rZt2yxJ1ujRo61t27YldxtLz/fevHlzq0aNGtbGjRutn376yapQoYLVpUsXuz6SW7ve9YiNjbUefvhhq0SJEtb27dutiIiI5C0mJib5PbgemeNGfzaudnV3OMviWmSmG12PhQsXWr6+vtakSZOsgwcPWuPHj7d8fHysdevWJb/H008/bZUsWdL68ccfrc2bN1t16tSx6tSpY9dHcls3uhb333+/VaVKFWvVqlXWH3/8YU2ZMsUKCAiwPv744+T34Fq4BkKQl/rqq6+sqlWrWv7+/lalSpWsSZMmOR1PTEy0XnvtNato0aKWv7+/9eCDD1r79++3qVrPFhUVZfXv398qWbKkFRAQYJUtW9b6v//7P6d/2HE9ssaqVassSam2Hj16WJaVvu/933//tbp06WLlyZPHCgoKsnr27GmdP3/ehk/j/q53PQ4fPpzmMUnWqlWrkt+D65E5bvRn42pphSCuReZJz/X47LPPrPLly1sBAQFW9erVrcWLFzu9x6VLl6xnnnnGyp8/v5UrVy6rXbt2VkRERDZ/Evd3o2sRERFhhYeHW6GhoVZAQIBVsWJFa9SoUVZiYmLye3AtXIPDsq5Ylh4AAAAAPBz3BAEAAADwKoQgAAAAAF6FEAQAAADAqxCCAAAAAHgVQhAAAAAAr0IIAgAAAOBVCEEAAAAAvAohCAAAAIBXIQQBALzSkSNH5HA4tH37drtLAQBkM0IQACDThYeHq23btuk+3+FwaPHixVlWT1rCwsIUERGhqlWrSpJWr14th8Ohc+fOZWsdAIDsl9PuAgAAsIOPj4+KFStmdxkAABswEgQAyFINGzZUv379NGjQIBUoUEDFihXTG2+8kXy8dOnSkqR27drJ4XAkP5ekJUuWqGbNmgoICFDZsmU1dOhQxcfHJx93OByaPHmy2rVrp1y5cqlChQpaunRp8vGzZ8+qa9euKly4sAIDA1WhQgVNmTJFkvN0uCNHjqhRo0aSpPz588vhcCg8PFzTp09XwYIFFRMT4/SZ2rZtq27dumXyNwUAyC6EIABAlps2bZpy586tjRs36t1339Wbb76plStXSpI2bdokSZoyZYoiIiKSn69bt07du3dX//79tWfPHn3yySeaOnWq3nnnHaf3Hjp0qB599FHt3LlTLVu2VNeuXXXmzBlJ0muvvaY9e/Zo+fLl2rt3ryZMmKBChQqlqi8sLExffvmlJGn//v2KiIjQ2LFj1bFjRyUkJDgFq9OnT+vrr79Wr169Mv+LAgBkC0IQACDLVatWTa+//roqVKig7t27q3bt2vrhhx8kSYULF5Yk5cuXT8WKFUt+PnToUL388svq0aOHypYtqyZNmuitt97SJ5984vTe4eHh6tKli8qXL69hw4YpOjpav/76qyTp2LFjqlGjhmrXrq3SpUurcePGat26dar6fHx8VKBAAUlSkSJFVKxYMQUHByswMFCPPfZY8uiRJM2cOVMlS5ZUw4YNM/17AgBkD+4JAgBkuWrVqjk9DwkJ0enTp6/7mh07dujnn392GvlJSEjQ5cuXdfHiReXKlSvVe+fOnVtBQUHJ7927d2898sgj2rp1q5o2baq2bduqbt26N1X7U089pbvuuksnT55U8eLFNXXqVIWHh8vhcNzU+wAAXAchCACQ5Xx9fZ2eOxwOJSYmXvc10dHRGjp0qNq3b5/qWEBAQLreu0WLFjp69Ki++eYbrVy5Ug8++KD69Omj999/P92116hRQ9WrV9f06dPVtGlT/fbbb/r666/T/XoAgOshBAEAbOfr66uEhASnfTVr1tT+/ftVvnz5W3rvwoULq0ePHurRo4caNGiggQMHphmC/Pz8JClVHZL05JNPasyYMTp58qQaN26ssLCwW6oJAGAv7gkCANiudOnS+uGHH3Tq1CmdPXtWkjRkyBBNnz5dQ4cO1W+//aa9e/dq7ty5evXVV9P9vkOGDNGSJUv0+++/67ffftOyZct0++23p3luqVKl5HA4tGzZMv3999+Kjo5OPvbYY4/pxIkT+vTTT2mIAAAegBAEALDdqFGjtHLlSoWFhalGjRqSpGbNmmnZsmX67rvvdNddd+nee+/VBx98oFKlSqX7ff38/DR48GBVq1ZN9913n3x8fDR37tw0zy1evHhyM4aiRYuqb9++yceCg4P1yCOPKE+ePDe1CCwAwDU5LMuy7C4CAABX9+CDD6pKlSoaN26c3aUAAG4RIQgAgOs4e/asVq9erQ4dOmjPnj2qWLGi3SUBAG4RjREAALiOGjVq6OzZsxo5ciQBCAA8BCNBAAAAALwKjREAAAAAeBVCEAAAAACvQggCAAAA4FUIQQAAAAC8CiEIAAAAgFchBAEAAADwKoQgAAAAAF6FEAQAAADAq/w/dcjFH1kDKRwAAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "# plot fit of data\n", "obla.plot.plot_fit()" @@ -500,18 +50,7 @@ "execution_count": null, "id": "279f619a", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "np.float64(145.93695290778828)" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# predict intensity at 4 mmol\n", "obla.predict(4)" @@ -522,45 +61,216 @@ "execution_count": null, "id": "6fcd47b1", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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KqXZs2erd+/estlsWXodAIDzIAQBAHKdj49Pmvs2m012u/26z4mJidHIkSPVoUOHdI/5+/tn6rVbtmypEydO6IsvvtD69et13333qX///nr77bczXXvt2rVVq1YtzZ07Vw888IB+/vlnff7555l+PgDA+RCCAACW8/HxUUpKSpp9derU0ZEjR1SxYsWbeu2iRYuqV69e6tWrlxo1aqShQ4dmGIJ8fX0lKV0dktS3b19NmjRJp0+fVrNmzRQeHn5TNQEArMU1QQAAy0VEROibb77RmTNndOHCBUnSq6++qrlz52rkyJH6+eefdejQIS1atEgvv/xypl/31Vdf1apVq/Trr7/q559/1po1a1S1atUMjy1btqxsNpvWrFmjv//+WzExMY7HunXrpj/++EMffPABDREAwA0QggAAlpswYYLWr1+v8PBw1a5dW5LUvHlzrVmzRl999ZXq16+vO++8U++8847Kli2b6df19fXV8OHDVbNmTd1zzz3y8vLSokWLMjy2VKlSjmYMxYsX14ABAxyPhYaG6uGHH1ZwcHCWFoEFADgnm2EYhtVFAADg7O677z5Vr15dU6ZMsboUAMBNIgQBAHAdFy5c0MaNG9WxY0cdPHhQlStXtrokAMBNojECAADXUbt2bV24cEFjx44lAAGAm2AkCAAAAIBHoTECAAAAAI9CCAIAAADgUQhBAAAAADwKIQgAAACARyEEAQAAAPAohCAAAAAAHoUQBAAAAMCjEIIAAAAAeJT/B7ul6vz1SKszAAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "# Plot the prediction at 4 mmol\n", "obla.plot.plot_predictions(4)" ] }, { - "cell_type": "markdown", - "id": "e65a3ee0", + "cell_type": "code", + "execution_count": null, + "id": "acff8813", + "metadata": {}, + "outputs": [], + "source": [ + "from lactopy.lactate_models import Dmax" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "65aa368c", + "metadata": {}, + "outputs": [], + "source": [ + "model = Dmax()\n", + "model.fit(df['watt'], df['lactate'], impl='modified')\n", + "# plot fit of data\n", + "model.plot.plot_fit()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7ef327c7", + "metadata": {}, + "outputs": [], + "source": [ + "model.plot.plot_predictions()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b7ba2348", + "metadata": {}, + "outputs": [], + "source": [ + "from lactopy.lactate_models.lt1_si import LT1_si" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "15e234c7", + "metadata": {}, + "outputs": [], + "source": [ + "model = LT1_si()\n", + "model.fit(df['watt'], df['lactate'], si=0.5)\n", + "lt1_intensity = model.predict()\n", + "print(f\"LT1 occurs at: {lt1_intensity:.2f} watts\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "42e695f9", + "metadata": {}, + "outputs": [], + "source": [ + "model.plot.plot_predictions()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d02f1652", + "metadata": {}, + "outputs": [], + "source": [ + "from lactopy.lactate_models.lt1_si_lowest import LT1_si_lowest" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "54283247", + "metadata": {}, + "outputs": [], + "source": [ + "model = LT1_si_lowest()\n", + "model.fit(df['watt'], df['lactate'], si=0.5)\n", + "lt1_intensity = model.predict()\n", + "print(f\"LT1 occurs at: {lt1_intensity:.2f} watts\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2739c2f2", + "metadata": {}, + "outputs": [], + "source": [ + "model.plot.plot_predictions()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7f6316fa", "metadata": {}, + "outputs": [], + "source": [ + "from lactopy.lactate_models.lt1_loglog import LT1_loglog" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "738f6b41", + "metadata": {}, + "outputs": [], + "source": [ + "model = LT1_loglog()\n", + "model.fit(df['watt'], df['lactate'])\n", + "lt1_intensity = model.predict()\n", + "print(f\"LT1 occurs at: {lt1_intensity:.2f} watts\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a5d00229", + "metadata": {}, + "outputs": [], + "source": [ + "model.plot.plot_predictions()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "76b18ab1", + "metadata": {}, + "outputs": [], + "source": [ + "from lactopy.lactate_models.lt1_lt2_breakpoint import LT1_LT2_breakpoint" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4f688ce1", + "metadata": {}, + "outputs": [], + "source": [ + "model = LT1_LT2_breakpoint()\n", + "model.fit(df['watt'], df['lactate'])\n", + "lt1_intensity = model.predict()\n", + "lt1_intensity\n", + "print(f\"LT1 occurs at: {lt1_intensity[0]:.2f} watts\")\n", + "print(f\"LT1 occurs at: {lt1_intensity[1]:.2f} watts\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9d61bd43", + "metadata": {}, + "outputs": [], + "source": [ + "model.plot.plot_predictions()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0aac8a14", + "metadata": {}, + "outputs": [], + "source": [ + "from lactopy.lactate_models.lt2_breakpoint import LT2_breakpoint" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "63236ce4", + "metadata": {}, + "outputs": [], + "source": [ + "model = LT2_breakpoint()\n", + "model.fit(df['watt'], df['lactate'])\n", + "lt1_intensity = model.predict()\n", + "print(f\"LT1 occurs at: {lt1_intensity:.2f} watts\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5fb4e329", + "metadata": {}, + "outputs": [], "source": [ - "# plot of all different models" + "model.plot.plot_predictions()" ] } ], "metadata": { "kernelspec": { - "display_name": ".venv", + "display_name": "lactopy", "language": "python", "name": "python3" }, @@ -573,8 +283,7 @@ "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.16" + "pygments_lexer": "ipython3" } }, "nbformat": 4, diff --git a/mkdocs.yml b/mkdocs.yml index 09017e2..a5a5acd 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -84,5 +84,11 @@ markdown_extensions: - pymdownx.superfences - pymdownx.mark - attr_list + - md_in_html + - pymdownx.blocks.caption - toc: toc_depth: 3 + + +extra_css: + - css/custom_css.css diff --git a/notebooks/data/big_middle_zone.csv b/notebooks/data/big_middle_zone.csv new file mode 100644 index 0000000..0c0f418 --- /dev/null +++ b/notebooks/data/big_middle_zone.csv @@ -0,0 +1,11 @@ +watt,lactate +50,0.98 +75,1.23 +100,1.98 +125,2.2 +150,2.4 +175,2.8 +200,3.3 +225,4.21 +250,6.66 +271.7,8.64 diff --git a/notebooks/data/high_anaerobic_reserve.csv b/notebooks/data/high_anaerobic_reserve.csv new file mode 100644 index 0000000..3cef9f8 --- /dev/null +++ b/notebooks/data/high_anaerobic_reserve.csv @@ -0,0 +1,8 @@ +watt,lactate +50,0.98 +75,1.23 +100,1.88 +125,2.8 +150,4.21 +175,6.66 +191.7,8.64 diff --git a/notebooks/data/initial_la_drop.csv b/notebooks/data/initial_la_drop.csv new file mode 100644 index 0000000..1e6c922 --- /dev/null +++ b/notebooks/data/initial_la_drop.csv @@ -0,0 +1,8 @@ +watt,lactate +50,1.9 +75,1.23 +100,1.9 +125,2.8 +150,4.21 +175,6.66 +191.7,8.64 diff --git a/notebooks/data/low_anaerobic_reserve.csv b/notebooks/data/low_anaerobic_reserve.csv new file mode 100644 index 0000000..c667ee2 --- /dev/null +++ b/notebooks/data/low_anaerobic_reserve.csv @@ -0,0 +1,7 @@ +watt,lactate +50,1.1 +75,1.23 +100,1.88 +125,2.8 +150,4.21 +165,6.00 diff --git a/notebooks/data/no_base.csv b/notebooks/data/no_base.csv new file mode 100644 index 0000000..c2b1cba --- /dev/null +++ b/notebooks/data/no_base.csv @@ -0,0 +1,5 @@ +watt,lactate +50,3 +75,4 +100,6 +125,10 diff --git a/notebooks/data/test.csv b/notebooks/data/test.csv index 3cef9f8..1e77b74 100644 --- a/notebooks/data/test.csv +++ b/notebooks/data/test.csv @@ -1,8 +1,8 @@ watt,lactate 50,0.98 75,1.23 -100,1.88 +100,1.5 125,2.8 -150,4.21 -175,6.66 -191.7,8.64 +150,3.21 +175,5.66 +191.7,7.64 diff --git a/notebooks/data/test_2.csv b/notebooks/data/test_2.csv new file mode 100644 index 0000000..863ac37 --- /dev/null +++ b/notebooks/data/test_2.csv @@ -0,0 +1,12 @@ +watt,lactate +50,1.25 +75,1.23 +100,1.4 +125,1.5 +150,1.7 +175,2.1 +200,2.9 +225,3.6 +250,4.6 +275,7.1 +290,11.4 \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 60fed47..aab6ebc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,7 @@ authors = [ requires-python = ">=3.10" dependencies = [ "numpy>=2.2.5", + "piecewise-regression>=1.5.0", "scikit-learn>=1.6.1", "scipy>=1.15.2", ] @@ -26,6 +27,7 @@ dev = [ "mkdocs-literate-nav>=0.6.2", "mkdocs-material>=9.6.12", "mkdocstrings-python>=1.16.10", + "nb-clean>=4.0.1", "pandas>=2.2.3", "pre-commit>=4.2.0", "pytest>=8.3.5", diff --git a/src/lactopy/__init__.py b/src/lactopy/__init__.py index 44be4e7..886f970 100644 --- a/src/lactopy/__init__.py +++ b/src/lactopy/__init__.py @@ -1,2 +1 @@ -def hello() -> str: - return "Hello from lactopy!" +from .lactate_models import OBLA, Bsln, Dmax, lt1_si, lt1_si_lowest, lt1_loglog, lt2_breakpoint, lt1_lt2_breakpoint # noqa: F401 diff --git a/src/lactopy/base/adaptors/cubicAdaptors.py b/src/lactopy/base/adaptors/cubicAdaptors.py index 0439815..76d7463 100644 --- a/src/lactopy/base/adaptors/cubicAdaptors.py +++ b/src/lactopy/base/adaptors/cubicAdaptors.py @@ -1,3 +1,4 @@ +import copy import numpy as np from scipy.interpolate import CubicSpline @@ -34,5 +35,6 @@ def predict_inverse(self, Y): return roots[0] def dxdt(self): - self._spline = self._spline.derivative() - return self + r_obj = copy.deepcopy(self) + r_obj._spline = r_obj._spline.derivative() + return r_obj diff --git a/src/lactopy/base/adaptors/polyAdapter.py b/src/lactopy/base/adaptors/polyAdapter.py index e3c6735..4d77e15 100644 --- a/src/lactopy/base/adaptors/polyAdapter.py +++ b/src/lactopy/base/adaptors/polyAdapter.py @@ -1,3 +1,4 @@ +import copy import numpy as np @@ -30,5 +31,6 @@ def predict_inverse(self, Y): return real_roots_within_domain[-1] def dxdt(self): - self.p = np.poly1d(np.polyder(self._coef)) - return self + r_obj = copy.deepcopy(self) + r_obj.p = np.poly1d(np.polyder(r_obj._coef)) + return r_obj diff --git a/src/lactopy/base/base.py b/src/lactopy/base/base.py index d552a89..0d1214e 100644 --- a/src/lactopy/base/base.py +++ b/src/lactopy/base/base.py @@ -1,8 +1,9 @@ +from numpy.typing import ArrayLike +from typing import Union + from sklearn.base import BaseEstimator, RegressorMixin import numpy as np -from numpy.typing import ArrayLike -from typing import Union from lactopy.base.adaptors import PolyAdaptor, CubicAdaptor from lactopy.plots.base import Plot @@ -27,8 +28,9 @@ def _validate_lactate_test(self, X: ArrayLike, y: ArrayLike): raise ValueError("X and y must have the same length.") if len(X) == 0: raise ValueError("X and y must not be empty.") + return X, y - def fit(self, X: ArrayLike, y: ArrayLike, method="3th_poly"): + def fit(self, X: ArrayLike, y: ArrayLike, method="4th_poly", _mask=None): """ Fit the model to the training data. @@ -42,15 +44,20 @@ def fit(self, X: ArrayLike, y: ArrayLike, method="3th_poly"): - `"3th_poly"`: 3rd degree polynomial - `"4th_poly"`: 4th degree polynomial - - `"spline"`: Spline + - `"spline"`: Cubic Spline Returns: self (object): Fitted model. """ - self._validate_lactate_test(X, y) - self.X = np.array(X) - self.y = np.array(y) + + X, y = self._validate_lactate_test(X, y) + mask = _mask if _mask is not None else np.ones_like(X, dtype=bool) + self.X_raw_for_plot = X + self.y_raw_for_plot = y + + self.X = np.array(X)[mask] + self.y = np.array(y)[mask] match method: case "3th_poly": self.model = PolyAdaptor().fit(self.X, self.y, degree=3) diff --git a/src/lactopy/lactate_models/__init__.py b/src/lactopy/lactate_models/__init__.py index e69de29..3db2810 100644 --- a/src/lactopy/lactate_models/__init__.py +++ b/src/lactopy/lactate_models/__init__.py @@ -0,0 +1,8 @@ +from .bsln import Bsln # noqa: F401 +from .dmax import Dmax # noqa: F401 +from .obla import OBLA # noqa: F401 +from .lt1_si import LT1_si # noqa: F401 +from .lt1_si_lowest import LT1_si_lowest # noqa: F401 +from .lt1_loglog import LT1_loglog # noqa: F401 +from .lt2_breakpoint import LT2_breakpoint # noqa: F401 +from .lt1_lt2_breakpoint import LT1_LT2_breakpoint # noqa: F401 \ No newline at end of file diff --git a/src/lactopy/lactate_models/bsln.py b/src/lactopy/lactate_models/bsln.py index 05b98bf..2a78c6f 100644 --- a/src/lactopy/lactate_models/bsln.py +++ b/src/lactopy/lactate_models/bsln.py @@ -1,4 +1,4 @@ -from lactopy.lactate_models.OBLA import OBLA +from lactopy.lactate_models.obla import OBLA class Bsln(OBLA): diff --git a/src/lactopy/lactate_models/dmax.py b/src/lactopy/lactate_models/dmax.py index bc2e0d7..dbad1d1 100644 --- a/src/lactopy/lactate_models/dmax.py +++ b/src/lactopy/lactate_models/dmax.py @@ -1,21 +1,89 @@ +import numpy as np +from numpy.typing import ArrayLike + + from lactopy.base import BaseModel +from lactopy.plots.Dmax import DmaxPlot class Dmax(BaseModel): """ Dmax model for lactate threshold estimation. + + The Dmax method is used to estimate the lactate threshold by identifying + the point of maximal perpendicular distance from a straight line connecting + the first and last points of the lactate curve. + + This is computed by finding the point where the derivative of the lactate + curve is equal to the average rate of change between the first and last points. + + Attributes: + plot (DmaxPlot): An instance of the DmaxPlot class for visualizing the model. """ + def __init__(self): + """ + Initializes the Dmax model and its associated plot. + + """ + super().__init__() + self.plot = DmaxPlot(self) + + def fit( + self, + X: ArrayLike, + y: ArrayLike, + impl: str = "normal", + threshold_above_baseline=0.4, + **kwargs, + ): + """ + Fits the Dmax model to the given data. + + Args: + X (ArrayLike): The independent variable (e.g., intensity). + y (ArrayLike): The dependent variable (e.g., lactate concentration). + impl (str, optional): The implementation type. Options are "normal" or + "modified". + Defaults to "normal". + threshold_above_baseline (float, optional): Threshold above baseline + for filtering + in the "modified" implementation. Defaults to 0.4. + + method (str): + Method to use for fitting the model. Options are: + + - `"3th_poly"`: 3rd degree polynomial + - `"4th_poly"`: 4th degree polynomial + - `"spline"`: Cubic Spline + + Defaults to "4th_poly". + + Returns: + self: Fitted Dmax model instance. + """ + self._impl = impl + mask = None + + match impl: + case "normal": + pass + case "modified": + X, y = np.array(X), np.array(y) + mask = X > X[0] + threshold_above_baseline + case _: + raise ValueError(f"Unknown implementation: {impl}") + + super().fit(X, y, _mask=mask, **kwargs) + self.dxdt_model = self.model.dxdt() + return self + def predict(self) -> float: """ Predicts intensity for the Dmax method. Returns: - float: - Predicted intensity. + float: Predicted intensity. """ - - dxdt_first_last_value = (self.y.max() - self.y.min()) / ( - self.X.max() - self.X.min() - ) - return self.model.dxdt().predict_inverse(dxdt_first_last_value) + dxdt_first_last_value = (self.y[-1] - self.y[0]) / (self.X[-1] - self.X[0]) + return self.dxdt_model.predict_inverse(dxdt_first_last_value) diff --git a/src/lactopy/lactate_models/lt1_loglog.py b/src/lactopy/lactate_models/lt1_loglog.py new file mode 100644 index 0000000..eba48d5 --- /dev/null +++ b/src/lactopy/lactate_models/lt1_loglog.py @@ -0,0 +1,84 @@ +from numpy.typing import ArrayLike +import numpy as np +import piecewise_regression + +from lactopy.base import BaseModel +from lactopy.plots.lt1_loglog_plot import LT1_loglog_Plot + + +class LT1_loglog(BaseModel): + """ + Log-Log model for lactate threshold estimation. + + Log-Log method is used to estimate the first lactate threshold by plotting + the lactate response against intensity on a logaritmic scale. + + Plot is divided into 2 segments --> segmented regression identifies breakpoint + + Attributes: + plot (lt1_loglog_plot): For visualizing the model.. + """ + + def __init__(self): + """ + Initializes the lt1_loglog model and its associated plot. + + """ + super().__init__() + self.plot = LT1_loglog_Plot(self) + + def fit( + self, + X: ArrayLike, + y: ArrayLike, + **kwargs, + ): + """ + Fits the lt1_loglog model to the given data. + + Args: + X (ArrayLike): The independent variable (e.g., intensity). + y (ArrayLike): The dependent variable (e.g., lactate concentration). + si: the standard increment, defaults to 0.5 + + method (str): + Method to use for fitting the model. Options are: + + - `"3th_poly"`: 3rd degree polynomial + - `"4th_poly"`: 4th degree polynomial + - `"spline"`: Cubic Spline + + Defaults to "4th_poly". + + Returns: + self: Fitted lt1_loglog model instance. + """ + + y_log = np.log(y) + x_log = np.log(X) + + super().fit(x_log, y_log, **kwargs) + return self + + def predict(self) -> float: + """ + Predicts intensity for the lt1_loglog method. + + Returns: + float: Predicted intensity. + + """ + + pw_fit = piecewise_regression.Fit(self.X, self.y, n_breakpoints=1) + pw_results = pw_fit.get_results() + + if "breakpoint1" not in pw_results.get("estimates", {}): + raise ValueError( + f"'breakpoint1' not found in regression results." + f"It is recommended to use a different treshold estimation method." + f"Piecewise regression may have failed. Result: {pw_results}" + ) + else: + predicted_x = pw_results["estimates"]["breakpoint1"]["estimate"] + + return predicted_x diff --git a/src/lactopy/lactate_models/lt1_lt2_breakpoint.py b/src/lactopy/lactate_models/lt1_lt2_breakpoint.py new file mode 100644 index 0000000..e5dc98c --- /dev/null +++ b/src/lactopy/lactate_models/lt1_lt2_breakpoint.py @@ -0,0 +1,89 @@ +from collections import namedtuple + +from numpy.typing import ArrayLike +import piecewise_regression + + +from lactopy.base import BaseModel +from lactopy.plots.lt1_lt2_breakpoint_plot import LT1_LT2_breakpoint_Plot + + +class LT1_LT2_breakpoint(BaseModel): + """ + Determining both LT1 and LT2 based on the breakpoints in the lactate curve + Piecewise regression module is used to identify the breakpoints + + Plot is divided into 3 segments --> segmented regression identifies breakpoint + + + Attributes: + plot (lt1_lt2_breakpoint_plot): For visualizing the model. + """ + + def __init__(self): + """ + Initializes the LT1_LT2_breakpoint model and its associated plot. + """ + super().__init__() + self.plot = LT1_LT2_breakpoint_Plot(self) + + def fit( + self, + X: ArrayLike, + y: ArrayLike, + **kwargs, + ): + """ + Fits the LT1_LT2_breakpoint model to the given data. + + Args: + X (ArrayLike): The independent variable (e.g., intensity). + y (ArrayLike): The dependent variable (e.g., lactate concentration). + si: the standard increment, defaults to 0.5 + + + method (str): + Method to use for fitting the model. Options are: + + - `"3th_poly"`: 3rd degree polynomial + - `"4th_poly"`: 4th degree polynomial + - `"spline"`: Cubic Spline + + Defaults to "4th_poly". + + Returns: + self: Fitted Dmax model instance. + """ + return super().fit(X, y, **kwargs) + + def predict(self) -> float: + """ + Predicts intensity for LT1 and LT2 method. + + Returns: + float: Predicted intensity. + + """ + + pw_fit = piecewise_regression.Fit(self.X, self.y, n_breakpoints=2) + pw_results = pw_fit.get_results() + + if "breakpoint1" not in pw_results.get("estimates", {}): + raise ValueError( + f"'breakpoint1' not found in regression results." + f"It is recommended to use a different treshold estimation method." + f"Piecewise regression may have failed. Result: {pw_results}" + ) + elif "breakpoint1" not in pw_results.get("estimates", {}): + raise ValueError( + f"'breakpoint2' not found in regression results." + f"It is recommended to use a different treshold estimation method." + f"Piecewise regression may have failed. Result: {pw_results}" + ) + else: + predicted_lt1 = pw_results["estimates"]["breakpoint1"]["estimate"] + predicted_lt2 = pw_results["estimates"]["breakpoint2"]["estimate"] + + result = namedtuple("LT1_LT2_breakpoint", ["lt1", "lt2"]) + + return result(predicted_lt1, predicted_lt2) diff --git a/src/lactopy/lactate_models/lt1_si.py b/src/lactopy/lactate_models/lt1_si.py new file mode 100644 index 0000000..826d466 --- /dev/null +++ b/src/lactopy/lactate_models/lt1_si.py @@ -0,0 +1,81 @@ +from numpy.typing import ArrayLike + + +from lactopy.base import BaseModel +from lactopy.plots.lt1_si_plot import LT1_si_Plot + + +class LT1_si(BaseModel): + """ + Standard increment model for lactate threshold estimation. + + The Standard increment method is used to estimate the first lactate + threshold by identifying + the point in which the first meaningfull lactate increase occurs. + + This is computed by using a standard value, preferably equlual to the lowest + detectable change in lactate concentration of the measurement tool + + Attributes: + plot (lt1_si_plot): For visualizing the model.. + """ + + def __init__(self): + """ + Initializes the LT1_si model and its associated plot. + + """ + super().__init__() + self.plot = LT1_si_Plot(self) + + def fit( + self, + X: ArrayLike, + y: ArrayLike, + si: float = 0.5, + **kwargs, + ): + """ + Fits the Standard Increment model to the given data. + + Args: + X (ArrayLike): The independent variable (e.g., intensity). + y (ArrayLike): The dependent variable (e.g., lactate concentration). + si: the standard increment, defaults to 0.5 + + threshold_above_baseline (float, optional): Threshold above baseline + for filtering in the "modified" implementation. Defaults to 0.5. + + method (str): + Method to use for fitting the model. Options are: + + - `"3th_poly"`: 3rd degree polynomial + - `"4th_poly"`: 4th degree polynomial + - `"spline"`: Cubic Spline + + Defaults to "4th_poly". + + Returns: + self: Fitted Dmax model instance. + """ + self._si = si + + if si > 0: + y_lt1 = y[0] + si + self.y_lt1 = y_lt1 + else: + raise ValueError(f"Impossible lactate change to find LT1: {si}") + + self.y = y + + super().fit(X, y, **kwargs) + return self + + def predict(self) -> float: + """ + Predicts intensity for the LT1_si method. + + Returns: + float: Predicted intensity. + """ + return self.model.predict_inverse(self.y_lt1) diff --git a/src/lactopy/lactate_models/lt1_si_lowest.py b/src/lactopy/lactate_models/lt1_si_lowest.py new file mode 100644 index 0000000..ad7d67d --- /dev/null +++ b/src/lactopy/lactate_models/lt1_si_lowest.py @@ -0,0 +1,95 @@ +from numpy.typing import ArrayLike +import numpy as np + + +from lactopy.base import BaseModel +from lactopy.plots.lt1_si_lowest_plot import LT1_si_lowest_Plot + + +class LT1_si_lowest(BaseModel): + """ + Standard increment model for lactate threshold estimation. + + The Standard increment method is used to estimate the first lactate + threshold by identifying + the point in which the first meaningfull lactate increase occurs. + In LT1_si_lowest the lowest measurment of lactate concentration + is used as reference point. + This means that that the reference measuement is not necessarily + equal to the first measurement. + + This is computed by using a standard value, preferably equlual to the lowest + detectable change in lactate concentration of the measurement tool + + Attributes: + plot (lt1_si_lowest_plot): For visualizing the model. + """ + + def __init__(self): + """ + Initializes the LT1_si_lowest model and its associated plot. + + """ + super().__init__() + self.plot = LT1_si_lowest_Plot(self) + + def fit( + self, + X: ArrayLike, + y: ArrayLike, + si: float = 0.5, + **kwargs, + ): + """ + Fits the Standard Increment model to the given data. + + Args: + X (ArrayLike): The independent variable (e.g., intensity). + y (ArrayLike): The dependent variable (e.g., lactate concentration). + si: the standard increment, defaults to 0.5 + + threshold_above_baseline (float, optional): Threshold above baseline + for filtering in the "modified" implementation. Defaults to 0.5. + + method (str): + Method to use for fitting the model. Options are: + + - `"3th_poly"`: 3rd degree polynomial + - `"4th_poly"`: 4th degree polynomial + - `"spline"`: Cubic Spline + + Defaults to "4th_poly". + + Returns: + self: Fitted LT1_si_lowest model instance. + """ + self._si = si + + if si > 0: + min_y_idx = np.argmin(y) # get the index of the lowest y + self.min_y_idx = min_y_idx + self.x_at_min_y = X[min_y_idx] + self.y_lt1 = y[self.min_y_idx] + si + else: + raise ValueError(f"Impossible lactate change to find LT1: {si}") + + # Use only values AFTER x_at_min_y for model fitting + valid_mask = X >= self.x_at_min_y + if not np.any(valid_mask): + raise ValueError( + "No X values greater than X at minimum Y. Cannot fit model." + ) + + super().fit(self.X, self.y, _mask=valid_mask, **kwargs) + return self + + def predict(self) -> float: + """ + Predicts intensity (X) at LT1 based on y_lt1. + """ + prediction = self.model.predict_inverse(self.y_lt1) + + if prediction <= self.x_at_min_y: + raise ValueError("Predicted LT1 intensity is not greater than baseline.") + + return prediction diff --git a/src/lactopy/lactate_models/lt2_breakpoint.py b/src/lactopy/lactate_models/lt2_breakpoint.py new file mode 100644 index 0000000..06973d4 --- /dev/null +++ b/src/lactopy/lactate_models/lt2_breakpoint.py @@ -0,0 +1,79 @@ +from numpy.typing import ArrayLike +import piecewise_regression + +from lactopy.base import BaseModel +from lactopy.plots.lt2_breakpoint_plot import LT2_breakpoint_Plot + + +class LT2_breakpoint(BaseModel): + """ + Determining both LT1 and LT2 based on the breakpoints in the lactate curve + Piecewise regression module is used to identify the breakpoints + + Plot is divided into 3 segments --> segmented regression identifies breakpoints + + The second breakpoint which corresponds to LT2 is extracted + + Attributes: + plot (lt2_breakpoint_plot): For visualizing the model. + """ + + def __init__(self): + """ + Initializes the LT2_breakpoint model and its associated plot. + + """ + super().__init__() + self.plot = LT2_breakpoint_Plot(self) + + def fit( + self, + X: ArrayLike, + y: ArrayLike, + **kwargs, + ): + """ + Fits the LT2_breakpoint model to the given data. + + Args: + X (ArrayLike): The independent variable (e.g., intensity). + y (ArrayLike): The dependent variable (e.g., lactate concentration). + si: the standard increment, defaults to 0.5 + + method (str): + Method to use for fitting the model. Options are: + + - `"3th_poly"`: 3rd degree polynomial + - `"4th_poly"`: 4th degree polynomial + - `"spline"`: Cubic Spline + + Defaults to "4th_poly". + + Returns: + self: Fitted LT2_breakpoint model instance. + """ + super().fit(X, y, **kwargs) + return self + + def predict(self) -> float: + """ + Predicts intensity for the LT2_breakpoint method. + + Returns: + float: Predicted intensity. + + """ + + pw_fit = piecewise_regression.Fit(self.X, self.y, n_breakpoints=2) + pw_results = pw_fit.get_results() + + if "breakpoint2" not in pw_results.get("estimates", {}): + raise ValueError( + f"'breakpoint2' not found in regression results." + f"It is recommended to use a different treshold estimation method." + f"Piecewise regression may have failed. Result: {pw_results}" + ) + else: + predicted_lt2 = pw_results["estimates"]["breakpoint2"]["estimate"] + + return predicted_lt2 diff --git a/src/lactopy/lactate_models/OBLA.py b/src/lactopy/lactate_models/obla.py similarity index 80% rename from src/lactopy/lactate_models/OBLA.py rename to src/lactopy/lactate_models/obla.py index e221a2c..db1f143 100644 --- a/src/lactopy/lactate_models/OBLA.py +++ b/src/lactopy/lactate_models/obla.py @@ -7,7 +7,7 @@ class OBLA(BaseModel): This model uses a 3rd degree polynomial to fit the lactate data. """ - def predict(self, lactate_value: float) -> float: + def predict(self, X: float) -> float: """ Predicts intensity at a given lactate value. @@ -19,4 +19,4 @@ def predict(self, lactate_value: float) -> float: float: Predicted intensity. """ - return self.model.predict_inverse(lactate_value) + return self.model.predict_inverse(X) diff --git a/src/lactopy/plots/Dmax.py b/src/lactopy/plots/Dmax.py new file mode 100644 index 0000000..7753786 --- /dev/null +++ b/src/lactopy/plots/Dmax.py @@ -0,0 +1,29 @@ +import numpy as np +from lactopy.plots.base import Plot + + +class DmaxPlot(Plot): + """ + Plot for Dmax model. + """ + + _title = "Dmax Model Fit" + + def plot_predictions(self): + """ + Plot the Dmax model predictions. + """ + plot = super().plot_predictions() + X = np.linspace( + self.base_lactate_model.X.min(), + self.base_lactate_model.X.max(), + 100, + ) + Y = np.linspace( + self.base_lactate_model.y.min(), + self.base_lactate_model.y.max(), + 100, + ) + plot = Plot.add_line(plot, X, Y, color="blue") + + return plot diff --git a/src/lactopy/plots/base.py b/src/lactopy/plots/base.py index 8c35735..a444cd2 100644 --- a/src/lactopy/plots/base.py +++ b/src/lactopy/plots/base.py @@ -1,6 +1,9 @@ +import inspect + from typing import TYPE_CHECKING import matplotlib.pyplot as plt import numpy as np +from numpy.typing import ArrayLike if TYPE_CHECKING: from lactopy.lactate_models.base import BaseModel @@ -11,8 +14,10 @@ class Plot: Base class for all plots. """ - def __init__(self, base_lactate_model: "BaseModel"): - self.base_lactate_model = base_lactate_model + _title = "Lactate Model Plot" + + def __init__(self, context: "BaseModel"): + self.base_lactate_model = context def __call__(self): return self.plot_fit() @@ -26,29 +31,65 @@ def plot_fit(self): ) plt.figure(figsize=(10, 6)) plt.scatter( - self.base_lactate_model.X, - self.base_lactate_model.y, + ( + self.base_lactate_model.X_raw_for_plot + if hasattr(self.base_lactate_model, "X_raw_for_plot") + else self.base_lactate_model.X + ), + ( + self.base_lactate_model.y_raw_for_plot + if hasattr(self.base_lactate_model, "y_raw_for_plot") + else self.base_lactate_model.y + ), color="blue", label="Data", ) plt.plot( X, self.base_lactate_model.model.predict(X), color="red", label="Model" ) - plt.title("Lactate Model Fit") + plt.title(self.__class__._title) plt.xlabel("Intensity") plt.ylabel("Lactate") plt.legend() return plt.gca() - def plot_predictions(self, X): + @classmethod + def add_threshold_lines(self, ax: plt.Axes, threshold: float, color: str = "black"): + # TODO: create a seprate builder class for buidling plots (even building ) + """ + Add threshold lines to the plot. + + Args: + threshold (float): The threshold value. + color (str): Color of the threshold line. + """ + ax.axvline(x=threshold, color=color, linestyle="--", label="Threshold") + ax.legend() + return ax + + @classmethod + def add_line(cls, ax: plt.Axes, x: ArrayLike, y: ArrayLike, color: str = "blue"): + """ + Add a straight line to the plot. + + Args: + ax (plt.Axes): The axes to plot on. + x (array-like): The x-coordinates of the line. + y (array-like): The y-coordinates of the line. + color (str): Color of the line. + """ + ax.plot(x, y, color=color, label="Line") + return ax + + def plot_predictions(self, X=None): """ Plot the model predictions. """ - self.plot_fit() - plt.axvline( - self.base_lactate_model.predict(X), - color="black", - label="Predictions", - linestyle="--", + predict_x = ( + self.base_lactate_model.predict(X) + if "X" in inspect.getfullargspec(self.base_lactate_model.predict).args + else self.base_lactate_model.predict() ) - return plt.gca() + ax = self.plot_fit() + Plot.add_threshold_lines(ax, predict_x) + return ax diff --git a/src/lactopy/plots/lt1_loglog_plot.py b/src/lactopy/plots/lt1_loglog_plot.py new file mode 100644 index 0000000..c476c44 --- /dev/null +++ b/src/lactopy/plots/lt1_loglog_plot.py @@ -0,0 +1,5 @@ +from lactopy.plots.base import Plot + + +class LT1_loglog_Plot(Plot): + _title = "LT1 Log-Log Plot" diff --git a/src/lactopy/plots/lt1_lt2_breakpoint_plot.py b/src/lactopy/plots/lt1_lt2_breakpoint_plot.py new file mode 100644 index 0000000..e869fae --- /dev/null +++ b/src/lactopy/plots/lt1_lt2_breakpoint_plot.py @@ -0,0 +1,19 @@ +from lactopy.plots.base import Plot + + +class LT1_LT2_breakpoint_Plot(Plot): + """ + Plot for LT1_LT2 breakpoint model. + """ + + _title = "LT1-LT2 Breakpoint Plot" + + def plot_predictions(self): + """ + Plot the LT1 and LT2 model predictions. + """ + ax = self.plot_fit() + result = self.base_lactate_model.predict() + ax = Plot.add_threshold_lines(ax, result.lt1, color="black") + ax = Plot.add_threshold_lines(ax, result.lt2, color="red") + return ax diff --git a/src/lactopy/plots/lt1_si_lowest_plot.py b/src/lactopy/plots/lt1_si_lowest_plot.py new file mode 100644 index 0000000..7090742 --- /dev/null +++ b/src/lactopy/plots/lt1_si_lowest_plot.py @@ -0,0 +1,9 @@ +from lactopy.plots.base import Plot + + +class LT1_si_lowest_Plot(Plot): + """ + Plot for LT1 si lowest model. + """ + + _title = "LT1 Standard Increment From Lowest La- Measurement Plot" diff --git a/src/lactopy/plots/lt1_si_plot.py b/src/lactopy/plots/lt1_si_plot.py new file mode 100644 index 0000000..8ba3b8a --- /dev/null +++ b/src/lactopy/plots/lt1_si_plot.py @@ -0,0 +1,9 @@ +from lactopy.plots.base import Plot + + +class LT1_si_Plot(Plot): + """ + Plot for LT1 si model. + """ + + _title = "LT1 Standard Increment Model" diff --git a/src/lactopy/plots/lt2_breakpoint_plot.py b/src/lactopy/plots/lt2_breakpoint_plot.py new file mode 100644 index 0000000..e1d2f1c --- /dev/null +++ b/src/lactopy/plots/lt2_breakpoint_plot.py @@ -0,0 +1,9 @@ +from lactopy.plots.base import Plot + + +class LT2_breakpoint_Plot(Plot): + """ + Plot for LT2_breakpoint model. + """ + + _title = "LT2 Breakpoint Plot" diff --git a/tests/test_obla.py b/tests/test_obla.py index 2ee261a..37c6727 100644 --- a/tests/test_obla.py +++ b/tests/test_obla.py @@ -1,9 +1,9 @@ import pytest import numpy as np -from lactopy.lactate_models.OBLA import OBLA -from lactopy.lactate_models.bsln import Bsln -from lactopy.lactate_models.dmax import Dmax +from lactopy.lactate_models import OBLA +from lactopy.lactate_models import Bsln +from lactopy.lactate_models import Dmax @pytest.mark.parametrize( @@ -27,21 +27,25 @@ def test_obla_model(input_data, method, lactate, expected_value): @pytest.mark.parametrize( - "method, expected_value", + "method, impl, expected_value", [ - ("3th_poly", 131.1), # values obtained from physlab - ("4th_poly", 131.1), - ("spline", 136.99), + ("3th_poly", "normal", 131.1), # values obtained from physlab + ("4th_poly", "normal", 131.1), + ("spline", "normal", 136.99), + ("3th_poly", "modified", 131.1), # values obtained from physlab + ("4th_poly", "modified", 131.1), + ("spline", "modified", 136.99), ], ) -def test_dmax_model(input_data, method, expected_value): +def test_dmax_model(input_data, method, impl, expected_value): lactate_array, intensity_array = input_data dmax_model = Dmax() predicted_lactate = dmax_model.fit( - intensity_array, lactate_array, method=method + intensity_array, lactate_array, method=method, impl=impl ).predict() assert isinstance(predicted_lactate, float) - assert np.isclose(predicted_lactate, expected_value, atol=3) + if not impl == "modified": + assert np.isclose(predicted_lactate, expected_value, atol=3) @pytest.mark.parametrize("method", ["3th_poly", "4th_poly", "spline"]) diff --git a/uv.lock b/uv.lock index 2ad97fb..ca9cc47 100644 --- a/uv.lock +++ b/uv.lock @@ -808,6 +808,7 @@ version = "0.1.0" source = { editable = "." } dependencies = [ { name = "numpy" }, + { name = "piecewise-regression" }, { name = "scikit-learn" }, { name = "scipy" }, ] @@ -821,6 +822,7 @@ dev = [ { name = "mkdocs-literate-nav" }, { name = "mkdocs-material" }, { name = "mkdocstrings-python" }, + { name = "nb-clean" }, { name = "pandas" }, { name = "pre-commit" }, { name = "pytest" }, @@ -829,6 +831,7 @@ dev = [ [package.metadata] requires-dist = [ { name = "numpy", specifier = ">=2.2.5" }, + { name = "piecewise-regression", specifier = ">=1.5.0" }, { name = "scikit-learn", specifier = ">=1.6.1" }, { name = "scipy", specifier = ">=1.15.2" }, ] @@ -842,6 +845,7 @@ dev = [ { name = "mkdocs-literate-nav", specifier = ">=0.6.2" }, { name = "mkdocs-material", specifier = ">=9.6.12" }, { name = "mkdocstrings-python", specifier = ">=1.16.10" }, + { name = "nb-clean", specifier = ">=4.0.1" }, { name = "pandas", specifier = ">=2.2.3" }, { name = "pre-commit", specifier = ">=4.2.0" }, { name = "pytest", specifier = ">=8.3.5" }, @@ -1176,6 +1180,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/53/37/19549c5e0179785308cc988a68e16aa7550e4e270ec8a9878334e86070c6/mkdocstrings_python-1.16.10-py3-none-any.whl", hash = "sha256:63bb9f01f8848a644bdb6289e86dc38ceddeaa63ecc2e291e3b2ca52702a6643", size = 124112 }, ] +[[package]] +name = "nb-clean" +version = "4.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nbformat" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/81/2ef0aaf3df03ec4146a92d62ce9b9533d368431791c7e5cb8e027068794a/nb_clean-4.0.1.tar.gz", hash = "sha256:f4af1bec3f25e7b18eb09024947bcc809efc97fd68bec482d219c304d850809f", size = 24170 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/be/dd0d1c964fa8e2c645bb9298290c761c34b292ef4b2c113018f1b3c4cd90/nb_clean-4.0.1-py3-none-any.whl", hash = "sha256:6a673775523ad5ae18566bb0880012c169944a9357760dfcf993cdb14e2e5f82", size = 21355 }, +] + [[package]] name = "nbclient" version = "0.10.2" @@ -1404,6 +1420,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, ] +[[package]] +name = "patsy" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/81/74f6a65b848ffd16c18f920620ce999fe45fe27f01ab3911260ce4ed85e4/patsy-1.0.1.tar.gz", hash = "sha256:e786a9391eec818c054e359b737bbce692f051aee4c661f4141cc88fb459c0c4", size = 396010 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/2b/b50d3d08ea0fc419c183a84210571eba005328efa62b6b98bc28e9ead32a/patsy-1.0.1-py2.py3-none-any.whl", hash = "sha256:751fb38f9e97e62312e921a1954b81e1bb2bcda4f5eeabaf94db251ee791509c", size = 232923 }, +] + [[package]] name = "pexpect" version = "4.9.0" @@ -1416,6 +1444,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772 }, ] +[[package]] +name = "piecewise-regression" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "matplotlib" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "statsmodels" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/21/77472bd4937b1335d05f2e6732eb37ff67245f1e7e3c15f16e44f6476370/piecewise-regression-1.5.0.tar.gz", hash = "sha256:77d0111e2db06f077cf23ca5ca0b3e99cd852ec08a3c03c6bae23506f3c20612", size = 22941 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/4d/5d1279dba139e7e6bb81c45cf600f2122471d1eb2caba71175bfa7155900/piecewise_regression-1.5.0-py3-none-any.whl", hash = "sha256:9666346fca04db39aaddc8ceee0a22d06c3ee3fa7c58149487c020faf791a9b8", size = 24433 }, +] + [[package]] name = "pillow" version = "11.2.1" @@ -2049,6 +2092,45 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521 }, ] +[[package]] +name = "statsmodels" +version = "0.14.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "patsy" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/cc/8c1bf59bf8203dea1bf2ea811cfe667d7bcc6909c83d8afb02b08e30f50b/statsmodels-0.14.5.tar.gz", hash = "sha256:de260e58cccfd2ceddf835b55a357233d6ca853a1aa4f90f7553a52cc71c6ddf", size = 20525016 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/2c/55b2a5d10c1a211ecab3f792021d2581bbe1c5ca0a1059f6715dddc6899d/statsmodels-0.14.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9fc2b5cdc0c95cba894849651fec1fa1511d365e3eb72b0cc75caac44077cd48", size = 10058241 }, + { url = "https://files.pythonhosted.org/packages/66/d9/6967475805de06691e951072d05e40e3f1c71b6221bb92401193ee19bd2a/statsmodels-0.14.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b8d96b0bbaeabd3a557c35cc7249baa9cfbc6dd305c32a9f2cbdd7f46c037e7f", size = 9734017 }, + { url = "https://files.pythonhosted.org/packages/df/a8/803c280419a7312e2472969fe72cf461c1210a27770a662cbe3b5cd7c6fe/statsmodels-0.14.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:145bc39b2cb201efb6c83cc3f2163c269e63b0d4809801853dec6f440bd3bc37", size = 10459677 }, + { url = "https://files.pythonhosted.org/packages/a1/25/edf20acbd670934b02cd9344e29c9a03ce040122324b3491bb075ae76b2d/statsmodels-0.14.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7c14fb2617bb819fb2532e1424e1da2b98a3419a80e95f33365a72d437d474e", size = 10678631 }, + { url = "https://files.pythonhosted.org/packages/64/22/8b1e38310272e766abd6093607000a81827420a3348f09eff08a9e54cbaf/statsmodels-0.14.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1e9742d8a5ac38a3bfc4b7f4b0681903920f20cbbf466d72b1fd642033846108", size = 10699273 }, + { url = "https://files.pythonhosted.org/packages/d1/6f/6de51f1077b7cef34611f1d6721392ea170153251b4d977efcf6d100f779/statsmodels-0.14.5-cp310-cp310-win_amd64.whl", hash = "sha256:1cab9e6fce97caf4239cdb2df375806937da5d0b7ba2699b13af33a07f438464", size = 9644785 }, + { url = "https://files.pythonhosted.org/packages/14/30/fd49902b30416b828de763e161c0d6e2cc04d119ae4fbdd3f3b43dc8f1be/statsmodels-0.14.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b7091a8442076c708c926de3603653a160955e80a2b6d931475b7bb8ddc02e5", size = 10053330 }, + { url = "https://files.pythonhosted.org/packages/ca/c1/2654541ff6f5790d01d1e5ba36405fde873f4a854f473e90b4fe56b37333/statsmodels-0.14.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:128872be8f3208f4446d91ea9e4261823902fc7997fee7e1a983eb62fd3b7c6e", size = 9735555 }, + { url = "https://files.pythonhosted.org/packages/ce/da/6ebb64d0db4e86c0d2d9cde89e03247702da0ab191789f7813d4f9a348da/statsmodels-0.14.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f2ad5aee04ae7196c429df2174df232c057e478c5fa63193d01c8ec9aae04d31", size = 10307522 }, + { url = "https://files.pythonhosted.org/packages/67/49/ac803ca093ec3845184a752a91cd84511245e1f97103b15cfe32794a3bb0/statsmodels-0.14.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f402fc793458dd6d96e099acb44cd1de1428565bf7ef3030878a8daff091f08a", size = 10474665 }, + { url = "https://files.pythonhosted.org/packages/f0/c8/ae82feb00582f4814fac5d2cb3ec32f93866b413cf5878b2fe93688ec63c/statsmodels-0.14.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:26c028832730aebfbfd4e7501694e1f9ad31ec8536e776716673f4e7afd4059a", size = 10713120 }, + { url = "https://files.pythonhosted.org/packages/05/ac/4276459ea71aa46e2967ea283fc88ee5631c11f29a06787e16cf4aece1b8/statsmodels-0.14.5-cp311-cp311-win_amd64.whl", hash = "sha256:ec56f771d9529cdc17ed2fb2a950d100b6e83a7c5372aae8ac5bb065c474b856", size = 9640980 }, + { url = "https://files.pythonhosted.org/packages/5f/a5/fcc4f5f16355660ce7a1742e28a43e3a9391b492fc4ff29fdd6893e81c05/statsmodels-0.14.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:37e7364a39f9aa3b51d15a208c2868b90aadb8412f868530f5cba9197cb00eaa", size = 10042891 }, + { url = "https://files.pythonhosted.org/packages/1c/6f/db0cf5efa48277ac6218d9b981c8fd5e63c4c43e0d9d65015fdc38eed0ef/statsmodels-0.14.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4263d7f4d0f1d5ac6eb4db22e1ee34264a14d634b9332c975c9d9109b6b46e12", size = 9698912 }, + { url = "https://files.pythonhosted.org/packages/4a/93/4ddc3bc4a59c51e6a57c49df1b889882c40d9e141e855b3517f6a8de3232/statsmodels-0.14.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:86224f6e36f38486e471e75759d241fe2912d8bc25ab157d54ee074c6aedbf45", size = 10237801 }, + { url = "https://files.pythonhosted.org/packages/66/de/dc6bf2f6e8c8eb4c5815560ebdbdf2d69a767bc0f65fde34bc086cf5b36d/statsmodels-0.14.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c3dd760a6fa80cd5e0371685c697bb9c2c0e6e1f394d975e596a1e6d0bbb9372", size = 10424154 }, + { url = "https://files.pythonhosted.org/packages/16/4f/2d5a8d14bebdf2b03b3ea89b8c6a2c837bb406ba5b7a41add8bd303bce29/statsmodels-0.14.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6264fb00e02f858b86bd01ef2dc05055a71d4a0cc7551b9976b07b0f0e6cf24f", size = 10652915 }, + { url = "https://files.pythonhosted.org/packages/df/4c/2feda3a9f0e17444a84ba5398ada6a4d2e1b8f832760048f04e2b8ea0c41/statsmodels-0.14.5-cp312-cp312-win_amd64.whl", hash = "sha256:b2ed065bfbaf8bb214c7201656df840457c2c8c65e1689e3eb09dc7440f9c61c", size = 9611236 }, + { url = "https://files.pythonhosted.org/packages/84/fd/4c374108cf108b3130240a5b45847a61f70ddf973429044a81a05189b046/statsmodels-0.14.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:906263134dd1a640e55ecb01fda4a9be7b9e08558dba9e4c4943a486fdb0c9c8", size = 10013958 }, + { url = "https://files.pythonhosted.org/packages/5a/36/bf3d7f0e36acd3ba9ec0babd79ace25506b6872780cbd710fb7cd31f0fa2/statsmodels-0.14.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9118f76344f77cffbb3a9cbcff8682b325be5eed54a4b3253e09da77a74263d3", size = 9674243 }, + { url = "https://files.pythonhosted.org/packages/90/ce/a55a6f37b5277683ceccd965a5828b24672bbc427db6b3969ae0b0fc29fb/statsmodels-0.14.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9dc4ee159070557c9a6c000625d85f653de437772fe7086857cff68f501afe45", size = 10219521 }, + { url = "https://files.pythonhosted.org/packages/1e/48/973da1ee8bc0743519759e74c3615b39acdc3faf00e0a0710f8c856d8c9d/statsmodels-0.14.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a085d47c8ef5387279a991633883d0e700de2b0acc812d7032d165888627bef", size = 10453538 }, + { url = "https://files.pythonhosted.org/packages/c7/d6/18903fb707afd31cf1edaec5201964dbdacb2bfae9a22558274647a7c88f/statsmodels-0.14.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9f866b2ebb2904b47c342d00def83c526ef2eb1df6a9a3c94ba5fe63d0005aec", size = 10681584 }, + { url = "https://files.pythonhosted.org/packages/44/d6/80df1bbbfcdc50bff4152f43274420fa9856d56e234d160d6206eb1f5827/statsmodels-0.14.5-cp313-cp313-win_amd64.whl", hash = "sha256:2a06bca03b7a492f88c8106103ab75f1a5ced25de90103a89f3a287518017939", size = 9604641 }, +] + [[package]] name = "threadpoolctl" version = "3.6.0"