diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md
new file mode 100644
index 00000000000..02bc5d0f7b0
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug-report.md
@@ -0,0 +1,39 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+
+
+**What happened**:
+
+**What you expected to happen**:
+
+**Minimal Complete Verifiable Example**:
+
+```python
+# Put your MCVE code here
+```
+
+**Anything else we need to know?**:
+
+**Environment**:
+
+Output of xr.show_versions()
+
+
+
+
+
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
deleted file mode 100644
index c712cf27979..00000000000
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ /dev/null
@@ -1,35 +0,0 @@
----
-name: Bug report / Feature request
-about: 'Post a problem or idea'
-title: ''
-labels: ''
-assignees: ''
-
----
-
-
-
-
-#### MCVE Code Sample
-
-
-```python
-# Your code here
-
-```
-
-#### Expected Output
-
-
-#### Problem Description
-
-
-
-#### Versions
-
-Output of xr.show_versions()
-
-
-
-
-
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 00000000000..3389fbfe071
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,5 @@
+blank_issues_enabled: true
+contact_links:
+ - name: General Question
+ url: https://stackoverflow.com/questions/tagged/python-xarray
+ about: "If you have a question like *How do I append to an xarray.Dataset?* then please ask on Stack Overflow using the #python-xarray tag."
diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md
new file mode 100644
index 00000000000..7021fe490aa
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature-request.md
@@ -0,0 +1,22 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context about the feature request here.
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index a921bddaa23..c9c0b720c35 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -3,4 +3,5 @@
- [ ] Closes #xxxx
- [ ] Tests added
- [ ] Passes `isort -rc . && black . && mypy . && flake8`
- - [ ] Fully documented, including `whats-new.rst` for all changes and `api.rst` for new API
+ - [ ] User visible changes (including notable bug fixes) are documented in `whats-new.rst`
+ - [ ] New functions/methods are listed in `api.rst`
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 26bf4803ef6..447f0007fc2 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -11,12 +11,16 @@ repos:
rev: stable
hooks:
- id: black
+ - repo: https://github.com/keewis/blackdoc
+ rev: stable
+ hooks:
+ - id: blackdoc
- repo: https://gitlab.com/pycqa/flake8
rev: 3.7.9
hooks:
- id: flake8
- repo: https://github.com/pre-commit/mirrors-mypy
- rev: v0.761 # Must match ci/requirements/*.yml
+ rev: v0.780 # Must match ci/requirements/*.yml
hooks:
- id: mypy
# run this occasionally, ref discussion https://github.com/pydata/xarray/pull/3194
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000000..7a909aefd08
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1 @@
+Xarray's contributor guidelines [can be found in our online documentation](http://xarray.pydata.org/en/stable/contributing.html)
diff --git a/HOW_TO_RELEASE.md b/HOW_TO_RELEASE.md
index 3fdd1d7236d..c890d61d966 100644
--- a/HOW_TO_RELEASE.md
+++ b/HOW_TO_RELEASE.md
@@ -1,4 +1,4 @@
-How to issue an xarray release in 16 easy steps
+# How to issue an xarray release in 17 easy steps
Time required: about an hour.
@@ -6,7 +6,16 @@ Time required: about an hour.
```
git pull upstream master
```
- 2. Look over whats-new.rst and the docs. Make sure "What's New" is complete
+ 2. Get a list of contributors with:
+ ```
+ git log "$(git tag --sort="v:refname" | sed -n 'x;$p').." --format=%aN | sort -u | perl -pe 's/\n/$1, /'
+ ```
+ or by substituting the _previous_ release in:
+ ```
+ git log v0.X.Y-1.. --format=%aN | sort -u | perl -pe 's/\n/$1, /'
+ ```
+ Add these into `whats-new.rst` somewhere :)
+ 3. Look over whats-new.rst and the docs. Make sure "What's New" is complete
(check the date!) and consider adding a brief summary note describing the
release at the top.
Things to watch out for:
@@ -16,41 +25,41 @@ Time required: about an hour.
due to a bad merge. Check for these before a release by using git diff,
e.g., `git diff v0.X.Y whats-new.rst` where 0.X.Y is the previous
release.
- 3. If you have any doubts, run the full test suite one final time!
+ 4. If you have any doubts, run the full test suite one final time!
```
pytest
```
- 4. Check that the ReadTheDocs build is passing.
- 5. On the master branch, commit the release in git:
+ 5. Check that the ReadTheDocs build is passing.
+ 6. On the master branch, commit the release in git:
```
git commit -am 'Release v0.X.Y'
```
- 6. Tag the release:
+ 7. Tag the release:
```
git tag -a v0.X.Y -m 'v0.X.Y'
```
- 7. Build source and binary wheels for pypi:
+ 8. Build source and binary wheels for pypi:
```
git clean -xdf # this deletes all uncommited changes!
python setup.py bdist_wheel sdist
```
- 8. Use twine to check the package build:
+ 9. Use twine to check the package build:
```
twine check dist/xarray-0.X.Y*
```
- 9. Use twine to register and upload the release on pypi. Be careful, you can't
+10. Use twine to register and upload the release on pypi. Be careful, you can't
take this back!
```
twine upload dist/xarray-0.X.Y*
```
You will need to be listed as a package owner at
https://pypi.python.org/pypi/xarray for this to work.
-10. Push your changes to master:
+11. Push your changes to master:
```
git push upstream master
git push upstream --tags
```
-11. Update the stable branch (used by ReadTheDocs) and switch back to master:
+12. Update the stable branch (used by ReadTheDocs) and switch back to master:
```
git checkout stable
git rebase master
@@ -60,7 +69,7 @@ Time required: about an hour.
It's OK to force push to 'stable' if necessary. (We also update the stable
branch with `git cherrypick` for documentation only fixes that apply the
current released version.)
-12. Add a section for the next release (v.X.Y+1) to doc/whats-new.rst:
+13. Add a section for the next release (v.X.Y+1) to doc/whats-new.rst:
```
.. _whats-new.0.X.Y+1:
@@ -86,19 +95,19 @@ Time required: about an hour.
Internal Changes
~~~~~~~~~~~~~~~~
```
-13. Commit your changes and push to master again:
+14. Commit your changes and push to master again:
```
git commit -am 'New whatsnew section'
git push upstream master
```
You're done pushing to master!
-14. Issue the release on GitHub. Click on "Draft a new release" at
+15. Issue the release on GitHub. Click on "Draft a new release" at
https://github.com/pydata/xarray/releases. Type in the version number, but
don't bother to describe it -- we maintain that on the docs instead.
-15. Update the docs. Login to https://readthedocs.org/projects/xray/versions/
+16. Update the docs. Login to https://readthedocs.org/projects/xray/versions/
and switch your new release tag (at the bottom) from "Inactive" to "Active".
It should now build automatically.
-16. Issue the release announcement! For bug fix releases, I usually only email
+17. Issue the release announcement! For bug fix releases, I usually only email
xarray@googlegroups.com. For major/feature releases, I will email a broader
list (no more than once every 3-6 months):
- pydata@googlegroups.com
@@ -109,18 +118,8 @@ Time required: about an hour.
Google search will turn up examples of prior release announcements (look for
"ANN xarray").
- You can get a list of contributors with:
- ```
- git log "$(git tag --sort="v:refname" | sed -n 'x;$p').." --format="%aN" | sort -u
- ```
- or by substituting the _previous_ release in:
- ```
- git log v0.X.Y-1.. --format="%aN" | sort -u
- ```
- NB: copying this output into a Google Groups form can cause
- [issues](https://groups.google.com/forum/#!topic/xarray/hK158wAviPs) with line breaks, so take care
-Note on version numbering:
+## Note on version numbering
We follow a rough approximation of semantic version. Only major releases (0.X.0)
should include breaking changes. Minor releases (0.X.Y) are for bug fixes and
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index ff85501c555..e04c8f74f68 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -108,21 +108,3 @@ jobs:
python ci/min_deps_check.py ci/requirements/py36-bare-minimum.yml
python ci/min_deps_check.py ci/requirements/py36-min-all-deps.yml
displayName: minimum versions policy
-
-- job: Docs
- pool:
- vmImage: 'ubuntu-16.04'
- steps:
- - template: ci/azure/install.yml
- parameters:
- env_file: ci/requirements/doc.yml
- - bash: |
- source activate xarray-tests
- # Replicate the exact environment created by the readthedocs CI
- conda install --yes --quiet -c pkgs/main mock pillow sphinx sphinx_rtd_theme
- displayName: Replicate readthedocs CI environment
- - bash: |
- source activate xarray-tests
- cd doc
- sphinx-build -W --keep-going -j auto -b html -d _build/doctrees . _build/html
- displayName: Build HTML docs
diff --git a/ci/azure/install.yml b/ci/azure/install.yml
index eff229e863a..83895eebe01 100644
--- a/ci/azure/install.yml
+++ b/ci/azure/install.yml
@@ -10,6 +10,8 @@ steps:
conda env create -n xarray-tests --file ${{ parameters.env_file }}
displayName: Install conda dependencies
+# TODO: add sparse back in, once Numba works with the development version of
+# NumPy again: https://github.com/pydata/xarray/issues/4146
- bash: |
source activate xarray-tests
conda uninstall -y --force \
@@ -23,7 +25,8 @@ steps:
cftime \
rasterio \
pint \
- bottleneck
+ bottleneck \
+ sparse
python -m pip install \
-i https://pypi.anaconda.org/scipy-wheels-nightly/simple \
--no-deps \
diff --git a/ci/requirements/py36-min-all-deps.yml b/ci/requirements/py36-min-all-deps.yml
index 86540197dcc..a72cd000680 100644
--- a/ci/requirements/py36-min-all-deps.yml
+++ b/ci/requirements/py36-min-all-deps.yml
@@ -15,8 +15,8 @@ dependencies:
- cfgrib=0.9
- cftime=1.0
- coveralls
- - dask=2.2
- - distributed=2.2
+ - dask=2.5
+ - distributed=2.5
- flake8
- h5netcdf=0.7
- h5py=2.9 # Policy allows for 2.10, but it's a conflict-fest
diff --git a/ci/requirements/py36-min-nep18.yml b/ci/requirements/py36-min-nep18.yml
index a5eded49cd4..cd2b1a18c77 100644
--- a/ci/requirements/py36-min-nep18.yml
+++ b/ci/requirements/py36-min-nep18.yml
@@ -6,12 +6,11 @@ dependencies:
# require drastically newer packages than everything else
- python=3.6
- coveralls
- - dask=2.4
- - distributed=2.4
+ - dask=2.5
+ - distributed=2.5
- msgpack-python=0.6 # remove once distributed is bumped. distributed GH3491
- numpy=1.17
- pandas=0.25
- - pint=0.11
- pip
- pytest
- pytest-cov
@@ -19,3 +18,5 @@ dependencies:
- scipy=1.2
- setuptools=41.2
- sparse=0.8
+ - pip:
+ - pint==0.13
diff --git a/ci/requirements/py36.yml b/ci/requirements/py36.yml
index a500173f277..aa2baf9dcce 100644
--- a/ci/requirements/py36.yml
+++ b/ci/requirements/py36.yml
@@ -28,7 +28,6 @@ dependencies:
- numba
- numpy
- pandas
- - pint
- pip
- pseudonetcdf
- pydap
@@ -45,3 +44,4 @@ dependencies:
- zarr
- pip:
- numbagg
+ - pint
diff --git a/ci/requirements/py37-windows.yml b/ci/requirements/py37-windows.yml
index e9e5c7a900a..8b12704d644 100644
--- a/ci/requirements/py37-windows.yml
+++ b/ci/requirements/py37-windows.yml
@@ -28,7 +28,6 @@ dependencies:
- numba
- numpy
- pandas
- - pint
- pip
- pseudonetcdf
- pydap
@@ -45,3 +44,4 @@ dependencies:
- zarr
- pip:
- numbagg
+ - pint
diff --git a/ci/requirements/py37.yml b/ci/requirements/py37.yml
index dba3926596e..70c453e8776 100644
--- a/ci/requirements/py37.yml
+++ b/ci/requirements/py37.yml
@@ -28,7 +28,6 @@ dependencies:
- numba
- numpy
- pandas
- - pint
- pip
- pseudonetcdf
- pydap
@@ -45,3 +44,4 @@ dependencies:
- zarr
- pip:
- numbagg
+ - pint
diff --git a/ci/requirements/py38-all-but-dask.yml b/ci/requirements/py38-all-but-dask.yml
index a375d9e1e5a..6d76eecbd6a 100644
--- a/ci/requirements/py38-all-but-dask.yml
+++ b/ci/requirements/py38-all-but-dask.yml
@@ -25,7 +25,6 @@ dependencies:
- numba
- numpy
- pandas
- - pint
- pip
- pseudonetcdf
- pydap
@@ -42,3 +41,4 @@ dependencies:
- zarr
- pip:
- numbagg
+ - pint
diff --git a/ci/requirements/py38.yml b/ci/requirements/py38.yml
index 24602f884e9..6f35138978c 100644
--- a/ci/requirements/py38.yml
+++ b/ci/requirements/py38.yml
@@ -22,13 +22,12 @@ dependencies:
- isort
- lxml # Optional dep of pydap
- matplotlib
- - mypy=0.761 # Must match .pre-commit-config.yaml
+ - mypy=0.780 # Must match .pre-commit-config.yaml
- nc-time-axis
- netcdf4
- numba
- numpy
- pandas
- - pint
- pip
- pseudonetcdf
- pydap
@@ -45,3 +44,4 @@ dependencies:
- zarr
- pip:
- numbagg
+ - pint
diff --git a/doc/_templates/autosummary/accessor.rst b/doc/_templates/autosummary/accessor.rst
new file mode 100644
index 00000000000..4ba745cd6fd
--- /dev/null
+++ b/doc/_templates/autosummary/accessor.rst
@@ -0,0 +1,6 @@
+{{ fullname }}
+{{ underline }}
+
+.. currentmodule:: {{ module.split('.')[0] }}
+
+.. autoaccessor:: {{ (module.split('.')[1:] + [objname]) | join('.') }}
diff --git a/doc/_templates/autosummary/accessor_attribute.rst b/doc/_templates/autosummary/accessor_attribute.rst
new file mode 100644
index 00000000000..b5ad65d6a73
--- /dev/null
+++ b/doc/_templates/autosummary/accessor_attribute.rst
@@ -0,0 +1,6 @@
+{{ fullname }}
+{{ underline }}
+
+.. currentmodule:: {{ module.split('.')[0] }}
+
+.. autoaccessorattribute:: {{ (module.split('.')[1:] + [objname]) | join('.') }}
diff --git a/doc/_templates/autosummary/accessor_callable.rst b/doc/_templates/autosummary/accessor_callable.rst
new file mode 100644
index 00000000000..7a3301814f5
--- /dev/null
+++ b/doc/_templates/autosummary/accessor_callable.rst
@@ -0,0 +1,6 @@
+{{ fullname }}
+{{ underline }}
+
+.. currentmodule:: {{ module.split('.')[0] }}
+
+.. autoaccessorcallable:: {{ (module.split('.')[1:] + [objname]) | join('.') }}.__call__
diff --git a/doc/_templates/autosummary/accessor_method.rst b/doc/_templates/autosummary/accessor_method.rst
new file mode 100644
index 00000000000..aefbba6ef1b
--- /dev/null
+++ b/doc/_templates/autosummary/accessor_method.rst
@@ -0,0 +1,6 @@
+{{ fullname }}
+{{ underline }}
+
+.. currentmodule:: {{ module.split('.')[0] }}
+
+.. autoaccessormethod:: {{ (module.split('.')[1:] + [objname]) | join('.') }}
diff --git a/doc/api-hidden.rst b/doc/api-hidden.rst
index 313428c29d2..efef4259b74 100644
--- a/doc/api-hidden.rst
+++ b/doc/api-hidden.rst
@@ -9,8 +9,6 @@
.. autosummary::
:toctree: generated/
- auto_combine
-
Dataset.nbytes
Dataset.chunks
@@ -43,8 +41,6 @@
core.rolling.DatasetCoarsen.all
core.rolling.DatasetCoarsen.any
- core.rolling.DatasetCoarsen.argmax
- core.rolling.DatasetCoarsen.argmin
core.rolling.DatasetCoarsen.count
core.rolling.DatasetCoarsen.max
core.rolling.DatasetCoarsen.mean
@@ -70,8 +66,6 @@
core.groupby.DatasetGroupBy.where
core.groupby.DatasetGroupBy.all
core.groupby.DatasetGroupBy.any
- core.groupby.DatasetGroupBy.argmax
- core.groupby.DatasetGroupBy.argmin
core.groupby.DatasetGroupBy.count
core.groupby.DatasetGroupBy.max
core.groupby.DatasetGroupBy.mean
@@ -87,8 +81,6 @@
core.resample.DatasetResample.all
core.resample.DatasetResample.any
core.resample.DatasetResample.apply
- core.resample.DatasetResample.argmax
- core.resample.DatasetResample.argmin
core.resample.DatasetResample.assign
core.resample.DatasetResample.assign_coords
core.resample.DatasetResample.bfill
@@ -112,8 +104,6 @@
core.resample.DatasetResample.dims
core.resample.DatasetResample.groups
- core.rolling.DatasetRolling.argmax
- core.rolling.DatasetRolling.argmin
core.rolling.DatasetRolling.count
core.rolling.DatasetRolling.max
core.rolling.DatasetRolling.mean
@@ -187,8 +177,6 @@
core.rolling.DataArrayCoarsen.all
core.rolling.DataArrayCoarsen.any
- core.rolling.DataArrayCoarsen.argmax
- core.rolling.DataArrayCoarsen.argmin
core.rolling.DataArrayCoarsen.count
core.rolling.DataArrayCoarsen.max
core.rolling.DataArrayCoarsen.mean
@@ -213,8 +201,6 @@
core.groupby.DataArrayGroupBy.where
core.groupby.DataArrayGroupBy.all
core.groupby.DataArrayGroupBy.any
- core.groupby.DataArrayGroupBy.argmax
- core.groupby.DataArrayGroupBy.argmin
core.groupby.DataArrayGroupBy.count
core.groupby.DataArrayGroupBy.max
core.groupby.DataArrayGroupBy.mean
@@ -230,8 +216,6 @@
core.resample.DataArrayResample.all
core.resample.DataArrayResample.any
core.resample.DataArrayResample.apply
- core.resample.DataArrayResample.argmax
- core.resample.DataArrayResample.argmin
core.resample.DataArrayResample.assign_coords
core.resample.DataArrayResample.bfill
core.resample.DataArrayResample.count
@@ -254,8 +238,6 @@
core.resample.DataArrayResample.dims
core.resample.DataArrayResample.groups
- core.rolling.DataArrayRolling.argmax
- core.rolling.DataArrayRolling.argmin
core.rolling.DataArrayRolling.count
core.rolling.DataArrayRolling.max
core.rolling.DataArrayRolling.mean
@@ -425,8 +407,6 @@
IndexVariable.all
IndexVariable.any
- IndexVariable.argmax
- IndexVariable.argmin
IndexVariable.argsort
IndexVariable.astype
IndexVariable.broadcast_equals
@@ -566,8 +546,6 @@
CFTimeIndex.all
CFTimeIndex.any
CFTimeIndex.append
- CFTimeIndex.argmax
- CFTimeIndex.argmin
CFTimeIndex.argsort
CFTimeIndex.asof
CFTimeIndex.asof_locs
diff --git a/doc/api.rst b/doc/api.rst
index 3f25ac1a070..603e3e8f6cf 100644
--- a/doc/api.rst
+++ b/doc/api.rst
@@ -21,7 +21,6 @@ Top-level functions
broadcast
concat
merge
- auto_combine
combine_by_coords
combine_nested
where
@@ -233,6 +232,15 @@ Reshaping and reorganizing
Dataset.sortby
Dataset.broadcast_like
+Plotting
+--------
+
+.. autosummary::
+ :toctree: generated/
+ :template: autosummary/accessor_method.rst
+
+ Dataset.plot.scatter
+
DataArray
=========
@@ -403,6 +411,122 @@ Computation
:py:attr:`~core.groupby.DataArrayGroupBy.where`
:py:attr:`~core.groupby.DataArrayGroupBy.quantile`
+
+String manipulation
+-------------------
+
+.. autosummary::
+ :toctree: generated/
+ :template: autosummary/accessor_method.rst
+
+ DataArray.str.capitalize
+ DataArray.str.center
+ DataArray.str.contains
+ DataArray.str.count
+ DataArray.str.decode
+ DataArray.str.encode
+ DataArray.str.endswith
+ DataArray.str.find
+ DataArray.str.get
+ DataArray.str.index
+ DataArray.str.isalnum
+ DataArray.str.isalpha
+ DataArray.str.isdecimal
+ DataArray.str.isdigit
+ DataArray.str.isnumeric
+ DataArray.str.isspace
+ DataArray.str.istitle
+ DataArray.str.isupper
+ DataArray.str.len
+ DataArray.str.ljust
+ DataArray.str.lower
+ DataArray.str.lstrip
+ DataArray.str.match
+ DataArray.str.pad
+ DataArray.str.repeat
+ DataArray.str.replace
+ DataArray.str.rfind
+ DataArray.str.rindex
+ DataArray.str.rjust
+ DataArray.str.rstrip
+ DataArray.str.slice
+ DataArray.str.slice_replace
+ DataArray.str.startswith
+ DataArray.str.strip
+ DataArray.str.swapcase
+ DataArray.str.title
+ DataArray.str.translate
+ DataArray.str.upper
+ DataArray.str.wrap
+ DataArray.str.zfill
+
+Datetimelike properties
+-----------------------
+
+**Datetime properties**:
+
+.. autosummary::
+ :toctree: generated/
+ :template: autosummary/accessor_attribute.rst
+
+ DataArray.dt.year
+ DataArray.dt.month
+ DataArray.dt.day
+ DataArray.dt.hour
+ DataArray.dt.minute
+ DataArray.dt.second
+ DataArray.dt.microsecond
+ DataArray.dt.nanosecond
+ DataArray.dt.weekofyear
+ DataArray.dt.week
+ DataArray.dt.dayofweek
+ DataArray.dt.weekday
+ DataArray.dt.weekday_name
+ DataArray.dt.dayofyear
+ DataArray.dt.quarter
+ DataArray.dt.days_in_month
+ DataArray.dt.daysinmonth
+ DataArray.dt.season
+ DataArray.dt.time
+ DataArray.dt.is_month_start
+ DataArray.dt.is_month_end
+ DataArray.dt.is_quarter_end
+ DataArray.dt.is_year_start
+ DataArray.dt.is_leap_year
+
+**Datetime methods**:
+
+.. autosummary::
+ :toctree: generated/
+ :template: autosummary/accessor_method.rst
+
+ DataArray.dt.floor
+ DataArray.dt.ceil
+ DataArray.dt.round
+ DataArray.dt.strftime
+
+**Timedelta properties**:
+
+.. autosummary::
+ :toctree: generated/
+ :template: autosummary/accessor_attribute.rst
+
+ DataArray.dt.days
+ DataArray.dt.seconds
+ DataArray.dt.microseconds
+ DataArray.dt.nanoseconds
+
+**Timedelta methods**:
+
+.. autosummary::
+ :toctree: generated/
+ :template: autosummary/accessor_method.rst
+
+ DataArray.dt.floor
+ DataArray.dt.ceil
+ DataArray.dt.round
+
+
Reshaping and reorganizing
--------------------------
@@ -419,6 +543,27 @@ Reshaping and reorganizing
DataArray.sortby
DataArray.broadcast_like
+Plotting
+--------
+
+.. autosummary::
+ :toctree: generated/
+ :template: autosummary/accessor_callable.rst
+
+ DataArray.plot
+
+.. autosummary::
+ :toctree: generated/
+ :template: autosummary/accessor_method.rst
+
+ DataArray.plot.contourf
+ DataArray.plot.contour
+ DataArray.plot.hist
+ DataArray.plot.imshow
+ DataArray.plot.line
+ DataArray.plot.pcolormesh
+ DataArray.plot.step
+
.. _api.ufuncs:
Universal functions
@@ -664,25 +809,6 @@ Creating custom indexes
cftime_range
-Plotting
-========
-
-.. autosummary::
- :toctree: generated/
-
- Dataset.plot
- plot.scatter
- DataArray.plot
- plot.plot
- plot.contourf
- plot.contour
- plot.hist
- plot.imshow
- plot.line
- plot.pcolormesh
- plot.step
- plot.FacetGrid
-
Faceting
--------
.. autosummary::
diff --git a/doc/conf.py b/doc/conf.py
index 6b16468d29e..d3d126cb33f 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -20,6 +20,12 @@
import sys
from contextlib import suppress
+# --------- autosummary templates ------------------
+# TODO: eventually replace this with a sphinx.ext.auto_accessor module
+import sphinx
+from sphinx.ext.autodoc import AttributeDocumenter, Documenter, MethodDocumenter
+from sphinx.util import rpartition
+
# make sure the source version is preferred (#3567)
root = pathlib.Path(__file__).absolute().parent.parent
os.environ["PYTHONPATH"] = str(root)
@@ -358,3 +364,113 @@
"dask": ("https://docs.dask.org/en/latest", None),
"cftime": ("https://unidata.github.io/cftime", None),
}
+
+
+# --------- autosummary templates ------------------
+# TODO: eventually replace this with a sphinx.ext.auto_accessor module
+class AccessorDocumenter(MethodDocumenter):
+ """
+ Specialized Documenter subclass for accessors.
+ """
+
+ objtype = "accessor"
+ directivetype = "method"
+
+ # lower than MethodDocumenter so this is not chosen for normal methods
+ priority = 0.6
+
+ def format_signature(self):
+ # this method gives an error/warning for the accessors, therefore
+ # overriding it (accessor has no arguments)
+ return ""
+
+
+class AccessorLevelDocumenter(Documenter):
+ """
+ Specialized Documenter subclass for objects on accessor level (methods,
+ attributes).
+ """
+
+ # This is the simple straightforward version
+ # modname is None, base the last elements (eg 'hour')
+ # and path the part before (eg 'Series.dt')
+ # def resolve_name(self, modname, parents, path, base):
+ # modname = 'pandas'
+ # mod_cls = path.rstrip('.')
+ # mod_cls = mod_cls.split('.')
+ #
+ # return modname, mod_cls + [base]
+
+ def resolve_name(self, modname, parents, path, base):
+ if modname is None:
+ if path:
+ mod_cls = path.rstrip(".")
+ else:
+ mod_cls = None
+ # if documenting a class-level object without path,
+ # there must be a current class, either from a parent
+ # auto directive ...
+ mod_cls = self.env.temp_data.get("autodoc:class")
+ # ... or from a class directive
+ if mod_cls is None:
+ mod_cls = self.env.temp_data.get("py:class")
+ # ... if still None, there's no way to know
+ if mod_cls is None:
+ return None, []
+ # HACK: this is added in comparison to ClassLevelDocumenter
+ # mod_cls still exists of class.accessor, so an extra
+ # rpartition is needed
+ modname, accessor = rpartition(mod_cls, ".")
+ modname, cls = rpartition(modname, ".")
+ parents = [cls, accessor]
+ # if the module name is still missing, get it like above
+ if not modname:
+ modname = self.env.temp_data.get("autodoc:module")
+ if not modname:
+ if sphinx.__version__ > "1.3":
+ modname = self.env.ref_context.get("py:module")
+ else:
+ modname = self.env.temp_data.get("py:module")
+ # ... else, it stays None, which means invalid
+ return modname, parents + [base]
+
+
+class AccessorAttributeDocumenter(AccessorLevelDocumenter, AttributeDocumenter):
+
+ objtype = "accessorattribute"
+ directivetype = "attribute"
+
+ # lower than AttributeDocumenter so this is not chosen for normal attributes
+ priority = 0.6
+
+
+class AccessorMethodDocumenter(AccessorLevelDocumenter, MethodDocumenter):
+
+ objtype = "accessormethod"
+ directivetype = "method"
+
+ # lower than MethodDocumenter so this is not chosen for normal methods
+ priority = 0.6
+
+
+class AccessorCallableDocumenter(AccessorLevelDocumenter, MethodDocumenter):
+ """
+ This documenter lets us removes .__call__ from the method signature for
+ callable accessors like Series.plot
+ """
+
+ objtype = "accessorcallable"
+ directivetype = "method"
+
+ # lower than MethodDocumenter; otherwise the doc build prints warnings
+ priority = 0.5
+
+ def format_name(self):
+ return MethodDocumenter.format_name(self).rstrip(".__call__")
+
+
+def setup(app):
+ app.add_autodocumenter(AccessorDocumenter)
+ app.add_autodocumenter(AccessorAttributeDocumenter)
+ app.add_autodocumenter(AccessorMethodDocumenter)
+ app.add_autodocumenter(AccessorCallableDocumenter)
diff --git a/doc/contributing.rst b/doc/contributing.rst
index 51dba2bb0cc..9e6a3c250e9 100644
--- a/doc/contributing.rst
+++ b/doc/contributing.rst
@@ -148,7 +148,7 @@ We'll now kick off a two-step process:
1. Install the build dependencies
2. Build and install xarray
-.. code-block:: none
+.. code-block:: sh
# Create and activate the build environment
# This is for Linux and MacOS. On Windows, use py37-windows.yml instead.
@@ -162,7 +162,10 @@ We'll now kick off a two-step process:
# Build and install xarray
pip install -e .
-At this point you should be able to import *xarray* from your locally built version::
+At this point you should be able to import *xarray* from your locally
+built version:
+
+.. code-block:: sh
$ python # start an interpreter
>>> import xarray
@@ -256,7 +259,9 @@ Some other important things to know about the docs:
- The tutorials make heavy use of the `ipython directive
`_ sphinx extension.
This directive lets you put code in the documentation which will be run
- during the doc build. For example::
+ during the doc build. For example:
+
+ .. code:: rst
.. ipython:: python
@@ -290,7 +295,7 @@ Requirements
Make sure to follow the instructions on :ref:`creating a development environment above `, but
to build the docs you need to use the environment file ``ci/requirements/doc.yml``.
-.. code-block:: none
+.. code-block:: sh
# Create and activate the docs environment
conda env create -f ci/requirements/doc.yml
@@ -347,7 +352,10 @@ Code Formatting
xarray uses several tools to ensure a consistent code format throughout the project:
-- `Black `_ for standardized code formatting
+- `Black `_ for standardized
+ code formatting
+- `blackdoc `_ for
+ standardized code formatting in documentation
- `Flake8 `_ for general code quality
- `isort `_ for standardized order in imports.
See also `flake8-isort `_.
@@ -356,12 +364,13 @@ xarray uses several tools to ensure a consistent code format throughout the proj
``pip``::
- pip install black flake8 isort mypy
+ pip install black flake8 isort mypy blackdoc
and then run from the root of the Xarray repository::
isort -rc .
black -t py36 .
+ blackdoc -t py36 .
flake8
mypy .
diff --git a/doc/dask.rst b/doc/dask.rst
index df223982ba4..de25ee2200e 100644
--- a/doc/dask.rst
+++ b/doc/dask.rst
@@ -432,6 +432,7 @@ received by the applied function.
print(da.sizes)
return da.time
+
mapped = xr.map_blocks(func, ds.temperature)
mapped
@@ -461,9 +462,10 @@ Here is a common example where automated inference will not work.
:okexcept:
def func(da):
- print(da.sizes)
+ print(da.sizes)
return da.isel(time=[1])
+
mapped = xr.map_blocks(func, ds.temperature)
``func`` cannot be run on 0-shaped inputs because it is not possible to extract element 1 along a
@@ -501,6 +503,7 @@ Notice that the 0-shaped sizes were not printed to screen. Since ``template`` ha
def func(obj, a, b=0):
return obj + a + b
+
mapped = ds.map_blocks(func, args=[10], kwargs={"b": 10})
expected = ds + 10 + 10
mapped.identical(expected)
diff --git a/doc/internals.rst b/doc/internals.rst
index 27c7c4e1d87..46c117e312b 100644
--- a/doc/internals.rst
+++ b/doc/internals.rst
@@ -182,9 +182,10 @@ re-open it directly with Zarr:
.. ipython:: python
- ds = xr.tutorial.load_dataset('rasm')
- ds.to_zarr('rasm.zarr', mode='w')
+ ds = xr.tutorial.load_dataset("rasm")
+ ds.to_zarr("rasm.zarr", mode="w")
import zarr
- zgroup = zarr.open('rasm.zarr')
+
+ zgroup = zarr.open("rasm.zarr")
print(zgroup.tree())
- dict(zgroup['Tair'].attrs)
+ dict(zgroup["Tair"].attrs)
\ No newline at end of file
diff --git a/doc/io.rst b/doc/io.rst
index 1f854586202..4aac5e0b6f7 100644
--- a/doc/io.rst
+++ b/doc/io.rst
@@ -994,8 +994,8 @@ be done directly from zarr, as described in the
GRIB format via cfgrib
----------------------
-xarray supports reading GRIB files via ECMWF cfgrib_ python driver and ecCodes_
-C-library, if they are installed. To open a GRIB file supply ``engine='cfgrib'``
+xarray supports reading GRIB files via ECMWF cfgrib_ python driver,
+if it is installed. To open a GRIB file supply ``engine='cfgrib'``
to :py:func:`open_dataset`:
.. ipython::
@@ -1003,13 +1003,11 @@ to :py:func:`open_dataset`:
In [1]: ds_grib = xr.open_dataset("example.grib", engine="cfgrib")
-We recommend installing ecCodes via conda::
+We recommend installing cfgrib via conda::
- conda install -c conda-forge eccodes
- pip install cfgrib
+ conda install -c conda-forge cfgrib
.. _cfgrib: https://github.com/ecmwf/cfgrib
-.. _ecCodes: https://confluence.ecmwf.int/display/ECC/ecCodes+Home
.. _io.pynio:
diff --git a/doc/plotting.rst b/doc/plotting.rst
index 14e64650902..02ddba1e00c 100644
--- a/doc/plotting.rst
+++ b/doc/plotting.rst
@@ -220,7 +220,7 @@ from the time and assign it as a non-dimension coordinate:
.. ipython:: python
- decimal_day = (air1d.time - air1d.time[0]) / pd.Timedelta('1d')
+ decimal_day = (air1d.time - air1d.time[0]) / pd.Timedelta("1d")
air1d_multi = air1d.assign_coords(decimal_day=("time", decimal_day))
air1d_multi
@@ -912,4 +912,4 @@ One can also make line plots with multidimensional coordinates. In this case, ``
f, ax = plt.subplots(2, 1)
da.plot.line(x="lon", hue="y", ax=ax[0])
@savefig plotting_example_2d_hue_xy.png
- da.plot.line(x="lon", hue="x", ax=ax[1])
+ da.plot.line(x="lon", hue="x", ax=ax[1])
\ No newline at end of file
diff --git a/doc/whats-new.rst b/doc/whats-new.rst
index eaed6d2811b..98f35beacc1 100644
--- a/doc/whats-new.rst
+++ b/doc/whats-new.rst
@@ -33,6 +33,15 @@ Breaking changes
`_.
(:pull:`3274`)
By `Elliott Sales de Andrade `_
+- The old :py:func:`auto_combine` function has now been removed in
+ favour of the :py:func:`combine_by_coords` and
+ :py:func:`combine_nested` functions. This also means that
+ the default behaviour of :py:func:`open_mfdataset` has changed to use
+ ``combine='by_coords'`` as the default argument value. (:issue:`2616`, :pull:`3926`)
+ By `Tom Nicholas `_.
+- The ``DataArray`` and ``Variable`` HTML reprs now expand the data section by
+ default (:issue:`4176`)
+ By `Stephan Hoyer `_.
Enhancements
~~~~~~~~~~~~
@@ -48,6 +57,13 @@ Enhancements
New Features
~~~~~~~~~~~~
+- :py:meth:`DataArray.argmin` and :py:meth:`DataArray.argmax` now support
+ sequences of 'dim' arguments, and if a sequence is passed return a dict
+ (which can be passed to :py:meth:`isel` to get the value of the minimum) of
+ the indices for each dimension of the minimum or maximum of a DataArray.
+ (:pull:`3936`)
+ By `John Omotani `_, thanks to `Keisuke Fujii
+ `_ for work in :pull:`1469`.
- Added :py:meth:`xarray.infer_freq` for extending frequency inferring to CFTime indexes and data (:pull:`4033`).
By `Pascal Bourgault `_.
- ``chunks='auto'`` is now supported in the ``chunks`` argument of
@@ -69,13 +85,16 @@ New Features
- Limited the length of array items with long string reprs to a
reasonable width (:pull:`3900`)
By `Maximilian Roos `_
+- Limited the number of lines of large arrays when numpy reprs would have greater than 40.
+ (:pull:`3905`)
+ By `Maximilian Roos `_
- Implement :py:meth:`DataArray.idxmax`, :py:meth:`DataArray.idxmin`,
:py:meth:`Dataset.idxmax`, :py:meth:`Dataset.idxmin`. (:issue:`60`, :pull:`3871`)
By `Todd Jennings `_
- Support dask handling for :py:meth:`DataArray.idxmax`, :py:meth:`DataArray.idxmin`,
- :py:meth:`Dataset.idxmax`, :py:meth:`Dataset.idxmin`. (:pull:`3922`)
- By `Kai Mühlbauer `_.
-- More support for unit aware arrays with pint (:pull:`3643`)
+ :py:meth:`Dataset.idxmax`, :py:meth:`Dataset.idxmin`. (:pull:`3922`, :pull:`4135`)
+ By `Kai Mühlbauer `_ and `Pascal Bourgault `_.
+- More support for unit aware arrays with pint (:pull:`3643`, :pull:`3975`)
By `Justus Magin `_.
- Support overriding existing variables in ``to_zarr()`` with ``mode='a'`` even
without ``append_dim``, as long as dimension sizes do not change.
@@ -99,7 +118,6 @@ New Features
By `Deepak Cherian `_
- :py:meth:`map_blocks` can now handle dask-backed xarray objects in ``args``. (:pull:`3818`)
By `Deepak Cherian `_
-
- Add keyword ``decode_timedelta`` to :py:func:`xarray.open_dataset`,
(:py:func:`xarray.open_dataarray`, :py:func:`xarray.open_dataarray`,
:py:func:`xarray.decode_cf`) that allows to disable/enable the decoding of timedeltas
@@ -108,6 +126,8 @@ New Features
Bug fixes
~~~~~~~~~
+- Fix errors combining attrs in :py:func:`open_mfdataset` (:issue:`4009`, :pull:`4173`)
+ By `John Omotani `_
- If groupby receives a ``DataArray`` with name=None, assign a default name (:issue:`158`)
By `Phil Butcher `_.
- Support dark mode in VS code (:issue:`4024`)
@@ -177,6 +197,8 @@ Documentation
By `Justus Magin `_.
- Narrative documentation now describes :py:meth:`map_blocks`: :ref:`dask.automatic-parallelization`.
By `Deepak Cherian `_.
+- Document ``.plot``, ``.dt``, ``.str`` accessors the way they are called. (:issue:`3625`, :pull:`3988`)
+ By `Justus Magin `_.
- Add documentation for the parameters and return values of :py:meth:`DataArray.sel`.
By `Justus Magin `_.
@@ -187,6 +209,9 @@ Internal Changes
- Run the ``isort`` pre-commit hook only on python source files
and update the ``flake8`` version. (:issue:`3750`, :pull:`3711`)
By `Justus Magin `_.
+- Add `blackdoc `_ to the list of
+ checkers for development. (:pull:`4177`)
+ By `Justus Magin `_.
- Add a CI job that runs the tests with every optional dependency
except ``dask``. (:issue:`3794`, :pull:`3919`)
By `Justus Magin `_.
@@ -253,6 +278,8 @@ New Features
:py:meth:`core.groupby.DatasetGroupBy.quantile`, :py:meth:`core.groupby.DataArrayGroupBy.quantile`
(:issue:`3843`, :pull:`3844`)
By `Aaron Spring `_.
+- Add a diff summary for `testing.assert_allclose`. (:issue:`3617`, :pull:`3847`)
+ By `Justus Magin `_.
Bug fixes
~~~~~~~~~
diff --git a/xarray/__init__.py b/xarray/__init__.py
index cb4824d188d..3886edc60e6 100644
--- a/xarray/__init__.py
+++ b/xarray/__init__.py
@@ -16,7 +16,7 @@
from .coding.frequencies import infer_freq
from .conventions import SerializationWarning, decode_cf
from .core.alignment import align, broadcast
-from .core.combine import auto_combine, combine_by_coords, combine_nested
+from .core.combine import combine_by_coords, combine_nested
from .core.common import ALL_DIMS, full_like, ones_like, zeros_like
from .core.computation import apply_ufunc, corr, cov, dot, polyval, where
from .core.concat import concat
@@ -47,7 +47,6 @@
"align",
"apply_ufunc",
"as_variable",
- "auto_combine",
"broadcast",
"cftime_range",
"combine_by_coords",
diff --git a/xarray/backends/api.py b/xarray/backends/api.py
index 0919d2a582b..8d7c2230b2d 100644
--- a/xarray/backends/api.py
+++ b/xarray/backends/api.py
@@ -4,7 +4,6 @@
from io import BytesIO
from numbers import Number
from pathlib import Path
-from textwrap import dedent
from typing import (
TYPE_CHECKING,
Callable,
@@ -23,7 +22,6 @@
from ..core.combine import (
_infer_concat_order_from_positions,
_nested_combine,
- auto_combine,
combine_by_coords,
)
from ..core.dataarray import DataArray
@@ -726,14 +724,14 @@ def close(self):
def open_mfdataset(
paths,
chunks=None,
- concat_dim="_not_supplied",
+ concat_dim=None,
compat="no_conflicts",
preprocess=None,
engine=None,
lock=None,
data_vars="all",
coords="different",
- combine="_old_auto",
+ combine="by_coords",
autoclose=None,
parallel=False,
join="outer",
@@ -746,9 +744,8 @@ def open_mfdataset(
the datasets into one before returning the result, and if combine='nested' then
``combine_nested`` is used. The filepaths must be structured according to which
combining function is used, the details of which are given in the documentation for
- ``combine_by_coords`` and ``combine_nested``. By default the old (now deprecated)
- ``auto_combine`` will be used, please specify either ``combine='by_coords'`` or
- ``combine='nested'`` in future. Requires dask to be installed. See documentation for
+ ``combine_by_coords`` and ``combine_nested``. By default ``combine='by_coords'``
+ will be used. Requires dask to be installed. See documentation for
details on dask [1]_. Global attributes from the ``attrs_file`` are used
for the combined dataset.
@@ -758,7 +755,7 @@ def open_mfdataset(
Either a string glob in the form ``"path/to/my/files/*.nc"`` or an explicit list of
files to open. Paths can be given as strings or as pathlib Paths. If
concatenation along more than one dimension is desired, then ``paths`` must be a
- nested list-of-lists (see ``manual_combine`` for details). (A string glob will
+ nested list-of-lists (see ``combine_nested`` for details). (A string glob will
be expanded to a 1-dimensional list.)
chunks : int or dict, optional
Dictionary with keys given by dimension names and values given by chunk sizes.
@@ -768,15 +765,16 @@ def open_mfdataset(
see the full documentation for more details [2]_.
concat_dim : str, or list of str, DataArray, Index or None, optional
Dimensions to concatenate files along. You only need to provide this argument
- if any of the dimensions along which you want to concatenate is not a dimension
- in the original datasets, e.g., if you want to stack a collection of 2D arrays
- along a third dimension. Set ``concat_dim=[..., None, ...]`` explicitly to
- disable concatenation along a particular dimension.
+ if ``combine='by_coords'``, and if any of the dimensions along which you want to
+ concatenate is not a dimension in the original datasets, e.g., if you want to
+ stack a collection of 2D arrays along a third dimension. Set
+ ``concat_dim=[..., None, ...]`` explicitly to disable concatenation along a
+ particular dimension. Default is None, which for a 1D list of filepaths is
+ equivalent to opening the files separately and then merging them with
+ ``xarray.merge``.
combine : {'by_coords', 'nested'}, optional
Whether ``xarray.combine_by_coords`` or ``xarray.combine_nested`` is used to
- combine all the data. If this argument is not provided, `xarray.auto_combine` is
- used, but in the future this behavior will switch to use
- `xarray.combine_by_coords` by default.
+ combine all the data. Default is to use ``xarray.combine_by_coords``.
compat : {'identical', 'equals', 'broadcast_equals',
'no_conflicts', 'override'}, optional
String indicating how to compare variables of the same name for
@@ -869,7 +867,6 @@ def open_mfdataset(
--------
combine_by_coords
combine_nested
- auto_combine
open_dataset
References
@@ -897,11 +894,8 @@ def open_mfdataset(
# If combine='nested' then this creates a flat list which is easier to
# iterate over, while saving the originally-supplied structure as "ids"
if combine == "nested":
- if str(concat_dim) == "_not_supplied":
- raise ValueError("Must supply concat_dim when using " "combine='nested'")
- else:
- if isinstance(concat_dim, (str, DataArray)) or concat_dim is None:
- concat_dim = [concat_dim]
+ if isinstance(concat_dim, (str, DataArray)) or concat_dim is None:
+ concat_dim = [concat_dim]
combined_ids_paths = _infer_concat_order_from_positions(paths)
ids, paths = (list(combined_ids_paths.keys()), list(combined_ids_paths.values()))
@@ -933,30 +927,7 @@ def open_mfdataset(
# Combine all datasets, closing them in case of a ValueError
try:
- if combine == "_old_auto":
- # Use the old auto_combine for now
- # Remove this after deprecation cycle from #2616 is complete
- basic_msg = dedent(
- """\
- In xarray version 0.15 the default behaviour of `open_mfdataset`
- will change. To retain the existing behavior, pass
- combine='nested'. To use future default behavior, pass
- combine='by_coords'. See
- http://xarray.pydata.org/en/stable/combining.html#combining-multi
- """
- )
- warnings.warn(basic_msg, FutureWarning, stacklevel=2)
-
- combined = auto_combine(
- datasets,
- concat_dim=concat_dim,
- compat=compat,
- data_vars=data_vars,
- coords=coords,
- join=join,
- from_openmfds=True,
- )
- elif combine == "nested":
+ if combine == "nested":
# Combined nested list by successive concat and merge operations
# along each dimension, using structure given by "ids"
combined = _nested_combine(
@@ -967,12 +938,18 @@ def open_mfdataset(
coords=coords,
ids=ids,
join=join,
+ combine_attrs="drop",
)
elif combine == "by_coords":
# Redo ordering from coordinates, ignoring how they were ordered
# previously
combined = combine_by_coords(
- datasets, compat=compat, data_vars=data_vars, coords=coords, join=join
+ datasets,
+ compat=compat,
+ data_vars=data_vars,
+ coords=coords,
+ join=join,
+ combine_attrs="drop",
)
else:
raise ValueError(
diff --git a/xarray/coding/times.py b/xarray/coding/times.py
index dafa8ca03b1..77b2d2c7937 100644
--- a/xarray/coding/times.py
+++ b/xarray/coding/times.py
@@ -158,7 +158,7 @@ def decode_cf_datetime(num_dates, units, calendar=None, use_cftime=None):
dates = _decode_datetime_with_pandas(flat_num_dates, units, calendar)
except (KeyError, OutOfBoundsDatetime, OverflowError):
dates = _decode_datetime_with_cftime(
- flat_num_dates.astype(np.float), units, calendar
+ flat_num_dates.astype(float), units, calendar
)
if (
@@ -179,7 +179,7 @@ def decode_cf_datetime(num_dates, units, calendar=None, use_cftime=None):
dates = cftime_to_nptime(dates)
elif use_cftime:
dates = _decode_datetime_with_cftime(
- flat_num_dates.astype(np.float), units, calendar
+ flat_num_dates.astype(float), units, calendar
)
else:
dates = _decode_datetime_with_pandas(flat_num_dates, units, calendar)
diff --git a/xarray/conventions.py b/xarray/conventions.py
index 588fcea71a3..fc0572944f3 100644
--- a/xarray/conventions.py
+++ b/xarray/conventions.py
@@ -116,7 +116,7 @@ def maybe_default_fill_value(var):
def maybe_encode_bools(var):
if (
- (var.dtype == np.bool)
+ (var.dtype == bool)
and ("dtype" not in var.encoding)
and ("dtype" not in var.attrs)
):
diff --git a/xarray/core/combine.py b/xarray/core/combine.py
index 1f990457798..58bd7178fa2 100644
--- a/xarray/core/combine.py
+++ b/xarray/core/combine.py
@@ -1,7 +1,5 @@
import itertools
-import warnings
from collections import Counter
-from textwrap import dedent
import pandas as pd
@@ -762,272 +760,3 @@ def combine_by_coords(
join=join,
combine_attrs=combine_attrs,
)
-
-
-# Everything beyond here is only needed until the deprecation cycle in #2616
-# is completed
-
-
-_CONCAT_DIM_DEFAULT = "__infer_concat_dim__"
-
-
-def auto_combine(
- datasets,
- concat_dim="_not_supplied",
- compat="no_conflicts",
- data_vars="all",
- coords="different",
- fill_value=dtypes.NA,
- join="outer",
- from_openmfds=False,
-):
- """
- Attempt to auto-magically combine the given datasets into one.
-
- This entire function is deprecated in favour of ``combine_nested`` and
- ``combine_by_coords``.
-
- This method attempts to combine a list of datasets into a single entity by
- inspecting metadata and using a combination of concat and merge.
- It does not concatenate along more than one dimension or sort data under
- any circumstances. It does align coordinates, but different variables on
- datasets can cause it to fail under some scenarios. In complex cases, you
- may need to clean up your data and use ``concat``/``merge`` explicitly.
- ``auto_combine`` works well if you have N years of data and M data
- variables, and each combination of a distinct time period and set of data
- variables is saved its own dataset.
-
- Parameters
- ----------
- datasets : sequence of xarray.Dataset
- Dataset objects to merge.
- concat_dim : str or DataArray or Index, optional
- Dimension along which to concatenate variables, as used by
- :py:func:`xarray.concat`. You only need to provide this argument if
- the dimension along which you want to concatenate is not a dimension
- in the original datasets, e.g., if you want to stack a collection of
- 2D arrays along a third dimension.
- By default, xarray attempts to infer this argument by examining
- component files. Set ``concat_dim=None`` explicitly to disable
- concatenation.
- compat : {'identical', 'equals', 'broadcast_equals',
- 'no_conflicts', 'override'}, optional
- String indicating how to compare variables of the same name for
- potential conflicts:
-
- - 'broadcast_equals': all values must be equal when variables are
- broadcast against each other to ensure common dimensions.
- - 'equals': all values and dimensions must be the same.
- - 'identical': all values, dimensions and attributes must be the
- same.
- - 'no_conflicts': only values which are not null in both datasets
- must be equal. The returned dataset then contains the combination
- of all non-null values.
- - 'override': skip comparing and pick variable from first dataset
- data_vars : {'minimal', 'different', 'all' or list of str}, optional
- Details are in the documentation of concat
- coords : {'minimal', 'different', 'all' o list of str}, optional
- Details are in the documentation of concat
- fill_value : scalar, optional
- Value to use for newly missing values
- join : {'outer', 'inner', 'left', 'right', 'exact'}, optional
- String indicating how to combine differing indexes
- (excluding concat_dim) in objects
-
- - 'outer': use the union of object indexes
- - 'inner': use the intersection of object indexes
- - 'left': use indexes from the first object with each dimension
- - 'right': use indexes from the last object with each dimension
- - 'exact': instead of aligning, raise `ValueError` when indexes to be
- aligned are not equal
- - 'override': if indexes are of same size, rewrite indexes to be
- those of the first object with that dimension. Indexes for the same
- dimension must have the same size in all objects.
-
- Returns
- -------
- combined : xarray.Dataset
-
- See also
- --------
- concat
- Dataset.merge
- """
-
- if not from_openmfds:
- basic_msg = dedent(
- """\
- In xarray version 0.15 `auto_combine` will be deprecated. See
- http://xarray.pydata.org/en/stable/combining.html#combining-multi"""
- )
- warnings.warn(basic_msg, FutureWarning, stacklevel=2)
-
- if concat_dim == "_not_supplied":
- concat_dim = _CONCAT_DIM_DEFAULT
- message = ""
- else:
- message = dedent(
- """\
- Also `open_mfdataset` will no longer accept a `concat_dim` argument.
- To get equivalent behaviour from now on please use the new
- `combine_nested` function instead (or the `combine='nested'` option to
- `open_mfdataset`)."""
- )
-
- if _dimension_coords_exist(datasets):
- message += dedent(
- """\
- The datasets supplied have global dimension coordinates. You may want
- to use the new `combine_by_coords` function (or the
- `combine='by_coords'` option to `open_mfdataset`) to order the datasets
- before concatenation. Alternatively, to continue concatenating based
- on the order the datasets are supplied in future, please use the new
- `combine_nested` function (or the `combine='nested'` option to
- open_mfdataset)."""
- )
- else:
- message += dedent(
- """\
- The datasets supplied do not have global dimension coordinates. In
- future, to continue concatenating without supplying dimension
- coordinates, please use the new `combine_nested` function (or the
- `combine='nested'` option to open_mfdataset."""
- )
-
- if _requires_concat_and_merge(datasets):
- manual_dims = [concat_dim].append(None)
- message += dedent(
- """\
- The datasets supplied require both concatenation and merging. From
- xarray version 0.15 this will operation will require either using the
- new `combine_nested` function (or the `combine='nested'` option to
- open_mfdataset), with a nested list structure such that you can combine
- along the dimensions {}. Alternatively if your datasets have global
- dimension coordinates then you can use the new `combine_by_coords`
- function.""".format(
- manual_dims
- )
- )
-
- warnings.warn(message, FutureWarning, stacklevel=2)
-
- return _old_auto_combine(
- datasets,
- concat_dim=concat_dim,
- compat=compat,
- data_vars=data_vars,
- coords=coords,
- fill_value=fill_value,
- join=join,
- )
-
-
-def _dimension_coords_exist(datasets):
- """
- Check if the datasets have consistent global dimension coordinates
- which would in future be used by `auto_combine` for concatenation ordering.
- """
-
- # Group by data vars
- sorted_datasets = sorted(datasets, key=vars_as_keys)
- grouped_by_vars = itertools.groupby(sorted_datasets, key=vars_as_keys)
-
- # Simulates performing the multidimensional combine on each group of data
- # variables before merging back together
- try:
- for vars, datasets_with_same_vars in grouped_by_vars:
- _infer_concat_order_from_coords(list(datasets_with_same_vars))
- return True
- except ValueError:
- # ValueError means datasets don't have global dimension coordinates
- # Or something else went wrong in trying to determine them
- return False
-
-
-def _requires_concat_and_merge(datasets):
- """
- Check if the datasets require the use of both xarray.concat and
- xarray.merge, which in future might require the user to use
- `manual_combine` instead.
- """
- # Group by data vars
- sorted_datasets = sorted(datasets, key=vars_as_keys)
- grouped_by_vars = itertools.groupby(sorted_datasets, key=vars_as_keys)
-
- return len(list(grouped_by_vars)) > 1
-
-
-def _old_auto_combine(
- datasets,
- concat_dim=_CONCAT_DIM_DEFAULT,
- compat="no_conflicts",
- data_vars="all",
- coords="different",
- fill_value=dtypes.NA,
- join="outer",
-):
- if concat_dim is not None:
- dim = None if concat_dim is _CONCAT_DIM_DEFAULT else concat_dim
-
- sorted_datasets = sorted(datasets, key=vars_as_keys)
- grouped = itertools.groupby(sorted_datasets, key=vars_as_keys)
-
- concatenated = [
- _auto_concat(
- list(datasets),
- dim=dim,
- data_vars=data_vars,
- coords=coords,
- compat=compat,
- fill_value=fill_value,
- join=join,
- )
- for vars, datasets in grouped
- ]
- else:
- concatenated = datasets
- merged = merge(concatenated, compat=compat, fill_value=fill_value, join=join)
- return merged
-
-
-def _auto_concat(
- datasets,
- dim=None,
- data_vars="all",
- coords="different",
- fill_value=dtypes.NA,
- join="outer",
- compat="no_conflicts",
-):
- if len(datasets) == 1 and dim is None:
- # There is nothing more to combine, so kick out early.
- return datasets[0]
- else:
- if dim is None:
- ds0 = datasets[0]
- ds1 = datasets[1]
- concat_dims = set(ds0.dims)
- if ds0.dims != ds1.dims:
- dim_tuples = set(ds0.dims.items()) - set(ds1.dims.items())
- concat_dims = {i for i, _ in dim_tuples}
- if len(concat_dims) > 1:
- concat_dims = {d for d in concat_dims if not ds0[d].equals(ds1[d])}
- if len(concat_dims) > 1:
- raise ValueError(
- "too many different dimensions to " "concatenate: %s" % concat_dims
- )
- elif len(concat_dims) == 0:
- raise ValueError(
- "cannot infer dimension to concatenate: "
- "supply the ``concat_dim`` argument "
- "explicitly"
- )
- (dim,) = concat_dims
- return concat(
- datasets,
- dim=dim,
- data_vars=data_vars,
- coords=coords,
- fill_value=fill_value,
- compat=compat,
- )
diff --git a/xarray/core/common.py b/xarray/core/common.py
index e343f342040..f759f4c32dd 100644
--- a/xarray/core/common.py
+++ b/xarray/core/common.py
@@ -1481,7 +1481,7 @@ def zeros_like(other, dtype: DTypeLike = None):
* lat (lat) int64 1 2
* lon (lon) int64 0 1 2
- >>> xr.zeros_like(x, dtype=np.float)
+ >>> xr.zeros_like(x, dtype=float)
array([[0., 0., 0.],
[0., 0., 0.]])
diff --git a/xarray/core/computation.py b/xarray/core/computation.py
index cecd4fd8e70..d8a0c53e817 100644
--- a/xarray/core/computation.py
+++ b/xarray/core/computation.py
@@ -1096,10 +1096,14 @@ def cov(da_a, da_b, dim=None, ddof=1):
Examples
--------
- >>> da_a = DataArray(np.array([[1, 2, 3], [0.1, 0.2, 0.3], [3.2, 0.6, 1.8]]),
- ... dims=("space", "time"),
- ... coords=[('space', ['IA', 'IL', 'IN']),
- ... ('time', pd.date_range("2000-01-01", freq="1D", periods=3))])
+ >>> da_a = DataArray(
+ ... np.array([[1, 2, 3], [0.1, 0.2, 0.3], [3.2, 0.6, 1.8]]),
+ ... dims=("space", "time"),
+ ... coords=[
+ ... ("space", ["IA", "IL", "IN"]),
+ ... ("time", pd.date_range("2000-01-01", freq="1D", periods=3)),
+ ... ],
+ ... )
>>> da_a
array([[1. , 2. , 3. ],
@@ -1108,10 +1112,14 @@ def cov(da_a, da_b, dim=None, ddof=1):
Coordinates:
* space (space) >> da_b = DataArray(np.array([[0.2, 0.4, 0.6], [15, 10, 5], [3.2, 0.6, 1.8]]),
- ... dims=("space", "time"),
- ... coords=[('space', ['IA', 'IL', 'IN']),
- ... ('time', pd.date_range("2000-01-01", freq="1D", periods=3))])
+ >>> da_b = DataArray(
+ ... np.array([[0.2, 0.4, 0.6], [15, 10, 5], [3.2, 0.6, 1.8]]),
+ ... dims=("space", "time"),
+ ... coords=[
+ ... ("space", ["IA", "IL", "IN"]),
+ ... ("time", pd.date_range("2000-01-01", freq="1D", periods=3)),
+ ... ],
+ ... )
>>> da_b
array([[ 0.2, 0.4, 0.6],
@@ -1123,7 +1131,7 @@ def cov(da_a, da_b, dim=None, ddof=1):
>>> xr.cov(da_a, da_b)
array(-3.53055556)
- >>> xr.cov(da_a, da_b, dim='time')
+ >>> xr.cov(da_a, da_b, dim="time")
array([ 0.2, -0.5, 1.69333333])
Coordinates:
@@ -1165,10 +1173,14 @@ def corr(da_a, da_b, dim=None):
Examples
--------
- >>> da_a = DataArray(np.array([[1, 2, 3], [0.1, 0.2, 0.3], [3.2, 0.6, 1.8]]),
- ... dims=("space", "time"),
- ... coords=[('space', ['IA', 'IL', 'IN']),
- ... ('time', pd.date_range("2000-01-01", freq="1D", periods=3))])
+ >>> da_a = DataArray(
+ ... np.array([[1, 2, 3], [0.1, 0.2, 0.3], [3.2, 0.6, 1.8]]),
+ ... dims=("space", "time"),
+ ... coords=[
+ ... ("space", ["IA", "IL", "IN"]),
+ ... ("time", pd.date_range("2000-01-01", freq="1D", periods=3)),
+ ... ],
+ ... )
>>> da_a
array([[1. , 2. , 3. ],
@@ -1177,10 +1189,14 @@ def corr(da_a, da_b, dim=None):
Coordinates:
* space (space) >> da_b = DataArray(np.array([[0.2, 0.4, 0.6], [15, 10, 5], [3.2, 0.6, 1.8]]),
- ... dims=("space", "time"),
- ... coords=[('space', ['IA', 'IL', 'IN']),
- ... ('time', pd.date_range("2000-01-01", freq="1D", periods=3))])
+ >>> da_b = DataArray(
+ ... np.array([[0.2, 0.4, 0.6], [15, 10, 5], [3.2, 0.6, 1.8]]),
+ ... dims=("space", "time"),
+ ... coords=[
+ ... ("space", ["IA", "IL", "IN"]),
+ ... ("time", pd.date_range("2000-01-01", freq="1D", periods=3)),
+ ... ],
+ ... )
>>> da_b
array([[ 0.2, 0.4, 0.6],
@@ -1192,7 +1208,7 @@ def corr(da_a, da_b, dim=None):
>>> xr.corr(da_a, da_b)
array(-0.57087777)
- >>> xr.corr(da_a, da_b, dim='time')
+ >>> xr.corr(da_a, da_b, dim="time")
array([ 1., -1., 1.])
Coordinates:
@@ -1563,7 +1579,7 @@ def _calc_idxminmax(
chunks = dict(zip(array.dims, array.chunks))
dask_coord = dask.array.from_array(array[dim].data, chunks=chunks[dim])
- res = indx.copy(data=dask_coord[(indx.data,)])
+ res = indx.copy(data=dask_coord[indx.data.ravel()].reshape(indx.shape))
# we need to attach back the dim name
res.name = dim
else:
diff --git a/xarray/core/dataarray.py b/xarray/core/dataarray.py
index 44773e36e30..0ce76a5e23a 100644
--- a/xarray/core/dataarray.py
+++ b/xarray/core/dataarray.py
@@ -53,7 +53,7 @@
from .formatting import format_item
from .indexes import Indexes, default_indexes, propagate_indexes
from .indexing import is_fancy_indexer
-from .merge import PANDAS_TYPES, _extract_indexes_from_coords
+from .merge import PANDAS_TYPES, MergeError, _extract_indexes_from_coords
from .options import OPTIONS
from .utils import Default, ReprObject, _check_inplace, _default, either_dict_or_kwargs
from .variable import (
@@ -260,7 +260,7 @@ class DataArray(AbstractArray, DataWithCoords):
_resample_cls = resample.DataArrayResample
_weighted_cls = weighted.DataArrayWeighted
- dt = property(CombinedDatetimelikeAccessor)
+ dt = utils.UncachedAccessor(CombinedDatetimelikeAccessor)
def __init__(
self,
@@ -2713,8 +2713,15 @@ def func(self, other):
# don't support automatic alignment with in-place arithmetic.
other_coords = getattr(other, "coords", None)
other_variable = getattr(other, "variable", other)
- with self.coords._merge_inplace(other_coords):
- f(self.variable, other_variable)
+ try:
+ with self.coords._merge_inplace(other_coords):
+ f(self.variable, other_variable)
+ except MergeError as exc:
+ raise MergeError(
+ "Automatic alignment is not supported for in-place operations.\n"
+ "Consider aligning the indices manually or using a not-in-place operation.\n"
+ "See https://github.com/pydata/xarray/issues/3910 for more explanations."
+ ) from exc
return self
return func
@@ -2722,24 +2729,7 @@ def func(self, other):
def _copy_attrs_from(self, other: Union["DataArray", Dataset, Variable]) -> None:
self.attrs = other.attrs
- @property
- def plot(self) -> _PlotMethods:
- """
- Access plotting functions for DataArray's
-
- >>> d = xr.DataArray([[1, 2], [3, 4]])
-
- For convenience just call this directly
-
- >>> d.plot()
-
- Or use it as a namespace to use xarray.plot functions as
- DataArray methods
-
- >>> d.plot.imshow() # equivalent to xarray.plot.imshow(d)
-
- """
- return _PlotMethods(self)
+ plot = utils.UncachedAccessor(_PlotMethods)
def _title_for_slice(self, truncate: int = 50) -> str:
"""
@@ -3829,9 +3819,212 @@ def idxmax(
keep_attrs=keep_attrs,
)
+ def argmin(
+ self,
+ dim: Union[Hashable, Sequence[Hashable]] = None,
+ axis: int = None,
+ keep_attrs: bool = None,
+ skipna: bool = None,
+ ) -> Union["DataArray", Dict[Hashable, "DataArray"]]:
+ """Index or indices of the minimum of the DataArray over one or more dimensions.
+
+ If a sequence is passed to 'dim', then result returned as dict of DataArrays,
+ which can be passed directly to isel(). If a single str is passed to 'dim' then
+ returns a DataArray with dtype int.
+
+ If there are multiple minima, the indices of the first one found will be
+ returned.
+
+ Parameters
+ ----------
+ dim : hashable, sequence of hashable or ..., optional
+ The dimensions over which to find the minimum. By default, finds minimum over
+ all dimensions - for now returning an int for backward compatibility, but
+ this is deprecated, in future will return a dict with indices for all
+ dimensions; to return a dict with all dimensions now, pass '...'.
+ axis : int, optional
+ Axis over which to apply `argmin`. Only one of the 'dim' and 'axis' arguments
+ can be supplied.
+ keep_attrs : bool, optional
+ If True, the attributes (`attrs`) will be copied from the original
+ object to the new one. If False (default), the new object will be
+ returned without attributes.
+ skipna : bool, optional
+ If True, skip missing values (as marked by NaN). By default, only
+ skips missing values for float dtypes; other dtypes either do not
+ have a sentinel missing value (int) or skipna=True has not been
+ implemented (object, datetime64 or timedelta64).
+
+ Returns
+ -------
+ result : DataArray or dict of DataArray
+
+ See also
+ --------
+ Variable.argmin, DataArray.idxmin
+
+ Examples
+ --------
+ >>> array = xr.DataArray([0, 2, -1, 3], dims="x")
+ >>> array.min()
+
+ array(-1)
+ >>> array.argmin()
+
+ array(2)
+ >>> array.argmin(...)
+ {'x':
+ array(2)}
+ >>> array.isel(array.argmin(...))
+ array(-1)
+
+ >>> array = xr.DataArray([[[3, 2, 1], [3, 1, 2], [2, 1, 3]],
+ ... [[1, 3, 2], [2, -5, 1], [2, 3, 1]]],
+ ... dims=("x", "y", "z"))
+ >>> array.min(dim="x")
+
+ array([[ 1, 2, 1],
+ [ 2, -5, 1],
+ [ 2, 1, 1]])
+ Dimensions without coordinates: y, z
+ >>> array.argmin(dim="x")
+
+ array([[1, 0, 0],
+ [1, 1, 1],
+ [0, 0, 1]])
+ Dimensions without coordinates: y, z
+ >>> array.argmin(dim=["x"])
+ {'x':
+ array([[1, 0, 0],
+ [1, 1, 1],
+ [0, 0, 1]])
+ Dimensions without coordinates: y, z}
+ >>> array.min(dim=("x", "z"))
+
+ array([ 1, -5, 1])
+ Dimensions without coordinates: y
+ >>> array.argmin(dim=["x", "z"])
+ {'x':
+ array([0, 1, 0])
+ Dimensions without coordinates: y, 'z':
+ array([2, 1, 1])
+ Dimensions without coordinates: y}
+ >>> array.isel(array.argmin(dim=["x", "z"]))
+
+ array([ 1, -5, 1])
+ Dimensions without coordinates: y
+ """
+ result = self.variable.argmin(dim, axis, keep_attrs, skipna)
+ if isinstance(result, dict):
+ return {k: self._replace_maybe_drop_dims(v) for k, v in result.items()}
+ else:
+ return self._replace_maybe_drop_dims(result)
+
+ def argmax(
+ self,
+ dim: Union[Hashable, Sequence[Hashable]] = None,
+ axis: int = None,
+ keep_attrs: bool = None,
+ skipna: bool = None,
+ ) -> Union["DataArray", Dict[Hashable, "DataArray"]]:
+ """Index or indices of the maximum of the DataArray over one or more dimensions.
+
+ If a sequence is passed to 'dim', then result returned as dict of DataArrays,
+ which can be passed directly to isel(). If a single str is passed to 'dim' then
+ returns a DataArray with dtype int.
+
+ If there are multiple maxima, the indices of the first one found will be
+ returned.
+
+ Parameters
+ ----------
+ dim : hashable, sequence of hashable or ..., optional
+ The dimensions over which to find the maximum. By default, finds maximum over
+ all dimensions - for now returning an int for backward compatibility, but
+ this is deprecated, in future will return a dict with indices for all
+ dimensions; to return a dict with all dimensions now, pass '...'.
+ axis : int, optional
+ Axis over which to apply `argmin`. Only one of the 'dim' and 'axis' arguments
+ can be supplied.
+ keep_attrs : bool, optional
+ If True, the attributes (`attrs`) will be copied from the original
+ object to the new one. If False (default), the new object will be
+ returned without attributes.
+ skipna : bool, optional
+ If True, skip missing values (as marked by NaN). By default, only
+ skips missing values for float dtypes; other dtypes either do not
+ have a sentinel missing value (int) or skipna=True has not been
+ implemented (object, datetime64 or timedelta64).
+
+ Returns
+ -------
+ result : DataArray or dict of DataArray
+
+ See also
+ --------
+ Variable.argmax, DataArray.idxmax
+
+ Examples
+ --------
+ >>> array = xr.DataArray([0, 2, -1, 3], dims="x")
+ >>> array.max()
+
+ array(3)
+ >>> array.argmax()
+
+ array(3)
+ >>> array.argmax(...)
+ {'x':
+ array(3)}
+ >>> array.isel(array.argmax(...))
+
+ array(3)
+
+ >>> array = xr.DataArray([[[3, 2, 1], [3, 1, 2], [2, 1, 3]],
+ ... [[1, 3, 2], [2, 5, 1], [2, 3, 1]]],
+ ... dims=("x", "y", "z"))
+ >>> array.max(dim="x")
+
+ array([[3, 3, 2],
+ [3, 5, 2],
+ [2, 3, 3]])
+ Dimensions without coordinates: y, z
+ >>> array.argmax(dim="x")
+
+ array([[0, 1, 1],
+ [0, 1, 0],
+ [0, 1, 0]])
+ Dimensions without coordinates: y, z
+ >>> array.argmax(dim=["x"])
+ {'x':
+ array([[0, 1, 1],
+ [0, 1, 0],
+ [0, 1, 0]])
+ Dimensions without coordinates: y, z}
+ >>> array.max(dim=("x", "z"))
+
+ array([3, 5, 3])
+ Dimensions without coordinates: y
+ >>> array.argmax(dim=["x", "z"])
+ {'x':
+ array([0, 1, 0])
+ Dimensions without coordinates: y, 'z':
+ array([0, 1, 2])
+ Dimensions without coordinates: y}
+ >>> array.isel(array.argmax(dim=["x", "z"]))
+
+ array([3, 5, 3])
+ Dimensions without coordinates: y
+ """
+ result = self.variable.argmax(dim, axis, keep_attrs, skipna)
+ if isinstance(result, dict):
+ return {k: self._replace_maybe_drop_dims(v) for k, v in result.items()}
+ else:
+ return self._replace_maybe_drop_dims(result)
+
# this needs to be at the end, or mypy will confuse with `str`
# https://mypy.readthedocs.io/en/latest/common_issues.html#dealing-with-conflicting-names
- str = property(StringAccessor)
+ str = utils.UncachedAccessor(StringAccessor)
# priority most be higher than Variable to properly work with binary ufuncs
diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py
index a8011afd3e3..b46b1d6dce0 100644
--- a/xarray/core/dataset.py
+++ b/xarray/core/dataset.py
@@ -27,6 +27,7 @@
TypeVar,
Union,
cast,
+ overload,
)
import numpy as np
@@ -1241,13 +1242,25 @@ def loc(self) -> _LocIndexer:
"""
return _LocIndexer(self)
- def __getitem__(self, key: Any) -> "Union[DataArray, Dataset]":
+ # FIXME https://github.com/python/mypy/issues/7328
+ @overload
+ def __getitem__(self, key: Mapping) -> "Dataset": # type: ignore
+ ...
+
+ @overload
+ def __getitem__(self, key: Hashable) -> "DataArray": # type: ignore
+ ...
+
+ @overload
+ def __getitem__(self, key: Any) -> "Dataset":
+ ...
+
+ def __getitem__(self, key):
"""Access variables or coordinates this dataset as a
:py:class:`~xarray.DataArray`.
Indexing with a list of names will return a new ``Dataset`` object.
"""
- # TODO(shoyer): type this properly: https://github.com/python/mypy/issues/7328
if utils.is_dict_like(key):
return self.isel(**cast(Mapping, key))
@@ -5563,16 +5576,7 @@ def real(self):
def imag(self):
return self._unary_op(lambda x: x.imag, keep_attrs=True)(self)
- @property
- def plot(self):
- """
- Access plotting functions for Datasets.
- Use it as a namespace to use xarray.plot functions as Dataset methods
-
- >>> ds.plot.scatter(...) # equivalent to xarray.plot.scatter(ds,...)
-
- """
- return _Dataset_PlotMethods(self)
+ plot = utils.UncachedAccessor(_Dataset_PlotMethods)
def filter_by_attrs(self, **kwargs):
"""Returns a ``Dataset`` with variables that match specific conditions.
@@ -6364,5 +6368,131 @@ def idxmax(
)
)
+ def argmin(self, dim=None, axis=None, **kwargs):
+ """Indices of the minima of the member variables.
+
+ If there are multiple minima, the indices of the first one found will be
+ returned.
+
+ Parameters
+ ----------
+ dim : str, optional
+ The dimension over which to find the minimum. By default, finds minimum over
+ all dimensions - for now returning an int for backward compatibility, but
+ this is deprecated, in future will be an error, since DataArray.argmin will
+ return a dict with indices for all dimensions, which does not make sense for
+ a Dataset.
+ axis : int, optional
+ Axis over which to apply `argmin`. Only one of the 'dim' and 'axis' arguments
+ can be supplied.
+ keep_attrs : bool, optional
+ If True, the attributes (`attrs`) will be copied from the original
+ object to the new one. If False (default), the new object will be
+ returned without attributes.
+ skipna : bool, optional
+ If True, skip missing values (as marked by NaN). By default, only
+ skips missing values for float dtypes; other dtypes either do not
+ have a sentinel missing value (int) or skipna=True has not been
+ implemented (object, datetime64 or timedelta64).
+
+ Returns
+ -------
+ result : Dataset
+
+ See also
+ --------
+ DataArray.argmin
+
+ """
+ if dim is None and axis is None:
+ warnings.warn(
+ "Once the behaviour of DataArray.argmin() and Variable.argmin() with "
+ "neither dim nor axis argument changes to return a dict of indices of "
+ "each dimension, for consistency it will be an error to call "
+ "Dataset.argmin() with no argument, since we don't return a dict of "
+ "Datasets.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ if (
+ dim is None
+ or axis is not None
+ or (not isinstance(dim, Sequence) and dim is not ...)
+ or isinstance(dim, str)
+ ):
+ # Return int index if single dimension is passed, and is not part of a
+ # sequence
+ argmin_func = getattr(duck_array_ops, "argmin")
+ return self.reduce(argmin_func, dim=dim, axis=axis, **kwargs)
+ else:
+ raise ValueError(
+ "When dim is a sequence or ..., DataArray.argmin() returns a dict. "
+ "dicts cannot be contained in a Dataset, so cannot call "
+ "Dataset.argmin() with a sequence or ... for dim"
+ )
+
+ def argmax(self, dim=None, axis=None, **kwargs):
+ """Indices of the maxima of the member variables.
+
+ If there are multiple maxima, the indices of the first one found will be
+ returned.
+
+ Parameters
+ ----------
+ dim : str, optional
+ The dimension over which to find the maximum. By default, finds maximum over
+ all dimensions - for now returning an int for backward compatibility, but
+ this is deprecated, in future will be an error, since DataArray.argmax will
+ return a dict with indices for all dimensions, which does not make sense for
+ a Dataset.
+ axis : int, optional
+ Axis over which to apply `argmax`. Only one of the 'dim' and 'axis' arguments
+ can be supplied.
+ keep_attrs : bool, optional
+ If True, the attributes (`attrs`) will be copied from the original
+ object to the new one. If False (default), the new object will be
+ returned without attributes.
+ skipna : bool, optional
+ If True, skip missing values (as marked by NaN). By default, only
+ skips missing values for float dtypes; other dtypes either do not
+ have a sentinel missing value (int) or skipna=True has not been
+ implemented (object, datetime64 or timedelta64).
+
+ Returns
+ -------
+ result : Dataset
+
+ See also
+ --------
+ DataArray.argmax
+
+ """
+ if dim is None and axis is None:
+ warnings.warn(
+ "Once the behaviour of DataArray.argmax() and Variable.argmax() with "
+ "neither dim nor axis argument changes to return a dict of indices of "
+ "each dimension, for consistency it will be an error to call "
+ "Dataset.argmax() with no argument, since we don't return a dict of "
+ "Datasets.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ if (
+ dim is None
+ or axis is not None
+ or (not isinstance(dim, Sequence) and dim is not ...)
+ or isinstance(dim, str)
+ ):
+ # Return int index if single dimension is passed, and is not part of a
+ # sequence
+ argmax_func = getattr(duck_array_ops, "argmax")
+ return self.reduce(argmax_func, dim=dim, axis=axis, **kwargs)
+ else:
+ raise ValueError(
+ "When dim is a sequence or ..., DataArray.argmin() returns a dict. "
+ "dicts cannot be contained in a Dataset, so cannot call "
+ "Dataset.argmin() with a sequence or ... for dim"
+ )
+
ops.inject_all_ops_and_reduce_methods(Dataset, array_only=False)
diff --git a/xarray/core/duck_array_ops.py b/xarray/core/duck_array_ops.py
index 1340b456cf2..df579d23544 100644
--- a/xarray/core/duck_array_ops.py
+++ b/xarray/core/duck_array_ops.py
@@ -6,6 +6,7 @@
import contextlib
import inspect
import warnings
+from distutils.version import LooseVersion
from functools import partial
import numpy as np
@@ -20,6 +21,14 @@
except ImportError:
dask_array = None # type: ignore
+# TODO: remove after we stop supporting dask < 2.9.1
+try:
+ import dask
+
+ dask_version = dask.__version__
+except ImportError:
+ dask_version = None
+
def _dask_or_eager_func(
name,
@@ -199,8 +208,19 @@ def allclose_or_equiv(arr1, arr2, rtol=1e-5, atol=1e-8):
"""
arr1 = asarray(arr1)
arr2 = asarray(arr2)
+
lazy_equiv = lazy_array_equiv(arr1, arr2)
if lazy_equiv is None:
+ # TODO: remove after we require dask >= 2.9.1
+ sufficient_dask_version = (
+ dask_version is not None and LooseVersion(dask_version) >= "2.9.1"
+ )
+ if not sufficient_dask_version and any(
+ isinstance(arr, dask_array_type) for arr in [arr1, arr2]
+ ):
+ arr1 = np.array(arr1)
+ arr2 = np.array(arr2)
+
return bool(isclose(arr1, arr2, rtol=rtol, atol=atol, equal_nan=True).all())
else:
return lazy_equiv
@@ -339,6 +359,7 @@ def f(values, axis=None, skipna=None, **kwargs):
cumprod_1d.numeric_only = True
cumsum_1d = _create_nan_agg_method("cumsum")
cumsum_1d.numeric_only = True
+unravel_index = _dask_or_eager_func("unravel_index")
_mean = _create_nan_agg_method("mean")
diff --git a/xarray/core/formatting.py b/xarray/core/formatting.py
index d6732fc182e..28eaae5f05b 100644
--- a/xarray/core/formatting.py
+++ b/xarray/core/formatting.py
@@ -3,7 +3,7 @@
import contextlib
import functools
from datetime import datetime, timedelta
-from itertools import zip_longest
+from itertools import chain, zip_longest
from typing import Hashable
import numpy as np
@@ -140,7 +140,7 @@ def format_item(x, timedelta_format=None, quote_strings=True):
return format_timedelta(x, timedelta_format=timedelta_format)
elif isinstance(x, (str, bytes)):
return repr(x) if quote_strings else x
- elif isinstance(x, (float, np.float)):
+ elif isinstance(x, (float, np.float_)):
return f"{x:.4}"
else:
return str(x)
@@ -422,6 +422,17 @@ def set_numpy_options(*args, **kwargs):
np.set_printoptions(**original)
+def limit_lines(string: str, *, limit: int):
+ """
+ If the string is more lines than the limit,
+ this returns the middle lines replaced by an ellipsis
+ """
+ lines = string.splitlines()
+ if len(lines) > limit:
+ string = "\n".join(chain(lines[: limit // 2], ["..."], lines[-limit // 2 :]))
+ return string
+
+
def short_numpy_repr(array):
array = np.asarray(array)
@@ -447,7 +458,7 @@ def short_data_repr(array):
elif hasattr(internal_data, "__array_function__") or isinstance(
internal_data, dask_array_type
):
- return repr(array.data)
+ return limit_lines(repr(array.data), limit=40)
elif array._in_memory or array.size < 1e5:
return short_numpy_repr(array)
else:
@@ -539,7 +550,10 @@ def extra_items_repr(extra_keys, mapping, ab_side):
for k in a_keys & b_keys:
try:
# compare xarray variable
- compatible = getattr(a_mapping[k], compat)(b_mapping[k])
+ if not callable(compat):
+ compatible = getattr(a_mapping[k], compat)(b_mapping[k])
+ else:
+ compatible = compat(a_mapping[k], b_mapping[k])
is_variable = True
except AttributeError:
# compare attribute value
@@ -596,8 +610,13 @@ def extra_items_repr(extra_keys, mapping, ab_side):
def _compat_to_str(compat):
+ if callable(compat):
+ compat = compat.__name__
+
if compat == "equals":
return "equal"
+ elif compat == "allclose":
+ return "close"
else:
return compat
@@ -611,8 +630,12 @@ def diff_array_repr(a, b, compat):
]
summary.append(diff_dim_summary(a, b))
+ if callable(compat):
+ equiv = compat
+ else:
+ equiv = array_equiv
- if not array_equiv(a.data, b.data):
+ if not equiv(a.data, b.data):
temp = [wrap_indent(short_numpy_repr(obj), start=" ") for obj in (a, b)]
diff_data_repr = [
ab_side + "\n" + ab_data_repr
diff --git a/xarray/core/formatting_html.py b/xarray/core/formatting_html.py
index 69832d6ca3d..400ef61502e 100644
--- a/xarray/core/formatting_html.py
+++ b/xarray/core/formatting_html.py
@@ -20,7 +20,9 @@ def short_data_repr_html(array):
internal_data = getattr(array, "variable", array)._data
if hasattr(internal_data, "_repr_html_"):
return internal_data._repr_html_()
- return escape(short_data_repr(array))
+ else:
+ text = escape(short_data_repr(array))
+ return f"