Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,20 @@ Given a version number MAJOR.MINOR.PATCH, increment the:

Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format.

---
## [2.6.0]
02-Apr-2026

### New Features and Enhancements
1. Updated `apply_wspd_slope_offset_adj()` in order to also apply slope and offset adjustments to related columns (for 'avg', 'min', 'max', 'gust', 'median', 'mode', 'range' and 'sd' statistic types) in addition to the main variable itself. It should be noted given lack of required information available no correction is applied to 'ti' 'ti30sec' or 'sum'. ([#575](https://github.com/brightwind-dev/brightwind/issues/575))
1. Updated `MeasurementStation` function `_mast_section_geometry()` to extract mast section geometry properties. This is called by a user with `MeasurementStation.mast_section_geometry`. ([#362](https://github.com/brightwind-dev/brightwind/issues/362))
1. Added `export_tws_file()` to allow export of a WindSim .tws formated timeseries climatology file. ([#305](https://github.com/brightwind-dev/brightwind/issues/305))
1. Updated `plot_scatter()`, `plot_scatter_wdir()`, `plot_scatter_wspd()`, `CorrelBase.plot()` and `SpeedSort.plot_wind_directions()`, to accept an `ax` parameter to support use in subplots. ([#596](https://github.com/brightwind-dev/brightwind/issues/596))
1. Updated `scale_air_pressure_to_height()` to accept air temperature and air pressure time series at different heights, the user can now specify a separate height for the reference temperature time series. This is then used to scale the time series to the target height before scaled air pressure is calculated. ([#562](https://github.com/brightwind-dev/brightwind/issues/562))

### Bug Fixes
1. Fixed two bugs on `SpeedSort.synthesize()` accounting for NaNs and when direction is 360. ([#406](https://github.com/brightwind-dev/brightwind/issues/406))

---
## [2.5.0]
12-Feb-2026
Expand All @@ -23,6 +37,8 @@ Additional labels for pre-release and build metadata are available as extensions
### Bug Fixes
1. Fixed two bugs on `apply_device_orientation_offset()` reported table and prints. ([#571](https://github.com/brightwind-dev/brightwind/issues/571))
2. Fixed bug on `apply_wind_vane_deadband_offset()` reported table and prints. ([#569](https://github.com/brightwind-dev/brightwind/issues/569))

---
## [2.4.0]

### New Features and Enhancements
Expand Down
2 changes: 1 addition & 1 deletion brightwind/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@

__all__ = ['analyse', 'transform', 'export', 'load', 'demo_datasets']

__version__ = '2.5.0'
__version__ = '2.6.0'
48 changes: 38 additions & 10 deletions brightwind/analyse/correlation.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,16 +98,18 @@ def show_params(self):
"""Show the dictionary of parameters"""
pprint.pprint(self.params)

def plot(self, figure_size=(10, 10.2)):
def plot(self, figure_size=(10, 10.2), ax=None):
"""
Plots scatter plot of reference versus target speed data. If ref_dir is given as input to the correlation then
the plot is showing scatter subplots for each sector. The regression line and the line of slope 1 passing
through the origin are also shown on each plot.

:param figure_size: Figure size in tuple format (width, height)
:type figure_size: tuple
:param ax: Matplotlib ax object for a subplot.
:type ax: matplotlib.axes.Axes
:returns: A matplotlib figure
:rtype: matplotlib.figure.Figure
:rtype: matplotlib.figure.Figure | None

**Example usage**
::
Expand All @@ -134,7 +136,7 @@ def plot(self, figure_size=(10, 10.2)):
self.data[self._tar_spd_col_name],
self._predict(self.data[self._ref_spd_col_name]),
x_label=self._ref_spd_col_name, y_label=self._tar_spd_col_name,
line_of_slope_1=True, figure_size=figure_size)
line_of_slope_1=True, figure_size=figure_size, ax=ax)
else:
"""For plotting scatter by sector"""
return plot_scatter_by_sector(self.data[self._ref_spd_col_name],
Expand Down Expand Up @@ -286,9 +288,10 @@ def synthesize(self, ext_input=None, ref_coverage_threshold=None, target_coverag
coverage_threshold=ref_coverage_threshold,
return_coverage=False)
synth_data = ref_averaged[self._ref_spd_col_name].copy() * np.nan
ref_dir_normalised = ref_averaged[self._ref_dir_col_name] % 360
for params_dict in self.params:
if params_dict['num_data_points'] > 1:
logic_sect = self._get_logic_dir_sector(ref_dir=ref_averaged[self._ref_dir_col_name],
logic_sect = self._get_logic_dir_sector(ref_dir=ref_dir_normalised,
sector_min=params_dict['sector_min'],
sector_max=params_dict['sector_max'])

Expand Down Expand Up @@ -1091,7 +1094,7 @@ def _linear_interpolation(xa, xb, ya, yb, xc):

def _predict_dir(self, x_dir):

x_dir = x_dir.dropna().rename('dir')
x_dir = (x_dir.dropna() % 360).rename('dir')

sector_min = []
sector_max = []
Expand Down Expand Up @@ -1222,7 +1225,27 @@ def _predict(self, x_spd, x_dir):
return prediction.sort_index()

def synthesize(self, input_spd=None, input_dir=None):

"""
Apply the derived SpeedSort correlation model to produce a synthesized speed and direction dataset.

When called with no arguments, the model is applied to the original reference speed and direction data.
This is first averaged to the averaging period requested when `SpeedSort` is initialised. The resulting
synthesized dataset is then spliced with the target dataset — where a target value is available it is used
instead of the synthesized value.

When `input_spd` and `input_dir` are provided, the model is applied to those inputs directly and
no splicing with the target dataset is performed. The output index matches the input index.

:param input_spd: Optional external speed series to apply the model to instead of the original
reference speed. Must be provided together with `input_dir`.
:type input_spd: pd.Series or None
:param input_dir: Optional external direction series to apply the model to instead of the original
reference direction. Must be provided together with `input_spd`.
:type input_dir: pd.Series or None
:return: DataFrame with two columns: synthesized speed (named `<target_spd>_Synthesized`)
and synthesized direction (named `<target_dir>_Synthesized`).
:rtype: pd.DataFrame
"""
if input_spd is None and input_dir is None:
ref_start_date, target_start_date = self._get_synth_start_dates()

Expand All @@ -1241,22 +1264,27 @@ def synthesize(self, input_spd=None, input_dir=None):
return_coverage=False).combine_first(dir_output)

else:
output = self._predict(input_spd, input_dir)
dir_output = self._predict_dir(input_dir)
output = self._predict(input_spd, input_dir).reindex(input_spd.index)
dir_output = self._predict_dir(input_dir).reindex(input_dir.index)
output[output < 0] = 0
return pd.concat([output.rename(self._tar_spd_col_name + "_Synthesized"),
dir_output.rename(self._tar_dir_col_name + "_Synthesized")], axis=1, join='outer')

def plot_wind_directions(self):
def plot_wind_directions(self, ax=None):
"""
Plots reference and target directions in a scatter plot

:param ax: Matplotlib ax object for a subplot.
:type ax: matplotlib.axes.Axes
:returns: A matplotlib figure
:rtype: matplotlib.figure.Figure | None
"""
return plot_scatter_wdir(
self.data[self._ref_dir_col_name][(self.data[self._ref_spd_col_name] > self.cutoff) &
(self.data[self._tar_spd_col_name] > self.cutoff)],
self.data[self._tar_dir_col_name][(self.data[self._ref_spd_col_name] > self.cutoff) &
(self.data[self._tar_spd_col_name] > self.cutoff)],
x_label=self._ref_dir_col_name, y_label=self._tar_dir_col_name)
x_label=self._ref_dir_col_name, y_label=self._tar_dir_col_name, ax=ax)


class SVR:
Expand Down
36 changes: 24 additions & 12 deletions brightwind/analyse/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -972,7 +972,7 @@ def _get_best_row_col_number_for_subplot(number_subplots):

def plot_scatter(x, y, trendline_y=None, trendline_x=None, line_of_slope_1=False,
x_label=None, y_label=None, x_limits=None, y_limits=None, axes_equal=True, figure_size=(10, 10.2),
trendline_dots=False, **kwargs):
trendline_dots=False, ax=None, **kwargs):
"""
Plots a scatter plot of x and y data. The trendline_y data is also shown if provided as input of the function.

Expand Down Expand Up @@ -1001,9 +1001,11 @@ def plot_scatter(x, y, trendline_y=None, trendline_x=None, line_of_slope_1=False
:type trendline_dots: Bool
:param figure_size: Figure size in tuple format (width, height)
:type figure_size: tuple
:param ax: Matplotlib ax object for a subplot.
:type ax: matplotlib.axes.Axes
:param kwargs: Additional keyword arguments for matplotlib.pyplot.subplot
:return: A scatter plot
:rtype: matplotlib.figure.Figure
:rtype: matplotlib.figure.Figure | None

**Example usage**
::
Expand Down Expand Up @@ -1064,17 +1066,23 @@ def plot_scatter(x, y, trendline_y=None, trendline_x=None, line_of_slope_1=False
if line_of_slope_1 is True:
legend = True

fig, axes = plt.subplots(figsize=figure_size, **kwargs)
if ax is None:
fig, ax = plt.subplots(figsize=figure_size, **kwargs)
return_fig = True
else:
fig = ax.get_figure()
return_fig = False
_scatter_subplot(x, y, trendline_y=trendline_y, trendline_x=trendline_x, line_of_slope_1=line_of_slope_1,
x_label=x_label, y_label=y_label, x_limits=x_limits, y_limits=y_limits, axes_equal=axes_equal,
trendline_dots=trendline_dots, legend=legend, ax=axes)
trendline_dots=trendline_dots, legend=legend, ax=ax)

plt.close()
return fig
if return_fig:
plt.close()
return fig


def plot_scatter_wdir(x_wdir_series, y_wdir_series, x_label=None, y_label=None,
x_limits=(0, 360), y_limits=(0, 360)):
x_limits=(0, 360), y_limits=(0, 360), ax=None):
"""
Plots a scatter plot of two wind direction timeseries and adds a line from 0,0 to 360,360.

Expand All @@ -1090,8 +1098,10 @@ def plot_scatter_wdir(x_wdir_series, y_wdir_series, x_label=None, y_label=None,
:type x_limits: tuple
:param y_limits: y-axis min and max limits.
:type y_limits: tuple
:param ax: Matplotlib ax object for a subplot.
:type ax: matplotlib.axes.Axes
:return: A scatter plot
:rtype: matplotlib.figure.Figure
:rtype: matplotlib.figure.Figure | None

**Example usage**
::
Expand All @@ -1115,13 +1125,13 @@ def plot_scatter_wdir(x_wdir_series, y_wdir_series, x_label=None, y_label=None,
if y_label is None:
y_label = y_wdir_series.name + ' [°]'
scat_plot = plot_scatter(x_wdir_series, y_wdir_series, x_label=x_label, y_label=y_label,
x_limits=x_limits, y_limits=y_limits, line_of_slope_1=True)
x_limits=x_limits, y_limits=y_limits, line_of_slope_1=True, ax=ax)

return scat_plot


def plot_scatter_wspd(x_wspd_series, y_wspd_series, x_label=None, y_label=None,
x_limits=(0, 30), y_limits=(0, 30)):
x_limits=(0, 30), y_limits=(0, 30), ax=None):
"""
Plots a scatter plot of two wind speed timeseries and adds a reference line from 0,0 to 40,40. This should
only be used for wind speeds in m/s and not when one of the wind speed series is normalised. Please use the
Expand All @@ -1141,8 +1151,10 @@ def plot_scatter_wspd(x_wspd_series, y_wspd_series, x_label=None, y_label=None,
:param y_limits: y-axis min and max limits. Can be set to None to let the code derive the min and max from
the y_wspd_series.
:type y_limits: tuple, None
:param ax: Matplotlib ax object for a subplot.
:type ax: matplotlib.axes.Axes
:return: A scatter plot
:rtype: matplotlib.figure.Figure
:rtype: matplotlib.figure.Figure | None

**Example usage**
::
Expand All @@ -1166,7 +1178,7 @@ def plot_scatter_wspd(x_wspd_series, y_wspd_series, x_label=None, y_label=None,
if y_label is None:
y_label = y_wspd_series.name + ' [m/s]'
scat_plot = plot_scatter(x_wspd_series, y_wspd_series, x_label=x_label, y_label=y_label,
x_limits=x_limits, y_limits=y_limits, line_of_slope_1=True)
x_limits=x_limits, y_limits=y_limits, line_of_slope_1=True, ax=ax)

return scat_plot

Expand Down
26 changes: 26 additions & 0 deletions brightwind/demo_datasets/demo_tws_file.tws
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export_tws_file created using brightwind version 2.6.0-dev at 2026-03-05 15:16:51.
version : 48
site name : brightwind_site
measurement period : January 16 - January 16
site position : 10 10
coordinate system : 3
measurement height : 80

rec nr: year: mon: date: hour: min: dir: speed: SDspeed:
1 2016 01 09 17 10 115.6 6.252 0.933
2 2016 01 09 17 20 113.6 8.140 0.676
3 2016 01 09 17 30 117.4 8.330 0.842
4 2016 01 09 17 40 115.9 7.972 0.595
5 2016 01 09 17 50 118.9 7.649 0.575
6 2016 01 09 18 00 111.4 7.578 0.529
7 2016 01 09 18 10 113.8 7.791 0.750
8 2016 01 09 18 20 117.4 9.570 0.636
9 2016 01 09 18 30 113.2 8.940 0.798
10 2016 01 09 18 40 109.3 9.840 0.877
11 2016 01 09 18 50 106.0 9.830 1.079
12 2016 01 09 19 00 100.7 9.910 0.748
13 2016 01 09 19 10 99.9 9.240 0.889
14 2016 01 09 19 20 105.2 9.270 0.775
15 2016 01 09 19 30 118.6 10.510 0.534
16 2016 01 09 19 40 119.2 9.260 0.722
17 2016 01 09 19 50 112.8 6.995 1.667
Loading
Loading