{
"cells": [
{
"cell_type": "markdown",
"id": "major-expansion",
"metadata": {},
"source": [
"# _TriScale_ - Experiment Sizing\n",
"\n",
"> This notebook is intended for **live tutorial** sessions about _TriScale._ \n",
"Here is the [self-study version](tutorial_exp-sizing.ipynb)."
]
},
{
"cell_type": "markdown",
"id": "reflected-glass",
"metadata": {},
"source": [
"To get started, we need to import a few Python modules. All the _TriScale_-specific functions are part of one module called `triscale`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "primary-secret",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"from pathlib import Path\n",
"\n",
"import pandas as pd\n",
"import numpy as np\n",
"import plotly.graph_objects as go\n",
"\n",
"import triscale"
]
},
{
"cell_type": "markdown",
"id": "specific-french",
"metadata": {},
"source": [
"## Basics\n",
"\n",
"_TriScale_'s `experiment_sizing()` function implements the computation of the minimal number of samples required to estimate any percentile with any confidence level."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "atmospheric-hammer",
"metadata": {},
"outputs": [],
"source": [
"percentile = 50 # the median\n",
"confidence = 95 # the confidence level, in %\n",
"\n",
"triscale.experiment_sizing(\n",
" percentile, \n",
" confidence,\n",
" verbose=True); "
]
},
{
"cell_type": "markdown",
"id": "floating-portrait",
"metadata": {},
"source": [
"We can change the values in the cell above to see how the number of samples evolves with a larger confidence level or more extreme percentiles. "
]
},
{
"cell_type": "markdown",
"id": "starting-member",
"metadata": {},
"source": [
"Note that the probability distributions are symmetric: it takes the same number of samples to compute a lower bound for the $p$-th percentile as to compute an upper bound for the $(1-p)$-th percentile."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "conservative-spank",
"metadata": {},
"outputs": [],
"source": [
"percentile = 20 \n",
"confidence = 95 # the confidence level, in %\n",
"\n",
"if (triscale.experiment_sizing(percentile,confidence) == \n",
" triscale.experiment_sizing(100-percentile,confidence)):\n",
" print(\"It takes the same number of samples to estimate \\\n",
"the \\n{}-th and \\n{}-th percentiles.\".format(percentile, 100-percentile))\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "significant-joseph",
"metadata": {},
"outputs": [],
"source": [
"# Sets of percentiles and confidence levels to try\n",
"percentiles = [0.1, 1, 5, 10, 25, 50, 75, 90, 95, 99, 99.9]\n",
"confidences = [75, 90, 95, 99, 99.9, 99.99]\n",
"\n",
"# Computing the minimum number of runs for each (perc., conf.) pair\n",
"min_number_samples = []\n",
"for c in confidences:\n",
" tmp = []\n",
" for p in percentiles:\n",
" N = triscale.experiment_sizing(p,c)\n",
" tmp.append(N[0])\n",
" min_number_samples.append(tmp)\n",
" \n",
"# Put the results in a DataFrame for a convenient display of the results\n",
"df = pd.DataFrame(columns=percentiles, data=min_number_samples)\n",
"df['Confidence level'] = confidences\n",
"df.set_index('Confidence level', inplace=True)\n",
"\n",
"display(df)"
]
},
{
"cell_type": "markdown",
"id": "5080d7fd",
"metadata": {},
"source": [
"Let's visualize the same data with a heatmap..."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c3dc6fc7",
"metadata": {},
"outputs": [],
"source": [
"colorbar=dict(\n",
" title='Minimal N', \n",
" tickvals = [0, 1, 2, 3, 3.699, 4],\n",
" ticktext = ['1', '10', '100', '1000', '5000','10000']\n",
")\n",
"\n",
"fig = go.Figure(data=go.Heatmap(\n",
" z = np.log10(df),\n",
" y = df.index,\n",
" x = df.columns, \n",
" colorbar = colorbar,\n",
" hovertemplate='N:2^%{z}
percentile:%{x}
confidence:%{y}',\n",
" )\n",
")\n",
"\n",
"fig.update_layout(\n",
" title_text='Mininal number of samples',\n",
" xaxis=dict(title='Percentile'),\n",
" yaxis=dict(title='Confidence level')\n",
")\n",
"\n",
"fig.show()"
]
},
{
"cell_type": "markdown",
"id": "eeea6b5c",
"metadata": {},
"source": [
"> **Takeaway.** The required number of samples increase exponentially when the percentile to estimate becomes more extreme. The increase induced by the confidence level is not as dramatic. "
]
},
{
"cell_type": "markdown",
"id": "burning-controversy",
"metadata": {},
"source": [
"## Excluding outliers\n",
"\n",
"By default, `experiment_sizing()` returns the minimal number of samples, such that the __smallest one__ is the percentile estimate (or the largerest one, if the percentile is $> 50$).\n",
"\n",
"If the experiment is subject to outliers, or more generally to obtain tighter bounds, one may want to collect more samples. But how many? You can use the `robustness` argument to find out:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "immediate-testament",
"metadata": {},
"outputs": [],
"source": [
"percentile = 10\n",
"confidence = 99\n",
"triscale.experiment_sizing(\n",
" percentile, \n",
" confidence,\n",
" robustness=3,\n",
" verbose=True); "
]
},
{
"cell_type": "markdown",
"id": "6dfd718c",
"metadata": {},
"source": [
"> **Note.** Hence, `robustness` refers to the number of outliers that can be excluded from the confidence interval."
]
},
{
"cell_type": "markdown",
"id": "2a142934",
"metadata": {},
"source": [
"Again, we can plot the minimal value of $N$ as `robustness` increases. For example, for a few percentiles, with 95% confidence level:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "beef9688",
"metadata": {},
"outputs": [],
"source": [
"robustness_values = np.arange(1,10,dtype=int)\n",
"confidence = 95\n",
"N_50 = [triscale.experiment_sizing(50, confidence, robustness=int(x))[0] for x in robustness_values]\n",
"N_75 = [triscale.experiment_sizing(75, confidence, robustness=int(x))[0] for x in robustness_values]\n",
"N_90 = [triscale.experiment_sizing(90, confidence, robustness=int(x))[0] for x in robustness_values]\n",
"\n",
"fig = go.Figure()\n",
"fig.add_trace(go.Scatter(x=robustness_values, y=N_90, name='90th'))\n",
"fig.add_trace(go.Scatter(x=robustness_values, y=N_75, name='75th'))\n",
"fig.add_trace(go.Scatter(x=robustness_values, y=N_50, name='median'))\n",
"\n",
"fig.update_layout(\n",
" title_text='Mininal number of samples for 95% confidence level',\n",
" xaxis=dict(title='robustness'),\n",
" yaxis=dict(title='Minimal N')\n",
")\n",
"\n",
"fig.show()"
]
},
{
"cell_type": "markdown",
"id": "566bb82e",
"metadata": {},
"source": [
"> **Takeaway.** The increase in number of samples required with respect to the `robustness` parameter is essentially linear. "
]
},
{
"cell_type": "markdown",
"id": "determined-welsh",
"metadata": {},
"source": [
"## Your turn: time to practice\n",
"\n",
"Based on the explanations above, use _TriScale_'s `experiment_sizing` function to answer the following questions:\n",
"- What is the minimal number of runs required to estimate the\n",
" - **90th** percentile with **90%** confidence?\n",
" - **90th** percentile with **95%** confidence?\n",
" - **95th** percentile with **90%** confidence?\n",
"- Based on the answers to the previous questions, is it harder (i.e., does it require more runs) to increase the confidence level, or to estimate a more extreme percentile? \n",
"\n",
"_Optional question (harder):_ \n",
"- For $N = 50$ samples, how many outliers can be excluded when computing a lower bound with a 95% confidence level for the 25th percentile? "
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "right-alarm",
"metadata": {},
"outputs": [],
"source": [
"########## YOUR CODE HERE ###########\n",
"# ...\n",
"#####################################"
]
},
{
"cell_type": "markdown",
"id": "antique-bishop",
"metadata": {},
"source": [
"### Solution\n",
"\n",
"\n",
"
Click here to show the solution.
\n",
" \n",
"```python\n",
">>> print(triscale.experiment_sizing(90,90)[0])\n",
"22\n",
">>> print(triscale.experiment_sizing(90,95)[0])\n",
"29\n",
">>> print(triscale.experiment_sizing(95,90)[0])\n",
"45\n",
"```\n",
"We observe that it \"costs\" many more runs to estimate a more extreme percentile (95th instead of 90th) than to increase the confidence level (90% to 95%). This observation holds true in general. The number of runs required increases exponentially when the percentiles get more extreme (close to $0$ or to $1$).\n",
" \n",
"For the last question, we must play with the `robustness` parameter. We can write a simple loop to increase its value until the number of runs required reaches 50.\n",
" \n",
"```python\n",
">>> r = 0\n",
">>> while (triscale.experiment_sizing(25,95,r)[0] <= 50):\n",
">>> r += 1 \n",
">>> print(r-1)\n",
"7 \n",
"``` \n",
"We can exclude the 7 \"worst\" samples from the confidence interval. Hence, with $N=50$ samples, the best lower bound for the 25th percentile with 95% confidence is $x_8$ (assuming the first sample is $x_1$).\n",
" "
]
},
{
"cell_type": "markdown",
"id": "alpine-faith",
"metadata": {},
"source": [
"---\n",
"Next step: [Data Analysis](live_data-analysis.ipynb) \n",
"[Back to repo](.)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.9.1 64-bit ('triscale': conda)",
"language": "python",
"name": "python391jvsc74a57bd0684f90775fb1f43db0d8eed0780ba829f42a97c2f4b9a1bd592c18e47e5c272e"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.6"
}
},
"nbformat": 4,
"nbformat_minor": 5
}