Wind Resource Temporal Variability

Diurnal and monthly variability of NREL Wind Toolkit Data
Python
renewable energy
Author

This code sample uses statistical analyses and visualizations to explore the diurnal and monthly variability of wind resources at Mount Washington in New Hampshire using data from the National Renewable Energy Laboratory (NREL) Wind Integration National Dataset (WIND) Toolkit. Datasets within this tool include meteorological conditions such as temperature, pressure, relative humidity, wind direction, and wind speed. Hourly data is available for the continental United States from 2007 to 2013. This analysis used the dataset of wind speed at 100 meters for the year 2012.

Note

Click the drop down to the right of the title to view all code written for this analysis

Access data

The code below was used to access the data with the h5pyd Python package and NREL Highly Scalable Data Service (HSDS). The data are stored in the file wtk_us.h5 which is in HDF5 format. Data are stored in three dimensions dataset[t, y, x] where x is the longitudinal index, y is the latitudinal index, and t is the temporal index. Timestamps are in the UTC time zone.

import packages
import h5pyd
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
from pyproj import Proj
import dateutil
import folium
import plotly.graph_objects as go
access wind toolkit file
f = h5pyd.File("/nrel/wtk-us.h5", 'r', bucket="nrel-pds-hsds")

# access the windspeed_100m dataset
windspeed_100m_dset = f['windspeed_100m']
access datetime dataset
datetime_df = f["datetime"]
datetime_df = pd.DataFrame({"datetime": datetime_df[:]},index=range(0,datetime_df.shape[0]))
datetime_df['datetime'] = datetime_df['datetime'].apply(dateutil.parser.parse)
specify datetime range
start_date = '2012-01-01'
end_date = '2013-01-01'
datetime_range = datetime_df.loc[(datetime_df.datetime >= start_date) & (datetime_df.datetime < end_date)].index

Determine nearest timeseries for given Lat/Lon

The file structure organizes the data into 2 kilometer x 2 kilometer grids. The code below takes the latitude/longitude coordinates of an individual site (in this case Mount Washington) and finds the indices and coordinates of the nearest site within the dataset. Latitude and longitude coordinates are in a modified Lambert Conic projection.

site specific info
site_name = "Mount Washington"
site_coords = (44.27, -71.3)
function to find nearest point
# This function finds the nearest x/y indices for a given lat/lon.
# Rather than fetching the entire coordinates database, which is 500+ MB, this
# uses the Proj4 library to find a nearby point and then converts to x/y indices

def indicesForCoord(f, lat_index, lon_index):
    dset_coords = f['coordinates']
    projstring = """+proj=lcc +lat_1=30 +lat_2=60 
                    +lat_0=38.47240422490422 +lon_0=-96.0 
                    +x_0=0 +y_0=0 +ellps=sphere 
                    +units=m +no_defs """
    projectLcc = Proj(projstring)
    origin_ll = reversed(dset_coords[0][0])  # Grab origin directly from database
    origin = projectLcc(*origin_ll)
    
    coords = (lon_index,lat_index)
    coords = projectLcc(*coords)
    delta = np.subtract(coords, origin)
    ij = [int(round(x/2000)) for x in delta]
    return tuple(reversed(ij))

nearest_site = indicesForCoord(f, site_coords[0], site_coords[1] )

print("y,x indices for", site_name, ": \t\t {}".format(nearest_site))
y,x indices for Mount Washington :       (1258, 2423)
function to find nearest point
print("Coordinates of", site_name, ": \t {}".format(site_coords))
Coordinates of Mount Washington :    (44.27, -71.3)
function to find nearest point
print("Coordinates of nearest point: \t {}".format(f["coordinates"][nearest_site[0]][nearest_site[1]]))
Coordinates of nearest point:    (44.265121, -71.280396)

Map

This map shows the location of Mount Washington and the nearest point from the WIND Toolkit.

create map showing location of site and nearest point
nearest_site_coords = f["coordinates"][nearest_site[0]][nearest_site[1]]

site_map = folium.Map(location = site_coords, zoom_start = 10)
folium.Marker(site_coords, popup = site_name).add_to(site_map)
<folium.map.Marker object at 0x173c93460>
create map showing location of site and nearest point
folium.Marker(nearest_site_coords, popup = 'Nearest Site').add_to(site_map)
<folium.map.Marker object at 0x173c93d00>
create map showing location of site and nearest point
site_map
Make this Notebook Trusted to load map: File -> Trust Notebook

Create dataframes

Next, the time series for the specified time range is used to get corresponding wind speed data for the point nearest Mount Washington and the data is converted to a pandas dataframe with columns parsed for month, day, and hour.

get the entire 2012 timeseries data for a point near the site
tseries = windspeed_100m_dset[min(datetime_range):max(datetime_range)+1, nearest_site[0], nearest_site[1]]
convert NumPy array to pandas dataframe
windspeed_100m_df = pd.DataFrame(tseries, columns = ["windspeed_100m"], index = datetime_df.iloc[datetime_range,].datetime)

windspeed_100m_df["year"] = windspeed_100m_df.index.year
windspeed_100m_df["month"] = windspeed_100m_df.index.month
windspeed_100m_df["day"] = windspeed_100m_df.index.day
windspeed_100m_df["hour"] = windspeed_100m_df.index.hour
windspeed_100m_df["day_of_year"] = windspeed_100m_df.index.dayofyear
windspeed_100m_df["month_name"] = windspeed_100m_df.index.month_name()

Aggregate data

The pandas dataframe was then aggregated to group the data by month and hour and calculate values for mean and standard deviation. Functions to calculate the first, second, and third quartiles were also used. These dataframes were used for the statistical analysis and visualizations.

functions to calculate quartiles
def quantile25(column):
    return column.quantile(0.25)

def quantile50(column):
    return column.quantile(0.50)

def quantile75(column):
    return column.quantile(0.75)
create dataframe of average wind speed for each hour
hourly_avg = windspeed_100m_df.groupby("hour")["windspeed_100m"].agg(["mean", "std", quantile25, quantile50, quantile75])
hourly_avg = hourly_avg.reset_index()
hourly_avg.head()
   hour       mean       std  quantile25  quantile50  quantile75
0     0  12.387863  6.712486    7.355722   12.079224   16.777550
1     1  12.496613  6.747195    7.489243   12.189095   16.737114
2     2  12.483580  6.864682    7.070374   12.457657   16.932434
3     3  12.368402  6.868386    6.917786   12.387463   16.807304
4     4  12.149086  6.715096    6.789604   12.137211   16.203804
create dataframe of average wind speed for each month
monthly_avg = windspeed_100m_df.groupby(["month", "month_name"])["windspeed_100m"].agg(["mean", "std", quantile25, quantile50, quantile75]).reset_index()
monthly_avg.head()
   month month_name       mean       std  quantile25  quantile50  quantile75
0      1    January  15.694266  6.280080   11.762596   15.463730   19.280067
1      2   February  15.809338  7.093691   11.239204   16.272469   20.300148
2      3      March  12.230855  6.782397    6.586655   11.641289   17.367319
3      4      April  13.519631  6.016217    9.871977   14.194160   17.768639
4      5        May   9.106350  5.020830    5.429245    8.146919   12.250893
create dataframe of average wind speed for each hour grouped by month
hourly_avg_by_month = windspeed_100m_df.groupby(["hour", "month"]).mean()
<string>:1: FutureWarning: The default value of numeric_only in DataFrameGroupBy.mean is deprecated. In a future version, numeric_only will default to False. Either specify numeric_only or select only columns which should be valid for the function.
create dataframe of average wind speed for each hour grouped by month
hourly_avg_by_month = hourly_avg_by_month.reset_index().pivot(index = "hour", columns = str("month"), values = "windspeed_100m")
hourly_avg_by_month.columns = hourly_avg_by_month.columns.astype(str)
hourly_avg_by_month.head()
month          1          2          3  ...         10         11         12
hour                                    ...                                 
0      16.933538  17.768404  12.863748  ...  12.589771  11.489303  12.568310
1      16.990442  17.863747  12.890527  ...  13.027860  11.083610  12.639684
2      17.024899  17.666428  12.944868  ...  13.207229  11.067130  12.651399
3      17.247192  17.689474  12.414536  ...  13.030813  10.995311  12.727105
4      17.087215  17.182659  12.194508  ...  13.004233  10.831832  12.852821

[5 rows x 12 columns]
create dataframe of wind speed standard deviation for each hour by month
hourly_std_by_month = windspeed_100m_df.groupby(["hour", "month"]).std()
<string>:1: FutureWarning: The default value of numeric_only in DataFrameGroupBy.std is deprecated. In a future version, numeric_only will default to False. Either specify numeric_only or select only columns which should be valid for the function.
create dataframe of wind speed standard deviation for each hour by month
hourly_std_by_month = hourly_std_by_month.reset_index().pivot(index = "hour", columns = str("month"), values = "windspeed_100m")
hourly_std_by_month.columns = hourly_std_by_month.columns.astype(str)
hourly_std_by_month.head()
month         1         2         3  ...        10        11        12
hour                                 ...                              
0      5.391501  6.570820  6.416065  ...  7.869388  8.129505  7.792957
1      5.445199  6.508991  6.569621  ...  7.946729  7.871586  7.952874
2      5.927134  6.394429  7.066956  ...  8.314149  7.716703  7.624423
3      5.916150  6.125285  6.914925  ...  8.346868  7.587217  7.677165
4      5.975209  5.834017  6.484714  ...  8.281481  7.604963  7.446602

[5 rows x 12 columns]
calculate moving averages
# 24 hour moving average
window_size_24hr = 24
windows_24hr = windspeed_100m_df.rolling(window_size_24hr)
moving_averages_24hr = windows_24hr.mean()

# 10 day moving average
calculate moving averages
window_size_10day = 240
windows_10day = windspeed_100m_df.rolling(window_size_10day)
moving_averages_10day = windows_10day.mean()

# 30 day moving average
window_size_30day = 720
windows_30day = windspeed_100m_df.rolling(window_size_30day)
moving_averages_30day = windows_30day.mean()

Statistical analysis

Before beginning the statistical analysis, here is a graph of the full time series to get a general idea of the shape and magnitude of the data for this location. Wind speed seems to fluctuate a lot. Toggle the legend lines on/off to see 24 hour, 10 day, and 30 day rolling averages. From the 30 day average, wind speed seems to be higher in the winter than the summer. Let’s explore the monthly and daily wind speed data further to quantify the variability.

code to graph rolling averages
fig = go.Figure([
    go.Scatter(x = windspeed_100m_df.index, y = windspeed_100m_df['windspeed_100m'], 
              mode = 'lines', legendrank = 1, 
              name = 'hourly', line=dict(color='blue', width=0.75)),
    go.Scatter(x = moving_averages_24hr.index, y = moving_averages_24hr['windspeed_100m'], 
              mode = 'lines', legendrank = 1,
              name = '24 hour avg', line=dict(color='green', width=1), visible='legendonly'),
    go.Scatter(x = moving_averages_10day.index, y = moving_averages_10day['windspeed_100m'], 
              mode = 'lines', legendrank = 1, 
              name = '10 day avg', line=dict(color='red', width=1), visible='legendonly'),
    go.Scatter(x = moving_averages_30day.index, y = moving_averages_30day['windspeed_100m'], 
              mode = 'lines', legendrank = 1, 
              name = '30 day avg', line=dict(color='yellow', width=3), visible='legendonly')
])

fig.update_layout(
    margin=dict(l=20, r=20, t=30, b=20),
    paper_bgcolor="#FFFFFF",
    plot_bgcolor='#f5f5f5',
    yaxis=dict(
        title_text="windspeed (m/s)",
        titlefont=dict(size=16)),
    title={
        'text': "Hourly Wind Speed",
        'y':0.99,
        'x':0.5,
        'xanchor': 'center',
        'yanchor': 'top'}
)


Diurnal variability is important when selecting potential sites for utility scale wind power because it is important to have adequate wind speeds when electricity demand peaks. To begin looking at the diurnal variability for Mount Washington, the graph on the left shows average hourly wind speed for the year 2012. This graph also presents the interquartile range (IQR) and standard deviation of this data. The IQR illustrates the spread of the middle half of wind speed values. The standard deviation quantifies the spread of the data around the mean. The lower the standard deviation, the more the data is clustered around the mean. The greater the standard deviation, the more spread out the data.

Based on the hourly wind speed visualization, wind speed at Mount Washington seems to be lowest in the middle of the day, but based on the standard deviation bars and interquartile range, there is a large spread to the data. Seasonal diurnal variability will be explored further down the page.

The graph on the right shows a similar visual for average monthly wind speed. Here we see that wind speeds tend to be lower in the summer than the winter. Low mid-day wind speeds during the summer result in decreased power generation from wind sources. This could be problematic when electricity demands from air conditioning units are high.

Hourly average

code for graph of hourly averages with IQR and standard deviation
fig = go.Figure([
    go.Scatter(name = 'mean', y = hourly_avg['mean'], x = hourly_avg['hour'], mode = 'lines',
              line = dict(color = "blue", width = 4),
              error_y = dict(type = 'data', array = hourly_avg['std'], visible = True)),
    go.Scatter(
        name = 'IQR 75', y = hourly_avg['quantile75'], x = hourly_avg['hour'],
        mode='lines',
        marker=dict(color="#444"),
        line=dict(width=0),
        #legendgroup = 'IQR',
        showlegend = False
    ),
    # Create IQR 25 fill color
    go.Scatter(
        name='IQR', y = hourly_avg['quantile25'], x = hourly_avg['hour'],
        marker=dict(color="#444"),
        line=dict(width=0),
        mode='lines',
        fillcolor='rgba(68, 68, 68, 0.3)',
        fill='tonexty', # fill to next y
        legendgroup = 'IQR',
        showlegend = True
    )
])
fig.update_layout(
    xaxis=dict(
        title_text="hour (UTC)",
        titlefont=dict(size=16),
        dtick = 2),
    yaxis=dict(
        title_text="windspeed (m/s)",
        titlefont=dict(size=16)),
    title={
        'text': "Average Hourly Wind Speed for the Year 2012",
        'y':0.99,
        'x':0.5,
        'xanchor': 'center',
        'yanchor': 'top'},
    margin=dict(l=20, r=20, t=30, b=20),
    paper_bgcolor="#FFFFFF",
    plot_bgcolor='#f5f5f5'
)

Monthly average

hourly averages with IQR and standard deviation
fig = go.Figure([
    go.Scatter(name = 'mean', y = monthly_avg['mean'], x = monthly_avg['month'], 
              mode = 'lines', line = dict(color = "blue", width = 4),
              error_y = dict(type = 'data', array = monthly_avg['std'], visible = True)),
    go.Scatter(
        name = 'IQR 75', y = monthly_avg['quantile75'], x = monthly_avg['month'],
        mode='lines', marker=dict(color="#444"), line=dict(width=0),
        showlegend = False
    ),

    # Create IQR 25 fill color
    go.Scatter(
        name='IQR', y = monthly_avg['quantile25'], x = monthly_avg['month'],
        marker=dict(color="#444"), line=dict(width=0), mode='lines',
        fillcolor='rgba(68, 68, 68, 0.3)',
        fill='tonexty', # fill to next y
        legendgroup = 'IQR',
        showlegend = True)
])
fig.update_layout(
    xaxis=dict(
        title_text="month",
        titlefont=dict(size=16),
        dtick = 1),
    yaxis=dict(
        title_text="windspeed (m/s)",
        titlefont=dict(size=16)),
    title={
        'text': "Average Monthly Wind Speed for the Year 2012",
        'y':0.99,
        'x':0.5,
        'xanchor': 'center',
        'yanchor': 'top'},
    margin=dict(l=20, r=20, t=30, b=20),
    paper_bgcolor="#FFFFFF",
    plot_bgcolor='#f5f5f5'
)


The next graph shows hourly average wind speed with lines for each month. The thick black line represents the annual mean for each hour.

This graph illustrates both the magnitude of wind speed values related to other months as well and the seasonal variability of daily wind speed patterns. For the year 2012, the months of January, February, March, April, October and December had higher than average wind speeds while the months of May, June, July, August, September, and November had lower than average wind speed. For most months, wind speeds are lowest during the middle of the day.

code to graph hourly averages for each month
fig = go.Figure([
    go.Scatter(y = hourly_avg_by_month['1'], x = hourly_avg_by_month.index, 
              mode = 'lines', legendrank = 1, 
              name = 'January', line=dict(color='#DC050C', width=2)),
    go.Scatter(y = hourly_avg_by_month['2'], x = hourly_avg_by_month.index,
              mode = 'lines+markers', legendrank = 2, 
              name = 'February', line=dict(color='#E8601c', width=2)),
    go.Scatter(y = hourly_avg_by_month['3'], x = hourly_avg_by_month.index, 
              mode = 'lines', legendrank = 3, 
              name = 'March', line=dict(color='#f4a736', width=2)),
    go.Scatter(y = hourly_avg_by_month['4'], x = hourly_avg_by_month.index, 
              mode = 'lines+markers', legendrank = 4, 
              name = 'April', line=dict(color='#f7f056', width=2)),
    go.Scatter(y = hourly_avg_by_month['5'], x = hourly_avg_by_month.index, 
              mode = 'lines', legendrank = 5, 
              name = 'May', line=dict(color='#cae0ab', width=2)),
    go.Scatter(y = hourly_avg_by_month['6'], x = hourly_avg_by_month.index, 
              mode = 'lines+markers', legendrank = 6, 
              name = 'June', line=dict(color='#4eb265', width=2)),
    go.Scatter(y = hourly_avg_by_month['7'], x = hourly_avg_by_month.index, 
              mode = 'lines', legendrank = 7, 
              name = 'July', line=dict(color='#7bafde', width=2)),
    go.Scatter(y = hourly_avg_by_month['8'], x = hourly_avg_by_month.index, 
              mode = 'lines+markers', legendrank = 8, 
              name = 'August', line=dict(color='#5289c7', width=2)),
    go.Scatter(y = hourly_avg_by_month['9'], x = hourly_avg_by_month.index, 
              mode = 'lines', legendrank = 9, 
              name = 'September', line=dict(color='#1965b0', width=2)),
    go.Scatter(y = hourly_avg_by_month['10'], x = hourly_avg_by_month.index, 
              mode = 'lines+markers', legendrank = 10, 
              name = 'October', line=dict(color='#882e72', width=2)),
    go.Scatter(y = hourly_avg_by_month['11'], x = hourly_avg_by_month.index, 
              mode = 'lines', legendrank = 11, 
              name = 'November', line=dict(color='#ae76a3', width=2)),
    go.Scatter(y = hourly_avg_by_month['12'], x = hourly_avg_by_month.index, 
              mode = 'lines+markers', legendrank = 12, 
              name = 'December', line=dict(color='#d1bbd7', width=2)),
    go.Scatter(name = 'annual mean', y = hourly_avg['mean'], x = hourly_avg['hour'], mode = 'lines',
              line = dict(color = "black", width = 4))

])
fig.update_layout(
    xaxis=dict(
        title_text="hour (UTC)",
        titlefont=dict(size=16),
        dtick = 4),
    yaxis=dict(
        title_text="windspeed (m/s)",
        titlefont=dict(size=16)),
    title={
        'text': "Average Hourly Wind Speed by Month",
        'y':0.99,
        'x':0.5,
        'xanchor': 'center',
        'yanchor': 'top'},
    margin=dict(l=20, r=20, t=30, b=20),
    paper_bgcolor="#FFFFFF",
    plot_bgcolor='#f5f5f5'
)
# fig.write_image("avg_hourly_windspeed_by_month.png")


This heatmap summarizes both the daily and monthly variability of wind speed at Mount Washington in 2012. Here we can clearly see that for this location, wind speeds are lowest in the summer and mid day and highest in the winter and early/late hours.

code for heatmap of hourly and monthly wind speed averages
heatmap_month = hourly_avg_by_month.columns.tolist()
heatmap_hour = hourly_avg_by_month.index.tolist()
heatmap_windspeed = hourly_avg_by_month.values.tolist()

trace = go.Heatmap(
   x = heatmap_month,
   y = heatmap_hour,
   z = heatmap_windspeed,
   type = 'heatmap',
   #colorscale = [(0,"blue"), (1,"red")],
   colorscale = 'mint',
   colorbar=dict(title='Wind Speed (m/s)')
)
data = [trace]
fig = go.Figure(data = data)

fig.update_layout(
    #width=1000,
    height=650,
    xaxis=dict(
        title_text="month",
        titlefont=dict(size=16),
        #dtick = 1,
        tickmode = 'array',
        # Set tick intervals to correspond with months
        tickvals = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
        ticktext = ['January', 'February', 'March', 'April', 
                    'May', 'June', 'July', 'August', 
                    'September', 'October', 'November', 'December'],
        tickfont = dict(size=16)),
    yaxis=dict(
        title_text="hour (UTC)",
        titlefont=dict(size=16),
        dtick = 1,
        tickfont = dict(size=16)),
    title={
        'text': "Average Wind Speed by Month and Hour",
        'y':0.99,
        'x':0.5,
        'xanchor': 'center',
        'yanchor': 'top'},
    margin=dict(l=20, r=20, t=30, b=20),
)

This next heatmap summarizes the size of the standard deviation varies by hour and month. Wind speed values tend to have a greater spread in the winter than the summer. The hourly pattern for standard deviation is less clear.

code for heatmap of hourly and monthly wind speed standard deviation
std_heatmap_month = hourly_std_by_month.columns.tolist()
std_heatmap_hour = hourly_std_by_month.index.tolist()
std_heatmap_windspeed = hourly_std_by_month.values.tolist()

trace = go.Heatmap(
   x = std_heatmap_month,
   y = std_heatmap_hour,
   z = std_heatmap_windspeed,
   type = 'heatmap',
   colorscale = 'Blues',
   colorbar=dict(title='Standard Deviation (m/s)')
)
data = [trace]
fig = go.Figure(data = data)

fig.update_layout(
    #width=1000,
    height=650,
    xaxis=dict(
        title_text="month",
        titlefont=dict(size=16),
        #dtick = 1,
        tickmode = 'array',
        # Set tick intervals to correspond with months
        tickvals = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
        ticktext = ['January', 'February', 'March', 'April', 
                    'May', 'June', 'July', 'August', 
                    'September', 'October', 'November', 'December'],
        tickfont = dict(size=16)),
    yaxis=dict(
        title_text="hour (UTC)",
        titlefont=dict(size=16),
        dtick = 1,
        tickfont = dict(size=16)),
    title={
        'text': "Wind Speed Standard Deviation by Month and Hour",
        'y':0.99,
        'x':0.5,
        'xanchor': 'center',
        'yanchor': 'top'},
    margin=dict(l=20, r=20, t=30, b=20),
)

Results and Conclusions

This analysis concludes that wind speeds at 100 meters near Mount Washington in New Hampshire varied based on month and time of day. As summarized in the table below, smallest monthly variability occurred in July and the greatest monthly variability occurred in November. August had the smallest average wind speed and February had the greatest average wind speed. Hourly variability was smallest for hour 19 and largest for hour 13. Average hourly wind speed was smallest for hour 16 and greatest for hour 1.

Overall, the diurnal wind speed pattern resulted in higher speeds between hours 1-4; from there, wind speed tended to decrease and reach a minimum between hours 13-18; wind speeds generally increased between hours 19-23. Monthly wind speeds tended to be highest during the winter and lowest during the summer.

Based on the seasonal variability, this site would be better at meeting high winter demands than summer demands. This site may not be ideal for meeting all daytime demands.

Code
min_std_month = monthly_avg.loc[monthly_avg['std'].idxmin()]['month_name']
max_std_month = monthly_avg.loc[monthly_avg['std'].idxmax()]['month_name']
min_mean_wind_month = monthly_avg.loc[monthly_avg['mean'].idxmin()]['month_name']
max_mean_wind_month = monthly_avg.loc[monthly_avg['mean'].idxmax()]['month_name']
Code
min_std_hour = hourly_avg.loc[hourly_avg['std'].idxmin()]['hour']
max_std_hour = hourly_avg.loc[hourly_avg['std'].idxmax()]['hour']
min_mean_wind_hour = hourly_avg.loc[hourly_avg['mean'].idxmin()]['hour']
max_mean_wind_hour = hourly_avg.loc[hourly_avg['mean'].idxmax()]['hour']
Code
min_hr_month_std = hourly_std_by_month.values.min()
max_hr_month_std = hourly_std_by_month.values.max()

min_hr_month_wind = hourly_avg_by_month.values.min()
max_hr_month_wind = hourly_avg_by_month.values.max()

From this analysis we can conclude that…

Summary of stats for monthly and hourly wind speed
smallest variability greatest variability smallest average wind speed greatest average wind speed
monthly July November August February
hourly 19 13 16 1
month & hour March, hour 16 October, hour 13 August, hour 15 February, hour 1
code to calculate min and max wind speeds
df_min = windspeed_100m_df['windspeed_100m'].min()
df_max = windspeed_100m_df['windspeed_100m'].max()

hourly_avg_min = hourly_avg['mean'].min()
hourly_avg_max = hourly_avg['mean'].max()

monthly_avg_min = monthly_avg['mean'].min()
monthly_avg_max = monthly_avg['mean'].max()

In 2012, wind speed at Mount Washington ranged from 0.12 m/s to 36.66 m/s. Hourly average wind speeds ranged from 10.71 m/s to 12.50 m/s. Monthly average wind speeds ranged from 8.78 m/s to 15.81 m/s.

When planning utility scale wind power plants, it is important to evaluate how the diurnal and seasonal wind speed patterns compare to electricity demand patterns. Is wind generated electricity available when people need it? If high wind speeds at a proposed site do not coincide with peak electricity demands, than that site may not be suitable for utility scale wind farms. Renewable energy planners should also consider the frequency with which wind speeds are within the acceptable operating range for a given turbine. For example, how often are wind speeds to slow for turbines to operate, or how often are wind speeds too high?

Expanded geographic scale

A report was created as a tool for expanding the geographic scale of this analysis. This report was generated using the parameterized reporting capabilities of Quarto, an open-source scientific and technical publishing system.

The report can be modified by specifying parameters for:

  • site name (site_name)
  • site latitude (site_lat)
  • site longitude (site_lon)
  • start date (start_date)
  • end date (end_date)
  • turbine cut-in speed (cut_in_speed)
  • turbine cut-out speed (cut_out_speed)
  • required annual average wind speed (req_annual_avg_speed)

Default parameters are:

  • start_date: ‘2012-01-01’
  • end_date: ‘2013-01-01’
  • cut_in_speed: 3.6 m/s
  • cut_out_speed: 24.6 m/s
  • req_annual_avg_speed: 5.8 m/s

For the specified site, the report answers the following questions:

  • is the annual average wind speed at least 13 mph (5.8 m/s)? 1
  • how often the wind is below the cut-in speed of 8 mph (3.6 m/s)? 2
  • how often the wind exceed the cut-out speed of 55 mph (24.6 m/s)?

A new report can be generated using the command line interface. The code below shows an example that renders a report for New York City in 2010 using default values for cut-in speed, cut-out speed, and required annual average wind speed.

quarto render report.qmd -P site_name:“New York City” -P site_lat:40.7128 -P site_lon:-74.0059 -P start_date:2010-01-01 -P end_date:2011-01-01 --output new_york_city_report.pdf

The function below can be used to generate multiple reports from a dataframe of parameters for different sites.

render_fun <- function(param_df){
  quarto::quarto_render(
    input = "report.qmd",
    execute_params = list(site_name = param_df$site_name,
                          site_lat = param_df$site_lat,
                          site_lon = param_df$site_lon,
                          start_date = param_df$start_date,
                          end_date = param_df$end_date),
    output_file = glue::glue("{param_df$site_name}-report.pdf")
  )
}

The next code chunks show an example of how to work with this function from a dataframe of input. Running the function generates four reports, one for each site.

report_parameters = data.frame(site_name = c("University of Delaware", "Umass Amherst", "UC Santa Barbara", "Golden, CO"), 
                               site_lat = c(39.68071, 42.39405, 34.413202, 39.74086), 
                               site_lon = c(-75.75173, -72.52938, -119.841955, -105.1686), 
                               start_date = c("2009-01-01", "2011-01-01", "2007-06-01", "2008-01-01"), 
                               end_date = c("2010-01-01", "2012-01-01", "2008-03-01", "2010-01-01"))
source(here("code_samples", "wind-resource-temporal-variability", "R", "render_fun.R"))

param_list <- split(report_parameters, seq(nrow(report_parameters))) %>% 
  purrr::walk(render_fun)

The example reports generated above can be viewed here:

University of Delaware

UMass Amherst

UC Santa Barbara

NREL - Golden, CO

Citations

Draxl, C., B.M. Hodge, A. Clifton, and J. McCaa. 2015. Overview and Meteorological Validation of the Wind Integration National Dataset Toolkit (Technical Report, NREL/TP-5000-61740). Golden, CO: National Renewable Energy Laboratory.

Draxl, C., B.M. Hodge, A. Clifton, and J. McCaa. 2015. “The Wind Integration National Dataset (WIND) Toolkit.” Applied Energy 151: 355366.

Energy.gov. “How Do Wind Turbines Survive Severe Storms?” Accessed October 11, 2022. https://www.energy.gov/eere/articles/how-do-wind-turbines-survive-severe-storms.

King, J., A. Clifton, and B.M. Hodge. 2014. Validation of Power Output for the WIND Toolkit (Technical Report, NREL/TP-5D00-61714). Golden, CO: National Renewable Energy Laboratory.

“Where Wind Power Is Harnessed - U.S. Energy Information Administration (EIA).” Accessed October 11, 2022. https://www.eia.gov/energyexplained/wind/where-wind-power-is-harnessed.php.

Footnotes

  1. The U.S. Energy Information Administration recommends an annual average wind speed of at least 9 mph (4 m/s) for small wind turbines and 13 mph (5.8 m/s) for utility-scale turbines. https://www.eia.gov/energyexplained/wind/where-wind-power-is-harnessed.php#:~:text=Good%20places%20for%20wind%20turbines,)%20for%20utility%2Dscale%20turbines.↩︎

  2. The Office of Energy Efficiency & Renewable Energy notes a typical cut-in speed of 6 to 9 mpg and cut-out speed of 55 mph. https://www.energy.gov/eere/articles/how-do-wind-turbines-survive-severe-storms↩︎

Citation

BibTeX citation:
@online{rivers,
  author = {Rivers, Marie},
  title = {Wind {Resource} {Temporal} {Variability}},
  url = {https://marierivers.github.io/code_samples/wind-resource-temporal-variability/},
  langid = {en}
}
For attribution, please cite this work as:
Rivers, Marie. n.d. “Wind Resource Temporal Variability.” https://marierivers.github.io/code_samples/wind-resource-temporal-variability/.