def test_scpdsi(self): # the indices.scpdsi() function is a wrapper for palmer.pdsi(), so we'll # just confirm that this function can be called without raising an error and # the compute.pdsi() function itself being tested within test_palmer.py indices.scpdsi(self.fixture_precips_mm_monthly, self.fixture_pet_mm, self.fixture_awc_inches, self.fixture_data_year_start_monthly, self.fixture_calibration_year_start_monthly, self.fixture_calibration_year_end_monthly)
def test_scpdsi(precips_mm_monthly, pet_thornthwaite_mm, awc_inches, data_year_start_monthly, calibration_year_start_monthly, calibration_year_end_monthly): # the indices.scpdsi() function is a wrapper for palmer.scpdsi(), so we'll # just confirm that this function can be called without raising an error and # the palmer.scpdsi() function itself is being tested within test_palmer.py indices.scpdsi(precips_mm_monthly, pet_thornthwaite_mm, awc_inches, data_year_start_monthly, calibration_year_start_monthly, calibration_year_end_monthly)
def scPDSI(cts, awc=123, rs='1M'): ''' Wrapper for the self-calibrating Palmer Drought Severity Index etc. Input cts as pd.DataFrame of a climate time series, awc is the available water capacity (in mm), rs is the resampling frequency. ''' from climate_indices import indices #this uses a conversion to inches! *0.0393701 prcp = cts.pr.resample(rs).sum().values * 0.0393701 pet = cts.et_harg.resample(rs).sum().values * 0.0393701 awc = awc * 0.0393701 start_year = cts.index[10].year end_year = cts.index[-10].year scpdsi, pdsi, phdi, pmdi, zindex = indices.scpdsi(prcp, pet, awc, start_year, start_year, end_year) dummy = pd.DataFrame([scpdsi, pdsi, phdi, pmdi, zindex]).T dummy.index = cts.pr.resample(rs).sum().index dummy.columns = ['scpdsi', 'pdsi', 'phdi', 'pmdi', 'zindex'] return dummy
def _compute_and_write_division(self, div_index): """ Computes indices for a single division, writing the output into NetCDF. :param div_index: """ # only process specified divisions if self.divisions is not None and div_index not in self.divisions: return # open the NetCDF files with netCDF4.Dataset(self.divisions_file, 'a') as divisions_dataset: climdiv_id = divisions_dataset['division'][div_index] # only process divisions within CONUS, 101 - 4811 if climdiv_id > 4811: return logger.info('Processing indices for division %s', climdiv_id) # read the division of input temperature values temperature = divisions_dataset[self.var_name_temperature][ div_index, :] # assuming (divisions, time) orientation # initialize the latitude outside of the valid range, in order to use this within a conditional below to verify a valid latitude latitude = -100.0 # latitudes are only available for certain divisions, make sure we have one for this division index if div_index < divisions_dataset['lat'][:].size: # get the actual latitude value (assumed to be in degrees north) for the latitude slice specified by the index latitude = divisions_dataset['lat'][div_index] # only proceed if the latitude value is within valid range if not np.isnan(latitude) and (latitude < 90.0) and (latitude > -90.0): # convert temperatures from Fahrenheit to Celsius, if necessary temperature_units = divisions_dataset[ self.var_name_temperature].units if temperature_units in [ 'degree_Fahrenheit', 'degrees Fahrenheit', 'degrees F', 'fahrenheit', 'Fahrenheit', 'F' ]: # TODO make sure this application of the ufunc is any faster pylint: disable=fixme temperature = scipy.constants.convert_temperature( temperature, 'F', 'C') elif temperature_units not in [ 'degree_Celsius', 'degrees Celsius', 'degrees C', 'celsius', 'Celsius', 'C' ]: raise ValueError( 'Unsupported temperature units: \'{0}\''.format( temperature_units)) # use the numpy.apply_along_axis() function for computing indices such as PET that take a single time series # array as input (i.e. each division's time series is the initial 1-D array argument to the function we'll apply) logger.info('\tComputing PET for division %s', climdiv_id) logger.info('\t\tCalculating PET using Thornthwaite method') # compute PET across all longitudes of the latitude slice # Thornthwaite PE pet_time_series = indices.pet( temperature, latitude_degrees=latitude, data_start_year=self.data_start_year) # the above returns PET in millimeters, note this for further consideration pet_units = 'millimeter' # write the PET values to NetCDF lock.acquire() divisions_dataset['pet'][div_index, :] = np.reshape( pet_time_series, (1, pet_time_series.size)) divisions_dataset.sync() lock.release() else: pet_time_series = np.full(temperature.shape, np.NaN) pet_units = None # read the division's input precipitation and available water capacity values precip_time_series = divisions_dataset[self.var_name_precip][ div_index, :] # assuming (divisions, time) orientation if div_index < divisions_dataset[self.var_name_soil][:].size: awc = divisions_dataset[self.var_name_soil][ div_index] # assuming (divisions) orientation awc += 1 # AWC values need to include top inch, values from the soil file do not, so we add top inch here else: awc = np.NaN # allocate arrays to contain a latitude slice of Palmer values time_size = divisions_dataset['time'].size division_shape = (time_size) pdsi = np.full(division_shape, np.NaN) phdi = np.full(division_shape, np.NaN) pmdi = np.full(division_shape, np.NaN) scpdsi = np.full(division_shape, np.NaN) zindex = np.full(division_shape, np.NaN) # compute SPI and SPEI for the current division only if we have valid inputs if not np.isnan(precip_time_series).all(): # put precipitation into inches if not already mm_to_inches_multiplier = 0.0393701 possible_mm_units = ['millimeters', 'millimeter', 'mm'] if divisions_dataset[ self.var_name_precip].units in possible_mm_units: precip_time_series = precip_time_series * mm_to_inches_multiplier if not np.isnan(pet_time_series).all(): # compute Palmer indices if we have valid inputs if not np.isnan(awc): # if PET is in mm, convert to inches if pet_units in possible_mm_units: pet_time_series = pet_time_series * mm_to_inches_multiplier # PET is in mm, convert to inches since the Palmer uses imperial units pet_time_series = pet_time_series * mm_to_inches_multiplier logger.info('\tComputing PDSI for division %s', climdiv_id) # compute Palmer indices palmer_values = indices.scpdsi( precip_time_series, pet_time_series, awc, self.data_start_year, self.calibration_start_year, self.calibration_end_year) scpdsi = palmer_values[0] pdsi = palmer_values[1] phdi = palmer_values[2] pmdi = palmer_values[3] zindex = palmer_values[4] # write the PDSI values to NetCDF lock.acquire() divisions_dataset['pdsi'][div_index, :] = np.reshape( pdsi, (1, pdsi.size)) divisions_dataset['phdi'][div_index, :] = np.reshape( phdi, (1, phdi.size)) divisions_dataset['pmdi'][div_index, :] = np.reshape( pmdi, (1, pmdi.size)) divisions_dataset['scpdsi'][div_index, :] = np.reshape( pdsi, (1, scpdsi.size)) divisions_dataset['zindex'][div_index, :] = np.reshape( zindex, (1, zindex.size)) divisions_dataset.sync() lock.release() # process the SPI and SPEI at the specified month scales for months in self.scale_months: logger.info( '\tComputing SPI/SPEI/PNP at %s-month scale for division %s', months, climdiv_id) #TODO ensure that the precipitation and PET values are using the same units pylint: disable=fixme # compute SPEI/Gamma spei_gamma = indices.spei_gamma(months, precip_time_series, pet_mm=pet_time_series) # compute SPEI/Pearson spei_pearson = indices.spei_pearson( months, self.data_start_year, precip_time_series, pet_mm=pet_time_series, calibration_year_initial=self. calibration_start_year, calibration_year_final=self.calibration_end_year) # compute SPI/Gamma spi_gamma = indices.spi_gamma(precip_time_series, months) # compute SPI/Pearson spi_pearson = indices.spi_pearson( precip_time_series, months, self.data_start_year, self.calibration_start_year, self.calibration_end_year) # compute PNP pnp = indices.percentage_of_normal( precip_time_series, months, self.data_start_year, self.calibration_start_year, self.calibration_end_year) # create variable names which should correspond to the appropriate scaled index output variables scaled_name_suffix = str(months).zfill(2) spei_gamma_variable_name = 'spei_gamma_' + scaled_name_suffix spei_pearson_variable_name = 'spei_pearson_' + scaled_name_suffix spi_gamma_variable_name = 'spi_gamma_' + scaled_name_suffix spi_pearson_variable_name = 'spi_pearson_' + scaled_name_suffix pnp_variable_name = 'pnp_' + scaled_name_suffix # write the SPI, SPEI, and PNP values to NetCDF lock.acquire() divisions_dataset[spei_gamma_variable_name][ div_index, :] = np.reshape(spei_gamma, (1, spei_gamma.size)) divisions_dataset[spei_pearson_variable_name][ div_index, :] = np.reshape(spei_pearson, (1, spei_pearson.size)) divisions_dataset[spi_gamma_variable_name][ div_index, :] = np.reshape(spi_gamma, (1, spi_gamma.size)) divisions_dataset[spi_pearson_variable_name][ div_index, :] = np.reshape(spi_pearson, (1, spi_pearson.size)) divisions_dataset[pnp_variable_name][ div_index, :] = np.reshape(pnp, (1, pnp.size)) divisions_dataset.sync() lock.release()