├── README.md ├── wd_bar_calculation.py ├── LICENSE ├── machine_learning_example.py ├── power_curve_query_func.py ├── IEC_sensitivity_functions.py ├── save_cup_data_CV_SULI.py ├── windrose.py ├── wind_resource_graphs.py └── windeval.py /README.md: -------------------------------------------------------------------------------- 1 | # POWER 2 | Program for Operational Wind Energy Resource: A Python toolbox for operational wind farm data. 3 | -------------------------------------------------------------------------------- /wd_bar_calculation.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon Jun 13 16:18:13 2016 4 | 5 | @author: jnewman 6 | """ 7 | 8 | import numpy as np 9 | 10 | ws = [5,5.3,5.2,4.8,4.5] 11 | wd = [180,190,185,175,170] 12 | #ws: 10-min. mean horizontal wind speed 13 | #wd: 10-min. mean wind direction 14 | 15 | u = np.sin(np.radians(wd) - np.pi)*ws #10-min. mean u values 16 | v = np.cos(np.radians(wd) - np.pi)*ws #10-min. mean v values 17 | 18 | u_bar = np.mean(u) #Average u value for hour-long time period 19 | v_bar = np.mean(v) #Average v value for hour-long time period 20 | wd_bar = (180./np.pi)*(np.arctan2(u_bar,v_bar) + np.pi) 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright © 2017 Alliance for Sustainable Energy, LLC 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /machine_learning_example.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Tue Jul 26 15:20:19 2016 4 | 5 | @author: jnewman 6 | """ 7 | import numpy as np 8 | from sklearn.cross_validation import train_test_split 9 | 10 | def machine_learning_RF(x_train,y_train,x_test,y_test): 11 | import numpy as np 12 | mask = [] 13 | 14 | #Gets rid of NaNs 15 | for i in range(np.shape(x_train)[1]): 16 | mask.append(~np.isnan(x_train[:,i])) 17 | mask.append(~np.isnan(np.transpose(y_train))) 18 | mask = np.transpose(reduce(np.logical_and, mask)) 19 | mask = mask.reshape(len(mask),) 20 | 21 | inputs = x_train[mask,:] 22 | targets = y_train[mask] 23 | 24 | mask2 = [] 25 | for i in range(np.shape(x_test)[1]): 26 | mask2.append(~np.isnan(x_test[:,i])) 27 | mask2 = np.transpose(reduce(np.logical_and, mask2)) 28 | inputs_test = x_test[mask2,:] 29 | #End getting rid of NaNs 30 | 31 | #Sets up forest 32 | #n-estimators is how many "trees" (samples) you will take 33 | from sklearn.ensemble import RandomForestRegressor 34 | rfc_new = RandomForestRegressor(n_estimators=100,random_state=42,max_features=2) 35 | #Training 36 | rfc_new = rfc_new.fit(inputs,targets) 37 | #Predicting 38 | predicted_y = rfc_new.predict(inputs_test) 39 | print rfc_new.feature_importances_ 40 | return y_test[mask2], predicted_y 41 | 42 | 43 | shear_exp = [-0.1,0.3,0.4,-0.2] 44 | TI_vals = [35,12,20,25] 45 | power = [1500,1200,1000,850] 46 | 47 | inputs_orig = np.transpose([shear_exp,TI_vals]) 48 | targets_orig = np.transpose(power) 49 | 50 | indices = np.arange(np.array(targets_orig).shape[0]) 51 | #Splits into training set and testing set 52 | x_train, x_test, y_train, y_test,idx_train,idx_test = train_test_split(inputs_orig,targets_orig,indices,test_size=0.25,random_state= 42) 53 | 54 | y_train = y_train.ravel() 55 | y_test = y_test.ravel() 56 | 57 | power_pred = machine_learning_RF(x_train,y_train,x_test,y_test) 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /power_curve_query_func.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Aug 5 10:15:18 2016 4 | 5 | @author: jnewman 6 | """ 7 | 8 | 9 | def power_curve_query(ws,TI,opt="normal_TI"): 10 | import numpy as np 11 | hub_height_ws = np.arange(3,13.5,0.5) 12 | 13 | power_normal_TI = np.array([0,20,63,116,177,248,331,428,540,667,812,972,1141,1299,1448,1561,1633,1661,1677,1678,1680]) 14 | power_low_TI = np.array([0,18,61,114,174,244,325,421,532,657,801,961,1134,1304,1463,1585,1654,1675,1680,1680,1680]) 15 | power_high_TI = np.array([0,24,68,123,185,258,344,446,562,693,841,994,1148,1287,1419,1519,1589,1637,1665,1679,1680]) 16 | 17 | if "var_TI" not in opt: 18 | if "normal_TI" in opt: 19 | power = power_normal_TI 20 | if "low_TI" in opt: 21 | power = power_low_TI 22 | if "high_TI" in opt: 23 | power = power_high_TI 24 | 25 | power_interp = np.interp(ws, hub_height_ws, power) 26 | else: 27 | from power_curve_query_func import power_curve_var_TI 28 | power_interp = power_curve_var_TI(ws,TI) 29 | 30 | return power_interp 31 | 32 | 33 | def power_curve_var_TI(ws,TI): 34 | 35 | import numpy as np 36 | hub_height_ws = np.arange(3,13.5,0.5) 37 | 38 | power_normal_TI = np.array([0,20,63,116,177,248,331,428,540,667,812,972,1141,1299,1448,1561,1633,1661,1677,1678,1680]) 39 | power_low_TI = np.array([0,18,61,114,174,244,325,421,532,657,801,961,1134,1304,1463,1585,1654,1675,1680,1680,1680]) 40 | power_high_TI = np.array([0,24,68,123,185,258,344,446,562,693,841,994,1148,1287,1419,1519,1589,1637,1665,1679,1680]) 41 | 42 | power_interp = np.zeros(len(ws)) 43 | power_interp[:] = np.nan 44 | index = 0 45 | for i,j in zip(ws,TI): 46 | if j < 10: 47 | power_interp[index] = np.interp(i, hub_height_ws, power_low_TI) 48 | if j >= 10 and j < 15: 49 | power_interp[index] = np.interp(i, hub_height_ws, power_normal_TI) 50 | if j >= 15 and j < 20: 51 | power_interp[index] = np.interp(i, hub_height_ws, power_high_TI) 52 | index += 1 53 | return power_interp 54 | -------------------------------------------------------------------------------- /IEC_sensitivity_functions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Mar 4 12:56:57 2016 4 | 5 | @author: jnewman 6 | """ 7 | 8 | import numpy as np 9 | from numpy import linalg as LA 10 | import matplotlib.pyplot as plt 11 | 12 | 13 | def bin_mean_func(data_min,data_max,bin_width,x_data,y_data): 14 | """Find avgs for each bin and return bin center and bin mean to plot. 15 | 16 | Arguments: 17 | data_min -- the minumum value included in the x data 18 | data max -- the maximum values included in the x data 19 | bin_width -- the width of bins desired 20 | x_data -- your independent variable 21 | y_data -- your dependent variable 22 | """ 23 | n_bins = (data_max-data_min)/bin_width 24 | 25 | bin_mean = [] 26 | bin_center = [] 27 | 28 | for i in np.arange(data_min,data_max,bin_width): 29 | mask = [x_data > i, x_data <= i + bin_width,~np.isnan(x_data),~np.isnan(y_data)] 30 | mask = reduce(np.logical_and, mask) 31 | data_temp = y_data[mask] 32 | if len(data_temp) > len(y_data)/(2*n_bins): 33 | bin_mean.append(np.mean(data_temp)) 34 | else: 35 | bin_mean.append(np.nan) 36 | bin_center.append(i + bin_width/2.) 37 | 38 | return np.array(bin_center),np.array(bin_mean) 39 | 40 | 41 | def linear_regression_intercept(x,y): 42 | """Find the slope, intercept and R^2 value for a scatterplot. 43 | 44 | Arguments: 45 | x -- independent variable 46 | y -- dependent variable 47 | """ 48 | masks = [~np.isnan(x),~np.isnan(y)] 49 | total_mask = reduce(np.logical_and, masks) 50 | x = x[total_mask] 51 | y = y[total_mask] 52 | A = np.vstack([x, np.ones(len(x))]).T 53 | m, c = np.linalg.lstsq(A, y)[0] 54 | y_pred = m*x + c 55 | total = 0 56 | for i in range(len(y)): 57 | total+=(y_pred[i]-np.mean(y))**2 58 | total = 0 59 | for i in y: 60 | total+=(i-np.mean(y))**2 61 | SST2 = LA.norm((y-np.mean(y)))**2 62 | total = 0 63 | for i in range(len(y)): 64 | total+=(y_pred[i]-y[i])**2 65 | SSE2 = LA.norm((y_pred-y))**2 66 | R_squared = 1-SSE2/SST2 67 | return m,c,R_squared 68 | 69 | def bin_mean_plot(data_min,data_max,bin_width,x_data,y_data,var_name): 70 | """Plot data and return statistical data. 71 | 72 | Arguments: 73 | data_min -- the minumum value included in the x data 74 | data max -- the maximum values included in the x data 75 | bin_width -- the width of bins desired 76 | x_data -- your independent variable 77 | y_data -- your dependent variable 78 | var_name -- the name of the variable you will test for sensitivity (string) 79 | 80 | Returns: 81 | A list containing: slope, sensitivity (slope * std), r^2, r*sensitivity 82 | """ 83 | #If sensitivity > 0.5 or r*sensitivitiy > 0.1, it is sensitive. 84 | 85 | 86 | #from IEC_sensitivity_functions import bin_mean_func 87 | [bin_center,bin_mean] = bin_mean_func(data_min,data_max,bin_width,x_data,y_data) 88 | #from stats_functions import linear_regression_intercept 89 | [m,c,r_squared] = linear_regression_intercept(bin_center,bin_mean) 90 | x_vals = np.arange(data_min,data_max,bin_width) 91 | y_vals = x_vals*m + c 92 | 93 | plt.figure() 94 | plt.scatter(x_data,y_data,s=5,color="blue") 95 | plt.scatter(bin_center,bin_mean,s=20,color="red") 96 | plt.plot(x_vals,y_vals,color="red",label = 'y = %(number)0.2f*x + %(number2)0.2f. R$^2$ = %(number3)0.2f'%\ 97 | {"number": m,"number2": c,"number3": r_squared} ) 98 | plt.xlim([data_min,data_max]) 99 | plt.ylim([-50,50]) 100 | plt.legend(loc='best') 101 | plt.xlabel(var_name) 102 | plt.ylabel('TI % Difference') 103 | var_stats = [m,m*np.std(x_data[~np.isnan(x_data)]),r_squared,np.sqrt(r_squared)*m*np.std(x_data[~np.isnan(x_data)])] 104 | return var_stats -------------------------------------------------------------------------------- /save_cup_data_CV_SULI.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu Oct 15 11:09:56 2015 4 | 5 | @author: jnewman 6 | """ 7 | 8 | from datetime import datetime 9 | from datetime import timedelta 10 | import numpy as np 11 | 12 | 13 | def get_array(column): 14 | var_temp = np.loadtxt(filename, delimiter=',', usecols=(column,),skiprows=1,dtype=str) 15 | var = np.zeros(len(var_temp)) 16 | for k in range(len(var_temp)): 17 | try: 18 | var[k] = float(var_temp[k]) 19 | except: 20 | var[k] = np.nan 21 | return var 22 | 23 | months = [1] 24 | tower_loc = "B5" 25 | 26 | dir_name = '/Users/jnewman/Desktop/Data for Elise/' + tower_loc + ' Data/Processed Python Files/' 27 | data_dir = '/Users/jnewman/Desktop/Dissertation Data/Chisholm_View_Data/Chisholm_View_OU_LLNL_Shared_Data/Met Tower Data/' 28 | 29 | 30 | for j in months: 31 | 32 | if j == 11: 33 | filename = data_dir + 'Met ' + tower_loc + ' Data 2013 1107-1130.csv' 34 | if j == 12: 35 | filename = data_dir + 'Met ' + tower_loc + ' Data 2013 1201-1231.csv' 36 | if j == 1: 37 | filename = data_dir + 'Met ' + tower_loc + ' Data 2014 0101-0114.csv' 38 | 39 | if j == 5 or j==6: 40 | filename = data_dir + 'Met ' + tower_loc + ' Data 2014 0501-0630.csv' 41 | 42 | timestamp = np.loadtxt(filename, delimiter=',', usecols=(0,),dtype=str, unpack=True,skiprows=1) 43 | 44 | time_datenum = [] 45 | for i in timestamp: 46 | try: 47 | time_datenum.append(datetime.strptime(i,"%m/%d/%Y %H:%M")- timedelta(minutes=20)+ timedelta(hours=6)) 48 | except: 49 | time_datenum.append(datetime.strptime(i,"%m/%d/%y %H:%M")- timedelta(minutes=20)+ timedelta(hours=6)) 50 | 51 | pressure = np.loadtxt(filename, delimiter=',', usecols=(2,),skiprows=1) 52 | rain_rate = np.loadtxt(filename, delimiter=',', usecols=(8,),skiprows=1) 53 | RH_76 = np.loadtxt(filename, delimiter=',', usecols=(9,),skiprows=1) 54 | temp_76 = get_array(13) 55 | temp_10 = get_array(17) 56 | wd_76_1 = get_array(25) 57 | wd_76_2 = get_array(29) 58 | wd_43 = get_array(33) 59 | ws_78 = np.loadtxt(filename, delimiter=',', usecols=(37,),skiprows=1) 60 | ws_78_std_dev = np.loadtxt(filename, delimiter=',', usecols=(40,),skiprows=1) 61 | ws_80 = np.loadtxt(filename, delimiter=',', usecols=(41,),skiprows=1) 62 | ws_80_std_dev = np.loadtxt(filename, delimiter=',', usecols=(44,),skiprows=1) 63 | ws_74 = np.loadtxt(filename, delimiter=',', usecols=(45,),skiprows=1) 64 | ws_74_std_dev = np.loadtxt(filename, delimiter=',', usecols=(48,),skiprows=1) 65 | ws_38 = np.loadtxt(filename, delimiter=',', usecols=(49,),skiprows=1) 66 | ws_38_std_dev = np.loadtxt(filename, delimiter=',', usecols=(52,),skiprows=1) 67 | 68 | time_all = [] 69 | pressure_all = [] 70 | rain_rate_all = [] 71 | RH_76_all = [] 72 | temp_76_all = [] 73 | temp_10_all = [] 74 | wd_76_1_all = [] 75 | wd_76_2_all = [] 76 | wd_43_all = [] 77 | ws_78_all = [] 78 | ws_78_std_dev_all = [] 79 | ws_80_all = [] 80 | ws_80_std_dev_all = [] 81 | ws_74_all = [] 82 | ws_74_std_dev_all = [] 83 | ws_38_all = [] 84 | ws_38_std_dev_all = [] 85 | 86 | for mm in months: 87 | for dd in range(32): 88 | for ii in range(len(time_datenum)): 89 | if time_datenum[ii].month == mm and time_datenum[ii].day == dd: 90 | time_all.append(time_datenum[ii]) 91 | pressure_all.append(pressure[ii]) 92 | rain_rate_all.append(rain_rate[ii]) 93 | RH_76_all.append(RH_76[ii]) 94 | temp_76_all.append(temp_76[ii]) 95 | temp_10_all.append(temp_10[ii]) 96 | wd_76_1_all.append(wd_76_1[ii]) 97 | wd_76_2_all.append(wd_76_2[ii]) 98 | wd_43_all.append(wd_43[ii]) 99 | ws_78_all.append(ws_78[ii]) 100 | ws_78_std_dev_all.append(ws_78_std_dev[ii]) 101 | ws_80_all.append(ws_80[ii]) 102 | ws_80_std_dev_all.append(ws_80_std_dev[ii]) 103 | ws_74_all.append(ws_74[ii]) 104 | ws_74_std_dev_all.append(ws_74_std_dev[ii]) 105 | ws_38_all.append(ws_38[ii]) 106 | ws_38_std_dev_all.append(ws_38_std_dev[ii]) 107 | if len(pressure_all) > 3: 108 | filename = dir_name + '2014' + str(mm).zfill(2) + str(dd).zfill(2) 109 | np.savez(filename,time = time_all,pressure=pressure_all,rain_rate = rain_rate_all,RH_76 = RH_76_all,temp_76 = temp_76_all,temp_10=temp_10_all,wd_76_1 = wd_76_1_all,\ 110 | wd_76_2 = wd_76_2_all,wd_43 = wd_43_all,ws_78 = ws_78_all,ws_78_std_dev = ws_78_std_dev_all,ws_80 = ws_80_all,ws_80_std_dev = ws_80_std_dev_all,\ 111 | ws_74 = ws_74_all,ws_74_std_dev = ws_74_std_dev_all,ws_38 = ws_38_all,ws_38_std_dev = ws_38_std_dev_all) 112 | time_all = [] 113 | pressure_all = [] 114 | rain_rate_all = [] 115 | RH_76_all = [] 116 | temp_76_all = [] 117 | temp_10_all = [] 118 | wd_76_1_all = [] 119 | wd_76_2_all = [] 120 | wd_43_all = [] 121 | ws_78_all = [] 122 | ws_78_std_dev_all = [] 123 | ws_80_all = [] 124 | ws_80_std_dev_all = [] 125 | ws_74_all = [] 126 | ws_74_std_dev_all = [] 127 | ws_38_all = [] 128 | ws_38_std_dev_all = [] 129 | 130 | 131 | -------------------------------------------------------------------------------- /windrose.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | __version__ = '1.4' 5 | __author__ = 'Lionel Roubeyrie' 6 | __mail__ = 'lionel.roubeyrie@gmail.com' 7 | __license__ = 'CeCILL-B' 8 | 9 | import matplotlib 10 | import matplotlib.cm as cm 11 | import numpy as np 12 | from matplotlib.patches import Rectangle, Polygon 13 | from matplotlib.ticker import ScalarFormatter, AutoLocator 14 | from matplotlib.text import Text, FontProperties 15 | from matplotlib.projections.polar import PolarAxes 16 | from numpy.lib.twodim_base import histogram2d 17 | import matplotlib.pyplot as plt 18 | from pylab import poly_between 19 | 20 | RESOLUTION = 100 21 | ZBASE = -1000 #The starting zorder for all drawing, negative to have the grid on 22 | 23 | class WindroseAxes(PolarAxes): 24 | """ 25 | 26 | Create a windrose axes 27 | 28 | """ 29 | 30 | def __init__(self, *args, **kwargs): 31 | """ 32 | See Axes base class for args and kwargs documentation 33 | """ 34 | 35 | #Uncomment to have the possibility to change the resolution directly 36 | #when the instance is created 37 | #self.RESOLUTION = kwargs.pop('resolution', 100) 38 | PolarAxes.__init__(self, *args, **kwargs) 39 | self.set_aspect('equal', adjustable='box', anchor='C') 40 | self.radii_angle = 67.5 41 | self.cla() 42 | 43 | 44 | def cla(self): 45 | """ 46 | Clear the current axes 47 | """ 48 | PolarAxes.cla(self) 49 | 50 | self.theta_angles = np.arange(0, 360, 45) 51 | self.theta_labels = ['E', 'N-E', 'N', 'N-W', 'W', 'S-W', 'S', 'S-E'] 52 | self.set_thetagrids(angles=self.theta_angles, labels=self.theta_labels) 53 | 54 | self._info = {'dir' : list(), 55 | 'bins' : list(), 56 | 'table' : list()} 57 | 58 | self.patches_list = list() 59 | 60 | 61 | def _colors(self, cmap, n): 62 | ''' 63 | Returns a list of n colors based on the colormap cmap 64 | 65 | ''' 66 | return [cmap(i) for i in np.linspace(0.0, 1.0, n)] 67 | 68 | 69 | def set_radii_angle(self, **kwargs): 70 | """ 71 | Set the radii labels angle 72 | """ 73 | 74 | null = kwargs.pop('labels', None) 75 | angle = kwargs.pop('angle', None) 76 | if angle is None: 77 | angle = self.radii_angle 78 | self.radii_angle = angle 79 | radii = np.linspace(0.1, self.get_rmax(), 6) 80 | radii_labels = [ "%.1f" %r for r in radii ] 81 | radii_labels[0] = "" #Removing label 0 82 | null = self.set_rgrids(radii=radii, labels=radii_labels, 83 | angle=self.radii_angle, **kwargs) 84 | 85 | 86 | def _update(self): 87 | self.set_rmax(rmax=np.max(np.sum(self._info['table'], axis=0))) 88 | self.set_radii_angle(angle=self.radii_angle) 89 | 90 | 91 | def legend(self, loc='lower left', **kwargs): 92 | """ 93 | Sets the legend location and her properties. 94 | The location codes are 95 | 96 | 'best' : 0, 97 | 'upper right' : 1, 98 | 'upper left' : 2, 99 | 'lower left' : 3, 100 | 'lower right' : 4, 101 | 'right' : 5, 102 | 'center left' : 6, 103 | 'center right' : 7, 104 | 'lower center' : 8, 105 | 'upper center' : 9, 106 | 'center' : 10, 107 | 108 | If none of these are suitable, loc can be a 2-tuple giving x,y 109 | in axes coords, ie, 110 | 111 | loc = (0, 1) is left top 112 | loc = (0.5, 0.5) is center, center 113 | 114 | and so on. The following kwargs are supported: 115 | 116 | isaxes=True # whether this is an axes legend 117 | prop = FontProperties(size='smaller') # the font property 118 | pad = 0.2 # the fractional whitespace inside the legend border 119 | shadow # if True, draw a shadow behind legend 120 | labelsep = 0.005 # the vertical space between the legend entries 121 | handlelen = 0.05 # the length of the legend lines 122 | handletextsep = 0.02 # the space between the legend line and legend text 123 | axespad = 0.02 # the border between the axes and legend edge 124 | """ 125 | 126 | def get_handles(): 127 | handles = list() 128 | for p in self.patches_list: 129 | if isinstance(p, matplotlib.patches.Polygon) or \ 130 | isinstance(p, matplotlib.patches.Rectangle): 131 | color = p.get_facecolor() 132 | elif isinstance(p, matplotlib.lines.Line2D): 133 | color = p.get_color() 134 | else: 135 | raise AttributeError("Can't handle patches") 136 | handles.append(Rectangle((0, 0), 0.2, 0.2, 137 | facecolor=color, edgecolor='black')) 138 | return handles 139 | 140 | def get_labels(): 141 | labels = np.copy(self._info['bins']) 142 | labels = ["[%.1f : %0.1f[" %(labels[i], labels[i+1]) \ 143 | for i in range(len(labels)-1)] 144 | return labels 145 | 146 | null = kwargs.pop('labels', None) 147 | null = kwargs.pop('handles', None) 148 | handles = get_handles() 149 | labels = get_labels() 150 | self.legend_ = matplotlib.legend.Legend(self, handles, labels, 151 | loc, **kwargs) 152 | return self.legend_ 153 | 154 | 155 | def _init_plot(self, dir, var, **kwargs): 156 | """ 157 | Internal method used by all plotting commands 158 | """ 159 | #self.cla() 160 | null = kwargs.pop('zorder', None) 161 | 162 | #Init of the bins array if not set 163 | bins = kwargs.pop('bins', None) 164 | if bins is None: 165 | bins = np.linspace(np.min(var), np.max(var), 6) 166 | if isinstance(bins, int): 167 | bins = np.linspace(np.min(var), np.max(var), bins) 168 | bins = np.asarray(bins) 169 | nbins = len(bins) 170 | 171 | #Number of sectors 172 | nsector = kwargs.pop('nsector', None) 173 | if nsector is None: 174 | nsector = 16 175 | 176 | #Sets the colors table based on the colormap or the "colors" argument 177 | colors = kwargs.pop('colors', None) 178 | cmap = kwargs.pop('cmap', None) 179 | if colors is not None: 180 | if isinstance(colors, str): 181 | colors = [colors]*nbins 182 | if isinstance(colors, (tuple, list)): 183 | if len(colors) != nbins: 184 | raise ValueError("colors and bins must have same length") 185 | else: 186 | if cmap is None: 187 | cmap = cm.jet 188 | colors = self._colors(cmap, nbins) 189 | 190 | #Building the angles list 191 | angles = np.arange(0, -2*np.pi, -2*np.pi/nsector) + np.pi/2 192 | 193 | normed = kwargs.pop('normed', False) 194 | blowto = kwargs.pop('blowto', False) 195 | 196 | #Set the global information dictionnary 197 | self._info['dir'], self._info['bins'], self._info['table'] = histogram(dir, var, bins, nsector, normed, blowto) 198 | 199 | return bins, nbins, nsector, colors, angles, kwargs 200 | 201 | 202 | def contour(self, dir, var, **kwargs): 203 | """ 204 | Plot a windrose in linear mode. For each var bins, a line will be 205 | draw on the axes, a segment between each sector (center to center). 206 | Each line can be formated (color, width, ...) like with standard plot 207 | pylab command. 208 | 209 | Mandatory: 210 | * dir : 1D array - directions the wind blows from, North centred 211 | * var : 1D array - values of the variable to compute. Typically the wind 212 | speeds 213 | Optional: 214 | * nsector: integer - number of sectors used to compute the windrose 215 | table. If not set, nsectors=16, then each sector will be 360/16=22.5°, 216 | and the resulting computed table will be aligned with the cardinals 217 | points. 218 | * bins : 1D array or integer- number of bins, or a sequence of 219 | bins variable. If not set, bins=6, then 220 | bins=linspace(min(var), max(var), 6) 221 | * blowto : bool. If True, the windrose will be pi rotated, 222 | to show where the wind blow to (usefull for pollutant rose). 223 | * colors : string or tuple - one string color ('k' or 'black'), in this 224 | case all bins will be plotted in this color; a tuple of matplotlib 225 | color args (string, float, rgb, etc), different levels will be plotted 226 | in different colors in the order specified. 227 | * cmap : a cm Colormap instance from matplotlib.cm. 228 | - if cmap == None and colors == None, a default Colormap is used. 229 | 230 | others kwargs : see help(pylab.plot) 231 | 232 | """ 233 | 234 | bins, nbins, nsector, colors, angles, kwargs = self._init_plot(dir, var, 235 | **kwargs) 236 | 237 | #closing lines 238 | angles = np.hstack((angles, angles[-1]-2*np.pi/nsector)) 239 | vals = np.hstack((self._info['table'], 240 | np.reshape(self._info['table'][:,0], 241 | (self._info['table'].shape[0], 1)))) 242 | 243 | offset = 0 244 | for i in range(nbins): 245 | val = vals[i,:] + offset 246 | offset += vals[i, :] 247 | zorder = ZBASE + nbins - i 248 | patch = self.plot(angles, val, color=colors[i], zorder=zorder, 249 | **kwargs) 250 | self.patches_list.extend(patch) 251 | self._update() 252 | 253 | 254 | def contourf(self, dir, var, **kwargs): 255 | """ 256 | Plot a windrose in filled mode. For each var bins, a line will be 257 | draw on the axes, a segment between each sector (center to center). 258 | Each line can be formated (color, width, ...) like with standard plot 259 | pylab command. 260 | 261 | Mandatory: 262 | * dir : 1D array - directions the wind blows from, North centred 263 | * var : 1D array - values of the variable to compute. Typically the wind 264 | speeds 265 | Optional: 266 | * nsector: integer - number of sectors used to compute the windrose 267 | table. If not set, nsectors=16, then each sector will be 360/16=22.5°, 268 | and the resulting computed table will be aligned with the cardinals 269 | points. 270 | * bins : 1D array or integer- number of bins, or a sequence of 271 | bins variable. If not set, bins=6, then 272 | bins=linspace(min(var), max(var), 6) 273 | * blowto : bool. If True, the windrose will be pi rotated, 274 | to show where the wind blow to (usefull for pollutant rose). 275 | * colors : string or tuple - one string color ('k' or 'black'), in this 276 | case all bins will be plotted in this color; a tuple of matplotlib 277 | color args (string, float, rgb, etc), different levels will be plotted 278 | in different colors in the order specified. 279 | * cmap : a cm Colormap instance from matplotlib.cm. 280 | - if cmap == None and colors == None, a default Colormap is used. 281 | 282 | others kwargs : see help(pylab.plot) 283 | 284 | """ 285 | 286 | bins, nbins, nsector, colors, angles, kwargs = self._init_plot(dir, var, 287 | **kwargs) 288 | null = kwargs.pop('facecolor', None) 289 | null = kwargs.pop('edgecolor', None) 290 | 291 | #closing lines 292 | angles = np.hstack((angles, angles[-1]-2*np.pi/nsector)) 293 | vals = np.hstack((self._info['table'], 294 | np.reshape(self._info['table'][:,0], 295 | (self._info['table'].shape[0], 1)))) 296 | offset = 0 297 | for i in range(nbins): 298 | val = vals[i,:] + offset 299 | offset += vals[i, :] 300 | zorder = ZBASE + nbins - i 301 | xs, ys = poly_between(angles, 0, val) 302 | patch = self.fill(xs, ys, facecolor=colors[i], 303 | edgecolor=colors[i], zorder=zorder, **kwargs) 304 | self.patches_list.extend(patch) 305 | 306 | 307 | def bar(self, dir, var, **kwargs): 308 | """ 309 | Plot a windrose in bar mode. For each var bins and for each sector, 310 | a colored bar will be draw on the axes. 311 | 312 | Mandatory: 313 | * dir : 1D array - directions the wind blows from, North centred 314 | * var : 1D array - values of the variable to compute. Typically the wind 315 | speeds 316 | Optional: 317 | * nsector: integer - number of sectors used to compute the windrose 318 | table. If not set, nsectors=16, then each sector will be 360/16=22.5°, 319 | and the resulting computed table will be aligned with the cardinals 320 | points. 321 | * bins : 1D array or integer- number of bins, or a sequence of 322 | bins variable. If not set, bins=6 between min(var) and max(var). 323 | * blowto : bool. If True, the windrose will be pi rotated, 324 | to show where the wind blow to (usefull for pollutant rose). 325 | * colors : string or tuple - one string color ('k' or 'black'), in this 326 | case all bins will be plotted in this color; a tuple of matplotlib 327 | color args (string, float, rgb, etc), different levels will be plotted 328 | in different colors in the order specified. 329 | * cmap : a cm Colormap instance from matplotlib.cm. 330 | - if cmap == None and colors == None, a default Colormap is used. 331 | edgecolor : string - The string color each edge bar will be plotted. 332 | Default : no edgecolor 333 | * opening : float - between 0.0 and 1.0, to control the space between 334 | each sector (1.0 for no space) 335 | 336 | """ 337 | 338 | bins, nbins, nsector, colors, angles, kwargs = self._init_plot(dir, var, 339 | **kwargs) 340 | null = kwargs.pop('facecolor', None) 341 | edgecolor = kwargs.pop('edgecolor', None) 342 | if edgecolor is not None: 343 | if not isinstance(edgecolor, str): 344 | raise ValueError('edgecolor must be a string color') 345 | opening = kwargs.pop('opening', None) 346 | if opening is None: 347 | opening = 0.8 348 | dtheta = 2*np.pi/nsector 349 | opening = dtheta*opening 350 | 351 | for j in range(nsector): 352 | offset = 0 353 | for i in range(nbins): 354 | if i > 0: 355 | offset += self._info['table'][i-1, j] 356 | val = self._info['table'][i, j] 357 | zorder = ZBASE + nbins - i 358 | patch = Rectangle((angles[j]-opening/2, offset), opening, val, 359 | facecolor=colors[i], edgecolor=edgecolor, zorder=zorder, 360 | **kwargs) 361 | self.add_patch(patch) 362 | if j == 0: 363 | self.patches_list.append(patch) 364 | self._update() 365 | 366 | 367 | def box(self, dir, var, **kwargs): 368 | """ 369 | Plot a windrose in proportional bar mode. For each var bins and for each 370 | sector, a colored bar will be draw on the axes. 371 | 372 | Mandatory: 373 | * dir : 1D array - directions the wind blows from, North centred 374 | * var : 1D array - values of the variable to compute. Typically the wind 375 | speeds 376 | Optional: 377 | * nsector: integer - number of sectors used to compute the windrose 378 | table. If not set, nsectors=16, then each sector will be 360/16=22.5°, 379 | and the resulting computed table will be aligned with the cardinals 380 | points. 381 | * bins : 1D array or integer- number of bins, or a sequence of 382 | bins variable. If not set, bins=6 between min(var) and max(var). 383 | * blowto : bool. If True, the windrose will be pi rotated, 384 | to show where the wind blow to (usefull for pollutant rose). 385 | * colors : string or tuple - one string color ('k' or 'black'), in this 386 | case all bins will be plotted in this color; a tuple of matplotlib 387 | color args (string, float, rgb, etc), different levels will be plotted 388 | in different colors in the order specified. 389 | * cmap : a cm Colormap instance from matplotlib.cm. 390 | - if cmap == None and colors == None, a default Colormap is used. 391 | edgecolor : string - The string color each edge bar will be plotted. 392 | Default : no edgecolor 393 | 394 | """ 395 | 396 | bins, nbins, nsector, colors, angles, kwargs = self._init_plot(dir, var, 397 | **kwargs) 398 | null = kwargs.pop('facecolor', None) 399 | edgecolor = kwargs.pop('edgecolor', None) 400 | if edgecolor is not None: 401 | if not isinstance(edgecolor, str): 402 | raise ValueError('edgecolor must be a string color') 403 | opening = np.linspace(0.0, np.pi/16, nbins) 404 | 405 | for j in range(nsector): 406 | offset = 0 407 | for i in range(nbins): 408 | if i > 0: 409 | offset += self._info['table'][i-1, j] 410 | val = self._info['table'][i, j] 411 | zorder = ZBASE + nbins - i 412 | patch = Rectangle((angles[j]-opening[i]/2, offset), opening[i], 413 | val, facecolor=colors[i], edgecolor=edgecolor, 414 | zorder=zorder, **kwargs) 415 | self.add_patch(patch) 416 | if j == 0: 417 | self.patches_list.append(patch) 418 | self._update() 419 | 420 | def histogram(dir, var, bins, nsector, normed=False, blowto=False): 421 | """ 422 | Returns an array where, for each sector of wind 423 | (centred on the north), we have the number of time the wind comes with a 424 | particular var (speed, polluant concentration, ...). 425 | * dir : 1D array - directions the wind blows from, North centred 426 | * var : 1D array - values of the variable to compute. Typically the wind 427 | speeds 428 | * bins : list - list of var category against we're going to compute the table 429 | * nsector : integer - number of sectors 430 | * normed : boolean - The resulting table is normed in percent or not. 431 | * blowto : boolean - Normaly a windrose is computed with directions 432 | as wind blows from. If true, the table will be reversed (usefull for 433 | pollutantrose) 434 | 435 | """ 436 | 437 | if len(var) != len(dir): 438 | raise ValueError, "var and dir must have same length" 439 | 440 | angle = 360./nsector 441 | 442 | dir_bins = np.arange(-angle/2 ,360.+angle, angle, dtype=np.float) 443 | dir_edges = dir_bins.tolist() 444 | dir_edges.pop(-1) 445 | dir_edges[0] = dir_edges.pop(-1) 446 | dir_bins[0] = 0. 447 | 448 | var_bins = bins.tolist() 449 | var_bins.append(np.inf) 450 | 451 | if blowto: 452 | dir = dir + 180. 453 | dir[dir>=360.] = dir[dir>=360.] - 360 454 | 455 | table = histogram2d(x=var, y=dir, bins=[var_bins, dir_bins], 456 | normed=False)[0] 457 | # add the last value to the first to have the table of North winds 458 | table[:,0] = table[:,0] + table[:,-1] 459 | # and remove the last col 460 | table = table[:, :-1] 461 | if normed: 462 | table = table*100/table.sum() 463 | 464 | return dir_edges, var_bins, table 465 | 466 | 467 | def wrcontour(dir, var, **kwargs): 468 | fig = plt.figure() 469 | rect = [0.1, 0.1, 0.8, 0.8] 470 | ax = WindroseAxes(fig, rect) 471 | fig.add_axes(ax) 472 | ax.contour(dir, var, **kwargs) 473 | l = ax.legend(axespad=-0.10) 474 | plt.setp(l.get_texts(), fontsize=8) 475 | plt.draw() 476 | plt.show() 477 | return ax 478 | 479 | def wrcontourf(dir, var, **kwargs): 480 | fig = plt.figure() 481 | rect = [0.1, 0.1, 0.8, 0.8] 482 | ax = WindroseAxes(fig, rect) 483 | fig.add_axes(ax) 484 | ax.contourf(dir, var, **kwargs) 485 | l = ax.legend(axespad=-0.10) 486 | plt.setp(l.get_texts(), fontsize=8) 487 | plt.draw() 488 | plt.show() 489 | return ax 490 | 491 | def wrbox(dir, var, **kwargs): 492 | fig = plt.figure() 493 | rect = [0.1, 0.1, 0.8, 0.8] 494 | ax = WindroseAxes(fig, rect) 495 | fig.add_axes(ax) 496 | ax.box(dir, var, **kwargs) 497 | l = ax.legend(axespad=-0.10) 498 | plt.setp(l.get_texts(), fontsize=8) 499 | plt.draw() 500 | plt.show() 501 | return ax 502 | 503 | def wrbar(dir, var, **kwargs): 504 | fig = plt.figure() 505 | rect = [0.1, 0.1, 0.8, 0.8] 506 | ax = WindroseAxes(fig, rect) 507 | fig.add_axes(ax) 508 | ax.bar(dir, var, **kwargs) 509 | l = ax.legend(axespad=-0.10) 510 | plt.setp(l.get_texts(), fontsize=8) 511 | plt.draw() 512 | plt.show() 513 | return ax 514 | 515 | def clean(dir, var): 516 | ''' 517 | Remove masked values in the two arrays, where if a direction data is masked, 518 | the var data will also be removed in the cleaning process (and vice-versa) 519 | ''' 520 | dirmask = dir.mask==False 521 | varmask = var.mask==False 522 | ind = dirmask*varmask 523 | return dir[ind], var[ind] 524 | 525 | if __name__=='__main__': 526 | from pylab import figure, show, setp, random, grid, draw 527 | vv=random(500)*6 528 | dv=random(500)*360 529 | fig = figure(figsize=(8, 8), dpi=80, facecolor='w', edgecolor='w') 530 | rect = [0.1, 0.1, 0.8, 0.8] 531 | ax = WindroseAxes(fig, rect, axisbg='w') 532 | fig.add_axes(ax) 533 | 534 | # ax.contourf(dv, vv, bins=np.arange(0,8,1), cmap=cm.hot) 535 | # ax.contour(dv, vv, bins=np.arange(0,8,1), colors='k') 536 | # ax.bar(dv, vv, normed=True, opening=0.8, edgecolor='white') 537 | ax.box(dv, vv, normed=True) 538 | l = ax.legend(axespad=-0.10) 539 | setp(l.get_texts(), fontsize=8) 540 | draw() 541 | #print ax._info 542 | show() 543 | 544 | 545 | 546 | 547 | 548 | 549 | -------------------------------------------------------------------------------- /wind_resource_graphs.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*-b 2 | """ 3 | Spyder Editor 4 | 5 | Read in met tower and SCADA data. 6 | """ 7 | 8 | import numpy as np 9 | import datetime as datetime 10 | import matplotlib.pyplot as plt 11 | import windeval as we 12 | from sklearn.cross_validation import train_test_split 13 | from machine_learning_example import machine_learning_RF 14 | from power_curve_query_func import power_curve_query as pc 15 | from matplotlib import cm 16 | 17 | """ 18 | BEGIN USER INPUT 19 | """ 20 | 21 | file_dir = 'C:\\Users\\epenn\\Documents\\Sample Data\\Sample Data\\' 22 | 23 | #Read in all SCADA data 24 | SCADA_dir = 'C:\\Users\\epenn\\Documents\\wind_site_data\\SCADA\\' 25 | met_south_dir = 'C:\\Users\\epenn\\Documents\\wind_site_data\\met_south\\' 26 | met_north_dir = 'C:\\Users\\epenn\\Documents\\wind_site_data\\met_north\\' 27 | 28 | max_power = 1000 #maximum power produced in the entire plant 29 | nturbines = 10 #number of turbines in your plant 30 | 31 | SCADA_keys = ['operating_state', 'nacelle_ws', 'pitch_southngle', 'power', 'nacelle_ws_std', 'rotor_speed', 'nacelle_position', 'time', 'state_fault', 'turbine_name'] 32 | met_keys = ['ws_1_std_dev', 'ws_1', 'temp_76', 'ws_2', 'rain_rate', 'ws_2_std_dev', 'wd_2', 'pressure', 'wd_3', 'wd_1', 'time', 'ws_4', 'temp_1', 'RH_1', 'ws_4_std_dev', 'ws_3', 'ws_3_std_dev'] 33 | turbine_names = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'] #Input the names of your turbines in your documents. 34 | 35 | 36 | bins = np.array([3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0, 6.5, 7.0, 7.5, 8.0, 8.5, 9.0, 9.5, 10.0, 10.5, 11.0, 11.5, 12.0, 12.5, 13.0, 13.5]) 37 | power_curve = np.array([0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11., 12., 13., 14., 15., 16., 17., 18., 19., 20., 21.]) 38 | 39 | #start_time = datetime.datetime(2013,11,7) 40 | #end_time = datetime.datetime(2014,1,31) 41 | 42 | #start_time = datetime.datetime(2014,5,1) 43 | #end_time = datetime.datetime(2014,6,30) 44 | 45 | start_time = datetime.datetime(2013,11,7) 46 | end_time = datetime.datetime(2014,6,30) 47 | 48 | fix_time_series = False 49 | 50 | """ 51 | END USER INPUT 52 | """ 53 | 54 | 55 | 56 | """ 57 | THIS PART READS IN ALL FILES 58 | """ 59 | 60 | #THIS PART IS WHERE I FIX THE TIME SERIES AND CREATE NEW DATASETS FROM THE ORIGINALS. 61 | if fix_time_series: 62 | 63 | #SCADA 64 | time_range = [] 65 | 66 | time_step = datetime.timedelta(days = 1) 67 | entry = start_time 68 | while entry <= end_time: 69 | time_range.append(entry) 70 | entry += time_step 71 | time_range = np.array(time_range) 72 | 73 | for t in time_range: 74 | file_name = we.read_in(SCADA_dir,t,t,SCADA_keys) 75 | we.fix_time("SCADA",t,file_name,10, turbine_names = turbine_names) 76 | 77 | #south met tower 78 | time_range = [] 79 | 80 | time_step = datetime.timedelta(days = 1) 81 | entry = start_time 82 | while entry <= end_time: 83 | time_range.append(entry) 84 | entry += time_step 85 | time_range = np.array(time_range) 86 | 87 | for t in time_range: 88 | file_name = we.read_in(met_south_dir,t,t,met_keys) 89 | we.fix_time("met_south",t,file_name,10, turbine_names = turbine_names) 90 | 91 | #north met tower 92 | time_range = [] 93 | 94 | time_step = datetime.timedelta(days = 1) 95 | entry = start_time 96 | while entry <= end_time: 97 | time_range.append(entry) 98 | entry += time_step 99 | time_range = np.array(time_range) 100 | 101 | for t in time_range: 102 | file_name = we.read_in(met_north_dir,t,t,met_keys) 103 | we.fix_time("met_north",t,file_name,10, turbine_names = turbine_names) 104 | 105 | 106 | fixed_SCADA_file = "C:\\Users\\epenn\\Documents\\FormattedData\\SCADA\\" 107 | fixed_south_file = 'C:\\Users\\epenn\\Documents\\FormattedData\\met_south\\' 108 | fixed_north_file = 'C:\\Users\\epenn\\Documents\\FormattedData\\met_north\\' 109 | winter_file = 'C:\\Users\\epenn\\Documents\\FormattedData\\winter.npz' 110 | summer_file = 'C:\\Users\\epenn\\Documents\\FormattedData\\summer.npz' 111 | 112 | print("SCADA") 113 | SCADA = we.read_in(fixed_SCADA_file,start_time,end_time,SCADA_keys) 114 | 115 | winter_total = np.load(winter_file) 116 | summer_total = np.load(summer_file) 117 | 118 | 119 | print("met_south") 120 | met_south = we.read_in(fixed_south_file,start_time,end_time,met_keys) 121 | print("met_north") 122 | met_north = we.read_in(fixed_north_file,start_time,end_time,met_keys) 123 | #we.save_total_power(SCADA['power'],SCADA['time'],SCADA['state_fault'],'summer') 124 | 125 | 126 | 127 | """ 128 | This part cleans the data. 129 | """ 130 | print('Clean') 131 | 132 | filtered_total = np.zeros(len(winter_total['total_power'])) 133 | filtered_total[:] = np.nan 134 | filtered_total[winter_total['total_power'] >= 0.98] = winter_total['total_power'][winter_total['total_power'] >= 0.98] 135 | 136 | clean_south_temp = we.filter_temp(met_south['temp_76']) 137 | temp_south_K = clean_south_temp + 273.15 138 | pressure_south_Pa = met_south['pressure'] * 100.0 139 | clean_south_wd = we.filter_icing(met_south['wd_2'],clean_south_temp,met_south['RH_1'],met_south['rain_rate']) 140 | #clean_south_ws = we.filter_obstacles(we.filter_obstacles(we.filter_icing(we.filter_streaking(met_south['ws_4']), clean_south_temp, met_south['RH_1'], met_south['rain_rate']),clean_south_wd,285.0,74.0),clean_south_wd,40.0,74.0) 141 | deiced_south_ws = we.correct_density(we.filter_icing(we.filter_streaking(met_south['ws_4']), clean_south_temp, met_south['RH_1'], met_south['rain_rate']),pressure_south_Pa,temp_south_K,met_south['RH_1']/100.0) 142 | deiced_south_ws_1 = we.correct_density(we.filter_icing(we.filter_streaking(met_south['ws_1']), clean_south_temp, met_south['RH_1'], met_south['rain_rate']),pressure_south_Pa,temp_south_K,met_south['RH_1']/100.0) 143 | deiced_south_std_dev = we.filter_icing(we.filter_streaking(met_south['ws_4_std_dev']), clean_south_temp, met_south['RH_1'], met_south['rain_rate']) 144 | 145 | clean_north_temp = we.filter_temp(met_south['temp_76']) 146 | temp_north_K = clean_north_temp + 273.15 147 | pressure_north_Pa = met_north['pressure'] * 100.0 148 | clean_north_wd = we.filter_icing(met_north['wd_2'],clean_south_temp,met_south['RH_1'],met_south['rain_rate']) 149 | clean_north_ws = we.correct_density(we.filter_obstacles(we.filter_obstacles(we.filter_icing(we.filter_streaking(met_north['ws_4']), clean_north_temp, met_north['RH_1'], met_south['rain_rate']),clean_north_wd,164.0,60.0),clean_north_wd, 241.0,70.0),pressure_north_Pa,temp_north_K,met_north['RH_1']/100.0) 150 | clean_north_ws_1 = we.correct_density(we.filter_obstacles(we.filter_obstacles(we.filter_icing(we.filter_streaking(met_north['ws_1']), clean_north_temp, met_north['RH_1'], met_south['rain_rate']),clean_north_wd,164.0,60.0),clean_north_wd, 241.0,70.0),pressure_north_Pa,temp_north_K,met_north['RH_1']/100.0) 151 | clean_north_std_dev = we.filter_obstacles(we.filter_obstacles(we.filter_icing(we.filter_streaking(met_north['ws_4_std_dev']), clean_north_temp, met_north['RH_1'], met_north['rain_rate']),clean_north_wd,164.0,60.0),clean_north_wd,164.0,60.0) 152 | 153 | #Replace waked values from met tower south with waked values from met tower north. 154 | met_ws = we.sub_data(we.sub_data(deiced_south_ws, clean_north_ws, clean_south_wd, 285.0, 74.0), clean_north_ws, clean_south_wd, 40.0, 74.0) 155 | met_std_dev = we.sub_data(we.sub_data(deiced_south_std_dev, clean_north_std_dev, clean_south_wd, 285.0, 74.0), clean_north_std_dev, clean_south_wd, 40.0, 74.0) 156 | 157 | Ti = np.array(met_std_dev) / np.array(met_ws) * 100.0 158 | 159 | #Calculate shear 160 | all_clean_ws = np.zeros(2) 161 | all_clean_ws_1 = we.sub_data(we.sub_data(deiced_south_ws_1, clean_north_ws_1, clean_south_wd, 285.0, 74.0), clean_north_ws_1, clean_south_wd, 40.0, 74.0) 162 | all_clean_ws_1[all_clean_ws_1 <= 1.0] = np.nan #Filter values below the threshold of the instrument. 163 | all_clean_ws_4 = met_ws 164 | wind_shear = we.calc_wind_shear(met_south['time'], np.array([38.0, 80.0]), [all_clean_ws_1,all_clean_ws_4]) 165 | 166 | 167 | 168 | """ 169 | Uncomment these to access different features. 170 | """ 171 | 172 | #Machine Learning 173 | """ 174 | inputs_orig = np.transpose([met_ws,wind_shear,Ti,clean_south_wd]) 175 | targets_orig = np.transpose(filtered_total) 176 | 177 | indices = np.arange(np.array(targets_orig).shape[0]) 178 | #Splits into training set and testing set 179 | x_train, x_test, y_train, y_test,idx_train,idx_test = train_test_split(inputs_orig,targets_orig,indices,test_size=0.25,random_state= 42) 180 | 181 | y_train = y_train.ravel() 182 | y_test = y_test.ravel() 183 | 184 | y_test2, power_pred = machine_learning_RF(x_train,y_train,x_test,y_test) 185 | 186 | plt.figure(num=15) 187 | plt.scatter(y_test2 / max_power / nturbines, power_pred / max_power / nturbines, s = 4) 188 | regline_ml = we.linear_regression_intercept(y_test2 / max_power / nturbines, power_pred / max_power / nturbines) 189 | plt.plot(y_test2 / max_power / nturbines, y_test2 / max_power / nturbines, color ='green', label = 'x=y') 190 | plt.plot(y_test2 / max_power / nturbines, y_test2 / max_power / nturbines * regline_ml[0] + regline_ml[1], color ='red', label = 'line of regression') 191 | plt.xlim(0.0,1.05) 192 | plt.ylim(0.0,1.05) 193 | plt.title("Machine Learning vs. Actual Values") 194 | axes = plt.gca() 195 | xmin, xmax = axes.get_xlim() 196 | ymin, ymax = axes.get_ylim() 197 | plt.text(xmax-0.35*(xmax-xmin), ymin+0.05*(ymax-ymin), \ 198 | "Filtered for icing and wind direction. \nm = %s \nc = %s \nr^2 = %s" % \ 199 | ("{0:.2f}".format(regline_ml[0]), "{0:.2f}".format(regline_ml[1]), "{0:.2f}".format(regline_ml[2])), fontsize = 8) 200 | plt.legend(loc=0) 201 | 202 | #Predict using Manufacturer's Power Curve 203 | mpc_pred = pc(x_test[:,0],x_test[:,2],'normal_TI') 204 | plt.figure(num=16) 205 | plt.scatter(y_test / max_power / nturbines, mpc_pred / max_power, s = 4) 206 | regline_mpc = we.linear_regression_intercept(y_test / max_power / nturbines, mpc_pred / max_power) 207 | plt.plot(y_test / max_power / nturbines, y_test / max_power / nturbines, color ='green', label = 'x=y') 208 | plt.plot(y_test / max_power / nturbines, y_test / max_power / nturbines * regline_mpc[0] + regline_mpc[1], color ='red', label = 'line of regression') 209 | plt.xlim(0.0,1.05) 210 | plt.ylim(0.0,1.05) 211 | plt.title("Manufacturer's Power Curve vs. Actual Values") 212 | axes = plt.gca() 213 | xmin, xmax = axes.get_xlim() 214 | ymin, ymax = axes.get_ylim() 215 | plt.text(xmax-0.35*(xmax-xmin), ymin+0.05*(ymax-ymin), \ 216 | "Filtered for icing and wind direction. \nm = %s \nc = %s \nr^2 = %s" % \ 217 | ("{0:.2f}".format(regline_mpc[0]), "{0:.2f}".format(regline_mpc[1]), "{0:.2f}".format(regline_mpc[2])), fontsize = 8) 218 | plt.legend(loc=2) 219 | """ 220 | 221 | 222 | #Create a power curve. 223 | """ 224 | print('plot') 225 | plt.figure(num=42) 226 | mask = [summer_total['percent_southctive'] > 0.98] 227 | clean_total = np.empty(len(summer_total['total_power'])) 228 | clean_total[:] = np.nan 229 | clean_total[mask] = summer_total['total_power'][mask] / 1000.0 / 235.2 # Normalized 230 | plt.scatter(met_ws, clean_total) 231 | 232 | plt.xlim([0.0,20]) 233 | plt.ylim([0.0,1.05]) 234 | plt.title('Summer Power Curve') 235 | plt.ylabel('Total Plant Production (normalized)') 236 | plt.xlabel('Wind Resource (m/s)') 237 | """ 238 | 239 | 240 | #Color-code a power curve. 241 | #Wind direction 242 | #Day/Night 243 | """ 244 | current_power = winter_total 245 | print('plot') 246 | mask = [current_power['percent_southctive'] > 0.98] 247 | clean_total = np.empty(len(current_power['total_power'])) 248 | clean_total[:] = np.nan 249 | clean_total[mask] = current_power['total_power'][mask] / 1000.0 / 235.2 # Normalized 250 | 251 | plt.figure(num=134) 252 | 253 | #South 254 | mask2 = [clean_south_wd <= 225, clean_south_wd >= 135] 255 | mask2 = reduce(np.logical_southnd,mask2) 256 | plt.scatter(met_ws[mask2], clean_total[mask2], color = 'green', alpha = 0.4, label = 'South') 257 | 258 | #East 259 | mask2 = [clean_south_wd <= 135, clean_south_wd >= 45] 260 | mask2 = reduce(np.logical_southnd,mask2) 261 | plt.scatter(met_ws[mask2], clean_total[mask2], color = 'blue', alpha = 0.4, label = 'East') 262 | 263 | #North 264 | mask2 = [clean_south_wd <= 45, clean_south_wd >= 315] 265 | mask2 = reduce(np.logical_or,mask2) 266 | plt.scatter(met_ws[mask2], clean_total[mask2], color = 'red', alpha = 0.4, label = 'North') 267 | 268 | #West 269 | mask2 = [clean_south_wd <= 315, clean_south_wd >= 225] 270 | mask2 = reduce(np.logical_southnd,mask2) 271 | plt.scatter(met_ws[mask2], clean_total[mask2], color = 'yellow', alpha = 0.4, label = 'West') 272 | 273 | # 274 | # del SCADA_power 275 | plt.xlim([0.0,20]) 276 | plt.ylim([0.0,1.05]) 277 | plt.title('Summer Power Curve') 278 | plt.ylabel('Total Plant Production (normalized)') 279 | plt.xlabel('Wind Resource (m/s)') 280 | plt.legend(loc=2) 281 | 282 | #Binned Turbulence 283 | shear_mask = [wind_shear>=0.2,wind_shear<=0.5] 284 | shear_mask = reduce(np.logical_southnd,shear_mask) 285 | tot = np.zeros(len(clean_total)) 286 | tot[:] = np.nan 287 | tot[shear_mask] = clean_total[shear_mask] 288 | 289 | ws = np.zeros(len(met_ws)) 290 | ws[:] = np.nan 291 | ws[shear_mask] = met_ws[shear_mask] 292 | 293 | plt.figure(num=23) 294 | mask = [Ti>=0., Ti<5.] 295 | mask = reduce(np.logical_southnd,mask) 296 | plt.scatter(ws[mask],tot[mask], c='blue', alpha=0.3) 297 | 298 | mask = [Ti>=5.,Ti<10.] 299 | mask = reduce(np.logical_southnd,mask) 300 | plt.scatter(ws[mask],tot[mask], c='green', alpha=0.3) 301 | 302 | mask = [Ti>=10.,Ti<15.] 303 | mask = reduce(np.logical_southnd,mask) 304 | plt.scatter(ws[mask],tot[mask], c='yellow', alpha=0.3) 305 | 306 | mask = [Ti>=15.] 307 | plt.scatter(ws[mask],tot[mask], c='red', alpha=0.3) 308 | 309 | #Turbulence 310 | #Make a plot with vertical colorbar 311 | fig, ax = plt.subplots() 312 | #mask_shear = [wind_shear <= 0.3, wind_shear>= 0.2] 313 | #mask_shear = reduce(np.logical_southnd,mask_shear) 314 | plot = ax.scatter(met_ws, clean_total, c=wind_shear, cmap=cm.hot_r, alpha = 0.3) 315 | cbar = fig.colorbar(plot, ax=ax, orientation='vertical') 316 | plot.autoscale() 317 | plot.set_clim(0.0,0.75) 318 | #plt.xlim([0.0,20]) 319 | plt.ylim([0.0,1.05]) 320 | cbar.set_label('Shear exponent') 321 | 322 | #Day vs Night 323 | plt.figure(num=92) 324 | hours = np.array([t.hour for t in met_south['time']]) 325 | 326 | #Night 327 | mask_time = [hours < 7, hours > 17] 328 | mask_time = reduce(np.logical_or,mask_time) 329 | plt.scatter(met_ws[mask_time],clean_total[mask_time],c='blue',alpha=0.3) 330 | 331 | #Day 332 | mask_time = [hours > 7, hours < 17] 333 | mask_time = reduce(np.logical_southnd,mask_time) 334 | plt.scatter(met_ws[mask_time],clean_total[mask_time],c='orange',alpha=0.3) 335 | """ 336 | 337 | 338 | #Create unfiltered power curve 339 | """ 340 | unfiltered_total = np.array([]) 341 | for time in np.unique(met_south['time']): 342 | unfiltered_total = np.append(unfiltered_total,np.sum(SCADA['power'][SCADA['time'] == time])) 343 | 344 | plt.figure(num=999) 345 | plt.scatter(met_south['ws_4'], unfiltered_total / max_power / nturbines, c = '#660520') 346 | 347 | plt.xlim([0.0,20]) 348 | plt.ylim([0.0,1.05]) 349 | plt.title('Unfiltered Winter Power Curve') 350 | plt.ylabel('Total Plant Production (normalized)') 351 | plt.xlabel('Wind Resource from south Met Tower (m/s)') 352 | """ 353 | 354 | 355 | #Plot a wind rose! 356 | """ 357 | bins = [0.,3.,6.,9.,12.,15.] 358 | we.plot_wind_rose(clean_south_wd,deiced_south_ws,bins) 359 | we.save_figure('wind_rose_winter') 360 | """ 361 | 362 | 363 | #Make a power rose! 364 | """ 365 | we.plot_power_rose(clean_south_wd,winter_total['total_power'] / max_power / nturbines * 100.0,16) 366 | we.save_figure('power_rose_winter') 367 | """ 368 | 369 | 370 | #Make a wind speed histogram! 371 | """ 372 | plt.figure(num=45) 373 | 374 | ws_northins = np.arange(0,20,0.5) 375 | mask = ~np.isnan(met_ws) 376 | 377 | plt.hist(met_ws[mask],normed=1,bins=ws_northins) 378 | plt.xlabel("wind speed (m/s)") 379 | plt.ylabel("frequency (%)") 380 | plt.title('Summer Wind Speed Frequency') 381 | """ 382 | 383 | 384 | #Plot hourly wind speed 385 | """ 386 | plt.figure(num=2) 387 | ws_time_38, ws_1 = we.hourly_wind_speed(all_clean_ws_1,met_south['time']) 388 | ws_time_80, ws_4 = we.hourly_wind_speed(all_clean_ws_4,met_south['time']) 389 | 390 | 391 | plt.plot(ws_time_80,ws_4,label='wind speed 80m') 392 | plt.plot(ws_time_38,ws_1,label='wind speed 38m') 393 | plt.title('Winter Wind Velocity over 24h') 394 | plt.xlim(0,23) 395 | plt.ylim(0,9.3) 396 | plt.xlabel('local time (hr)') 397 | plt.ylabel('wind speed (m/s)') 398 | plt.legend(loc=4) 399 | we.save_figure('hourly_wind_speed') 400 | """ 401 | 402 | 403 | #Test Sensitivity 404 | """ 405 | difference = np.empty(len(clean_south_ws)) 406 | difference[:] = np.nan 407 | 408 | for ws in bins: 409 | mask = [clean_south_ws > ws - 0.25, clean_south_ws < ws + 0.25] 410 | mask = reduce(np.logical_southnd, mask) 411 | 412 | difference[mask] = power_curve[bins == ws] * nturbines / 1000 - clean_total[mask] 413 | 414 | #x = Ti 415 | x = wind_shear_south 416 | y = difference 417 | stats = we.bin_mean_plot(np.nanmin(x),np.nanmax(x),0.05,x,y,'Wind Shear Exponent') 418 | plt.title("sensitivity: %s" % (stats[1])) 419 | axes = plt.gca() 420 | xmin, xmax = axes.get_xlim() 421 | ymin, ymax = axes.get_ylim() 422 | plt.text(xmax-0.35*(xmax-xmin), ymin+0.05*(ymax-ymin), "Filtered for icing and wind direction. \n>98% of turbines active.", fontsize = 8) 423 | """ 424 | 425 | 426 | #Create a resource map 427 | """ 428 | ll_filename = 'C:\\Users\\epenn\\Documents\\latlonturb.csv' 429 | 430 | ll_turbine_name = np.loadtxt(ll_filename, delimiter=',', usecols=(0,),dtype=str, unpack=True,skiprows=1) 431 | lat = np.loadtxt(ll_filename, delimiter=',', usecols=(1,),dtype=float, unpack=True,skiprows=1) 432 | lon = np.loadtxt(ll_filename, delimiter=',', usecols=(2,),dtype=float, unpack=True,skiprows=1) 433 | 434 | rotor_d = 82.5 #rotor diameter in m 435 | rated_power = 1000 #in kW 436 | 437 | lat_dist = 111.2*1000 #Distance betwen lines of longitude. This is constant with lon. In m. 438 | lon_dist = 89.50573266967802*1000 #Distance between lines of longitude at mean latitude. In m. 439 | 440 | #Normalized to units of rotor diameter. 441 | lat_norm = (lat - min(lat)) * lat_dist / rotor_d 442 | lon_norm = (lon - min(lon)) * lon_dist / rotor_d 443 | 444 | #Convert all np.nans into 0's for averaging... CF doesn't care WHY a turbine isn't producing power, just that it isn't. 445 | SCADA['power'][np.isnan(SCADA['power'])] = 0.0 446 | 447 | avg_cf = np.array([]) 448 | for turb in ll_turbine_name: 449 | avg_cf = np.append(avg_cf, (np.mean(SCADA['power'][SCADA['turbine_name'] == turb]) / max_power) * 100) 450 | 451 | plt.figure(num = 15) 452 | mask = [avg_cf <= 44.] 453 | plt.scatter(lon_norm[mask],lat_norm[mask], color = 'purple', label='CF <= 44%') 454 | 455 | mask = [avg_cf > 44., avg_cf <= 45.] 456 | mask = reduce(np.logical_southnd,mask) 457 | plt.scatter(lon_norm[mask],lat_norm[mask], color = 'blue', label='44% < CF <= 45%') 458 | 459 | mask = [avg_cf > 45., avg_cf <= 46.] 460 | mask = reduce(np.logical_southnd,mask) 461 | plt.scatter(lon_norm[mask],lat_norm[mask], color = 'green', label='45% < CF <= 46%') 462 | 463 | mask = [avg_cf > 46., avg_cf <= 47.] 464 | mask = reduce(np.logical_southnd,mask) 465 | plt.scatter(lon_norm[mask],lat_norm[mask], color = 'orange', label='46% < CF <= 47%') 466 | 467 | mask = [avg_cf > 47.5] 468 | plt.scatter(lon_norm[mask],lat_norm[mask], color = 'red', label='47.5% < CF') 469 | 470 | 471 | plt.gca().set_southspect('equal',adjustable='box') 472 | plt.title('Southern Plains Wind Farm - Summer') 473 | plt.legend(loc=3) 474 | """ 475 | 476 | 477 | #Create a (directional) resource map 478 | """ 479 | #directions = [0.0, 45.0, 90.0, 135.0, 180.0, 225.0, 270.0, 315.0] 480 | directions = [0.0, 90.0, 180.0, 270.0] 481 | ll_filename = 'C:\\Users\\epenn\\Documents\\latlonturb.csv' 482 | 483 | ll_turbine_name = np.loadtxt(ll_filename, delimiter=',', usecols=(0,),dtype=str, unpack=True,skiprows=1) 484 | lat = np.loadtxt(ll_filename, delimiter=',', usecols=(1,),dtype=float, unpack=True,skiprows=1) 485 | lon = np.loadtxt(ll_filename, delimiter=',', usecols=(2,),dtype=float, unpack=True,skiprows=1) 486 | elevation = np.loadtxt(ll_filename, delimiter=',', usecols=(3,),dtype=float, unpack=True,skiprows=1) 487 | 488 | for wd in directions: 489 | rotor_d = 82.5 #rotor diameter in m 490 | rated_power = 1000 #in kW 491 | 492 | lat_dist = 111.2*1000 #Distance between lines of longitude. This is constant with lon. In m. 493 | lon_dist = 89.50573266967802*1000 #Distance between lines of longitude at mean latitude. In m. 494 | 495 | #Normalized to units of rotor diameter. 496 | lat_norm = (lat - min(lat)) * lat_dist / rotor_d 497 | lon_norm = (lon - max(lon)) * lon_dist / rotor_d 498 | 499 | #Convert all np.nans into 0's for averaging... CF doesn't care WHY a turbine isn't producing power, just that it isn't. 500 | SCADA['power'][np.isnan(SCADA['power'])] = 0.0 501 | 502 | avg_cf = np.array([]) 503 | for turb in ll_turbine_name: 504 | #Filter all but the 90 degrees surrounding wd. 505 | filtered_power = we.filter_obstacles(SCADA['power'][SCADA['turbine_name'] == turb], clean_south_wd, (wd + 180.0) % 360, 360.0 - (360.0 / float(len(directions)))) 506 | avg_cf = np.append(avg_cf, (np.nanmean(filtered_power / max_power) * 100)) 507 | 508 | #Make a plot with vertical colorbar 509 | fig, ax = plt.subplots() 510 | X, Y, Z = we.grid(lon_norm, lat_norm, elevation) 511 | contour = plt.contourf(X,Y,Z, cmap = cm.Greys, alpha = 0.5) 512 | #plt.clabel(contour) 513 | plot = ax.scatter(lon_norm, lat_norm, c=avg_cf, cmap=cm.CMRmap_r, s = 64) 514 | cbar = fig.colorbar(plot, ax=ax, orientation='horizontal') 515 | plot.autoscale() 516 | plot.set_clim(20.0,50.0) 517 | cbar.set_label('Capacity Factor (%)') 518 | 519 | #plt.xlim([-10.0,310]) 520 | plt.gca().set_southspect('equal',adjustable='box') 521 | plt.title('Winter - %s Degrees' % (wd)) 522 | """ 523 | 524 | 525 | #Plot with OS filtered out 526 | """ 527 | clean_south_wd = we.filter_icing(met_south['wd_2'],clean_south_temp,met_south['RH_1'],met_south['rain_rate']) 528 | print('Plot2') 529 | plt.figure(num=2) 530 | total_power = np.array([]) 531 | for time in met_south['time']: 532 | #SCADA_fault = SCADA['state_fault'][SCADA['time'] == time] 533 | SCADA_fault = SCADA['state_fault'][SCADA['time'] == time] 534 | met_ws = clean_south_ws[met_south['time'] == time] 535 | total_power = np.sum(SCADA['power'][SCADA['time'] == time]) 536 | 537 | fault_mask = [SCADA_fault == 2,SCADA_fault ==1] 538 | fault_mask = reduce(np.logical_or,fault_mask) 539 | 540 | if float(np.sum(fault_mask))/float(len(fault_mask)) >= 0.98: 541 | plt.scatter(met_ws,total_power) 542 | 543 | plt.title('Wind Plant Power Curve - A') 544 | plt.xlabel('wind speed (m/s)') 545 | plt.ylabel('power (kW)') 546 | """ 547 | -------------------------------------------------------------------------------- /windeval.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu Jun 16 15:21:55 2016 4 | 5 | @author: epenn 6 | 7 | Funtions for evaluating wind resource. 8 | """ 9 | 10 | import numpy as np 11 | import datetime as datetime 12 | import matplotlib.pyplot as plt 13 | from pytz import timezone 14 | from windrose import WindroseAxes 15 | #import collections 16 | import glob 17 | import os 18 | import re 19 | from numpy import linalg as LA 20 | from matplotlib import cm 21 | from matplotlib.mlab import griddata 22 | 23 | #Print an error message. 24 | def print_err(): 25 | """Print \"Error!! Your inputs do not have equal lengths.\"""" 26 | print("Error!! Your inputs do not have equal lengths.") 27 | 28 | """ 29 | Read in Files 30 | ------------- 31 | """ 32 | def read_in(directory, start_date, end_date, var): 33 | """Read in a set of .pyz files with filenames which are dates. 34 | 35 | Arguments: 36 | directory -- the path to the directory where files are stored 37 | start_date -- the first date to average over 38 | end_sate -- the last date to average over 39 | var -- a list of variables to read in (default: read in all variables) 40 | """ 41 | #start = float(start_date.strftime('%Y%m%d')) 42 | #end = float(end_date.strftime('%Y%m%d')) 43 | 44 | #fplen = len(directory) 45 | 46 | #Get filenames within the date range. 47 | #filenames = [t for t in glob.glob(directory+'*') if float(t[fplen:fplen+8]) >= start and float(t[fplen:fplen+8]) <= end] 48 | 49 | filenames = [] 50 | time_step = datetime.timedelta(days = 1) 51 | while start_date <= end_date: 52 | filenames.append(os.path.normpath('%s/%s' % (directory, start_date.strftime('%Y%m%d.npz')))) 53 | start_date += time_step 54 | filenames = np.array(filenames) 55 | 56 | #print filenames 57 | 58 | #Read each variable into the dictionary. 59 | #d = collections.defaultdict(list) 60 | d = {} 61 | for v in var: 62 | d[v] = [] 63 | 64 | for n in filenames: 65 | if os.path.exists(n): #Check that the file for this date exists. 66 | f = np.load(n) 67 | for v in var: 68 | d[v].extend(f[v]) 69 | f.close() 70 | else: #If it does not, append a 144 nons in each key. 71 | for v in var: #fix_time will fix this later. 72 | d[v].append(np.nan) 73 | 74 | #Convert each list to an np.array. 75 | for key in d: 76 | d[key] = np.array(d[key], dtype = type(d[key][0])) 77 | 78 | # final_dictionary = {} 79 | # for key in d: 80 | # print(key) 81 | # temp_array = np.array([]) 82 | # for i in d[key]: 83 | # temp_array = np.append(temp_array,i) 84 | # final_dictionary[key] = temp_array 85 | 86 | return d 87 | 88 | def save_figure(name): 89 | """Save a figure under the filename \'name.png,\' then close and clear the figure. 90 | Saves in the current directory of this file. 91 | """ 92 | directory = os.path.normpath('%s/Figures/' % (os.getcwd())) 93 | if not os.path.exists(directory): 94 | os.makedirs(directory) 95 | print("Directory %s created." % (directory)) 96 | figure_string = os.path.normpath('%s/%s.png' % (directory,name)) 97 | plt.savefig(figure_string) 98 | plt.close() 99 | plt.clf() 100 | 101 | 102 | #[new_folder_name, date, old_dictionary, step_in_minutes, turbine_names] = ['SCADA',start_time,winter_met_file,10, turbine_names] 103 | 104 | def fix_time(new_folder_name, date, old_dictionary,step_in_minutes, turbine_names = None): 105 | """Fill in missing dates with np.nan and save data in a new folder with 106 | dates as titles of the filenames. 107 | 108 | Arguments: 109 | new_folder_name -- the name of the folder where corrected files are stored 110 | dictionary -- a dictionary containing all data you want to store 111 | time -- the array in the dictionary which contains the times 112 | step_in_minutes -- timestep between each data point recorded (usu. 10mins) 113 | """ 114 | 115 | #Create a folder for formatted data, if it does not already exist. 116 | formatted_data_dir = os.path.normpath('%s/FormattedData/' % (os.getcwd())) 117 | if not os.path.exists(formatted_data_dir): 118 | os.makedirs(formatted_data_dir) 119 | print('Directory %s created.' % (formatted_data_dir)) 120 | 121 | new_folder = os.path.normpath('%s/%s/' % (formatted_data_dir,new_folder_name)) 122 | if not os.path.exists(new_folder): 123 | os.makedirs(new_folder) 124 | print('Directory %s created.' % (new_folder)) 125 | 126 | date_str = date.strftime('%Y%m%d') 127 | 128 | old_time = np.array(old_dictionary['time']) 129 | 130 | #Create an array with complete times for every 10 minutes. 131 | start_time = datetime.datetime(date.year,date.month,date.day,0,0,0) #Start at midnight 132 | end_time = datetime.datetime(date.year, date.month, date.day,23,50,00) #End at 11:50pm 133 | new_time = [] 134 | time_step = datetime.timedelta(minutes=step_in_minutes) 135 | while start_time <= end_time: 136 | new_time.append(start_time) 137 | start_time += time_step 138 | new_time = np.array(new_time) 139 | 140 | #Create a list of times which spans the range of TIME 141 | if turbine_names is None: 142 | #Create a new dictionary to hold the full new_time (rather than incomplete old_time) 143 | new_dictionary = {} 144 | for key in old_dictionary: 145 | new_dictionary[key] = np.zeros([len(new_time)],type(old_dictionary[key][0])) 146 | 147 | #This breaks if there is more than one of the same timestamp. 148 | for nt in range(len(new_time)): 149 | #Loop over old_time until you reach a value = new_time 150 | print(new_time[nt]) 151 | ot = 0 152 | while ot < len(old_time): 153 | if old_time[ot] == new_time[nt]: 154 | for key in old_dictionary: #Loop over keys in old_dictionary. 155 | if key != 'time': #Do not create an incomplete 'time' array. This is useless. 156 | new_dictionary[key][nt] = old_dictionary[key][ot] 157 | ot +=1 158 | break #If a value is found, stop searching. 159 | else: #If no value matches the new time array, fill with nan. 160 | for key in old_dictionary: #Loop over keys in old dictionary. 161 | if key != 'time': #Do not create an incomplete 'time' array. This is useless. 162 | new_dictionary[key][nt] = np.nan 163 | ot += 1 164 | 165 | #Set your time entry equal to the new, fully-populated time array. 166 | new_dictionary['time'] = np.array(new_time) 167 | 168 | #Save your new dictionary as an npz after deleting any file of the same name. 169 | file_path = os.path.normpath("%s/%s.npz" % (new_folder,date_str)) 170 | if os.path.exists(file_path): #Remove the file if it already exists. 171 | os.remove(file_path) 172 | np.savez(file_path,**new_dictionary) 173 | 174 | else: #If turbine_names are input. 175 | all_turbine_names = old_dictionary['turbine_name'] #Create an array listing each turbine once. 176 | 177 | new_dictionary = {} 178 | for key in old_dictionary: 179 | new_dictionary[key] = [None]*(len(new_time)*len(turbine_names)) 180 | 181 | for nt in range(len(new_time)): 182 | for tb in range(len(turbine_names)): 183 | turb_time = np.array([]) 184 | 185 | #If none of the turbines are valid, turb_time == [ nan]. 186 | if ~np.any([all_turbine_names == turbine_names[tb]]): 187 | turb_time = np.append(turb_time,np.nan) 188 | else: 189 | turb_time = old_time[all_turbine_names == turbine_names[tb]] 190 | 191 | tt = 0 192 | while tt < len(turb_time): 193 | if turb_time[tt] == new_time[nt]: 194 | for key in old_dictionary: 195 | if key == 'time': 196 | new_dictionary[key][nt*len(turbine_names) + tb] = new_time[nt] 197 | elif key == 'turbine_name': 198 | new_dictionary[key][nt*len(turbine_names) + tb] = turbine_names[tb] 199 | else: 200 | new_dictionary[key][nt*len(turbine_names) + tb] = old_dictionary[key][all_turbine_names == turbine_names[tb]][tt] 201 | #print old_dictionary[key][all_turbine_names == unique_turb[tb]][tt] 202 | #print "Line 168: %s" % (new_dictionary[key][nt*len(unique_turb) +tb]) 203 | tt += 1 204 | break 205 | else: #If the times do not match. 206 | for key in old_dictionary: 207 | if key == 'time': 208 | new_dictionary[key][nt*len(turbine_names) + tb] = new_time[nt] 209 | elif key == 'turbine_name': 210 | new_dictionary[key][nt*len(turbine_names) + tb] = turbine_names[tb] 211 | else: 212 | new_dictionary[key][nt*len(turbine_names) + tb] = np.nan 213 | tt += 1 214 | #print "Line 177: %s" % (new_dictionary[key][0:90]) 215 | 216 | #Convert dictionary to np.array type. 217 | 218 | final_dictionary = {} 219 | for key in new_dictionary: 220 | temp_array = np.array([]) 221 | for i in new_dictionary[key]: 222 | temp_array = np.append(temp_array,i) 223 | final_dictionary[key] = temp_array 224 | 225 | #Save your new dictionary as an npz after deleting any file of the same name. 226 | file_path = os.path.normpath("%s/%s.npz" % (new_folder,date_str)) 227 | if os.path.exists(file_path): #Remove the file if it already exists. 228 | os.remove(file_path) 229 | np.savez(file_path,**final_dictionary) 230 | 231 | 232 | 233 | def save_total_power(data,times,SCADA_faults,filename): 234 | total_power = np.array([]) 235 | new_times = np.array([]) 236 | percent_active = np.array([]) 237 | for time in np.unique(times): 238 | state_fault = SCADA_faults[times == time] 239 | fault_mask = [state_fault == 2,state_fault == 1] 240 | fault_mask = reduce(np.logical_or,fault_mask) 241 | 242 | total_power = np.append(total_power,np.sum(data[times == time])) 243 | new_times = np.append(new_times,time) 244 | percent_active = np.append(percent_active,float(np.sum(fault_mask))/float(len(fault_mask))) 245 | 246 | 247 | total_dictionary = {} 248 | total_dictionary['total_power'] = total_power 249 | total_dictionary['time'] = new_times 250 | total_dictionary['percent_active'] = percent_active 251 | 252 | file_path = os.path.normpath('%s/FormattedData/%s' % (os.getcwd(),filename)) 253 | np.savez(file_path,**total_dictionary) 254 | 255 | 256 | """ 257 | Unit Conversions 258 | ---------------- 259 | """ 260 | def convert_to_central(times_UTC): 261 | """Convert a list of naive UTC datetimes to Central Time.""" 262 | times_central = [] 263 | for time in times_UTC: 264 | #Tell the time variable what timezone it is in 265 | time_utc = time.replace(tzinfo=timezone('UTC')) 266 | #Convert from UTC to Central (presumably including DST) 267 | time_central = time_utc.astimezone(timezone('US/Central')) 268 | times_central.append(time_central) 269 | return times_central 270 | 271 | def convert_to_uv(wind_directions,wind_speeds): 272 | """Convert arrays of wind directions and wind speeds to arrays of u,v vectors. 273 | 274 | Arguments: 275 | wind_directions -- np array of wind directions in meteorological degrees 276 | wind_speeds -- np array of corresponding wind speeds 277 | """ 278 | uv = [] 279 | u = - abs(wind_speeds) * np.sin(np.deg2rad(wind_directions)) #Negative because u,v shows the direction wind is moving 280 | v = - abs(wind_speeds) * np.cos(np.deg2rad(wind_directions)) #rather than direction wind comes from (as in met_deg) 281 | uv.append(u) 282 | uv.append(v) 283 | return np.array(uv) #[[list of u values],[list of v values]] 284 | 285 | def convert_to_degspd(u,v): 286 | """Convert arrays of u,v vectors to arrays of direction (in met.deg) and speed 287 | 288 | Arguments: 289 | u - np array of the u (east) part of the vector 290 | v - np array of the v (north) part of the vector 291 | """ 292 | dir_spd = [] 293 | wind_direction = (np.rad2deg(np.arctan2(-u,-v))) % 360.0 294 | wind_speed = np.sqrt(u ** 2 + v ** 2) 295 | dir_spd.append(wind_direction) 296 | dir_spd.append(wind_speed) 297 | return np.array(dir_spd) #[[list of wind dirs], [list of wind spds]] 298 | 299 | def correct_density(wind_speed,pressure,temperature,relative_humidity): 300 | """Calculates air density based on IEC 61400-12-1, 2016 edition. 301 | 302 | Arguments: 303 | pressure -- list of 10-min avg pressures. Units: Pa 304 | temperture -- list of 10-min avg temperatures. Units: K 305 | relative_humidity -- list of 10-min avg RH. Units: Fraction between 0 and 1. 306 | """ 307 | gas_const_air = 287.05 # J/kgK; gas constant of dry air 308 | gas_const_water = 461.5 # J/kgK; gas constant of water vapor 309 | vapor_pressure = 0.0000205 * np.exp(0.0631846 * temperature) # Pa 310 | 311 | density = (1 / temperature) * ((pressure / gas_const_air) - ( (relative_humidity * vapor_pressure) * ((1.0 / gas_const_air) - (1.0 / gas_const_water)) )) 312 | density_sea_level = 1.225 # kg/m^3 313 | 314 | new_ws = wind_speed * (density / density_sea_level) ** (1.0/3.0) 315 | return new_ws 316 | 317 | def grid(x, y, z, resX=100, resY=100): 318 | """"Convert 3 column data to matplotlib grid 319 | Credit: Elyase of Stackoverflow.""" 320 | xi = np.linspace(min(x), max(x), resX) 321 | yi = np.linspace(min(y), max(y), resY) 322 | Z = griddata(x, y, z, xi, yi,interp='linear') 323 | X, Y = np.meshgrid(xi, yi) 324 | return X, Y, Z 325 | 326 | """ 327 | Filtering Data 328 | -------------- 329 | """ 330 | 331 | def filter_icing(data,temp,RH,precip): 332 | """Remove data points where the instrument experienced icing. 333 | Icing occurs when temp is below 0.0 degC AND relative humidity is above 80.0 334 | OR when temp is below 0.0 degC AND precipitation occurs 335 | 336 | Arguments: 337 | data -- a np array of data you wish to filter 338 | temp -- a np array of corresponding temperatures for these data 339 | RH -- a np array of corresponding relative humidity values for these data 340 | precip -- a np array of corresponding precipitation values for these data 341 | """ 342 | if len(temp) == len(RH) == len(data): 343 | deiced_data = [] 344 | for (temp_10m,RH_10m,precip_10m,data_10m) in zip(temp,RH,precip,data): 345 | if np.isnan(temp_10m): 346 | deiced_data.append(np.nan) 347 | elif temp_10m <= 0.0 and RH_10m >= 80.0: 348 | deiced_data.append(np.nan) 349 | elif temp_10m <= 0.0 and precip_10m != 0.0: 350 | deiced_data.append(np.nan) 351 | else: 352 | deiced_data.append(data_10m) 353 | return np.array(deiced_data) 354 | else: 355 | print_err() 356 | 357 | def filter_streaking(data): 358 | destreaked_data = np.empty(len(data)) 359 | i = 0 360 | while i < len(data)-3: 361 | if data[i] == data[i+1] and data[i] == data[i+2]: #Compare 3 values at one time. 362 | destreaked_data[i] = np.nan 363 | destreaked_data[i+1] = np.nan 364 | destreaked_data[i+2] = np.nan 365 | i += 3 #Move forward 3 steps if they are all equal 366 | while data[i] == data[i-1] and i < len(data)-3: #Move forward by 1's afterwards until the value changes. 367 | destreaked_data[i] = np.nan 368 | i += 1 369 | else: #If three in a row not equal, move by only one step. 370 | destreaked_data[i] = data[i] 371 | i += 1 372 | while i >= len(data)-3 and i <= len(data)-1: #For values near the end, the procedure changes to keep within the indices of the array. 373 | if data[i] == data[i-1] and data[i] == data[i-2]: 374 | destreaked_data[i] = np.nan 375 | i += 1 376 | elif i != len(data)-1 and data[i] == data[i-1] and data[i] == data[i+1]: 377 | destreaked_data[i] = np.nan 378 | i += 1 379 | else: 380 | destreaked_data[i] = data[i] 381 | i += 1 382 | return destreaked_data 383 | 384 | def filter_obstacles(data,wind_directions,direction_filtered,sector_filtered): 385 | """Remove data in the specified sector. 386 | 387 | Arguments: 388 | data -- a np array of the data you wish to filter 389 | temp -- a np array of corresponding wind directions for these data 390 | direction_filtered -- the direction (in meteorological degrees) you wish to remove 391 | sector_filtered -- the size of sector (in degrees) you wish to remove 392 | """ 393 | min_filtered_angle = (float(direction_filtered) - float(sector_filtered) / 2.0) % 360.0 394 | max_filtered_angle = (float(direction_filtered) + float(sector_filtered) / 2.0) % 360.0 395 | filtered_data = [] 396 | if len(data) == len(wind_directions): #Check that both lists have the same length. 397 | if max_filtered_angle < min_filtered_angle: #If sector crosses 0deg, this is true. 398 | for (datapoint,direction) in zip(data,wind_directions): #(e.g. if excluded range is 350deg - 10 deg). 399 | if direction >= min_filtered_angle or direction <= max_filtered_angle: 400 | filtered_data.append(np.nan) 401 | else: 402 | filtered_data.append(datapoint) 403 | else: #If sector does not cross 0 deg, this is true. 404 | for (datapoint,direction) in zip(data,wind_directions): 405 | if direction >= min_filtered_angle and direction <= max_filtered_angle: 406 | filtered_data.append(np.nan) 407 | else: 408 | filtered_data.append(datapoint) 409 | return np.array(filtered_data) 410 | else: 411 | print_err() 412 | 413 | def filter_temp(temp_array): 414 | """Filter temp if it is < -50 or > 130 deg C (or F). 415 | 416 | Arguments: 417 | temp_array -- a np array of temperatures to filter. 418 | """ 419 | filtered_temp = np.array([]) 420 | for temp in temp_array: 421 | if temp < -50.0 or temp > 130.0: 422 | filtered_temp = np.append(filtered_temp, np.nan) 423 | else: 424 | filtered_temp = np.append(filtered_temp, temp) 425 | 426 | return filtered_temp 427 | 428 | def sub_data(data,filtered_sub,wind_directions,direction_filtered,sector_filtered): 429 | """Remove data in a specified sector and replace with data for another met tower. 430 | 431 | Arguments: 432 | data -- orig. data filtered for icing but NOT for direction. 433 | filtered_sub -- fully filtered data (filtered for wind direction as well!!) 434 | direction_filtered -- the direction (in meteorological degrees) you wish to remove 435 | sector_filtered -- the size of sector (in degrees) you wish to remove 436 | """ 437 | min_filtered_angle = (float(direction_filtered) - float(sector_filtered) / 2.0) % 360.0 438 | max_filtered_angle = (float(direction_filtered) + float(sector_filtered) / 2.0) % 360.0 439 | filled_data = np.array([]) 440 | if max_filtered_angle < min_filtered_angle: #If sector crosses 0deg, this is true. 441 | for i in range(len(data)): #(e.g. if excluded range is 350deg - 10 deg). 442 | if wind_directions[i] >= min_filtered_angle or wind_directions[i] <= max_filtered_angle: 443 | filled_data = np.append(filled_data,filtered_sub[i]) #Replace filtered values with the substitute met tower values 444 | else: 445 | filled_data = np.append(filled_data,data[i]) 446 | else: #If sector does not cross 0 deg, this is true. 447 | for i in range(len(data)): 448 | if wind_directions[i] >= min_filtered_angle and wind_directions[i] <= max_filtered_angle: 449 | filled_data = np.append(filled_data,filtered_sub[i]) #Replace filtered values with the substitute met tower values 450 | else: 451 | filled_data = np.append(filled_data,data[i]) 452 | return np.array(filled_data) 453 | 454 | """ 455 | Finding Hourly Wind Speed and Direction 456 | --------------------------------------- 457 | """ 458 | 459 | def hourly_wind_speed(wind_speeds, times): 460 | """Average wind speed over hours and return a 1x24 numpy array. 461 | 462 | Arguments: 463 | wind_speeds -- a np array of all wind speeds 464 | times -- a np array of all times with indexes corresponding to wind_speeds 465 | """ 466 | avg_hourly_ws = [] 467 | new_times = [] 468 | hours = np.array([t.hour for t in times]) #Make an array of just the hours. 469 | for i in range(24): 470 | avg_hourly_ws.append(np.nanmean(wind_speeds[hours == i])) 471 | new_times.append(i) 472 | return np.array(new_times), np.array(avg_hourly_ws) #Return the wind speeds and their corresponding times as a NumPy array 473 | 474 | #Gets average wind dir for each hour of the day (returns 24h averaged over multiple days) 475 | def hourly_wind_direction(wind_directions,times): 476 | """Average wind direction over hours and return a 1x24 numpy array. 477 | 478 | Arguments: 479 | wind_directions -- a np array of all wind speeds 480 | times -- a np array of all times with indexes corresponding to wind_speeds 481 | """ 482 | wind_speeds = np.ones(len(wind_directions)) #Assign wind speeds unit values. 483 | uv = convert_to_uv(wind_directions,wind_speeds) #Conver to u,v unit vectors 484 | u, v = np.array(uv[0]), np.array(uv[1]) 485 | avg_hourly_u, avg_hourly_v, new_times = [], [], [] 486 | avg_hourly_wd = [] 487 | hours = np.array([t.hour for t in times]) 488 | for i in range(24): 489 | avg_hourly_u.append(np.nanmean(u[hours == i])) #Average over u and v 490 | avg_hourly_v.append(np.nanmean(v[hours == i])) 491 | new_times.append(i) 492 | avg_hourly_wd = convert_to_degspd(np.array(avg_hourly_u), np.array(avg_hourly_v))[0] #Convert u,v to wind direction. Make sure inputs are arrays. 493 | return np.array(new_times), np.array(avg_hourly_wd) #Return the wind directions and their corresponding times as a NumPy array 494 | 495 | #def hourly_wind_speed(wind_speeds, times): 496 | # """Find average wind speed for each hour for which there is data 497 | # 498 | # Arguments: 499 | # wind_speeds -- np array of wind speeds for each hour 500 | # times -- np array of datetimes corresponding to each wind_speed 501 | # """ 502 | # hourly_wind_speeds = [] 503 | # new_times = [] 504 | # #Concatenate yyyymmddhh for each measurement from times. Intentionally drop mins. 505 | # individual_hours = np.array([datetime.datetime(t.year, t.month, t.day, t.hour, tzinfo = t.tzinfo) for t in times]) 506 | # #Make a list of unique hours listed in the time variable 507 | # unique_hours = np.unique(individual_hours) 508 | # for hour in unique_hours: 509 | # hourly_wind_speeds.append(np.nanmean(wind_speeds[individual_hours == hour])) #Append the average of values at that unique hour 510 | # new_times.append(hour) #Append the corresponding unique hour to new_times 511 | # return [new_times,hourly_wind_speeds] 512 | # 513 | #def hourly_wind_direction(wind_directions,wind_speeds,times): 514 | # """ Gets average wdir for each hour measured (returns a value for each hour for which there is data) """ 515 | # uv = convert_to_uv(wind_directions,wind_speeds) 516 | # u = uv[0] 517 | # v = uv[1] 518 | # hourly_u = [] 519 | # hourly_v = [] 520 | # hourly_wd = [] 521 | # new_times = [] 522 | # #Concatenate yyyymmddhh for each measurement from times. Intentionally dropped mins. 523 | # individual_hours = np.array([datetime.datetime(t.year, t.month, t.day, t.hour, tzinfo = t.tzinfo) for t in times]) 524 | # unique_hours = np.unique(individual_hours) 525 | # #Make a list of unique hours listed in the time variable 526 | # for hour in unique_hours: 527 | # hourly_u.append(np.nanmean(u[individual_hours == hour])) #Append the average of u,v values at that unique hour 528 | # hourly_v.append(np.nanmean(v[individual_hours == hour])) 529 | # new_times.append(hour) 530 | # hourly_wd = convert_to_degspd(np.array(hourly_u),np.array(hourly_v))[0] #Convert from u, to degrees and speed. Make sure inputs are arrays 531 | # return np.array([new_times,hourly_wd]) #Return the wind directions and their corresponding times. 532 | 533 | """ 534 | Creating a Wind Rose 535 | -------------------- 536 | author: Lionel Roubeyrie 537 | """ 538 | def new_axes(): 539 | """Create a set of wind rose axes.""" 540 | fig = plt.figure(figsize=(8, 8), dpi=80, facecolor='w', edgecolor='w') 541 | rect = [0.1, 0.1, 0.8, 0.8] 542 | ax = WindroseAxes(fig, rect, axisbg='w') 543 | fig.add_axes(ax) 544 | return ax 545 | 546 | def set_legend(ax): 547 | """Create a legend for the wind rose.""" 548 | l = ax.legend(borderaxespad=-0.10) 549 | plt.setp(l.get_texts(), fontsize=8) 550 | 551 | def plot_wind_rose(wind_directions,wind_speeds,bins): 552 | """Plot a wind rose. 553 | 554 | Arguments: 555 | wind_directions -- a np array of wind directions filtered for icing 556 | wind_speeds -- a np array of filtered wind speed corresponding to wind_directions 557 | """ 558 | mask = [~np.isnan(wind_directions),~np.isnan(wind_speeds)] 559 | mask = reduce(np.logical_and,mask) 560 | ax = new_axes() 561 | ax.bar(wind_directions[mask], wind_speeds[mask],normed=True, opening=0.8, edgecolor='white', bins = bins) 562 | set_legend(ax) 563 | 564 | def plot_power_rose(wind_directions,power,num_wd_bins): 565 | """Plot a power rose. Kind of a hacked wind rose. 566 | 567 | Arguments: 568 | wind_directions -- a np array of wind directions filtered for icing 569 | power -- a np array of percent power production corresponding to wind_directions 570 | num_wd_bins -- the number of wind direction bins to include on the rose. 571 | """ 572 | dir_bins = np.array(np.linspace(0.0,360.0 - 360.0 / num_wd_bins,num_wd_bins)) 573 | #Find the total amount of power produced in each sector. 574 | dir_power = np.array([np.nansum(filter_obstacles(power,wind_directions,(wd + 180.0) % 360.0, 360 - 360/float(num_wd_bins))) for wd in dir_bins]) 575 | dir_power = np.round(dir_power * 100.0 / np.nansum(dir_power), decimals=0) #Normalize it and round to nearest int. 576 | 577 | proportional_wd = np.array([]) 578 | for i in range(len(dir_power)): 579 | for n in range(int(dir_power[i])): #Loop as many times as the percent of power produced in this sector. 580 | proportional_wd = np.append(proportional_wd,dir_bins[i]) #i.e., if 50% of power comes from the south, append 50 instances of 180.0 degrees. 581 | ones = np.ones(len(proportional_wd)) 582 | 583 | ax = new_axes() 584 | ax.bar(proportional_wd, ones,normed=False, opening=0.8, edgecolor='white', bins = [0.0,100.], cmap=cm.RdGy) 585 | set_legend(ax) 586 | 587 | 588 | """ 589 | Sensitivity Functions 590 | --------------------- 591 | 592 | All sensitivity code from Jennifer Newman 593 | """ 594 | 595 | def bin_mean_func(data_min,data_max,bin_width,x_data,y_data): 596 | """Find avgs for each bin and return bin center and bin mean to plot. 597 | 598 | Arguments: 599 | data_min -- the minumum value included in the x data 600 | data max -- the maximum values included in the x data 601 | bin_width -- the width of bins desired 602 | x_data -- your independent variable 603 | y_data -- your dependent variable 604 | """ 605 | n_bins = (data_max-data_min)/bin_width 606 | 607 | bin_mean = [] 608 | bin_center = [] 609 | 610 | for i in np.arange(data_min,data_max,bin_width): 611 | mask = [x_data > i, x_data <= i + bin_width,~np.isnan(x_data),~np.isnan(y_data)] 612 | mask = reduce(np.logical_and, mask) 613 | data_temp = y_data[mask] 614 | if len(data_temp) > len(y_data)/(2*n_bins): 615 | bin_mean.append(np.mean(data_temp)) 616 | else: 617 | bin_mean.append(np.nan) 618 | bin_center.append(i + bin_width/2.) 619 | 620 | return np.array(bin_center),np.array(bin_mean) 621 | 622 | 623 | def linear_regression_intercept(x,y): 624 | """Find the slope, intercept and R^2 value for a scatterplot. 625 | 626 | Arguments: 627 | x -- independent variable 628 | y -- dependent variable 629 | """ 630 | masks = [~np.isnan(x),~np.isnan(y)] 631 | total_mask = reduce(np.logical_and, masks) 632 | x = x[total_mask] 633 | y = y[total_mask] 634 | A = np.vstack([x, np.ones(len(x))]).T 635 | m, c = np.linalg.lstsq(A, y)[0] 636 | y_pred = m*x + c 637 | total = 0 638 | for i in range(len(y)): 639 | total+=(y_pred[i]-np.mean(y))**2 640 | total = 0 641 | for i in y: 642 | total+=(i-np.mean(y))**2 643 | SST2 = LA.norm((y-np.mean(y)))**2 644 | total = 0 645 | for i in range(len(y)): 646 | total+=(y_pred[i]-y[i])**2 647 | SSE2 = LA.norm((y_pred-y))**2 648 | R_squared = 1-SSE2/SST2 649 | return m,c,R_squared 650 | 651 | def bin_mean_plot(data_min,data_max,bin_width,x_data,y_data,var_name): 652 | """Plot data and return statistical data. 653 | 654 | Arguments: 655 | data_min -- the minumum value included in the x data 656 | data max -- the maximum values included in the x data 657 | bin_width -- the width of bins desired 658 | x_data -- your independent variable 659 | y_data -- your dependent variable 660 | var_name -- the name of the variable you will test for sensitivity (string) 661 | 662 | Returns: 663 | A list containing: slope, sensitivity (slope * std), r^2, r*sensitivity 664 | """ 665 | #If sensitivity > 0.5 or r*sensitivitiy > 0.1, it is sensitive. 666 | 667 | 668 | #from IEC_sensitivity_functions import bin_mean_func 669 | [bin_center,bin_mean] = bin_mean_func(data_min,data_max,bin_width,x_data,y_data) 670 | #from stats_functions import linear_regression_intercept 671 | [m,c,r_squared] = linear_regression_intercept(bin_center,bin_mean) 672 | x_vals = np.arange(data_min,data_max,bin_width) 673 | y_vals = x_vals*m + c 674 | 675 | plt.figure() 676 | plt.scatter(x_data,y_data,s=5,color="blue") 677 | plt.scatter(bin_center,bin_mean,s=20,color="red") 678 | plt.plot(x_vals,y_vals,color="red",label = 'y = %(number)0.2f*x + %(number2)0.2f. R$^2$ = %(number3)0.2f'%\ 679 | {"number": m,"number2": c,"number3": r_squared} ) 680 | plt.xlim([data_min,data_max]) 681 | plt.ylim([-50,50]) 682 | plt.legend(loc='best') 683 | plt.xlabel(var_name) 684 | plt.ylabel('TI % Difference') 685 | var_stats = [m,m*np.std(x_data[~np.isnan(x_data)]),r_squared,np.sqrt(r_squared)*m*np.std(x_data[~np.isnan(x_data)])] 686 | return var_stats 687 | 688 | def calc_wind_shear(time,heights,wind_speeds): 689 | """Plot data and return statistical data. 690 | 691 | Arguments: 692 | time -- array of times. Same length as wind_speeds sub-arrays 693 | wind_speeds -- array of at least 2 arrays of wind speeds. 694 | Each sub-array corresponds to a different measurement height. 695 | heights -- List of heights corresponding to each array of wind_speeds 696 | """ 697 | 698 | 699 | wind_shear = np.array([]) 700 | for t in time: 701 | #Extract just the wind speeds that correspond to this time. 702 | this_wind_speed = np.array([]) 703 | for i in range(len(heights)): 704 | this_wind_speed = np.append(this_wind_speed, wind_speeds[i][time == t]) 705 | #Find alpha 706 | if np.sum(~np.isnan(this_wind_speed)) >= 2: #Alpha only exists if there are at least 2 values. 707 | wind_shear = np.append(wind_shear, linear_regression_intercept(np.log(heights), np.log(this_wind_speed))[0]) 708 | else: 709 | wind_shear = np.append(wind_shear, np.nan) 710 | 711 | del this_wind_speed 712 | 713 | return wind_shear --------------------------------------------------------------------------------