def extract_month(cube, month): """Slice cube to get only the data belonging to a specific month. Parameters ---------- cube: iris.cube.Cube Original data month: int Month to extract as a number from 1 to 12 Returns ------- iris.cube.Cube data cube for specified month. Raises ------ ValueError if requested month is not present in the cube """ if month not in range(1, 13): raise ValueError('Please provide a month number between 1 and 12.') if not cube.coords('month_number'): iris.coord_categorisation.add_month_number(cube, 'time', name='month_number') result = cube.extract(iris.Constraint(month_number=month)) if result is None: raise ValueError(f'Month {month!r} not present in cube {cube}') return result
def extract_season(cube, season): """Slice cube to get only the data belonging to a specific season. Parameters ---------- cube: iris.cube.Cube Original data season: str Season to extract. Available: DJF, MAM, JJA, SON and all sequentially correct combinations: e.g. JJAS Returns ------- iris.cube.Cube data cube for specified season. Raises ------ ValueError if requested season is not present in the cube """ season = season.upper() allmonths = 'JFMAMJJASOND' * 2 if season not in allmonths: raise ValueError(f"Unable to extract Season {season} " f"combination of months not possible.") sstart = allmonths.index(season) res_season = allmonths[sstart + len(season):sstart + 12] seasons = [season, res_season] coords_to_remove = [] if not cube.coords('clim_season'): iris.coord_categorisation.add_season(cube, 'time', name='clim_season', seasons=seasons) coords_to_remove.append('clim_season') if not cube.coords('season_year'): iris.coord_categorisation.add_season_year(cube, 'time', name='season_year', seasons=seasons) coords_to_remove.append('season_year') result = cube.extract(iris.Constraint(clim_season=season)) for coord in coords_to_remove: cube.remove_coord(coord) if result is None: raise ValueError(f'Season {season!r} not present in cube {cube}') return result
def seasonal_statistics(cube, operator='mean'): """ Compute seasonal statistics. Chunks time in 3-month periods and computes statistics over them; Parameters ---------- cube: iris.cube.Cube input cube. operator: str, optional Select operator to apply. Available operators: 'mean', 'median', 'std_dev', 'sum', 'min', 'max', 'rms' Returns ------- iris.cube.Cube Seasonal statistic cube """ if not cube.coords('clim_season'): iris.coord_categorisation.add_season(cube, 'time', name='clim_season') if not cube.coords('season_year'): iris.coord_categorisation.add_season_year(cube, 'time', name='season_year') operator = get_iris_analysis_operation(operator) cube = cube.aggregated_by(['clim_season', 'season_year'], operator) # CMOR Units are days so we are safe to operate on days # Ranging on [90, 92] days makes this calendar-independent def spans_three_months(time): """ Check for three months. Parameters ---------- time: iris.DimCoord cube time coordinate Returns ------- bool truth statement if time bounds are 90+2 days. """ return 90 <= (time.bound[1] - time.bound[0]).days <= 92 three_months_bound = iris.Constraint(time=spans_three_months) return cube.extract(three_months_bound)
def resample_hours(cube, interval, offset=0): """Convert x-hourly data to y-hourly by eliminating extra timesteps. Convert x-hourly data to y-hourly (y > x) by eliminating the extra timesteps. This is intended to be used only with instantaneous values. For example: - resample_hours(cube, interval=6): Six-hourly intervals at 0:00, 6:00, 12:00, 18:00. - resample_hours(cube, interval=6, offset=3): Six-hourly intervals at 3:00, 9:00, 15:00, 21:00. - resample_hours(cube, interval=12, offset=6): Twelve-hourly intervals at 6:00, 18:00. Parameters ---------- cube: iris.cube.Cube Input cube. interval: int The period (hours) of the desired data. offset: int, optional The firs hour (hours) of the desired data. Returns ------- iris.cube.Cube Cube with the new frequency. Raises ------ ValueError: The specified frequency is not a divisor of 24. """ allowed_intervals = (1, 2, 3, 4, 6, 12) if interval not in allowed_intervals: raise ValueError( f'The number of hours must be one of {allowed_intervals}') if offset >= interval: raise ValueError(f'The offset ({offset}) must be lower than ' f'the interval ({interval})') time = cube.coord('time') cube_period = time.cell(1).point - time.cell(0).point if cube_period.total_seconds() / 3600 >= interval: raise ValueError(f"Data period ({cube_period}) should be lower than " f"the interval ({interval})") hours = range(0 + offset, 24, interval) select_hours = iris.Constraint(time=lambda cell: cell.point.hour in hours) return cube.extract(select_hours)
def resample_time(cube, month=None, day=None, hour=None): """Change frequency of data by resampling it. Converts data from one frequency to another by extracting the timesteps that match the provided month, day and/or hour. This is meant to be used with instantaneous values when computing statistics is not desired. For example: - resample_time(cube, hour=6): Daily values taken at 6:00. - resample_time(cube, day=15, hour=6): Monthly values taken at 15th 6:00. - resample_time(cube, month=6): Yearly values, taking in June - resample_time(cube, month=6, day=1): Yearly values, taking 1st June The condition must yield only one value per interval: the last two samples above will produce yearly data, but the first one is meant to be used to sample from monthly output and the second one will work better with daily. Parameters ---------- cube: iris.cube.Cube Input cube. month: int, optional Month to extract day: int, optional Day to extract hour: int, optional Hour to extract Returns ------- iris.cube.Cube Cube with the new frequency. """ def compare(cell): date = cell.point if month is not None and month != date.month: return False if day is not None and day != date.day: return False if hour is not None and hour != date.hour: return False return True return cube.extract(iris.Constraint(time=compare))
def extract_season(cube, season): """ Slice cube to get only the data belonging to a specific season. Parameters ---------- cube: iris.cube.Cube Original data season: str Season to extract. Available: DJF, MAM, JJA, SON Returns ------- iris.cube.Cube data cube for specified season. """ if not cube.coords('clim_season'): iris.coord_categorisation.add_season(cube, 'time', name='clim_season') if not cube.coords('season_year'): iris.coord_categorisation.add_season_year(cube, 'time', name='season_year') return cube.extract(iris.Constraint(clim_season=season.lower()))
def extract_time(cube, start_year, start_month, start_day, end_year, end_month, end_day): """Extract a time range from a cube. Given a time range passed in as a series of years, months and days, it returns a time-extracted cube with data only within the specified time range. Parameters ---------- cube: iris.cube.Cube input cube. start_year: int start year start_month: int start month start_day: int start day end_year: int end year end_month: int end month end_day: int end day Returns ------- iris.cube.Cube Sliced cube. Raises ------ ValueError if time ranges are outside the cube time limits """ time_coord = cube.coord('time') time_units = time_coord.units if time_units.calendar == '360_day': if start_day > 30: start_day = 30 if end_day > 30: end_day = 30 t_1 = PartialDateTime(year=int(start_year), month=int(start_month), day=int(start_day)) t_2 = PartialDateTime(year=int(end_year), month=int(end_month), day=int(end_day)) constraint = iris.Constraint(time=lambda t: t_1 <= t.point < t_2) cube_slice = cube.extract(constraint) if cube_slice is None: raise ValueError( f"Time slice {start_year:0>4d}-{start_month:0>2d}-{start_day:0>2d}" f" to {end_year:0>4d}-{end_month:0>2d}-{end_day:0>2d} is outside " f"cube time bounds {time_coord.cell(0)} to {time_coord.cell(-1)}.") # Issue when time dimension was removed when only one point as selected. if cube_slice.ndim != cube.ndim: if cube_slice.coord('time') == time_coord: logger.debug('No change needed to time.') return cube return cube_slice