<?xml version="1.0" encoding="UTF-8"?>
<rss  xmlns:atom="http://www.w3.org/2005/Atom" 
      xmlns:media="http://search.yahoo.com/mrss/" 
      xmlns:content="http://purl.org/rss/1.0/modules/content/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      version="2.0">
<channel>
<title>velodata</title>
<link>https://velodata.ai/blog/</link>
<atom:link href="https://velodata.ai/blog/index.xml" rel="self" type="application/rss+xml"/>
<description>The Mathematical Beauty of Cycling. Data science, applied maths and modelling for professional cycling.</description>
<generator>quarto-1.10.18</generator>
<lastBuildDate>Thu, 01 Oct 2026 00:00:00 GMT</lastBuildDate>
<item>
  <title>Tubeless vs. Tubular: Are SRAM’s Claims Inflated?</title>
  <dc:creator>Juanfran Garamendi</dc:creator>
  <link>https://velodata.ai/blog/sram-tubeless-vs-tubular/</link>
  <description><![CDATA[ 




<p>In the fast-paced world of cycling, innovation is the name of the game. With technology constantly evolving, riders and manufacturers are always on the hunt for that extra edge to gain a competitive advantage. One of the latest debates in the cycling world revolves around the choice between traditional tubular/tube tires and the newer tubeless systems. SRAM, the company behind ZIPP wheels, recently made waves by claiming that their tubeless system outperforms the traditional tubular setup. Their claims stem from a single, one-time test conducted in the demanding and brutal cobbled section of the Paris-Roubaix Trouée d’Arenberg. But, do these claims rest upon robust scientific methodology, or are they merely a case of pseudo-scientific testing designed to serve an ulterior motive as it can be pure marketing?</p>
<p>Note: You can find SRAM’s article <a href="https://www.sram.com/en/life/stories/tubeless-at-trouee-darenberg">here</a>.</p>
<section id="too-long-didnt-read-tldr" class="level2">
<h2 class="anchored" data-anchor-id="too-long-didnt-read-tldr">Too Long; Didn’t Read (TL;DR)</h2>
<p>SRAM claims tubeless tire superiority, but upon closer scrutiny, concerns arise. Their test, conducted with the Movistar cycling team, offers a limited view of the overall picture. Let’s break down the key issues:</p>
<ol type="1">
<li><p>SRAM’s experiment involved altering tire pressure between runs. This introduces a confounding variable, making it impossible to determine if the observed improvements are due to the tubeless system or simply changes in tire pressure. Controlled conditions, where the only variable is the tire type, are essential for drawing reliable conclusions.</p></li>
<li><p>The differences SRAM reports are well within normal ride-to-ride variability: with no change of setup at all, differences this large would appear between 34.9% and 84% of the time (or even more often).</p></li>
<li><p>Assuming that the unknown standard deviation falls within the lowest 98% of a real historical variability data and considering typical probabilities of making Type I and Type II errors (5% and 20%, respectively), the required number of rides per setup should exceed 28. If we aim for lower error rates (1% for Type I and 10% for Type II), the necessary number of rides per setup increases to 53.</p></li>
</ol>
<p>In essence, while SRAM’s claims appear intriguing, the experiment’s limitations raise significant doubts. The need for controlled testing conditions, a larger quantity of data, and stringent statistical analysis is evident to draw concrete conclusions about the superiority of tubeless tires.</p>
<p>In the subsequent sections, we will explore the intricacies of the analysis, the statistical considerations, and the critical factors that influence the interpretation of these findings. This deeper exploration will shed light on the nuances and potential limitations of the data, providing a more comprehensive understanding of the evaluation process.</p>
<p>So, let’s venture further into the analytical terrain to uncover the full story behind the numbers.</p>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Important
</div>
</div>
<div class="callout-body-container callout-body">
<p>It is crucial to clarify that this article does not attempt to prove that SRAM’s claims are wrong, suggesting there is no difference between the tubular setup and the tubeless system. This article emphasizes that there is not enough data to assert that tubeless tires are better than tubular tires. The article centers on the need for more robust data and controlled testing conditions to draw meaningful conclusions about the relative merits of these tire systems.</p>
</div>
</div>
</section>
<section id="introduction" class="level2">
<h2 class="anchored" data-anchor-id="introduction">Introduction</h2>
<p>SRAM’s claims are rooted in a test conducted at the challenging cobbled Arenberg sector, complete with precise protocol specifications. These details involve the participation of riders A and B, baseline runs utilizing Zipp 303 Firecrest Tubular paired with Continental 30mm tubular tires, and subsequent runs employing Zipp 303 Firecrest Tubeless wheels fitted with Continental GP5000 S TR 30mm tires at varying pressures. The parameters monitored encompassed time, power, and subjective evaluations concerning grip and comfort.</p>
<p>The reported facts by SRAM can be summarized as follows:</p>
<ul>
<li>In the initial tubeless trial, Rider B managed to save 48 watts compared to tubular tires while maintaining a similar time.</li>
<li>On the second tubeless trial, Rider B achieved a 21-second time improvement with a power output approximately identical to the previous run.</li>
<li>Rider A showcased similar advantages by achieving faster times at the same wattage or maintaining the same times with a lower power output.</li>
</ul>
<p>However, a pivotal error lies in extrapolating these specific results as universal truths and neglecting the consideration of intra-subject variability. Conducting multiple runs on the same segment and achieving precisely the same power and speed is, in fact, an impossibility. Variations in measurements are inevitable. The cause of these discrepancies resides in conditions influenced by uncontrollable variables such as wind, hand or foot positions, and the very nature of the road surface itself, particularly when it involves a challenging cobbled sector like the Forest of Arenberg.</p>
<p>As a concrete and real example, consider the real-world data (from my usual rides) collected on the same day (2023-03-08) for the same Strava segment <a href="https://www.strava.com/segments/27164832">‘Rotonda Sant Fost - Rotonda intermitja’</a> near Barcelona. Notably, these trials were conducted using the same bike with same setup on the same day. What catches the eye is the remarkable consistency in mean power output: 179.5 watts and 179.8 watts, nearly identical. Nevertheless, a substantial disparity of 18 seconds persists in the results. This perplexing outcome underscores the undeniable influence of uncontrollable variables on our measurements.</p>
<div id="358a8e51" class="cell" data-execution_count="1">
<details class="code-fold">
<summary>Show the code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> numpy <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> np</span>
<span id="cb1-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> pandas <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> pd</span>
<span id="cb1-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> os</span>
<span id="cb1-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> matplotlib.pyplot <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> plt</span>
<span id="cb1-5"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> seaborn <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> sns</span>
<span id="cb1-6"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> scipy <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> stats</span>
<span id="cb1-7"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> statsmodels.stats.api <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> sms</span>
<span id="cb1-8"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> fitter <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Fitter</span>
<span id="cb1-9"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> scipy.stats <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> beta</span>
<span id="cb1-10"></span>
<span id="cb1-11"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> warnings</span>
<span id="cb1-12">warnings.filterwarnings(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'ignore'</span>)</span>
<span id="cb1-13"></span>
<span id="cb1-14">np.random.seed(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># same jitter in the strip plot on every run</span></span></code></pre></div></div>
</details>
</div>
<div id="b7600630" class="cell" data-execution_count="2">
<details class="code-fold">
<summary>Show the code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1">SEGMENT_DATA_PATH <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'./data/all/'</span></span>
<span id="cb2-2"></span>
<span id="cb2-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Read data</span></span>
<span id="cb2-4">dfs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb2-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Iterate over each file in the folder</span></span>
<span id="cb2-6"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> file_name <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> os.listdir(SEGMENT_DATA_PATH):</span>
<span id="cb2-7">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Check if the file is a CSV file</span></span>
<span id="cb2-8">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> file_name.endswith(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'.csv'</span>):</span>
<span id="cb2-9">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Construct the full path to the file</span></span>
<span id="cb2-10">        file_path <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> os.path.join(SEGMENT_DATA_PATH, file_name)</span>
<span id="cb2-11"></span>
<span id="cb2-12">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Read the CSV file into a DataFrame</span></span>
<span id="cb2-13">        df <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd.read_csv(file_path)</span>
<span id="cb2-14"></span>
<span id="cb2-15">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Append the DataFrame to the list</span></span>
<span id="cb2-16">        dfs.append(df)</span>
<span id="cb2-17"></span>
<span id="cb2-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Concatenate all the DataFrames into a single DataFrame</span></span>
<span id="cb2-19">data_df <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd.concat(dfs, ignore_index<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb2-20"></span>
<span id="cb2-21"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## CLEAN DATA</span></span>
<span id="cb2-22"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Remove activities without power meter or wrong measurements</span></span>
<span id="cb2-23">data_df <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> data_df[data_df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Power Meter'</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb2-24"></span>
<span id="cb2-25"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Remove activities done in group</span></span>
<span id="cb2-26">data_df <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> data_df[data_df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Group Size'</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb2-27"></span>
<span id="cb2-28"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Keep the days with more than 2 measure.</span></span>
<span id="cb2-29">data_df <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> data_df.groupby([<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"When"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Name"</span>]).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">filter</span>(<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> x: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(x)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb2-30"></span>
<span id="cb2-31"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Remove hour in the date</span></span>
<span id="cb2-32">data_df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'When'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> data_df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'When'</span>].<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">apply</span>(<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> d : d[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>] )</span>
<span id="cb2-33"></span>
<span id="cb2-34"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Keep only Name, Date, Elapsed Time, Speed, Pace, Power</span></span>
<span id="cb2-35">data_df <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> data_df[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Name'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'When'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Elapsed Time'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Speed km/h'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Power (W)'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Dist km'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Gear'</span>]]</span>
<span id="cb2-36"></span>
<span id="cb2-37"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># The data is in SI units even if it said the opposite in the column name, so change it</span></span>
<span id="cb2-38">data_df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Speed km/h'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> data_df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Speed km/h'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.6</span></span>
<span id="cb2-39">data_df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Dist km'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> data_df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Dist km'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1000</span></span>
<span id="cb2-40"></span>
<span id="cb2-41"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Keep only segments between 2 and 3 kilometers</span></span>
<span id="cb2-42">data_df <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> data_df[ (data_df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Dist km'</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span> (data_df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Dist km'</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)]</span>
<span id="cb2-43"></span>
<span id="cb2-44"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># add a column with the Power to Speed ratio</span></span>
<span id="cb2-45">data_df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Pw/Sp'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> data_df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Power (W)'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> data_df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Speed km/h'</span>]</span>
<span id="cb2-46">data_df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Sp/Pw'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> data_df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Speed km/h'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> data_df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Power (W)'</span>]</span></code></pre></div></div>
</details>
</div>
<div id="12f3279e" class="cell" data-execution_count="3">
<details class="code-fold">
<summary>Show the code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1">data_df[data_df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'When'</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'2023-03-08'</span>][data_df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Name'</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Rotonda Sant Fost - Rotonda intermitja'</span>]</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-display" data-execution_count="3">
<div>


<table class="dataframe caption-top table table-sm table-striped small" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">Name</th>
<th data-quarto-table-cell-role="th">When</th>
<th data-quarto-table-cell-role="th">Elapsed Time</th>
<th data-quarto-table-cell-role="th">Speed km/h</th>
<th data-quarto-table-cell-role="th">Power (W)</th>
<th data-quarto-table-cell-role="th">Dist km</th>
<th data-quarto-table-cell-role="th">Gear</th>
<th data-quarto-table-cell-role="th">Pw/Sp</th>
<th data-quarto-table-cell-role="th">Sp/Pw</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<th data-quarto-table-cell-role="th">8050</th>
<td>Rotonda Sant Fost - Rotonda intermitja</td>
<td>2023-03-08</td>
<td>335</td>
<td>30.737552</td>
<td>179.8</td>
<td>2.8603</td>
<td>Clementina Gran touring G</td>
<td>5.849522</td>
<td>0.170954</td>
</tr>
<tr class="even">
<th data-quarto-table-cell-role="th">8094</th>
<td>Rotonda Sant Fost - Rotonda intermitja</td>
<td>2023-03-08</td>
<td>353</td>
<td>29.170198</td>
<td>179.5</td>
<td>2.8603</td>
<td>Clementina Gran touring G</td>
<td>6.153541</td>
<td>0.162508</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
<p>In essence, the enhancement claimed by SRAM might be attributed to the intra-subject variability caused by the interference of uncontrolled variables, as exemplified by the presented data.</p>
<p>Additionally, the conclusions are derived from a comparison between tubular and tubeless setups, where the tire pressures differ, potentially accounting for the observed variations. Consequently, while the genuineness of the reported facts are not questioned, they remain inadequate for reaching a conclusive verdict and in the sections that follow, we provide an extensive analysis of our arguments.</p>
<div id="51a458b1" class="cell" data-execution_count="4">
<details class="code-fold">
<summary>Show the code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Compute mean and variance grouped by 'When' and 'Name' columns</span></span>
<span id="cb4-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> std(x):</span>
<span id="cb4-3">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> np.std(x, ddof<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb4-4"></span>
<span id="cb4-5">grouped_stats <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> data_df.groupby([<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'When'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Name'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Gear'</span>]).agg([<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'mean'</span>, std, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'count'</span>]).drop([(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Elapsed Time'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'count'</span>),</span>
<span id="cb4-6">                                                                                            (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Speed km/h'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'count'</span>),</span>
<span id="cb4-7">                                                                                            (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Power (W)'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'count'</span>),</span>
<span id="cb4-8">                                                                                            (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Pw/Sp'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'count'</span>),</span>
<span id="cb4-9">                                                                                            (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Dist km'</span>,)], axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb4-10"></span>
<span id="cb4-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Reset the index if needed</span></span>
<span id="cb4-12">grouped_stats <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> grouped_stats.reset_index()</span>
<span id="cb4-13"></span>
<span id="cb4-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Remove days with only one measure</span></span>
<span id="cb4-15">grouped_stats <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> grouped_stats[grouped_stats[(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Sp/Pw'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'count'</span>)]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb4-16"></span>
<span id="cb4-17">grouped_stats <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> grouped_stats.dropna()</span>
<span id="cb4-18"></span>
<span id="cb4-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># pw_std/Speed_std the smallest the better</span></span>
<span id="cb4-20">grouped_stats[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'ratio std'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> grouped_stats[(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Power (W)'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'std'</span>)]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span>grouped_stats[(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Speed km/h'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'std'</span>)]</span>
<span id="cb4-21"></span>
<span id="cb4-22">grouped_stats <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> grouped_stats.sort_values(by<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'ratio std'</span>, ascending<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb4-23"></span>
<span id="cb4-24">grouped_stats.head(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>)</span></code></pre></div></div>
</details>
</div>
</section>
<section id="understanding-intra-subject-variability-and-its-importance" class="level2">
<h2 class="anchored" data-anchor-id="understanding-intra-subject-variability-and-its-importance">Understanding Intra-subject Variability and its Importance</h2>
<p>To grasp the significance of intra-subject variability, it is essential to comprehend its essence. Intra-subject variability refers to the fluctuations observed within the same subject during multiple test runs. In our context, it signifies that even with the same rider and bike, variations in performance will occur due to uncontrollable factors. These factors might encompass the rider’s posture, muscle fatigue, minor deviations in tire pressure, or subtle variations in the road surface (particularly noticeable on a challenging cobble sector). The presence of these variables highlights the intricate web of influences that can lead to performance discrepancies.</p>
<p>To illustrate the impact of intra-subject variability, we turn to the wealth of data collected during countless hours of my training sessions on the La Roca Road. These sessions, meticulously prescribed by my trainer, Raul Celdran, primarily consist of extended series ridden at a consistent pace. As a result, I’ve amassed a comprehensive dataset on the Strava segments along my trainer road. This dataset serves as an excellent illustration of how uncontrollable factors can introduce variability into our results.</p>
<div id="4c6ac3da" class="cell" data-execution_count="5">
<details class="code-fold">
<summary>Show the code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Strip Plot for Univariate Visualization</span></span>
<span id="cb5-2">plt.figure(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>))</span>
<span id="cb5-3">sns.stripplot(x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>grouped_stats[(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Pw/Sp'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'std'</span>)], palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Set1"</span>, jitter<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb5-4">plt.xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Standard deviation of performance in Watts/Speed"</span>)</span>
<span id="cb5-5">plt.show()</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-display">
<div>
<figure class="figure">
<p><img src="https://velodata.ai/blog/sram-tubeless-vs-tubular/index_files/figure-html/cell-6-output-1.png" alt="Strip plot of the standard deviation of the watts per speed ratio across my repeated rides. Most points sit below 0.5 and a few reach 1.4." width="619" height="429" class="figure-img"></p>
</figure>
</div>
</div>
</div>
<p>In the provided graph, each data point signifies the variability, measured as the standard deviation, within a set of rides completed on the same day, along the same segment, using identical bike setups. The X-axis represents the standard deviation of the Power/Speed ratio measured, while the Y-axis is used merely to facilitate the distribution of data points for enhanced visual clarity.</p>
<p>To comprehend the standard deviation, consider this example: Suppose we have measurements for a specific day and segment, with a mean performance value of 5 watts per kilometer per hour (5 watts/(Km/h)) and a standard deviation of 0.7. This implies that, on average, for that particular day and segment, 5 watts are needed to maintain a speed of 1 kilometer per hour. The standard deviation of 0.7 reflects the average deviation of all measurements, from the mean. It quantifies the variability in performance, showing how results can diverge from the average under varying conditions. In other words, standard deviation can be seen as the average difference of a group of measurements with respect to its mean.</p>
<p>As depicted in the graph, the standard deviation in my data exhibits a range of variability, fluctuating from nearly zero (although never precisely 0) to 1.4 Watts per kilometer per hour (Watts/(Km/h)).</p>
<p>This distribution of standard deviations can be visualized more effectively through a histogram, which captures the frequency of different standard deviation values. Additionally, fitting the data to a Beta distribution allows us to model and understand the distribution of variability more comprehensively.</p>
<div id="74617a03" class="cell" data-execution_count="6">
<details class="code-fold">
<summary>Show the code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1">f <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Fitter(grouped_stats[(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Pw/Sp'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'std'</span>)],</span>
<span id="cb6-2">           distributions<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'beta'</span>])</span>
<span id="cb6-3">f.fit()</span>
<span id="cb6-4">f.summary()</span>
<span id="cb6-5">plt.xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Standard deviation of performance in Watts/Speed'</span>)</span>
<span id="cb6-6">plt.ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Frequency'</span>)</span>
<span id="cb6-7">plt.show()</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-display">
<div>
<figure class="figure">
<p><img src="https://velodata.ai/blog/sram-tubeless-vs-tubular/index_files/figure-html/cell-7-output-1.png" alt="Histogram of the standard deviations with the fitted Beta distribution on top. The mass is concentrated near zero with a long tail to the right." width="589" height="429" class="figure-img"></p>
</figure>
</div>
</div>
</div>
<div id="14422153" class="cell" data-execution_count="7">
<details class="code-fold">
<summary>Show the code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1">param <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> f.get_best(method <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'sumsquare_error'</span>)</span>
<span id="cb7-2"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>({name: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(value) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> name, value <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> param[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'beta'</span>].items()})</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>{'a': 1.199427276659787, 'b': 5.654111551576102, 'loc': 0.0005549308175234002, 'scale': 1.8742923083585157}</code></pre>
</div>
</div>
<p>The fitted Beta distribution provides insight into the variability of standard deviations in my training data. This Beta distribution is characterized by the following parameters:</p>
<ul>
<li>Shape Parameter (a): 1.1994</li>
<li>Shape Parameter (b): 5.6541</li>
<li>Location Parameter (loc): 0.0006</li>
<li>Scale Parameter (scale): 1.8743</li>
</ul>
<p>In this dataset, the median of the standard deviation is approximately 0.27. This means that 50% of my training sessions exhibit a standard deviation greater than 0.27. It’s worth emphasizing that a standard deviation of 0.27 in the Watts/(Km/h) units is relatively high. To put this into perspective, let’s revisit the example of the ‘Rotonda Sant Fost - Rotonda intermitja’ segment on a specific day (2023-03-08). On that day, the standard deviation was only 0.15, which is relatively low. Even with this lower standard deviation and consistent power output, there was still a significant difference of 18 seconds in a segment that is less than 3 kilometers in length.</p>
<p>Moreover, it’s noteworthy that 98% of the standard deviation values in this dataset fall below 1, indicating that most of the data points have a standard deviation within this range.</p>
<p>This analysis emphasizes the need to account for intra-subject (intra-group) variations when evaluating cycling performance and making training and racing decisions, something SRAM didn’t do.</p>
<div id="574c2c32" class="cell" data-execution_count="8">
<details class="code-fold">
<summary>Show the code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb9-1">probability_median <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> beta.cdf( <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.27</span>,</span>
<span id="cb9-2">    param[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'beta'</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'a'</span>],</span>
<span id="cb9-3">    param[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'beta'</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'b'</span>],</span>
<span id="cb9-4">    param[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'beta'</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'loc'</span>],</span>
<span id="cb9-5">    param[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'beta'</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'scale'</span>]</span>
<span id="cb9-6">    )</span>
<span id="cb9-7">probability_more_015 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>beta.cdf( <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.15</span>,</span>
<span id="cb9-8">    param[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'beta'</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'a'</span>],</span>
<span id="cb9-9">    param[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'beta'</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'b'</span>],</span>
<span id="cb9-10">    param[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'beta'</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'loc'</span>],</span>
<span id="cb9-11">    param[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'beta'</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'scale'</span>]</span>
<span id="cb9-12">    )</span>
<span id="cb9-13"></span>
<span id="cb9-14">probability_less_1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> beta.cdf( <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,</span>
<span id="cb9-15">    param[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'beta'</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'a'</span>],</span>
<span id="cb9-16">    param[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'beta'</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'b'</span>],</span>
<span id="cb9-17">    param[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'beta'</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'loc'</span>],</span>
<span id="cb9-18">    param[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'beta'</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'scale'</span>]</span>
<span id="cb9-19">    )</span>
<span id="cb9-20"></span>
<span id="cb9-21"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'Probability of make a std lower than 0.27: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>probability_median<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">'</span>)</span>
<span id="cb9-22"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'Probability of make a std bigger than 0.15: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>probability_more_015<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">'</span>)</span>
<span id="cb9-23"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'Probability of make a std less than 1: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>probability_less_1<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">'</span>)</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>Probability of make a std lower than 0.27: 0.5021593049537484
Probability of make a std bigger than 0.15: 0.7089318903118318
Probability of make a std less than 1: 0.9809097575208771</code></pre>
</div>
</div>
</section>
<section id="analysis-of-the-sram-data" class="level2">
<h2 class="anchored" data-anchor-id="analysis-of-the-sram-data">Analysis of the SRAM data</h2>
<p>Let’s start by visualizing the data collected by SRAM. The scatter plot below illustrates the performance of different setups in terms of ‘watts’ per kilometer per hour (watts/(km/h)).</p>
<div id="f8f3449b" class="cell" data-execution_count="9">
<details class="code-fold">
<summary>Show the code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb11-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Data from https://www.sram.com/en/life/stories/tubeless-at-trouee-darenberg</span></span>
<span id="cb11-2"></span>
<span id="cb11-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># The Trouée d'Arenberg or Tranchée de Wallers-Arenberg (English: Trench of Arenberg) is a 2.3 km long cobbled road in the municipality of Wallers in Northern France, in the Département Nord.</span></span>
<span id="cb11-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># What sram says is true, but it is indicative of a real difference?</span></span>
<span id="cb11-5">sram_data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>{ <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'rider'</span>: [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'rider A'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'rider A'</span>,<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'rider A'</span>,<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'rider A'</span>,<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'rider A'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'rider B'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'rider B'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'rider B'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'rider B'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'rider B'</span>],</span>
<span id="cb11-6">             <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'setup'</span> : [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'tubular'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'tubeless'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'tubeless'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'tubeless'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'tubeless'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'tubular'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'tubeless'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'tubeless'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'tubeless'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'tubeless'</span>],</span>
<span id="cb11-7">             <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'time'</span>: [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">243</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">247</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">230</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">233</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">230</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">257</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">255</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">238</span>, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>],</span>
<span id="cb11-8">             <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'watts'</span>: [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">390</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">347</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">376</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">397</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">394</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">271</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">223</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">275</span>, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>],</span>
<span id="cb11-9">             <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'pressure'</span>: [<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.5</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.5</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.5</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.5</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.5</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.5</span> ]}</span>
<span id="cb11-10"></span>
<span id="cb11-11">sram_df <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd.DataFrame.from_dict(sram_data)</span>
<span id="cb11-12"></span>
<span id="cb11-13">sram_df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'performance'</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>sram_df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'watts'</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>sram_df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'time'</span>]</span>
<span id="cb11-14"></span>
<span id="cb11-15">lenght_arenberg <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2300</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#meters</span></span>
<span id="cb11-16">sram_df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Speed km/h'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (lenght_arenberg<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span>sram_df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'time'</span>]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.6</span></span>
<span id="cb11-17"></span>
<span id="cb11-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># add a column with the Power to Speed ratio</span></span>
<span id="cb11-19">sram_df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'watts_per_kph'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sram_df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'watts'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> sram_df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Speed km/h'</span>]</span>
<span id="cb11-20"></span>
<span id="cb11-21">sram_df</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-display" data-execution_count="9">
<div>


<table class="dataframe caption-top table table-sm table-striped small" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">rider</th>
<th data-quarto-table-cell-role="th">setup</th>
<th data-quarto-table-cell-role="th">time</th>
<th data-quarto-table-cell-role="th">watts</th>
<th data-quarto-table-cell-role="th">pressure</th>
<th data-quarto-table-cell-role="th">performance</th>
<th data-quarto-table-cell-role="th">Speed km/h</th>
<th data-quarto-table-cell-role="th">watts_per_kph</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<th data-quarto-table-cell-role="th">0</th>
<td>rider A</td>
<td>tubular</td>
<td>243.0</td>
<td>390.0</td>
<td>4.5</td>
<td>94770.0</td>
<td>34.074074</td>
<td>11.445652</td>
</tr>
<tr class="even">
<th data-quarto-table-cell-role="th">1</th>
<td>rider A</td>
<td>tubeless</td>
<td>247.0</td>
<td>347.0</td>
<td>5.0</td>
<td>85709.0</td>
<td>33.522267</td>
<td>10.351329</td>
</tr>
<tr class="odd">
<th data-quarto-table-cell-role="th">2</th>
<td>rider A</td>
<td>tubeless</td>
<td>230.0</td>
<td>376.0</td>
<td>4.5</td>
<td>86480.0</td>
<td>36.000000</td>
<td>10.444444</td>
</tr>
<tr class="even">
<th data-quarto-table-cell-role="th">3</th>
<td>rider A</td>
<td>tubeless</td>
<td>233.0</td>
<td>397.0</td>
<td>4.0</td>
<td>92501.0</td>
<td>35.536481</td>
<td>11.171618</td>
</tr>
<tr class="odd">
<th data-quarto-table-cell-role="th">4</th>
<td>rider A</td>
<td>tubeless</td>
<td>230.0</td>
<td>394.0</td>
<td>3.5</td>
<td>90620.0</td>
<td>36.000000</td>
<td>10.944444</td>
</tr>
<tr class="even">
<th data-quarto-table-cell-role="th">5</th>
<td>rider B</td>
<td>tubular</td>
<td>257.0</td>
<td>271.0</td>
<td>4.5</td>
<td>69647.0</td>
<td>32.217899</td>
<td>8.411473</td>
</tr>
<tr class="odd">
<th data-quarto-table-cell-role="th">6</th>
<td>rider B</td>
<td>tubeless</td>
<td>255.0</td>
<td>223.0</td>
<td>5.0</td>
<td>56865.0</td>
<td>32.470588</td>
<td>6.867754</td>
</tr>
<tr class="even">
<th data-quarto-table-cell-role="th">7</th>
<td>rider B</td>
<td>tubeless</td>
<td>238.0</td>
<td>275.0</td>
<td>4.5</td>
<td>65450.0</td>
<td>34.789916</td>
<td>7.904589</td>
</tr>
<tr class="odd">
<th data-quarto-table-cell-role="th">8</th>
<td>rider B</td>
<td>tubeless</td>
<td>NaN</td>
<td>NaN</td>
<td>4.0</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
</tr>
<tr class="even">
<th data-quarto-table-cell-role="th">9</th>
<td>rider B</td>
<td>tubeless</td>
<td>NaN</td>
<td>NaN</td>
<td>3.5</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
<div id="3f702387" class="cell" data-execution_count="10">
<details class="code-fold">
<summary>Show the code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb12-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Graphical representation of the columns for 'pressure' and 'watts_per_kph'. Blue indicates the tubular setup, while orange represents the tubeless setup. Circles denote rider A, while crosses represent rider B.</span></span>
<span id="cb12-2">sns.relplot(x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'pressure'</span>, y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'watts_per_kph'</span>, data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>sram_df, hue<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'setup'</span>, style<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'rider'</span>)</span>
<span id="cb12-3">plt.xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Pressure in bars'</span>)</span>
<span id="cb12-4">plt.ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Performance as Power/Speed, Watts/(Km/h)"</span>)</span>
<span id="cb12-5">plt.title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Relationship between setup, pressure and performance for each rider"</span>)</span>
<span id="cb12-6">plt.show()</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-display">
<div>
<figure class="figure">
<p><img src="https://velodata.ai/blog/sram-tubeless-vs-tubular/index_files/figure-html/cell-11-output-1.png" alt="Scatter plot of performance in watts per km/h against tire pressure in bars, for riders A and B. The tubular runs sit above the tubeless runs at 4.5 bars." width="603" height="490" class="figure-img"></p>
</figure>
</div>
</div>
</div>
<p>The graph clearly illustrates that rides conducted with the tubeless system consistently outperform those executed with the tubular setup, but in SRAM’s test, a detailed study of variability is absent, and the only directly comparable values are those recorded at a tire pressure of 4.5 bars, which is the sole pressure setting with values for both setups.</p>
<p><strong>There is a 52% probability of detecting a (spurious) difference for rider B that, in reality, does not exist.</strong></p>
<p>When considering the 4.5-bar pressure scenario, we observe that the tubeless system outperforms the tubular by 1 Watt per kilometer/hour for rider A and 0.51 Watts per kilometer/hour for rider B. To provide context, when I analyze my own data, where I use the same bike and setup, measured on the same segment, I find that I encounter the same difference as rider A about 22% of the time, the same difference as rider B approximately 52% of the time, and the same difference as the average observed by riders A and B around 34.9% of the time.</p>
<p>This means that, when considering my own data, due to my intra-subject variability, I observe differences equivalent to those of rider A, rider B, and the mean of both riders the 22%, 52%, and 34.9% of the time, respectively and it’s crucial to understand that in reality, there is no such difference. These percentages are calculated based on the cumulative distribution function of the previously computed Beta function.</p>
<p>In conclusion, the data presented by SRAM indeed indicates a noticeable gap between measurements. However, we must interpret these findings cautiously. Real-world data, including my own observations, reflects similar differences, but it’s crucial to acknowledge that these disparities likely stem from uncontrolled and hide factors rather than an actual difference. This is evident because all rides were conducted with the same bike, on the same segment, during the same training session.</p>
<p>Note: The assumption that the variability of these Movistar cyclists on the highly demanding cobble section of the Trouée d’Arenberg is similar to my variability measured on a perfect tarmac provides an advantage to SRAM’s claims. The cobble section poses significantly greater challenges and introduces more uncontrolled variables, likely resulting in higher variability. This, in turn, makes the earlier percentages conservative: on the cobbles, differences of this size with no real cause behind them would be even more frequent, as the next paragraph shows.</p>
<div id="a79c4375" class="cell" data-execution_count="11">
<details class="code-fold">
<summary>Show the code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb13-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># the difference between the two measurements is 1, so the standard deviation is 0.5</span></span>
<span id="cb13-2">probability_more_05 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>beta.cdf( <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>,</span>
<span id="cb13-3">    param[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'beta'</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'a'</span>],</span>
<span id="cb13-4">    param[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'beta'</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'b'</span>],</span>
<span id="cb13-5">    param[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'beta'</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'loc'</span>],</span>
<span id="cb13-6">    param[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'beta'</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'scale'</span>]</span>
<span id="cb13-7">    )</span>
<span id="cb13-8"></span>
<span id="cb13-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># the difference between the two measurements is 0.51, so the standard deviation is 0.255</span></span>
<span id="cb13-10">probability_more_0255 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>beta.cdf(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.255</span>,</span>
<span id="cb13-11">    param[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'beta'</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'a'</span>],</span>
<span id="cb13-12">    param[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'beta'</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'b'</span>],</span>
<span id="cb13-13">    param[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'beta'</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'loc'</span>],</span>
<span id="cb13-14">    param[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'beta'</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'scale'</span>]</span>
<span id="cb13-15">    )</span>
<span id="cb13-16"></span>
<span id="cb13-17"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Mean value of the two riders</span></span>
<span id="cb13-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># the mean value of the two differences is 0.755</span></span>
<span id="cb13-19">probability_more_mean <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>beta.cdf(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.3775</span>,</span>
<span id="cb13-20">    param[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'beta'</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'a'</span>],</span>
<span id="cb13-21">    param[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'beta'</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'b'</span>],</span>
<span id="cb13-22">    param[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'beta'</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'loc'</span>],</span>
<span id="cb13-23">    param[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'beta'</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'scale'</span>]</span>
<span id="cb13-24">    )</span>
<span id="cb13-25"></span>
<span id="cb13-26"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'Probability of make a std more than 0.3775: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>probability_more_mean<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">'</span>)</span>
<span id="cb13-27"></span>
<span id="cb13-28"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Rider A, 1 is the difference in the values for 4.5 bars, i.e. std = 1/2</span></span>
<span id="cb13-29"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'Probability of make a std more than 0.5: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>probability_more_05<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">'</span>)</span>
<span id="cb13-30"></span>
<span id="cb13-31"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Rider B, 0.51 is the difference in the values for 4.5 bars, i.e. std = 0.51/2</span></span>
<span id="cb13-32"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'Probability of make a std more than 0.255: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>probability_more_0255<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">'</span>)</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>Probability of make a std more than 0.3775: 0.34911663478207977
Probability of make a std more than 0.5: 0.22305059979474806
Probability of make a std more than 0.255: 0.5216511969837742</code></pre>
</div>
</div>
<p><strong>From another approach: if there were no difference at all between the tubular and tubeless systems, a gap at least as large as the one SRAM measured would still show up 84% of the time.</strong></p>
<p>Now, let’s approach the data from a different perspective, and, even though it’s incorrect due to the non-weighted-normalized power data and the variations in tire pressures, let’s take a step that favors SRAM’s claims. Let’s categorize the data into two groups without distinguishing between the riders: the control group, which includes all tubular measurements (only 2 in total), and the treatment group, consisting of all tubeless measurements (6 in total). When we conduct an A/B test under these circumstances, the resulting p-value is 0.84. This means that if there were actually no difference between the tubular and tubeless systems, we would still see a gap between the two groups at least as large as this one 84% of the time. In other words, the data are entirely compatible with there being no difference.</p>
<p>Note: If you have reservations about utilizing the variability of my data as a reference, the recommended course of action is to estimate their own variability through a series of repeated rides, as I elaborate in the following section. In essence, regardless of the chosen approach, a significantly larger number of rides should have been carried out.</p>
<div id="5167e25b" class="cell" data-execution_count="12">
<details class="code-fold">
<summary>Show the code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb15" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb15-1">df_control <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sram_df[sram_df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'setup'</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'tubular'</span>]</span>
<span id="cb15-2">df_treat <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sram_df[sram_df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'setup'</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'tubeless'</span>].dropna()</span>
<span id="cb15-3"></span>
<span id="cb15-4">t_statistic, pval <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> stats.ttest_ind(df_control[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'watts_per_kph'</span>], df_treat[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'watts_per_kph'</span>])</span>
<span id="cb15-5"></span>
<span id="cb15-6"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(pval)</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>0.8417936710527993</code></pre>
</div>
</div>
</section>
<section id="how-should-have-been-conducted-a-simple-ab-test" class="level2">
<h2 class="anchored" data-anchor-id="how-should-have-been-conducted-a-simple-ab-test">How should have been conducted a simple A/B test</h2>
<p>The experiment’s goal is to assess the performance of two distinct bike setups (tubeless vs.&nbsp;tubular) under specific conditions using a defined metric. To achieve this, one potential approach is to execute a straightforward A/B test:</p>
<section id="step-1-metric-selection" class="level3">
<h3 class="anchored" data-anchor-id="step-1-metric-selection">Step 1: Metric Selection</h3>
<p>The first step in conducting an A/B test is to select a metric for comparison. This choice significantly influences the design of the test parameters. For instance, we can opt for either the time required to complete a segment or a power-to-speed metric:</p>
<ul>
<li><strong>Time to complete a segment:</strong> This metric offers straightforward interpretation but doesn’t permit the mixing of measurements from different cyclists.</li>
<li><strong>Power-to-weight ratio per kilometer per hour (‘(watts/kg)/(km/h)’):</strong> This more complex metric involves calculating the rider’s power-to-weight ratio (in watts per kilogram) divided by speed (in kilometers per hour). It allows for the comparison of rides with varying power outputs among different cyclists.</li>
</ul>
</section>
<section id="step-2-establish-the-minimum-effect" class="level3">
<h3 class="anchored" data-anchor-id="step-2-establish-the-minimum-effect">Step 2: Establish the Minimum Effect</h3>
<p>Define the minimum effect (minimum difference in means of the chosen metric) that justifies a move to the treatment group setup. For instance, if the chosen metric is time, an improvement of 3 to 5 seconds might be selected, as it represents the minimum gap time required to a cyclist being dropped from a group.</p>
</section>
<section id="step-3-establish-the-level-of-significance-and-power" class="level3">
<h3 class="anchored" data-anchor-id="step-3-establish-the-level-of-significance-and-power">Step 3: Establish the Level of Significance and Power</h3>
<p>In this critical step, we need to set the level of significance and determine the statistical power for our A/B test. These two parameters play pivotal roles in the design and interpretation of our experiment. Let’s define them:</p>
<ul>
<li><strong>Level of Significance:</strong> In the world of statistical hypothesis testing, the level of significance, denoted as alpha (α), is a crucial factor that helps us control the likelihood of making a Type I error. A Type I error occurs when we incorrectly reject the null hypothesis, in our case when we say that there is a difference in performance between tubular setting and tubeless when in fact the performance it’s the same. In simpler terms, it’s the probability of detecting an effect that doesn’t exist. Typical values are 0.05, and choosing a lower alpha (e.g., α = 0.01) reduces the risk of Type I errors, but it may also require a larger sample size to detect real effects. In our case, selecting an alpha value is essential to determine the threshold for statistical significance.</li>
<li><strong>Statistical Power:</strong> Statistical power is related to the probability of correctly rejecting a null hypothesis when it is indeed false. In other words, it’s the probability of detecting a real effect. High statistical power (typically 0.8 or higher) implies that the test is effective in distinguishing actual differences or effects. On the other hand, low power increases the risk of Type II errors, where we fail to detect real effects.</li>
</ul>
<p>These parameters enable us to balance the risk of making erroneous conclusions (Type I and Type II errors) with the ability to detect true differences in bike setups. Properly configuring the level of significance and statistical power enhances the reliability and credibility of our A/B test results.</p>
</section>
<section id="step-4-determine-the-required-number-of-repetitions" class="level3">
<h3 class="anchored" data-anchor-id="step-4-determine-the-required-number-of-repetitions">Step 4: Determine the Required Number of Repetitions</h3>
<p>Calculate the number of repeated rides necessary. This number relies on the minimum effect we want to detect, intra-subject standard deviation, power, and significance level. It’s essential to normalize the effect by the standard deviation. In cases where prior variability data is unavailable, conduct additional rides and calculate power and the standard deviation as you go.</p>
<p>For example, a graph illustrating the number of required rides (per group) is displayed, depending on intra-subject variability (if the rides are conducted by a single cyclist) or intra-group variability (if several cyclists participate in the same group), and the effect to be detected.</p>
<div id="775d68b2" class="cell" data-execution_count="13">
<details class="code-fold">
<summary>Show the code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb17" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb17-1">ratio_std_domain <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linspace(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>).tolist()</span>
<span id="cb17-2"></span>
<span id="cb17-3">effect_domain <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linspace(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>).tolist()</span>
<span id="cb17-4"></span>
<span id="cb17-5"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i,e <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(effect_domain):</span>
<span id="cb17-6">    n_samples_lst<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[]</span>
<span id="cb17-7">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> j,r_std <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(ratio_std_domain):</span>
<span id="cb17-8">        norm_e <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> e <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> r_std</span>
<span id="cb17-9">        n_samples <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sms.NormalIndPower().solve_power(</span>
<span id="cb17-10">                    effect_size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>norm_e,</span>
<span id="cb17-11">                    power<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>,</span>
<span id="cb17-12">                    alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.05</span>,</span>
<span id="cb17-13">                    ratio<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb17-14">                    )</span>
<span id="cb17-15">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span>  <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(n_samples, np.ndarray):</span>
<span id="cb17-16">            n_samples<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb17-17"></span>
<span id="cb17-18">        n_samples_lst.append(n_samples)</span>
<span id="cb17-19"></span>
<span id="cb17-20">    plt.plot(ratio_std_domain, n_samples_lst, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'Effect </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>e<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">  Watts/(Km/h)'</span>)</span>
<span id="cb17-21"></span>
<span id="cb17-22">plt.xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Standard deviation'</span>)</span>
<span id="cb17-23">plt.ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Number of rides per group'</span>)</span>
<span id="cb17-24">plt.title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Relationship between number of rides, effect and variability'</span>)</span>
<span id="cb17-25">plt.legend(loc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"best"</span>)</span>
<span id="cb17-26">plt.show()</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-display">
<div>
<figure class="figure">
<p><img src="https://velodata.ai/blog/sram-tubeless-vs-tubular/index_files/figure-html/cell-14-output-1.png" alt="Number of rides per group needed against the standard deviation, for effects of 0.50, 0.75 and 1.00 watts per km/h, with a significance level of 5% and a power of 0.8. The number of rides grows quickly with the standard deviation." width="585" height="449" class="figure-img"></p>
</figure>
</div>
</div>
</div>
<div id="a1a68e91" class="cell" data-execution_count="14">
<details class="code-fold">
<summary>Show the code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb18" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb18-1">ratio_std_domain <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linspace(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>).tolist()</span>
<span id="cb18-2"></span>
<span id="cb18-3">effect_domain <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linspace(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>).tolist()</span>
<span id="cb18-4"></span>
<span id="cb18-5"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i,e <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(effect_domain):</span>
<span id="cb18-6">    n_samples_lst<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[]</span>
<span id="cb18-7">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> j,r_std <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(ratio_std_domain):</span>
<span id="cb18-8">        norm_e <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> e <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> r_std</span>
<span id="cb18-9">        n_samples <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sms.NormalIndPower().solve_power(</span>
<span id="cb18-10">                    effect_size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>norm_e,</span>
<span id="cb18-11">                    power<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.9</span>,</span>
<span id="cb18-12">                    alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.01</span>,</span>
<span id="cb18-13">                    ratio<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb18-14">                    )</span>
<span id="cb18-15">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span>  <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(n_samples, np.ndarray):</span>
<span id="cb18-16">            n_samples<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb18-17"></span>
<span id="cb18-18">        n_samples_lst.append(n_samples)</span>
<span id="cb18-19"></span>
<span id="cb18-20">    plt.plot(ratio_std_domain, n_samples_lst, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'Effect </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>e<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">  Watts/(Km/h)'</span>)</span>
<span id="cb18-21"></span>
<span id="cb18-22">plt.xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Standard deviation'</span>)</span>
<span id="cb18-23">plt.ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Number of rides per group'</span>)</span>
<span id="cb18-24">plt.title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Relationship between number of rides, effect and variability'</span>)</span>
<span id="cb18-25">plt.legend(loc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"best"</span>)</span>
<span id="cb18-26">plt.show()</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-display">
<div>
<figure class="figure">
<p><img src="https://velodata.ai/blog/sram-tubeless-vs-tubular/index_files/figure-html/cell-15-output-1.png" alt="The same graph with a significance level of 1% and a power of 0.9. The number of rides needed per group roughly doubles." width="593" height="449" class="figure-img"></p>
</figure>
</div>
</div>
</div>
<p>These two graphs illustrate the relationship between various factors for the data provided by SRAM:</p>
<ul>
<li>For Rider B’s effect (0.51) and a variability of 1 or less (encompassing 98% of my variability values), if we aim for a significance level of 5% and a power of 0.8, the required number of repetitions per setup exceeds 60. Opting for a lower probability of Type I and II errors with a significance level of 1% and a power of 0.9, the number of rides increases to 115.</li>
<li>For the effect of the average of Rider A and Rider B (0.75) and a variability of 1 or less (encompassing 98% of my variability values), the number of needed repetitions per group, with a significance level of 5% and a power of 0.8, is more than 28. Reducing the probability of Type I and II errors with a significance level of 1% and a power of 0.9 elevates the number of rides to 53.</li>
<li>For the effect of the average of Rider A (1) and a variability of 1 or less (encompassing 98% of my variability values), the required number of repetitions per group is more than 16 at a significance level of 5% and a power of 0.8. Choosing a lower probability of Type I and II errors with a significance level of 1% and a power of 0.9 increases the number of rides to 30.</li>
</ul>
</section>
<section id="step-5-repeat-the-rides" class="level3">
<h3 class="anchored" data-anchor-id="step-5-repeat-the-rides">Step 5: Repeat the Rides</h3>
<p>Repeat the rides while ensuring consistent testing conditions. Factors like tire pressure, terrain, rider weight, and bike setup should remain consistent. If variations in factors like tire pressure are of interest, design separate A/B tests to explore those variables individually. More advanced tests may be performed to assess the impact of varying multiple variables simultaneously.</p>
</section>
<section id="step-6-perform-the-ab-test" class="level3">
<h3 class="anchored" data-anchor-id="step-6-perform-the-ab-test">Step 6: Perform the A/B test</h3>
<p>Using the available data, we conducted the A/B test. If the obtained p-value is greater than the chosen significance level, it suggests that there isn’t enough evidence to conclude that the measurements from the two setups are different. Conversely, if the p-value is lower than the significance level, we can claim that there is a difference between the two setups. The p-value tells us how surprising the data would be if there were no difference: e.g., a p-value of 0.04 means that, if the two setups performed the same, a difference at least as large as the one measured would appear only 4% of the time. It is not the probability that the claim is wrong.</p>
<div id="4ccabc78" class="cell" data-execution_count="15">
<details class="code-fold">
<summary>Show the code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb19" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb19-1">norm_e <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.51</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb19-2">n_samples <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sms.NormalIndPower().solve_power(</span>
<span id="cb19-3">                    effect_size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>norm_e,</span>
<span id="cb19-4">                    power<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.9</span>,</span>
<span id="cb19-5">                    alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.01</span>,</span>
<span id="cb19-6">                    ratio<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb19-7">                    )</span>
<span id="cb19-8"></span>
<span id="cb19-9"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(n_samples)</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>114.41281941133546</code></pre>
</div>
</div>
</section>
</section>
<section id="conclusions" class="level2">
<h2 class="anchored" data-anchor-id="conclusions">Conclusions</h2>
<p>SRAM promotes the superiority of tubeless tires, but upon closer examination, concerns emerge. Their test, conducted in collaboration with the Movistar cycling team, provides only a limited perspective of the broader picture. Let’s delve into the essential findings:</p>
<ul>
<li><strong>Pressure Variability:</strong> One glaring issue is the variation in tire pressure. SRAM’s experiment involved changing tire pressure between runs. This introduces a confounding variable, making it impossible to determine if the observed improvements are due to the tubeless system or merely changes in tire pressure. Controlled conditions, where the only variable is the tire type, are crucial for reliable conclusions.</li>
<li><strong>Variability caused by uncontrolled variables:</strong> Variability caused by uncontrolled variables refers to fluctuations or differences in data that arise due to factors or elements in an experiment that are not intentionally controlled or manipulated. These uncontrolled variables can introduce noise or random variation into the data, making it challenging to attribute observed differences or effects solely to the variables being studied. To put this into perspective, consider a personal experience. In rides using the same bike and setup on a well-maintained tarmac, approximately 52% of the time, there’s a similar variation in performance as observed in SRAM’s test with Rider B. This suggests that the majority of the observed variation could be attributed to uncontrollable factors rather than the tire setup.</li>
<li><strong>Limited Comparative Data:</strong> When it comes to making meaningful comparisons, the only feasible option is to consider the data collected at 4.5 bar of tire pressure. However, this leaves us with only one measurement per setup. Additionally, weight information for the riders is unavailable, making it challenging to normalize the effort based on power. Even when we set aside the issue of tire pressure variation and combine data from both riders to perform an A/B test, the resulting p-value is 0.84. This means that if there were no actual difference between the two setups, a gap at least as large as the observed one would appear 84% of the time, so the data provide no evidence that one setup is better than the other.</li>
<li><strong>Data Quantity and Statistical Significance:</strong> To establish meaningful statistical significance, the number of data points becomes crucial. Assuming that the unknown standard deviation falls within the lowest 98% of my historical (variability) data and considering typical probabilities of making Type I and Type II errors (5% and 20%, respectively), the number of rides needed per setup should exceed 28. If we aim for lower error rates (1% for Type I and 10% for Type II), the required number of rides per setup increases to 53.</li>
</ul>
<p>In essence, while SRAM’s claims may seem intriguing, the limitations of the experiment raise significant doubts. It is clear that controlled testing conditions, a more substantial dataset, and rigorous statistical analysis are essential to draw definitive conclusions regarding the superiority of tubeless tires.</p>
<hr>
<p><em>No GitHub account? Write to me at <script type="text/javascript">
<!--
h='&#118;&#x65;&#108;&#x6f;&#100;&#x61;&#116;&#x61;&#46;&#x61;&#x69;';a='&#64;';n='&#x63;&#x6f;&#110;&#116;&#x61;&#x63;&#116;';e=n+a+h;
document.write('<a h'+'ref'+'="ma'+'ilto'+':'+e+'" clas'+'s="em' + 'ail">'+e+'<\/'+'a'+'>');
// -->
</script><noscript>contact at velodata dot ai</noscript>.</em></p>


</section>

 ]]></description>
  <category>equipment</category>
  <guid>https://velodata.ai/blog/sram-tubeless-vs-tubular/</guid>
  <pubDate>Thu, 01 Oct 2026 00:00:00 GMT</pubDate>
</item>
</channel>
</rss>
