Example #1
0
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
Example #2
0
    def test_coord_conversion(self):
        cube = iris.tests.stock.realistic_4d()
        
        # Single string
        self.assertEquals(len(cube._as_list_of_coords('grid_longitude')), 1)
        
        # List of string and unicode
        self.assertEquals(len(cube._as_list_of_coords(['grid_longitude', u'grid_latitude'], )), 2)
        
        # Coord object(s)
        lat = cube.coords("grid_latitude")[0]
        lon = cube.coords("grid_longitude")[0]
        self.assertEquals(len(cube._as_list_of_coords(lat)), 1)
        self.assertEquals(len(cube._as_list_of_coords([lat, lon])), 2)
        
        # Mix of string-like and coord
        self.assertEquals(len(cube._as_list_of_coords(["grid_latitude", lon])), 2)

        # Empty list
        self.assertEquals(len(cube._as_list_of_coords([])), 0)
        
        # Invalid coords
        invalid_choices = [iris.analysis.MEAN, # Caused by mixing up argument order in call to cube.collasped for example
                           None,
                           ['grid_latitude', None],
                           [lat, None],
                          ]

        for coords in invalid_choices:
            with self.assertRaises(TypeError):
                cube._as_list_of_coords(coords)
Example #3
0
def _get_plot_defn(cube, mode, ndims=2):
    """
    Return data and plot-axis coords given a cube & a mode of either
    POINT_MODE or BOUND_MODE.

    """
    if cube.ndim != ndims:
        msg = 'Cube must be %s-dimensional. Got %s dimensions.'
        raise ValueError(msg % (ndims, cube.ndim))

    # Start by taking the DimCoords from each dimension.
    coords = [None] * ndims
    for dim_coord in cube.dim_coords:
        dim = cube.coord_dims(dim_coord)[0]
        coords[dim] = dim_coord

    # When appropriate, restrict to 1D with bounds.
    if mode == iris.coords.BOUND_MODE:
        coords = list(map(_valid_bound_coord, coords))

    def guess_axis(coord):
        axis = None
        if coord is not None:
            axis = iris.util.guess_coord_axis(coord)
        return axis

    # Allow DimCoords in aux_coords to fill in for missing dim_coords.
    for dim, coord in enumerate(coords):
        if coord is None:
            aux_coords = cube.coords(dimensions=dim)
            aux_coords = [coord for coord in aux_coords
                          if isinstance(coord, iris.coords.DimCoord)]
            if aux_coords:
                key_func = lambda coord: coord._as_defn()
                aux_coords.sort(key=key_func)
                coords[dim] = aux_coords[0]

    if mode == iris.coords.POINT_MODE:
        # Allow multi-dimensional aux_coords to override the dim_coords
        # along the Z axis. This results in a preference for using the
        # derived altitude over model_level_number or level_height.
        # Limit to Z axis to avoid preferring latitude over grid_latitude etc.
        axes = list(map(guess_axis, coords))
        axis = 'Z'
        if axis in axes:
            for coord in cube.coords(dim_coords=False):
                if max(coord.shape) > 1 and \
                        iris.util.guess_coord_axis(coord) == axis:
                    coords[axes.index(axis)] = coord

    # Re-order the coordinates to achieve the preferred
    # horizontal/vertical associations.
    def sort_key(coord):
        order = {'X': 2, 'T': 1, 'Y': -1, 'Z': -2}
        axis = guess_axis(coord)
        return (order.get(axis, 0), coord and coord.name())
    sorted_coords = sorted(coords, key=sort_key)

    transpose = (sorted_coords != coords)
    return PlotDefn(sorted_coords, transpose)
Example #4
0
    def test_coord_conversion(self):
        cube = iris.tests.stock.realistic_4d()
        
        # Single string
        self.assertEqual(len(cube._as_list_of_coords('grid_longitude')), 1)
        
        # List of string and unicode
        self.assertEqual(len(cube._as_list_of_coords(['grid_longitude',
                                                      u'grid_latitude'], )), 2)
        
        # Coord object(s)
        lat = cube.coords("grid_latitude")[0]
        lon = cube.coords("grid_longitude")[0]
        self.assertEqual(len(cube._as_list_of_coords(lat)), 1)
        self.assertEqual(len(cube._as_list_of_coords([lat, lon])), 2)
        
        # Mix of string-like and coord
        self.assertEqual(len(cube._as_list_of_coords(['grid_latitude', lon])),
                         2)

        # Empty list
        self.assertEqual(len(cube._as_list_of_coords([])), 0)
        
        # Invalid coords
        invalid_choices = [iris.analysis.MEAN, # Caused by mixing up argument order in call to cube.collasped for example
                           None,
                           ['grid_latitude', None],
                           [lat, None],
                          ]

        for coords in invalid_choices:
            with self.assertRaises(TypeError):
                cube._as_list_of_coords(coords)
Example #5
0
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)
Example #6
0
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
Example #7
0
def intersection_of_cubes(cube, other_cube):
    """
    Return the two Cubes of intersection given two Cubes.

    .. note:: The intersection of cubes function will ignore all single valued
        coordinates in checking the intersection.

    Args:

    * cube:
        An instance of :class:`iris.cube.Cube`.
    * other_cube:
        An instance of :class:`iris.cube.Cube`.

    Returns:
        A pair of :class:`iris.cube.Cube` instances in a tuple corresponding
        to the original cubes restricted to their intersection.

    """
    # Take references of the original cubes (which will be copied when
    # slicing later).
    new_cube_self = cube
    new_cube_other = other_cube

    # This routine has not been written to cope with multi-dimensional
    # coordinates.
    for coord in cube.coords() + other_cube.coords():
        if coord.ndim != 1:
            raise iris.exceptions.CoordinateMultiDimError(coord)

    coord_comp = iris.analysis._dimensional_metadata_comparison(
        cube, other_cube)

    if coord_comp["ungroupable_and_dimensioned"]:
        raise ValueError("Cubes do not share all coordinates in common, "
                         "cannot intersect.")

    # cubes must have matching coordinates
    for coord in cube.coords():
        other_coord = other_cube.coord(coord)

        # Only intersect coordinates which are different, single values
        # coordinates may differ.
        if coord.shape[0] > 1 and coord != other_coord:
            intersected_coord = coord.intersect(other_coord)
            new_cube_self = new_cube_self.subset(intersected_coord)
            new_cube_other = new_cube_other.subset(intersected_coord)

    return new_cube_self, new_cube_other
Example #8
0
def _get_period_coord(cube, period):
    """Get periods."""
    if period in ['daily', 'day']:
        if not cube.coords('day_of_year'):
            iris.coord_categorisation.add_day_of_year(cube, 'time')
        return cube.coord('day_of_year')
    if period in ['monthly', 'month', 'mon']:
        if not cube.coords('month_number'):
            iris.coord_categorisation.add_month_number(cube, 'time')
        return cube.coord('month_number')
    if period in ['seasonal', 'season']:
        if not cube.coords('season_number'):
            iris.coord_categorisation.add_season_number(cube, 'time')
        return cube.coord('season_number')
    raise ValueError(f"Period '{period}' not supported")
Example #9
0
def intersection_of_cubes(cube, other_cube):
    """
    Return the two Cubes of intersection given two Cubes.

    .. note:: The intersection of cubes function will ignore all single valued
        coordinates in checking the intersection.

    Args:

    * cube:
        An instance of :class:`iris.cube.Cube`.
    * other_cube:
        An instance of :class:`iris.cube.Cube`.

    Returns:
        A pair of :class:`iris.cube.Cube` instances in a tuple corresponding
        to the original cubes restricted to their intersection.

    """
    # Take references of the original cubes (which will be copied when
    # slicing later).
    new_cube_self = cube
    new_cube_other = other_cube

    # This routine has not been written to cope with multi-dimensional
    # coordinates.
    for coord in cube.coords() + other_cube.coords():
        if coord.ndim != 1:
            raise iris.exceptions.CoordinateMultiDimError(coord)

    coord_comp = iris.analysis.coord_comparison(cube, other_cube)

    if coord_comp['ungroupable_and_dimensioned']:
        raise ValueError('Cubes do not share all coordinates in common, '
                         'cannot intersect.')

    # cubes must have matching coordinates
    for coord in cube.coords():
        other_coord = other_cube.coord(coord)

        # Only intersect coordinates which are different, single values
        # coordinates may differ.
        if coord.shape[0] > 1 and coord != other_coord:
            intersected_coord = coord.intersect(other_coord)
            new_cube_self = new_cube_self.subset(intersected_coord)
            new_cube_other = new_cube_other.subset(intersected_coord)

    return new_cube_self, new_cube_other
Example #10
0
    def _get_dim_names(self, cube):
        """
        Determine suitable CF-netCDF data dimension names.

        Args:

        * cube (:class:`iris.cube.Cube`) or cubelist
          (:class:`iris.cube.CubeList`):
            A :class:`iris.cube.Cube`, :class:`iris.cube.CubeList` or list of
            cubes to be saved to a netCDF file.

        Returns:
            List of dimension names with length equal the number of dimensions
            in the cube.

        """
        dimension_names = []
        for dim in xrange(cube.ndim):
            coords = cube.coords(dimensions=dim, dim_coords=True)
            if coords:
                coord = coords[0]

                dim_name = self._get_coord_variable_name(cube, coord)
                # Add only dimensions that have not already been added.
                if coord not in self._dim_coords:
                    # Determine unique dimension name
                    while (dim_name in self._existing_dim or
                           dim_name in self._name_coord_map.names):
                        dim_name = self._increment_name(dim_name)

                    # Update names added, current cube dim names used and
                    # unique coordinates added.
                    self._existing_dim[dim_name] = coord.shape[0]
                    dimension_names.append(dim_name)
                    self._dim_coords.append(coord)
                else:
                    # Return the dim_name associated with the existing
                    # coordinate.
                    dim_name = self._name_coord_map.name(coord)
                    dimension_names.append(dim_name)

            else:
                # No CF-netCDF coordinates describe this data dimension.
                dim_name = 'dim%d' % dim
                if dim_name in self._existing_dim:
                    # Increment name if conflicted with one already existing.
                    if self._existing_dim[dim_name] != cube.shape[dim]:
                        while (dim_name in self._existing_dim and
                               self._existing_dim[dim_name] !=
                               cube.shape[dim] or
                               dim_name in self._name_coord_map.names):
                            dim_name = self._increment_name(dim_name)
                        # Update dictionary with new entry
                        self._existing_dim[dim_name] = cube.shape[dim]
                else:
                    # Update dictionary with new entry
                    self._existing_dim[dim_name] = cube.shape[dim]

                dimension_names.append(dim_name)
        return dimension_names
Example #11
0
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)
Example #12
0
def cube_delta(cube, coord):
    """
    Given a cube calculate the difference between each value in the given coord's direction.


    Args:

    * coord
        either a Coord instance or the unique name of a coordinate in the cube.
        If a Coord instance is provided, it does not necessarily have to exist in the cube.

    Example usage::

        change_in_temperature_wrt_pressure = cube_delta(temperature_cube, 'pressure')

    .. note:: Missing data support not yet implemented.

    """
    # handle the case where a user passes a coordinate name
    if isinstance(coord, basestring):
        coord = cube.coord(coord)

    if coord.ndim != 1:
        raise iris.exceptions.CoordinateMultiDimError(coord)

    # Try and get a coord dim
    delta_dims = cube.coord_dims(coord)
    if (coord.shape[0] == 1
            and not getattr(coord, 'circular', False)) or not delta_dims:
        raise ValueError(
            'Cannot calculate delta over "%s" as it has length of 1.' %
            coord.name())
    delta_dim = delta_dims[0]

    # Calculate the actual delta, taking into account whether the given coordinate is circular
    delta_cube_data = delta(cube.data,
                            delta_dim,
                            circular=getattr(coord, 'circular', False))

    # If the coord/dim is circular there is no change in cube shape
    if getattr(coord, 'circular', False):
        delta_cube = cube.copy(data=delta_cube_data)
    else:
        # Subset the cube to the appropriate new shape by knocking off the last row of the delta dimension
        subset_slice = [slice(None, None)] * cube.ndim
        subset_slice[delta_dim] = slice(None, -1)
        delta_cube = cube[tuple(subset_slice)]
        delta_cube.data = delta_cube_data

    # Replace the delta_dim coords with midpoints (no shape change if circular).
    for cube_coord in cube.coords(dimensions=delta_dim):
        delta_cube.replace_coord(
            _construct_midpoint_coord(cube_coord,
                                      circular=getattr(coord, 'circular',
                                                       False)))

    delta_cube.rename('change_in_%s_wrt_%s' %
                      (delta_cube.name(), coord.name()))

    return delta_cube
Example #13
0
    def _get_dim_names(self, cube):
        """
        Determine suitable CF-netCDF data dimension names.

        Args:

        * cube (:class:`iris.cube.Cube`) or cubelist
          (:class:`iris.cube.CubeList`):
            A :class:`iris.cube.Cube`, :class:`iris.cube.CubeList` or list of
            cubes to be saved to a netCDF file.

        Returns:
            List of dimension names with length equal the number of dimensions
            in the cube.

        """
        dimension_names = []
        for dim in xrange(cube.ndim):
            coords = cube.coords(dimensions=dim, dim_coords=True)
            if coords:
                coord = coords[0]

                dim_name = self._get_coord_variable_name(cube, coord)
                # Add only dimensions that have not already been added.
                if coord not in self._dim_coords:
                    # Determine unique dimension name
                    while (dim_name in self._existing_dim or
                           dim_name in self._name_coord_map.names):
                        dim_name = self._increment_name(dim_name)

                    # Update names added, current cube dim names used and
                    # unique coordinates added.
                    self._existing_dim[dim_name] = coord.shape[0]
                    dimension_names.append(dim_name)
                    self._dim_coords.append(coord)
                else:
                    # Return the dim_name associated with the existing
                    # coordinate.
                    dim_name = self._name_coord_map.name(coord)
                    dimension_names.append(dim_name)

            else:
                # No CF-netCDF coordinates describe this data dimension.
                dim_name = 'dim%d' % dim
                if dim_name in self._existing_dim:
                    # Increment name if conflicted with one already existing.
                    if self._existing_dim[dim_name] != cube.shape[dim]:
                        while (dim_name in self._existing_dim and
                               self._existing_dim[dim_name] !=
                               cube.shape[dim] or
                               dim_name in self._name_coord_map.names):
                            dim_name = self._increment_name(dim_name)
                        # Update dictionary with new entry
                        self._existing_dim[dim_name] = cube.shape[dim]
                else:
                    # Update dictionary with new entry
                    self._existing_dim[dim_name] = cube.shape[dim]

                dimension_names.append(dim_name)
        return dimension_names
Example #14
0
def _create_cf_cell_methods(cube, dimension_names):
    """Create CF-netCDF string representation of a cube cell methods."""
    cell_methods = []

    # Identify the collection of coordinates that represent CF-netCDF coordinate variables.
    cf_coordinates = cube.dim_coords
    
    for cm in cube.cell_methods:
        names = ''

        for name in cm.coord_names:
            coord = cube.coords(name)

            if coord:
                coord = coord[0]
                if coord in cf_coordinates:
                    name = dimension_names[cube.coord_dims(coord)[0]]

            names += '%s: ' % name
        
        interval = ' '.join(['interval: %s' % interval for interval in cm.intervals or []])
        comment = ' '.join(['comment: %s' % comment for comment in cm.comments or []])
        extra = ' '.join([interval, comment]).strip()
        
        if extra:
            extra = ' (%s)' % extra
            
        cell_methods.append(names + cm.method + extra)
            
    return ' '.join(cell_methods)
Example #15
0
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
Example #16
0
    def _get_horizontal_coord(cube, axis):
        """
        Gets the horizontal coordinate on the supplied cube along the
        specified axis.

        Args:

        * cube:
            An instance of :class:`iris.cube.Cube`.
        * axis:
            Locate coordinates on `cube` along this axis.

        Returns:
            The horizontal coordinate on the specified axis of the supplied
            cube.

        """
        coords = cube.coords(axis=axis, dim_coords=False)
        if len(coords) != 1:
            raise ValueError(
                "Cube {!r} must contain a single 1D {} "
                "coordinate.".format(cube.name()),
                axis,
            )
        return coords[0]
Example #17
0
def vector_coord(cube, coord_name):
    """Try to find a one-dimensional, multi-valued coord with the given name."""
    found_coord = None
    for coord in cube.coords(coord_name):
        if len(coord.shape) == 1 and coord.shape[0] > 1:
            found_coord = coord
            break
    return found_coord
Example #18
0
def scalar_coord(cube, coord_name):
    """Try to find a single-valued coord with the given name."""
    found_coord = None
    for coord in cube.coords(name=coord_name):
        if coord.shape == (1,):
            found_coord = coord
            break
    return found_coord
Example #19
0
def vector_coord(cube, coord_name):
    """Try to find a one-dimensional, multi-valued coord with the given name."""
    found_coord = None
    for coord in cube.coords(name=coord_name):
        if len(coord.shape) == 1 and coord.shape[0] > 1:
            found_coord = coord
            break
    return found_coord
Example #20
0
def scalar_coord(cube, coord_name):
    """Try to find a single-valued coord with the given name."""
    found_coord = None
    for coord in cube.coords(coord_name):
        if coord.shape == (1,):
            found_coord = coord
            break
    return found_coord
Example #21
0
def cube_delta(cube, coord):
    """
    Given a cube calculate the difference between each value in the
    given coord's direction.


    Args:

    * coord
        either a Coord instance or the unique name of a coordinate in the cube.
        If a Coord instance is provided, it does not necessarily have to
        exist in the cube.

    Example usage::

        change_in_temperature_wrt_pressure = \
cube_delta(temperature_cube, 'pressure')

    .. note:: Missing data support not yet implemented.

    """
    # handle the case where a user passes a coordinate name
    if isinstance(coord, six.string_types):
        coord = cube.coord(coord)

    if coord.ndim != 1:
        raise iris.exceptions.CoordinateMultiDimError(coord)

    # Try and get a coord dim
    delta_dims = cube.coord_dims(coord.name())
    if (coord.shape[0] == 1 and not getattr(coord, "circular", False)) or not delta_dims:
        raise ValueError("Cannot calculate delta over {!r} as it has " "length of 1.".format(coord.name()))
    delta_dim = delta_dims[0]

    # Calculate the actual delta, taking into account whether the given
    # coordinate is circular.
    delta_cube_data = delta(cube.data, delta_dim, circular=getattr(coord, "circular", False))

    # If the coord/dim is circular there is no change in cube shape
    if getattr(coord, "circular", False):
        delta_cube = cube.copy(data=delta_cube_data)
    else:
        # Subset the cube to the appropriate new shape by knocking off
        # the last row of the delta dimension.
        subset_slice = [slice(None, None)] * cube.ndim
        subset_slice[delta_dim] = slice(None, -1)
        delta_cube = cube[tuple(subset_slice)]
        delta_cube.data = delta_cube_data

    # Replace the delta_dim coords with midpoints
    # (no shape change if circular).
    for cube_coord in cube.coords(dimensions=delta_dim):
        delta_cube.replace_coord(_construct_midpoint_coord(cube_coord, circular=getattr(coord, "circular", False)))

    delta_cube.rename("change_in_{}_wrt_{}".format(delta_cube.name(), coord.name()))

    return delta_cube
Example #22
0
File: rules.py Project: cdr30/iris
def scalar_cell_method(cube, method, coord_name):
    """Try to find the given type of cell method over a single coord with the given name."""
    found_cell_method = None
    for cell_method in cube.cell_methods:
        if cell_method.method == method and len(cell_method.coord_names) == 1:
            name = cell_method.coord_names[0]
            coords = cube.coords(name)
            if len(coords) == 1:
                found_cell_method = cell_method
    return found_cell_method
Example #23
0
def scalar_cell_method(cube, method, coord_name):
    """Try to find the given type of cell method over a single coord with the given name."""
    found_cell_method = None
    for cell_method in cube.cell_methods:
        if cell_method.method == method and len(cell_method.coord_names) == 1:
            name = cell_method.coord_names[0]
            coords = cube.coords(name=name)
            if len(coords) == 1:
                found_cell_method = cell_method
    return found_cell_method
Example #24
0
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
Example #25
0
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()))
Example #26
0
    def test_axis(self):
        cube = self.t.copy()
        cube.coord("dim1").rename("latitude")
        cube.coord("dim2").rename("longitude")
        
        coords = cube.coords(axis='y')
        self.assertEqual([coord.name() for coord in coords], ['latitude'])
        
        coords = cube.coords(axis='x')
        self.assertEqual([coord.name() for coord in coords], ['longitude'])

        # Renaming shoudn't be enough
        cube.coord("an_other").rename("time")
        coords = cube.coords(axis='t')
        self.assertEqual([coord.name() for coord in coords], [])
        # Change units to "hours since ..." as it's the presence of a
        # time unit that identifies a time axis.
        cube.coord("time").units = 'hours since 1970-01-01 00:00:00'
        coords = cube.coords(axis='t')
        self.assertEqual([coord.name() for coord in coords], ['time'])
        
        coords = cube.coords(axis='z')
        self.assertEqual(coords, [])
Example #27
0
    def test_axis(self):
        cube = self.t.copy()
        cube.coord("dim1").rename("latitude")
        cube.coord("dim2").rename("longitude")
        
        coords = cube.coords(axis='y')
        self.assertEqual([coord.name() for coord in coords], ['latitude'])
        
        coords = cube.coords(axis='x')
        self.assertEqual([coord.name() for coord in coords], ['longitude'])

        # Renaming shoudn't be enough
        cube.coord("an_other").rename("time")
        coords = cube.coords(axis='t')
        self.assertEqual([coord.name() for coord in coords], [])
        # Change units to "hours since ..." as it's the presence of a
        # time unit that identifies a time axis.
        cube.coord("time").units = 'hours since 1970-01-01 00:00:00'
        coords = cube.coords(axis='t')
        self.assertEqual([coord.name() for coord in coords], ['time'])
        
        coords = cube.coords(axis='z')
        self.assertEqual(coords, [])
Example #28
0
def _get_xy_dim_coords(cube):
    """
    Return the x and y dimension coordinates from a cube.

    This function raises a ValueError if the cube does not contain one and
    only one set of x and y dimension coordinates. It also raises a ValueError
    if the identified x and y coordinates do not have coordinate systems that
    are equal.

    Args:

    * cube:
        An instance of :class:`iris.cube.Cube`.

    Returns:
        A tuple containing the cube's x and y dimension coordinates.

    """
    x_coords = cube.coords(axis='x', dim_coords=True)
    if len(x_coords) != 1:
        raise ValueError('Cube {!r} must contain a single 1D x '
                         'coordinate.'.format(cube.name()))
    x_coord = x_coords[0]

    y_coords = cube.coords(axis='y', dim_coords=True)
    if len(y_coords) != 1:
        raise ValueError('Cube {!r} must contain a single 1D y '
                         'coordinate.'.format(cube.name()))
    y_coord = y_coords[0]

    if x_coord.coord_system != y_coord.coord_system:
        raise ValueError("The cube's x ({!r}) and y ({!r}) "
                         "coordinates must have the same coordinate "
                         "system.".format(x_coord.name(), y_coord.name()))

    return x_coord, y_coord
Example #29
0
    def _create_cf_cell_methods(self, cube, dimension_names):
        """
        Create CF-netCDF string representation of a cube cell methods.

        Args:

        * cube (:class:`iris.cube.Cube`) or cubelist
          (:class:`iris.cube.CubeList`):
            A :class:`iris.cube.Cube`, :class:`iris.cube.CubeList` or list of
            cubes to be saved to a netCDF file.
        * dimension_names (list):
            Names associated with the dimensions of the cube.

        Returns:
            CF-netCDF string representation of a cube cell methods.

        """
        cell_methods = []

        # Identify the collection of coordinates that represent CF-netCDF
        # coordinate variables.
        cf_coordinates = cube.dim_coords

        for cm in cube.cell_methods:
            names = ''

            for name in cm.coord_names:
                coord = cube.coords(name)

                if coord:
                    coord = coord[0]
                    if coord in cf_coordinates:
                        name = dimension_names[cube.coord_dims(coord)[0]]

                names += '%s: ' % name

            interval = ' '.join(['interval: %s' % interval for interval in
                                 cm.intervals or []])
            comment = ' '.join(['comment: %s' % comment for comment in
                                cm.comments or []])
            extra = ' '.join([interval, comment]).strip()

            if extra:
                extra = ' (%s)' % extra

            cell_methods.append(names + cm.method + extra)

        return ' '.join(cell_methods)
Example #30
0
    def _create_cf_cell_methods(self, cube, dimension_names):
        """
        Create CF-netCDF string representation of a cube cell methods.

        Args:

        * cube (:class:`iris.cube.Cube`) or cubelist
          (:class:`iris.cube.CubeList`):
            A :class:`iris.cube.Cube`, :class:`iris.cube.CubeList` or list of
            cubes to be saved to a netCDF file.
        * dimension_names (list):
            Names associated with the dimensions of the cube.

        Returns:
            CF-netCDF string representation of a cube cell methods.

        """
        cell_methods = []

        # Identify the collection of coordinates that represent CF-netCDF
        # coordinate variables.
        cf_coordinates = cube.dim_coords

        for cm in cube.cell_methods:
            names = ''

            for name in cm.coord_names:
                coord = cube.coords(name)

                if coord:
                    coord = coord[0]
                    if coord in cf_coordinates:
                        name = dimension_names[cube.coord_dims(coord)[0]]

                names += '%s: ' % name

            interval = ' '.join(['interval: %s' % interval for interval in
                                 cm.intervals or []])
            comment = ' '.join(['comment: %s' % comment for comment in
                                cm.comments or []])
            extra = ' '.join([interval, comment]).strip()

            if extra:
                extra = ' (%s)' % extra

            cell_methods.append(names + cm.method + extra)

        return ' '.join(cell_methods)
Example #31
0
    def _get_horizontal_coord(cube, axis):
        """
        Gets the horizontal coordinate on the supplied cube along the
        specified axis.

        Args:

        * cube:
            An instance of :class:`iris.cube.Cube`.
        * axis:
            Locate coordinates on `cube` along this axis.

        Returns:
            The horizontal coordinate on the specified axis of the supplied
            cube.

        """
        coords = cube.coords(axis=axis, dim_coords=False)
        if len(coords) != 1:
            raise ValueError('Cube {!r} must contain a single 1D {} '
                             'coordinate.'.format(cube.name()), axis)
        return coords[0]
Example #32
0
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)
Example #33
0
def load_cube(paths, variable_name=None):
    """Read datasets from paths into Iris cubes.

    Combines cubes if there are more than one dataset in the same file.

    Returns a list of lists. Inner lists corresponds to the areas (in
    order), outer lists corresponds to the paths

    """

    if isinstance(paths, (str, pathlib.Path)):
        if variable_name:
            cubes = iris.load_cubes(str(paths), constraints=variable_name)
        else:
            cubes = iris.load_cubes(str(paths))
    else:
        if variable_name:
            cubes = iris.load([str(path) for path in paths],
                              constraints=variable_name)
        else:
            cubes = iris.load([str(path) for path in paths])
    # Select only the cubes with 3/4D data (time, lat, long, height)
    cubes = iris.cube.CubeList(
        [cube for cube in cubes if len(cube.coords()) >= 3])

    if len(cubes) == 0:
        return None
    equalise_attributes(cubes)
    unify_time_units(cubes)

    try:
        cube = cubes.concatenate_cube()
    except iris.exceptions.ConcatenateError as exc:
        logger.warning("%s for %s", exc, str(paths))
        logger.warning("Using only the first cube of [%s]", cubes)
        cube = cubes[
            0]  # iris.load always returns a cubelist, so just take the first element
    return cube
Example #34
0
File: maths.py Project: zklaus/iris
def _broadcast_cube_coord_data(cube, other, operation_name, dim=None):
    # What dimension are we processing?
    data_dimension = None
    if dim is not None:
        # Ensure the given dim matches the coord
        if other in cube.coords() and cube.coord_dims(other) != [dim]:
            raise ValueError("dim provided does not match dim found for coord")
        data_dimension = dim
    else:
        # Try and get a coord dim
        if other.shape != (1,):
            try:
                coord_dims = cube.coord_dims(other)
                data_dimension = coord_dims[0] if coord_dims else None
            except iris.exceptions.CoordinateNotFoundError:
                raise ValueError("Could not determine dimension for %s. "
                                 "Use %s(cube, coord, dim=dim)"
                                 % (operation_name, operation_name))

    if other.ndim != 1:
        raise iris.exceptions.CoordinateMultiDimError(other)

    if other.has_bounds():
        warnings.warn('Using {!r} with a bounded coordinate is not well '
                      'defined; ignoring bounds.'.format(operation_name))

    points = other.points

    # If the `data_dimension` is defined then shape the provided points for
    # proper array broadcasting
    if data_dimension is not None:
        points_shape = [1] * cube.ndim
        points_shape[data_dimension] = -1
        points = points.reshape(points_shape)

    return points
Example #35
0
def _broadcast_cube_coord_data(cube, other, operation_name, dim=None):
    # What dimension are we processing?
    data_dimension = None
    if dim is not None:
        # Ensure the given dim matches the coord
        if other in cube.coords() and cube.coord_dims(other) != [dim]:
            raise ValueError("dim provided does not match dim found for coord")
        data_dimension = dim
    else:
        # Try and get a coord dim
        if other.shape != (1,):
            try:
                coord_dims = cube.coord_dims(other)
                data_dimension = coord_dims[0] if coord_dims else None
            except iris.exceptions.CoordinateNotFoundError:
                raise ValueError("Could not determine dimension for %s. "
                                 "Use %s(cube, coord, dim=dim)"
                                 % (operation_name, operation_name))

    if other.ndim != 1:
        raise iris.exceptions.CoordinateMultiDimError(other)

    if other.has_bounds():
        warnings.warn('Using {!r} with a bounded coordinate is not well '
                      'defined; ignoring bounds.'.format(operation_name))

    points = other.points

    # If the `data_dimension` is defined then shape the provided points for
    # proper array broadcasting
    if data_dimension is not None:
        points_shape = [1] * cube.ndim
        points_shape[data_dimension] = -1
        points = points.reshape(points_shape)

    return points
Example #36
0
def linear(cube, sample_points, extrapolation_mode='linear'):
    """
    Return a cube of the linearly interpolated points given the desired
    sample points.
    
    Given a list of tuple pairs mapping coordinates to their desired
    values, return a cube with linearly interpolated values. If more
    than one coordinate is specified, the linear interpolation will be
    carried out in sequence, thus providing n-linear interpolation
    (bi-linear, tri-linear, etc.).
    
    .. note::
        By definition, linear interpolation requires all coordinates to
        be 1-dimensional.
    
    Args:
    
    * cube
        The cube to be interpolated.
        
    * sample_points
        List of one or more tuple pairs mapping coordinate to desired
        points to interpolate. Points may be a scalar or a numpy array
        of values.
    
    Kwargs:
    
    * extrapolation_mode - string - one of 'linear', 'nan' or 'error'
    
        * If 'linear' the point will be calculated by extending the
          gradient of closest two points.
        * If 'nan' the extrapolation point will be put as a NAN.
        * If 'error' a value error will be raised notifying of the
          attempted extrapolation.
    
    .. note::
        The datatype of the resultant cube's data and coordinates will
        updated to the data type of the incoming cube.
     
    """
    if not isinstance(cube, iris.cube.Cube):
        raise ValueError('Expecting a cube instance, got %s' % type(cube))

    if isinstance(sample_points, dict):
        warnings.warn('Providing a dictionary to specify points is deprecated. Please provide a list of (coordinate, values) pairs.')
        sample_points = sample_points.items()

    # catch the case where a user passes a single (coord/name, value) pair rather than a list of pairs
    if sample_points and not (isinstance(sample_points[0], collections.Container) and not isinstance(sample_points[0], basestring)):
        raise TypeError('Expecting the sample points to be a list of tuple pairs representing (coord, points), got a list of %s.' % type(sample_points[0]))
    
    points = []
    for (coord, values) in sample_points:
        if isinstance(coord, basestring):
            coord = cube.coord(coord)
        else:
            coord = cube.coord(coord=coord)
        points.append((coord, values))
    sample_points = points

    if len(sample_points) == 0:
        raise ValueError('Expecting a non-empty list of coord value pairs, got %r.' % sample_points)

    if cube.data.dtype.kind == 'i':
        raise ValueError("Cannot linearly interpolate a cube which has integer type data. Consider casting the "
                         "cube's data to floating points in order to continue.")

    bounds_error = (extrapolation_mode == 'error')

    # Handle an over-specified points_dict or a specification which does not describe a data dimension
    data_dimensions_requested = []
    for coord, values in sample_points:
        if coord.ndim > 1:
            raise ValueError('Cannot linearly interpolate over %s as it is multi-dimensional.' % coord.name())
        data_dim = cube.coord_dims(coord)
        if not data_dim:
            raise ValueError('Requested a point over a coordinate which does not describe a dimension (%s).' % coord.name())
        else:
            data_dim = data_dim[0]
        if data_dim in data_dimensions_requested:
            raise ValueError('Requested a point which over specifies a dimension: (%s). ' % coord.name())
        data_dimensions_requested.append(data_dim)

    # Iterate over all of the requested keys in the given points_dict calling this routine repeatedly.
    if len(sample_points) > 1:
        result = cube
        for coord, cells in sample_points:
            result = linear(result, [(coord, cells)], extrapolation_mode=extrapolation_mode)
        return result
    
    else:
        # take the single coordinate name and associated cells from the dictionary
        coord, requested_points = sample_points[0]
        
        requested_points = numpy.array(requested_points, dtype=cube.data.dtype)
        
        # build up indices so that we can quickly subset the original cube to be of the desired size
        new_cube_slices = [slice(None, None)] * cube.data.ndim
        # get this coordinate's index position (which we have already tested is not None)
        data_dim = cube.coord_dims(coord)[0]
        
        if requested_points.ndim > 0:
            # we want the interested dimension to be of len(requested_points)
            new_cube_slices[data_dim] = tuple([0] * len(requested_points))
        else:
            new_cube_slices[data_dim] = 0
        
        # Subset the original cube to get an appropriately sized cube.
        # NB. This operation will convert any DimCoords on the dimension
        # being sliced into AuxCoords. This removes the value of their
        # `circular` flags, and there's nowhere left to put it.
        new_cube = cube[tuple(new_cube_slices)]

        # now that we have got a cube at the desired location, get the data.
        if getattr(coord, 'circular', False):
            coord_slice_in_cube = [slice(None, None)] * cube.data.ndim
            coord_slice_in_cube[data_dim] = slice(0, 1)
            points = numpy.append(coord.points, coord.points[0] + numpy.array(coord.units.modulus or 0, dtype=coord.dtype))
            data = numpy.append(cube.data, cube.data[tuple(coord_slice_in_cube)], axis=data_dim)
        else:
            points = coord.points
            data = cube.data
        
        if len(points) <= 1:
            raise ValueError('Cannot linearly interpolate a coordinate (%s) with one point.' % coord.name())
        
        monotonic, direction = iris.util.monotonic(points, return_direction=True)
        if not monotonic:
            raise ValueError('Unable to linearly interpolate this cube as the coordinate "%s" is not monotonic' % coord.name())
        
        # if the coord is monotonic decreasing, then we need to flip it as SciPy's interp1d is expecting monotonic increasing.
        if direction == -1:
            points = iris.util.reverse(points, axes=0)
            data = iris.util.reverse(data, axes=data_dim)
        
        # limit the datatype of the outcoming points to be the datatype of the cube's data
        # (otherwise, interp1d will up-cast an incoming pair. i.e. (int32, float32) -> float64)
        if points.dtype.num < data.dtype.num:
            points = points.astype(data.dtype)
        
        # Now that we have subsetted the original cube, we must update all coordinates on the data dimension.
        for shared_dim_coord in cube.coords(contains_dimension=data_dim):
            if shared_dim_coord.ndim != 1:
                raise iris.exceptions.NotYetImplementedError('Linear interpolation of multi-dimensional coordinates.')
            
            new_coord = new_cube.coord(coord=shared_dim_coord)
            new_coord.bounds = None
            
            if shared_dim_coord._as_defn() != coord._as_defn():
                shared_coord_points = shared_dim_coord.points
                if getattr(coord, 'circular', False):
                    mod_val = numpy.array(shared_dim_coord.units.modulus or 0, dtype=shared_coord_points.dtype)
                    shared_coord_points = numpy.append(shared_coord_points, shared_coord_points[0] + mod_val)
                
                # If the coordinate which we were interpolating over was monotonic decreasing,
                # we need to flip this coordinate's values
                if direction == -1:
                    shared_coord_points = iris.util.reverse(shared_coord_points, axes=0)
                
                coord_points = points
                
                if shared_coord_points.dtype.num < data.dtype.num:
                    shared_coord_points = shared_coord_points.astype(data.dtype)
                
                interpolator = interpolate.interp1d(coord_points, shared_coord_points,
                                                    kind='linear', bounds_error=bounds_error)
                
                if extrapolation_mode == 'linear':
                    interpolator = iris.util.Linear1dExtrapolator(interpolator)
                
                new_coord.points = interpolator(requested_points)
            else:
                new_coord.points = requested_points
                    
        # now we can go ahead and interpolate the data
        interpolator = interpolate.interp1d(points, data, axis=data_dim,
                                            kind='linear', copy=False,
                                            bounds_error=bounds_error)
        
        if extrapolation_mode == 'linear':
            interpolator = iris.util.Linear1dExtrapolator(interpolator)
        
        new_cube.data = interpolator(requested_points)
        
        return new_cube
Example #37
0
def _map_common(draw_method_name, arg_func, mode, cube, data, *args, **kwargs):
    """
    Draw the given cube on a map using its points or bounds.
    
    "Mode" parameter will switch functionality between POINT or BOUND plotting.
    
    """
    # get the 2d lons and 2d lats from the CS
    if mode == iris.coords.POINT_MODE:
        lats, lons = iris.analysis.cartography.get_lat_lon_grids(cube)
    else:
        lats, lons = iris.analysis.cartography.get_lat_lon_contiguous_bounded_grids(cube)

    # take a copy of the data so that we can make modifications to it
    data = data.copy()

    # if we are global, then append the first column of data the array to the last (and add 360 degrees)  	  	 
    # NOTE: if it is found that this block of code is useful in anywhere other than this plotting routine, it  	  	 
    # may be better placed in the CS.
    lon_coord = filter(lambda coord: coord.standard_name in ["longitude", "grid_longitude"], cube.coords())[0]
    if lon_coord.circular:
        lats = numpy.append(lats, lats[:, 0:1], axis=1)
        lons = numpy.append(lons, lons[:, 0:1] + 360, axis=1)
        data = numpy.ma.concatenate([data, data[:, 0:1]], axis=1)

    # Do we need to flip the longitude to avoid basemap's "non positive monotonic" warning?
    # Assume we have a non-scalar longitude coord describing a data dimension.
    mono, direction = iris.util.monotonic(lons[0, :], return_direction=True) 
    if mono and direction == -1:
        data = data[:, ::-1]
        lons = lons[:, ::-1]
        lats = lats[:, ::-1]
            
    # Attempt to mimic the pyplot stateful interface with basemap.
    # If the current Basemap instance hasn't been registered on the current axes then
    # we assume we've moved to a new axes and create a new map.
    bm = _CURRENT_MAP
    if bm is None or hash(plt.gca()) not in bm._initialized_axes:
        # Provide lat & lon ranges as we have already calculated our lats and lons.
        bm = map_setup(cube=cube, lon_range=(numpy.min(lons), numpy.max(lons)),
                        lat_range=(numpy.min(lats), numpy.max(lats)), )

    # Convert the lons and lats into the plot coordinates
    px, py = bm(lons, lats)

    if mode == iris.coords.POINT_MODE:
        # TODO #480 Include mdi in this index when it is available
        invalid_points = numpy.where((px == 1e+30) | (py == 1e+30) | (numpy.isnan(data)))
        data[invalid_points] = numpy.nan
    else:
        # TODO #480 Include mdi in this index
        invalid_points = numpy.where( (px == 1e+30) | (py == 1e+30) )
        
    px[invalid_points] = numpy.nan
    py[invalid_points] = numpy.nan

    # Draw the contour lines/filled contours
    draw_method = getattr(bm, draw_method_name)
    
    if arg_func is not None:
        new_args, kwargs = arg_func(px, py, data, *args, **kwargs)
    else:
        new_args = (px, py, data) + args

    drawn_object = draw_method(*new_args, **kwargs)

    # if the range of the data is outside the range of the map, then bring the data back 360 degrees and re-plot
    if numpy.max(lons) > bm.urcrnrlon:
        px, py = bm(lons-360, lats)
        if hasattr(drawn_object, 'levels'):
            if arg_func is not None:
                new_args, kwargs = arg_func(px, py, data, drawn_object.levels, *args, **kwargs)
            else:
                new_args = (px, py, data, drawn_object.levels) + args
                       
        drawn_object = draw_method(*new_args, **kwargs)

    return drawn_object
Example #38
0
def _get_xy_coords(cube):
    """
    Return the x and y coordinates from a cube.

    This function will preferentially return a pair of dimension
    coordinates (if there are more than one potential x or y dimension
    coordinates a ValueError will be raised). If the cube does not have
    a pair of x and y dimension coordinates it will return 1D auxiliary
    coordinates (including scalars). If there is not one and only one set
    of x and y auxiliary coordinates a ValueError will be raised.

    Having identified the x and y coordinates, the function checks that they
    have equal coordinate systems and that they do not occupy the same
    dimension on the cube.

    Args:

    * cube:
        An instance of :class:`iris.cube.Cube`.

    Returns:
        A tuple containing the cube's x and y coordinates.

    """
    # Look for a suitable dimension coords first.
    x_coords = cube.coords(axis='x', dim_coords=True)
    if not x_coords:
        # If there is no x coord in dim_coords look for scalars or
        # monotonic coords in aux_coords.
        x_coords = [coord for coord in cube.coords(axis='x', dim_coords=False)
                    if coord.ndim == 1 and coord.is_monotonic()]
    if len(x_coords) != 1:
        raise ValueError('Cube {!r} must contain a single 1D x '
                         'coordinate.'.format(cube.name()))
    x_coord = x_coords[0]

    # Look for a suitable dimension coords first.
    y_coords = cube.coords(axis='y', dim_coords=True)
    if not y_coords:
        # If there is no y coord in dim_coords look for scalars or
        # monotonic coords in aux_coords.
        y_coords = [coord for coord in cube.coords(axis='y', dim_coords=False)
                    if coord.ndim == 1 and coord.is_monotonic()]
    if len(y_coords) != 1:
        raise ValueError('Cube {!r} must contain a single 1D y '
                         'coordinate.'.format(cube.name()))
    y_coord = y_coords[0]

    if x_coord.coord_system != y_coord.coord_system:
        raise ValueError("The cube's x ({!r}) and y ({!r}) "
                         "coordinates must have the same coordinate "
                         "system.".format(x_coord.name(), y_coord.name()))

    # The x and y coordinates must describe different dimensions
    # or be scalar coords.
    x_dims = cube.coord_dims(x_coord)
    x_dim = None
    if x_dims:
        x_dim = x_dims[0]

    y_dims = cube.coord_dims(y_coord)
    y_dim = None
    if y_dims:
        y_dim = y_dims[0]

    if x_dim is not None and y_dim == x_dim:
        raise ValueError("The cube's x and y coords must not describe the "
                         "same data dimension.")

    return x_coord, y_coord
Example #39
0
def _multiply_divide_common(operation_function,
                            operation_symbol,
                            operation_noun,
                            cube,
                            other,
                            dim=None,
                            in_place=False):
    """
    Function which shares common code between multiplication and division of cubes.

    operation_function   - function which does the operation (e.g. numpy.divide)
    operation_symbol     - the textual symbol of the operation (e.g. '/')
    operation_noun       - the noun of the operation (e.g. 'division')
    operation_past_tense - the past tense of the operation (e.g. 'divided')

    .. seealso:: For information on the dim keyword argument see :func:`multiply`.

    """
    if not isinstance(cube, iris.cube.Cube):
        raise TypeError(
            'The "cube" argument must be an instance of iris.Cube.')

    if isinstance(other, (int, float)):
        other = np.array(other)

    other_unit = None

    if isinstance(other, np.ndarray):
        _assert_compatible(cube, other)

        if in_place:
            new_cube = cube
            new_cube.data = operation_function(cube.data, other)
        else:
            new_cube = cube.copy(data=operation_function(cube.data, other))

        other_unit = '1'
    elif isinstance(other, iris.coords.Coord):
        # Deal with cube multiplication/division by coordinate

        # What dimension are we processing?
        data_dimension = None
        if dim is not None:
            # Ensure the given dim matches the coord
            if other in cube.coords() and cube.coord_dims(other) != [dim]:
                raise ValueError(
                    "dim provided does not match dim found for coord")
            data_dimension = dim
        else:
            # Try and get a coord dim
            if other.shape != (1, ):
                try:
                    coord_dims = cube.coord_dims(other)
                    data_dimension = coord_dims[0] if coord_dims else None
                except iris.exceptions.CoordinateNotFoundError:
                    raise ValueError(
                        "Could not determine dimension for mul/div. Use mul(coord, dim=dim)"
                    )

        if other.ndim != 1:
            raise iris.exceptions.CoordinateMultiDimError(other)

        if other.has_bounds():
            warnings.warn(
                '%s by a bounded coordinate not well defined, ignoring bounds.'
                % operation_noun)

        points = other.points

        # If the axis is defined then shape the provided points so that we can do the
        # division (this is needed as there is no "axis" keyword to numpy's divide/multiply)
        if data_dimension is not None:
            points_shape = [1] * cube.ndim
            points_shape[data_dimension] = -1
            points = points.reshape(points_shape)

        if in_place:
            new_cube = cube
            new_cube.data = operation_function(cube.data, points)
        else:
            new_cube = cube.copy(data=operation_function(cube.data, points))

        other_unit = other.units
    elif isinstance(other, iris.cube.Cube):
        # Deal with cube multiplication/division by cube

        if in_place:
            new_cube = cube
            new_cube.data = operation_function(cube.data, other.data)
        else:
            new_cube = cube.copy(
                data=operation_function(cube.data, other.data))

        other_unit = other.units
    else:
        return NotImplemented

    # Update the units
    if operation_function == np.multiply:
        new_cube.units = cube.units * other_unit
    elif operation_function == np.divide:
        new_cube.units = cube.units / other_unit

    iris.analysis.clear_phenomenon_identity(new_cube)

    return new_cube
Example #40
0
def _add_subtract_common(operation_function, operation_symbol, operation_noun, operation_past_tense,
                         cube, other, dim=None, ignore=True, update_history=True, in_place=False):
    """
    Function which shares common code between addition and subtraction of cubes.

    operation_function   - function which does the operation (e.g. numpy.subtract)
    operation_symbol     - the textual symbol of the operation (e.g. '-')
    operation_noun       - the noun of the operation (e.g. 'subtraction')
    operation_past_tense - the past tense of the operation (e.g. 'subtracted')

    """
    if not isinstance(cube, iris.cube.Cube):
        raise TypeError('The "cube" argument must be an instance of iris.Cube.')

    if isinstance(other, (int, float)):
        # Promote scalar to a coordinate and associate unit type with cube unit type
        other = np.array(other)

    # Check that the units of the cube and the other item are the same, or if the other does not have a unit, skip this test
    if cube.units != getattr(other, 'units', cube.units) :
        raise iris.exceptions.NotYetImplementedError('Differing units (%s & %s) %s not implemented' % \
                                                     (cube.units, other.units, operation_noun))

    history = None

    if isinstance(other, np.ndarray):
        _assert_compatible(cube, other)

        if in_place:
            new_cube = cube
            operation_function(new_cube.data, other, new_cube.data)
        else:
            new_cube = cube.copy(data=operation_function(cube.data, other))

        if update_history:
            if other.ndim == 0:
                history = '%s %s %s' % (cube.name(), operation_symbol, other)
            else:
                history = '%s %s array' % (cube.name(), operation_symbol)
    elif isinstance(other, iris.coords.Coord):
        # Deal with cube addition/subtraction by coordinate

        # What dimension are we processing?
        data_dimension = None
        if dim is not None:
            # Ensure the given dim matches the coord
            if other in cube.coords() and cube.coord_dims(other) != [dim]:
                raise ValueError("dim provided does not match dim found for coord")
            data_dimension = dim
        else:
            # Try and get a coord dim
            if other.shape != (1,):
                try:
                    coord_dims = cube.coord_dims(other)
                    data_dimension = coord_dims[0] if coord_dims else None
                except iris.exceptions.CoordinateNotFoundError:
                    raise ValueError("Could not determine dimension for add/sub. Use add(coord, dim=dim)")

        if other.ndim != 1:
            raise iris.exceptions.CoordinateMultiDimError(other)

        if other.has_bounds():
            warnings.warn('%s by a bounded coordinate not well defined, ignoring bounds.' % operation_noun)

        points = other.points

        if data_dimension is not None:
            points_shape = [1] * cube.data.ndim
            points_shape[data_dimension] = -1
            points = points.reshape(points_shape)

        if in_place:
            new_cube = cube
            operation_function(new_cube.data, points, new_cube.data)
        else:
            new_cube = cube.copy(data=operation_function(cube.data, points))

        if update_history:
            history = '%s %s %s (coordinate)' % (cube.name(), operation_symbol, other.name())
    elif isinstance(other, iris.cube.Cube):
        # Deal with cube addition/subtraction by cube

        # get a coordinate comparison of this cube and the cube to do the operation with
        coord_comp = iris.analysis.coord_comparison(cube, other)

        if coord_comp['transposable']:
            raise ValueError('Cubes cannot be %s, differing axes. '
                                 'cube.transpose() may be required to re-order the axes.' % operation_past_tense)

        # provide a deprecation warning if the ignore keyword has been set
        if ignore is not True:
            warnings.warn('The "ignore" keyword has been deprecated in add/subtract. This functionality is now automatic. '
                          'The provided value to "ignore" has been ignored, and has been automatically calculated.')

        bad_coord_grps = (coord_comp['ungroupable_and_dimensioned'] + coord_comp['resamplable'])
        if bad_coord_grps:
            raise ValueError('This operation cannot be performed as there are differing coordinates (%s) remaining '
                             'which cannot be ignored.' % ', '.join({coord_grp.name() for coord_grp in bad_coord_grps}))

        if in_place:
            new_cube = cube
            operation_function(new_cube.data, other.data, new_cube.data)
        else:
            new_cube = cube.copy(data=operation_function(cube.data, other.data))

        # If a coordinate is to be ignored - remove it
        ignore = filter(None, [coord_grp[0] for coord_grp in coord_comp['ignorable']])
        if not ignore:
            ignore_string = ''
        else:
            ignore_string = ' (ignoring %s)' % ', '.join([coord.name() for coord in ignore])
        for coord in ignore:
            new_cube.remove_coord(coord)

        if update_history:
            history = '%s %s %s%s' % (cube.name() or 'unknown', operation_symbol,
                                      other.name() or 'unknown', ignore_string)

    else:
        return NotImplemented

    iris.analysis.clear_phenomenon_identity(new_cube)

    if history is not None:
        new_cube.add_history(history)

    return new_cube
Example #41
0
def save(cube, filename, netcdf_format='NETCDF4'):
    """
    Save a cube to a netCDF file, given the cube and the filename.
    
    Args:
    
    * cube (:class:`iris.cube.Cube`):
        The :class:`iris.cube.Cube` to be saved to a netCDF file.

    * filename (string):
        Name of the netCDF file to save the cube.

    * netcdf_format (string):
        Underlying netCDF file format, one of 'NETCDF4', 'NETCDF4_CLASSIC', 
        'NETCDF3_CLASSIC' or 'NETCDF3_64BIT'. Default is 'NETCDF4' format.

    Returns:
        None.
    
    """
    if not isinstance(cube, iris.cube.Cube):
        raise TypeError('Expecting a single cube instance, got %r.' % type(cube))

    if netcdf_format not in ['NETCDF4', 'NETCDF4_CLASSIC', 'NETCDF3_CLASSIC', 'NETCDF3_64BIT']:
        raise ValueError('Unknown netCDF file format, got %r' % netcdf_format)

    if len(cube.aux_factories) > 1:
        raise ValueError('Multiple auxiliary factories are not supported.')

    dataset = netCDF4.Dataset(filename, mode='w', format=netcdf_format)
    
    # Create the CF-netCDF data dimension names.
    dimension_names = []
    for dim in xrange(cube.ndim):
        coords = cube.coords(dimensions=dim, dim_coords=True)
        if coords is not None:
            if len(coords) != 1:
                raise iris.exceptions.IrisError('Cube appears to have multiple dimension coordinates on dimension %d' % dim)
            dimension_names.append(coords[0].name())
        else:
             # There are no CF-netCDF coordinates describing this data dimension.
            dimension_names.append('dim%d' % dim)

    # Create the CF-netCDF data dimensions.
    # Make the outermost dimension an unlimited dimension.
    if dimension_names:
        dataset.createDimension(dimension_names[0])
    for dim_name, dim_len in zip(dimension_names, cube.shape)[1:]:
        dataset.createDimension(dim_name, dim_len)

    # Identify the collection of coordinates that represent CF-netCDF coordinate variables.
    cf_coordinates = cube.dim_coords

    # Create the associated cube CF-netCDF data variable.
    cf_var_cube = _create_cf_data_variable(dataset, cube, dimension_names)

    factory_defn = None
    if cube.aux_factories:
        factory = cube.aux_factories[0]
        factory_defn = _FACTORY_DEFNS.get(type(factory), None)

    # Ensure we create the netCDF coordinate variables first.
    for coord in cf_coordinates:
        # Create the associated coordinate CF-netCDF variable.
        _create_cf_variable(dataset, cube, dimension_names, coord, factory_defn)
    
    # List of CF-netCDF auxiliary coordinate variable names.
    auxiliary_coordinate_names = []
    for coord in sorted(cube.aux_coords, key=lambda coord: coord.name()):
        # Create the associated coordinate CF-netCDF variable.
        cf_name = _create_cf_variable(dataset, cube, dimension_names, coord, factory_defn)

        if cf_name is not None:
            auxiliary_coordinate_names.append(cf_name)

    # Add CF-netCDF auxiliary coordinate variable references to the CF-netCDF data variable.
    if auxiliary_coordinate_names:
        cf_var_cube.coordinates = ' '.join(sorted(auxiliary_coordinate_names))

    # Flush any buffered data to the CF-netCDF file before closing.
    dataset.sync()
    dataset.close()
Example #42
0
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]
Example #43
0
def _multiply_divide_common(operation_function, operation_symbol, operation_noun,
                            cube, other, dim=None, update_history=True):
    """
    Function which shares common code between multiplication and division of cubes.

    operation_function   - function which does the operation (e.g. numpy.divide)
    operation_symbol     - the textual symbol of the operation (e.g. '/')
    operation_noun       - the noun of the operation (e.g. 'division')
    operation_past_tense - the past tesnse of the operation (e.g. 'divided')

    .. seealso:: For information on the dim keyword argument see :func:`multiply`.

    """
    if not isinstance(cube, iris.cube.Cube):
        raise TypeError('The "cube" argument must be an instance of iris.Cube.')

    if isinstance(other, (int, float)):
        other = np.array(other)

    other_unit = None
    history = None

    if isinstance(other, np.ndarray):
        _assert_compatible(cube, other)

        copy_cube = cube.copy(data=operation_function(cube.data, other))

        if update_history:
            if other.ndim == 0:
                history = '%s %s %s' % (cube.name(), operation_symbol, other)
            else:
                history = '%s %s array' % (cube.name(), operation_symbol)

        other_unit = '1'
    elif isinstance(other, iris.coords.Coord):
        # Deal with cube multiplication/division by coordinate

        # What dimension are we processing?
        data_dimension = None
        if dim is not None:
            # Ensure the given dim matches the coord
            if other in cube.coords() and cube.coord_dims(other) != [dim]:
                raise ValueError("dim provided does not match dim found for coord")
            data_dimension = dim
        else:
            # Try and get a coord dim
            if other.shape != (1,):
                try:
                    coord_dims = cube.coord_dims(other)
                    data_dimension = coord_dims[0] if coord_dims else None
                except iris.exceptions.CoordinateNotFoundError:
                    raise ValueError("Could not determine dimension for mul/div. Use mul(coord, dim=dim)")

        if other.ndim != 1:
            raise iris.exceptions.CoordinateMultiDimError(other)

        if other.has_bounds():
            warnings.warn('%s by a bounded coordinate not well defined, ignoring bounds.' % operation_noun)

        points = other.points

        # If the axis is defined then shape the provided points so that we can do the
        # division (this is needed as there is no "axis" keyword to numpy's divide/multiply)
        if data_dimension is not None:
            points_shape = [1] * cube.data.ndim
            points_shape[data_dimension] = -1
            points = points.reshape(points_shape)

        copy_cube = cube.copy(data=operation_function(cube.data, points))

        if update_history:
            history = '%s %s %s' % (cube.name(), operation_symbol, other.name())

        other_unit = other.units
    elif isinstance(other, iris.cube.Cube):
        # Deal with cube multiplication/division by cube
        copy_cube = cube.copy(data=operation_function(cube.data, other.data))

        if update_history:
            history = '%s %s %s' % (cube.name() or 'unknown', operation_symbol,
                                    other.name() or 'unknown')

        other_unit = other.units
    else:
        return NotImplemented

    # Update the units
    if operation_function == np.multiply:
        copy_cube.units = cube.units * other_unit
    elif operation_function == np.divide:
        copy_cube.units = cube.units / other_unit

    iris.analysis.clear_phenomenon_identity(copy_cube)

    if history is not None:
        copy_cube.add_history(history)

    return copy_cube
Example #44
0
def _get_xy_coords(cube):
    """
    Return the x and y coordinates from a cube.

    This function will preferentially return a pair of dimension
    coordinates (if there are more than one potential x or y dimension
    coordinates a ValueError will be raised). If the cube does not have
    a pair of x and y dimension coordinates it will return 1D auxiliary
    coordinates (including scalars). If there is not one and only one set
    of x and y auxiliary coordinates a ValueError will be raised.

    Having identified the x and y coordinates, the function checks that they
    have equal coordinate systems and that they do not occupy the same
    dimension on the cube.

    Args:

    * cube:
        An instance of :class:`iris.cube.Cube`.

    Returns:
        A tuple containing the cube's x and y coordinates.

    """
    # Look for a suitable dimension coords first.
    x_coords = cube.coords(axis='x', dim_coords=True)
    if not x_coords:
        # If there is no x coord in dim_coords look for scalars or
        # monotonic coords in aux_coords.
        x_coords = [
            coord for coord in cube.coords(axis='x', dim_coords=False)
            if coord.ndim == 1 and coord.is_monotonic()
        ]
    if len(x_coords) != 1:
        raise ValueError('Cube {!r} must contain a single 1D x '
                         'coordinate.'.format(cube.name()))
    x_coord = x_coords[0]

    # Look for a suitable dimension coords first.
    y_coords = cube.coords(axis='y', dim_coords=True)
    if not y_coords:
        # If there is no y coord in dim_coords look for scalars or
        # monotonic coords in aux_coords.
        y_coords = [
            coord for coord in cube.coords(axis='y', dim_coords=False)
            if coord.ndim == 1 and coord.is_monotonic()
        ]
    if len(y_coords) != 1:
        raise ValueError('Cube {!r} must contain a single 1D y '
                         'coordinate.'.format(cube.name()))
    y_coord = y_coords[0]

    if x_coord.coord_system != y_coord.coord_system:
        raise ValueError("The cube's x ({!r}) and y ({!r}) "
                         "coordinates must have the same coordinate "
                         "system.".format(x_coord.name(), y_coord.name()))

    # The x and y coordinates must describe different dimensions
    # or be scalar coords.
    x_dims = cube.coord_dims(x_coord)
    x_dim = None
    if x_dims:
        x_dim = x_dims[0]

    y_dims = cube.coord_dims(y_coord)
    y_dim = None
    if y_dims:
        y_dim = y_dims[0]

    if x_dim is not None and y_dim == x_dim:
        raise ValueError("The cube's x and y coords must not describe the "
                         "same data dimension.")

    return x_coord, y_coord
Example #45
0
def _add_subtract_common(operation_function,
                         operation_symbol,
                         operation_noun,
                         operation_past_tense,
                         cube,
                         other,
                         dim=None,
                         ignore=True,
                         in_place=False):
    """
    Function which shares common code between addition and subtraction of cubes.

    operation_function   - function which does the operation (e.g. numpy.subtract)
    operation_symbol     - the textual symbol of the operation (e.g. '-')
    operation_noun       - the noun of the operation (e.g. 'subtraction')
    operation_past_tense - the past tense of the operation (e.g. 'subtracted')

    """
    if not isinstance(cube, iris.cube.Cube):
        raise TypeError(
            'The "cube" argument must be an instance of iris.Cube.')

    if isinstance(other, (int, float)):
        # Promote scalar to a coordinate and associate unit type with cube unit type
        other = np.array(other)

    # Check that the units of the cube and the other item are the same, or if the other does not have a unit, skip this test
    if cube.units != getattr(other, 'units', cube.units):
        raise iris.exceptions.NotYetImplementedError('Differing units (%s & %s) %s not implemented' % \
                                                     (cube.units, other.units, operation_noun))

    if isinstance(other, np.ndarray):
        _assert_compatible(cube, other)

        if in_place:
            new_cube = cube
            operation_function(new_cube.data, other, new_cube.data)
        else:
            new_cube = cube.copy(data=operation_function(cube.data, other))
    elif isinstance(other, iris.coords.Coord):
        # Deal with cube addition/subtraction by coordinate

        # What dimension are we processing?
        data_dimension = None
        if dim is not None:
            # Ensure the given dim matches the coord
            if other in cube.coords() and cube.coord_dims(other) != [dim]:
                raise ValueError(
                    "dim provided does not match dim found for coord")
            data_dimension = dim
        else:
            # Try and get a coord dim
            if other.shape != (1, ):
                try:
                    coord_dims = cube.coord_dims(other)
                    data_dimension = coord_dims[0] if coord_dims else None
                except iris.exceptions.CoordinateNotFoundError:
                    raise ValueError(
                        "Could not determine dimension for add/sub. Use add(coord, dim=dim)"
                    )

        if other.ndim != 1:
            raise iris.exceptions.CoordinateMultiDimError(other)

        if other.has_bounds():
            warnings.warn(
                '%s by a bounded coordinate not well defined, ignoring bounds.'
                % operation_noun)

        points = other.points

        if data_dimension is not None:
            points_shape = [1] * cube.ndim
            points_shape[data_dimension] = -1
            points = points.reshape(points_shape)

        if in_place:
            new_cube = cube
            operation_function(new_cube.data, points, new_cube.data)
        else:
            new_cube = cube.copy(data=operation_function(cube.data, points))
    elif isinstance(other, iris.cube.Cube):
        # Deal with cube addition/subtraction by cube

        # get a coordinate comparison of this cube and the cube to do the operation with
        coord_comp = iris.analysis.coord_comparison(cube, other)

        if coord_comp['transposable']:
            # User does not need to transpose their cubes if numpy
            # array broadcasting will make the dimensions match
            broadcast_padding = cube.ndim - other.ndim
            coord_dims_equal = True
            for coord_group in coord_comp['transposable']:
                cube_coord, other_coord = coord_group.coords
                cube_coord_dims = cube.coord_dims(coord=cube_coord)
                other_coord_dims = other.coord_dims(coord=other_coord)
                other_coord_dims_broadcasted = tuple(
                    [dim + broadcast_padding for dim in other_coord_dims])
                if cube_coord_dims != other_coord_dims_broadcasted:
                    coord_dims_equal = False

            if not coord_dims_equal:
                raise ValueError('Cubes cannot be %s, differing axes. '
                                 'cube.transpose() may be required to '
                                 're-order the axes.' % operation_past_tense)

        # provide a deprecation warning if the ignore keyword has been set
        if ignore is not True:
            warnings.warn(
                'The "ignore" keyword has been deprecated in add/subtract. This functionality is now automatic. '
                'The provided value to "ignore" has been ignored, and has been automatically calculated.'
            )

        bad_coord_grps = (coord_comp['ungroupable_and_dimensioned'] +
                          coord_comp['resamplable'])
        if bad_coord_grps:
            raise ValueError(
                'This operation cannot be performed as there are differing coordinates (%s) remaining '
                'which cannot be ignored.' %
                ', '.join({coord_grp.name()
                           for coord_grp in bad_coord_grps}))

        if in_place:
            new_cube = cube
            operation_function(new_cube.data, other.data, new_cube.data)
        else:
            new_cube = cube.copy(
                data=operation_function(cube.data, other.data))

        # If a coordinate is to be ignored - remove it
        ignore = filter(
            None, [coord_grp[0] for coord_grp in coord_comp['ignorable']])
        if not ignore:
            ignore_string = ''
        else:
            ignore_string = ' (ignoring %s)' % ', '.join(
                [coord.name() for coord in ignore])
        for coord in ignore:
            new_cube.remove_coord(coord)

    else:
        return NotImplemented

    iris.analysis.clear_phenomenon_identity(new_cube)

    return new_cube