def daily_statistics(cube, operator='mean'): """Compute daily statistics. Chunks time in daily 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 Daily statistics cube """ 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(['day_of_year', 'year'], operator) cube.remove_coord('day_of_year') cube.remove_coord('year') return cube
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 test_bounded_level(self): cube = iris.load_cube( tests.get_data_path(("GRIB", "uk_t", "uk_t.grib2"))) # Changing pressure to altitude due to grib api bug: # https://github.com/SciTools/iris/pull/715#discussion_r5901538 cube.remove_coord("pressure") cube.add_aux_coord( iris.coords.AuxCoord( 1030.0, long_name="altitude", units="m", bounds=np.array([111.0, 1949.0]), )) with self.temp_filename(".grib2") as testfile: iris.save(cube, testfile) with open(testfile, "rb") as saved_file: g = gribapi.grib_new_from_file(saved_file) self.assertEqual( gribapi.grib_get_double(g, "scaledValueOfFirstFixedSurface"), 111.0, ) self.assertEqual( gribapi.grib_get_double(g, "scaledValueOfSecondFixedSurface"), 1949.0, )
def test_missing_coords(self): cube = iris.tests.stock.realistic_4d() cube.remove_coord('time') cube.remove_coord('model_level_number') self.assertString(repr(cube), ('cdm', 'str_repr', 'missing_coords_cube.repr.txt')) self.assertString(str(cube), ('cdm', 'str_repr', 'missing_coords_cube.str.txt'))
def test_fully_wrapped_not_circular(self): cube = stock.lat_lon_cube() new_long = cube.coord('longitude').copy( cube.coord('longitude').points + 710) cube.remove_coord('longitude') cube.add_dim_coord(new_long, 1) interpolator = LinearInterpolator(cube, ['longitude']) res = interpolator([-10]) self.assertArrayEqual(res.data, cube[:, 1].data)
def test_fully_wrapped_not_circular(self): cube = stock.lat_lon_cube() new_long = cube.coord("longitude").copy( cube.coord("longitude").points + 710) cube.remove_coord("longitude") cube.add_dim_coord(new_long, 1) interpolator = RectilinearInterpolator(cube, ["longitude"], LINEAR, EXTRAPOLATE) res = interpolator([-10]) self.assertArrayEqual(res.data, cube[:, 1].data)
def test_irregular(self): cube = self._load_basic() lat_coord = cube.coord("latitude") cube.remove_coord("latitude") new_lats = np.append(lat_coord.points[:-1], lat_coord.points[0]) # Irregular cube.add_aux_coord(iris.coords.AuxCoord(new_lats, "latitude", units="degrees", coord_system=lat_coord.coord_system), 0) saved_grib = iris.util.create_temp_filename(suffix='.grib2') self.assertRaises(iris.exceptions.TranslationError, iris.save, cube, saved_grib) os.remove(saved_grib)
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 test_anonymous(self): cubes = [] y = (0, 2) cubes.append(_make_cube((0, 2), y, 1)) cubes.append(_make_cube((2, 4), y, 2)) cube = _make_cube((4, 6), y, 3) cube.remove_coord('x') cubes.append(cube) result = concatenate(cubes) self.assertCML(result, ('concatenate', 'concat_anonymous.cml')) self.assertEqual(len(result), 2) self.assertEqual(result[0].shape, (2, 4)) self.assertEqual(result[1].shape, (2, 2))
def test_missing_latlon(self): cube = self.cube.copy() cube.remove_coord('grid_latitude') with self.assertRaises(ValueError): iris.analysis.cartography.project(cube, self.target_proj) cube = self.cube.copy() cube.remove_coord('grid_longitude') with self.assertRaises(ValueError): iris.analysis.cartography.project(cube, self.target_proj) self.cube.remove_coord('grid_longitude') self.cube.remove_coord('grid_latitude') with self.assertRaises(ValueError): iris.analysis.cartography.project(self.cube, self.target_proj)
def test_one_cube_has_anon_dim(self): cubes = [] y = (0, 2) cubes.append(_make_cube((0, 2), y, 1)) cubes.append(_make_cube((2, 4), y, 2)) cube = _make_cube((4, 6), y, 3) cube.remove_coord("x") cubes.append(cube) result = concatenate(cubes) self.assertCML(result, ("concatenate", "concat_anonymous.cml")) self.assertEqual(len(result), 2) self.assertEqual(result[0].shape, (2, 4)) self.assertEqual(result[1].shape, (2, 2))
def test_bounded_level(self): cube = iris.load_cube(tests.get_data_path(("GRIB", "uk_t", "uk_t.grib2"))) # Changing pressure to altitude due to grib api bug: # https://github.com/SciTools/iris/pull/715#discussion_r5901538 cube.remove_coord("pressure") cube.add_aux_coord( iris.coords.AuxCoord(1030.0, long_name="altitude", units="m", bounds=np.array([111.0, 1949.0])) ) with self.temp_filename(".grib2") as testfile: iris.save(cube, testfile) with open(testfile, "rb") as saved_file: g = gribapi.grib_new_from_file(saved_file) self.assertEqual(gribapi.grib_get_double(g, "scaledValueOfFirstFixedSurface"), 111.0) self.assertEqual(gribapi.grib_get_double(g, "scaledValueOfSecondFixedSurface"), 1949.0)
def _compute_anomalies(cube, reference, period, seasons): cube_coord = _get_period_coord(cube, period, seasons) ref_coord = _get_period_coord(reference, period, seasons) data = cube.core_data() cube_time = cube.coord('time') ref = {} for ref_slice in reference.slices_over(ref_coord): ref[ref_slice.coord(ref_coord).points[0]] = ref_slice.core_data() cube_coord_dim = cube.coord_dims(cube_coord)[0] slicer = [slice(None)] * len(data.shape) new_data = [] for i in range(cube_time.shape[0]): slicer[cube_coord_dim] = i new_data.append(data[tuple(slicer)] - ref[cube_coord.points[i]]) data = da.stack(new_data, axis=cube_coord_dim) cube = cube.copy(data) cube.remove_coord(cube_coord) return cube
def test_multi_d(self): cube = iris.tests.stock.realistic_4d() # TODO: Re-instate surface_altitude & hybrid-height once we're # using the post-CF test results. cube.remove_aux_factory(cube.aux_factories[0]) cube.remove_coord('surface_altitude') self.assertCML(cube, ('cube_collapsed', 'original.cml')) # Compare 2-stage collapsing with a single stage collapse over 2 Coords. self.collapse_test_common(cube, 'grid_latitude', 'grid_longitude', decimal=1) self.collapse_test_common(cube, 'grid_longitude', 'grid_latitude', decimal=1) self.collapse_test_common(cube, 'time', 'grid_latitude', decimal=1) self.collapse_test_common(cube, 'grid_latitude', 'time', decimal=1) self.collapse_test_common(cube, 'time', 'grid_longitude', decimal=1) self.collapse_test_common(cube, 'grid_longitude', 'time', decimal=1) self.collapse_test_common(cube, 'grid_latitude', 'model_level_number', decimal=1) self.collapse_test_common(cube, 'model_level_number', 'grid_latitude', decimal=1) self.collapse_test_common(cube, 'grid_longitude', 'model_level_number', decimal=1) self.collapse_test_common(cube, 'model_level_number', 'grid_longitude', decimal=1) self.collapse_test_common(cube, 'time', 'model_level_number', decimal=1) self.collapse_test_common(cube, 'model_level_number', 'time', decimal=1) self.collapse_test_common(cube, 'model_level_number', 'time', decimal=1) self.collapse_test_common(cube, 'time', 'model_level_number', decimal=1) # Collapse 3 things at once. triple_collapse = cube.collapsed(['model_level_number', 'time', 'grid_longitude'], iris.analysis.MEAN) self.assertCMLApproxData(triple_collapse, ('cube_collapsed', 'triple_collapse_ml_pt_lon.cml'), decimal=1) triple_collapse = cube.collapsed(['grid_latitude', 'model_level_number', 'time'], iris.analysis.MEAN) self.assertCMLApproxData(triple_collapse, ('cube_collapsed', 'triple_collapse_lat_ml_pt.cml'), decimal=1) # Ensure no side effects self.assertCML(cube, ('cube_collapsed', 'original.cml'))
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 test_no_coord(self): cube = _point_cube(59, iris.unit.CALENDAR_GREGORIAN) constraint = TimeRangeConstraint(day_of_year=[(3, 1), (9, 30)]) cube.remove_coord('time') with self.assertRaises(AttributeError): result = self.constraint.extract(cube)
def low_res_4d(): cube = iris.tests.stock.realistic_4d_no_derived() cube = cube[0:2, 0:3, ::10, ::10] cube.remove_coord('surface_altitude') return cube
def regrid_time(cube, frequency): """Align time axis for cubes so they can be subtracted. Operations on time units, time points and auxiliary coordinates so that any cube from cubes can be subtracted from any other cube from cubes. Currently this function supports yearly (frequency=yr), monthly (frequency=mon), daily (frequency=day), 6-hourly (frequency=6hr), 3-hourly (frequency=3hr) and hourly (frequency=1hr) data time frequencies. Parameters ---------- cube: iris.cube.Cube input cube. frequency: str data frequency: mon, day, 1hr, 3hr or 6hr Returns ------- iris.cube.Cube cube with converted time axis and units. """ # standardize time points time_c = [cell.point for cell in cube.coord('time').cells()] if frequency == 'yr': time_cells = [datetime.datetime(t.year, 7, 1, 0, 0, 0) for t in time_c] elif frequency == 'mon': time_cells = [ datetime.datetime(t.year, t.month, 15, 0, 0, 0) for t in time_c ] elif frequency == 'day': time_cells = [ datetime.datetime(t.year, t.month, t.day, 0, 0, 0) for t in time_c ] elif frequency == '1hr': time_cells = [ datetime.datetime(t.year, t.month, t.day, t.hour, 0, 0) for t in time_c ] elif frequency == '3hr': time_cells = [ datetime.datetime(t.year, t.month, t.day, t.hour - t.hour % 3, 0, 0) for t in time_c ] elif frequency == '6hr': time_cells = [ datetime.datetime(t.year, t.month, t.day, t.hour - t.hour % 6, 0, 0) for t in time_c ] cube.coord('time').points = [ cube.coord('time').units.date2num(cl) for cl in time_cells ] # uniformize bounds cube.coord('time').bounds = None cube.coord('time').bounds = _get_time_bounds(cube.coord('time'), frequency) # remove aux coords that will differ reset_aux = ['day_of_month', 'day_of_year'] for auxcoord in cube.aux_coords: if auxcoord.long_name in reset_aux: cube.remove_coord(auxcoord) # re-add the converted aux coords iris.coord_categorisation.add_day_of_month(cube, cube.coord('time'), name='day_of_month') iris.coord_categorisation.add_day_of_year(cube, cube.coord('time'), name='day_of_year') return cube
def test_missing_latlon(self): cube = low_res_4d() cube.remove_coord('grid_longitude') cube.remove_coord('grid_latitude') with self.assertRaises(ValueError): project(cube, ROBINSON)
def fix_bpch2coards(cube, field, filename): """ An Iris load callback for properly loading the NetCDF files created by BPCH2COARDS (GAMAP v2-17+). """ global _coordcache2 # units units = field.units try: cube.units = units except ValueError: # Try to get equivalent units compatible with udunits. # Store original unit as cube attribute conform_units = ctm2cf.get_cfcompliant_units(units) try: cube.units = conform_units except ValueError: warnings.warn("Invalid udunits2 '{0}'".format(units)) cube.attributes["ctm_units"] = units # a hack for keeping cube's long_name but show var_name in cube summary iris.std_names.STD_NAMES[cube.var_name] = {'canonical_units': cube.units} cube.standard_name = cube.var_name # attributes # TODO: don't remove all attributes cube.attributes.clear() # longitude coordinate (non strictly monotonic) degrees -> degrees_east try: lon = cube.coord('longitude') lon_dim = cube.coord_dims(lon)[0] cache_key = 'longitude', filename if _coordcache2.get(cache_key) is None: west_ind = np.nonzero(lon.points >= 180.) lon.points[west_ind] = -1. * (360. - lon.points) lon.units = 'degrees_east' _coordcache2[cache_key] = iris.coords.DimCoord.from_coord(lon) cube.remove_coord(lon) cube.add_dim_coord(_coordcache2[cache_key], lon_dim) except iris.exceptions.CoordinateNotFoundError: pass # levels coordinate # 'sigma_level' depreciated in the CF standard (not supported by UDUNITS) try: lev = cube.coord('Eta Centers') lev_dim = cube.coord_dims(lev)[0] lev_std_name = 'atmosphere_hybrid_sigma_pressure_coordinate' cache_key = lev_std_name, filename if _coordcache2.get(cache_key) is None: lev.standard_name = lev_std_name lev.units = iris.unit.Unit('1') d = nc.Dataset(filename) elev = d.variables['edge'][:] lev.bounds = np.column_stack((elev[:-1], elev[1:])) _coordcache2[cache_key] = iris.coords.DimCoord.from_coord(lev) cube.remove_coord(lev) cube.add_dim_coord(_coordcache2[cache_key], lev_dim) except iris.exceptions.CoordinateNotFoundError: pass # time: dimension -> scalar coordinate (+ add bounds) try: time_coord = cube.coord('time') time_dim = cube.coord_dims(time_coord)[0] with iris.FUTURE.context(cell_datetime_objects=True): tstart = time_coord.cell(0).point delta_t = time_coord.attributes.pop('delta_t') tend = tstart + timeutil.strp_relativedelta(delta_t) time_coord.bounds = [timeutil.time2tau(tstart), timeutil.time2tau(tend)] if cube.shape[time_dim] == 1: slices_dims = [d for d in range(cube.ndim) if d != time_dim] return cube.slices(slices_dims).next() except iris.exceptions.CoordinateNotFoundError: pass
def test_multi_d(self): cube = iris.tests.stock.realistic_4d() # TODO: Re-instate surface_altitude & hybrid-height once we're # using the post-CF test results. cube.remove_aux_factory(cube.aux_factories[0]) cube.remove_coord('surface_altitude') self.assertCML(cube, ('cube_collapsed', 'original.cml')) # Compare 2-stage collapsing with a single stage collapse # over 2 Coords. self.collapse_test_common(cube, 'grid_latitude', 'grid_longitude', rtol=1e-05) self.collapse_test_common(cube, 'grid_longitude', 'grid_latitude', rtol=1e-05) self.collapse_test_common(cube, 'time', 'grid_latitude', rtol=1e-05) self.collapse_test_common(cube, 'grid_latitude', 'time', rtol=1e-05) self.collapse_test_common(cube, 'time', 'grid_longitude', rtol=1e-05) self.collapse_test_common(cube, 'grid_longitude', 'time', rtol=1e-05) self.collapse_test_common(cube, 'grid_latitude', 'model_level_number', rtol=5e-04) self.collapse_test_common(cube, 'model_level_number', 'grid_latitude', rtol=5e-04) self.collapse_test_common(cube, 'grid_longitude', 'model_level_number', rtol=5e-04) self.collapse_test_common(cube, 'model_level_number', 'grid_longitude', rtol=5e-04) self.collapse_test_common(cube, 'time', 'model_level_number', rtol=5e-04) self.collapse_test_common(cube, 'model_level_number', 'time', rtol=5e-04) self.collapse_test_common(cube, 'model_level_number', 'time', rtol=5e-04) self.collapse_test_common(cube, 'time', 'model_level_number', rtol=5e-04) # Collapse 3 things at once. triple_collapse = cube.collapsed(['model_level_number', 'time', 'grid_longitude'], iris.analysis.MEAN) self.assertCMLApproxData(triple_collapse, ('cube_collapsed', ('triple_collapse_ml_pt_' 'lon.cml')), rtol=5e-04) triple_collapse = cube.collapsed(['grid_latitude', 'model_level_number', 'time'], iris.analysis.MEAN) self.assertCMLApproxData(triple_collapse, ('cube_collapsed', ('triple_collapse_lat_ml' '_pt.cml')), rtol=0.05) # KNOWN PROBLEM: the previous 'rtol' is very large. # Numpy 1.10 and 1.11 give significantly different results here. # This may relate to known problems with summing over large arrays, # which were largely fixed in numpy 1.9 but still occur in some cases, # as-of numpy 1.11. # Ensure no side effects self.assertCML(cube, ('cube_collapsed', 'original.cml'))
def test_missing_lon(self): cube = low_res_4d() cube.remove_coord('grid_longitude') with self.assertRaises(ValueError): project(cube, ROBINSON)