Repository: ESMValGroup/ESMValCore

Issue:
Dataset problem: `typesi` missing for the `siconc` variable of some CMIP6 models 
**Describe the dataset issue**
A clear and concise description of what the problem with the data is is. Here are some guidelines that you can use
right out the box by filling in the blanks (note that, if needed we will assist you with writing a fix; also note that
if the problem is with a CMIP6 dataset, we will alert the ESGF and/or the model developers for them to fix the problem
in the long run; fixes for CMIP5 by the model developers are no longer possible):

- Data file has been changed by you in any way (if you answer yes the issue will be void and closed, we are not
supporting custom-made datasets that present problems, it is your resposability to fix those problems):
- Project (CMIP5/CMIP6/OBS/obs4mips/etc): CMIP6
- Full dataset description (fill out as many as you know, please):
  - datasets: `cesm2_fv2`, `cesm2_waccm`, `cesm2_waccm_fv2`, `gfdl_cm4` and `kiost-esm`
  - experiment: historical
  - mip: SImon
  - grid: gn, gr, gr1
  - variable used: siconc
  - data version:
- Problems encountered (please describe what the actual problems are: e.g. wrong standard name, issue with dimensions): 
```
esmvalcore.cmor.check.CMORCheckError: There were errors in variable siconc:
typesi: does not exist
```
- Pointer to existing copy of data on ESGF node (it would be very useful if you could provide a physical
fullpath to the file(s) that are causing the problem, e.g. on CEDA Jasmin or DKRZ):
/mnt/lustre02/work/ik1017/CMIP6/data/CMIP6/CMIP/KIOST/KIOST-ESM/historical/r1i1p1f1/SImon/siconc/gr1/v20201211/siconc_SImon_KIOST-ESM_historical_r1i1p1f1_gr1_185001-201412.nc
/mnt/lustre02/work/ik1017/CMIP6/data/CMIP6/CMIP/NOAA-GFDL/GFDL-CM4/historical/r1i1p1f1/SImon/siconc/gr/v20180701/siconc_SImon_GFDL-CM4_historical_r1i1p1f1_gr_185001-194912.nc
/mnt/lustre02/work/ik1017/CMIP6/data/CMIP6/CMIP/NOAA-GFDL/GFDL-CM4/historical/r1i1p1f1/SImon/siconc/gn/v20180701/siconc_SImon_GFDL-CM4_historical_r1i1p1f1_gn_185001-194912.nc
[...]
- Other notes and mentions:
This is the same as #590. The fix from `GDFL_ESM4` could be reused. 

**Assign the right people**
If you are already a member of the ESMValTool GitHub project, please assign Valeriu Predoi (valeriupredoi) and
Klaus Zimmermann (zklaus) to the issue. They will then check the issue raised and propagate it further to the
data model developers.

Cheers :beer:


Discussion:
remi-kazeroni: I moved the `typesi` fix into the shared fixes since it is needed for quite a few models. It's unclear to me if the `value` should be `default` or `sea_ice`.
```python
def add_scalar_typesi_coord(cube, value='default'):
```
The few `CESM2` examples with `typesea` or `typeland` are not so clear. That would help me to fix the failing test and add tests for other models. 
remi-kazeroni: This is now ready for review. I have refactored things a bit to share the fix accross datasets. 

Patch:
diff --git a/doc/recipe/preprocessor.rst b/doc/recipe/preprocessor.rst
index 8664530ba..997149225 100644
--- a/doc/recipe/preprocessor.rst
+++ b/doc/recipe/preprocessor.rst
@@ -1258,9 +1258,9 @@ The area manipulation module contains the following preprocessor functions:
 ``extract_region``
 ------------------
 
-This function masks data outside a rectangular region requested. The boundaries
-of the region are provided as latitude and longitude coordinates in the
-arguments:
+This function returns a subset of the data on the rectangular region requested.
+The boundaries of the region are provided as latitude and longitude coordinates
+in the arguments:
 
 * ``start_longitude``
 * ``end_longitude``
@@ -1270,6 +1270,10 @@ arguments:
 Note that this function can only be used to extract a rectangular region. Use
 ``extract_shape`` to extract any other shaped region from a shapefile.
 
+If the grid is irregular, the returned region retains the original coordinates,
+but is cropped to a rectangular bounding box defined by the start/end
+coordinates. The deselected area inside the region is masked.
+
 See also :func:`esmvalcore.preprocessor.extract_region`.
 
 
diff --git a/esmvalcore/cmor/_fixes/cmip6/cesm2.py b/esmvalcore/cmor/_fixes/cmip6/cesm2.py
index 4ab1f0a4b..51bc04685 100644
--- a/esmvalcore/cmor/_fixes/cmip6/cesm2.py
+++ b/esmvalcore/cmor/_fixes/cmip6/cesm2.py
@@ -4,6 +4,7 @@ from shutil import copyfile
 import numpy as np
 from netCDF4 import Dataset
 
+from ..common import SiconcFixScalarCoord
 from ..fix import Fix
 from ..shared import (
     add_scalar_depth_coord,
@@ -12,7 +13,6 @@ from ..shared import (
     add_scalar_typesea_coord,
     fix_ocean_depth_coord,
 )
-from .gfdl_esm4 import Siconc as Addtypesi
 
 
 class Cl(Fix):
@@ -215,6 +215,9 @@ class Sftof(Fix):
         return cubes
 
 
+Siconc = SiconcFixScalarCoord
+
+
 class Tos(Fix):
     """Fixes for tos."""
 
@@ -243,9 +246,6 @@ class Tos(Fix):
         return cubes
 
 
-Siconc = Addtypesi
-
-
 class Omon(Fix):
     """Fixes for ocean variables."""
 
diff --git a/esmvalcore/cmor/_fixes/cmip6/cesm2_fv2.py b/esmvalcore/cmor/_fixes/cmip6/cesm2_fv2.py
index 2683b8fb8..80f2e5884 100644
--- a/esmvalcore/cmor/_fixes/cmip6/cesm2_fv2.py
+++ b/esmvalcore/cmor/_fixes/cmip6/cesm2_fv2.py
@@ -1,7 +1,8 @@
 """Fixes for CESM2-FV2 model."""
 from .cesm2 import Cl as BaseCl
-from .cesm2 import Tas as BaseTas
 from .cesm2 import Fgco2 as BaseFgco2
+from .cesm2 import Tas as BaseTas
+from ..common import SiconcFixScalarCoord
 
 
 Cl = BaseCl
@@ -13,7 +14,10 @@ Cli = Cl
 Clw = Cl
 
 
-Tas = BaseTas
+Fgco2 = BaseFgco2
 
 
-Fgco2 = BaseFgco2
+Siconc = SiconcFixScalarCoord
+
+
+Tas = BaseTas
diff --git a/esmvalcore/cmor/_fixes/cmip6/cesm2_waccm.py b/esmvalcore/cmor/_fixes/cmip6/cesm2_waccm.py
index 9678430d1..d0014f308 100644
--- a/esmvalcore/cmor/_fixes/cmip6/cesm2_waccm.py
+++ b/esmvalcore/cmor/_fixes/cmip6/cesm2_waccm.py
@@ -2,8 +2,9 @@
 from netCDF4 import Dataset
 
 from .cesm2 import Cl as BaseCl
-from .cesm2 import Tas as BaseTas
 from .cesm2 import Fgco2 as BaseFgco2
+from .cesm2 import Tas as BaseTas
+from ..common import SiconcFixScalarCoord
 
 
 class Cl(BaseCl):
@@ -48,7 +49,10 @@ Cli = Cl
 Clw = Cl
 
 
-Tas = BaseTas
+Fgco2 = BaseFgco2
 
 
-Fgco2 = BaseFgco2
+Siconc = SiconcFixScalarCoord
+
+
+Tas = BaseTas
diff --git a/esmvalcore/cmor/_fixes/cmip6/cesm2_waccm_fv2.py b/esmvalcore/cmor/_fixes/cmip6/cesm2_waccm_fv2.py
index a1ff61bfa..bc8068af8 100644
--- a/esmvalcore/cmor/_fixes/cmip6/cesm2_waccm_fv2.py
+++ b/esmvalcore/cmor/_fixes/cmip6/cesm2_waccm_fv2.py
@@ -4,6 +4,8 @@ from .cesm2 import Fgco2 as BaseFgco2
 from .cesm2_waccm import Cl as BaseCl
 from .cesm2_waccm import Cli as BaseCli
 from .cesm2_waccm import Clw as BaseClw
+from ..common import SiconcFixScalarCoord
+
 
 Cl = BaseCl
 
@@ -14,7 +16,10 @@ Cli = BaseCli
 Clw = BaseClw
 
 
-Tas = BaseTas
+Fgco2 = BaseFgco2
 
 
-Fgco2 = BaseFgco2
+Siconc = SiconcFixScalarCoord
+
+
+Tas = BaseTas
diff --git a/esmvalcore/cmor/_fixes/cmip6/gfdl_cm4.py b/esmvalcore/cmor/_fixes/cmip6/gfdl_cm4.py
index ce61e7fab..35baf68a1 100644
--- a/esmvalcore/cmor/_fixes/cmip6/gfdl_cm4.py
+++ b/esmvalcore/cmor/_fixes/cmip6/gfdl_cm4.py
@@ -1,7 +1,7 @@
 """Fixes for GFDL-CM4 model."""
 import iris
 
-from ..common import ClFixHybridPressureCoord
+from ..common import ClFixHybridPressureCoord, SiconcFixScalarCoord
 from ..fix import Fix
 from ..shared import add_aux_coords_from_cubes, add_scalar_height_coord
 
@@ -38,6 +38,9 @@ Cli = Cl
 Clw = Cl
 
 
+Siconc = SiconcFixScalarCoord
+
+
 class Tas(Fix):
     """Fixes for tas."""
 
diff --git a/esmvalcore/cmor/_fixes/cmip6/gfdl_esm4.py b/esmvalcore/cmor/_fixes/cmip6/gfdl_esm4.py
index 02938907d..c2610a318 100644
--- a/esmvalcore/cmor/_fixes/cmip6/gfdl_esm4.py
+++ b/esmvalcore/cmor/_fixes/cmip6/gfdl_esm4.py
@@ -1,36 +1,10 @@
 """Fixes for GFDL-ESM4 model."""
-import iris
+from ..common import SiconcFixScalarCoord
 from ..fix import Fix
-from ..shared import (add_scalar_depth_coord, fix_ocean_depth_coord)
-
-
-class Siconc(Fix):
-    """Fixes for siconc."""
-
-    def fix_metadata(self, cubes):
-        """
-        Fix missing type.
-
-        Parameters
-        ----------
-        cubes: iris CubeList
-            List of cubes to fix
-
-        Returns
-        -------
-        iris.cube.CubeList
-
-        """
-        typesi = iris.coords.AuxCoord(
-            'siconc',
-            standard_name='area_type',
-            long_name='Sea Ice area type',
-            var_name='type',
-            units='1',
-            bounds=None)
-        for cube in cubes:
-            cube.add_aux_coord(typesi)
-        return cubes
+from ..shared import (
+    add_scalar_depth_coord,
+    fix_ocean_depth_coord,
+)
 
 
 class Fgco2(Fix):
@@ -76,3 +50,6 @@ class Omon(Fix):
                 if z_coord.standard_name is None:
                     fix_ocean_depth_coord(cube)
         return cubes
+
+
+Siconc = SiconcFixScalarCoord
diff --git a/esmvalcore/cmor/_fixes/cmip6/kiost_esm.py b/esmvalcore/cmor/_fixes/cmip6/kiost_esm.py
index 9f98e319f..518a82e54 100644
--- a/esmvalcore/cmor/_fixes/cmip6/kiost_esm.py
+++ b/esmvalcore/cmor/_fixes/cmip6/kiost_esm.py
@@ -1,4 +1,5 @@
 """Fixes for KIOST-ESM model."""
+from ..common import SiconcFixScalarCoord
 from ..fix import Fix
 from ..shared import add_scalar_height_coord
 
@@ -57,3 +58,6 @@ class Uas(SfcWind):
 
 class Vas(SfcWind):
     """Fixes for vas."""
+
+
+Siconc = SiconcFixScalarCoord
diff --git a/esmvalcore/cmor/_fixes/common.py b/esmvalcore/cmor/_fixes/common.py
index 41c1d974a..90c64162e 100644
--- a/esmvalcore/cmor/_fixes/common.py
+++ b/esmvalcore/cmor/_fixes/common.py
@@ -6,7 +6,11 @@ import numpy as np
 from scipy.ndimage import map_coordinates
 
 from .fix import Fix
-from .shared import add_plev_from_altitude, fix_bounds
+from .shared import (
+    add_plev_from_altitude,
+    add_scalar_typesi_coord,
+    fix_bounds,
+)
 
 logger = logging.getLogger(__name__)
 
@@ -191,3 +195,24 @@ class OceanFixGrid(Fix):
         cube.coord('longitude').bounds = lon_vertices
 
         return iris.cube.CubeList([cube])
+
+
+class SiconcFixScalarCoord(Fix):
+    """Fixes for siconc."""
+
+    def fix_metadata(self, cubes):
+        """Add typesi coordinate.
+
+        Parameters
+        ----------
+        cubes : iris.cube.CubeList
+            Input cubes.
+
+        Returns
+        -------
+        iris.cube.CubeList
+
+        """
+        cube = self.get_cube_from_list(cubes)
+        add_scalar_typesi_coord(cube)
+        return iris.cube.CubeList([cube])
diff --git a/esmvalcore/cmor/_fixes/shared.py b/esmvalcore/cmor/_fixes/shared.py
index de03d7885..d5c74c095 100644
--- a/esmvalcore/cmor/_fixes/shared.py
+++ b/esmvalcore/cmor/_fixes/shared.py
@@ -361,6 +361,21 @@ def add_scalar_typesea_coord(cube, value='default'):
     return cube
 
 
+def add_scalar_typesi_coord(cube, value='sea_ice'):
+    """Add scalar coordinate 'typesi' with value of `value`."""
+    logger.debug("Adding typesi coordinate (%s)", value)
+    typesi_coord = iris.coords.AuxCoord(value,
+                                        var_name='type',
+                                        standard_name='area_type',
+                                        long_name='Sea Ice area type',
+                                        units=Unit('no unit'))
+    try:
+        cube.coord('area_type')
+    except iris.exceptions.CoordinateNotFoundError:
+        cube.add_aux_coord(typesi_coord, ())
+    return cube
+
+
 def cube_to_aux_coord(cube):
     """Convert cube to iris AuxCoord."""
     return iris.coords.AuxCoord(
diff --git a/esmvalcore/cmor/variable_alt_names.yml b/esmvalcore/cmor/variable_alt_names.yml
index 787a20c99..e0416797e 100644
--- a/esmvalcore/cmor/variable_alt_names.yml
+++ b/esmvalcore/cmor/variable_alt_names.yml
@@ -9,7 +9,6 @@
 ###############################################################################
 ---
 - ['sic', 'siconc']
-- ['sit', 'sithick']
 - ['tro3', 'o3']
 - ['usi', 'siu']
-- ['vsi', 'siv']
\ No newline at end of file
+- ['vsi', 'siv']
diff --git a/esmvalcore/preprocessor/_area.py b/esmvalcore/preprocessor/_area.py
index f3a6a69c7..b3b057a6f 100644
--- a/esmvalcore/preprocessor/_area.py
+++ b/esmvalcore/preprocessor/_area.py
@@ -22,14 +22,12 @@ from ._shared import (
 logger = logging.getLogger(__name__)
 
 
-# slice cube over a restricted area (box)
 def extract_region(cube, start_longitude, end_longitude, start_latitude,
                    end_latitude):
     """Extract a region from a cube.
 
     Function that subsets a cube on a box (start_longitude, end_longitude,
     start_latitude, end_latitude)
-    This function is a restriction of masked_cube_lonlat().
 
     Parameters
     ----------
@@ -63,16 +61,29 @@ def extract_region(cube, start_longitude, end_longitude, start_latitude,
             ignore_bounds=True,
         )
         region_subset = region_subset.intersection(longitude=(0., 360.))
-        return region_subset
-    # Irregular grids
-    lats = cube.coord('latitude').points
-    lons = cube.coord('longitude').points
+    else:
+        region_subset = _extract_irregular_region(
+            cube,
+            start_longitude,
+            end_longitude,
+            start_latitude,
+            end_latitude,
+        )
+    return region_subset
+
+
+def _extract_irregular_region(cube, start_longitude, end_longitude,
+                              start_latitude, end_latitude):
+    """Extract a region from a cube on an irregular grid."""
     # Convert longitudes to valid range
     if start_longitude != 360.:
         start_longitude %= 360.
     if end_longitude != 360.:
         end_longitude %= 360.
 
+    # Select coordinates inside the region
+    lats = cube.coord('latitude').points
+    lons = (cube.coord('longitude').points + 360.) % 360.
     if start_longitude <= end_longitude:
         select_lons = (lons >= start_longitude) & (lons <= end_longitude)
     else:
@@ -84,8 +95,19 @@ def extract_region(cube, start_longitude, end_longitude, start_latitude,
         select_lats = (lats >= start_latitude) | (lats <= end_latitude)
 
     selection = select_lats & select_lons
-    selection = da.broadcast_to(selection, cube.shape)
-    cube.data = da.ma.masked_where(~selection, cube.core_data())
+
+    # Crop the selection, but keep rectangular shape
+    i_range, j_range = selection.nonzero()
+    if i_range.size == 0:
+        raise ValueError("No data points available in selected region")
+    i_min, i_max = i_range.min(), i_range.max()
+    j_min, j_max = j_range.min(), j_range.max()
+    i_slice, j_slice = slice(i_min, i_max + 1), slice(j_min, j_max + 1)
+    cube = cube[..., i_slice, j_slice]
+    selection = selection[i_slice, j_slice]
+    # Mask remaining coordinates outside region
+    mask = da.broadcast_to(~selection, cube.shape)
+    cube.data = da.ma.masked_where(mask, cube.core_data())
     return cube
 
 


Test patch:
diff --git a/tests/integration/cmor/_fixes/cmip6/test_cesm2.py b/tests/integration/cmor/_fixes/cmip6/test_cesm2.py
index 0d53f330d..212902dfe 100644
--- a/tests/integration/cmor/_fixes/cmip6/test_cesm2.py
+++ b/tests/integration/cmor/_fixes/cmip6/test_cesm2.py
@@ -14,9 +14,11 @@ from esmvalcore.cmor._fixes.cmip6.cesm2 import (
     Clw,
     Fgco2,
     Omon,
+    Siconc,
     Tas,
     Tos,
 )
+from esmvalcore.cmor._fixes.common import SiconcFixScalarCoord
 from esmvalcore.cmor.fix import Fix
 from esmvalcore.cmor.table import get_var_info
 
@@ -285,6 +287,12 @@ def test_get_fgco2_fix():
     assert fix == [Fgco2(None), Omon(None)]
 
 
+def test_get_siconc_fix():
+    """Test getting of fix."""
+    fix = Fix.get_fixes('CMIP6', 'CESM2', 'SImon', 'siconc')
+    assert fix == [Siconc(None)]
+
+
 def test_tas_fix_metadata(tas_cubes):
     """Test ``fix_metadata`` for ``tas``."""
     for cube in tas_cubes:
@@ -366,3 +374,8 @@ def test_fgco2_fix_metadata():
     # Check values of depth coordinate
     np.testing.assert_allclose(depth_coord.points, 0.0)
     assert depth_coord.bounds is None
+
+
+def test_siconc_fix():
+    """Test fix for ``siconc``."""
+    assert Siconc is SiconcFixScalarCoord
diff --git a/tests/integration/cmor/_fixes/cmip6/test_cesm2_fv2.py b/tests/integration/cmor/_fixes/cmip6/test_cesm2_fv2.py
index 016876388..4a5e04912 100644
--- a/tests/integration/cmor/_fixes/cmip6/test_cesm2_fv2.py
+++ b/tests/integration/cmor/_fixes/cmip6/test_cesm2_fv2.py
@@ -2,7 +2,15 @@
 from esmvalcore.cmor._fixes.cmip6.cesm2 import Cl as BaseCl
 from esmvalcore.cmor._fixes.cmip6.cesm2 import Fgco2 as BaseFgco2
 from esmvalcore.cmor._fixes.cmip6.cesm2 import Tas as BaseTas
-from esmvalcore.cmor._fixes.cmip6.cesm2_fv2 import Cl, Cli, Clw, Fgco2, Tas
+from esmvalcore.cmor._fixes.cmip6.cesm2_fv2 import (
+    Cl,
+    Cli,
+    Clw,
+    Fgco2,
+    Siconc,
+    Tas,
+)
+from esmvalcore.cmor._fixes.common import SiconcFixScalarCoord
 from esmvalcore.cmor.fix import Fix
 
 
@@ -50,6 +58,17 @@ def test_fgco2_fix():
     assert Fgco2 is BaseFgco2
 
 
+def test_get_siconc_fix():
+    """Test getting of fix."""
+    fix = Fix.get_fixes('CMIP6', 'CESM2-FV2', 'SImon', 'siconc')
+    assert fix == [Siconc(None)]
+
+
+def test_siconc_fix():
+    """Test fix for ``siconc``."""
+    assert Siconc is SiconcFixScalarCoord
+
+
 def test_get_tas_fix():
     """Test getting of fix."""
     fix = Fix.get_fixes('CMIP6', 'CESM2-FV2', 'Amon', 'tas')
diff --git a/tests/integration/cmor/_fixes/cmip6/test_cesm2_waccm.py b/tests/integration/cmor/_fixes/cmip6/test_cesm2_waccm.py
index 201e27575..5cc66aef7 100644
--- a/tests/integration/cmor/_fixes/cmip6/test_cesm2_waccm.py
+++ b/tests/integration/cmor/_fixes/cmip6/test_cesm2_waccm.py
@@ -10,7 +10,15 @@ import pytest
 from esmvalcore.cmor._fixes.cmip6.cesm2 import Cl as BaseCl
 from esmvalcore.cmor._fixes.cmip6.cesm2 import Fgco2 as BaseFgco2
 from esmvalcore.cmor._fixes.cmip6.cesm2 import Tas as BaseTas
-from esmvalcore.cmor._fixes.cmip6.cesm2_waccm import Cl, Cli, Clw, Fgco2, Tas
+from esmvalcore.cmor._fixes.cmip6.cesm2_waccm import (
+    Cl,
+    Cli,
+    Clw,
+    Fgco2,
+    Siconc,
+    Tas,
+)
+from esmvalcore.cmor._fixes.common import SiconcFixScalarCoord
 from esmvalcore.cmor.fix import Fix
 
 
@@ -84,6 +92,17 @@ def test_fgco2_fix():
     assert Fgco2 is BaseFgco2
 
 
+def test_get_siconc_fix():
+    """Test getting of fix."""
+    fix = Fix.get_fixes('CMIP6', 'CESM2-WACCM', 'SImon', 'siconc')
+    assert fix == [Siconc(None)]
+
+
+def test_siconc_fix():
+    """Test fix for ``siconc``."""
+    assert Siconc is SiconcFixScalarCoord
+
+
 @pytest.fixture
 def tas_cubes():
     """Cubes to test fixes for ``tas``."""
diff --git a/tests/integration/cmor/_fixes/cmip6/test_cesm2_waccm_fv2.py b/tests/integration/cmor/_fixes/cmip6/test_cesm2_waccm_fv2.py
index b2329020d..5aec25422 100644
--- a/tests/integration/cmor/_fixes/cmip6/test_cesm2_waccm_fv2.py
+++ b/tests/integration/cmor/_fixes/cmip6/test_cesm2_waccm_fv2.py
@@ -7,8 +7,10 @@ from esmvalcore.cmor._fixes.cmip6.cesm2_waccm_fv2 import (
     Cli,
     Clw,
     Fgco2,
+    Siconc,
     Tas,
 )
+from esmvalcore.cmor._fixes.common import SiconcFixScalarCoord
 from esmvalcore.cmor.fix import Fix
 
 
@@ -56,6 +58,17 @@ def test_fgco2_fix():
     assert Fgco2 is BaseFgco2
 
 
+def test_get_siconc_fix():
+    """Test getting of fix."""
+    fix = Fix.get_fixes('CMIP6', 'CESM2-WACCM-FV2', 'SImon', 'siconc')
+    assert fix == [Siconc(None)]
+
+
+def test_siconc_fix():
+    """Test fix for ``siconc``."""
+    assert Siconc is SiconcFixScalarCoord
+
+
 def test_get_tas_fix():
     """Test getting of fix."""
     fix = Fix.get_fixes('CMIP6', 'CESM2-WACCM-FV2', 'Amon', 'tas')
diff --git a/tests/integration/cmor/_fixes/cmip6/test_gfdl_cm4.py b/tests/integration/cmor/_fixes/cmip6/test_gfdl_cm4.py
index d813a4214..f53379a1d 100644
--- a/tests/integration/cmor/_fixes/cmip6/test_gfdl_cm4.py
+++ b/tests/integration/cmor/_fixes/cmip6/test_gfdl_cm4.py
@@ -3,7 +3,8 @@ import iris
 import numpy as np
 import pytest
 
-from esmvalcore.cmor._fixes.cmip6.gfdl_cm4 import Cl, Cli, Clw
+from esmvalcore.cmor._fixes.cmip6.gfdl_cm4 import Cl, Cli, Clw, Siconc
+from esmvalcore.cmor._fixes.common import SiconcFixScalarCoord
 from esmvalcore.cmor.fix import Fix
 from esmvalcore.cmor.table import get_var_info
 
@@ -90,3 +91,14 @@ def test_get_clw_fix():
 def test_clw_fix():
     """Test fix for ``clw``."""
     assert Clw is Cl
+
+
+def test_get_siconc_fix():
+    """Test getting of fix."""
+    fix = Fix.get_fixes('CMIP6', 'GFDL-CM4', 'SImon', 'siconc')
+    assert fix == [Siconc(None)]
+
+
+def test_siconc_fix():
+    """Test fix for ``siconc``."""
+    assert Siconc is SiconcFixScalarCoord
diff --git a/tests/integration/cmor/_fixes/cmip6/test_gfdl_esm4.py b/tests/integration/cmor/_fixes/cmip6/test_gfdl_esm4.py
index 2cad4a38b..d21323bb1 100644
--- a/tests/integration/cmor/_fixes/cmip6/test_gfdl_esm4.py
+++ b/tests/integration/cmor/_fixes/cmip6/test_gfdl_esm4.py
@@ -3,60 +3,22 @@
 import iris
 import numpy as np
 import pytest
-from cf_units import Unit
 
 from esmvalcore.cmor._fixes.cmip6.gfdl_esm4 import Fgco2, Omon, Siconc
+from esmvalcore.cmor._fixes.common import SiconcFixScalarCoord
 from esmvalcore.cmor.fix import Fix
 from esmvalcore.cmor.table import get_var_info
 
 
-@pytest.fixture
-def siconc_cubes():
-    """Sample cube."""
-    time_coord = iris.coords.DimCoord([0.0], standard_name='time',
-                                      var_name='time',
-                                      units='days since 6543-2-1')
-    lat_coord = iris.coords.DimCoord([-30.0], standard_name='latitude',
-                                     var_name='lat', units='degrees_north')
-    lon_coord = iris.coords.DimCoord([30.0], standard_name='longitude',
-                                     var_name='lon', units='degrees_east')
-    coords_specs = [(time_coord, 0), (lat_coord, 1), (lon_coord, 2)]
-    cube = iris.cube.Cube([[[22.0]]], standard_name='sea_ice_area_fraction',
-                          var_name='siconc', units='%',
-                          dim_coords_and_dims=coords_specs)
-    return iris.cube.CubeList([cube])
-
-
 def test_get_siconc_fix():
     """Test getting of fix."""
     fix = Fix.get_fixes('CMIP6', 'GFDL-ESM4', 'SImon', 'siconc')
     assert fix == [Siconc(None)]
 
 
-def test_siconc_fix_metadata(siconc_cubes):
-    """Test ``fix_metadata`` for ``cl``."""
-    assert len(siconc_cubes) == 1
-    siconc_cube = siconc_cubes[0]
-    assert siconc_cube.var_name == "siconc"
-
-    # Extract siconc cube
-    siconc_cube = siconc_cubes.extract_cube('sea_ice_area_fraction')
-    assert not siconc_cube.coords('typesi')
-
-    # Apply fix
-    vardef = get_var_info('CMIP6', 'SImon', 'siconc')
-    fix = Siconc(vardef)
-    fixed_cubes = fix.fix_metadata(siconc_cubes)
-    assert len(fixed_cubes) == 1
-    fixed_siconc_cube = fixed_cubes.extract_cube(
-        'sea_ice_area_fraction')
-    fixed_typesi_coord = fixed_siconc_cube.coord('area_type')
-    assert fixed_typesi_coord.points is not None
-    assert fixed_typesi_coord.bounds is None
-    np.testing.assert_equal(fixed_typesi_coord.points,
-                            ['siconc'])
-    np.testing.assert_equal(fixed_typesi_coord.units,
-                            Unit('1'))
+def test_siconc_fix():
+    """Test fix for ``siconc``."""
+    assert Siconc is SiconcFixScalarCoord
 
 
 @pytest.fixture
diff --git a/tests/integration/cmor/_fixes/cmip6/test_kiost_esm.py b/tests/integration/cmor/_fixes/cmip6/test_kiost_esm.py
index bbe1c360d..41de6089c 100644
--- a/tests/integration/cmor/_fixes/cmip6/test_kiost_esm.py
+++ b/tests/integration/cmor/_fixes/cmip6/test_kiost_esm.py
@@ -3,7 +3,12 @@ import iris
 import pytest
 from cf_units import Unit
 
-from esmvalcore.cmor._fixes.cmip6.kiost_esm import SfcWind, Tas
+from esmvalcore.cmor._fixes.cmip6.kiost_esm import (
+    SfcWind,
+    Siconc,
+    Tas,
+)
+from esmvalcore.cmor._fixes.common import SiconcFixScalarCoord
 from esmvalcore.cmor._fixes.fix import Fix
 from esmvalcore.cmor.table import get_var_info
 
@@ -97,6 +102,17 @@ def test_sfcwind_fix_metadata(sfcwind_cubes):
     assert coord == height_coord
 
 
+def test_get_siconc_fix():
+    """Test getting of fix."""
+    fix = Fix.get_fixes('CMIP6', 'KIOST-ESM', 'SImon', 'siconc')
+    assert fix == [Siconc(None)]
+
+
+def test_siconc_fix():
+    """Test fix for ``siconc``."""
+    assert Siconc is SiconcFixScalarCoord
+
+
 def test_get_tas_fix():
     fix = Fix.get_fixes('CMIP6', 'KIOST-ESM', 'Amon', 'tas')
     assert fix == [Tas(None)]
diff --git a/tests/integration/cmor/_fixes/test_common.py b/tests/integration/cmor/_fixes/test_common.py
index 42966fd0b..922f807fe 100644
--- a/tests/integration/cmor/_fixes/test_common.py
+++ b/tests/integration/cmor/_fixes/test_common.py
@@ -2,11 +2,13 @@
 import iris
 import numpy as np
 import pytest
+from cf_units import Unit
 
 from esmvalcore.cmor._fixes.common import (
     ClFixHybridHeightCoord,
     ClFixHybridPressureCoord,
     OceanFixGrid,
+    SiconcFixScalarCoord,
 )
 from esmvalcore.cmor.table import get_var_info
 from esmvalcore.iris_helpers import var_name_constraint
@@ -169,6 +171,49 @@ def test_cl_hybrid_height_coord_fix_metadata(test_data_path):
                                      ClFixHybridHeightCoord(vardef))
 
 
+@pytest.fixture
+def siconc_cubes():
+    """Sample cube."""
+    time_coord = iris.coords.DimCoord([0.0], standard_name='time',
+                                      var_name='time',
+                                      units='days since 6543-2-1')
+    lat_coord = iris.coords.DimCoord([-30.0], standard_name='latitude',
+                                     var_name='lat', units='degrees_north')
+    lon_coord = iris.coords.DimCoord([30.0], standard_name='longitude',
+                                     var_name='lon', units='degrees_east')
+    coords_specs = [(time_coord, 0), (lat_coord, 1), (lon_coord, 2)]
+    cube = iris.cube.Cube([[[22.0]]], standard_name='sea_ice_area_fraction',
+                          var_name='siconc', units='%',
+                          dim_coords_and_dims=coords_specs)
+    return iris.cube.CubeList([cube])
+
+
+def test_siconc_fix_metadata(siconc_cubes):
+    """Test ``fix_metadata`` for ``siconc``."""
+    assert len(siconc_cubes) == 1
+    siconc_cube = siconc_cubes[0]
+    assert siconc_cube.var_name == "siconc"
+
+    # Extract siconc cube
+    siconc_cube = siconc_cubes.extract_cube('sea_ice_area_fraction')
+    assert not siconc_cube.coords('typesi')
+
+    # Apply fix
+    vardef = get_var_info('CMIP6', 'SImon', 'siconc')
+    fix = SiconcFixScalarCoord(vardef)
+    fixed_cubes = fix.fix_metadata(siconc_cubes)
+    assert len(fixed_cubes) == 1
+    fixed_siconc_cube = fixed_cubes.extract_cube(
+        'sea_ice_area_fraction')
+    fixed_typesi_coord = fixed_siconc_cube.coord('area_type')
+    assert fixed_typesi_coord.points is not None
+    assert fixed_typesi_coord.bounds is None
+    np.testing.assert_equal(fixed_typesi_coord.points,
+                            ['sea_ice'])
+    np.testing.assert_equal(fixed_typesi_coord.units,
+                            Unit('No unit'))
+
+
 def get_tos_cubes(wrong_ij_names=False, ij_bounds=False):
     """Cubes containing tos variable."""
     if wrong_ij_names:
diff --git a/tests/integration/cmor/_fixes/test_shared.py b/tests/integration/cmor/_fixes/test_shared.py
index b185c7c92..69e9a0f09 100644
--- a/tests/integration/cmor/_fixes/test_shared.py
+++ b/tests/integration/cmor/_fixes/test_shared.py
@@ -12,6 +12,7 @@ from esmvalcore.cmor._fixes.shared import (
     add_scalar_height_coord,
     add_scalar_typeland_coord,
     add_scalar_typesea_coord,
+    add_scalar_typesi_coord,
     add_sigma_factory,
     cube_to_aux_coord,
     fix_bounds,
@@ -327,6 +328,33 @@ def test_add_scalar_typesea_coord(cube_in, typesea):
     assert coord == typesea_coord
 
 
+@pytest.mark.sequential
+@pytest.mark.parametrize('cube_in,typesi', TEST_ADD_SCALAR_COORD)
+def test_add_scalar_typesi_coord(cube_in, typesi):
+    """Test adding of scalar typesi coordinate."""
+    cube_in = cube_in.copy()
+    if typesi is None:
+        typesi = 'sea_ice'
+    typesi_coord = iris.coords.AuxCoord(typesi,
+                                        var_name='type',
+                                        standard_name='area_type',
+                                        long_name='Sea Ice area type',
+                                        units=Unit('no unit'))
+    with pytest.raises(iris.exceptions.CoordinateNotFoundError):
+        cube_in.coord('area_type')
+    if typesi == 'sea_ice':
+        cube_out = add_scalar_typesi_coord(cube_in)
+    else:
+        cube_out = add_scalar_typesi_coord(cube_in, typesi)
+    assert cube_out is cube_in
+    coord = cube_in.coord('area_type')
+    assert coord == typesi_coord
+    cube_out_2 = add_scalar_typesi_coord(cube_out)
+    assert cube_out_2 is cube_out
+    coord = cube_in.coord('area_type')
+    assert coord == typesi_coord
+
+
 PS_COORD = iris.coords.AuxCoord([[[101000.0]]], var_name='ps', units='Pa')
 PTOP_COORD = iris.coords.AuxCoord(1000.0, var_name='ptop', units='Pa')
 LEV_COORD = iris.coords.AuxCoord([0.5], bounds=[[0.2, 0.8]], var_name='lev',
diff --git a/tests/unit/preprocessor/_area/test_area.py b/tests/unit/preprocessor/_area/test_area.py
index 2a521dc68..96359502f 100644
--- a/tests/unit/preprocessor/_area/test_area.py
+++ b/tests/unit/preprocessor/_area/test_area.py
@@ -214,8 +214,14 @@ class Test(tests.Test):
 
 def create_irregular_grid_cube(data, lons, lats):
     """Create test cube on irregular grid."""
-    nlat = iris.coords.DimCoord(range(data.shape[0]), var_name='nlat')
-    nlon = iris.coords.DimCoord(range(data.shape[1]), var_name='nlon')
+    times = iris.coords.DimCoord(np.array([10, 20], dtype=np.float64),
+                                 standard_name='time',
+                                 units=Unit('days since 1950-01-01',
+                                            calendar='gregorian'))
+
+    # Construct cube
+    nlat = iris.coords.DimCoord(range(data.shape[1]), var_name='nlat')
+    nlon = iris.coords.DimCoord(range(data.shape[2]), var_name='nlon')
     lat = iris.coords.AuxCoord(lats,
                                var_name='lat',
                                standard_name='latitude',
@@ -225,12 +231,13 @@ def create_irregular_grid_cube(data, lons, lats):
                                standard_name='longitude',
                                units='degrees')
     dim_coord_spec = [
-        (nlat, 0),
-        (nlon, 1),
+        (times, 0),
+        (nlat, 1),
+        (nlon, 2),
     ]
     aux_coord_spec = [
-        (lat, [0, 1]),
-        (lon, [0, 1]),
+        (lat, [1, 2]),
+        (lon, [1, 2]),
     ]
     cube = iris.cube.Cube(
         data,
@@ -243,29 +250,58 @@ def create_irregular_grid_cube(data, lons, lats):
 
 
 IRREGULAR_EXTRACT_REGION_TESTS = [
+    {
+        'region': (100, 140, -10, 90),
+        'mask': np.array(
+            [
+                [False],
+                [False],
+            ],
+            dtype=bool,
+        ),
+        'data': np.arange(18, dtype=np.float32).reshape((2, 3, 3))[:, 1:3, 1:2]
+    },
+    {
+        'region': (100, 360, -60, 0),
+        'mask': np.array(
+            [
+                [True, False],
+                [False, False],
+            ],
+            dtype=bool,
+        ),
+        'data': np.arange(18, dtype=np.float32).reshape((2, 3, 3))[:, 0:2, 1:3]
+    },
     {
         'region': (10, 360, 0, 90),
-        'mask':
-        np.array(
+        'mask': np.array(
             [
-                [True, True, True],
-                [True, True, False],
-                [True, False, False],
+                [True, False],
+                [False, False],
             ],
             dtype=bool,
         ),
+        'data': np.arange(18, dtype=np.float32).reshape((2, 3, 3))[:, 1:, 1:]
+    },
+    {
+        'region': (0, 360, -90, -30),
+        'mask': np.array(
+            [
+                [False, False, False],
+            ],
+            dtype=bool,
+        ),
+        'data': np.arange(18, dtype=np.float32).reshape((2, 3, 3))[:, :1, :]
     },
     {
         'region': (200, 10, -90, -60),
-        'mask':
-        np.array(
+        'mask': np.array(
             [
                 [False, True, False],
-                [True, True, True],
-                [True, True, True],
             ],
             dtype=bool,
         ),
+        'data': np.arange(18, dtype=np.float32).reshape((2, 3, 3))[:, :1, :]
     },
     {
         'region': (-150, 50, 50, -50),
@@ -278,6 +314,8 @@ IRREGULAR_EXTRACT_REGION_TESTS = [
             ],
             dtype=bool,
         ),
+        'data':
+        np.arange(18, dtype=np.float32).reshape((2, 3, 3))
     },
     {
         'region': (0, 0, -100, 0),
@@ -293,7 +331,7 @@ IRREGULAR_EXTRACT_REGION_TESTS = [
 @pytest.fixture
 def irregular_extract_region_cube():
     """Create a test cube on an irregular grid to test `extract_region`."""
-    data = np.arange(9, dtype=np.float32).reshape((3, 3))
+    data = np.arange(18, dtype=np.float32).reshape((2, 3, 3))
     lons = np.array(
         [
             [0, 120, 240],
@@ -327,9 +365,9 @@ def test_extract_region_irregular(irregular_extract_region_cube, case):
             end_latitude=end_lat,
         )
 
-        data = np.arange(9, dtype=np.float32).reshape((3, 3))
-        np.testing.assert_array_equal(cube.data.mask, case['mask'])
-        np.testing.assert_array_equal(cube.data.data, data)
+        for i in range(2):
+            np.testing.assert_array_equal(cube.data[i].mask, case['mask'])
+        np.testing.assert_array_equal(cube.data.data, case['data'])
     else:
         with pytest.raises(ValueError) as exc:
             extract_region(
@@ -833,7 +871,7 @@ def test_extract_composite_shape_negative_bounds(make_testcube,
 @pytest.fixture
 def irreg_extract_shape_cube():
     """Create a test cube on an irregular grid to test `extract_shape`."""
-    data = np.arange(9, dtype=np.float32).reshape((3, 3))
+    data = np.arange(18, dtype=np.float32).reshape((2, 3, 3))
     lats = np.array(
         [
             [0.0, 0.0, 0.1],
@@ -870,7 +908,7 @@ def test_extract_shape_irregular(irreg_extract_shape_cube, tmp_path, method):
 
     cube = extract_shape(irreg_extract_shape_cube, shapefile, method)
 
-    data = np.arange(9, dtype=np.float32).reshape((3, 3))
+    data = np.arange(18, dtype=np.float32).reshape((2, 3, 3))
     mask = np.array(
         [
             [True, True, True],
@@ -882,7 +920,8 @@ def test_extract_shape_irregular(irreg_extract_shape_cube, tmp_path, method):
     if method == 'representative':
         mask[1, 1] = True
     np.testing.assert_array_equal(cube.data, data)
-    np.testing.assert_array_equal(cube.data.mask, mask)
+    for i in range(2):
+        np.testing.assert_array_equal(cube.data[i].mask, mask)
 
 
 def test_extract_shape_wrong_method_raises():