Skip to content
forked from pydata/xarray

Commit c94d9c2

Browse files
committed
Merge branch 'main' into grouper-public-api
* main: exclude the bots from the release notes (pydata#9235) switch the documentation to run with `numpy>=2` (pydata#9177) `numpy` 2 compatibility in the iris code paths (pydata#9156) `numpy` 2 compatibility in the `netcdf4` and `h5netcdf` backends (pydata#9136) Fix time indexing regression in `convert_calendar` (pydata#9192) Use duckarray assertions in test_coding_times (pydata#9226) Use reshape and ravel from duck_array_ops in coding/times.py (pydata#9225) Cleanup test_coding_times.py (pydata#9223) Only use necessary dims when creating temporary dataarray (pydata#9206) Fix two bugs in DataTree.update() (pydata#9214) Use numpy 2.0-compat `np.complex64` dtype in test (pydata#9217)
2 parents 1142663 + a69815f commit c94d9c2

25 files changed

+275
-172
lines changed

.github/release.yml

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
changelog:
2+
exclude:
3+
authors:
4+
- dependabot
5+
- pre-commit-ci

ci/install-upstream-wheels.sh

+2-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ $conda remove -y numba numbagg sparse
1313
# temporarily remove numexpr
1414
$conda remove -y numexpr
1515
# temporarily remove backends
16-
$conda remove -y cf_units hdf5 h5py netcdf4 pydap
16+
$conda remove -y pydap
1717
# forcibly remove packages to avoid artifacts
1818
$conda remove -y --force \
1919
numpy \
@@ -37,8 +37,7 @@ python -m pip install \
3737
numpy \
3838
scipy \
3939
matplotlib \
40-
pandas \
41-
h5py
40+
pandas
4241
# for some reason pandas depends on pyarrow already.
4342
# Remove once a `pyarrow` version compiled with `numpy>=2.0` is on `conda-forge`
4443
python -m pip install \

ci/requirements/all-but-dask.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ dependencies:
2222
- netcdf4
2323
- numba
2424
- numbagg
25-
- numpy<2
25+
- numpy
2626
- packaging
2727
- pandas
2828
- pint>=0.22

ci/requirements/doc.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ dependencies:
2121
- nbsphinx
2222
- netcdf4>=1.5
2323
- numba
24-
- numpy>=1.21,<2
24+
- numpy>=2
2525
- packaging>=21.3
2626
- pandas>=1.4,!=2.1.0
2727
- pooch

ci/requirements/environment-windows.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ dependencies:
2323
- netcdf4
2424
- numba
2525
- numbagg
26-
- numpy<2
26+
- numpy
2727
- packaging
2828
- pandas
2929
# - pint>=0.22

ci/requirements/environment.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ dependencies:
2626
- numba
2727
- numbagg
2828
- numexpr
29-
- numpy<2
29+
- numpy
3030
- opt_einsum
3131
- packaging
3232
- pandas

doc/getting-started-guide/faq.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -352,9 +352,9 @@ Some packages may have additional functionality beyond what is shown here. You c
352352
How does xarray handle missing values?
353353
--------------------------------------
354354

355-
**xarray can handle missing values using ``np.NaN``**
355+
**xarray can handle missing values using ``np.nan``**
356356

357-
- ``np.NaN`` is used to represent missing values in labeled arrays and datasets. It is a commonly used standard for representing missing or undefined numerical data in scientific computing. ``np.NaN`` is a constant value in NumPy that represents "Not a Number" or missing values.
357+
- ``np.nan`` is used to represent missing values in labeled arrays and datasets. It is a commonly used standard for representing missing or undefined numerical data in scientific computing. ``np.nan`` is a constant value in NumPy that represents "Not a Number" or missing values.
358358

359359
- Most of xarray's computation methods are designed to automatically handle missing values appropriately.
360360

doc/user-guide/computation.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,7 @@ However, the functions also take missing values in the data into account:
426426

427427
.. ipython:: python
428428
429-
data = xr.DataArray([np.NaN, 2, 4])
429+
data = xr.DataArray([np.nan, 2, 4])
430430
weights = xr.DataArray([8, 1, 1])
431431
432432
data.weighted(weights).mean()
@@ -444,7 +444,7 @@ If the weights add up to to 0, ``sum`` returns 0:
444444
445445
data.weighted(weights).sum()
446446
447-
and ``mean``, ``std`` and ``var`` return ``NaN``:
447+
and ``mean``, ``std`` and ``var`` return ``nan``:
448448

449449
.. ipython:: python
450450

doc/user-guide/interpolation.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -292,8 +292,8 @@ Let's see how :py:meth:`~xarray.DataArray.interp` works on real data.
292292
axes[0].set_title("Raw data")
293293
294294
# Interpolated data
295-
new_lon = np.linspace(ds.lon[0], ds.lon[-1], ds.sizes["lon"] * 4)
296-
new_lat = np.linspace(ds.lat[0], ds.lat[-1], ds.sizes["lat"] * 4)
295+
new_lon = np.linspace(ds.lon[0].item(), ds.lon[-1].item(), ds.sizes["lon"] * 4)
296+
new_lat = np.linspace(ds.lat[0].item(), ds.lat[-1].item(), ds.sizes["lat"] * 4)
297297
dsi = ds.interp(lat=new_lat, lon=new_lon)
298298
dsi.air.plot(ax=axes[1])
299299
@savefig interpolation_sample3.png width=8in

doc/user-guide/testing.rst

+3-2
Original file line numberDiff line numberDiff line change
@@ -239,9 +239,10 @@ If the array type you want to generate has an array API-compliant top-level name
239239
you can use this neat trick:
240240

241241
.. ipython:: python
242-
:okwarning:
243242
244-
from numpy import array_api as xp # available in numpy 1.26.0
243+
import numpy as xp # compatible in numpy 2.0
244+
245+
# use `import numpy.array_api as xp` in numpy>=1.23,<2.0
245246
246247
from hypothesis.extra.array_api import make_strategies_namespace
247248

doc/whats-new.rst

+11-1
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,24 @@ Deprecations
3737

3838
Bug fixes
3939
~~~~~~~~~
40+
- Fix scatter plot broadcasting unneccesarily. (:issue:`9129`, :pull:`9206`)
41+
By `Jimmy Westling <https://github.com/illviljan>`_.
4042
- Don't convert custom indexes to ``pandas`` indexes when computing a diff (:pull:`9157`)
4143
By `Justus Magin <https://github.com/keewis>`_.
4244
- Make :py:func:`testing.assert_allclose` work with numpy 2.0 (:issue:`9165`, :pull:`9166`).
4345
By `Pontus Lurcock <https://github.com/pont-us>`_.
4446
- Allow diffing objects with array attributes on variables (:issue:`9153`, :pull:`9169`).
4547
By `Justus Magin <https://github.com/keewis>`_.
48+
- ``numpy>=2`` compatibility in the ``netcdf4`` backend (:pull:`9136`).
49+
By `Justus Magin <https://github.com/keewis>`_ and `Kai Mühlbauer <https://github.com/kmuehlbauer>`_.
4650
- Promote floating-point numeric datetimes before decoding (:issue:`9179`, :pull:`9182`).
4751
By `Justus Magin <https://github.com/keewis>`_.
52+
- Address regression introduced in :pull:`9002` that prevented objects returned
53+
by py:meth:`DataArray.convert_calendar` to be indexed by a time index in
54+
certain circumstances (:issue:`9138`, :pull:`9192`). By `Mark Harfouche
55+
<https://github.com/hmaarrfk>`_ and `Spencer Clark
56+
<https://github.com/spencerkclark>`.
57+
4858
- Fiy static typing of tolerance arguments by allowing `str` type (:issue:`8892`, :pull:`9194`).
4959
By `Michael Niklas <https://github.com/headtr1ck>`_.
5060
- Dark themes are now properly detected for ``html[data-theme=dark]``-tags (:pull:`9200`).
@@ -59,7 +69,7 @@ Documentation
5969
- Adds a flow-chart diagram to help users navigate help resources (`Discussion #8990 <https://github.com/pydata/xarray/discussions/8990>`_).
6070
By `Jessica Scheick <https://github.com/jessicas11>`_.
6171
- Improvements to Zarr & chunking docs (:pull:`9139`, :pull:`9140`, :pull:`9132`)
62-
By `Maximilian Roos <https://github.com/max-sixty>`_
72+
By `Maximilian Roos <https://github.com/max-sixty>`_.
6373

6474

6575
Internal Changes

xarray/coding/calendar_ops.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55

66
from xarray.coding.cftime_offsets import date_range_like, get_date_type
77
from xarray.coding.cftimeindex import CFTimeIndex
8-
from xarray.coding.times import _should_cftime_be_used, convert_times
8+
from xarray.coding.times import (
9+
_should_cftime_be_used,
10+
convert_times,
11+
)
912
from xarray.core.common import _contains_datetime_like_objects, is_np_datetime_like
1013

1114
try:
@@ -222,6 +225,13 @@ def convert_calendar(
222225
# Remove NaN that where put on invalid dates in target calendar
223226
out = out.where(out[dim].notnull(), drop=True)
224227

228+
if use_cftime:
229+
# Reassign times to ensure time index of output is a CFTimeIndex
230+
# (previously it was an Index due to the presence of NaN values).
231+
# Note this is not needed in the case that the output time index is
232+
# a DatetimeIndex, since DatetimeIndexes can handle NaN values.
233+
out[dim] = CFTimeIndex(out[dim].data)
234+
225235
if missing is not None:
226236
time_target = date_range_like(time, calendar=calendar, use_cftime=use_cftime)
227237
out = out.reindex({dim: time_target}, fill_value=missing)

xarray/coding/times.py

+12-12
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
)
2323
from xarray.core import indexing
2424
from xarray.core.common import contains_cftime_datetimes, is_np_datetime_like
25-
from xarray.core.duck_array_ops import asarray
25+
from xarray.core.duck_array_ops import asarray, ravel, reshape
2626
from xarray.core.formatting import first_n_items, format_timestamp, last_item
2727
from xarray.core.pdcompat import nanosecond_precision_timestamp
2828
from xarray.core.utils import emit_user_level_warning
@@ -315,7 +315,7 @@ def decode_cf_datetime(
315315
cftime.num2date
316316
"""
317317
num_dates = np.asarray(num_dates)
318-
flat_num_dates = num_dates.ravel()
318+
flat_num_dates = ravel(num_dates)
319319
if calendar is None:
320320
calendar = "standard"
321321

@@ -348,7 +348,7 @@ def decode_cf_datetime(
348348
else:
349349
dates = _decode_datetime_with_pandas(flat_num_dates, units, calendar)
350350

351-
return dates.reshape(num_dates.shape)
351+
return reshape(dates, num_dates.shape)
352352

353353

354354
def to_timedelta_unboxed(value, **kwargs):
@@ -369,8 +369,8 @@ def decode_cf_timedelta(num_timedeltas, units: str) -> np.ndarray:
369369
"""
370370
num_timedeltas = np.asarray(num_timedeltas)
371371
units = _netcdf_to_numpy_timeunit(units)
372-
result = to_timedelta_unboxed(num_timedeltas.ravel(), unit=units)
373-
return result.reshape(num_timedeltas.shape)
372+
result = to_timedelta_unboxed(ravel(num_timedeltas), unit=units)
373+
return reshape(result, num_timedeltas.shape)
374374

375375

376376
def _unit_timedelta_cftime(units: str) -> timedelta:
@@ -428,7 +428,7 @@ def infer_datetime_units(dates) -> str:
428428
'hours', 'minutes' or 'seconds' (the first one that can evenly divide all
429429
unique time deltas in `dates`)
430430
"""
431-
dates = np.asarray(dates).ravel()
431+
dates = ravel(np.asarray(dates))
432432
if np.asarray(dates).dtype == "datetime64[ns]":
433433
dates = to_datetime_unboxed(dates)
434434
dates = dates[pd.notnull(dates)]
@@ -456,7 +456,7 @@ def infer_timedelta_units(deltas) -> str:
456456
{'days', 'hours', 'minutes' 'seconds'} (the first one that can evenly
457457
divide all unique time deltas in `deltas`)
458458
"""
459-
deltas = to_timedelta_unboxed(np.asarray(deltas).ravel())
459+
deltas = to_timedelta_unboxed(ravel(np.asarray(deltas)))
460460
unique_timedeltas = np.unique(deltas[pd.notnull(deltas)])
461461
return _infer_time_units_from_diff(unique_timedeltas)
462462

@@ -643,7 +643,7 @@ def encode_datetime(d):
643643
except TypeError:
644644
return np.nan if d is None else cftime.date2num(d, units, calendar)
645645

646-
return np.array([encode_datetime(d) for d in dates.ravel()]).reshape(dates.shape)
646+
return reshape(np.array([encode_datetime(d) for d in ravel(dates)]), dates.shape)
647647

648648

649649
def cast_to_int_if_safe(num) -> np.ndarray:
@@ -753,7 +753,7 @@ def _eagerly_encode_cf_datetime(
753753
# Wrap the dates in a DatetimeIndex to do the subtraction to ensure
754754
# an OverflowError is raised if the ref_date is too far away from
755755
# dates to be encoded (GH 2272).
756-
dates_as_index = pd.DatetimeIndex(dates.ravel())
756+
dates_as_index = pd.DatetimeIndex(ravel(dates))
757757
time_deltas = dates_as_index - ref_date
758758

759759
# retrieve needed units to faithfully encode to int64
@@ -791,7 +791,7 @@ def _eagerly_encode_cf_datetime(
791791
floor_division = True
792792

793793
num = _division(time_deltas, time_delta, floor_division)
794-
num = num.values.reshape(dates.shape)
794+
num = reshape(num.values, dates.shape)
795795

796796
except (OutOfBoundsDatetime, OverflowError, ValueError):
797797
num = _encode_datetime_with_cftime(dates, units, calendar)
@@ -879,7 +879,7 @@ def _eagerly_encode_cf_timedelta(
879879
units = data_units
880880

881881
time_delta = _time_units_to_timedelta64(units)
882-
time_deltas = pd.TimedeltaIndex(timedeltas.ravel())
882+
time_deltas = pd.TimedeltaIndex(ravel(timedeltas))
883883

884884
# retrieve needed units to faithfully encode to int64
885885
needed_units = data_units
@@ -911,7 +911,7 @@ def _eagerly_encode_cf_timedelta(
911911
floor_division = True
912912

913913
num = _division(time_deltas, time_delta, floor_division)
914-
num = num.values.reshape(timedeltas.shape)
914+
num = reshape(num.values, timedeltas.shape)
915915

916916
if dtype is not None:
917917
num = _cast_to_dtype_if_safe(num, dtype)

xarray/coding/variables.py

+10-6
Original file line numberDiff line numberDiff line change
@@ -516,10 +516,13 @@ def encode(self, variable: Variable, name: T_Name = None) -> Variable:
516516
dims, data, attrs, encoding = unpack_for_encoding(variable)
517517

518518
pop_to(encoding, attrs, "_Unsigned")
519-
signed_dtype = np.dtype(f"i{data.dtype.itemsize}")
519+
# we need the on-disk type here
520+
# trying to get it from encoding, resort to an int with the same precision as data.dtype if not available
521+
signed_dtype = np.dtype(encoding.get("dtype", f"i{data.dtype.itemsize}"))
520522
if "_FillValue" in attrs:
521-
new_fill = signed_dtype.type(attrs["_FillValue"])
522-
attrs["_FillValue"] = new_fill
523+
new_fill = np.array(attrs["_FillValue"])
524+
# use view here to prevent OverflowError
525+
attrs["_FillValue"] = new_fill.view(signed_dtype).item()
523526
data = duck_array_ops.astype(duck_array_ops.around(data), signed_dtype)
524527

525528
return Variable(dims, data, attrs, encoding, fastpath=True)
@@ -535,10 +538,11 @@ def decode(self, variable: Variable, name: T_Name = None) -> Variable:
535538
if unsigned == "true":
536539
unsigned_dtype = np.dtype(f"u{data.dtype.itemsize}")
537540
transform = partial(np.asarray, dtype=unsigned_dtype)
538-
data = lazy_elemwise_func(data, transform, unsigned_dtype)
539541
if "_FillValue" in attrs:
540-
new_fill = unsigned_dtype.type(attrs["_FillValue"])
541-
attrs["_FillValue"] = new_fill
542+
new_fill = np.array(attrs["_FillValue"], dtype=data.dtype)
543+
# use view here to prevent OverflowError
544+
attrs["_FillValue"] = new_fill.view(unsigned_dtype).item()
545+
data = lazy_elemwise_func(data, transform, unsigned_dtype)
542546
elif data.dtype.kind == "u":
543547
if unsigned == "false":
544548
signed_dtype = np.dtype(f"i{data.dtype.itemsize}")

xarray/core/computation.py

+1-46
Original file line numberDiff line numberDiff line change
@@ -1064,7 +1064,7 @@ def apply_ufunc(
10641064
supported:
10651065
10661066
>>> magnitude(3, 4)
1067-
5.0
1067+
np.float64(5.0)
10681068
>>> magnitude(3, np.array([0, 4]))
10691069
array([3., 5.])
10701070
>>> magnitude(array, 0)
@@ -1587,15 +1587,6 @@ def cross(
15871587
array([-3, 6, -3])
15881588
Dimensions without coordinates: dim_0
15891589
1590-
Vector cross-product with 2 dimensions, returns in the perpendicular
1591-
direction:
1592-
1593-
>>> a = xr.DataArray([1, 2])
1594-
>>> b = xr.DataArray([4, 5])
1595-
>>> xr.cross(a, b, dim="dim_0")
1596-
<xarray.DataArray ()> Size: 8B
1597-
array(-3)
1598-
15991590
Vector cross-product with 3 dimensions but zeros at the last axis
16001591
yields the same results as with 2 dimensions:
16011592
@@ -1606,42 +1597,6 @@ def cross(
16061597
array([ 0, 0, -3])
16071598
Dimensions without coordinates: dim_0
16081599
1609-
One vector with dimension 2:
1610-
1611-
>>> a = xr.DataArray(
1612-
... [1, 2],
1613-
... dims=["cartesian"],
1614-
... coords=dict(cartesian=(["cartesian"], ["x", "y"])),
1615-
... )
1616-
>>> b = xr.DataArray(
1617-
... [4, 5, 6],
1618-
... dims=["cartesian"],
1619-
... coords=dict(cartesian=(["cartesian"], ["x", "y", "z"])),
1620-
... )
1621-
>>> xr.cross(a, b, dim="cartesian")
1622-
<xarray.DataArray (cartesian: 3)> Size: 24B
1623-
array([12, -6, -3])
1624-
Coordinates:
1625-
* cartesian (cartesian) <U1 12B 'x' 'y' 'z'
1626-
1627-
One vector with dimension 2 but coords in other positions:
1628-
1629-
>>> a = xr.DataArray(
1630-
... [1, 2],
1631-
... dims=["cartesian"],
1632-
... coords=dict(cartesian=(["cartesian"], ["x", "z"])),
1633-
... )
1634-
>>> b = xr.DataArray(
1635-
... [4, 5, 6],
1636-
... dims=["cartesian"],
1637-
... coords=dict(cartesian=(["cartesian"], ["x", "y", "z"])),
1638-
... )
1639-
>>> xr.cross(a, b, dim="cartesian")
1640-
<xarray.DataArray (cartesian: 3)> Size: 24B
1641-
array([-10, 2, 5])
1642-
Coordinates:
1643-
* cartesian (cartesian) <U1 12B 'x' 'y' 'z'
1644-
16451600
Multiple vector cross-products. Note that the direction of the
16461601
cross product vector is defined by the right-hand rule:
16471602

0 commit comments

Comments
 (0)