def annual_statistics(cube, operator='mean'): """Compute annual statistics. Note that this function does not weight the annual mean if uneven time periods are present. Ie, all data inside the year are treated equally. 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 Annual statistics cube """ # TODO: Add weighting in time dimension. See iris issue 3290 # https://github.com/SciTools/iris/issues/3290 operator = get_iris_analysis_operation(operator) if not cube.coords('year'): iris.coord_categorisation.add_year(cube, 'time') return cube.aggregated_by('year', operator)
def climate_statistics(cube, operator='mean', period='full', seasons=('DJF', 'MAM', 'JJA', 'SON')): """Compute climate statistics with the specified granularity. Computes statistics for the whole dataset. It is possible to get them for the full period or with the data grouped by day, month or season Parameters ---------- cube: iris.cube.Cube input cube. operator: str, optional Select operator to apply. Available operators: 'mean', 'median', 'std_dev', 'sum', 'min', 'max', 'rms' period: str, optional Period to compute the statistic over. Available periods: 'full', 'season', 'seasonal', 'monthly', 'month', 'mon', 'daily', 'day' seasons: list or tuple of str, optional Seasons to use if needed. Defaults to ('DJF', 'MAM', 'JJA', 'SON') Returns ------- iris.cube.Cube Monthly statistics cube """ period = period.lower() if period in ('full', ): operator_method = get_iris_analysis_operation(operator) if operator_accept_weights(operator): time_weights = get_time_weights(cube) if time_weights.min() == time_weights.max(): # No weighting needed. cube = cube.collapsed('time', operator_method) else: cube = cube.collapsed('time', operator_method, weights=time_weights) else: cube = cube.collapsed('time', operator_method) return cube clim_coord = _get_period_coord(cube, period, seasons) operator = get_iris_analysis_operation(operator) clim_cube = cube.aggregated_by(clim_coord, operator) clim_cube.remove_coord('time') if clim_cube.coord(clim_coord.name()).is_monotonic(): iris.util.promote_aux_coord_to_dim_coord(clim_cube, clim_coord.name()) else: clim_cube = iris.cube.CubeList(clim_cube.slices_over( clim_coord.name())).merge_cube() cube.remove_coord(clim_coord) return clim_cube
def monthly_statistics(cube, operator='mean'): """Compute monthly statistics. Chunks time in monthly 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 Monthly statistics cube """ if not cube.coords('month_number'): iris.coord_categorisation.add_month_number(cube, 'time') if not cube.coords('year'): iris.coord_categorisation.add_year(cube, 'time') operator = get_iris_analysis_operation(operator) cube = cube.aggregated_by(['month_number', 'year'], operator) return cube
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 hourly_statistics(cube, hours, operator='mean'): """Compute hourly statistics. Chunks time in x hours periods and computes statistics over them. Parameters ---------- cube: iris.cube.Cube input cube. hours: int Number of hours per period. Must be a divisor of 24 (1, 2, 3, 4, 6, 8, 12) operator: str, optional Select operator to apply. Available operators: 'mean', 'median', 'std_dev', 'sum', 'min', 'max' Returns ------- iris.cube.Cube Hourly statistics cube """ if not cube.coords('hour_group'): iris.coord_categorisation.add_categorised_coord( cube, 'hour_group', 'time', lambda coord, value: coord.units.num2date(value).hour // hours, units='1') if not cube.coords('day_of_year'): iris.coord_categorisation.add_day_of_year(cube, 'time') if not cube.coords('year'): iris.coord_categorisation.add_year(cube, 'time') operator = get_iris_analysis_operation(operator) cube = cube.aggregated_by(['hour_group', 'day_of_year', 'year'], operator) cube.remove_coord('hour_group') cube.remove_coord('day_of_year') cube.remove_coord('year') return cube
def decadal_statistics(cube, operator='mean'): """ Compute decadal statistics. Note that this function does not weight the decadal mean if uneven time periods are present. Ie, all data inside the decade are treated equally. 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 Decadal statistics cube """ # TODO: Add weighting in time dimension. See iris issue 3290 # https://github.com/SciTools/iris/issues/3290 operator = get_iris_analysis_operation(operator) if not cube.coords('decade'): def get_decade(coord, value): """Categorize time coordinate into decades.""" date = coord.units.num2date(value) return date.year - date.year % 10 iris.coord_categorisation.add_categorised_coord( cube, 'decade', 'time', get_decade) return cube.aggregated_by('decade', operator)
def seasonal_statistics(cube, operator='mean', seasons=('DJF', 'MAM', 'JJA', 'SON')): """Compute seasonal statistics. Chunks time seasons 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' seasons: list or tuple of str, optional Seasons to build. Available: ('DJF', 'MAM', 'JJA', SON') (default) and all sequentially correct combinations holding every month of a year: e.g. ('JJAS','ONDJFMAM'), or less in case of prior season extraction. Returns ------- iris.cube.Cube Seasonal statistic cube """ seasons = tuple([sea.upper() for sea in seasons]) if any([len(sea) < 2 for sea in seasons]): raise ValueError( f"Minimum of 2 month is required per Seasons: {seasons}.") if not cube.coords('clim_season'): iris.coord_categorisation.add_season(cube, 'time', name='clim_season', seasons=seasons) else: old_seasons = list(set(cube.coord('clim_season').points)) if not all([osea in seasons for osea in old_seasons]): raise ValueError( f"Seasons {seasons} do not match prior season extraction " f"{old_seasons}.") if not cube.coords('season_year'): iris.coord_categorisation.add_season_year(cube, 'time', name='season_year', seasons=seasons) 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 [29, 31] days makes this calendar-independent # the only season this could not work is 'F' but this raises an # ValueError def spans_full_season(cube): """Check for all month present in the season. Parameters ---------- cube: iris.cube.Cube input cube. Returns ------- bool truth statement if time bounds are within (month*29, month*31) """ time = cube.coord('time') num_days = [(tt.bounds[0, 1] - tt.bounds[0, 0]) for tt in time] seasons = cube.coord('clim_season').points tar_days = [(len(sea) * 29, len(sea) * 31) for sea in seasons] return [dt[0] <= dn <= dt[1] for dn, dt in zip(num_days, tar_days)] full_seasons = spans_full_season(cube) return cube[full_seasons]