Repository: fmaussion/salem

Issue:
np.NaN removed from numpy 2.0.0
Hello,

I wanted to inform you that in the latest release of numpy 2.0.0, np.NaN has been removed (we should use np.nan instead). As a result, I encountered some issues when trying to use the Salem library.

Thanks,

Floriane

Discussion:
fmaussion: Thanks @benatouba - much appreciated. Failing tests seem unrelated - I need to investigate.

Patch:
diff --git a/docs/whats-new.rst b/docs/whats-new.rst
index 68114c6..024a22d 100644
--- a/docs/whats-new.rst
+++ b/docs/whats-new.rst
@@ -133,7 +133,7 @@ Enhancements
 
 - :py:func:`~transform_geopandas` can now handle grid to proj transformations.
 - Thw WRF vertical interpolation methods now accept a `fill_value` kwarg
-  which is `np.NaN` per default but can be set to `'extrapolate'` if
+  which is `np.nan` per default but can be set to `'extrapolate'` if
   needed.
 - New :py:func:`~reduce` function, useful to aggregate structured high-res
   grids to lower-res grids (:ref:`sphx_glr_auto_examples_plot_subgrid_mask.py`)
diff --git a/salem/gis.py b/salem/gis.py
index 16b32c3..27ff56d 100644
--- a/salem/gis.py
+++ b/salem/gis.py
@@ -835,10 +835,11 @@ class Grid(object):
         out_shape = list(in_shape)
         out_shape[-2:] = [self.ny, self.nx]
 
-        if data.dtype.kind == 'i':
-            out_data = np.zeros(out_shape, dtype=float) * np.NaN
-        else:
-            out_data = np.zeros(out_shape, dtype=data.dtype) * np.NaN
+        out_data = np.full(
+            out_shape,
+            np.nan,
+            dtype=float if data.dtype.kind == 'i' else data.dtype,
+        )
 
         def _2d_trafo(ind, outd):
             for ji, l in lut.items():
diff --git a/salem/graphics.py b/salem/graphics.py
index fa512ad..9f61582 100644
--- a/salem/graphics.py
+++ b/salem/graphics.py
@@ -515,14 +515,15 @@ class Map(DataLevels):
                     warnings.filterwarnings("ignore", message=mess)
                     mess = "Passing `np.nan` to mean no clipping in np.clip"
                     warnings.filterwarnings("ignore", message=mess)
+                    nans = data.filled(np.nan)
                     try:
-                        data = imresize(data.filled(np.NaN),
+                        data = imresize(nans,
                                         (self.grid.ny, self.grid.nx),
                                         order=interp, mode='edge',
                                         anti_aliasing=True)
                     except RuntimeError:
                         # For some order anti_aliasing doesnt work with 'edge'
-                        data = imresize(data.filled(np.NaN),
+                        data = imresize(nans,
                                         (self.grid.ny, self.grid.nx),
                                         order=interp, mode='edge',
                                         anti_aliasing=False)
diff --git a/salem/sio.py b/salem/sio.py
index a6ca87a..a8fda32 100644
--- a/salem/sio.py
+++ b/salem/sio.py
@@ -747,7 +747,7 @@ class DataArrayAccessor(_XarrayAccessorBase):
 
         return out
 
-    def interpz(self, zcoord, levels, dim_name='', fill_value=np.NaN,
+    def interpz(self, zcoord, levels, dim_name='', fill_value=np.nan,
                 use_multiprocessing=True):
         """Interpolates the array along the vertical dimension
 
@@ -759,7 +759,7 @@ class DataArrayAccessor(_XarrayAccessorBase):
           the levels at which to interpolate
         dim_name: str
           the name of the new dimension
-        fill_value : np.NaN or 'extrapolate', optional
+        fill_value : np.nan or 'extrapolate', optional
           how to handle levels below the topography. Default is to mark them
           as invalid, but you might want the have them extrapolated.
         use_multiprocessing: bool
@@ -835,7 +835,7 @@ class DatasetAccessor(_XarrayAccessorBase):
                     new_name = v
                 self._obj[new_name] = out[v]
 
-    def wrf_zlevel(self, varname, levels=None, fill_value=np.NaN,
+    def wrf_zlevel(self, varname, levels=None, fill_value=np.nan,
                    use_multiprocessing=True):
         """Interpolates to a specified height above sea level.
 
@@ -845,7 +845,7 @@ class DatasetAccessor(_XarrayAccessorBase):
           the name of the variable to interpolate
         levels: 1d array
           levels at which to interpolate (default: some levels I thought of)
-        fill_value : np.NaN or 'extrapolate', optional
+        fill_value : np.nan or 'extrapolate', optional
           how to handle levels below the topography. Default is to mark them
           as invalid, but you might want the have them extrapolated.
         use_multiprocessing: bool
@@ -867,7 +867,7 @@ class DatasetAccessor(_XarrayAccessorBase):
         out['z'].attrs['units'] = 'm'
         return out
 
-    def wrf_plevel(self, varname, levels=None, fill_value=np.NaN,
+    def wrf_plevel(self, varname, levels=None, fill_value=np.nan,
                    use_multiprocessing=True):
         """Interpolates to a specified pressure level (hPa).
 
@@ -877,7 +877,7 @@ class DatasetAccessor(_XarrayAccessorBase):
           the name of the variable to interpolate
         levels: 1d array
           levels at which to interpolate (default: some levels I thought of)
-        fill_value : np.NaN or 'extrapolate', optional
+        fill_value : np.nan or 'extrapolate', optional
           how to handle levels below the topography. Default is to mark them
           as invalid, but you might want the have them extrapolated.
         use_multiprocessing: bool
diff --git a/salem/wrftools.py b/salem/wrftools.py
index f2749cc..8b95d32 100644
--- a/salem/wrftools.py
+++ b/salem/wrftools.py
@@ -286,7 +286,7 @@ class AccumulatedVariable(FakeVariable):
                 except:
                     pass
                 out[1:, ...] -= var[item]
-                out[0, ...] = np.NaN
+                out[0, ...] = np.nan
             else:
                 out = var[itemr]
                 out -= var[item]
@@ -508,7 +508,7 @@ def _interp1d(args):
     return f(args[2])
 
 
-def interp3d(data, zcoord, levels, fill_value=np.NaN,
+def interp3d(data, zcoord, levels, fill_value=np.nan,
              use_multiprocessing=True):
     """Interpolate on the first dimension of a 3d var
 
@@ -522,7 +522,7 @@ def interp3d(data, zcoord, levels, fill_value=np.NaN,
       same dims as data, the z coordinates of the data points
     levels: 1darray
       the levels at which to interpolate
-    fill_value : np.NaN or 'extrapolate', optional
+    fill_value : np.nan or 'extrapolate', optional
       how to handle levels below the topography. Default is to mark them
       as invalid, but you might want the have them extrapolated.
     use_multiprocessing: bool


Test patch:
diff --git a/salem/tests/test_gis.py b/salem/tests/test_gis.py
index 5f7eb25..f73c956 100644
--- a/salem/tests/test_gis.py
+++ b/salem/tests/test_gis.py
@@ -706,7 +706,7 @@ class TestGrid(unittest.TestCase):
             nx, ny = (4, 5)
             data = np.arange(nx * ny).reshape((ny, nx)).astype(float)
 
-            in_data = data * np.NaN
+            in_data = data * np.nan
             in_data[0, :] = 78
 
             # Nearest Neighbor
@@ -823,13 +823,13 @@ class TestGrid(unittest.TestCase):
         odata = grid_to.map_gridded_data(data, grid_from, interp='linear')
         # At the borders IDL and Python take other decision on wether it
         # should be a NaN or not (Python seems to be more conservative)
-        ref_data[np.where(odata.mask)] = np.NaN
+        ref_data[np.where(odata.mask)] = np.nan
         assert np.sum(np.isfinite(ref_data)) != 0
-        assert_allclose(ref_data, odata.filled(np.NaN), atol=1e-3)
+        assert_allclose(ref_data, odata.filled(np.nan), atol=1e-3)
 
         odata = grid_to.map_gridded_data(data, grid_from, interp='spline')
-        odata[np.where(~ np.isfinite(ref_data))] = np.NaN
-        ref_data[np.where(~ np.isfinite(odata))] = np.NaN
+        odata[np.where(~ np.isfinite(ref_data))] = np.nan
+        ref_data[np.where(~ np.isfinite(odata))] = np.nan
         assert np.sum(np.isfinite(ref_data)) != 0
         assert_allclose(ref_data, odata, rtol=0.2, atol=3)
 
@@ -837,14 +837,14 @@ class TestGrid(unittest.TestCase):
         data = np.array([data, data])
         ref_data = np.array([ref_data, ref_data])
         odata = grid_to.map_gridded_data(data, grid_from, interp='linear')
-        odata = odata.filled(np.NaN)
-        ref_data[np.where(~ np.isfinite(odata))] = np.NaN
+        odata = odata.filled(np.nan)
+        ref_data[np.where(~ np.isfinite(odata))] = np.nan
         assert np.sum(np.isfinite(ref_data)) != 0
         assert_allclose(ref_data, odata, atol=1e-3)
 
         odata = grid_to.map_gridded_data(data, grid_from, interp='spline')
-        odata[np.where(~ np.isfinite(ref_data))] = np.NaN
-        ref_data[np.where(~ np.isfinite(odata))] = np.NaN
+        odata[np.where(~ np.isfinite(ref_data))] = np.nan
+        ref_data[np.where(~ np.isfinite(odata))] = np.nan
         assert np.sum(np.isfinite(ref_data)) != 0
         assert_allclose(ref_data, odata, rtol=0.2, atol=3)
 
diff --git a/salem/tests/test_graphics.py b/salem/tests/test_graphics.py
index 03cb193..68c186c 100644
--- a/salem/tests/test_graphics.py
+++ b/salem/tests/test_graphics.py
@@ -327,7 +327,7 @@ class TestGraphics(unittest.TestCase):
         cmap.set_bad('pink')
 
         # Add masked arrays
-        a[1, 1] = np.NaN
+        a[1, 1] = np.nan
         c.set_data(a)
         rgb1 = c.to_rgb()
         c.set_data(a, crs=g)
@@ -436,7 +436,7 @@ def test_datalevels():
     c.visualize(next(ax), title="Auto levels with oob data")
 
     # Missing data
-    a[3, 0] = np.NaN
+    a[3, 0] = np.nan
     c = DataLevels(nlevels=127, vmin=0, vmax=3, data=a, cmap=cm)
     c.visualize(next(ax), title="missing data")
 
@@ -801,7 +801,7 @@ def test_hef_topo_withnan():
     c.set_cmap(get_cmap('topo'))
     c.set_plot_params(nlevels=256)
     # Try with nan data
-    h[-100:, -100:] = np.NaN
+    h[-100:, -100:] = np.nan
     c.set_data(h)
     fig, ax = plt.subplots(1, 1)
     c.visualize(ax=ax, title='color with NaN')
diff --git a/salem/tests/test_misc.py b/salem/tests/test_misc.py
index 3ae5721..1cc437c 100644
--- a/salem/tests/test_misc.py
+++ b/salem/tests/test_misc.py
@@ -274,8 +274,8 @@ class TestXarray(unittest.TestCase):
         import xarray as xr
         # Check that all attrs are preserved
         with sio.open_xr_dataset(get_demo_file('era_interim_tibet.nc')) as ds:
-            ds.encoding = {'_FillValue': np.NaN}
-            ds['t2m'].encoding = {'_FillValue': np.NaN}
+            ds.encoding = {'_FillValue': np.nan}
+            ds['t2m'].encoding = {'_FillValue': np.nan}
             ds_ = ds.salem.roi(roi=np.ones_like(ds.t2m.values[0, ...]))
             xr.testing.assert_identical(ds, ds_)
             assert ds.encoding == ds_.encoding
@@ -524,13 +524,13 @@ class TestXarray(unittest.TestCase):
         uns = nc['RAINNC'].isel(Time=slice(1, len(nc.bottom_top_stag))).values - \
               nc['RAINNC'].isel(Time=slice(0, -1)).values
         nc['REF_PRCP_NC'].values[1:, ...] = uns * 60 / 180.  # for three hours
-        nc['REF_PRCP_NC'].values[0, ...] = np.NaN
+        nc['REF_PRCP_NC'].values[0, ...] = np.nan
 
         nc['REF_PRCP_C'] = nc['RAINC']*0.
         uns = nc['RAINC'].isel(Time=slice(1, len(nc.bottom_top_stag))).values - \
               nc['RAINC'].isel(Time=slice(0, -1)).values
         nc['REF_PRCP_C'].values[1:, ...] = uns * 60 / 180.  # for three hours
-        nc['REF_PRCP_C'].values[0, ...] = np.NaN
+        nc['REF_PRCP_C'].values[0, ...] = np.nan
 
         nc['REF_PRCP'] = nc['REF_PRCP_C'] + nc['REF_PRCP_NC']