diff --git a/docs/TrendAnalysis_example.ipynb b/docs/TrendAnalysis_example.ipynb
index 45744e99..cd036911 100644
--- a/docs/TrendAnalysis_example.ipynb
+++ b/docs/TrendAnalysis_example.ipynb
@@ -22,7 +22,14 @@
{
"cell_type": "code",
"execution_count": 1,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:12:11.059602Z",
+ "iopub.status.busy": "2026-02-04T19:12:11.058607Z",
+ "iopub.status.idle": "2026-02-04T19:12:14.608421Z",
+ "shell.execute_reply": "2026-02-04T19:12:14.608421Z"
+ }
+ },
"outputs": [],
"source": [
"import pandas as pd\n",
@@ -36,7 +43,14 @@
{
"cell_type": "code",
"execution_count": 2,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:12:14.611430Z",
+ "iopub.status.busy": "2026-02-04T19:12:14.611430Z",
+ "iopub.status.idle": "2026-02-04T19:12:14.622950Z",
+ "shell.execute_reply": "2026-02-04T19:12:14.622431Z"
+ }
+ },
"outputs": [],
"source": [
"#Update the style of plots\n",
@@ -54,7 +68,14 @@
{
"cell_type": "code",
"execution_count": 3,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:12:14.625956Z",
+ "iopub.status.busy": "2026-02-04T19:12:14.625956Z",
+ "iopub.status.idle": "2026-02-04T19:12:14.628409Z",
+ "shell.execute_reply": "2026-02-04T19:12:14.628409Z"
+ }
+ },
"outputs": [],
"source": [
"# Set the random seed for numpy to ensure consistent results\n",
@@ -78,7 +99,14 @@
{
"cell_type": "code",
"execution_count": 4,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:12:14.630418Z",
+ "iopub.status.busy": "2026-02-04T19:12:14.630418Z",
+ "iopub.status.idle": "2026-02-04T19:12:14.682304Z",
+ "shell.execute_reply": "2026-02-04T19:12:14.681301Z"
+ }
+ },
"outputs": [],
"source": [
"# Import the example data\n",
@@ -99,7 +127,14 @@
{
"cell_type": "code",
"execution_count": 5,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:12:14.686163Z",
+ "iopub.status.busy": "2026-02-04T19:12:14.685306Z",
+ "iopub.status.idle": "2026-02-04T19:12:14.890023Z",
+ "shell.execute_reply": "2026-02-04T19:12:14.890023Z"
+ }
+ },
"outputs": [],
"source": [
"df = df.rename(columns = {\n",
@@ -139,7 +174,14 @@
{
"cell_type": "code",
"execution_count": 6,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:12:14.893032Z",
+ "iopub.status.busy": "2026-02-04T19:12:14.893032Z",
+ "iopub.status.idle": "2026-02-04T19:12:41.974653Z",
+ "shell.execute_reply": "2026-02-04T19:12:41.974146Z"
+ }
+ },
"outputs": [
{
"data": {
@@ -181,7 +223,14 @@
{
"cell_type": "code",
"execution_count": 7,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:12:42.016670Z",
+ "iopub.status.busy": "2026-02-04T19:12:42.016670Z",
+ "iopub.status.idle": "2026-02-04T19:13:04.387333Z",
+ "shell.execute_reply": "2026-02-04T19:13:04.386324Z"
+ }
+ },
"outputs": [],
"source": [
"ta = rdtools.TrendAnalysis(df['power'], df['poa'],\n",
@@ -209,14 +258,21 @@
{
"cell_type": "code",
"execution_count": 8,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:13:04.392334Z",
+ "iopub.status.busy": "2026-02-04T19:13:04.392334Z",
+ "iopub.status.idle": "2026-02-04T19:14:02.163631Z",
+ "shell.execute_reply": "2026-02-04T19:14:02.163127Z"
+ }
+ },
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
- "c:\\users\\mspringe\\onedrive - nrel\\msp\\pvfleets\\repos\\rdtools\\rdtools\\soiling.py:27: UserWarning: The soiling module is currently experimental. The API, results, and default behaviors may change in future releases (including MINOR and PATCH releases) as the code matures.\n",
- " warnings.warn(\n"
+ "c:\\users\\mspringe\\onedrive - nrel\\msp\\pvfleets\\repos\\public\\rdtools\\rdtools\\analysis_chains.py:851: UserWarning: The soiling module is currently experimental. The API, results, and default behaviors may change in future releases (including MINOR and PATCH releases) as the code matures.\n",
+ " from rdtools import soiling\n"
]
}
],
@@ -234,7 +290,14 @@
{
"cell_type": "code",
"execution_count": 9,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:14:02.168637Z",
+ "iopub.status.busy": "2026-02-04T19:14:02.168637Z",
+ "iopub.status.idle": "2026-02-04T19:14:02.173143Z",
+ "shell.execute_reply": "2026-02-04T19:14:02.172638Z"
+ }
+ },
"outputs": [],
"source": [
"yoy_results = ta.results['sensor']['yoy_degradation']\n",
@@ -244,7 +307,14 @@
{
"cell_type": "code",
"execution_count": 10,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:14:02.177148Z",
+ "iopub.status.busy": "2026-02-04T19:14:02.176152Z",
+ "iopub.status.idle": "2026-02-04T19:14:02.181516Z",
+ "shell.execute_reply": "2026-02-04T19:14:02.181516Z"
+ }
+ },
"outputs": [
{
"name": "stdout",
@@ -264,7 +334,14 @@
{
"cell_type": "code",
"execution_count": 11,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:14:02.185524Z",
+ "iopub.status.busy": "2026-02-04T19:14:02.184522Z",
+ "iopub.status.idle": "2026-02-04T19:14:02.189439Z",
+ "shell.execute_reply": "2026-02-04T19:14:02.189439Z"
+ }
+ },
"outputs": [
{
"name": "stdout",
@@ -292,7 +369,14 @@
{
"cell_type": "code",
"execution_count": 12,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:14:02.193452Z",
+ "iopub.status.busy": "2026-02-04T19:14:02.193452Z",
+ "iopub.status.idle": "2026-02-04T19:14:03.089080Z",
+ "shell.execute_reply": "2026-02-04T19:14:03.088071Z"
+ }
+ },
"outputs": [
{
"data": {
@@ -316,7 +400,14 @@
{
"cell_type": "code",
"execution_count": 13,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:14:03.093086Z",
+ "iopub.status.busy": "2026-02-04T19:14:03.093086Z",
+ "iopub.status.idle": "2026-02-04T19:14:03.409299Z",
+ "shell.execute_reply": "2026-02-04T19:14:03.408292Z"
+ }
+ },
"outputs": [
{
"data": {
@@ -340,7 +431,14 @@
{
"cell_type": "code",
"execution_count": 14,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:14:03.414300Z",
+ "iopub.status.busy": "2026-02-04T19:14:03.413304Z",
+ "iopub.status.idle": "2026-02-04T19:14:06.642546Z",
+ "shell.execute_reply": "2026-02-04T19:14:06.642546Z"
+ }
+ },
"outputs": [
{
"data": {
@@ -369,14 +467,21 @@
{
"cell_type": "code",
"execution_count": 15,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:14:06.646552Z",
+ "iopub.status.busy": "2026-02-04T19:14:06.646552Z",
+ "iopub.status.idle": "2026-02-04T19:14:30.828095Z",
+ "shell.execute_reply": "2026-02-04T19:14:30.827572Z"
+ }
+ },
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
- "c:\\users\\mspringe\\onedrive - nrel\\msp\\pvfleets\\repos\\rdtools\\rdtools\\plotting.py:173: UserWarning: The soiling module is currently experimental. The API, results, and default behaviors may change in future releases (including MINOR and PATCH releases) as the code matures.\n",
- " warnings.warn(\n"
+ "c:\\users\\mspringe\\onedrive - nrel\\msp\\pvfleets\\repos\\public\\rdtools\\rdtools\\analysis_chains.py:1144: UserWarning: The soiling module is currently experimental. The API, results, and default behaviors may change in future releases (including MINOR and PATCH releases) as the code matures.\n",
+ " fig = plotting.soiling_monte_carlo_plot(\n"
]
},
{
@@ -398,14 +503,21 @@
{
"cell_type": "code",
"execution_count": 16,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:14:30.831111Z",
+ "iopub.status.busy": "2026-02-04T19:14:30.831111Z",
+ "iopub.status.idle": "2026-02-04T19:14:31.013295Z",
+ "shell.execute_reply": "2026-02-04T19:14:31.013295Z"
+ }
+ },
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
- "c:\\users\\mspringe\\onedrive - nrel\\msp\\pvfleets\\repos\\rdtools\\rdtools\\plotting.py:233: UserWarning: The soiling module is currently experimental. The API, results, and default behaviors may change in future releases (including MINOR and PATCH releases) as the code matures.\n",
- " warnings.warn(\n"
+ "c:\\users\\mspringe\\onedrive - nrel\\msp\\pvfleets\\repos\\public\\rdtools\\rdtools\\analysis_chains.py:1176: UserWarning: The soiling module is currently experimental. The API, results, and default behaviors may change in future releases (including MINOR and PATCH releases) as the code matures.\n",
+ " fig = plotting.soiling_interval_plot(\n"
]
},
{
@@ -427,14 +539,21 @@
{
"cell_type": "code",
"execution_count": 17,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:14:31.016303Z",
+ "iopub.status.busy": "2026-02-04T19:14:31.016303Z",
+ "iopub.status.idle": "2026-02-04T19:14:31.143186Z",
+ "shell.execute_reply": "2026-02-04T19:14:31.143186Z"
+ }
+ },
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
- "c:\\users\\mspringe\\onedrive - nrel\\msp\\pvfleets\\repos\\rdtools\\rdtools\\plotting.py:273: UserWarning: The soiling module is currently experimental. The API, results, and default behaviors may change in future releases (including MINOR and PATCH releases) as the code matures.\n",
- " warnings.warn(\n"
+ "c:\\users\\mspringe\\onedrive - nrel\\msp\\pvfleets\\repos\\public\\rdtools\\rdtools\\analysis_chains.py:1206: UserWarning: The soiling module is currently experimental. The API, results, and default behaviors may change in future releases (including MINOR and PATCH releases) as the code matures.\n",
+ " fig = plotting.soiling_rate_histogram(results_dict[\"calc_info\"], **kwargs)\n"
]
},
{
@@ -463,7 +582,14 @@
{
"cell_type": "code",
"execution_count": 18,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:14:31.147269Z",
+ "iopub.status.busy": "2026-02-04T19:14:31.147269Z",
+ "iopub.status.idle": "2026-02-04T19:14:31.159709Z",
+ "shell.execute_reply": "2026-02-04T19:14:31.159195Z"
+ }
+ },
"outputs": [
{
"data": {
@@ -606,7 +732,14 @@
{
"cell_type": "code",
"execution_count": 19,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:14:31.162723Z",
+ "iopub.status.busy": "2026-02-04T19:14:31.161723Z",
+ "iopub.status.idle": "2026-02-04T19:15:26.676904Z",
+ "shell.execute_reply": "2026-02-04T19:15:26.676904Z"
+ }
+ },
"outputs": [],
"source": [
"# Instantiate a new instance of TrendAnalysis\n",
@@ -628,7 +761,14 @@
{
"cell_type": "code",
"execution_count": 20,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:15:26.682912Z",
+ "iopub.status.busy": "2026-02-04T19:15:26.681914Z",
+ "iopub.status.idle": "2026-02-04T19:15:26.691519Z",
+ "shell.execute_reply": "2026-02-04T19:15:26.691519Z"
+ }
+ },
"outputs": [
{
"data": {
@@ -726,7 +866,14 @@
{
"cell_type": "code",
"execution_count": 21,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:15:26.694526Z",
+ "iopub.status.busy": "2026-02-04T19:15:26.694526Z",
+ "iopub.status.idle": "2026-02-04T19:15:26.700822Z",
+ "shell.execute_reply": "2026-02-04T19:15:26.700822Z"
+ }
+ },
"outputs": [
{
"data": {
@@ -752,8 +899,44 @@
{
"cell_type": "code",
"execution_count": 22,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:15:26.703836Z",
+ "iopub.status.busy": "2026-02-04T19:15:26.703836Z",
+ "iopub.status.idle": "2026-02-04T19:15:28.228660Z",
+ "shell.execute_reply": "2026-02-04T19:15:28.228151Z"
+ }
+ },
"outputs": [
+ {
+ "data": {
+ "text/html": [
+ " \n",
+ " "
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
{
"data": {
"application/vnd.plotly.v1+json": {
@@ -61364,7 +61547,7 @@
},
"colorscale": [
[
- 0,
+ 0.0,
"#0d0887"
],
[
@@ -61400,7 +61583,7 @@
"#fdca26"
],
[
- 1,
+ 1.0,
"#f0f921"
]
],
@@ -61424,7 +61607,7 @@
},
"colorscale": [
[
- 0,
+ 0.0,
"#0d0887"
],
[
@@ -61460,7 +61643,7 @@
"#fdca26"
],
[
- 1,
+ 1.0,
"#f0f921"
]
],
@@ -61475,7 +61658,7 @@
},
"colorscale": [
[
- 0,
+ 0.0,
"#0d0887"
],
[
@@ -61511,7 +61694,7 @@
"#fdca26"
],
[
- 1,
+ 1.0,
"#f0f921"
]
],
@@ -61538,7 +61721,7 @@
},
"colorscale": [
[
- 0,
+ 0.0,
"#0d0887"
],
[
@@ -61574,7 +61757,7 @@
"#fdca26"
],
[
- 1,
+ 1.0,
"#f0f921"
]
],
@@ -61589,7 +61772,7 @@
},
"colorscale": [
[
- 0,
+ 0.0,
"#0d0887"
],
[
@@ -61625,7 +61808,7 @@
"#fdca26"
],
[
- 1,
+ 1.0,
"#f0f921"
]
],
@@ -61770,7 +61953,7 @@
},
"colorscale": [
[
- 0,
+ 0.0,
"#0d0887"
],
[
@@ -61806,7 +61989,7 @@
"#fdca26"
],
[
- 1,
+ 1.0,
"#f0f921"
]
],
@@ -61897,7 +62080,7 @@
],
"sequential": [
[
- 0,
+ 0.0,
"#0d0887"
],
[
@@ -61933,13 +62116,13 @@
"#fdca26"
],
[
- 1,
+ 1.0,
"#f0f921"
]
],
"sequentialminus": [
[
- 0,
+ 0.0,
"#0d0887"
],
[
@@ -61975,7 +62158,7 @@
"#fdca26"
],
[
- 1,
+ 1.0,
"#f0f921"
]
]
@@ -62107,8 +62290,8 @@
"xaxis": {
"anchor": "y",
"domain": [
- 0,
- 1
+ 0.0,
+ 1.0
],
"title": {
"text": "datetime"
@@ -62117,15 +62300,42 @@
"yaxis": {
"anchor": "x",
"domain": [
- 0,
- 1
+ 0.0,
+ 1.0
],
"title": {
"text": "energy_Wh"
}
}
}
- }
+ },
+ "text/html": [
+ "
"
+ ]
},
"metadata": {},
"output_type": "display_data"
@@ -62143,7 +62353,14 @@
{
"cell_type": "code",
"execution_count": 23,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:15:28.270175Z",
+ "iopub.status.busy": "2026-02-04T19:15:28.270175Z",
+ "iopub.status.idle": "2026-02-04T19:15:28.532947Z",
+ "shell.execute_reply": "2026-02-04T19:15:28.532947Z"
+ }
+ },
"outputs": [
{
"data": {
@@ -62175,7 +62392,14 @@
{
"cell_type": "code",
"execution_count": 24,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:15:28.536968Z",
+ "iopub.status.busy": "2026-02-04T19:15:28.535960Z",
+ "iopub.status.idle": "2026-02-04T19:15:28.541456Z",
+ "shell.execute_reply": "2026-02-04T19:15:28.540952Z"
+ }
+ },
"outputs": [],
"source": [
"def filter_stuck_values(pandas_series):\n",
@@ -62194,7 +62418,14 @@
{
"cell_type": "code",
"execution_count": 25,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:15:28.544460Z",
+ "iopub.status.busy": "2026-02-04T19:15:28.543463Z",
+ "iopub.status.idle": "2026-02-04T19:15:47.311806Z",
+ "shell.execute_reply": "2026-02-04T19:15:47.311806Z"
+ }
+ },
"outputs": [],
"source": [
"# Instantiate a new instance of TrendAnalysis\n",
@@ -62209,7 +62440,14 @@
{
"cell_type": "code",
"execution_count": 26,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:15:47.316873Z",
+ "iopub.status.busy": "2026-02-04T19:15:47.315875Z",
+ "iopub.status.idle": "2026-02-04T19:15:47.783337Z",
+ "shell.execute_reply": "2026-02-04T19:15:47.783337Z"
+ }
+ },
"outputs": [],
"source": [
"stuck_filter = (\n",
@@ -62228,7 +62466,14 @@
{
"cell_type": "code",
"execution_count": 27,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:15:47.788385Z",
+ "iopub.status.busy": "2026-02-04T19:15:47.787342Z",
+ "iopub.status.idle": "2026-02-04T19:16:12.566655Z",
+ "shell.execute_reply": "2026-02-04T19:16:12.566655Z"
+ }
+ },
"outputs": [
{
"data": {
@@ -62263,7 +62508,14 @@
{
"cell_type": "code",
"execution_count": 28,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:16:12.571003Z",
+ "iopub.status.busy": "2026-02-04T19:16:12.571003Z",
+ "iopub.status.idle": "2026-02-04T19:16:12.576522Z",
+ "shell.execute_reply": "2026-02-04T19:16:12.576017Z"
+ }
+ },
"outputs": [
{
"data": {
@@ -62290,7 +62542,14 @@
{
"cell_type": "code",
"execution_count": 29,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:16:12.578526Z",
+ "iopub.status.busy": "2026-02-04T19:16:12.578526Z",
+ "iopub.status.idle": "2026-02-04T19:16:36.383047Z",
+ "shell.execute_reply": "2026-02-04T19:16:36.383047Z"
+ }
+ },
"outputs": [
{
"data": {
@@ -62326,7 +62585,14 @@
{
"cell_type": "code",
"execution_count": 30,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:16:36.387060Z",
+ "iopub.status.busy": "2026-02-04T19:16:36.387060Z",
+ "iopub.status.idle": "2026-02-04T19:16:36.645957Z",
+ "shell.execute_reply": "2026-02-04T19:16:36.645957Z"
+ }
+ },
"outputs": [
{
"data": {
@@ -62364,7 +62630,14 @@
{
"cell_type": "code",
"execution_count": 31,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:16:36.649968Z",
+ "iopub.status.busy": "2026-02-04T19:16:36.648966Z",
+ "iopub.status.idle": "2026-02-04T19:16:36.654476Z",
+ "shell.execute_reply": "2026-02-04T19:16:36.653969Z"
+ }
+ },
"outputs": [],
"source": [
"from rdtools.soiling import CODSAnalysis\n",
@@ -62382,7 +62655,14 @@
{
"cell_type": "code",
"execution_count": 32,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:16:36.657488Z",
+ "iopub.status.busy": "2026-02-04T19:16:36.657488Z",
+ "iopub.status.idle": "2026-02-04T19:17:38.775433Z",
+ "shell.execute_reply": "2026-02-04T19:17:38.775433Z"
+ }
+ },
"outputs": [],
"source": [
"results_df, degradation, soiling_loss = CODS.run_bootstrap(reps=16, bootstrap_seed=42)"
@@ -62391,7 +62671,14 @@
{
"cell_type": "code",
"execution_count": 33,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:17:38.780440Z",
+ "iopub.status.busy": "2026-02-04T19:17:38.779440Z",
+ "iopub.status.idle": "2026-02-04T19:17:41.020579Z",
+ "shell.execute_reply": "2026-02-04T19:17:41.020579Z"
+ }
+ },
"outputs": [
{
"data": {
@@ -62522,7 +62809,14 @@
{
"cell_type": "code",
"execution_count": 34,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:17:41.024589Z",
+ "iopub.status.busy": "2026-02-04T19:17:41.024589Z",
+ "iopub.status.idle": "2026-02-04T19:17:41.029924Z",
+ "shell.execute_reply": "2026-02-04T19:17:41.029924Z"
+ }
+ },
"outputs": [
{
"name": "stdout",
@@ -62548,7 +62842,14 @@
{
"cell_type": "code",
"execution_count": 35,
- "metadata": {},
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-02-04T19:17:41.034026Z",
+ "iopub.status.busy": "2026-02-04T19:17:41.032930Z",
+ "iopub.status.idle": "2026-02-04T19:17:41.831294Z",
+ "shell.execute_reply": "2026-02-04T19:17:41.830790Z"
+ }
+ },
"outputs": [
{
"data": {
diff --git a/docs/nbval_sanitization_rules.cfg b/docs/nbval_sanitization_rules.cfg
index 7a8b786a..58fbe995 100644
--- a/docs/nbval_sanitization_rules.cfg
+++ b/docs/nbval_sanitization_rules.cfg
@@ -12,10 +12,16 @@
regex: .*: UserWarning:
replace: NBVAL-FILEPATH: UserWarning:
+# sanitize the specific line of code shown in warning tracebacks
+# since stacklevel changes will alter which line is reported
[regex2]
+regex: ^ .*$
+replace: CODE-LINE
+
+[regex3]
regex: \d{1,2}/\d{1,2}/\d{2,4}
replace: DATE-STAMP
-[regex3]
+[regex4]
regex: \d{2}:\d{2}:\d{2}
replace: TIME-STAMP
diff --git a/docs/sphinx/source/changelog/pending.rst b/docs/sphinx/source/changelog/pending.rst
index bda6468f..8c0c41e3 100644
--- a/docs/sphinx/source/changelog/pending.rst
+++ b/docs/sphinx/source/changelog/pending.rst
@@ -69,6 +69,10 @@ Enhancements
* Added frequency validation for ``clip_filter`` in ``TrendAnalysis._filter()`` that
raises a ``ValueError`` if the time series has a median time step greater than 60
minutes, as clipping detection requires higher resolution data.
+* Added ``stacklevel`` parameter to all ``warnings.warn()`` calls so that warning
+ messages point to user code rather than rdtools internals. Affected modules:
+ ``analysis_chains``, ``filtering``, ``soiling``, ``plotting``, ``normalization``,
+ ``availability``, and ``clearsky_temperature``.
Warnings
diff --git a/rdtools/analysis_chains.py b/rdtools/analysis_chains.py
index 77c5c323..d2c58022 100644
--- a/rdtools/analysis_chains.py
+++ b/rdtools/analysis_chains.py
@@ -475,7 +475,8 @@ def _pvwatts_norm(self, poa_global, temperature_cell):
if self.gamma_pdc is None:
warnings.warn(
"Temperature coefficient not passed in to TrendAnalysis. "
- "No temperature correction will be conducted."
+ "No temperature correction will be conducted.",
+ stacklevel=3,
)
pvwatts_kws = {
"poa_global": poa_global,
@@ -619,7 +620,8 @@ def _call_clearsky_filter(filter_string):
if ad_hoc_filter.isnull().any():
warnings.warn(
- "ad_hoc_filter contains NaN values; setting to False (excluding)"
+ "ad_hoc_filter contains NaN values; setting to False (excluding)",
+ stacklevel=3,
)
ad_hoc_filter.loc[ad_hoc_filter.isnull()] = False
@@ -627,7 +629,8 @@ def _call_clearsky_filter(filter_string):
warnings.warn(
"ad_hoc_filter index does not match index of other filters; missing "
"values will be set to True (kept). Align the index with the index "
- "of the filter_components attribute to prevent this warning"
+ "of the filter_components attribute to prevent this warning",
+ stacklevel=3,
)
ad_hoc_filter = ad_hoc_filter.reindex(filter_components.index)
ad_hoc_filter.loc[ad_hoc_filter.isnull()] = True
@@ -709,7 +712,8 @@ def _aggregated_filter(self, aggregated, case):
if ad_hoc_filter_aggregated.isnull().any():
warnings.warn(
- "aggregated ad_hoc_filter contains NaN values; setting to False (excluding)"
+ "aggregated ad_hoc_filter contains NaN values; setting to False (excluding)",
+ stacklevel=3,
)
ad_hoc_filter_aggregated.loc[ad_hoc_filter_aggregated.isnull()] = False
@@ -720,7 +724,8 @@ def _aggregated_filter(self, aggregated, case):
"Aggregated ad_hoc_filter index does not match index of other "
"filters; missing values will be set to True (kept). "
"Align the index with the index of the "
- "filter_components_aggregated attribute to prevent this warning"
+ "filter_components_aggregated attribute to prevent this warning",
+ stacklevel=3,
)
ad_hoc_filter_aggregated = ad_hoc_filter_aggregated.reindex(
filter_components_aggregated.index
@@ -963,7 +968,8 @@ def _clearsky_preprocess(self):
"""Clear-sky analysis is performed but `power_expected` was passed in by user.
In this case, the power normalization is not tied to the modeled clear-sky
irradiance and the clear-sky workflow may provide similar results to
- the sensor workflow."""
+ the sensor workflow.""",
+ stacklevel=2,
)
self._filter(cs_normalized, "clearsky")
cs_aggregated, cs_aggregated_insolation = self._aggregate(
diff --git a/rdtools/availability.py b/rdtools/availability.py
index 06d57780..e666eedf 100644
--- a/rdtools/availability.py
+++ b/rdtools/availability.py
@@ -543,7 +543,7 @@ def _combine_losses(self, rollup_period="ME"):
'levels. This is unexpected and could indicate a problem with '
'the input time series data.'
)
- warnings.warn(msg, UserWarning)
+ warnings.warn(msg, UserWarning, stacklevel=3)
self.loss_total = self.loss_system + self.loss_subsystem
diff --git a/rdtools/clearsky_temperature.py b/rdtools/clearsky_temperature.py
index ebe617f2..d87cae03 100644
--- a/rdtools/clearsky_temperature.py
+++ b/rdtools/clearsky_temperature.py
@@ -56,7 +56,8 @@ def get_clearsky_tamb(times, latitude, longitude, window_size=40,
# workaround from https://github.com/pandas-dev/pandas/issues/55794
freq_actual = pd.infer_freq(times[:10])
warnings.warn("Input 'times' has no frequency attribute. "
- "Inferring frequency from first 10 timestamps.")
+ "Inferring frequency from first 10 timestamps.",
+ stacklevel=2)
else:
freq_actual = times.freq
@@ -121,7 +122,8 @@ def solar_noon_offset(utc_offset):
df['solar_noon_offset'].values)
if df['Clear Sky Temperature (C)'].isna().any():
warnings.warn("Clear Sky Temperature includes NaNs, "
- "possibly invalid Lat/Lon coordinates.", UserWarning)
+ "possibly invalid Lat/Lon coordinates.", UserWarning,
+ stacklevel=2)
return df['Clear Sky Temperature (C)']
diff --git a/rdtools/filtering.py b/rdtools/filtering.py
index ecc5162d..3b2569b2 100644
--- a/rdtools/filtering.py
+++ b/rdtools/filtering.py
@@ -391,7 +391,8 @@ def _format_clipping_time_series(power_ac, mounting_type):
warnings.warn(
"Function expects timestamps in local time. "
"For best results pass a time-zone-localized "
- "time series localized to the correct local time zone."
+ "time series localized to the correct local time zone.",
+ stacklevel=3,
)
# Check the other input variables to ensure that they are the
# correct format
@@ -448,7 +449,8 @@ def _check_data_sampling_frequency(power_ac):
"Variable sampling frequency across time series. "
"Less than 95% of the time series is sampled at the "
"same interval. This function was not tested "
- "on variable frequency data--use at your own risk!"
+ "on variable frequency data--use at your own risk!",
+ stacklevel=3,
)
return
diff --git a/rdtools/normalization.py b/rdtools/normalization.py
index 8a2e02b1..194f24ff 100644
--- a/rdtools/normalization.py
+++ b/rdtools/normalization.py
@@ -588,7 +588,7 @@ def _interpolate_series(time_series, target_index, max_timedelta=None,
warnings.warn("Fraction of excluded data "
f"({100*fraction_excluded:0.02f}%) "
"exceeded threshold",
- UserWarning)
+ UserWarning, stacklevel=3)
# put data on index that includes both original and target indices
target_timestamps = pd.Index((target_index - epoch).total_seconds())
diff --git a/rdtools/plotting.py b/rdtools/plotting.py
index 93a07bac..d2081ac3 100644
--- a/rdtools/plotting.py
+++ b/rdtools/plotting.py
@@ -173,7 +173,7 @@ def soiling_monte_carlo_plot(soiling_info, normalized_yield, point_alpha=0.5,
warnings.warn(
'The soiling module is currently experimental. The API, results, '
'and default behaviors may change in future releases (including MINOR '
- 'and PATCH releases) as the code matures.'
+ 'and PATCH releases) as the code matures.', stacklevel=2
)
fig, ax = plt.subplots()
@@ -233,7 +233,7 @@ def soiling_interval_plot(soiling_info, normalized_yield, point_alpha=0.5,
warnings.warn(
'The soiling module is currently experimental. The API, results, '
'and default behaviors may change in future releases (including MINOR '
- 'and PATCH releases) as the code matures.'
+ 'and PATCH releases) as the code matures.', stacklevel=2
)
sratio = soiling_info['soiling_ratio_perfect_clean']
@@ -273,7 +273,7 @@ def soiling_rate_histogram(soiling_info, bins=None):
warnings.warn(
'The soiling module is currently experimental. The API, results, '
'and default behaviors may change in future releases (including MINOR '
- 'and PATCH releases) as the code matures.'
+ 'and PATCH releases) as the code matures.', stacklevel=2
)
soiling_summary = soiling_info['soiling_interval_summary']
diff --git a/rdtools/soiling.py b/rdtools/soiling.py
index e8524ff4..ef7b2b05 100644
--- a/rdtools/soiling.py
+++ b/rdtools/soiling.py
@@ -27,7 +27,7 @@
warnings.warn(
'The soiling module is currently experimental. The API, results, '
'and default behaviors may change in future releases (including MINOR '
- 'and PATCH releases) as the code matures.'
+ 'and PATCH releases) as the code matures.', stacklevel=2
)
@@ -123,7 +123,8 @@ def _calc_daily_df(self, day_scale=13, clean_threshold='infer',
warnings.warn('An even value of day_scale was passed. An odd value is '
'recommended, otherwise, consecutive days may be erroneously '
'flagged as cleaning events. '
- 'See https://github.com/NREL/rdtools/issues/189')
+ 'See https://github.com/NREL/rdtools/issues/189',
+ stacklevel=2)
df = self.pm.to_frame()
df.columns = ['pi']
@@ -382,7 +383,8 @@ def _calc_monte(self, monte, method='half_norm_clean'):
'validity criteria such as increasing "max_relative_slope_error" '
'and/or "max_negative_step" and/or decreasing "min_interval_length".'
' Alternatively, consider using method="perfect_clean". For more'
- ' info see https://github.com/NREL/rdtools/issues/272'
+ ' info see https://github.com/NREL/rdtools/issues/272',
+ stacklevel=2
)
monte_losses = []
random_profiles = []
@@ -906,7 +908,8 @@ def annual_soiling_ratios(stochastic_soiling_profiles,
'The indexes of stochastic_soiling_profiles are not entirely '
'contained within the index of insolation_daily. Every day in '
'stochastic_soiling_profiles should be represented in '
- 'insolation_daily. This may cause erroneous results.')
+ 'insolation_daily. This may cause erroneous results.',
+ stacklevel=2)
insolation_daily = insolation_daily.reindex(all_profiles.index)