├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── pull_request_template.md └── workflows │ ├── ci-testing.yml │ ├── ci.yml │ └── python-publish.yml ├── .gitignore ├── .readthedocs.yaml ├── .zenodo.json ├── CITATION.cff ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── docs ├── Makefile ├── _static │ └── logo.png ├── _templates │ └── autosummary │ │ ├── class.rst │ │ └── module.rst ├── api │ └── index.rst ├── conf.py ├── examples │ ├── 01_example_zamg.ipynb │ ├── 02_example_zamg_netcdf.ipynb │ ├── 03_example_knmi.ipynb │ ├── 04_example_coagmet.ipynb │ ├── 05_example_calibration.ipynb │ ├── 06_worked_examples_McMahon_etal_2013.ipynb │ ├── 07_example_climate_change.ipynb │ ├── 08_crop_coefficient.ipynb │ ├── 09_CMIP6_data.ipynb │ ├── 10_example_paper.ipynb │ ├── data │ │ ├── example_1 │ │ │ └── klima_daily.csv │ │ ├── example_10 │ │ │ ├── 10_example_meteo.csv │ │ │ ├── co2_conc │ │ │ │ ├── RCP3PD_MIDYR_CONC.DAT │ │ │ │ ├── RCP45_MIDYR_CONC.DAT │ │ │ │ ├── RCP6_MIDYR_CONC.DAT │ │ │ │ └── RCP85_MIDYR_CONC.DAT │ │ │ ├── df_Guo_2016.xlsx │ │ │ ├── elev_ens_0.25deg_reg_v25.0e.nc │ │ │ ├── fg_ens_mean_0.25deg_reg_2018_v25.0e.nc │ │ │ ├── hu_ens_mean_0.25deg_reg_2018_v25.0e.nc │ │ │ ├── qq_ens_mean_0.25deg_reg_2018_v25.0e.nc │ │ │ ├── spartacus-daily_19610101T0000_20211231T0000.nc │ │ │ ├── tasAdjust_AUT_AT.ST_area_annual.csv │ │ │ ├── tg_ens_mean_0.25deg_reg_2018_v25.0e.nc │ │ │ ├── tn_ens_mean_0.25deg_reg_2018_v25.0e.nc │ │ │ └── tx_ens_mean_0.25deg_reg_2018_v25.0e.nc │ │ ├── example_2 │ │ │ └── incal_hourly_20120501T0000_20120930T2300.nc │ │ ├── example_3 │ │ │ └── etmgeg_260.txt │ │ ├── example_4 │ │ │ └── et_coagmet.txt │ │ └── example_9 │ │ │ └── tas_day_EC-Earth3_ssp119_r4i1p1f1_gr_21000601-21000630_v20200425.nc │ ├── index.rst │ └── utils.py ├── index.rst ├── make.bat ├── publications.rst ├── references.rst └── user_guide │ ├── index.rst │ ├── installation.rst │ ├── methods.rst │ └── units.rst ├── methods.xlsx ├── pyet ├── __init__.py ├── combination.py ├── meteo_utils.py ├── rad_utils.py ├── radiation.py ├── temperature.py ├── utils.py └── version.py ├── pyproject.toml ├── requirements.txt └── tests ├── __init__.py ├── readme.rst ├── test_all.py ├── test_rpackage.py ├── testalternative.py └── testfao56.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Short Description 2 | Add a short description describing the pull request (PR) here. 3 | 4 | # Checklist before PR can be merged: 5 | - [ ] closes issue #xxxx 6 | - [ ] is documented 7 | - [ ] Format code with [Black formatting](https://black.readthedocs.io) 8 | - [ ] type hints for functions and methods 9 | - [ ] tests added / passed 10 | - [ ] Example Notebook (for new features) 11 | - [ ] Remove output for all notebooks with changes 12 | -------------------------------------------------------------------------------- /.github/workflows/ci-testing.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python 3 | 4 | name: Python package 5 | 6 | on: 7 | push: 8 | branches: [ "master", "dev" ] 9 | pull_request: 10 | branches: [ "master", "dev" ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | python-version: ["3.9", "3.10", "3.11"] 20 | 21 | steps: 22 | - uses: actions/checkout@v3 23 | - name: Set up Python ${{ matrix.python-version }} 24 | uses: actions/setup-python@v3 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | check-latest: true 28 | cache: "pip" 29 | cache-dependency-path: requirements.txt 30 | - name: Install dependencies 31 | run: | 32 | python -m pip install --upgrade pip 33 | pip install -r requirements.txt 34 | - name: Lint with flake8 35 | run: | 36 | # stop the build if there are Python syntax errors or undefined names 37 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 38 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 39 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 40 | - name: Test with pytest 41 | run: | 42 | pytest 43 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: codacy-coverage-reporter 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | python-version: ['3.9', '3.10', '3.11', '3.12',] 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | - name: Set up Python ${{ matrix.python-version }} 21 | uses: actions/setup-python@v5 22 | with: 23 | python-version: ${{ matrix.python-version }} 24 | - name: Install dependencies 25 | run: | 26 | python -m pip install --upgrade pip 27 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 28 | pip install codacy-coverage 29 | pip install coverage 30 | pip install -e . 31 | 32 | - name: Test with unittest 33 | run: | 34 | coverage run -m unittest discover 35 | coverage xml 36 | 37 | - name: Run codacy-coverage-reporter 38 | uses: codacy/codacy-coverage-reporter-action@master 39 | with: 40 | project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} 41 | coverage-reports: coverage.xml 42 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | name: Upload Python Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | deploy: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Set up Python 18 | uses: actions/setup-python@v5 19 | with: 20 | python-version: '3.x' 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install setuptools wheel twine build 25 | - name: Build package 26 | run: python -m build 27 | - name: Upload Python Package 28 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 29 | with: 30 | user: __token__ 31 | password: ${{ secrets.PYPY_TOKEN }} 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.xml 3 | 4 | *.iml 5 | 6 | *.pyc 7 | 8 | *.so 9 | 10 | pyet.egg-info 11 | 12 | examples/notebooks/.ipynb_checkpoints/ 13 | build 14 | dist 15 | 16 | .idea/* 17 | .idea/.name 18 | .cache/v/cache/lastfailed 19 | *.log 20 | 21 | **/*.ipynb_checkpoints/ 22 | 23 | .vscode/* 24 | 25 | .DS_Store 26 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | build: 9 | os: ubuntu-22.04 10 | tools: 11 | python: "3.11" 12 | 13 | # Build documentation in the docs/ directory with Sphinx 14 | sphinx: 15 | configuration: docs/conf.py 16 | 17 | # Build documentation with MkDocs 18 | #mkdocs: 19 | # configuration: mkdocs.yml 20 | 21 | # Optionally build your docs in additional formats such as PDF and ePub 22 | #formats: 23 | # - epub 24 | # - pdf 25 | 26 | # Optionally set the version of Python and requirements required to build your docs 27 | python: 28 | install: 29 | - method: pip 30 | path: . 31 | extra_requirements: 32 | - rtd -------------------------------------------------------------------------------- /.zenodo.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "PyEt is an open source python package for calculating reference and potential Evapotranspiration for 1D (pandas.Series) and 3D (xarray.DataArrray) data.", 3 | "license": "MIT", 4 | "title": "PyEt: A Python package for estimating potential evapotranspiration", 5 | "upload_type": "software", 6 | "creators": [ 7 | { 8 | "affiliation": "University of Graz, Graz, Austria.", 9 | "name": "Matevz Vremec", 10 | "orcid": "0000-0002-2730-494X" 11 | }, 12 | { 13 | "affiliation": "Eawag, Dübendorf, Switzerland.", 14 | "name": "Raoul Collenteur", 15 | "orcid": "0000-0001-7843-1538" 16 | } 17 | ], 18 | "access_right": "open", 19 | "keywords": [ 20 | "python", 21 | "evaporation", 22 | "hydrology", 23 | "water balance", 24 | "open source" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | message: "If you use this software, please cite it as below." 3 | authors: 4 | - family-names: "Vremec" 5 | given-names: "Matevz" 6 | orcid: "https://orcid.org/0000-0002-2730-494X" 7 | - family-names: "Collenteur" 8 | given-names: "Matevz" 9 | orcid: "https://orcid.org/0000-0001-7843-1538" 10 | - family-names: "Birk" 11 | given-names: "Steffen" 12 | orcid: "https://orcid.org/0000-0001-7474-3884" 13 | title: "Technical note: Improved handling of potential evapotranspiration in hydrological studies with PyEt" 14 | doi: https://doi.org/10.5194/hess-2022-417 15 | date-released: 2023 16 | url: "https://github.com/phydrus/pyet" 17 | preferred-citation: 18 | type: journal 19 | authors: 20 | - family-names: "Vremec" 21 | given-names: "Matevz" 22 | orcid: "https://orcid.org/0000-0002-2730-494X" 23 | - family-names: "Collenteur" 24 | given-names: "Matevz" 25 | orcid: "https://orcid.org/0000-0001-7843-1538" 26 | - family-names: "Birk" 27 | given-names: "Steffen" 28 | orcid: "https://orcid.org/0000-0001-7474-3884" 29 | doi: "https://doi.org/10.5194/hess-2022-417" 30 | journal: "Hydrol. Earth Syst. Sci. Discuss. [preprint]" 31 | year: "2023" 32 | title: "Technical note: Improved handling of potential evapotranspiration in hydrological studies with PyEt" -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | @mvremec. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 phydrus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # pyet: Estimation of Potential Evapotranspiration 4 | 5 | [![codacy-coverage-reporter](https://github.com/pyet-org/pyet/actions/workflows/ci.yml/badge.svg)](https://github.com/pyet-org/pyet/actions/workflows/ci.yml) 6 | 7 | 8 | [![Codacy Badge](https://app.codacy.com/project/badge/Grade/e49f23e356f441688422ec32cfcf6aaa)](https://www.codacy.com/gh/phydrus/pyet/dashboard?utm_source=github.com&utm_medium=referral&utm_content=phydrus/pyet&utm_campaign=Badge_Grade) 9 | [![Codacy Badge](https://app.codacy.com/project/badge/Coverage/e49f23e356f441688422ec32cfcf6aaa)](https://www.codacy.com/gh/phydrus/pyet/dashboard?utm_source=github.com&utm_medium=referral&utm_content=phydrus/pyet&utm_campaign=Badge_Coverage) 10 | 11 | 12 | 13 | pyet is an open source python package for calculating reference and potential Evapotranspiration (PET) for 1D (pandas.Series) 14 | and 3D (xarray.DataArrray) data. Currently, eighteen methods for calculating daily PET are implemented: 15 | 16 | | Method name | pyet function | T | RH | R | u2 | Lat. | El. | Benchmarked? | 17 | |:------------------|:------------------|:-------|:-----------|:-------|:-------|:-------|:-------|:------------------| 18 | | Penman | penman | ✓ $^a$ | ✓ $^{b,c}$ | ✓ $^d$ | ✓ | ✓ $^d$ | ✓ $^e$ | ✓ | 19 | | Penman-Monteith | pm | ✓ $^a$ | ✓ $^{b,c}$ | ✓ $^d$ | ✓ | ✓ $^d$ | ✓ $^e$ | ✓ | 20 | | ASCE-PM | pm_asce | ✓ $^a$ | ✓ $^{b,c}$ | ✓ $^d$ | ✓ | ✓ $^d$ | ✓ $^e$ | ✓ | 21 | | FAO-56 | pm_fao56 | ✓ $^a$ | ✓ $^{b,c}$ | ✓ $^d$ | ✓ | ✓ $^d$ | ✓ $^e$ | ✓ | 22 | | Priestley-Taylor | priestley_taylor | ✓ | ✓ $^h$ | ✓ $^h$ | - | ✓ $^h$ | ✓ $^e$ | ✓ | 23 | | Kimberly-Penman | kimberly_penman | ✓ $^a$ | ✓ $^{b,c}$ | ✓ $^d$ | ✓ | ✓ $^d$ | ✓ $^e$ | - | 24 | | Thom-Oliver | thom_oliver | ✓ $^a$ | ✓ $^{b,c}$ | ✓ $^d$ | ✓ | ✓ $^d$ | ✓ $^e$ | - | 25 | | Blaney-Criddle | blaney_criddle | ✓ | - $^i$ | - $^i$ | - $^i$ | ✓ | - | ✓ | 26 | | Hamon | hamon | ✓ | - | - | - | ✓ | - | ✓ | 27 | | Romanenko | romanenko | ✓ | ✓ | - | - | - | - | ✓ | 28 | | Linacre | linacre | ✓ $^j$ | - | - | - | - | ✓ | ✓ | 29 | | Haude | haude | ✓ | ✓ $^k$ | - | - | - | - | ✓ | 30 | | Turc | turc | ✓ | ✓ | ✓ | - | - | - | ✓ | 31 | | Jensen-Haise | jensen_haise | ✓ | - | ✓ $^l$ | - | ✓ $^l$ | - | ✓ | 32 | | McGuinness-Bordne | mcguinness_bordne | ✓ | - | - | - | ✓ | - | ✓ | 33 | | Hargreaves | hargreaves | ✓ $^m$ | - | - | - | ✓ | - | ✓ | 34 | | FAO-24 radiation | fao_24 | ✓ | ✓ | ✓ | ✓ | - | ✓ $^e$ | - | 35 | | Abtew | abtew | ✓ | - | ✓ | - | - | - | ✓ | 36 | | Makkink | makkink | ✓ | - | ✓ | - | - | ✓ $^e$ | ✓ | 37 | | Oudin | oudin | ✓ | - | - | - | ✓ | - | - | 38 | 39 | $^a$ $T_{max}$ and $T_{min}$ can also be provided. $^b$ $RH_{max}$ and $RH_{min}$ can also be provided. $^c$ If actual vapor pressure is provided, RH is not needed. $^d$ Input for radiation can be (1) Net radiation, (2) solar radiation or (3) sunshine hours. If (1), then latitude is not needed. If (1, 3) latitude and elevation is needed. $^e$ One must provide either the atmospheric pressure or elevation. $^f$ The PM method can be used to estimate potential crop evapotranspiration, if leaf area index or crop height data is available. $^g$ The effect of $CO_2$ on stomatal resistance can be included using the formulation of Yang et al. 2019. $^h$ If net radiation is provided, RH and Lat are not needed. $^i$ If method==2, $u_2$, $RH_{min}$ and sunshine hours are required. $^j$ Additional input of $T_{max}$ and $T_{min}$, or $T_{dew}$. $^k$ Input can be $RH$ or actual vapor pressure. $^l$ If method==1, latitude is needed instead of $R_s$. $^m$ $T_{max}$ and $T_{min}$ also needed. 40 | 41 | ## Examples and Documentation 42 | 43 | Examples of using *pyet* can be found in the example folder: 44 | 45 | * [Example 1](): Estimating PET using pandas.Series 46 | 47 | * [Example 2](): Estimating PET using xarray.DataArray 48 | 49 | * [Example 3](): Benchmarking Makkink 50 | against [KNMI data](https://www.knmi.nl/over-het-knmi/about) 51 | 52 | * [Example 4](): Benchmarking FAO56 53 | against [CoAgMET data](https://coagmet.colostate.edu/) 54 | 55 | * [Example 5](): Calibrating the Romanenko and Abtew method against the PM-FAO56 56 | 57 | * [Example 6](): Worked examples for estimating meteorological 58 | variables and potential evapotranspiration after McMahon et al., 2013 59 | 60 | * [Example 7](): Example for estimating potential evapotranspiration under 61 | warming and elevated $CO_2$ concentrations following Yang et al., (2019) 62 | 63 | * [Example 8](): Determining the crop coefficient function with Python 64 | 65 | * [Example 9](): Estimating PET using CMIP data 66 | 67 | * [Example 10](): Notebook supporting PyEt manuscript 68 | 69 | Documentation is hosted on [ReadTheDocs](https://pyet.readthedocs.io). 70 | 71 | After defining the input data, evapotranspiration is estimated using only one 72 | line of python code: 73 | 74 | `>>> pyet.pm_fao56(tmean, wind, rn=rn, tmax=tmax, tmin=tmin, rh=rh, elevation=elevation)` 75 | 76 | We support Python >= 3.8. 77 | 78 | ## Benchmarking 79 | 80 | Most of the methods implemented in *pyet* are benchmarked against literature values from the [FAO Irrigation and 81 | drainage paper 56](https://www.fao.org/3/x0490e/x0490e00.htm), [McMahon et al., 2013 (supplementary)](https://hess.copernicus.org/articles/17/4865/2013/) and [Schrödter, 1985](https://link.springer.com/book/10.1007/978-3-642-70434-5). In addition, two comparative analysis between daily PE estimated with *pyet* and other organizations is 82 | made: 83 | 84 | * `pyet.pm_fao56` against daily PET estimated with ASCE Penman-Monteith from [CoAgMET](https://coagmet.colostate.edu/) ( 85 | Colorado State University), 86 | 87 | * `pyet.makkink` against daily PET estimated with Makkink from The Royal Netherlands Meteorological 88 | Institute ([KNMI](https://www.knmi.nl/over-het-knmi/about)). 89 | 90 | ## Data dimensions 91 | 92 | As of version v1.2., *pyet* is compatible with both Pandas.Series and xarray.DataArray, which means you can now estimate 93 | potential evapotranspiration for both point and gridded data. 94 | 95 | ## Bug reports and Questions 96 | 97 | pyet is in active development, and bug reports are welcome as [GitHub 98 | Issues](https://github.com/phydrus/pyet/issues). 99 | General questions or discussions are possible through 100 | [GitHub Discussions](https://github.com/phydrus/pyet/discussions). 101 | 102 | ## Installation 103 | 104 | The *pyet* package is available from the Pypi package index and can be installed 105 | as follows:: 106 | 107 | `>>> pip install pyet` 108 | 109 | To install in developer mode, use the following syntax: 110 | 111 | `>>> pip install -e .` 112 | 113 | ## Citing 114 | 115 | If you use *pyet* in one of your studies, please cite the *pyet* EGU abstract: 116 | 117 | * Vremec, M., Collenteur, R. A., and Birk, S.: Technical note: Improved handling of potential evapotranspiration in 118 | hydrological studies with PyEt, Hydrol. Earth Syst. Sci. Discuss. [preprint], https://doi.org/10.5194/hess-2022-417, 119 | in review, 2023. 120 | 121 | ```Reference 122 | @Article{hess-2022-417, 123 | AUTHOR = {Vremec, M. and Collenteur, R. A. and Birk, S.}, 124 | TITLE = {Technical note: Improved handling of potential evapotranspiration in hydrological studies with \textit{PyEt}}, 125 | JOURNAL = {Hydrology and Earth System Sciences Discussions}, 126 | VOLUME = {2023}, 127 | YEAR = {2023}, 128 | PAGES = {1--23}, 129 | URL = {https://hess.copernicus.org/preprints/hess-2022-417/}, 130 | DOI = {10.5194/hess-2022-417} 131 | } 132 | ``` 133 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/_static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyet-org/pyet/ff411787fbaeaa903b9966e28df41db9afb05a28/docs/_static/logo.png -------------------------------------------------------------------------------- /docs/_templates/autosummary/class.rst: -------------------------------------------------------------------------------- 1 | {{ objname | escape | underline}} 2 | 3 | .. currentmodule:: {{ module }} 4 | 5 | .. autoclass:: {{ objname }} 6 | 7 | {% block attributes %} 8 | {% if attributes %} 9 | Attributes 10 | ---------- 11 | .. autosummary:: 12 | {% for item in attributes %} 13 | ~{{ name }}.{{ item }} 14 | {%- endfor %} 15 | {% endif %} 16 | {% endblock %} 17 | 18 | {% block methods %} 19 | {% if methods %} 20 | Methods 21 | ------- 22 | .. autosummary:: 23 | :nosignatures: 24 | :toctree: ./generated 25 | {% for item in methods %} 26 | ~{{ name }}.{{ item }} 27 | {%- endfor %} 28 | {% endif %} 29 | {% endblock %} 30 | -------------------------------------------------------------------------------- /docs/_templates/autosummary/module.rst: -------------------------------------------------------------------------------- 1 | {{ fullname | escape | underline}} 2 | 3 | .. automodule:: {{ fullname }} 4 | 5 | {% block attributes %} 6 | {% if attributes %} 7 | .. rubric:: Module Attributes 8 | 9 | .. autosummary:: 10 | :toctree: ./generated 11 | :nosignatures: 12 | {% for item in attributes %} 13 | {{ item }} 14 | {%- endfor %} 15 | {% endif %} 16 | {% endblock %} 17 | 18 | {% block functions %} 19 | {% if functions %} 20 | .. rubric:: {{ ('Functions') }} 21 | 22 | .. autosummary:: 23 | :toctree: ./generated 24 | :nosignatures: 25 | {% for item in functions %} 26 | {{ item }} 27 | {%- endfor %} 28 | {% endif %} 29 | {% endblock %} 30 | 31 | {% block classes %} 32 | {% if classes %} 33 | .. rubric:: {{ ('Classes') }} 34 | 35 | .. autosummary:: 36 | :toctree: ./generated 37 | :nosignatures: 38 | {% for item in classes %} 39 | {{ item }} 40 | {%- endfor %} 41 | {% endif %} 42 | {% endblock %} 43 | 44 | {% block exceptions %} 45 | {% if exceptions %} 46 | .. rubric:: {{ ('Exceptions') }} 47 | 48 | .. autosummary:: 49 | :toctree: ./generated 50 | {% for item in exceptions %} 51 | {{ item }} 52 | {%- endfor %} 53 | {% endif %} 54 | {% endblock %} 55 | 56 | {% block modules %} 57 | {% if modules %} 58 | .. rubric:: Modules 59 | 60 | .. autosummary:: 61 | :toctree: ./generated 62 | :template: module.rst 63 | :recursive: 64 | {% for item in modules %} 65 | {{ item }} 66 | {%- endfor %} 67 | {% endif %} 68 | {% endblock %} -------------------------------------------------------------------------------- /docs/api/index.rst: -------------------------------------------------------------------------------- 1 | API-docs 2 | ======== 3 | This section contains the Documentation of the Application Programming 4 | Interface (API) of pyet. The information in this section is automatically 5 | created from the documentation strings in original Python code. In the 6 | left-hand menu you will find the different categories of the API documentation. 7 | 8 | .. currentmodule:: pyet 9 | 10 | .. autosummary:: 11 | :toctree: ./generated 12 | :nosignatures: 13 | :recursive: 14 | 15 | combination 16 | temperature 17 | radiation 18 | meteo_utils 19 | rad_utils 20 | utils -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | from datetime import date 16 | 17 | import requests 18 | 19 | year = date.today().strftime("%Y") 20 | 21 | sys.path.insert(0, os.path.abspath(".")) 22 | 23 | # Get a Bibtex reference file from the Zotero group for referencing 24 | url = "https://api.zotero.org/groups/4846265/collections/M9ZRDX2U/items/" 25 | params = {"format": "bibtex", "style": "apa", "limit": 100} 26 | 27 | r = requests.get(url=url, params=params) 28 | with open("references.bib", mode="w") as file: 29 | file.write(r.text) 30 | 31 | # Get a Bibtex reference file from the Zotero group for publications list 32 | url = "https://api.zotero.org/groups/4846265/collections/UR7PHVDK/items/" 33 | params = {"format": "bibtex", "style": "apa", "limit": 100} 34 | 35 | r = requests.get(url=url, params=params) 36 | with open("publications.bib", mode="w") as file: 37 | file.write(r.text) 38 | 39 | # -- Project information ----------------------------------------------------- 40 | 41 | project = "pyet" 42 | copyright = "{}, M. Vremec, R.A. Collenteur".format(year) 43 | author = "M. Vremec, R.A. Collenteur" 44 | 45 | # The full version, including alpha/beta/rc tags 46 | release = "2020" 47 | 48 | # -- General configuration --------------------------------------------------- 49 | 50 | # Add any Sphinx extension module names here, as strings. They can be 51 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 52 | # ones. 53 | extensions = [ 54 | "sphinx.ext.autodoc", 55 | "sphinx.ext.autosummary", 56 | "sphinx.ext.napoleon", 57 | "sphinx.ext.doctest", 58 | "sphinx.ext.intersphinx", 59 | "sphinx.ext.todo", 60 | "sphinx.ext.mathjax", 61 | "sphinx.ext.ifconfig", 62 | "sphinx.ext.viewcode", 63 | "IPython.sphinxext.ipython_console_highlighting", # lowercase didn't work 64 | "sphinx.ext.autosectionlabel", 65 | "sphinxcontrib.bibtex", 66 | "myst_nb", 67 | "numpydoc", 68 | "sphinx_design", 69 | ] 70 | 71 | # Create custom bracket style with round brackets 72 | # From https://sphinxcontrib-bibtex.readthedocs.io/en/latest/usage.html 73 | 74 | from dataclasses import dataclass, field 75 | import sphinxcontrib.bibtex.plugin 76 | 77 | from sphinxcontrib.bibtex.style.referencing import BracketStyle 78 | from sphinxcontrib.bibtex.style.referencing.author_year import AuthorYearReferenceStyle 79 | 80 | 81 | def bracket_style() -> BracketStyle: 82 | return BracketStyle( 83 | left="(", 84 | right=")", 85 | ) 86 | 87 | 88 | @dataclass 89 | class MyReferenceStyle(AuthorYearReferenceStyle): 90 | bracket_parenthetical: BracketStyle = field(default_factory=bracket_style) 91 | bracket_textual: BracketStyle = field(default_factory=bracket_style) 92 | bracket_author: BracketStyle = field(default_factory=bracket_style) 93 | bracket_label: BracketStyle = field(default_factory=bracket_style) 94 | bracket_year: BracketStyle = field(default_factory=bracket_style) 95 | 96 | 97 | sphinxcontrib.bibtex.plugin.register_plugin( 98 | "sphinxcontrib.bibtex.style.referencing", "author_year_round", MyReferenceStyle 99 | ) 100 | 101 | bibtex_bibfiles = ["references.bib", "publications.bib"] 102 | bibtex_reference_style = "author_year_round" 103 | 104 | # Add any paths that contain templates here, relative to this directory. 105 | templates_path = ["_templates"] 106 | 107 | source_suffix = ".rst" 108 | 109 | # The master toctree document. 110 | master_doc = "index" 111 | 112 | # List of patterns, relative to source directory, that match files and 113 | # directories to ignore when looking for source files. 114 | # This pattern also affects html_static_path and html_extra_path. 115 | exclude_patterns = ["_build", "**.ipynb_checkpoints"] 116 | 117 | # -- Options for HTML output ------------------------------------------------- 118 | 119 | # The theme to use for HTML and HTML Help pages. See the documentation for 120 | # a list of builtin themes. 121 | 122 | html_theme = "pydata_sphinx_theme" 123 | # Add any paths that contain custom static files (such as style sheets) here, 124 | # relative to this directory. They are copied after the builtin static files, 125 | # so a file named "default.css" will overwrite the builtin "default.css". 126 | html_static_path = ["_static"] 127 | html_logo = "_static/logo.png" 128 | html_use_smartypants = True 129 | html_show_sourcelink = True 130 | 131 | html_theme_options = { 132 | "github_url": "https://github.com/pyet-org/pyet", 133 | "use_edit_page_button": True, 134 | "header_links_before_dropdown": 6, 135 | "icon_links": [ 136 | { 137 | "name": "GitHub", # Label for this link 138 | "url": "https://github.com/pyet-org/pyet", # required 139 | "icon": "fab fa-github-square", 140 | "type": "fontawesome", # Default is fontawesome 141 | } 142 | ], 143 | } 144 | 145 | autosummary_generate = True 146 | numpydoc_show_class_members = False 147 | 148 | # Example configuration for intersphinx: refer to the Python standard library. 149 | intersphinx_mapping = { 150 | "numpy": ("https://numpy.org/doc/stable/", None), 151 | "pandas": ("https://pandas.pydata.org/pandas-docs/stable/", None), 152 | "python": ("https://docs.python.org/3/", None), 153 | "xarray": ("https://docs.xarray.dev/en/stable/", None), 154 | } 155 | 156 | # -- myst_nb options ------------------------------------------------------------------ 157 | 158 | nb_execution_allow_errors = True # Allow errors in notebooks, to see the error online 159 | nb_execution_mode = "auto" 160 | 161 | # Enable specific MyST extensions, such as "dollarmath" for math rendering 162 | myst_enable_extensions = [ 163 | "dollarmath", 164 | ] 165 | 166 | # -- Numpydoc settings ---------------------------------------------------------------- 167 | 168 | numpydoc_class_members_toctree = True 169 | numpydoc_show_class_members = False 170 | -------------------------------------------------------------------------------- /docs/examples/01_example_zamg.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "999bc8b7-804c-4301-b8c7-5077a38dc06f", 6 | "metadata": { 7 | "pycharm": { 8 | "name": "#%% md\n" 9 | } 10 | }, 11 | "source": [ 12 | "# Potential Evapotranspiration from ZAMG data\n", 13 | "*A. Kokimova, November 2021, University of Graz*\n", 14 | "\n", 15 | "Data source: ZAMG - https://data.hub.zamg.ac.at\n", 16 | "\n", 17 | "What is done:\n", 18 | "\n", 19 | "- load the station data from ZAMG\n", 20 | "- estimate potential evapotranspiration\n", 21 | "- plot and store results" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": null, 27 | "id": "7a2ae568-3c3b-44f5-8b82-ab5a99d35d2d", 28 | "metadata": { 29 | "pycharm": { 30 | "name": "#%%\n" 31 | } 32 | }, 33 | "outputs": [], 34 | "source": [ 35 | "import pandas as pd\n", 36 | "import matplotlib.pyplot as plt\n", 37 | "import numpy as np\n", 38 | "import pyet\n", 39 | "pyet.show_versions()" 40 | ] 41 | }, 42 | { 43 | "cell_type": "markdown", 44 | "id": "da6f3e98-0073-4db9-b763-03beb2611e78", 45 | "metadata": { 46 | "pycharm": { 47 | "name": "#%% md\n" 48 | } 49 | }, 50 | "source": [ 51 | "## Loading daily data from ZAMG (Messstationen Tagesdaten)\n", 52 | "\n", 53 | "station: Graz Universität 16412\n", 54 | "\n", 55 | "Selected variables:\n", 56 | "- globalstrahlung (global radiation), J/cm2 needs to be in MJ/m3d, ZAMG abbreviation - strahl\n", 57 | "- arithmetische windgeschwindigkeit (wind speed), m/s, ZAMG abbreviation - vv\n", 58 | "- relative feuchte (relative humidity), %, ZAMG abbreviation - rel\n", 59 | "- lufttemparatur (air temperature) in 2 m, C, ZAMG abbreviation - t\n", 60 | "- lufttemperatur (air temperature) max in 2 m, C, ZAMG abbreviation - tmax\n", 61 | "- lufttemperatur (air temperature) min in 2 m, C, ZAMG abbreviation - tmin\n", 62 | "- latitute and elevation of a station" 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": null, 68 | "id": "236434b2-3c33-4772-93ec-de0cb7317209", 69 | "metadata": { 70 | "pycharm": { 71 | "name": "#%%\n" 72 | } 73 | }, 74 | "outputs": [], 75 | "source": [ 76 | "#read data\n", 77 | "data_16412 = pd.read_csv('data/example_1/klima_daily.csv', index_col=1, parse_dates=True)\n", 78 | "data_16412" 79 | ] 80 | }, 81 | { 82 | "cell_type": "markdown", 83 | "id": "22ebf3bc-6b9a-4f81-a455-7ca1b51879dd", 84 | "metadata": { 85 | "pycharm": { 86 | "name": "#%% md\n" 87 | } 88 | }, 89 | "source": [ 90 | "## Calculate PET for Graz Universität - 16412" 91 | ] 92 | }, 93 | { 94 | "cell_type": "code", 95 | "execution_count": null, 96 | "id": "bc5ba933-22c2-4c5b-9ca6-43a4bcdad344", 97 | "metadata": { 98 | "pycharm": { 99 | "name": "#%%\n" 100 | } 101 | }, 102 | "outputs": [], 103 | "source": [ 104 | "# Convert Glabalstrahlung J/cm2 to MJ/m2 by dividing to 100\n", 105 | "\n", 106 | "meteo = pd.DataFrame({\"time\":data_16412.index, \"tmean\":data_16412.t, \"tmax\":data_16412.tmax, \"tmin\":data_16412.tmin, \"rh\":data_16412.rel, \n", 107 | " \"wind\":data_16412.vv, \"rs\":data_16412.strahl/100})\n", 108 | "time, tmean, tmax, tmin, rh, wind, rs = [meteo[col] for col in meteo.columns]\n", 109 | "\n", 110 | "lat = 47.077778*np.pi/180 # Latitude of the meteorological station, converting from degrees to radians\n", 111 | "elevation = 367 # meters above sea-level\n", 112 | "\n", 113 | "# Estimate evapotranspiration with four different methods and create a dataframe\n", 114 | "pet_df = pyet.calculate_all(tmean, wind, rs, elevation, lat, tmax=tmax,\n", 115 | " tmin=tmin, rh=rh)" 116 | ] 117 | }, 118 | { 119 | "cell_type": "markdown", 120 | "id": "e6e2564c-48ed-46b7-a275-fd17db592456", 121 | "metadata": { 122 | "pycharm": { 123 | "name": "#%% md\n" 124 | } 125 | }, 126 | "source": [ 127 | "## Plot results" 128 | ] 129 | }, 130 | { 131 | "cell_type": "code", 132 | "execution_count": null, 133 | "id": "c9a4582f-219b-47f8-9ed2-dac80720cf55", 134 | "metadata": { 135 | "pycharm": { 136 | "name": "#%%\n" 137 | } 138 | }, 139 | "outputs": [], 140 | "source": [ 141 | "fig, axs = plt.subplots(figsize=(13,4), ncols=2)\n", 142 | "pet_df.plot(ax=axs[0])\n", 143 | "pet_df.cumsum().plot(ax=axs[1], legend=False)\n", 144 | "\n", 145 | "axs[0].set_ylabel(\"PET [mm/day]\", fontsize=12)\n", 146 | "axs[1].set_ylabel(\"Cumulative PET [mm]\", fontsize=12)\n", 147 | "axs[0].legend(ncol=6, loc=[0,1.])\n", 148 | "for i in (0,1):\n", 149 | " axs[i].set_xlabel(\"Date\", fontsize=12)" 150 | ] 151 | }, 152 | { 153 | "cell_type": "markdown", 154 | "id": "1fe2a55b-838a-4e3a-a621-c5fe55db399a", 155 | "metadata": { 156 | "pycharm": { 157 | "name": "#%% md\n" 158 | } 159 | }, 160 | "source": [ 161 | "## Store results" 162 | ] 163 | }, 164 | { 165 | "cell_type": "code", 166 | "execution_count": null, 167 | "id": "3c5e47fb-3ec7-4bc9-ad6c-be0edc67ba09", 168 | "metadata": { 169 | "pycharm": { 170 | "name": "#%%\n" 171 | } 172 | }, 173 | "outputs": [], 174 | "source": [ 175 | "#plt.savefig(\"PET_methods.png\", dpi=300)\n", 176 | "#pet_u.to_csv('../evap_16412.csv')" 177 | ] 178 | } 179 | ], 180 | "metadata": { 181 | "kernelspec": { 182 | "display_name": "Python 3 (ipykernel)", 183 | "language": "python", 184 | "name": "python3" 185 | }, 186 | "language_info": { 187 | "codemirror_mode": { 188 | "name": "ipython", 189 | "version": 3 190 | }, 191 | "file_extension": ".py", 192 | "mimetype": "text/x-python", 193 | "name": "python", 194 | "nbconvert_exporter": "python", 195 | "pygments_lexer": "ipython3", 196 | "version": "3.11.5" 197 | } 198 | }, 199 | "nbformat": 4, 200 | "nbformat_minor": 5 201 | } 202 | -------------------------------------------------------------------------------- /docs/examples/02_example_zamg_netcdf.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "999bc8b7-804c-4301-b8c7-5077a38dc06f", 6 | "metadata": {}, 7 | "source": [ 8 | "# Potential Evapotranspiration from ZAMG INCA data (NetCDF)\n", 9 | "*M. Vremec, October 2022, University of Graz*\n", 10 | "\n", 11 | "\n", 12 | "What is done:\n", 13 | "\n", 14 | "- load the data from ZAMG\n", 15 | "- estimate potential evapotranspiration\n", 16 | "- plot and store results\n", 17 | "\n", 18 | "Data source: ZAMG - https://data.hub.zamg.ac.at" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": null, 24 | "id": "7a2ae568-3c3b-44f5-8b82-ab5a99d35d2d", 25 | "metadata": {}, 26 | "outputs": [], 27 | "source": [ 28 | "import pandas as pd\n", 29 | "import matplotlib.pyplot as plt\n", 30 | "import numpy as np\n", 31 | "import xarray as xr\n", 32 | "#import netcdf4 # Needs to be installed\n", 33 | "import pyet\n", 34 | "pyet.show_versions()" 35 | ] 36 | }, 37 | { 38 | "cell_type": "markdown", 39 | "id": "da6f3e98-0073-4db9-b763-03beb2611e78", 40 | "metadata": {}, 41 | "source": [ 42 | "## 1. Loading daily data from ZAMG (INCA hourly NetCDF data)" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": null, 48 | "id": "236434b2-3c33-4772-93ec-de0cb7317209", 49 | "metadata": {}, 50 | "outputs": [], 51 | "source": [ 52 | "#read data\n", 53 | "xr_ds = xr.open_dataset(\"data/example_2/incal_hourly_20120501T0000_20120930T2300.nc\", \n", 54 | " engine=\"netcdf4\")" 55 | ] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": null, 60 | "id": "fd56788b-5aed-499b-8a6a-9c6fb68bb2f6", 61 | "metadata": {}, 62 | "outputs": [], 63 | "source": [ 64 | "# Resample and define input meteorological variables\n", 65 | "tmean = xr_ds[\"T2M\"].resample(time=\"1D\").mean()\n", 66 | "tmax = xr_ds[\"T2M\"].resample(time=\"1D\").max()\n", 67 | "tmin = xr_ds[\"T2M\"].resample(time=\"1D\").min()\n", 68 | "rh = xr_ds[\"RH2M\"].resample(time=\"1D\").mean()\n", 69 | "rhmax = xr_ds[\"RH2M\"].resample(time=\"1D\").max()\n", 70 | "rhmin = xr_ds[\"RH2M\"].resample(time=\"1D\").min()\n", 71 | "wind = ((np.abs(xr_ds[\"VV\"]) + np.abs(xr_ds[\"UU\"])) / 2).resample(time=\"1D\").mean()\n", 72 | "rs = xr_ds[\"GL\"].resample(time=\"1D\").mean() * 86400 / 1000000" 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": null, 78 | "id": "f5bb9668-075b-4a8c-99e5-64a998c9c9d5", 79 | "metadata": {}, 80 | "outputs": [], 81 | "source": [ 82 | "# Define latitude and elevation\n", 83 | "lat = tmean.lat * np.pi / 180 \n", 84 | "elevation = lat / lat * 350" 85 | ] 86 | }, 87 | { 88 | "cell_type": "markdown", 89 | "id": "22ebf3bc-6b9a-4f81-a455-7ca1b51879dd", 90 | "metadata": {}, 91 | "source": [ 92 | "## 2. Calculate PET" 93 | ] 94 | }, 95 | { 96 | "cell_type": "code", 97 | "execution_count": null, 98 | "id": "542a2a7a-7354-4ce7-a7d7-644f8f650238", 99 | "metadata": {}, 100 | "outputs": [], 101 | "source": [ 102 | "lat1 = 80 * np.pi / 180" 103 | ] 104 | }, 105 | { 106 | "cell_type": "code", 107 | "execution_count": null, 108 | "id": "2b8859a2-37e3-40db-afc1-01b449adfc35", 109 | "metadata": {}, 110 | "outputs": [], 111 | "source": [ 112 | "# Estimate evapotranspiration with nine different methods \n", 113 | "pet_penman = pyet.penman(tmean, wind, rs=rs, elevation=elevation, lat=lat, tmax=tmax, tmin=tmin, rh=rh)\n", 114 | "pet_pt = pyet.priestley_taylor(tmean, rs=rs, elevation=elevation, lat=lat, tmax=tmax, tmin=tmin, rh=rh)\n", 115 | "pet_makkink = pyet.makkink(tmean, rs, elevation=elevation)\n", 116 | "pet_fao56 = pyet.pm_fao56(tmean, wind, rs=rs, elevation=elevation, lat=lat, tmax=tmax, tmin=tmin, rh=rh)\n", 117 | "pet_hamon = pyet.hamon(tmean, lat=lat, method=1)\n", 118 | "pet_oudin = pyet.oudin(tmean, lat=lat)\n", 119 | "pet_haude = pyet.haude(tmax, rh)\n", 120 | "pet_turc = pyet.turc(tmean, rs, rh)\n", 121 | "pet_har = pyet.hargreaves(tmean, tmax, tmin, lat)" 122 | ] 123 | }, 124 | { 125 | "cell_type": "markdown", 126 | "id": "e6e2564c-48ed-46b7-a275-fd17db592456", 127 | "metadata": {}, 128 | "source": [ 129 | "## 3. Plot results" 130 | ] 131 | }, 132 | { 133 | "cell_type": "code", 134 | "execution_count": null, 135 | "id": "14c613f4-edc1-4472-8ab3-5ed0e1d373f5", 136 | "metadata": {}, 137 | "outputs": [], 138 | "source": [ 139 | "fig, axs = plt.subplots(ncols=2, figsize=(12,3))\n", 140 | "pet_penman[:,2,2].plot(ax=axs[0], label=\"Penman\")\n", 141 | "pet_pt[:,2,2].plot(ax=axs[0], label=\"Priestley-Taylor\")\n", 142 | "pet_makkink[:,2,2].plot(ax=axs[0], label=\"Makkink\")\n", 143 | "pet_fao56[:,2,2].plot(ax=axs[0], label=\"FAO56\")\n", 144 | "pet_hamon[:,2,2].plot(ax=axs[0], label=\"Hamon\")\n", 145 | "pet_oudin[:,2,2].plot(ax=axs[0], label=\"Oudin\")\n", 146 | "pet_haude[:,2,2].plot(ax=axs[0], label=\"Haude\")\n", 147 | "pet_turc[:,2,2].plot(ax=axs[0], label=\"Turc\")\n", 148 | "pet_har[:,2,2].plot(ax=axs[0], label=\"Hargreaves\")\n", 149 | "axs[0].legend()\n", 150 | "\n", 151 | "pet_penman[:,2,2].cumsum().plot(ax=axs[1], label=\"Penman\")\n", 152 | "pet_pt[:,2,2].cumsum().plot(ax=axs[1], label=\"Priestley-Taylor\")\n", 153 | "pet_makkink[:,2,2].cumsum().plot(ax=axs[1], label=\"Makkink\")\n", 154 | "pet_fao56[:,2,2].cumsum().plot(ax=axs[1], label=\"FAO56\")\n", 155 | "pet_hamon[:,2,2].cumsum().plot(ax=axs[1], label=\"Hamon\")\n", 156 | "pet_oudin[:,2,2].cumsum().plot(ax=axs[1], label=\"Oudin\")\n", 157 | "pet_haude[:,2,2].cumsum().plot(ax=axs[1], label=\"Haude\")\n", 158 | "pet_turc[:,2,2].cumsum().plot(ax=axs[1], label=\"Turc\")\n", 159 | "pet_har[:,2,2].cumsum().plot(ax=axs[1], label=\"Hargreaves\")\n", 160 | "axs[1].legend()" 161 | ] 162 | }, 163 | { 164 | "cell_type": "markdown", 165 | "id": "d6697f56-83ef-49f4-8037-9235f6e88a51", 166 | "metadata": {}, 167 | "source": [ 168 | "## 4. Compare point with pandas.Series vs. point from xarray.DataArray" 169 | ] 170 | }, 171 | { 172 | "cell_type": "code", 173 | "execution_count": null, 174 | "id": "2b70386d-18f3-4485-bdc1-8969630018b8", 175 | "metadata": {}, 176 | "outputs": [], 177 | "source": [ 178 | "tmeans = xr_ds[\"T2M\"].resample(time=\"1D\").mean()[:, 4, 4].to_series()\n", 179 | "tmaxs = xr_ds[\"T2M\"].resample(time=\"1D\").max()[:, 4, 4].to_series()\n", 180 | "tmins = xr_ds[\"T2M\"].resample(time=\"1D\").min()[:, 4, 4].to_series()\n", 181 | "rhs = xr_ds[\"RH2M\"].resample(time=\"1D\").mean()[:, 4, 4].to_series()\n", 182 | "rhmaxs = xr_ds[\"RH2M\"].resample(time=\"1D\").max()[:, 4, 4].to_series()\n", 183 | "rhmins = xr_ds[\"RH2M\"].resample(time=\"1D\").min()[:, 4, 4].to_series()\n", 184 | "winds = ((np.abs(xr_ds[\"VV\"]) + np.abs(xr_ds[\"UU\"])) / 2).resample(time=\"1D\").mean()[:, 4, 4].to_series()\n", 185 | "rss = (xr_ds[\"GL\"].resample(time=\"1D\").mean() * 86400 / 1000000)[:, 4, 4].to_series()\n", 186 | "lats = float(lat[4, 4])\n", 187 | "elevations = float(elevation[4, 4])" 188 | ] 189 | }, 190 | { 191 | "cell_type": "code", 192 | "execution_count": null, 193 | "id": "0ef26699-5611-4c93-bb26-4fdef1138eec", 194 | "metadata": {}, 195 | "outputs": [], 196 | "source": [ 197 | "# Estimate evapotranspiration with nine different methods with Pandas.Series\n", 198 | "pet_penmans = pyet.penman(tmeans, winds, rs=rss, elevation=elevations, lat=lats, tmax=tmaxs, tmin=tmins, rh=rhs)\n", 199 | "pet_pts = pyet.priestley_taylor(tmeans, rs=rss, elevation=elevations, lat=lats, tmax=tmaxs, tmin=tmins, rh=rhs)\n", 200 | "pet_makkinks = pyet.makkink(tmeans, rss, elevation=elevations)\n", 201 | "pet_fao56s = pyet.pm_fao56(tmeans, winds, rs=rss, elevation=elevations, lat=lats, tmax=tmaxs, tmin=tmins, rh=rhs)\n", 202 | "pet_hamons = pyet.hamon(tmeans, lat=lats, method=1)\n", 203 | "pet_oudins = pyet.oudin(tmeans, lat=lats)\n", 204 | "pet_haudes = pyet.haude(tmaxs, rhs)\n", 205 | "pet_turcs = pyet.turc(tmeans, rss, rhs)\n", 206 | "pet_hars = pyet.hargreaves(tmeans, tmaxs, tmins, lats)" 207 | ] 208 | }, 209 | { 210 | "cell_type": "code", 211 | "execution_count": null, 212 | "id": "7165b896-c86e-460f-a592-874f2381f7d6", 213 | "metadata": {}, 214 | "outputs": [], 215 | "source": [ 216 | "fm = \"\"\"\n", 217 | " ABC\n", 218 | " DEF\n", 219 | " GHI\n", 220 | " \"\"\"\n", 221 | "fig,axs = plt.subplot_mosaic(mosaic=fm, figsize=(12,8))\n", 222 | "axs[\"A\"].scatter(pet_penman[:,4,4].values, pet_penmans.values)\n", 223 | "axs[\"B\"].scatter(pet_pt[:,4,4].values, pet_pts.values)\n", 224 | "axs[\"C\"].scatter(pet_makkink[:,4,4].values, pet_makkinks.values)\n", 225 | "axs[\"D\"].scatter(pet_fao56[:,4,4].values, pet_fao56s.values)\n", 226 | "axs[\"E\"].scatter(pet_hamon[:,4,4].values, pet_hamons.values)\n", 227 | "axs[\"F\"].scatter(pet_oudin[:,4,4].values, pet_oudins.values)\n", 228 | "axs[\"G\"].scatter(pet_haude[:,4,4].values, pet_haudes.values)\n", 229 | "axs[\"H\"].scatter(pet_turc[:,4,4].values, pet_turcs.values)\n", 230 | "axs[\"I\"].scatter(pet_har[:,4,4].values, pet_hars.values)\n", 231 | "for i in axs.keys():\n", 232 | " axs[i].plot([0,7], [0,7])" 233 | ] 234 | }, 235 | { 236 | "cell_type": "markdown", 237 | "id": "1fe2a55b-838a-4e3a-a621-c5fe55db399a", 238 | "metadata": {}, 239 | "source": [ 240 | "## 5. Store results" 241 | ] 242 | }, 243 | { 244 | "cell_type": "code", 245 | "execution_count": null, 246 | "id": "3c5e47fb-3ec7-4bc9-ad6c-be0edc67ba09", 247 | "metadata": {}, 248 | "outputs": [], 249 | "source": [ 250 | "#pet_pt.to_netcdf('../pe_pt_INCA.csv')" 251 | ] 252 | } 253 | ], 254 | "metadata": { 255 | "kernelspec": { 256 | "display_name": "Python 3 (ipykernel)", 257 | "language": "python", 258 | "name": "python3" 259 | }, 260 | "language_info": { 261 | "codemirror_mode": { 262 | "name": "ipython", 263 | "version": 3 264 | }, 265 | "file_extension": ".py", 266 | "mimetype": "text/x-python", 267 | "name": "python", 268 | "nbconvert_exporter": "python", 269 | "pygments_lexer": "ipython3", 270 | "version": "3.11.5" 271 | } 272 | }, 273 | "nbformat": 4, 274 | "nbformat_minor": 5 275 | } 276 | -------------------------------------------------------------------------------- /docs/examples/03_example_knmi.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "d0959a4c", 6 | "metadata": {}, 7 | "source": [ 8 | "# Potential Evapotranspiration from KNMI data\n", 9 | "*R.A. Collenteur, Eawag, 2023*\n", 10 | "\n", 11 | "Data source: KNMI - https://dataplatform.knmi.nl/\n", 12 | "\n", 13 | "In this notebook it is shown how to compute (potential) evapotranspiration from meteorological data using PyEt. Meteorological data is observed by the KNMI at De Bilt in the Netherlands." 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": null, 19 | "id": "f8c6cab3", 20 | "metadata": {}, 21 | "outputs": [], 22 | "source": [ 23 | "import pandas as pd\n", 24 | "import matplotlib.pyplot as plt\n", 25 | "\n", 26 | "import pyet\n", 27 | "pyet.show_versions()" 28 | ] 29 | }, 30 | { 31 | "cell_type": "markdown", 32 | "id": "61cb9b9e", 33 | "metadata": {}, 34 | "source": [ 35 | "## 1. Load KNMI Data\n", 36 | "\n", 37 | "We first load the raw meteorological data observed by the KNMI at De Bilt in the Netherlands. This datafile contains a lot of different variables, please see the end of the notebook for an explanation of all the variables. " 38 | ] 39 | }, 40 | { 41 | "cell_type": "code", 42 | "execution_count": null, 43 | "id": "cbc6c337", 44 | "metadata": {}, 45 | "outputs": [], 46 | "source": [ 47 | "data = pd.read_csv(\"data/example_3/etmgeg_260.txt\", skiprows=46, delimiter=\",\", \n", 48 | " skipinitialspace=True, index_col=\"YYYYMMDD\", parse_dates=True).loc[\"2018\",:]\n", 49 | "data.head()" 50 | ] 51 | }, 52 | { 53 | "cell_type": "markdown", 54 | "id": "be888ed3", 55 | "metadata": {}, 56 | "source": [ 57 | "## 2. Estimating potential evapotranspiration\n", 58 | "\n", 59 | "Now that we have the input data, we can estimate potential evapotranspiration with different estimation methods. Here we choose the Penman, Priestley-Taylor, Makkink, and Oudin methods. " 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": null, 65 | "id": "abe8a230", 66 | "metadata": {}, 67 | "outputs": [], 68 | "source": [ 69 | "# Preprocess the input data\n", 70 | "meteo = pd.DataFrame({\"tmean\":data.TG/10, \"tmax\":data.TX/10, \"tmin\":data.TN/10, \n", 71 | " \"rh\":data.UG, \"wind\":data.FG/10, \"rs\":data.Q/100})\n", 72 | "tmean, tmax, tmin, rh, wind, rs = [meteo[col] for col in meteo.columns]\n", 73 | "pressure = data.PG / 100 # to kPa\n", 74 | "wind = data.FG / 10 # to m/s\n", 75 | "lat = 0.91 # Latitude of the meteorological station\n", 76 | "elevation = 4 # meters above sea-level \n", 77 | "\n", 78 | "# Estimate evapotranspiration with four different methods\n", 79 | "pet_penman = pyet.penman(tmean, wind, rs=rs, elevation=4, lat=0.91, tmax=tmax, tmin=tmin, rh=rh)\n", 80 | "pet_pt = pyet.priestley_taylor(tmean, rs=rs, elevation=4, lat=0.91, tmax=tmax, tmin=tmin, rh=rh)\n", 81 | "pet_makkink = pyet.makkink(tmean, rs, elevation=4, pressure=pressure)\n", 82 | "pet_oudin = pyet.oudin(tmean, lat=0.91)" 83 | ] 84 | }, 85 | { 86 | "cell_type": "markdown", 87 | "id": "578f8ac2", 88 | "metadata": {}, 89 | "source": [ 90 | "## 3. Plot the results\n", 91 | "\n", 92 | "We plot the cumulative sums to compare the different estimation methods." 93 | ] 94 | }, 95 | { 96 | "cell_type": "code", 97 | "execution_count": null, 98 | "id": "4c5a8008", 99 | "metadata": {}, 100 | "outputs": [], 101 | "source": [ 102 | "fig, axs = plt.subplots(figsize=(14,3.5), ncols=2)\n", 103 | "\n", 104 | "pet = [pet_penman, pet_pt, pet_makkink, pet_makkink]\n", 105 | "names = [\"Penman\", \"Priestley-Taylor\", \"Makkink - PyEt\", \"Oudin\"]\n", 106 | "\n", 107 | "for df, name in zip(pet, names):\n", 108 | " axs[0].plot(df,label=name)\n", 109 | " axs[1].plot(df.cumsum(),label=name)\n", 110 | "\n", 111 | "axs[0].set_ylabel(\"PET [mm/day]\", fontsize=16)\n", 112 | "axs[1].set_ylabel(\"Cumulative PET [mm]\", fontsize=12)\n", 113 | "\n", 114 | "for i in (0,1):\n", 115 | " axs[i].set_xlabel(\"Date\", fontsize=12)\n", 116 | " axs[i].legend(loc=2, fontsize=14)\n", 117 | " axs[i].tick_params(\"both\", direction=\"in\", labelsize=14)\n", 118 | "plt.tight_layout()\n", 119 | "\n", 120 | "#plt.savefig(\"Figure1.png\", dpi=300)" 121 | ] 122 | }, 123 | { 124 | "attachments": {}, 125 | "cell_type": "markdown", 126 | "id": "75dacdc4", 127 | "metadata": {}, 128 | "source": [ 129 | "## 4. Comparison: pyet Makkink vs KNMI Makkink\n", 130 | "\n", 131 | "The KNMI also provides Makkink potential evapotranspiration data (column EV24). We can now compare the results from Pyet and the KNMI to confirm that these are roughly the same. Pyet also has a method `makkink_knmi` that calculates exactly the same Makkink evaporation as the KNMI but that is only suitable for the Netherlands." 132 | ] 133 | }, 134 | { 135 | "cell_type": "code", 136 | "execution_count": null, 137 | "id": "95c6b234", 138 | "metadata": {}, 139 | "outputs": [], 140 | "source": [ 141 | "pet_knmi = data.EV24 / 10 # Makkink potential evaporation computen by the KNMI for comparison\n", 142 | "pet_makkink_knmi = pyet.makkink_knmi(tmean, rs).round(1) # same as pet_knmi (if rounded up to 1 decimal) but calculated from tmean and rs\n", 143 | "\n", 144 | "# Plot the two series against each other\n", 145 | "_, ax = plt.subplots(1, 2, figsize=(9,4), sharex=True, sharey=True)\n", 146 | "ax[0].scatter(pet_makkink, pet_knmi, s=15, color=\"C0\")\n", 147 | "ax[0].plot([0,6],[0,6], color=\"red\", label=\"1:1 line\")\n", 148 | "ax[0].set_xlabel(\"pyet-Makkink 'EV24' [mm]\")\n", 149 | "ax[0].grid()\n", 150 | "\n", 151 | "ax[1].scatter(pet_makkink_knmi, pet_knmi, s=15, color=\"C1\")\n", 152 | "ax[1].plot([0,6],[0,6], color=\"red\")\n", 153 | "ax[1].set_xlabel(\"pyet-Makkink_KNMI [mm]\")\n", 154 | "ax[1].grid()\n", 155 | "\n", 156 | "ax[0].set_ylabel(\"KNMI-Makkink [mm]\")\n", 157 | "ax[0].legend(loc=(0,1), frameon=False)\n" 158 | ] 159 | }, 160 | { 161 | "cell_type": "markdown", 162 | "id": "a51c36d1", 163 | "metadata": {}, 164 | "source": [ 165 | "## 5. Estimation of PET - all methods" 166 | ] 167 | }, 168 | { 169 | "cell_type": "code", 170 | "execution_count": null, 171 | "id": "2a2b17d3", 172 | "metadata": {}, 173 | "outputs": [], 174 | "source": [ 175 | "pet_df = pyet.calculate_all(tmean, wind, rs, elevation, lat, tmax=tmax,\n", 176 | " tmin=tmin, rh=rh)" 177 | ] 178 | }, 179 | { 180 | "cell_type": "code", 181 | "execution_count": null, 182 | "id": "055e90d1", 183 | "metadata": {}, 184 | "outputs": [], 185 | "source": [ 186 | "fig, axs = plt.subplots(figsize=(13,4), ncols=2)\n", 187 | "pet_df.plot(ax=axs[0])\n", 188 | "pet_df.cumsum().plot(ax=axs[1], legend=False)\n", 189 | "\n", 190 | "axs[0].set_ylabel(\"PET [mm/day]\", fontsize=12)\n", 191 | "axs[1].set_ylabel(\"Cumulative PET [mm]\", fontsize=12)\n", 192 | "axs[0].legend(ncol=6, loc=[0,1.])\n", 193 | "for i in (0,1):\n", 194 | " axs[i].set_xlabel(\"Date\", fontsize=12)" 195 | ] 196 | }, 197 | { 198 | "cell_type": "code", 199 | "execution_count": null, 200 | "id": "7e55de8b", 201 | "metadata": {}, 202 | "outputs": [], 203 | "source": [ 204 | "#plt.savefig(\"PET_methods.png\", dpi=300)" 205 | ] 206 | }, 207 | { 208 | "cell_type": "markdown", 209 | "id": "815addc4", 210 | "metadata": {}, 211 | "source": [ 212 | "## References\n", 213 | "\n", 214 | "Column description of the KNMI data\n", 215 | "\n", 216 | "- DDVEC = Vector mean wind direction in degrees (360=north, 90=east, 180=south, 270=west, 0=calm/variable)\n", 217 | "- FHVEC = Vector mean windspeed (in 0.1 m/s)\n", 218 | "- FG = Daily mean windspeed (in 0.1 m/s) \n", 219 | "- FHX = Maximum hourly mean windspeed (in 0.1 m/s)\n", 220 | "- FHXH = Hourly division in which FHX was measured\n", 221 | "- FHN = Minimum hourly mean windspeed (in 0.1 m/s)\n", 222 | "- FHNH = Hourly division in which FHN was measured\n", 223 | "- FXX = Maximum wind gust (in 0.1 m/s)\n", 224 | "- FXXH = Hourly division in which FXX was measured\n", 225 | "- TG = Daily mean temperature in (0.1 degrees Celsius)\n", 226 | "- TN = Minimum temperature (in 0.1 degrees Celsius)\n", 227 | "- TNH = Hourly division in which TN was measured\n", 228 | "- TX = Maximum temperature (in 0.1 degrees Celsius)\n", 229 | "- TXH = Hourly division in which TX was measured\n", 230 | "- T10N = Minimum temperature at 10 cm above surface (in 0.1 degrees Celsius)\n", 231 | "- T10NH = 6-hourly division in which T10N was measured; 6=0-6 UT, 12=6-12 UT, 18=12-18 UT, 24=18-24 UT \n", 232 | "- SQ = Sunshine duration (in 0.1 hour) calculated from global radiation (-1 for <0.05 hour)\n", 233 | "- SP = Percentage of maximum potential sunshine duration\n", 234 | "- Q = Global radiation (in J/cm2)\n", 235 | "- DR = Precipitation duration (in 0.1 hour)\n", 236 | "- RH = Daily precipitation amount (in 0.1 mm) (-1 for <0.05 mm)\n", 237 | "- RHX = Maximum hourly precipitation amount (in 0.1 mm) (-1 for <0.05 mm)\n", 238 | "- RHXH = Hourly division in which RHX was measured\n", 239 | "- PG = Daily mean sea level pressure (in 0.1 hPa) calculated from 24 hourly values\n", 240 | "- PX = Maximum hourly sea level pressure (in 0.1 hPa)\n", 241 | "- PXH = Hourly division in which PX was measured\n", 242 | "- PN = Minimum hourly sea level pressure (in 0.1 hPa)\n", 243 | "- PNH = Hourly division in which PN was measured\n", 244 | "- VVN = Minimum visibility; 0: <100 m, 1:100-200 m, 2:200-300 m,..., 49:4900-5000 m, 50:5-6 km, 56:6-7 km, 57:7-8 km,..., 79:29-30 km, 80:30-35 km, 81:35-40 km,..., 89: >70 km)\n", 245 | "- VVNH = Hourly division in which VVN was measured\n", 246 | "- VVX = Maximum visibility; 0: <100 m, 1:100-200 m, 2:200-300 m,..., 49:4900-5000 m, 50:5-6 km, 56:6-7 km, 57:7-8 km,..., 79:29-30 km, 80:30-35 km, 81:35-40 km,..., 89: >70 km)\n", 247 | "- VVXH = Hourly division in which VVX was measured\n", 248 | "- NG = Mean daily cloud cover (in octants, 9=sky invisible)\n", 249 | "- UG = Daily mean relative atmospheric humidity (in percents)\n", 250 | "- UX = Maximum relative atmospheric humidity (in percents)\n", 251 | "- UXH = Hourly division in which UX was measured\n", 252 | "- UN = Minimum relative atmospheric humidity (in percents)\n", 253 | "- UNH = Hourly division in which UN was measured\n", 254 | "- EV24 = Potential evapotranspiration (Makkink) (in 0.1 mm)" 255 | ] 256 | }, 257 | { 258 | "cell_type": "code", 259 | "execution_count": null, 260 | "id": "df028060-9d5f-4616-9797-d44e6b66002b", 261 | "metadata": {}, 262 | "outputs": [], 263 | "source": [] 264 | } 265 | ], 266 | "metadata": { 267 | "kernelspec": { 268 | "display_name": "Python 3 (ipykernel)", 269 | "language": "python", 270 | "name": "python3" 271 | }, 272 | "language_info": { 273 | "codemirror_mode": { 274 | "name": "ipython", 275 | "version": 3 276 | }, 277 | "file_extension": ".py", 278 | "mimetype": "text/x-python", 279 | "name": "python", 280 | "nbconvert_exporter": "python", 281 | "pygments_lexer": "ipython3", 282 | "version": "3.11.5" 283 | }, 284 | "vscode": { 285 | "interpreter": { 286 | "hash": "8a365f246fd2b6cc3f433897ea78f6113fbca3a5aebe59945b06a3e6c75c5dde" 287 | } 288 | } 289 | }, 290 | "nbformat": 4, 291 | "nbformat_minor": 5 292 | } 293 | -------------------------------------------------------------------------------- /docs/examples/04_example_coagmet.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Potential Evapotranspiration from CoAgMET data\n", 8 | "\n", 9 | "*M. Vremec, University of Graz, 2021*\n", 10 | "\n", 11 | "In this notebook it is shown how to compute (reference) evapotranspiration ($ET_0$) from meteorological data observed by the Colorado State University (CoAgMET) at Holyoke in Colorado, USA. The notebook also includes a comparison between $ET_0$ estimated using *pyet* (FAO56) and $ET_0$ available from CoAgMET. According to CoAgMET documentation, the provided reference evapotranspiration is estimated using ASCE reference evapotranspiration for short reference grass (Wright, 2000), which corresponds to the FAO-56 method used with *pyet*.\n", 12 | "\n", 13 | "Source: https://coagmet.colostate.edu/station/selector" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": null, 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "import numpy as np\n", 23 | "import pandas as pd\n", 24 | "import matplotlib.pyplot as plt\n", 25 | "\n", 26 | "import pyet as pyet\n", 27 | "pyet.show_versions()" 28 | ] 29 | }, 30 | { 31 | "cell_type": "markdown", 32 | "metadata": {}, 33 | "source": [ 34 | "## 1. Load CoAgMET Data" 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": null, 40 | "metadata": {}, 41 | "outputs": [], 42 | "source": [ 43 | "data = pd.read_csv(\"data/example_4/et_coagmet.txt\", parse_dates=True, index_col=\"date\")\n", 44 | "data" 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": null, 50 | "metadata": {}, 51 | "outputs": [], 52 | "source": [ 53 | "et0_coagmet = data[\"et_asce0\"]\n", 54 | "\n", 55 | "meteo = pd.DataFrame({\"tmean\":data[\"tavg\"], \n", 56 | " \"tmax\":data[\"tmax\"],\n", 57 | " \"tmin\":data[\"tmin\"], \n", 58 | " \"rhmax\":data[\"rhmax\"]*100,\n", 59 | " \"rhmin\":data[\"rhmin\"]*100, \n", 60 | " \"u2\":data[\"windrun\"]*1000/86400,\n", 61 | " \"rs\":data[\"solar\"]*86400/1000000})\n", 62 | "\n", 63 | "tmean, tmax, tmin, rhmax, rhmin, wind, rs = [meteo[col] for col in meteo.columns]\n", 64 | "lat = 40.49 * np.pi / 180" 65 | ] 66 | }, 67 | { 68 | "cell_type": "markdown", 69 | "metadata": {}, 70 | "source": [ 71 | "## 2. Comparison: pyet FAO56 vs CoAgMET ASCE " 72 | ] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": null, 77 | "metadata": {}, 78 | "outputs": [], 79 | "source": [ 80 | "et0_fao56 = pyet.pm_fao56(tmean, wind, rs=rs, elevation=1138, lat=lat, \n", 81 | " tmax=tmax, tmin=tmin, rhmax=rhmax, rhmin=rhmin)\n", 82 | "\n", 83 | "et0_fao56.plot()\n", 84 | "et0_coagmet.plot();" 85 | ] 86 | }, 87 | { 88 | "cell_type": "markdown", 89 | "metadata": {}, 90 | "source": [ 91 | "## 3. Plot the results\n", 92 | "\n", 93 | "We now plot the evaporation time series against each other to see how these compare. " 94 | ] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": null, 99 | "metadata": {}, 100 | "outputs": [], 101 | "source": [ 102 | "plt.scatter(et0_fao56, et0_coagmet)\n", 103 | "plt.plot([0,15],[0,15], color=\"red\", label=\"1:1 line\")\n", 104 | "plt.legend()\n", 105 | "plt.xlabel(\"ET0 pyet-FAO56 [mm]\")\n", 106 | "plt.ylabel(\"ET0 CoAgMET-ASCE [mm]\");" 107 | ] 108 | } 109 | ], 110 | "metadata": { 111 | "kernelspec": { 112 | "display_name": "Python 3 (ipykernel)", 113 | "language": "python", 114 | "name": "python3" 115 | }, 116 | "language_info": { 117 | "codemirror_mode": { 118 | "name": "ipython", 119 | "version": 3 120 | }, 121 | "file_extension": ".py", 122 | "mimetype": "text/x-python", 123 | "name": "python", 124 | "nbconvert_exporter": "python", 125 | "pygments_lexer": "ipython3", 126 | "version": "3.11.5" 127 | } 128 | }, 129 | "nbformat": 4, 130 | "nbformat_minor": 4 131 | } 132 | -------------------------------------------------------------------------------- /docs/examples/05_example_calibration.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Calibration of potential evapotranspiration methods\n", 8 | "*M. Vremec, R.A. Collenteur University of Graz, 2021*\n", 9 | "\n", 10 | "In this notebook it is shown how to calibrate various (potential) evapotranspiration (PET) equations, using a linear regression relationship between daily potential evapotranspiration obtained with the FAO-56 equation and daily PET estimates obtained with the alternative methods. This notebook requires Scipy and SkLearn python packages to run." 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": null, 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [ 19 | "import pandas as pd\n", 20 | "import matplotlib.pyplot as plt\n", 21 | "from scipy.optimize import least_squares\n", 22 | "from sklearn.metrics import mean_squared_error\n", 23 | "\n", 24 | "import pyet as pyet" 25 | ] 26 | }, 27 | { 28 | "cell_type": "markdown", 29 | "metadata": {}, 30 | "source": [ 31 | "## 1. Load KNMI Data" 32 | ] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": null, 37 | "metadata": {}, 38 | "outputs": [], 39 | "source": [ 40 | "data = pd.read_csv(\"data/example_3/etmgeg_260.txt\", skiprows=46, delimiter=\",\", \n", 41 | " skipinitialspace=True, index_col=\"YYYYMMDD\", parse_dates=True).loc[\"2018\",:]\n", 42 | "data.head()" 43 | ] 44 | }, 45 | { 46 | "cell_type": "markdown", 47 | "metadata": {}, 48 | "source": [ 49 | "## 2.Estimation of potential evapotranspiration" 50 | ] 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": null, 55 | "metadata": {}, 56 | "outputs": [], 57 | "source": [ 58 | "# define meteorological variables needed for PE estimation\n", 59 | "meteo = pd.DataFrame({\"tmean\":data.TG/10, \"tmax\":data.TX/10, \"tmin\":data.TN/10, \"rh\":data.UG, \"wind\":data.FG/10, \"rs\":data.Q/100})\n", 60 | "tmean, tmax, tmin, rh, wind, rs = [meteo[col] for col in meteo.columns]\n", 61 | "pressure = data.PG / 100 # to kPa\n", 62 | "wind = data.FG/10 # to m/s\n", 63 | "lat = 0.91 # [rad]\n", 64 | "elevation = 4 \n", 65 | "\n", 66 | "pet_fao56 = pyet.pm_fao56(tmean, wind, rs=rs, elevation=elevation, lat=lat, \n", 67 | " tmax=tmax, tmin=tmin, rh=rh) # FAO-56 method\n", 68 | "pet_romanenko = pyet.romanenko(tmean, rh) # Romanenko method\n", 69 | "pet_abtew = pyet.abtew(tmean, rs) # Abtew method" 70 | ] 71 | }, 72 | { 73 | "cell_type": "markdown", 74 | "metadata": {}, 75 | "source": [ 76 | "The model performance of the Abtew and Romanenko method will be evaluated using the root mean square error (RMSE), as implemented in SkLearn’s mean_squared_error method." 77 | ] 78 | }, 79 | { 80 | "cell_type": "code", 81 | "execution_count": null, 82 | "metadata": {}, 83 | "outputs": [], 84 | "source": [ 85 | "pet_fao56.plot()\n", 86 | "pet_romanenko.plot()\n", 87 | "pet_abtew.plot()\n", 88 | "plt.ylabel(\"PE [mm/day]\")\n", 89 | "print(\"RMSE(Romanenko) = {} mm/d\".format(mean_squared_error(pet_fao56, pet_romanenko, squared=False)))\n", 90 | "print(\"RMSE(Abtew) = {} mm/d\".format(mean_squared_error(pet_fao56, pet_abtew, squared=False)))" 91 | ] 92 | }, 93 | { 94 | "cell_type": "markdown", 95 | "metadata": {}, 96 | "source": [ 97 | "## 3. Calibration of different PE equations\n", 98 | "\n", 99 | "The least squares approach is applied to estimate the parameters in the PE equations, by minimizing the sum of the residuals between the simulated (Abtew and Romanenko) and observed (FAO-56) data. The minimization of the objective function is done using the Trust Region Reflective algorithm, as implemented in Scipy’s least squares method." 100 | ] 101 | }, 102 | { 103 | "cell_type": "markdown", 104 | "metadata": {}, 105 | "source": [ 106 | "### 3.1 Romanenko method" 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": null, 112 | "metadata": {}, 113 | "outputs": [], 114 | "source": [ 115 | "# Define function for computing residuals\n", 116 | "def cal_romanenko(k, obs):\n", 117 | " return pyet.romanenko(tmean, rh, k)-obs" 118 | ] 119 | }, 120 | { 121 | "cell_type": "code", 122 | "execution_count": null, 123 | "metadata": {}, 124 | "outputs": [], 125 | "source": [ 126 | "# estimate k in the Romanenko method\n", 127 | "x0 = 4.5 # initial estimate of parameter\n", 128 | "res_1 = least_squares(cal_romanenko, x0, args=[pet_fao56])\n", 129 | "res_1.x" 130 | ] 131 | }, 132 | { 133 | "cell_type": "code", 134 | "execution_count": null, 135 | "metadata": {}, 136 | "outputs": [], 137 | "source": [ 138 | "# Compute RMSE using the calibrated value of k\n", 139 | "pet_romanenko_cal = pyet.romanenko(tmean, rh, k=res_1.x)\n", 140 | "print(\"RMSE(Romanenko) = {} mm/d\".format(mean_squared_error(pet_fao56, pet_romanenko_cal, squared=False)))" 141 | ] 142 | }, 143 | { 144 | "cell_type": "markdown", 145 | "metadata": {}, 146 | "source": [ 147 | "RMSE (calibrated) = 0.546 < RMSE (uncalibrated) = 0.694" 148 | ] 149 | }, 150 | { 151 | "cell_type": "markdown", 152 | "metadata": {}, 153 | "source": [ 154 | "### 3.2 Abtew method" 155 | ] 156 | }, 157 | { 158 | "cell_type": "code", 159 | "execution_count": null, 160 | "metadata": {}, 161 | "outputs": [], 162 | "source": [ 163 | "# Define function for computing residuals and initial estimate of parameters\n", 164 | "def cal_abtew(k,obs):\n", 165 | " return pyet.abtew(tmean, rs, k)-obs\n", 166 | "x0 = 0.53" 167 | ] 168 | }, 169 | { 170 | "cell_type": "code", 171 | "execution_count": null, 172 | "metadata": {}, 173 | "outputs": [], 174 | "source": [ 175 | "# estimate k in the Romanenko method\n", 176 | "res_2 = least_squares(cal_abtew, x0, args=[pet_fao56])\n", 177 | "res_2.x" 178 | ] 179 | }, 180 | { 181 | "cell_type": "code", 182 | "execution_count": null, 183 | "metadata": {}, 184 | "outputs": [], 185 | "source": [ 186 | "pet_abtew_cali = pyet.abtew(tmean, rs, res_2.x)\n", 187 | "print(\"RMSE(Abtew) = {} mm/d\".format(mean_squared_error(pet_fao56, pet_abtew_cali, squared=False)))" 188 | ] 189 | }, 190 | { 191 | "cell_type": "markdown", 192 | "metadata": {}, 193 | "source": [ 194 | "RMSE (calibrated) = 0.613 < RMSE (uncalibrated) = 0.741" 195 | ] 196 | }, 197 | { 198 | "cell_type": "code", 199 | "execution_count": null, 200 | "metadata": {}, 201 | "outputs": [], 202 | "source": [ 203 | "pet_fao56.plot()\n", 204 | "pet_romanenko_cal.plot()\n", 205 | "pet_abtew_cali.plot()\n", 206 | "plt.ylabel(\"PET [mm/d]\");" 207 | ] 208 | } 209 | ], 210 | "metadata": { 211 | "kernelspec": { 212 | "display_name": "Python 3 (ipykernel)", 213 | "language": "python", 214 | "name": "python3" 215 | }, 216 | "language_info": { 217 | "codemirror_mode": { 218 | "name": "ipython", 219 | "version": 3 220 | }, 221 | "file_extension": ".py", 222 | "mimetype": "text/x-python", 223 | "name": "python", 224 | "nbconvert_exporter": "python", 225 | "pygments_lexer": "ipython3", 226 | "version": "3.11.5" 227 | } 228 | }, 229 | "nbformat": 4, 230 | "nbformat_minor": 4 231 | } 232 | -------------------------------------------------------------------------------- /docs/examples/07_example_climate_change.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Potential evapotranspiration under warming and elevated $CO_2$ concentration\n", 8 | "*M. Vremec, R.A. Collenteur University of Graz, 2021*\n", 9 | "\n", 10 | "In this notebook it is shown how to estimate Potential Evapotranspiration under elevated $CO_2$ conditions and an increase in temperature.\n", 11 | "The work follows the suggestions made by Yang et al. (2019) to include the sensitivity of stomatal resistance to $CO_2$ in the Penman-Monteith equation.\n", 12 | "\n", 13 | "Yang, Y., Roderick, M.L., Zhang, S. et al. Hydrologic implications of vegetation response to elevated CO2 in climate projections. Nature Clim Change 9, 44–48 (2019). https://doi.org/10.1038/s41558-018-0361-0" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": null, 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "import pandas as pd\n", 23 | "import numpy as np\n", 24 | "import matplotlib.pyplot as plt\n", 25 | "\n", 26 | "import pyet as pyet" 27 | ] 28 | }, 29 | { 30 | "cell_type": "markdown", 31 | "metadata": {}, 32 | "source": [ 33 | "## 1. Load daily data from ZAMG (Messstationen Tagesdaten)\n", 34 | "\n", 35 | "station: Graz Universität 16412\n", 36 | "\n", 37 | "Selected variables:\n", 38 | "- globalstrahlung (global radiation), J/cm2 needs to be in MJ/m3d, ZAMG abbreviation - strahl\n", 39 | "- arithmetische windgeschwindigkeit (wind speed), m/s, ZAMG abbreviation - vv\n", 40 | "- relative feuchte (relative humidity), %, ZAMG abbreviation - rel\n", 41 | "- lufttemparatur (air temperature) in 2 m, C, ZAMG abbreviation - t\n", 42 | "- lufttemperatur (air temperature) max in 2 m, C, ZAMG abbreviation - tmax\n", 43 | "- lufttemperatur (air temperature) min in 2 m, C, ZAMG abbreviation - tmin\n", 44 | "- latitute and elevation of a station" 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": null, 50 | "metadata": {}, 51 | "outputs": [], 52 | "source": [ 53 | "#read data\n", 54 | "data_16412 = pd.read_csv('data/example_1/klima_daily.csv', index_col=1, parse_dates=True)\n", 55 | "data_16412" 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": null, 61 | "metadata": {}, 62 | "outputs": [], 63 | "source": [ 64 | "# Convert Glabalstrahlung J/cm2 to MJ/m2 by dividing to 100\n", 65 | "\n", 66 | "meteo = pd.DataFrame({\"time\":data_16412.index, \"tmean\":data_16412.t, \"tmax\":data_16412.tmax, \"tmin\":data_16412.tmin, \"rh\":data_16412.rel, \n", 67 | " \"wind\":data_16412.vv, \"rs\":data_16412.strahl/100})\n", 68 | "time, tmean, tmax, tmin, rh, wind, rs = [meteo[col] for col in meteo.columns]\n", 69 | "\n", 70 | "lat = 47.077778 * np.pi / 180 # Latitude of the meteorological station, converting from degrees to radians\n", 71 | "elevation = 367 # meters above sea-level" 72 | ] 73 | }, 74 | { 75 | "cell_type": "markdown", 76 | "metadata": {}, 77 | "source": [ 78 | "## 2.Estimate potential evapotranspiration under RCP scenarios" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": null, 84 | "metadata": {}, 85 | "outputs": [], 86 | "source": [ 87 | "# the first element in the list includes the rise in temperature, and the CO2 concentration\n", 88 | "rcp_26 = [1.3, 450]\n", 89 | "rcp_45 = [2, 650]\n", 90 | "rcp_85 = [4.5, 850]\n", 91 | "\n", 92 | "def pe_cc(tincrease, co2):\n", 93 | " pe = pyet.pm(tmean+tincrease, wind, rs=rs, elevation=elevation, lat=lat, \n", 94 | " tmax=tmax+tincrease, tmin=tmin+tincrease, rh=rh, co2=co2)\n", 95 | " return pe\n", 96 | "\n", 97 | "# Estimate evapotranspiration with four different methods and create a dataframe\n", 98 | "pet_df = pd.DataFrame()\n", 99 | "pet_df[\"amb.\"] = pe_cc(0, 420)\n", 100 | "pet_df[\"RCP 2.6\"] = pe_cc(rcp_26[0], rcp_26[1])\n", 101 | "pet_df[\"RCP 4.5\"] = pe_cc(rcp_45[0], rcp_45[1])\n", 102 | "pet_df[\"RCP 8.5\"] = pe_cc(rcp_85[0], rcp_85[1])" 103 | ] 104 | }, 105 | { 106 | "cell_type": "code", 107 | "execution_count": null, 108 | "metadata": {}, 109 | "outputs": [], 110 | "source": [ 111 | "fig, axs = plt.subplots(figsize=(13,4), ncols=2)\n", 112 | "pet_df.plot(ax=axs[0])\n", 113 | "pet_df.cumsum().plot(ax=axs[1], legend=False)\n", 114 | "\n", 115 | "axs[0].set_ylabel(\"PET [mm/day]\", fontsize=12)\n", 116 | "axs[1].set_ylabel(\"Cumulative PET [mm]\", fontsize=12)\n", 117 | "axs[0].legend(ncol=6, loc=[0,1.])\n", 118 | "for i in (0,1):\n", 119 | " axs[i].set_xlabel(\"Date\", fontsize=12)" 120 | ] 121 | } 122 | ], 123 | "metadata": { 124 | "kernelspec": { 125 | "display_name": "Python 3 (ipykernel)", 126 | "language": "python", 127 | "name": "python3" 128 | }, 129 | "language_info": { 130 | "codemirror_mode": { 131 | "name": "ipython", 132 | "version": 3 133 | }, 134 | "file_extension": ".py", 135 | "mimetype": "text/x-python", 136 | "name": "python", 137 | "nbconvert_exporter": "python", 138 | "pygments_lexer": "ipython3", 139 | "version": "3.11.5" 140 | } 141 | }, 142 | "nbformat": 4, 143 | "nbformat_minor": 4 144 | } 145 | -------------------------------------------------------------------------------- /docs/examples/08_crop_coefficient.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "999bc8b7-804c-4301-b8c7-5077a38dc06f", 6 | "metadata": { 7 | "pycharm": { 8 | "name": "#%% md\n" 9 | } 10 | }, 11 | "source": [ 12 | "# Determining the crop coefficient function with Python\n", 13 | "\n", 14 | "*M. Vremec, October 2022, University of Graz*\n", 15 | "\n", 16 | "Data source: ZAMG - https://data.hub.zamg.ac.at\n", 17 | "\n", 18 | "What is done:\n", 19 | "\n", 20 | "- load the station data from ZAMG\n", 21 | "- estimate potential evapotranspiration\n", 22 | "- determine the crop coefficient function based on equation 65 in Allen et al. 1998\n", 23 | "- plot and store result" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": null, 29 | "id": "7a2ae568-3c3b-44f5-8b82-ab5a99d35d2d", 30 | "metadata": { 31 | "pycharm": { 32 | "name": "#%%\n" 33 | } 34 | }, 35 | "outputs": [], 36 | "source": [ 37 | "import pandas as pd\n", 38 | "import matplotlib.pyplot as plt\n", 39 | "import numpy as np\n", 40 | "import pyet\n", 41 | "pyet.show_versions()" 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "id": "da6f3e98-0073-4db9-b763-03beb2611e78", 47 | "metadata": { 48 | "pycharm": { 49 | "name": "#%% md\n" 50 | } 51 | }, 52 | "source": [ 53 | "## 1. Loading daily data from ZAMG (Messstationen Tagesdaten)\n", 54 | "\n", 55 | "station: Graz Universität 16412\n", 56 | "\n", 57 | "Selected variables:\n", 58 | "- globalstrahlung (global radiation), J/cm2 needs to be in MJ/m3d, ZAMG abbreviation - strahl\n", 59 | "- arithmetische windgeschwindigkeit (wind speed), m/s, ZAMG abbreviation - vv\n", 60 | "- relative feuchte (relative humidity), %, ZAMG abbreviation - rel\n", 61 | "- lufttemparatur (air temperature) in 2 m, C, ZAMG abbreviation - t\n", 62 | "- lufttemperatur (air temperature) max in 2 m, C, ZAMG abbreviation - tmax\n", 63 | "- lufttemperatur (air temperature) min in 2 m, C, ZAMG abbreviation - tmin\n", 64 | "- latitute and elevation of a station" 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": null, 70 | "id": "236434b2-3c33-4772-93ec-de0cb7317209", 71 | "metadata": { 72 | "pycharm": { 73 | "name": "#%%\n" 74 | } 75 | }, 76 | "outputs": [], 77 | "source": [ 78 | "#read data\n", 79 | "data_16412 = pd.read_csv('data/example_1/klima_daily.csv', index_col=1, parse_dates=True)\n", 80 | "data_16412" 81 | ] 82 | }, 83 | { 84 | "cell_type": "markdown", 85 | "id": "22ebf3bc-6b9a-4f81-a455-7ca1b51879dd", 86 | "metadata": { 87 | "pycharm": { 88 | "name": "#%% md\n" 89 | } 90 | }, 91 | "source": [ 92 | "## 2. Calculate PET for Graz Universität - 16412" 93 | ] 94 | }, 95 | { 96 | "cell_type": "code", 97 | "execution_count": null, 98 | "id": "bc5ba933-22c2-4c5b-9ca6-43a4bcdad344", 99 | "metadata": { 100 | "pycharm": { 101 | "name": "#%%\n" 102 | } 103 | }, 104 | "outputs": [], 105 | "source": [ 106 | "# Convert Glabalstrahlung J/cm2 to MJ/m2 by dividing to 100\n", 107 | "\n", 108 | "meteo = pd.DataFrame({\"time\":data_16412.index, \"tmean\":data_16412.t, \"tmax\":data_16412.tmax, \"tmin\":data_16412.tmin, \"rh\":data_16412.rel, \n", 109 | " \"wind\":data_16412.vv, \"rs\":data_16412.strahl/100})\n", 110 | "time, tmean, tmax, tmin, rh, wind, rs = [meteo[col] for col in meteo.columns]\n", 111 | "\n", 112 | "lat = 47.077778*np.pi/180 # Latitude of the meteorological station, converting from degrees to radians\n", 113 | "elevation = 367 # meters above sea-level\n", 114 | "\n", 115 | "# Estimate potential ET with Penman-Monteith FAO-56\n", 116 | "pet_pm = pyet.pm_fao56(tmean, wind, rs=rs, elevation=elevation, \n", 117 | " lat=lat, tmax=tmax, tmin=tmin, rh=rh)" 118 | ] 119 | }, 120 | { 121 | "cell_type": "markdown", 122 | "id": "696cb6aa-773d-4e1b-8a11-64ee5a5242e9", 123 | "metadata": {}, 124 | "source": [ 125 | "## 3. Determine the crop coefficient function\n", 126 | "\n", 127 | "Based on: https://www.fao.org/3/x0490e/x0490e0b.htm\n", 128 | "figure 34.\n", 129 | "\n", 130 | "\n", 131 | "![Figure 34](https://www.fao.org/3/x0490e/x0490e6k.gif)" 132 | ] 133 | }, 134 | { 135 | "cell_type": "code", 136 | "execution_count": null, 137 | "id": "f92b3767-7233-461c-a924-cafc6f5c9882", 138 | "metadata": {}, 139 | "outputs": [], 140 | "source": [ 141 | "Kcini = 0.3 \n", 142 | "Kcmid = 1.1\n", 143 | "Kcend = 0.65\n", 144 | "\n", 145 | "crop_ini = pd.Timestamp(\"2020-04-01\")\n", 146 | "crop_dev = pd.Timestamp(\"2020-05-01\")\n", 147 | "mid_season = pd.Timestamp(\"2020-06-01\")\n", 148 | "late_s_start = pd.Timestamp(\"2020-07-01\")\n", 149 | "late_s_end = pd.Timestamp(\"2020-08-01\")" 150 | ] 151 | }, 152 | { 153 | "cell_type": "code", 154 | "execution_count": null, 155 | "id": "86d39fcb-0dea-42ca-9b7a-afa5327ad990", 156 | "metadata": {}, 157 | "outputs": [], 158 | "source": [ 159 | "kc = pd.Series(index=[crop_ini, crop_dev, mid_season, late_s_start, late_s_end],\n", 160 | " data=[Kcini, Kcini, Kcmid, Kcmid, Kcend])\n", 161 | "kc = kc.resample(\"d\").mean().interpolate()\n", 162 | "kc.plot()" 163 | ] 164 | }, 165 | { 166 | "cell_type": "code", 167 | "execution_count": null, 168 | "id": "c73067c3-2d7e-4bb4-8cff-91f707f7c34b", 169 | "metadata": {}, 170 | "outputs": [], 171 | "source": [ 172 | "petc = pet_pm.loc[crop_dev:late_s_end] * kc" 173 | ] 174 | }, 175 | { 176 | "cell_type": "markdown", 177 | "id": "e6e2564c-48ed-46b7-a275-fd17db592456", 178 | "metadata": { 179 | "pycharm": { 180 | "name": "#%% md\n" 181 | } 182 | }, 183 | "source": [ 184 | "## 4. Plot results" 185 | ] 186 | }, 187 | { 188 | "cell_type": "code", 189 | "execution_count": null, 190 | "id": "c9a4582f-219b-47f8-9ed2-dac80720cf55", 191 | "metadata": { 192 | "pycharm": { 193 | "name": "#%%\n" 194 | } 195 | }, 196 | "outputs": [], 197 | "source": [ 198 | "pet_pm.loc[crop_dev:late_s_end].plot(label=\"Potential evapotranspiration\")\n", 199 | "petc.loc[crop_dev:late_s_end].plot(label=\"Potential crop evapotranspiration\")" 200 | ] 201 | } 202 | ], 203 | "metadata": { 204 | "kernelspec": { 205 | "display_name": "Python 3 (ipykernel)", 206 | "language": "python", 207 | "name": "python3" 208 | }, 209 | "language_info": { 210 | "codemirror_mode": { 211 | "name": "ipython", 212 | "version": 3 213 | }, 214 | "file_extension": ".py", 215 | "mimetype": "text/x-python", 216 | "name": "python", 217 | "nbconvert_exporter": "python", 218 | "pygments_lexer": "ipython3", 219 | "version": "3.11.5" 220 | } 221 | }, 222 | "nbformat": 4, 223 | "nbformat_minor": 5 224 | } 225 | -------------------------------------------------------------------------------- /docs/examples/09_CMIP6_data.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "d8992de4-0b85-4df3-8835-d9dd3e7a3901", 6 | "metadata": {}, 7 | "source": [ 8 | "# Potential Evapotranspiration from CMIP6 climate projections (NetCDF)\n", 9 | "\n", 10 | "*M. Vremec, December 2022, University of Graz*\n", 11 | "\n", 12 | "\n", 13 | "What is done:\n", 14 | "\n", 15 | "- load the data from Copernicus\n", 16 | "- estimate potential evapotranspiration\n", 17 | "- plot\n", 18 | "\n", 19 | "Data source: \n", 20 | "* Copernicus - https://cds.climate.copernicus.eu/ (SSP1-1.9, EC-Earth3 (Europe))\n", 21 | "* [CMIP6 GMD special issue articles](https://gmd.copernicus.org/articles/special_issue590.html)\n", 22 | "* [Terms of using CMIP6 data](https://pcmdi.llnl.gov/CMIP6/TermsOfUse/TermsOfUse6-2.html)" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": null, 28 | "id": "768010e4-9e5d-4136-80ab-b8eea806dc69", 29 | "metadata": {}, 30 | "outputs": [], 31 | "source": [ 32 | "import numpy as np\n", 33 | "import pandas as pd\n", 34 | "import xarray as xr\n", 35 | "import pyet" 36 | ] 37 | }, 38 | { 39 | "cell_type": "code", 40 | "execution_count": null, 41 | "id": "69ade8af-753a-4b8d-8680-9531ea24a070", 42 | "metadata": {}, 43 | "outputs": [], 44 | "source": [ 45 | "# Import data\n", 46 | "xr_ds = xr.open_dataset(\"data/example_9/tas_day_EC-Earth3_ssp119_r4i1p1f1_gr_21000601-21000630_v20200425.nc\", \n", 47 | " engine=\"netcdf4\")" 48 | ] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": null, 53 | "id": "88f411a2-13b5-41c5-859c-b7d661ea5959", 54 | "metadata": {}, 55 | "outputs": [], 56 | "source": [ 57 | "# Define mean temperature and latitude\n", 58 | "tmean = xr_ds[\"tas\"] - 273\n", 59 | "lat = xr_ds.lat * np.pi/180" 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": null, 65 | "id": "3acf61a9-de98-4048-bcf1-70ef84e871bd", 66 | "metadata": {}, 67 | "outputs": [], 68 | "source": [ 69 | "# Compute PET with Oudin\n", 70 | "pet_oudin = pyet.oudin(tmean, lat=lat)" 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": null, 76 | "id": "8523231e-c618-4ad9-bb0d-204d2103ddb1", 77 | "metadata": {}, 78 | "outputs": [], 79 | "source": [ 80 | "pet_oudin.sel(time=\"2100-6-2\").plot()" 81 | ] 82 | }, 83 | { 84 | "cell_type": "markdown", 85 | "id": "9abfc543-b01e-4f76-bc36-81094d5d842f", 86 | "metadata": {}, 87 | "source": [ 88 | "## Acknowledgement\n", 89 | "\n", 90 | "We acknowledge the World Climate Research Programme, which, through its Working Group on Coupled Modelling, coordinated and promoted CMIP6. We thank the climate modeling groups for producing and making available their model output, the Earth System Grid Federation (ESGF) for archiving the data and providing access, and the multiple funding agencies who support CMIP6 and ESGF." 91 | ] 92 | } 93 | ], 94 | "metadata": { 95 | "kernelspec": { 96 | "display_name": "Python 3 (ipykernel)", 97 | "language": "python", 98 | "name": "python3" 99 | }, 100 | "language_info": { 101 | "codemirror_mode": { 102 | "name": "ipython", 103 | "version": 3 104 | }, 105 | "file_extension": ".py", 106 | "mimetype": "text/x-python", 107 | "name": "python", 108 | "nbconvert_exporter": "python", 109 | "pygments_lexer": "ipython3", 110 | "version": "3.11.5" 111 | } 112 | }, 113 | "nbformat": 4, 114 | "nbformat_minor": 5 115 | } 116 | -------------------------------------------------------------------------------- /docs/examples/data/example_10/10_example_meteo.csv: -------------------------------------------------------------------------------- 1 | YYYYMMDD,Tmean[°C],Tmax[°C],Tmin[°C],RH[%],Rs[MJ/m2/d],u2[m/s],PET_KNMI[mm/d] 2 | 2018-01-01,6.8,8.8,5.2,84,2.24,5.0,0.3 3 | 2018-01-02,6.5,9.1,4.5,88,1.74,4.5,0.2 4 | 2018-01-03,8.8,11.2,5.4,73,0.76,8.8,0.1 5 | 2018-01-04,8.2,10.7,6.8,82,1.09,5.6,0.2 6 | 2018-01-05,6.4,8.7,4.4,87,1.16,4.0,0.2 7 | 2018-01-06,4.5,6.6,2.2,89,1.89,2.8,0.2 8 | 2018-01-07,1.0,3.1,-1.2,77,4.11,5.9,0.5 9 | 2018-01-08,1.1,3.9,-1.4,77,3.88,5.5,0.4 10 | 2018-01-09,3.0,6.1,-0.2,89,1.9,3.3,0.2 11 | 2018-01-10,6.9,7.6,5.8,94,0.69,2.8,0.1 12 | 2018-01-11,5.7,7.6,2.8,97,1.35,1.6,0.2 13 | 2018-01-12,5.0,5.9,4.1,94,1.03,1.5,0.1 14 | 2018-01-13,4.2,6.2,1.2,90,0.9,3.1,0.1 15 | 2018-01-14,2.9,5.2,0.8,82,3.69,4.3,0.4 16 | 2018-01-15,5.2,8.0,1.7,89,0.57,6.5,0.1 17 | 2018-01-16,5.2,6.9,2.5,78,3.36,4.9,0.4 18 | 2018-01-17,4.6,6.0,2.0,73,2.05,5.3,0.3 19 | 2018-01-18,4.7,10.2,0.5,85,1.67,6.4,0.2 20 | 2018-01-19,3.7,6.9,1.4,85,3.26,3.1,0.4 21 | 2018-01-20,2.2,3.6,0.8,93,1.21,2.7,0.1 22 | 2018-01-21,3.0,6.9,-0.4,85,4.31,2.6,0.5 23 | 2018-01-22,6.3,8.4,2.0,88,2.37,3.5,0.3 24 | 2018-01-23,8.1,11.2,4.6,88,2.21,5.0,0.3 25 | 2018-01-24,11.7,14.4,7.3,84,1.47,7.0,0.2 26 | 2018-01-25,7.7,9.2,5.0,88,2.21,3.8,0.3 27 | 2018-01-26,5.4,8.6,2.5,95,2.97,1.8,0.4 28 | 2018-01-27,5.8,7.2,4.4,83,2.87,5.0,0.4 29 | 2018-01-28,9.7,10.8,6.9,90,2.78,5.4,0.4 30 | 2018-01-29,8.8,10.7,4.6,84,1.66,5.8,0.2 31 | 2018-01-30,4.7,7.4,2.1,86,4.85,2.5,0.6 32 | 2018-01-31,6.5,9.5,3.2,84,0.99,5.4,0.1 33 | 2018-02-01,3.4,6.1,1.9,87,2.9,4.1,0.3 34 | 2018-02-02,4.4,6.8,-0.5,87,3.35,3.0,0.4 35 | 2018-02-03,2.0,4.6,-0.4,90,1.85,2.6,0.2 36 | 2018-02-04,1.7,3.6,-0.4,76,5.0,4.3,0.6 37 | 2018-02-05,1.0,3.4,-2.1,81,3.41,4.1,0.4 38 | 2018-02-06,-2.1,2.0,-4.6,76,7.89,3.2,0.8 39 | 2018-02-07,-2.2,1.9,-6.8,76,7.92,2.1,0.8 40 | 2018-02-08,1.0,4.7,-2.5,79,6.66,2.6,0.7 41 | 2018-02-09,0.6,2.2,-0.9,79,4.15,4.6,0.4 42 | 2018-02-10,2.9,7.0,0.2,84,4.78,3.5,0.6 43 | 2018-02-11,4.5,7.3,2.1,80,3.73,5.7,0.5 44 | 2018-02-12,3.0,6.3,0.7,74,6.43,3.7,0.8 45 | 2018-02-13,2.0,6.3,-1.2,71,8.6,4.3,1.0 46 | 2018-02-14,1.9,5.5,-2.2,65,8.73,5.4,1.0 47 | 2018-02-15,4.3,9.9,0.5,88,2.52,4.5,0.3 48 | 2018-02-16,3.2,8.7,-2.1,79,8.3,2.5,1.0 49 | 2018-02-17,0.9,7.6,-3.6,82,6.86,1.9,0.8 50 | 2018-02-18,0.7,7.3,-4.9,87,8.4,2.3,0.9 51 | 2018-02-19,1.2,3.4,-2.5,84,3.26,2.5,0.4 52 | 2018-02-20,0.8,4.6,-3.8,82,4.39,2.3,0.5 53 | 2018-02-21,0.3,4.7,-3.9,77,9.29,2.8,1.0 54 | 2018-02-22,0.2,5.0,-3.0,71,9.89,4.0,1.1 55 | 2018-02-23,-0.5,3.5,-3.8,71,10.68,4.9,1.1 56 | 2018-02-24,-0.4,3.3,-3.4,55,10.13,6.6,1.1 57 | 2018-02-25,-2.2,1.1,-4.9,56,11.4,5.7,1.1 58 | 2018-02-26,-3.3,0.0,-5.9,67,8.66,4.6,0.8 59 | 2018-02-27,-3.6,0.2,-6.4,70,10.28,3.4,0.9 60 | 2018-02-28,-6.6,-4.6,-8.5,69,8.51,6.5,0.7 61 | 2018-03-01,-4.7,-0.7,-8.3,50,8.38,9.4,0.7 62 | 2018-03-02,-3.7,-0.5,-7.2,52,8.51,8.3,0.8 63 | 2018-03-03,-1.7,1.2,-4.5,64,5.36,4.9,0.5 64 | 2018-03-04,5.0,11.3,-2.2,78,8.93,4.4,1.1 65 | 2018-03-05,6.6,12.0,3.2,80,11.25,3.7,1.5 66 | 2018-03-06,5.6,10.1,1.0,82,7.08,2.4,0.9 67 | 2018-03-07,5.9,9.5,0.8,87,7.25,2.8,0.9 68 | 2018-03-08,4.8,8.3,1.2,84,4.26,5.4,0.5 69 | 2018-03-09,6.5,9.7,2.9,82,9.73,3.3,1.3 70 | 2018-03-10,11.4,15.1,7.1,89,6.9,3.5,1.0 71 | 2018-03-11,10.2,13.9,7.1,91,5.72,2.9,0.8 72 | 2018-03-12,10.3,14.3,7.4,85,7.2,3.0,1.1 73 | 2018-03-13,5.6,8.9,3.7,92,2.05,3.4,0.3 74 | 2018-03-14,5.8,11.1,-0.4,80,13.69,3.8,1.8 75 | 2018-03-15,7.3,10.7,3.6,70,7.61,6.0,1.0 76 | 2018-03-16,3.8,6.2,0.0,87,1.99,5.8,0.2 77 | 2018-03-17,-1.4,0.0,-2.6,60,6.21,9.3,0.6 78 | 2018-03-18,-1.0,1.2,-3.0,47,10.75,8.0,1.1 79 | 2018-03-19,-0.2,4.7,-3.9,45,16.62,5.3,1.7 80 | 2018-03-20,3.8,7.9,-1.3,69,16.63,4.4,2.0 81 | 2018-03-21,3.6,8.0,-3.6,78,11.26,2.8,1.4 82 | 2018-03-22,6.4,9.9,4.1,88,6.2,3.5,0.8 83 | 2018-03-23,6.2,7.3,5.2,79,5.73,4.1,0.8 84 | 2018-03-24,6.5,12.5,0.0,77,11.32,2.5,1.5 85 | 2018-03-25,5.4,12.3,-1.8,79,13.62,2.2,1.7 86 | 2018-03-26,4.7,11.2,0.0,88,9.94,1.6,1.2 87 | 2018-03-27,5.6,9.8,0.7,88,6.69,3.3,0.9 88 | 2018-03-28,5.9,7.8,2.8,92,3.18,2.8,0.4 89 | 2018-03-29,6.5,10.7,3.6,77,12.31,3.2,1.6 90 | 2018-03-30,9.0,13.5,5.2,74,13.36,4.4,1.9 91 | 2018-03-31,7.2,12.4,2.5,83,10.5,2.0,1.4 92 | 2018-04-01,4.8,7.7,0.6,89,4.13,2.0,0.5 93 | 2018-04-02,7.7,12.1,0.4,82,7.47,3.8,1.0 94 | 2018-04-03,12.7,17.0,9.1,79,12.18,5.0,1.9 95 | 2018-04-04,10.3,14.7,7.8,83,7.17,3.7,1.1 96 | 2018-04-05,5.9,9.9,0.1,79,10.14,4.6,1.3 97 | 2018-04-06,8.8,14.7,0.0,58,19.55,5.5,2.8 98 | 2018-04-07,13.9,21.6,8.6,62,17.65,3.9,2.8 99 | 2018-04-08,15.2,23.4,7.7,72,17.23,2.0,2.9 100 | 2018-04-09,12.6,17.5,9.2,81,9.75,3.2,1.5 101 | 2018-04-10,15.5,20.0,9.5,72,12.41,3.0,2.1 102 | 2018-04-11,12.7,17.5,8.5,87,10.28,2.3,1.6 103 | 2018-04-12,10.4,14.6,6.5,89,6.96,2.9,1.0 104 | 2018-04-13,10.0,14.4,3.8,86,9.31,2.1,1.4 105 | 2018-04-14,11.5,16.1,6.9,77,12.14,2.0,1.8 106 | 2018-04-15,12.7,17.2,9.4,89,8.64,2.0,1.4 107 | 2018-04-16,11.4,16.1,5.1,77,19.18,2.8,2.9 108 | 2018-04-17,12.8,20.3,4.7,72,20.21,3.0,3.2 109 | 2018-04-18,16.3,23.5,6.9,65,21.76,2.4,3.7 110 | 2018-04-19,19.2,27.4,9.4,58,22.19,3.3,4.0 111 | 2018-04-20,18.3,26.5,10.0,66,21.81,2.3,3.9 112 | 2018-04-21,15.5,22.1,7.9,68,22.41,2.3,3.7 113 | 2018-04-22,17.9,26.6,9.9,61,19.53,3.2,3.4 114 | 2018-04-23,13.1,16.6,9.7,67,17.55,3.8,2.8 115 | 2018-04-24,12.9,15.5,9.5,76,7.46,4.7,1.2 116 | 2018-04-25,11.5,14.5,8.5,76,13.13,5.0,2.0 117 | 2018-04-26,10.3,13.7,7.7,69,18.29,4.7,2.7 118 | 2018-04-27,10.3,15.1,4.0,68,12.29,2.9,1.8 119 | 2018-04-28,11.7,14.5,4.9,79,8.59,3.1,1.3 120 | 2018-04-29,9.2,12.2,3.2,94,4.78,3.2,0.7 121 | 2018-04-30,9.5,13.2,5.8,88,5.51,4.2,0.8 122 | 2018-05-01,7.9,12.8,2.6,72,18.61,4.8,2.6 123 | 2018-05-02,10.9,16.9,1.8,67,24.57,4.6,3.7 124 | 2018-05-03,10.4,15.6,4.1,67,25.78,2.4,3.8 125 | 2018-05-04,11.9,18.5,2.3,64,25.86,2.3,4.0 126 | 2018-05-05,14.7,22.0,5.8,59,26.2,3.0,4.3 127 | 2018-05-06,17.2,25.8,6.2,57,26.79,2.5,4.6 128 | 2018-05-07,18.0,25.3,8.5,61,26.68,2.5,4.7 129 | 2018-05-08,19.1,26.6,9.7,60,26.4,2.4,4.7 130 | 2018-05-09,17.9,25.4,10.0,67,24.97,2.2,4.4 131 | 2018-05-10,12.1,16.7,5.3,80,15.25,3.0,2.4 132 | 2018-05-11,12.3,18.7,2.5,72,24.61,2.1,3.8 133 | 2018-05-12,17.0,23.3,7.2,61,20.03,2.8,3.4 134 | 2018-05-13,13.1,16.3,11.2,88,6.89,2.8,1.1 135 | 2018-05-14,18.7,25.8,11.2,71,23.03,3.6,4.1 136 | 2018-05-15,19.2,24.6,12.6,57,26.27,3.5,4.7 137 | 2018-05-16,15.3,20.7,11.0,75,18.5,4.6,3.1 138 | 2018-05-17,11.4,15.3,8.5,69,26.17,4.4,4.0 139 | 2018-05-18,10.9,14.6,8.1,69,18.08,3.3,2.7 140 | 2018-05-19,11.0,14.1,6.1,83,10.48,1.8,1.6 141 | 2018-05-20,15.4,24.2,4.7,70,26.83,2.0,4.5 142 | 2018-05-21,19.2,25.7,9.1,50,23.24,3.5,4.2 143 | 2018-05-22,18.3,24.3,14.4,68,16.09,2.3,2.8 144 | 2018-05-23,18.8,25.2,14.3,67,13.25,2.9,2.4 145 | 2018-05-24,18.3,22.6,13.3,69,10.33,3.1,1.8 146 | 2018-05-25,20.6,26.1,15.9,73,23.8,2.6,4.4 147 | 2018-05-26,23.0,29.0,14.7,59,25.74,3.1,4.9 148 | 2018-05-27,20.3,26.2,15.3,71,12.24,3.0,2.2 149 | 2018-05-28,22.5,29.7,13.1,69,24.65,2.5,4.7 150 | 2018-05-29,22.1,30.7,18.0,79,19.39,2.6,3.7 151 | 2018-05-30,21.5,26.5,16.8,80,22.12,2.0,4.1 152 | 2018-05-31,20.2,24.0,16.5,85,17.74,2.2,3.2 153 | 2018-06-01,17.6,23.4,14.4,90,11.6,2.5,2.0 154 | 2018-06-02,17.0,20.3,14.8,89,7.67,2.7,1.3 155 | 2018-06-03,19.3,23.5,15.5,79,15.68,2.3,2.8 156 | 2018-06-04,18.2,23.2,15.8,82,17.18,2.9,3.0 157 | 2018-06-05,16.7,20.8,13.4,76,20.81,2.9,3.6 158 | 2018-06-06,19.8,27.6,11.5,74,26.81,2.3,4.9 159 | 2018-06-07,22.4,28.6,12.8,62,28.21,2.0,5.3 160 | 2018-06-08,18.5,19.9,17.4,91,5.36,2.3,0.9 161 | 2018-06-09,19.8,24.5,16.2,79,23.7,3.0,4.3 162 | 2018-06-10,18.2,22.8,12.9,78,24.65,3.3,4.3 163 | 2018-06-11,16.9,22.4,10.1,75,26.24,3.8,4.5 164 | 2018-06-12,14.8,16.7,13.1,76,9.79,3.2,1.6 165 | 2018-06-13,14.7,19.4,9.6,71,17.5,3.0,2.9 166 | 2018-06-14,15.7,19.8,9.0,80,11.46,3.5,1.9 167 | 2018-06-15,18.8,24.2,14.8,72,24.25,2.0,4.3 168 | 2018-06-16,17.5,21.3,14.7,65,17.13,3.0,3.0 169 | 2018-06-17,15.6,18.2,14.2,75,9.32,3.8,1.6 170 | 2018-06-18,16.7,19.2,14.7,81,7.61,4.3,1.3 171 | 2018-06-19,17.7,20.4,13.8,83,7.0,2.6,1.2 172 | 2018-06-20,19.1,25.2,13.9,79,18.97,3.4,3.4 173 | 2018-06-21,14.4,17.7,11.6,68,19.21,5.0,3.1 174 | 2018-06-22,13.3,16.8,9.1,68,13.12,4.4,2.1 175 | 2018-06-23,15.1,20.0,8.9,71,16.3,2.5,2.7 176 | 2018-06-24,14.7,17.7,10.7,78,13.49,2.9,2.2 177 | 2018-06-25,16.5,21.4,10.5,75,23.64,2.9,4.0 178 | 2018-06-26,16.0,21.5,8.9,73,29.22,2.9,4.9 179 | 2018-06-27,17.9,24.5,9.9,75,26.9,3.4,4.7 180 | 2018-06-28,21.2,27.0,14.5,63,26.68,3.6,5.0 181 | 2018-06-29,19.5,26.1,13.7,66,29.53,3.8,5.3 182 | 2018-06-30,21.3,27.9,12.6,51,30.05,4.1,5.6 183 | 2018-07-01,21.3,26.5,15.6,36,30.56,5.8,5.7 184 | 2018-07-02,20.6,26.9,12.9,40,30.35,4.6,5.6 185 | 2018-07-03,19.6,26.0,12.5,60,29.61,3.7,5.4 186 | 2018-07-04,19.2,25.2,13.2,66,26.14,2.8,4.7 187 | 2018-07-05,18.9,24.3,13.6,74,27.72,2.5,4.9 188 | 2018-07-06,19.5,25.2,13.0,67,20.82,2.5,3.8 189 | 2018-07-07,18.3,24.9,10.0,63,25.05,2.2,4.4 190 | 2018-07-08,18.6,24.4,10.0,66,28.74,2.8,5.1 191 | 2018-07-09,17.5,22.7,11.5,76,13.15,2.9,2.3 192 | 2018-07-10,16.1,21.7,11.4,72,11.61,3.8,2.0 193 | 2018-07-11,17.8,22.7,9.3,74,17.5,3.1,3.1 194 | 2018-07-12,19.0,24.6,13.6,74,26.35,3.3,4.7 195 | 2018-07-13,18.5,24.3,12.5,73,25.91,2.5,4.6 196 | 2018-07-14,18.8,24.9,10.8,67,28.8,2.6,5.1 197 | 2018-07-15,20.8,28.7,10.6,60,27.49,2.0,5.1 198 | 2018-07-16,22.6,29.7,11.4,53,26.67,2.0,5.1 199 | 2018-07-17,20.0,26.0,12.0,67,25.65,2.9,4.7 200 | 2018-07-18,19.1,25.3,10.4,64,20.99,2.0,3.8 201 | 2018-07-19,19.6,25.9,11.8,67,21.23,2.4,3.8 202 | 2018-07-20,19.1,25.2,10.5,68,25.54,2.4,4.6 203 | 2018-07-21,21.1,26.8,14.7,65,26.25,2.4,4.9 204 | 2018-07-22,20.6,27.2,14.0,68,23.1,2.3,4.3 205 | 2018-07-23,22.5,29.7,13.1,67,26.46,1.5,5.0 206 | 2018-07-24,24.6,31.9,15.9,62,21.83,1.9,4.3 207 | 2018-07-25,24.4,30.2,18.3,60,16.23,2.5,3.2 208 | 2018-07-26,27.7,35.7,19.2,53,24.97,2.4,5.1 209 | 2018-07-27,29.7,35.4,22.4,34,25.69,4.0,5.4 210 | 2018-07-28,20.3,24.2,13.4,72,15.7,3.5,2.9 211 | 2018-07-29,21.3,26.7,13.5,56,16.07,3.5,3.0 212 | 2018-07-30,24.0,30.6,16.8,64,22.61,3.0,4.4 213 | 2018-07-31,20.6,26.6,13.0,72,21.24,3.0,3.9 214 | 2018-08-01,20.4,27.6,11.1,65,23.05,1.8,4.2 215 | 2018-08-02,22.6,29.7,14.5,64,25.01,2.2,4.8 216 | 2018-08-03,23.9,30.7,14.3,61,25.28,2.3,4.9 217 | 2018-08-04,22.4,28.3,16.2,70,21.1,2.8,4.0 218 | 2018-08-05,20.5,25.5,14.4,68,23.32,2.5,4.3 219 | 2018-08-06,23.1,31.3,12.6,57,25.31,1.9,4.9 220 | 2018-08-07,25.9,33.9,14.6,52,23.43,1.8,4.7 221 | 2018-08-08,19.6,23.2,12.0,76,12.76,3.9,2.3 222 | 2018-08-09,17.2,23.0,10.9,82,7.96,3.4,1.4 223 | 2018-08-10,16.5,21.1,12.6,73,17.66,4.9,3.0 224 | 2018-08-11,15.6,20.9,11.0,71,20.85,3.1,3.5 225 | 2018-08-12,19.8,27.1,11.2,66,15.22,2.5,2.8 226 | 2018-08-13,18.7,22.6,16.2,84,9.18,2.5,1.6 227 | 2018-08-14,18.8,22.4,15.7,79,15.08,3.1,2.7 228 | 2018-08-15,19.7,22.6,16.4,78,10.3,3.3,1.9 229 | 2018-08-16,18.4,23.6,14.9,79,14.45,3.4,2.6 230 | 2018-08-17,17.4,23.2,11.0,77,19.25,1.8,3.3 231 | 2018-08-18,17.8,21.7,10.9,78,11.59,2.9,2.0 232 | 2018-08-19,19.4,22.7,17.0,78,9.53,4.0,1.7 233 | 2018-08-20,19.6,23.1,14.5,82,9.85,2.2,1.8 234 | 2018-08-21,20.0,25.8,14.2,81,15.7,1.3,2.9 235 | 2018-08-22,19.5,25.3,13.6,82,18.97,1.9,3.4 236 | 2018-08-23,18.3,22.2,12.9,85,8.17,3.3,1.4 237 | 2018-08-24,15.5,19.8,10.8,72,16.53,3.7,2.8 238 | 2018-08-25,12.7,16.8,9.1,85,13.83,2.7,2.2 239 | 2018-08-26,14.5,19.6,7.0,72,15.74,4.1,2.6 240 | 2018-08-27,17.1,19.3,13.3,80,7.48,4.0,1.3 241 | 2018-08-28,16.7,21.3,10.1,79,11.78,1.9,2.0 242 | 2018-08-29,14.7,19.7,8.3,89,7.42,1.9,1.2 243 | 2018-08-30,15.0,19.9,8.0,76,17.95,2.5,3.0 244 | 2018-08-31,12.4,18.0,6.9,80,9.43,1.0,1.5 245 | 2018-09-01,13.5,20.8,5.2,74,17.4,1.5,2.8 246 | 2018-09-02,15.9,22.5,7.1,72,17.31,2.5,2.9 247 | 2018-09-03,18.6,22.2,14.5,83,7.83,2.4,1.4 248 | 2018-09-04,20.6,25.3,18.3,88,12.86,1.7,2.4 249 | 2018-09-05,19.0,22.8,17.0,89,9.29,2.7,1.7 250 | 2018-09-06,16.6,19.0,13.1,94,4.69,1.6,0.8 251 | 2018-09-07,14.6,17.4,12.2,76,15.32,3.7,2.5 252 | 2018-09-08,14.8,18.4,11.5,76,11.12,3.3,1.8 253 | 2018-09-09,18.3,23.6,13.3,71,14.13,3.1,2.5 254 | 2018-09-10,17.4,20.1,14.9,78,8.79,3.2,1.5 255 | 2018-09-11,18.8,22.6,14.9,79,9.69,4.8,1.7 256 | 2018-09-12,15.3,18.4,11.9,90,4.44,2.4,0.7 257 | 2018-09-13,13.0,19.5,7.0,78,17.69,1.3,2.8 258 | 2018-09-14,13.5,19.9,4.8,74,14.3,3.0,2.3 259 | 2018-09-15,14.7,20.3,8.8,80,11.07,2.5,1.8 260 | 2018-09-16,14.6,22.4,7.8,78,11.56,2.4,1.9 261 | 2018-09-17,15.3,24.2,7.6,77,15.48,2.3,2.6 262 | 2018-09-18,18.9,24.6,9.3,67,16.01,4.4,2.9 263 | 2018-09-19,19.1,23.6,13.8,72,13.15,4.5,2.4 264 | 2018-09-20,19.7,23.6,16.4,69,12.16,4.2,2.2 265 | 2018-09-21,15.4,20.0,9.5,67,9.86,6.1,1.6 266 | 2018-09-22,12.1,15.8,10.5,84,5.93,3.5,0.9 267 | 2018-09-23,9.5,10.9,5.8,95,2.62,2.0,0.4 268 | 2018-09-24,9.9,15.4,4.9,79,13.3,2.5,1.9 269 | 2018-09-25,9.4,16.4,2.5,79,15.17,1.5,2.2 270 | 2018-09-26,11.9,19.0,3.9,76,14.28,2.8,2.2 271 | 2018-09-27,13.2,21.5,6.7,73,14.74,2.1,2.3 272 | 2018-09-28,11.4,16.3,2.7,75,11.6,3.1,1.8 273 | 2018-09-29,7.5,15.5,1.0,80,13.52,0.9,1.8 274 | 2018-09-30,9.9,17.5,-0.1,77,11.87,1.8,1.7 275 | 2018-10-01,9.5,13.3,6.2,82,10.16,2.9,1.5 276 | 2018-10-02,11.7,16.5,7.8,84,5.78,3.7,0.9 277 | 2018-10-03,12.6,16.4,8.4,73,10.76,2.6,1.7 278 | 2018-10-04,14.7,18.0,11.6,82,5.71,3.3,0.9 279 | 2018-10-05,12.8,21.2,7.3,83,12.54,1.9,2.0 280 | 2018-10-06,13.9,21.0,6.3,83,11.48,2.6,1.8 281 | 2018-10-07,10.1,15.3,3.6,79,11.1,2.6,1.6 282 | 2018-10-08,9.5,16.9,2.9,84,9.8,2.0,1.4 283 | 2018-10-09,10.9,19.1,5.8,84,11.59,2.0,1.7 284 | 2018-10-10,15.1,23.5,5.0,74,11.72,3.0,1.9 285 | 2018-10-11,17.6,22.9,13.0,73,8.85,3.5,1.5 286 | 2018-10-12,18.7,22.8,14.7,76,10.23,4.0,1.8 287 | 2018-10-13,20.0,26.3,15.4,69,10.34,3.3,1.9 288 | 2018-10-14,17.4,24.2,12.6,70,9.7,3.4,1.7 289 | 2018-10-15,16.1,22.9,10.7,76,8.42,2.1,1.4 290 | 2018-10-16,15.3,24.3,8.8,80,9.42,1.6,1.6 291 | 2018-10-17,13.2,20.2,8.2,88,7.63,1.4,1.2 292 | 2018-10-18,12.2,16.3,7.3,82,8.67,2.6,1.3 293 | 2018-10-19,9.2,15.8,2.7,83,9.77,1.9,1.4 294 | 2018-10-20,8.6,15.6,1.5,87,6.52,1.0,0.9 295 | 2018-10-21,12.2,16.5,6.1,79,6.35,1.9,1.0 296 | 2018-10-22,10.8,15.0,3.1,81,7.38,2.9,1.1 297 | 2018-10-23,12.8,14.7,7.9,78,2.34,4.6,0.4 298 | 2018-10-24,14.0,16.3,12.2,89,4.52,3.7,0.7 299 | 2018-10-25,12.1,13.6,10.9,86,2.2,2.8,0.3 300 | 2018-10-26,9.5,11.8,6.0,87,1.94,3.9,0.3 301 | 2018-10-27,7.1,11.3,3.6,83,7.86,2.5,1.1 302 | 2018-10-28,4.0,7.8,0.7,74,8.63,4.0,1.1 303 | 2018-10-29,4.4,6.1,2.0,85,1.69,4.3,0.2 304 | 2018-10-30,5.7,9.4,3.7,92,1.17,4.5,0.2 305 | 2018-10-31,8.5,11.4,4.6,80,6.82,3.7,1.0 306 | 2018-11-01,10.4,13.2,8.5,82,2.98,3.4,0.4 307 | 2018-11-02,7.8,11.8,2.3,82,6.97,2.7,1.0 308 | 2018-11-03,4.3,10.1,-0.1,90,5.88,2.0,0.7 309 | 2018-11-04,5.9,11.9,0.6,90,6.02,2.6,0.8 310 | 2018-11-05,7.7,11.2,5.1,94,4.15,2.7,0.6 311 | 2018-11-06,12.4,19.0,6.8,78,6.27,3.1,1.0 312 | 2018-11-07,11.1,13.9,8.7,82,3.21,4.4,0.5 313 | 2018-11-08,9.2,13.7,4.0,83,6.54,3.1,0.9 314 | 2018-11-09,9.0,11.2,5.8,87,4.0,3.2,0.6 315 | 2018-11-10,11.6,13.4,9.3,90,1.78,4.8,0.3 316 | 2018-11-11,11.4,13.4,8.2,87,2.09,4.2,0.3 317 | 2018-11-12,10.0,11.7,8.5,95,1.76,2.5,0.3 318 | 2018-11-13,10.2,13.2,8.1,86,5.01,4.6,0.7 319 | 2018-11-14,8.0,13.2,3.8,90,5.37,2.7,0.7 320 | 2018-11-15,6.9,12.8,3.1,88,5.38,2.8,0.7 321 | 2018-11-16,3.4,7.7,0.6,96,2.16,2.3,0.3 322 | 2018-11-17,4.1,9.1,1.0,79,5.56,3.5,0.7 323 | 2018-11-18,2.9,7.8,-1.4,69,5.31,3.9,0.6 324 | 2018-11-19,4.9,6.9,2.8,80,1.73,5.3,0.2 325 | 2018-11-20,3.4,4.4,2.6,73,0.59,7.0,0.1 326 | 2018-11-21,3.4,5.3,1.8,81,2.54,3.7,0.3 327 | 2018-11-22,3.2,4.3,1.7,84,1.59,3.3,0.2 328 | 2018-11-23,3.2,6.9,-0.8,88,3.07,2.3,0.4 329 | 2018-11-24,2.4,3.5,0.6,94,1.01,2.6,0.1 330 | 2018-11-25,3.4,4.7,1.9,93,0.95,2.9,0.1 331 | 2018-11-26,4.9,6.0,3.5,86,0.9,3.6,0.1 332 | 2018-11-27,3.3,5.2,1.4,86,1.33,2.8,0.2 333 | 2018-11-28,6.1,9.5,2.8,91,0.9,5.3,0.1 334 | 2018-11-29,10.2,11.2,9.5,87,0.89,5.1,0.1 335 | 2018-11-30,9.6,11.6,6.3,83,2.36,4.5,0.3 336 | 2018-12-01,7.7,9.6,5.7,92,2.15,4.8,0.3 337 | 2018-12-02,12.0,13.2,9.6,95,0.61,5.4,0.1 338 | 2018-12-03,11.4,13.3,7.4,88,2.17,4.8,0.3 339 | 2018-12-04,4.8,9.6,0.2,85,3.5,1.6,0.4 340 | 2018-12-05,5.5,8.4,1.7,93,1.36,3.5,0.2 341 | 2018-12-06,11.1,12.3,8.4,95,0.7,4.7,0.1 342 | 2018-12-07,10.0,11.8,7.1,89,0.44,6.2,0.1 343 | 2018-12-08,9.1,11.2,6.7,81,0.9,6.7,0.1 344 | 2018-12-09,8.1,10.0,5.5,81,1.88,5.3,0.3 345 | 2018-12-10,5.5,7.7,3.6,81,2.04,3.2,0.3 346 | 2018-12-11,3.9,7.7,-0.9,88,2.39,1.6,0.3 347 | 2018-12-12,0.8,4.3,-2.9,90,1.67,2.0,0.2 348 | 2018-12-13,0.8,2.5,-0.8,89,3.6,4.0,0.4 349 | 2018-12-14,0.9,3.3,-0.8,86,3.82,2.9,0.4 350 | 2018-12-15,0.3,0.8,-1.1,73,1.06,5.0,0.1 351 | 2018-12-16,1.4,3.3,-1.0,98,0.9,3.5,0.1 352 | 2018-12-17,5.6,8.4,2.4,94,0.88,3.5,0.1 353 | 2018-12-18,6.4,7.9,4.4,87,1.82,4.8,0.2 354 | 2018-12-19,7.4,9.8,5.5,86,1.29,4.2,0.2 355 | 2018-12-20,7.5,8.7,5.4,90,0.76,4.9,0.1 356 | 2018-12-21,9.7,12.3,7.4,90,0.47,5.9,0.1 357 | 2018-12-22,9.6,10.8,8.9,85,1.27,4.6,0.2 358 | 2018-12-23,7.1,8.9,5.4,95,0.67,2.8,0.1 359 | 2018-12-24,5.7,8.4,3.2,87,3.55,1.5,0.5 360 | 2018-12-25,5.6,7.5,3.7,77,2.19,3.2,0.3 361 | 2018-12-26,4.0,5.3,1.5,89,0.91,2.6,0.1 362 | 2018-12-27,0.8,4.8,-2.7,90,2.43,1.2,0.3 363 | 2018-12-28,2.3,5.4,-3.1,95,0.46,1.6,0.1 364 | 2018-12-29,7.9,10.9,5.4,88,0.57,4.5,0.1 365 | 2018-12-30,8.5,9.5,6.2,90,1.12,2.6,0.2 366 | 2018-12-31,8.9,10.0,7.7,93,1.37,2.7,0.2 367 | -------------------------------------------------------------------------------- /docs/examples/data/example_10/df_Guo_2016.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyet-org/pyet/ff411787fbaeaa903b9966e28df41db9afb05a28/docs/examples/data/example_10/df_Guo_2016.xlsx -------------------------------------------------------------------------------- /docs/examples/data/example_10/elev_ens_0.25deg_reg_v25.0e.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyet-org/pyet/ff411787fbaeaa903b9966e28df41db9afb05a28/docs/examples/data/example_10/elev_ens_0.25deg_reg_v25.0e.nc -------------------------------------------------------------------------------- /docs/examples/data/example_10/fg_ens_mean_0.25deg_reg_2018_v25.0e.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyet-org/pyet/ff411787fbaeaa903b9966e28df41db9afb05a28/docs/examples/data/example_10/fg_ens_mean_0.25deg_reg_2018_v25.0e.nc -------------------------------------------------------------------------------- /docs/examples/data/example_10/hu_ens_mean_0.25deg_reg_2018_v25.0e.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyet-org/pyet/ff411787fbaeaa903b9966e28df41db9afb05a28/docs/examples/data/example_10/hu_ens_mean_0.25deg_reg_2018_v25.0e.nc -------------------------------------------------------------------------------- /docs/examples/data/example_10/qq_ens_mean_0.25deg_reg_2018_v25.0e.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyet-org/pyet/ff411787fbaeaa903b9966e28df41db9afb05a28/docs/examples/data/example_10/qq_ens_mean_0.25deg_reg_2018_v25.0e.nc -------------------------------------------------------------------------------- /docs/examples/data/example_10/spartacus-daily_19610101T0000_20211231T0000.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyet-org/pyet/ff411787fbaeaa903b9966e28df41db9afb05a28/docs/examples/data/example_10/spartacus-daily_19610101T0000_20211231T0000.nc -------------------------------------------------------------------------------- /docs/examples/data/example_10/tasAdjust_AUT_AT.ST_area_annual.csv: -------------------------------------------------------------------------------- 1 | Variable: ,Mean Air Temperature 2 | Variable id: ,tasAdjust 3 | Country: ,AUT 4 | 5 | ,year,CAT current policies warming level,CAT current policies median,CAT current policies 97.5th percentile,CAT current policies 2.5th percentile,NGFS current policies warming level,NGFS current policies median,NGFS current policies 97.5th percentile,NGFS current policies 2.5th percentile,NGFS delayed transition warming level,NGFS delayed transition median,NGFS delayed transition 97.5th percentile,NGFS delayed transition 2.5th percentile,NGFS net-zero 2050 warming level,NGFS net-zero 2050 median,NGFS net-zero 2050 97.5th percentile,NGFS net-zero 2050 2.5th percentile,RCP2.6 warming level,RCP2.6 median,RCP2.6 97.5th percentile,RCP2.6 2.5th percentile,RCP4.5 warming level,RCP4.5 median,RCP4.5 97.5th percentile,RCP4.5 2.5th percentile,RCP6.0 warming level,RCP6.0 median,RCP6.0 97.5th percentile,RCP6.0 2.5th percentile,RCP8.5 warming level,RCP8.5 median,RCP8.5 97.5th percentile,RCP8.5 2.5th percentile 6 | 0,2015,1.1,0.8890512177713674,1.408510881206336,0.44526810821859997,1.1,0.8890512177713674,1.5774477687874622,0.3670427360463562,1.1,0.8890512177713674,1.5774477687874622,0.3670427360463562,1.1,0.8890512177713674,1.5774477687874622,0.3670427360463562,1.1,0.8890512177713674,1.5774477687874622,0.3670427360463562,1.1,0.8890512177713674,1.5774477687874622,0.3670427360463562,1.1,0.8890512177713674,1.5774477687874622,0.3670427360463562,1.1,0.8890512177713674,1.5774477687874622,0.3670427360463562 7 | 1,2020,1.2,1.0770024714642892,1.7197989314796265,0.5274414266381425,1.2,1.0770024714642892,1.7197989314796265,0.44526810821859997,1.2,1.0770024714642892,1.7197989314796265,0.44526810821859997,1.2,1.0770024714642892,1.7197989314796265,0.44526810821859997,1.2,1.0770024714642892,1.7197989314796265,0.44526810821859997,1.2,1.0770024714642892,1.7197989314796265,0.44526810821859997,1.2,1.0770024714642892,1.7197989314796265,0.44526810821859997,1.2,1.0770024714642892,1.7197989314796265,0.44526810821859997 8 | 2,2025,1.3,1.238537242914393,2.0094256848507075,0.5274414266381425,1.4,1.3734862894755349,2.0094256848507075,0.5274414266381425,1.4,1.3734862894755349,2.0094256848507075,0.5274414266381425,1.4,1.3734862894755349,2.1705821556317697,0.5274414266381425,1.3,1.238537242914393,2.0094256848507075,0.5274414266381425,1.3,1.238537242914393,2.0094256848507075,0.5274414266381425,1.3,1.238537242914393,1.8462183039589952,0.5274414266381425,1.3,1.238537242914393,2.1705821556317697,0.5274414266381425 9 | 3,2030,1.4,1.3734862894755349,2.2969903942819005,0.6827719607509698,1.5,1.4925035458238813,2.2969903942819005,0.6827719607509698,1.5,1.4925035458238813,2.2969903942819005,0.6827719607509698,1.4,1.3734862894755349,2.2969903942819005,0.5274414266381425,1.4,1.3734862894755349,2.1705821556317697,0.5274414266381425,1.4,1.3734862894755349,2.2969903942819005,0.6827719607509698,1.4,1.3734862894755349,2.1705821556317697,0.5274414266381425,1.5,1.4925035458238813,2.403906018220526,0.6827719607509698 10 | 4,2035,1.6,1.6483088105845713,2.5275519166474134,0.8116860126209792,1.6,1.6483088105845713,2.5275519166474134,0.8116860126209792,1.6,1.6483088105845713,2.5275519166474134,0.8116860126209792,1.5,1.4925035458238813,2.403906018220526,0.6827719607509698,1.5,1.4925035458238813,2.403906018220526,0.6827719607509698,1.5,1.4925035458238813,2.403906018220526,0.6827719607509698,1.5,1.4925035458238813,2.403906018220526,0.6827719607509698,1.7,1.8020631652346106,2.800213502206507,0.8116860126209792 11 | 5,2040,1.7,1.8020631652346106,2.800213502206507,0.9140143396020267,1.7,1.8020631652346106,2.800213502206507,0.8116860126209792,1.7,1.8020631652346106,2.800213502206507,0.8116860126209792,1.5,1.4925035458238813,2.5275519166474134,0.6827719607509698,1.6,1.6483088105845713,2.5275519166474134,0.6827719607509698,1.7,1.8020631652346106,2.668109850032883,0.8116860126209792,1.6,1.6483088105845713,2.5275519166474134,0.8116860126209792,1.9,2.0205827955613227,3.164776039760339,1.0004108763702784 12 | 6,2045,1.8,1.9210692877537194,3.0434355712761003,0.9140143396020267,1.9,2.0205827955613227,3.0434355712761003,0.9140143396020267,1.7,1.8020631652346106,2.9105192809731855,0.8116860126209792,1.5,1.4925035458238813,2.5275519166474134,0.5274414266381425,1.6,1.6483088105845713,2.668109850032883,0.6827719607509698,1.8,1.9210692877537194,2.9105192809731855,0.9140143396020267,1.7,1.8020631652346106,2.800213502206507,0.8116860126209792,2.1,2.269982395111635,3.4579421805407744,1.1235954215508743 13 | 7,2050,1.9,2.0205827955613227,3.356044753044298,1.0004108763702784,2.0,2.1368265778571875,3.356044753044298,1.0004108763702784,1.7,1.8020631652346106,2.9105192809731855,0.8116860126209792,1.5,1.4925035458238813,2.668109850032883,0.5274414266381425,1.6,1.6483088105845713,2.800213502206507,0.6827719607509698,1.9,2.0205827955613227,3.164776039760339,1.0004108763702784,1.8,1.9210692877537194,3.0434355712761003,0.9140143396020267,2.3,2.4975875937898926,3.9871378830129465,1.3311144595598332 14 | 8,2055,2.0,2.1368265778571875,3.623708014775762,1.1235954215508743,2.1,2.269982395111635,3.623708014775762,1.1235954215508743,1.7,1.8020631652346106,2.9105192809731855,0.6827719607509698,1.5,1.4925035458238813,2.668109850032883,0.5274414266381425,1.7,1.8020631652346106,2.800213502206507,0.6827719607509698,2.0,2.1368265778571875,3.356044753044298,1.0004108763702784,1.9,2.0205827955613227,3.2715258447966584,1.0004108763702784,2.6,2.836387809220298,4.569576295118593,1.3980072477873422 15 | 9,2060,2.2,2.3946839311542365,3.9871378830129465,1.1235954215508743,2.3,2.4975875937898926,3.9871378830129465,1.2447290566208191,1.7,1.8020631652346106,2.9105192809731855,0.6827719607509698,1.5,1.4925035458238813,2.668109850032883,0.5274414266381425,1.7,1.8020631652346106,2.9105192809731855,0.6827719607509698,2.1,2.269982395111635,3.623708014775762,1.1235954215508743,2.1,2.269982395111635,3.356044753044298,1.1235954215508743,2.8,3.007999912702369,5.415865439189144,1.5821654081774656 16 | 10,2065,2.3,2.4975875937898926,4.1457566903311,1.2447290566208191,2.4,2.623101767961785,4.295693097201068,1.3311144595598332,1.6,1.6483088105845713,2.9105192809731855,0.6827719607509698,1.4,1.3734862894755349,2.5275519166474134,0.44526810821859997,1.7,1.8020631652346106,2.9105192809731855,0.6827719607509698,2.2,2.3946839311542365,3.801640532927255,1.1235954215508743,2.2,2.3946839311542365,3.801640532927255,1.2447290566208191,3.1,3.5149892667814746,5.69739262616833,1.6742462246399727 17 | 11,2070,2.3,2.4975875937898926,4.444873728820421,1.2447290566208191,2.5,2.737040120315001,4.569576295118593,1.3980072477873422,1.6,1.6483088105845713,2.9105192809731855,0.6827719607509698,1.4,1.3734862894755349,2.5275519166474134,0.44526810821859997,1.7,1.8020631652346106,2.9105192809731855,0.6827719607509698,2.3,2.4975875937898926,3.9871378830129465,1.2447290566208191,2.4,2.623101767961785,4.1457566903311,1.3311144595598332,3.3,3.808740248707551,6.539402341429493,1.8374226222873322 18 | 12,2075,2.4,2.623101767961785,4.821049266759197,1.3311144595598332,2.6,2.836387809220298,5.029302266482884,1.3980072477873422,1.6,1.6483088105845713,2.9105192809731855,0.6827719607509698,1.4,1.3734862894755349,2.5275519166474134,0.44526810821859997,1.7,1.8020631652346106,2.9105192809731855,0.6827719607509698,2.3,2.4975875937898926,4.295693097201068,1.2447290566208191,2.5,2.737040120315001,4.569576295118593,1.3980072477873422,3.6,4.3118900698726135,6.90394112403116,1.9854672243856566 19 | 13,2080,2.5,2.737040120315001,5.029302266482884,1.3311144595598332,2.7,2.9135046013369155,5.415865439189144,1.4816303105031128,1.6,1.6483088105845713,2.9105192809731855,0.6827719607509698,1.4,1.3734862894755349,2.5275519166474134,0.44526810821859997,1.7,1.8020631652346106,2.9105192809731855,0.6827719607509698,2.4,2.623101767961785,4.444873728820421,1.2447290566208191,2.7,2.9135046013369155,5.294610462278789,1.4816303105031128,3.8,4.77064703313016,6.90394112403116,2.0299632969221793 20 | 14,2085,2.6,2.836387809220298,5.415865439189144,1.3311144595598332,2.8,3.007999912702369,5.617082762834901,1.5821654081774656,1.6,1.6483088105845713,2.9105192809731855,0.6827719607509698,1.4,1.3734862894755349,2.5275519166474134,0.44526810821859997,1.7,1.8020631652346106,3.0434355712761003,0.5274414266381425,2.4,2.623101767961785,4.569576295118593,1.2447290566208191,2.9,3.166363630806335,5.557066700409422,1.5821654081774656,4.1,5.070912985293205,6.90394112403116,2.2175808872314104 21 | 15,2090,2.7,2.9135046013369155,5.557066700409422,1.3980072477873422,2.9,3.166363630806335,5.69739262616833,1.6742462246399727,1.6,1.6483088105845713,2.9105192809731855,0.5274414266381425,1.4,1.3734862894755349,2.5275519166474134,0.44526810821859997,1.7,1.8020631652346106,3.0434355712761003,0.5274414266381425,2.5,2.737040120315001,4.821049266759197,1.2447290566208191,3.0,3.3368940328268053,5.666330063143531,1.5821654081774656,4.4,5.1704014736919435,6.90394112403116,2.5009650840463604 22 | 16,2095,2.7,2.9135046013369155,5.666330063143531,1.3980072477873422,3.1,3.5149892667814746,5.738777599626708,1.6742462246399727,1.6,1.6483088105845713,2.9105192809731855,0.5274414266381425,1.4,1.3734862894755349,2.5275519166474134,0.44526810821859997,1.7,1.8020631652346106,3.0434355712761003,0.5274414266381425,2.5,2.737040120315001,5.029302266482884,1.2447290566208191,3.1,3.5149892667814746,5.738777599626708,1.6742462246399727,4.6,5.653391200063614,6.90394112403116,2.619561055653396 23 | 17,2100,2.8,3.007999912702369,5.69739262616833,1.3980072477873422,3.2,3.666205957968605,6.236571558260422,1.7445291676955341,1.6,1.6483088105845713,2.9105192809731855,0.5274414266381425,1.3,1.238537242914393,2.5275519166474134,0.44526810821859997,1.7,1.8020631652346106,3.0434355712761003,0.5274414266381425,2.5,2.737040120315001,5.294610462278789,1.2447290566208191,3.2,3.666205957968605,6.236571558260422,1.6742462246399727,4.9,6.2009092441744444,6.90394112403116,2.8386324227204844 24 | -------------------------------------------------------------------------------- /docs/examples/data/example_10/tg_ens_mean_0.25deg_reg_2018_v25.0e.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyet-org/pyet/ff411787fbaeaa903b9966e28df41db9afb05a28/docs/examples/data/example_10/tg_ens_mean_0.25deg_reg_2018_v25.0e.nc -------------------------------------------------------------------------------- /docs/examples/data/example_10/tn_ens_mean_0.25deg_reg_2018_v25.0e.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyet-org/pyet/ff411787fbaeaa903b9966e28df41db9afb05a28/docs/examples/data/example_10/tn_ens_mean_0.25deg_reg_2018_v25.0e.nc -------------------------------------------------------------------------------- /docs/examples/data/example_10/tx_ens_mean_0.25deg_reg_2018_v25.0e.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyet-org/pyet/ff411787fbaeaa903b9966e28df41db9afb05a28/docs/examples/data/example_10/tx_ens_mean_0.25deg_reg_2018_v25.0e.nc -------------------------------------------------------------------------------- /docs/examples/data/example_2/incal_hourly_20120501T0000_20120930T2300.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyet-org/pyet/ff411787fbaeaa903b9966e28df41db9afb05a28/docs/examples/data/example_2/incal_hourly_20120501T0000_20120930T2300.nc -------------------------------------------------------------------------------- /docs/examples/data/example_9/tas_day_EC-Earth3_ssp119_r4i1p1f1_gr_21000601-21000630_v20200425.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyet-org/pyet/ff411787fbaeaa903b9966e28df41db9afb05a28/docs/examples/data/example_9/tas_day_EC-Earth3_ssp119_r4i1p1f1_gr_21000601-21000630_v20200425.nc -------------------------------------------------------------------------------- /docs/examples/index.rst: -------------------------------------------------------------------------------- 1 | Example Gallery 2 | =============== 3 | Below you can find examples of how *pyet* models are used for estimating (potential) 4 | evaporation. 5 | 6 | .. toctree:: 7 | :hidden: 8 | :maxdepth: 1 9 | 10 | 01_example_zamg 11 | 02_example_zamg_netcdf 12 | 03_example_knmi 13 | 04_example_coagmet 14 | 05_example_calibration 15 | 06_worked_examples_McMahon_etal_2013 16 | 07_example_climate_change 17 | 08_crop_coefficient 18 | 09_CMIP6_data 19 | 10_example_paper 20 | 21 | `Estimating PET using pandas.Series`_ 22 | 23 | `Estimating PET using xarray.DataArray`_ 24 | 25 | `Benchmarking Makkink`_ 26 | 27 | `Benchmarking FAO56`_ 28 | 29 | `Calibration`_ 30 | 31 | `Examples from McMahon et al., 2013`_ 32 | 33 | `PET under climate change`_ 34 | 35 | `Crop coefficient`_ 36 | 37 | `Estimating PET using CMIP data`_ 38 | 39 | `Notebook supporting PyEt GMD manuscript`_ 40 | 41 | .. _Estimating PET using pandas.Series: 01_example_zamg.html 42 | .. _Estimating PET using xarray.DataArray: 02_example_zamg_netcdf.html 43 | .. _Benchmarking Makkink: 03_example_knmi.html 44 | .. _Benchmarking FAO56: 04_example_coagmet.html 45 | .. _Calibration: 05_example_calibration.html 46 | .. _Examples from McMahon et al., 2013: 06_worked_examples_McMahon_etal_2013.html 47 | .. _PET under climate change: 07_example_climate_change.html 48 | .. _Crop coefficient: 08_crop_coefficient.html 49 | .. _Estimating PET using CMIP data: 09_CMIP6_data.html 50 | .. _Notebook supporting PyEt GMD manuscript: 10_example_paper.html 51 | 52 | .. tip:: 53 | The latest versions of the Jupyter Notebooks can be found in the 54 | examples folder on GitHub! 55 | -------------------------------------------------------------------------------- /docs/examples/utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | 4 | from spotpy.objectivefunctions import kge, rsquared, bias 5 | 6 | cm1 = 1 / 2.54 # centimeters in inches 7 | figw_1c = 8.5 * cm1 # maximum width for 1 column 8 | figw_2c = 17.5 * cm1 # maximum width for 2 columns 9 | 10 | 11 | def scatter_1(ax, x, y, label="treatment", xlabel="obs", ylabel="sim", 12 | best_fit=True, veg_ws=None): 13 | compare = pd.DataFrame({"x": x, "y": y}) 14 | if veg_ws is not None: 15 | compare[veg_ws == 0] = np.nan 16 | compare = compare.dropna() 17 | ax.plot(compare["x"], compare["y"], marker="o", 18 | linestyle="None", markersize=2, color="k", fillstyle="none") 19 | ax.plot([-0.1, 10], [-0.1, 10], color="dodgerblue", alpha=0.7, 20 | linewidth="0.8") 21 | ax.axes.set_xticks(np.arange(0, 10 + 2, 2)) 22 | ax.axes.set_yticks(np.arange(0, 10 + 2, 2)) 23 | ax.set_xlim(-0.1, 10) 24 | ax.set_ylim(-0.1, 10) 25 | if best_fit: 26 | p = np.polyfit(compare["x"], compare["y"], 1) 27 | f = np.poly1d(p) 28 | 29 | # Calculating new x's and y's 30 | x_new = np.linspace(0, 10, y.size) 31 | y_new = f(x_new) 32 | 33 | # Plotting the best fit line with the equation as a legend in latex 34 | ax.plot(x_new, y_new, "r--", linewidth="0.8") 35 | ax.text(0.02, 0.9, f"{label}", color="k", zorder=10, 36 | transform=ax.transAxes) 37 | ax.text(0.6, 0.04, "$Bias$ = " + str( 38 | round(bias(np.asarray(compare["y"]), np.asarray(compare["x"])), 2)) + 39 | "\n" + "$R^2$ = " + str( 40 | round(rsquared(np.asarray(compare["y"]), np.asarray(compare["x"])), 41 | 2)) + 42 | "\n" + "KGE = " + str( 43 | round(kge(np.asarray(compare["y"]), np.asarray(compare["x"])), 2)), 44 | color="k", zorder=10, transform=ax.transAxes) 45 | return ax 46 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | *pyet* - Estimation of Potential Evapotranspiration 2 | =================================================== 3 | 4 | *pyet* is an open source Python package for the estimation of reference and potential evapotranspiration (PET) from 5 | time series data (`Pandas `_) and gridded data (`Xarray `_). This 6 | allows users to estimate potential evapotranspiration and calibrate the models with just a few lines of python code. 7 | 8 | .. grid:: 9 | 10 | .. grid-item-card:: Getting started 11 | :link: user_guide/index 12 | :link-type: doc 13 | 14 | User guide on the basic concepts of Pastas. 15 | 16 | .. grid-item-card:: Examples 17 | :link: examples/index 18 | :link-type: doc 19 | 20 | Examples of *pyet* usage. 21 | 22 | .. grid-item-card:: Code Reference 23 | :link: api/index 24 | :link-type: doc 25 | 26 | *pyet* code reference. 27 | 28 | .. grid:: 29 | 30 | .. grid-item-card:: Publications 31 | :link: publications 32 | :link-type: doc 33 | 34 | Overview of publications that use *pyet* 35 | 36 | .. grid-item-card:: References 37 | :link: references 38 | :link-type: doc 39 | 40 | References used in the package. 41 | 42 | Currently, 18 methods are implemented for calculating daily PET 43 | ----------------------------- 44 | 45 | .. list-table:: PET Calculation Methods 46 | :widths: 15 15 5 5 5 5 5 5 10 47 | :header-rows: 1 48 | 49 | * - Method name 50 | - pyet function 51 | - T 52 | - RH 53 | - R 54 | - u2 55 | - Lat. 56 | - El. 57 | - Benchmarked? 58 | * - Penman 59 | - penman 60 | - ✓ [1] 61 | - ✓ [2] 62 | - ✓ [3] 63 | - ✓ 64 | - ✓ [3] 65 | - ✓ [4] 66 | - ✓ 67 | * - Penman-Monteith 68 | - pm 69 | - ✓ [1] 70 | - ✓ [2] 71 | - ✓ [3] 72 | - ✓ 73 | - ✓ [3] 74 | - ✓ [4] 75 | - ✓ 76 | * - ASCE-PM 77 | - pm_asce 78 | - ✓ [1] 79 | - ✓ [2] 80 | - ✓ [3] 81 | - ✓ 82 | - ✓ [3] 83 | - ✓ [4] 84 | - ✓ 85 | * - FAO-56 86 | - pm_fao56 87 | - ✓ [1] 88 | - ✓ [2] 89 | - ✓ [3] 90 | - ✓ 91 | - ✓ [3] 92 | - ✓ [4] 93 | - ✓ 94 | * - Priestley-Taylor 95 | - priestley_taylor 96 | - ✓ 97 | - ✓ [5] 98 | - ✓ [5] 99 | - - 100 | - ✓ [5] 101 | - ✓ [4] 102 | - ✓ 103 | * - Kimberly-Penman 104 | - kimberly_penman 105 | - ✓ [1] 106 | - ✓ [2] 107 | - ✓ [3] 108 | - ✓ 109 | - ✓ [3] 110 | - ✓ [4] 111 | - - 112 | * - Thom-Oliver 113 | - thom_oliver 114 | - ✓ [1] 115 | - ✓ [2] 116 | - ✓ [3] 117 | - ✓ 118 | - ✓ [3] 119 | - ✓ [4] 120 | - - 121 | * - Blaney-Criddle 122 | - blaney_criddle 123 | - ✓ 124 | - - [6] 125 | - - [6] 126 | - - [6] 127 | - ✓ 128 | - - 129 | - ✓ 130 | * - Hamon 131 | - hamon 132 | - ✓ 133 | - - 134 | - - 135 | - - 136 | - ✓ 137 | - - 138 | - ✓ 139 | * - Romanenko 140 | - romanenko 141 | - ✓ 142 | - ✓ 143 | - - 144 | - - 145 | - - 146 | - - 147 | - ✓ 148 | * - Linacre 149 | - linacre 150 | - ✓ [7] 151 | - - 152 | - - 153 | - - 154 | - - 155 | - ✓ 156 | - ✓ 157 | * - Haude 158 | - haude 159 | - ✓ 160 | - ✓ [8] 161 | - - 162 | - - 163 | - - 164 | - - 165 | - ✓ 166 | * - Turc 167 | - turc 168 | - ✓ 169 | - ✓ 170 | - ✓ 171 | - - 172 | - - 173 | - - 174 | - ✓ 175 | * - Jensen-Haise 176 | - jensen_haise 177 | - ✓ 178 | - - 179 | - ✓ [9] 180 | - - 181 | - ✓ [9] 182 | - - 183 | - ✓ 184 | * - McGuinness-Bordne 185 | - mcguinness_bordne 186 | - ✓ 187 | - - 188 | - - 189 | - - 190 | - ✓ 191 | - - 192 | - ✓ 193 | * - Hargreaves 194 | - hargreaves 195 | - ✓ [10] 196 | - - 197 | - - 198 | - - 199 | - ✓ 200 | - - 201 | - ✓ 202 | * - FAO-24 radiation 203 | - fao_24 204 | - ✓ 205 | - ✓ 206 | - ✓ 207 | - ✓ 208 | - - 209 | - ✓ [4] 210 | - - 211 | * - Abtew 212 | - abtew 213 | - ✓ 214 | - - 215 | - ✓ 216 | - - 217 | - - 218 | - - 219 | - ✓ 220 | * - Makkink 221 | - makkink 222 | - ✓ 223 | - - 224 | - ✓ 225 | - - 226 | - - 227 | - ✓ [4] 228 | - ✓ 229 | * - Oudin 230 | - oudin 231 | - ✓ 232 | - - 233 | - - 234 | - - 235 | - ✓ 236 | - - 237 | - - 238 | 239 | .. rubric:: Footnotes 240 | 241 | .. [1] T_max and T_min can also be provided. 242 | .. [2] RH_max and RH_min can also be provided. If actual vapor pressure is provided, RH is not needed. 243 | .. [3] Input for radiation can be (1) Net radiation, (2) solar radiation, or (3) sunshine hours. If (1), then latitude is not needed. If (1, 3) then latitude and elevation are needed. 244 | .. [4] One must provide either the atmospheric pressure or elevation. 245 | .. [5] If net radiation is provided, RH and Lat are not needed. 246 | .. [6] If method==2, u2, RH_min, and sunshine hours are required. 247 | .. [7] Additional input of Tmax and Tmin, or Tdew. 248 | .. [8] Input can be RH or actual vapor pressure. 249 | .. [9] If method==1, latitude is needed instead of Rs. 250 | .. [10] Tmax and Tmin also needed. 251 | 252 | 253 | Using *pyet*? Show your support by citing us! 254 | ----------------------------- 255 | 256 | If you find *pyet* useful and use it in your research or project, we kindly ask you to cite 257 | the *pyet* preprint published in Hydrology and Earth System Sciences (HESS) as follows: 258 | 259 | - Vremec, M., Collenteur, R. A., and Birk, S.: Technical note: Improved handling of potential 260 | evapotranspiration in hydrological studies with PyEt, Hydrol. Earth Syst. Sci. Discuss. 261 | [preprint], https://doi.org/10.5194/hess-2022-417, 2023. -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/publications.rst: -------------------------------------------------------------------------------- 1 | Publications 2 | ============ 3 | 4 | This page provides an overview of the publications that use *pyet*. The list is generated from the public Zotero 5 | library with the references. If you have used *pyet* in your work, please add the reference to the 6 | `Zotero library `_ (collection `Publications`) and it will show up here! 7 | 8 | 9 | Peer-reviewed publications 10 | -------------------------- 11 | 12 | .. bibliography:: publications.bib 13 | :all: 14 | -------------------------------------------------------------------------------- /docs/references.rst: -------------------------------------------------------------------------------- 1 | References 2 | ========== 3 | 4 | *pyet* is built on a lot of scientific literature on the estimation of potential evapotranspiration. Here the 5 | references are listed for all the methods implemented in *pyet*, and the references used to benchmark the methods. This 6 | list is automatically generated from a public 7 | `Zotero library `_ (collection `References`). For a list of 8 | publications using *pyet* we refer to the `Publications` page of this website. 9 | 10 | 11 | .. bibliography:: references.bib 12 | :all: 13 | 14 | -------------------------------------------------------------------------------- /docs/user_guide/index.rst: -------------------------------------------------------------------------------- 1 | User Guide 2 | ========== 3 | Here you can find guidance on how to get started with pyet, from installing the package on your computer to selecting 4 | a method and estimating potential evapotranspiration. A special section is provided on notation and units, to guide 5 | users to use the correct input data for the different methods. 6 | 7 | GitHub Discussions 8 | ------------------ 9 | If you have any questions on how to use *pyet* not answered in the documentation, please ask your question on the 10 | `GitHub Discussions forum `_. 11 | 12 | .. toctree:: 13 | :maxdepth: 1 14 | 15 | ./installation 16 | ./methods 17 | ./units -------------------------------------------------------------------------------- /docs/user_guide/installation.rst: -------------------------------------------------------------------------------- 1 | Installing and Updating pyet 2 | =============================== 3 | 4 | Installing Python 5 | ----------------- 6 | To install *pyet* a working version of Python 3.7 or higher has to be 7 | installed. We recommend using the `Anaconda Distribution 8 | `_ of Python. This Python distribution 9 | includes most of the python package dependencies and the Jupyter Lab 10 | software to run the notebooks. Moreover, it includes the Graphical User 11 | Interface (GUI) Spyder to start scripting in Python. However, you are free 12 | to install any Python distribution you want. 13 | 14 | Installing the *pyet* package 15 | ------------------------------ 16 | The latest stable version of the *pyet* package is available from the Pypi 17 | package index. 18 | 19 | >>> pip install pyet 20 | 21 | To install in developer mode, clone the GitHub repository and use the 22 | following syntax: 23 | 24 | >>> pip install -e . 25 | 26 | Updating the *pyet* package 27 | ---------------------------- 28 | If you have already installed pyet, it is possible to update pyet 29 | easily. To update, open a Windows command screen or a Mac terminal and type: 30 | 31 | >>> pip install *pyet* --upgrade 32 | 33 | Dependencies 34 | ------------ 35 | pyet depends on a number of Python packages, which are all automatically 36 | installed when using the pip install manager. The following packages are 37 | necessary for the installation of pyet: 38 | 39 | .. include:: ../../requirements.txt 40 | :literal: -------------------------------------------------------------------------------- /docs/user_guide/methods.rst: -------------------------------------------------------------------------------- 1 | Choosing a method 2 | ================= 3 | 4 | This package contains a large number of methods to compute potential evapotranspiration. Each method is separately 5 | documented in the `API-Docs` section of this documentation website. The methods have been subdivided into three broad 6 | classes, depending on the input data that is required for the computation. 7 | 8 | -------------------------------------------------------------------------------- /docs/user_guide/units.rst: -------------------------------------------------------------------------------- 1 | Notation and units 2 | ------------------ 3 | 4 | Many issues and errors in the evapotranspiration estimation come from the wrong units of the input data. Throughout 5 | PyET we have tried to be consistent in the notation of the variables and their units. Table 1 provides an 6 | overview of the different variables, their units, and python function argument name. When providing arguments to any 7 | the evapotranspiration methods, it is important to make sure the units of each variable are as listed in Table 1. 8 | 9 | .. list-table:: 10 | :widths: 25 50 25 11 | :header-rows: 1 12 | 13 | * - .. math:: Variable 14 | - .. math:: Description 15 | - .. math:: Units 16 | * - .. math:: PE 17 | - Potential evaporation 18 | - .. math:: mm d^{-1} 19 | * - .. math:: \Delta 20 | - Slope of vapor pressure curve 21 | - .. math:: kPa °C^{-1} 22 | * - .. math:: \gamma 23 | - Latent heat of vaporization 24 | - .. math:: MJ kg^{-1} 25 | * - .. math:: \rho_w 26 | - Water density (=1000) 27 | - .. math:: kg L^{-1} 28 | * - .. math:: \rho_a 29 | - Air density 30 | - .. math:: kg m^{-3} 31 | * - .. math:: \gamma 32 | - Psychrometric constant 33 | - .. math:: kPa °C^{-1} 34 | * - .. math:: e_s 35 | - Saturation vapour pressure 36 | - .. math:: kPa 37 | * - .. math:: e_a 38 | - Actual vapour pressure 39 | - .. math:: kPa 40 | * - .. math:: r_a 41 | - Aerodynamic resistance 42 | - .. math:: s m^{-1} 43 | * - .. math:: r_s 44 | - Surface resistance of reference crop (=70) 45 | - .. math:: s m^{-1} 46 | * - .. math:: u_2 47 | - Wind speed 2 m above soil surface 48 | - .. math:: m s^{-1} 49 | * - .. math:: T_a 50 | - Air temperature 51 | - .. math:: °C 52 | * - .. math:: T_d 53 | - Dew point temperature 54 | - .. math:: °C 55 | * - .. math:: T_{max} 56 | - Maximum air temperature 57 | - .. math:: °C 58 | * - .. math:: T_{min} 59 | - Minimum air temperature 60 | - .. math:: °C 61 | * - .. math:: R_a 62 | - Extraterrestrial radiation 63 | - .. math:: MJ m^{-2} d^{-1} 64 | * - .. math:: R_s 65 | - Global short-wave radiation 66 | - .. math:: MJ m^{-2} d^{-1} 67 | * - .. math:: R_n 68 | - Net incoming solar radiation 69 | - .. math:: MJ m^{-2} d^{-1} 70 | * - .. math:: RH 71 | - Relative humidity 72 | - .. math:: \% 73 | * - .. math:: DL 74 | - Day length 75 | - .. math:: h d^{-1} 76 | * - .. math:: \alpha 77 | - Surface albedo 78 | - .. math:: \% 79 | * - .. math:: J_D 80 | - Julian day 81 | - ordinal date 82 | * - .. math:: c_p 83 | - Air specific heat capacity 84 | - .. math:: MJ kg^{-1} °C^{-1} -------------------------------------------------------------------------------- /methods.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyet-org/pyet/ff411787fbaeaa903b9966e28df41db9afb05a28/methods.xlsx -------------------------------------------------------------------------------- /pyet/__init__.py: -------------------------------------------------------------------------------- 1 | from .combination import ( 2 | penman, 3 | pm_asce, 4 | pm, 5 | pm_fao56, 6 | priestley_taylor, 7 | kimberly_penman, 8 | thom_oliver, 9 | calculate_all, 10 | ) 11 | from .temperature import blaney_criddle, haude, hamon, romanenko, linacre 12 | from .radiation import ( 13 | turc, 14 | jensen_haise, 15 | mcguinness_bordne, 16 | hargreaves, 17 | fao_24, 18 | abtew, 19 | makkink, 20 | makkink_knmi, 21 | oudin, 22 | ) 23 | from .meteo_utils import ( 24 | calc_psy, 25 | calc_vpc, 26 | calc_lambda, 27 | calc_press, 28 | calc_rho, 29 | calc_e0, 30 | calc_es, 31 | calc_ea, 32 | extraterrestrial_r, 33 | calc_res_surf, 34 | calc_laieff, 35 | calc_res_aero, 36 | relative_distance, 37 | solar_declination, 38 | sunset_angle, 39 | day_of_year, 40 | daylight_hours, 41 | ) 42 | 43 | from .rad_utils import ( 44 | calc_rad_net, 45 | calc_rad_long, 46 | calc_rad_short, 47 | calc_rad_sol_in, 48 | calc_rso, 49 | ) 50 | from .version import __version__ 51 | from .utils import show_versions 52 | -------------------------------------------------------------------------------- /pyet/meteo_utils.py: -------------------------------------------------------------------------------- 1 | """The meteo_utils module contains utility functions for meteorological data. 2 | 3 | """ 4 | 5 | from numpy import cos, exp, isnan, log, pi, sin, tan 6 | from pandas import Series, to_numeric 7 | from xarray import DataArray 8 | from numpy import arccos, clip, nanmax, where 9 | 10 | # Specific heat of air [MJ kg-1 °C-1] 11 | CP = 1.013 * 10**-3 12 | 13 | 14 | def calc_psy(pressure, tmean=None): 15 | """Psychrometric constant [kPa °C-1]. 16 | 17 | Parameters 18 | ---------- 19 | pressure: array_like 20 | atmospheric pressure [kPa]. 21 | tmean: array_like 22 | average day temperature [°C]. 23 | 24 | Returns 25 | ------- 26 | array_like containing the Psychrometric 27 | constant [kPa °C-1]. 28 | 29 | Examples 30 | -------- 31 | >>> psy = calc_psy(pressure, tmean) 32 | 33 | Notes 34 | ----- 35 | if tmean is none: 36 | Based on equation 8 in :cite:t:`allen_crop_1998`. 37 | elif rh is None: 38 | From FAO (1990), ANNEX V, eq. 4. 39 | 40 | """ 41 | if tmean is None: 42 | return 0.000665 * pressure 43 | else: 44 | lambd = calc_lambda(tmean) # MJ kg-1 45 | return CP * pressure / (0.622 * lambd) 46 | 47 | 48 | def calc_vpc(tmean): 49 | """Slope of saturation vapour pressure curve at air Temperature [kPa °C-1]. 50 | 51 | Parameters 52 | ---------- 53 | tmean: array_like 54 | average day temperature [°C]. 55 | 56 | Returns 57 | ------- 58 | array_like containing the calculated 59 | Saturation vapour pressure [kPa °C-1]. 60 | 61 | Examples 62 | -------- 63 | >>> vpc = calc_vpc(tmean) 64 | 65 | Notes 66 | ----- 67 | Based on equation 13. in :cite:t:`allen_crop_1998`. 68 | 69 | """ 70 | es = calc_e0(tmean) 71 | return 4098 * es / (tmean + 237.3) ** 2 72 | 73 | 74 | def calc_lambda(tmean): 75 | """Latent Heat of Vaporization [MJ kg-1]. 76 | 77 | Parameters 78 | ---------- 79 | tmean: array_like 80 | average day temperature [°C]. 81 | 82 | Returns 83 | ------- 84 | array_like containing the calculated Latent Heat 85 | of Vaporization [MJ kg-1]. 86 | 87 | Examples 88 | -------- 89 | >>> lambd = calc_lambda(tmean) 90 | 91 | Notes 92 | ----- 93 | Based on equation (3-1) in :cite:t:`allen_crop_1998`. 94 | 95 | """ 96 | return 2.501 - 0.002361 * tmean 97 | 98 | 99 | def calc_press(elevation, pressure=None): 100 | """Atmospheric pressure [kPa]. 101 | 102 | Parameters 103 | ---------- 104 | elevation: array_like 105 | the site elevation [m]. 106 | pressure: array_like, optional 107 | atmospheric pressure [kPa]. 108 | 109 | Returns 110 | ------- 111 | array_like containing the calculated atmospheric pressure [kPa]. 112 | 113 | Examples 114 | -------- 115 | >>> pressure = calc_press(elevation) 116 | 117 | Notes 118 | ----- 119 | Based on equation 7 in :cite:t:`allen_crop_1998`. 120 | 121 | """ 122 | if pressure is None and elevation is None: 123 | raise Exception("Please provide either pressure or the elevation!") 124 | if pressure is None: 125 | return 101.3 * ((293 - 0.0065 * elevation) / 293) ** 5.26 126 | else: 127 | return pressure 128 | 129 | 130 | def calc_rho(pressure, tmean, ea): 131 | """Atmospheric air density calculated according to :cite:t:`allen_crop_1998`. 132 | 133 | Parameters 134 | ---------- 135 | pressure: array_like 136 | atmospheric pressure [kPa]. 137 | tmean: array_like 138 | average day temperature [°C]. 139 | ea: array_like 140 | actual vapour pressure [kPa]. 141 | 142 | Returns 143 | ------- 144 | float or pandas.Series or xarray.DataArray containing the calculated mean air 145 | density [kg/m3] 146 | 147 | Examples 148 | -------- 149 | >>> rho = calc_rho(pressure, tmean, ea) 150 | 151 | Notes 152 | ----- 153 | Based on equation (3-5) in :cite:t:`allen_crop_1998`. 154 | 155 | .. math:: rho = 3.486 \\frac{P}{T_{KV}} 156 | 157 | """ 158 | # Virtual temperature [tkv] 159 | tkv = (273.16 + tmean) * (1 - 0.378 * ea / pressure) ** -1 160 | return 3.486 * pressure / tkv 161 | 162 | 163 | def calc_e0(tmean): 164 | """Saturation vapor pressure at the air temperature T [kPa]. 165 | 166 | Parameters 167 | ---------- 168 | tmean: array_like 169 | average day temperature [°C]. 170 | 171 | Returns 172 | ------- 173 | array_like containing the calculated saturation vapor pressure at the air 174 | temperature tmean [kPa]. 175 | 176 | Examples 177 | -------- 178 | >>> e0 = calc_e0(tmean) 179 | 180 | Notes 181 | ----- 182 | Based on equation 11 in :cite:t:`allen_crop_1998`. 183 | 184 | """ 185 | return 0.6108 * exp(17.27 * tmean / (tmean + 237.3)) 186 | 187 | 188 | def calc_es(tmean=None, tmax=None, tmin=None): 189 | """Saturation vapor pressure [kPa]. 190 | 191 | Parameters 192 | ---------- 193 | tmean: array_like, optional 194 | average day temperature [°C]. 195 | tmax: array_like, optional 196 | maximum day temperature [°C]. 197 | tmin: array_like, optional 198 | minimum day temperature [°C]. 199 | 200 | Returns 201 | ------- 202 | float or pandas.Series or xarray.DataArray containing the calculated saturation 203 | vapor pressure [kPa]. 204 | 205 | Examples 206 | -------- 207 | >>> es = calc_es(tmean) 208 | 209 | Notes 210 | ----- 211 | Based on equation 11, 12 in :cite:t:`allen_crop_1998`. 212 | 213 | """ 214 | if tmax is not None: 215 | eamax = calc_e0(tmax) 216 | eamin = calc_e0(tmin) 217 | return (eamax + eamin) / 2 218 | else: 219 | return calc_e0(tmean) 220 | 221 | 222 | def calc_ea(tmean=None, tmax=None, tmin=None, rhmax=None, rhmin=None, rh=None, ea=None): 223 | """Actual vapor pressure [kPa]. 224 | 225 | Parameters 226 | ---------- 227 | tmean: array_like, optional 228 | average day temperature [°C]. 229 | tmax: array_like, optional 230 | maximum day temperature [°C]. 231 | tmin: array_like, optional 232 | minimum day temperature [°C]. 233 | rhmax: array_like, optional 234 | maximum daily relative humidity [%]. 235 | rhmin: array_like, optional 236 | mainimum daily relative humidity [%]. 237 | rh: array_like, optional 238 | mean daily relative humidity [%]. 239 | ea: array_like, optional 240 | actual vapor pressure [kPa]. 241 | 242 | Returns 243 | ------- 244 | float or pandas.Series or xarray.DataArray containing the calculated actual vapor 245 | pressure [kPa]. 246 | 247 | Examples 248 | -------- 249 | >>> ea = calc_ea(tmean, rh) 250 | 251 | Notes 252 | ----- 253 | Based on equation 17, 19 in :cite:t:`allen_crop_1998`. 254 | 255 | """ 256 | if ea is not None: 257 | return ea 258 | if rhmax is not None: # eq. 11 259 | esmax = calc_e0(tmax) 260 | esmin = calc_e0(tmin) 261 | return (esmin * rhmax / 200) + (esmax * rhmin / 200) 262 | else: # eq. 14 263 | if tmax is not None: 264 | es = calc_es(tmax=tmax, tmin=tmin) 265 | else: 266 | es = calc_e0(tmean) 267 | return rh / 100 * es 268 | 269 | 270 | def day_of_year(tindex): 271 | """Day of the year (1-365) based on pandas.Index. 272 | 273 | Parameters 274 | ---------- 275 | tindex: pandas.DatetimeIndex 276 | 277 | Returns 278 | ------- 279 | array_like with ints specifying day of year. 280 | 281 | """ 282 | return Series(to_numeric(tindex.strftime("%j")), tindex, dtype=int) 283 | 284 | 285 | def solar_declination(j): 286 | """Solar declination from day of year [rad]. 287 | 288 | Parameters 289 | ---------- 290 | j: array_like 291 | day of the year (1-365). 292 | 293 | Returns 294 | ------- 295 | array_like of solar declination [rad]. 296 | 297 | Notes 298 | ------- 299 | Based on equations 24 in :cite:t:`allen_crop_1998`. 300 | 301 | """ 302 | return 0.409 * sin(2.0 * pi / 365.0 * j - 1.39) 303 | 304 | 305 | def sunset_angle(sol_dec, lat): 306 | """Sunset hour angle from latitude and solar declination - daily [rad]. 307 | 308 | Parameters 309 | ---------- 310 | sol_dec: array_like 311 | solar declination [rad]. 312 | lat: array_like 313 | the site latitude [rad]. 314 | 315 | Returns 316 | ------- 317 | array_like containing the calculated sunset hour angle - daily [rad]. 318 | 319 | Notes 320 | ----- 321 | Based on equations 25 in :cite:t:`allen_crop_1998`. 322 | 323 | """ 324 | if isinstance(lat, DataArray): 325 | lat = lat.expand_dims(dim={"time": sol_dec.index}, axis=0) 326 | return arccos(clip(-tan(sol_dec.values) * tan(lat).T, -1, 1)).T 327 | else: 328 | return arccos(clip(-tan(sol_dec) * tan(lat), -1, 1)) 329 | 330 | 331 | def daylight_hours(tindex, lat): 332 | """Daylight hours [hour]. 333 | 334 | Parameters 335 | ---------- 336 | tindex: pandas.DatetimeIndex 337 | lat: array_like 338 | the site latitude [rad]. 339 | 340 | Returns 341 | ------- 342 | pandas.Series or xarray.DataArray containing the calculated daylight hours [hour]. 343 | 344 | Notes 345 | ----- 346 | Based on equation 34 in :cite:t:`allen_crop_1998`. 347 | 348 | """ 349 | j = day_of_year(tindex) 350 | sol_dec = solar_declination(j) 351 | sangle = sunset_angle(sol_dec, lat) 352 | # Account for subpolar belt which returns NaN values 353 | dl = 24 / pi * sangle 354 | if isinstance(lat, DataArray): 355 | sol_dec = ((dl / dl).T * sol_dec.values).T 356 | dl = where((sol_dec > 0) & (isnan(dl)), nanmax(dl), dl) 357 | dl = where((sol_dec < 0) & (isnan(dl)), 0, dl) 358 | return dl 359 | 360 | 361 | def relative_distance(j): 362 | """Inverse relative distance between earth and sun from day of the year. 363 | 364 | Parameters 365 | ---------- 366 | j: array_like 367 | day of the year (1-365). 368 | 369 | Returns 370 | ------- 371 | pandas.Series specifying relative distance between earth and sun. 372 | 373 | Notes 374 | ------- 375 | Based on equations 23 in :cite:t:`allen_crop_1998`. 376 | 377 | """ 378 | return 1 + 0.033 * cos(2.0 * pi / 365.0 * j) 379 | 380 | 381 | def extraterrestrial_r(tindex, lat): 382 | """ 383 | Extraterrestrial daily radiation [MJ m-2 d-1]. 384 | 385 | Parameters 386 | ---------- 387 | tindex: pandas.DatetimeIndex 388 | lat: array_like 389 | the site latitude [rad]. 390 | 391 | Returns 392 | ------- 393 | array_like containing the calculated extraterrestrial radiation [MJ m-2 d-1] 394 | 395 | Notes 396 | ----- 397 | Based on equation 21 in :cite:t:`allen_crop_1998`. 398 | 399 | """ 400 | j = day_of_year(tindex) 401 | dr = relative_distance(j) 402 | sol_dec = solar_declination(j) 403 | 404 | omega = sunset_angle(sol_dec, lat) 405 | if isinstance(lat, DataArray): 406 | lat = lat.expand_dims(dim={"time": sol_dec.index}, axis=0) 407 | xx = sin(sol_dec.values) * sin(lat.T) 408 | yy = cos(sol_dec.values) * cos(lat.T) 409 | return (118.08 / 3.141592654 * dr.values * (omega.T * xx + yy * sin(omega.T))).T 410 | else: 411 | xx = sin(sol_dec) * sin(lat) 412 | yy = cos(sol_dec) * cos(lat) 413 | return 118.08 / 3.141592654 * dr * (omega * xx + yy * sin(omega)) 414 | 415 | 416 | def calc_res_surf( 417 | lai=None, r_s=None, srs=0.002, co2=300, r_l=100, lai_eff=0, croph=0.12 418 | ): 419 | """Surface resistance [s m-1]. 420 | 421 | Parameters 422 | ---------- 423 | lai: float or pandas.Series or xarray.DataArray, optional 424 | leaf area index [-]. 425 | r_s: float or pandas.Series or xarray.DataArray, optional 426 | surface resistance [s m-1]. 427 | r_l: float or pandas.Series or xarray.DataArray, optional 428 | bulk stomatal resistance [s m-1]. 429 | lai_eff: float, optional 430 | 1 => LAI_eff = 0.5 * LAI 431 | 2 => LAI_eff = lai / (0.3 * lai + 1.2) 432 | 3 => LAI_eff = 0.5 * LAI; (LAI>4=4) 433 | 4 => see :cite:t:`zhang_comparison_2008`. 434 | srs: float or pandas.Series or xarray.DataArray, optional 435 | Relative sensitivity of rl to Δ[CO2] :cite:t:`yang_hydrologic_2019`. 436 | co2: float or pandas.Series or xarray.DataArray 437 | CO2 concentration [ppm]. 438 | croph: float or pandas.Series or xarray.DataArray, optional crop height [m]. 439 | 440 | Returns 441 | ------- 442 | float or pandas.Series or xarray.DataArray containing the calculated surface 443 | resistance [s / m] 444 | 445 | """ 446 | if r_s: 447 | return r_s 448 | else: 449 | fco2 = 1 + srs * (co2 - 300) 450 | if lai is None: 451 | return fco2 * r_l / (0.5 * croph * 24) # after FAO-56 452 | else: 453 | return fco2 * r_l / calc_laieff(lai=lai, lai_eff=lai_eff) 454 | 455 | 456 | def calc_laieff(lai=None, lai_eff=0): 457 | """Effective leaf area index [-]. 458 | 459 | Parameters 460 | ---------- 461 | lai: pandas.Series/float, optional 462 | leaf area index [-]. 463 | lai_eff: float, optional 464 | 0 => LAI_eff = 0.5 * LAI 465 | 1 => LAI_eff = lai / (0.3 * lai + 1.2) 466 | 2 => LAI_eff = 0.5 * LAI; (LAI>4=4) 467 | 3 => see :cite:t:`zhang_comparison_2008`. 468 | 469 | Returns 470 | ------- 471 | pandas.Series containing the calculated effective leaf area index. 472 | 473 | """ 474 | if lai_eff == 0: 475 | return 0.5 * lai 476 | if lai_eff == 1: 477 | return lai / (0.3 * lai + 1.2) 478 | if lai_eff == 2: 479 | laie = lai.copy() 480 | laie[(lai > 2) & (lai < 4)] = 2 481 | laie[lai > 4] = 0.5 * lai 482 | return laie 483 | if lai_eff == 3: 484 | laie = lai.copy() 485 | laie[lai > 4] = 4 486 | return laie * 0.5 487 | 488 | 489 | def calc_res_aero(wind, croph=0.12, zw=2, zh=2, ra_method=0): 490 | """Aerodynamic resistance [s m-1]. 491 | 492 | Parameters 493 | ---------- 494 | wind: float or pandas.Series or xarray.DataArray 495 | mean day wind speed [m/s]. 496 | croph: float or pandas.Series or xarray.DataArray, optional 497 | crop height [m]. 498 | zw: float, optional 499 | height of wind measurement [m]. 500 | zh: float, optional 501 | height of humidity and or air temperature measurement [m]. 502 | ra_method: float, optional 503 | 0 => ra = 208/wind 504 | 1 => ra is calculated based on equation 36 in FAO (1990), ANNEX V. 505 | 506 | Returns 507 | ------- 508 | pandas.Series containing the calculated aerodynamic resistance. 509 | 510 | """ 511 | if ra_method == 0: 512 | wind = wind.where(wind != 0, 0.0001) 513 | return 208 / wind 514 | else: 515 | d = 0.667 * croph 516 | zom = 0.123 * croph 517 | zoh = 0.0123 * croph 518 | return (log((zw - d) / zom)) * (log((zh - d) / zoh) / (0.41**2) / wind) 519 | -------------------------------------------------------------------------------- /pyet/rad_utils.py: -------------------------------------------------------------------------------- 1 | """The rad_utils module contains utility functions for radiation data. 2 | 3 | """ 4 | 5 | from numpy import sqrt, clip, newaxis 6 | 7 | from pandas import Series 8 | 9 | from xarray import DataArray 10 | 11 | from .meteo_utils import calc_ea, extraterrestrial_r, daylight_hours 12 | 13 | from .utils import get_index, check_rad, vectorize 14 | 15 | # Stefan Boltzmann constant - hourly [MJm-2K-4h-1] 16 | STEFAN_BOLTZMANN_HOUR = 2.042 * 10**-10 17 | # Stefan Boltzmann constant - daily [MJm-2K-4d-1] 18 | STEFAN_BOLTZMANN_DAY = 4.903 * 10**-9 19 | 20 | 21 | def calc_rad_net( 22 | tmean, 23 | rn=None, 24 | rs=None, 25 | lat=None, 26 | n=None, 27 | nn=None, 28 | tmax=None, 29 | tmin=None, 30 | rhmax=None, 31 | rhmin=None, 32 | rh=None, 33 | elevation=None, 34 | rso=None, 35 | a=1.35, 36 | b=-0.35, 37 | ea=None, 38 | albedo=0.23, 39 | as1=0.25, 40 | bs1=0.5, 41 | kab=None, 42 | ): 43 | """Net radiation [MJ m-2 d-1]. 44 | 45 | Parameters 46 | ---------- 47 | tmean: pandas.Series/xarray.DataArray 48 | average day temperature [°C]. 49 | rn: float or pandas.Series or xarray.DataArray, optional 50 | net radiation [MJ m-2 d-1]. 51 | rs: float or pandas.Series or xarray.DataArray, optional 52 | incoming solar radiation [MJ m-2 d-1]. 53 | lat: float/xarray.DataArray, optional 54 | the site latitude [rad]. 55 | n: float or pandas.Series or xarray.DataArray, optional 56 | actual duration of sunshine [hour]. 57 | nn: float or pandas.Series or xarray.DataArray, optional 58 | maximum possible duration of sunshine or daylight hours [hour]. 59 | tmax: float or pandas.Series or xarray.DataArray, optional 60 | maximum day temperature [°C]. 61 | tmin: float or pandas.Series or xarray.DataArray, optional 62 | minimum day temperature [°C]. 63 | rhmax: float or pandas.Series or xarray.DataArray, optional 64 | maximum daily relative humidity [%]. 65 | rhmin: float or pandas.Series or xarray.DataArray, optional 66 | mainimum daily relative humidity [%]. 67 | rh: float or pandas.Series or xarray.DataArray, optional 68 | mean daily relative humidity [%]. 69 | elevation: float/xarray.DataArray, optional 70 | the site elevation [m]. 71 | rso: float or pandas.Series or xarray.DataArray, optional 72 | clear-sky solar radiation [MJ m-2 day-1]. 73 | a: float, optional 74 | empirical coefficient for Net Long-Wave radiation [-]. 75 | b: float, optional 76 | empirical coefficient for Net Long-Wave radiation [-]. 77 | ea: float or pandas.Series or xarray.DataArray, optional 78 | actual vapor pressure [kPa]. 79 | albedo: float, optional 80 | surface albedo [-] 81 | as1: float, optional 82 | regression constant, expressing the fraction of extraterrestrial reaching the 83 | earth on overcast days (n = 0) [-] 84 | bs1: float, optional 85 | empirical coefficient for extraterrestrial radiation [-] 86 | kab: float, optional 87 | coefficient derived from as1, bs1 for estimating clear-sky radiation [degrees]. 88 | 89 | Returns 90 | ------- 91 | float or pandas.Series or xarray.DataArray, optional containing the calculated net 92 | shortwave radiation. 93 | 94 | Notes 95 | ----- 96 | Based on equation 40 in :cite:t:`allen_crop_1998`. 97 | 98 | """ 99 | if rn is not None: 100 | rn = check_rad(rn) 101 | return rn 102 | else: 103 | if rs is None: 104 | rs = calc_rad_sol_in(n, lat, as1=as1, bs1=bs1, nn=nn) 105 | rns = calc_rad_short( 106 | rs=rs, lat=lat, n=n, nn=nn, albedo=albedo, as1=as1, bs1=bs1 107 | ) # [MJ/m2/d] 108 | rnl = calc_rad_long( 109 | rs=rs, 110 | tmean=tmean, 111 | tmax=tmax, 112 | tmin=tmin, 113 | rhmax=rhmax, 114 | rhmin=rhmin, 115 | rh=rh, 116 | elevation=elevation, 117 | lat=lat, 118 | rso=rso, 119 | a=a, 120 | b=b, 121 | ea=ea, 122 | kab=kab, 123 | ) # [MJ/m2/d] 124 | rn = rns - rnl 125 | rn = check_rad(rn) 126 | return rn 127 | 128 | 129 | def calc_rad_long( 130 | rs, 131 | tmean=None, 132 | tmax=None, 133 | tmin=None, 134 | rhmax=None, 135 | rhmin=None, 136 | rh=None, 137 | elevation=None, 138 | lat=None, 139 | rso=None, 140 | a=1.35, 141 | b=-0.35, 142 | ea=None, 143 | kab=None, 144 | ): 145 | """Net longwave radiation [MJ m-2 d-1]. 146 | 147 | Parameters 148 | ---------- 149 | rs: float or pandas.Series or xarray.DataArray, optional 150 | incoming solar radiation [MJ m-2 d-1]. 151 | tmean: float or pandas.Series or xarray.DataArray, optional 152 | average day temperature [°C]. 153 | tmax: float or pandas.Series or xarray.DataArray, optional 154 | maximum day temperature [°C]. 155 | tmin: float or pandas.Series or xarray.DataArray, optional 156 | minimum day temperature [°C]. 157 | rhmax: float or pandas.Series or xarray.DataArray, optional 158 | maximum daily relative humidity [%]. 159 | rhmin: float or pandas.Series or xarray.DataArray, optional 160 | mainimum daily relative humidity [%]. 161 | rh: float or pandas.Series or xarray.DataArray, optional 162 | mean daily relative humidity [%]. 163 | elevation: float/xarray.DataArray, optional 164 | the site elevation [m]. 165 | lat: float/xarray.DataArray, optional 166 | the site latitude [rad]. 167 | rso: float or pandas.Series or xarray.DataArray, optional 168 | clear-sky solar radiation [MJ m-2 day-1]. 169 | a: float, optional 170 | empirical coefficient for Net Long-Wave radiation [-]. 171 | b: float, optional 172 | empirical coefficient for Net Long-Wave radiation [-]. 173 | ea: float or pandas.Series or xarray.DataArray, optional 174 | actual vapor pressure [kPa]. 175 | kab: float, optional 176 | coefficient that can be derived from the as and bs coefficients of the 177 | Angstrom formula, where Kab = as + bs, and where Kab represents the 178 | fraction of extraterrestrial radiation reaching the earth on clear-sky 179 | days [-]. 180 | 181 | Returns 182 | ------- 183 | float or pandas.Series or xarray.DataArray, optional containing the calculated net 184 | longwave radiation. 185 | 186 | Notes 187 | ----- 188 | Based on equation 39 in :cite:t:`allen_crop_1998`. 189 | 190 | """ 191 | if ea is None: 192 | ea = calc_ea(tmean=tmean, tmax=tmax, tmin=tmin, rhmax=rhmax, rhmin=rhmin, rh=rh) 193 | 194 | if rso is None: 195 | tindex = get_index(rs) 196 | ra = extraterrestrial_r(tindex, lat) 197 | rso = calc_rso(ra=ra, elevation=elevation, kab=kab) 198 | # Add a small constant to rso where it is zero to avoid division with zero 199 | rso = rso.where(rso != 0, 0.001) 200 | if len(rs.shape) == 3 and len(rso.shape) == 1: 201 | rso = rso.values[:, newaxis, newaxis] 202 | solar_rat = clip(rs / rso, 0.3, 1) 203 | if tmax is not None: 204 | tmp1 = STEFAN_BOLTZMANN_DAY * ((tmax + 273.16) ** 4 + (tmin + 273.16) ** 4) / 2 205 | else: 206 | tmp1 = STEFAN_BOLTZMANN_DAY * (tmean + 273.16) ** 4 207 | tmp2 = 0.34 - 0.14 * sqrt(ea) # OK 208 | tmp3 = a * solar_rat + b # OK 209 | tmp3 = clip(tmp3, 0.05, 1) 210 | rnl = tmp1 * tmp2 * tmp3 211 | return rnl 212 | 213 | 214 | def calc_rad_short(rs=None, lat=None, albedo=0.23, n=None, nn=None, as1=0.25, bs1=0.5): 215 | """Net shortwave radiation [MJ m-2 d-1]. 216 | 217 | Parameters 218 | ---------- 219 | rs: float or pandas.Series or xarray.DataArray, optional 220 | incoming solar radiation [MJ m-2 d-1]. 221 | lat: float, optional 222 | the site latitude [rad]. 223 | albedo: float or pandas.Series or xarray.DataArray, optional 224 | surface albedo [-]. 225 | n: pandas.Series/xarray.DataArray, optional 226 | actual duration of sunshine [hour]. 227 | as1: float, optional 228 | regression constant, expressing the fraction of extraterrestrial reaching the 229 | earth on overcast days (n = 0) [-]. 230 | bs1: float, optional 231 | empirical coefficient for extraterrestrial radiation [-]. 232 | nn: float or pandas.Series or xarray.DataArray, optional 233 | maximum possible duration of sunshine or daylight hours [hour]. 234 | 235 | Returns 236 | ------- 237 | float or pandas.Series or xarray.DataArray, optional containing the calculated 238 | net shortwave radiation. 239 | 240 | Notes 241 | ----- 242 | Based on equation 38 in :cite:t:`allen_crop_1998`. 243 | 244 | """ 245 | (vrs,) = vectorize(rs) 246 | if vrs is not None: 247 | return (1 - albedo) * vrs 248 | else: 249 | return (1 - albedo) * calc_rad_sol_in(n, lat, as1=as1, bs1=bs1, nn=nn) 250 | 251 | 252 | def calc_rad_sol_in(n, lat, as1=0.25, bs1=0.5, nn=None): 253 | """Incoming solar radiation [MJ m-2 d-1]. 254 | 255 | Parameters 256 | ---------- 257 | n: pandas.Series or xarray.DataArray 258 | actual duration of sunshine [hour]. 259 | lat: float, optional 260 | the site latitude [rad]. 261 | as1: float, optional 262 | regression constant, expressing the fraction of extraterrestrial reaching the 263 | earth on overcast days (n = 0) [-]. 264 | bs1: float, optional 265 | empirical coefficient for extraterrestrial radiation [-]. 266 | nn: pandas.Series/float, optional 267 | maximum possible duration of sunshine or daylight hours [hour]. 268 | 269 | Returns 270 | ------- 271 | pandas.Series containing the calculated net shortwave radiation. 272 | 273 | Notes 274 | ----- 275 | Based on equation 35 in :cite:t:`allen_crop_1998`. 276 | 277 | """ 278 | tindex = get_index(n) 279 | ra = extraterrestrial_r(tindex, lat) 280 | if nn is None: 281 | nn = daylight_hours(tindex, lat) 282 | return (as1 + bs1 * n / nn) * ra 283 | 284 | 285 | def calc_rso(ra, elevation, kab=None): 286 | """Clear-sky solar radiation [MJ m-2 day-1]. 287 | 288 | Parameters 289 | ---------- 290 | ra: pandas.Series/xarray.DataArray, optional 291 | Extraterrestrial daily radiation [MJ m-2 d-1]. 292 | elevation: float/xarray.DataArray, optional 293 | the site elevation [m]. 294 | kab: float, optional 295 | coefficient that can be derived from the as and bs coefficients of the 296 | Angstrom formula, where Kab = as + bs, and where Kab represents the 297 | fraction of extraterrestrial radiation reaching the earth on clear-sky 298 | days [-]. 299 | 300 | Returns 301 | ------- 302 | pandas.Series/xarray.DataArray, optional containing the calculated Clear-sky solar 303 | radiation. 304 | 305 | Notes 306 | ----- 307 | Based on equation 37 in :cite:t:`allen_crop_1998`. 308 | 309 | """ 310 | if isinstance(elevation, DataArray): 311 | tindex = get_index(ra) 312 | elevation = elevation.expand_dims(dim={"time": tindex}, axis=0) 313 | if isinstance(ra, Series): 314 | ra = ra.values[:, newaxis, newaxis] 315 | if kab is None: 316 | return (0.75 + (2 * 10**-5) * elevation) * ra 317 | else: 318 | return kab * ra 319 | -------------------------------------------------------------------------------- /pyet/radiation.py: -------------------------------------------------------------------------------- 1 | """The radiation module contains functions of radiation PET methods 2 | 3 | """ 4 | 5 | from numpy import sqrt, log 6 | from xarray import DataArray 7 | from pandas import Series 8 | from .meteo_utils import extraterrestrial_r, calc_press, calc_psy, calc_vpc, calc_lambda 9 | from .utils import get_index, check_rad, clip_zeros, pet_out, check_rh 10 | 11 | 12 | def turc(tmean, rs, rh, k=0.013, clip_zero=True): 13 | """Potential evapotranspiration calculated according to 14 | :cite:t:`turc_estimation_1961`. 15 | 16 | Parameters 17 | ---------- 18 | tmean: pandas.Series or xarray.DataArray 19 | average day temperature [°C]. 20 | rs: pandas.Series or xarray.DataArray 21 | incoming solar radiation [MJ m-2 d-1]. 22 | rh: pandas.Series or xarray.DataArray 23 | mean daily relative humidity [%]. 24 | k: float, optional 25 | calibration coefficient [-]. 26 | clip_zero: bool, optional 27 | if True, replace all negative values with 0. 28 | 29 | Returns 30 | ------- 31 | pandas.Series or xarray.DataArray containing the calculated 32 | Potential evapotranspiration [mm d-1]. 33 | 34 | Examples 35 | -------- 36 | >>> pet_turc = turc(tmean, rs, rh) 37 | 38 | Notes 39 | ----- 40 | Based on equation S9.10 and S9.11 in :cite:t:`mcmahon_estimating_2013`. 41 | 42 | .. math:: PET=k(\\frac{T_{mean}}{T_{mean}+15})(23.88R_s+50)0.013; 43 | for RH>50 44 | 45 | .. math:: PET=k(1+\\frac{50-RH}{70})(\\frac{T_{mean}}{T_{mean}+15}) 46 | (23.88R_s+50)0.013; for RH<50 47 | 48 | """ 49 | c = tmean / tmean 50 | c = c.where(check_rh(rh) >= 50, 1 + (50 - rh) / 70) 51 | pet = k * c * tmean / (tmean + 15) * (check_rad(rs) * 23.88 + 50) 52 | pet = clip_zeros(pet, clip_zero) 53 | return pet_out(tmean, pet, "Turc") 54 | 55 | 56 | def jensen_haise(tmean, rs=None, cr=0.025, tx=-3, lat=None, method=0, clip_zero=True): 57 | """Potential evapotranspiration calculated accordinf to 58 | :cite:t:`jensen_estimating_1963`. 59 | 60 | Parameters 61 | ---------- 62 | tmean: pandas.Series orxarray.DataArray 63 | average day temperature [°C]. 64 | rs: pandas.Series or xarray.DataArray, optional 65 | incoming solar radiation [MJ m-2 d-1]. 66 | cr: float, optional 67 | temperature coefficient [-]. 68 | tx: float, optional 69 | intercept of the temperature axis [°C]. 70 | lat: float/xarray.DataArray 71 | the site latitude [rad]. 72 | method: float, optional 73 | 0 => after :cite:t:`jensen_evaporation_2016` 74 | 1 => after :cite:t:`oudin_which_2005`. 75 | clip_zero: bool, optional 76 | if True, replace all negative values with 0. 77 | 78 | Returns 79 | ------- 80 | pandas.Series or xarray.DataArray containing the calculated potential 81 | evapotranspiration [mm d-1]. 82 | 83 | Examples 84 | -------- 85 | >>> pet_jh = jensen_haise(tmean, lat) 86 | 87 | Notes 88 | ----- 89 | Method = 0: Based on :cite:t:`jensen_evaporation_2016`. 90 | 91 | .. math:: PET = \\frac{C_r(T_{mean}-T_x)R_s}{\\lambda} 92 | 93 | Method = 1: Based on :cite:t:`oudin_which_2005`. 94 | 95 | .. math:: PET = \\frac{R_a(T_{mean}+5)}{68\\lambda} 96 | 97 | """ 98 | lambd = calc_lambda(tmean) 99 | if method == 0: 100 | if rs is None: 101 | raise Exception("If you choose method == 0, provide rs!") 102 | pet = check_rad(rs) / lambd * cr * (tmean - tx) 103 | elif method == 1: 104 | if lat is None: 105 | raise Exception("If you choose method == 1, provide lat!") 106 | index = get_index(tmean) 107 | ra = extraterrestrial_r(index, lat, tmean) 108 | pet = ra * (tmean + 5) / 68 / lambd 109 | else: 110 | raise Exception("Method can be either 0 or 1.") 111 | pet = clip_zeros(pet, clip_zero) 112 | return pet_out(tmean, pet, "Jensen_Haise") 113 | 114 | 115 | def mcguinness_bordne(tmean, lat, k=0.0147, clip_zero=True): 116 | """Potential evapotranspiration calculated according to 117 | :cite:t:`mcguinness_comparison_1972`. 118 | 119 | Parameters 120 | ---------- 121 | tmean: pandas.Series or xarray.DataArray 122 | average day temperature [°C]. 123 | lat: float/xarray.DataArray, optional 124 | the site latitude [rad]. 125 | k: float, optional 126 | calibration coefficient [-]. 127 | clip_zero: bool, optional 128 | if True, replace all negative values with 0. 129 | 130 | Returns 131 | ------- 132 | pandas.Series or xarray.DataArray containing the calculated potential 133 | evapotranspiration [mm d-1]. 134 | 135 | Examples 136 | -------- 137 | >>> pet_mcguinness_bordne = mcguinness_bordne(tmean, lat) 138 | 139 | Notes 140 | ----- 141 | Based on equation 13 in :cite:t:`xu_evaluation_2000`. 142 | 143 | .. math:: PET = k\\frac{R_a (T_{mean} + 5)}{\\lambda} 144 | 145 | """ 146 | lambd = calc_lambda(tmean) 147 | index = get_index(tmean) 148 | ra = extraterrestrial_r(index, lat) 149 | if isinstance(tmean, DataArray) and isinstance(ra, Series): 150 | ra = ra.values[:, None, None] 151 | pet = k * ra * (tmean + 5) / lambd 152 | pet = clip_zeros(pet, clip_zero) 153 | return pet_out(tmean, pet, "Mcguinness_Bordne") 154 | 155 | 156 | def hargreaves(tmean, tmax, tmin, lat, k=0.0135, method=0, clip_zero=True): 157 | """Potential evapotranspiration calculated according to 158 | :cite:t:`hargreaves_estimating_1982`. 159 | 160 | Parameters 161 | ---------- 162 | tmean: pandas.Series or xarray.DataArray 163 | average day temperature [°C]. 164 | tmax: pandas.Series or xarray.DataArray 165 | maximum day temperature [°C]. 166 | tmin: pandas.Series or xarray.DataArray 167 | minimum day temperature [°C]. 168 | lat: float/xarray.DataArray 169 | the site latitude [rad]. 170 | k: float, optional 171 | calirbation coefficient [-]. 172 | method: float, optional 173 | 0 => after :cite:t:`jensen_evaporation_2016` 174 | 1 => after :cite:t:`mcmahon_estimating_2013`. 175 | clip_zero: bool, optional 176 | if True, replace all negative values with 0. 177 | 178 | Returns 179 | ------- 180 | pandas.Series or xarray.DataArray containing the calculated potential 181 | evapotranspiration [mm d-1]. 182 | 183 | Examples 184 | -------- 185 | >>> pet_har = hargreaves(tmean, tmax, tmin, lat) 186 | 187 | Notes 188 | ----- 189 | Method = 0; Based on equation (8-16) in :cite:t:`jensen_evaporation_2016`. 190 | 191 | .. math:: PET = k \\frac{R_a (T_{mean}+17.8)\\sqrt{(T_{max}-T_{min})}}\ 192 | {\\lambda} 193 | 194 | Method = 1; Based on :cite:t:`mcmahon_estimating_2013`. 195 | 196 | .. math:: PET = chs k \\frac{R_a (T_{mean}+17.8)\\sqrt{(T_{max}-T_{min})}}\ 197 | {\\lambda} 198 | 199 | , where 200 | 201 | .. math:: chs=0.00185*(T_{max}-T_{min})^2-0.0433*(T_{max}-T_{min})+0.4023 202 | 203 | """ 204 | lambd = calc_lambda(tmean) 205 | index = get_index(tmean) 206 | ra = extraterrestrial_r(index, lat) 207 | if isinstance(tmean, DataArray) and isinstance(ra, Series): 208 | ra = ra.values[:, None, None] 209 | else: 210 | ra = ra.values 211 | chs = 0.00185 * (tmax - tmin) ** 2 - 0.0433 * (tmax - tmin) + 0.4023 212 | if method == 0: 213 | pet = k / 0.0135 * 0.0023 * (tmean + 17.8) * sqrt(tmax - tmin) * ra / lambd 214 | elif method == 1: 215 | pet = k * chs * sqrt(tmax - tmin) * ra / lambd * (tmean + 17.8) 216 | else: 217 | raise Exception("Method can be either 0 or 1.") 218 | pet = clip_zeros(pet, clip_zero) 219 | return pet_out(tmean, pet, "Hargreaves") 220 | 221 | 222 | def fao_24( 223 | tmean, wind, rs, rh, pressure=None, elevation=None, albedo=0.23, clip_zero=True 224 | ): 225 | """Potential evapotranspiration calculated according to 226 | :cite:t:`jensen_evapotranspiration_1990`. 227 | 228 | Parameters 229 | ---------- 230 | tmean: pandas.Series or xarray.DataArray 231 | average day temperature [°C]. 232 | wind: pandas.Series xarray.DataArray 233 | mean day wind speed [m/s]. 234 | rs: pandas.Series xarray.DataArray 235 | incoming solar radiation [MJ m-2 d-1]. 236 | rh: pandas.Series or xarray.DataArray 237 | mean daily relative humidity [%]. 238 | pressure: pandas.Series or xarray.DataArray, optional 239 | atmospheric pressure [kPa]. 240 | elevation: float/xarray.DataArray, optional 241 | the site elevation [m]. 242 | albedo: float/xarray.DataArray, optional 243 | surface albedo [-]. 244 | clip_zero: bool, optional 245 | if True, replace all negative values with 0. 246 | 247 | Returns 248 | ------- 249 | pandas.Series or xarray.DataArray containing the calculated potential 250 | evapotranspiration [mm d-1]. 251 | 252 | Examples 253 | -------- 254 | >>> pet_fao24 = fao_24(tmean, wind, rs, rh, elevation=elevation) 255 | 256 | .. math:: PE = \\frac{- 0.3 \\Delta + R_s (1-\\alpha) w}\ 257 | {\\lambda(\\Delta +\\gamma)} 258 | .. math:: w = 1.066-0.13*\\frac{rh}{100}+0.045*u_2-0.02*\\frac{rh}{100}\ 259 | *u_2-3.15*(\\frac{rh}{100})^2-0.0011*u_2 260 | 261 | """ 262 | pressure = calc_press(elevation, pressure) 263 | gamma = calc_psy(pressure) 264 | dlt = calc_vpc(tmean) 265 | lambd = calc_lambda(tmean) 266 | 267 | w = ( 268 | 1.066 269 | - 0.13 * check_rh(rh) / 100 270 | + 0.045 * wind 271 | - 0.02 * rh / 100 * wind 272 | - 0.315 * (rh / 100) ** 2 273 | - 0.0011 * wind 274 | ) 275 | pet = -0.3 + dlt / (dlt + gamma) * check_rad(rs) * (1 - albedo) * w / lambd 276 | pet = clip_zeros(pet, clip_zero) 277 | return pet_out(tmean, pet, "FAO_24") 278 | 279 | 280 | def abtew(tmean, rs, k=0.53, clip_zero=True): 281 | """Potential evapotranspiration calculated according to 282 | :cite:t:`abtew_evapotranspiration_1996`. 283 | 284 | Parameters 285 | ---------- 286 | tmean: pandas.Series or xarray.DataArray 287 | average day temperature [°C]. 288 | rs: pandas.Series or xarray.DataArray 289 | incoming solar radiation [MJ m-2 d-1]. 290 | k: float, optional 291 | calibration coefficient [-]. 292 | clip_zero: bool, optional 293 | if True, replace all negative values with 0. 294 | 295 | Returns 296 | ------- 297 | pandas.Series or xarray.DataArray containing the calculated potential 298 | evapotranspiration [mm d-1]. 299 | 300 | Examples 301 | -------- 302 | >>> pet_abtew = abtew(tmean, rs) 303 | 304 | Notes 305 | ----- 306 | Based on equation 14 in :cite:t:`xu_evaluation_2000`. 307 | 308 | .. math:: PE = \\frac{k R_s}{\\lambda} 309 | 310 | """ 311 | lambd = calc_lambda(tmean) 312 | pet = k * check_rad(rs) / lambd 313 | pet = clip_zeros(pet, clip_zero) 314 | return pet_out(tmean, pet, "Abtew") 315 | 316 | 317 | def makkink(tmean, rs, pressure=None, elevation=None, k=0.65, clip_zero=True): 318 | """Potential evapotranspiration calculated according to 319 | :cite:t:`makkink_testing_1957`. 320 | 321 | Parameters 322 | ---------- 323 | tmean: pandas.Series or xarray.DataArray 324 | average day temperature [°C]. 325 | rs: pandas.Series or xarray.DataArray 326 | incoming solar radiation [MJ m-2 d-1]. 327 | pressure: pandas.Series or xarray.DataArray, optional 328 | atmospheric pressure [kPa]. 329 | elevation: float/xarray.DataArray, optional 330 | the site elevation [m]. 331 | k: float, optional 332 | calirbation coefficient [-]. 333 | clip_zero: bool, optional 334 | if True, replace all negative values with 0. 335 | 336 | Returns 337 | ------- 338 | pandas.Series or xarray.DataArray containing the calculated potential 339 | evapotranspiration [mm d-1]. 340 | 341 | Examples 342 | -------- 343 | >>> pet_mak = makkink(tmean, rs, elevation=elevation) 344 | 345 | Notes 346 | ----- 347 | 348 | .. math:: PET = \\frac{0.65 \\Delta (R_s)}{\\lambda(\\Delta +\\gamma)} 349 | 350 | """ 351 | pressure = calc_press(elevation, pressure) 352 | gamma = calc_psy(pressure) 353 | dlt = calc_vpc(tmean) 354 | lambd = calc_lambda(tmean) 355 | pet = k * dlt / (dlt + gamma) * check_rad(rs) / lambd 356 | pet = clip_zeros(pet, clip_zero) 357 | return pet_out(tmean, pet, "Makkink") 358 | 359 | 360 | def makkink_knmi(tmean, rs, clip_zero=True): 361 | """Potential evapotranspiration calculated according to The Royal Netherlands 362 | Meteorological Institute (KNMI) 363 | 364 | Parameters 365 | ---------- 366 | tmean : pandas.Series or xarray.DataArray 367 | average day temperature [°C]. 368 | rs : pandas.Series or xarray.DataArray 369 | incoming solar radiation [MJ m-2 d-1]. 370 | clip_zero: bool, optional 371 | if True, replace all negative values with 0. 372 | 373 | Returns 374 | ------- 375 | pandas.Series or xarray.DataArray containing the calculated potential 376 | evapotranspiration [mm d-1]. 377 | 378 | Examples 379 | -------- 380 | >>> pet_mak = makkink_knmi(tmean, rs) 381 | 382 | Notes 383 | ---- 384 | This method is only applicable to the Netherlands (~sea level) due to some 385 | emperical values. Calculating the Makkink evaporation with the original 386 | formula is more suitable for general purposes. To obtain the same value as 387 | EV24 round the value up to one decimal. 388 | """ 389 | pet = ( 390 | 650 391 | * ( 392 | 1 393 | - (0.646 + 0.0006 * tmean) 394 | / ( 395 | 7.5 396 | * log(10) 397 | * 6.107 398 | * 10 ** (7.5 * (1 - 1 / (1 + tmean / 237.3))) 399 | / (237.3 * (1 + tmean / 237.3) * (1 + tmean / 237.3)) 400 | + 0.646 401 | + 0.0006 * tmean 402 | ) 403 | ) 404 | / (2501 - 2.38 * tmean) 405 | * rs 406 | ) 407 | pet = clip_zeros(pet, clip_zero) 408 | return pet_out(tmean, pet, "Makkink_KNMI") 409 | 410 | 411 | def oudin(tmean, lat, k1=100, k2=5, clip_zero=True): 412 | """Potential evapotranspiration calculated according to :cite:t:`oudin_which_2005`. 413 | 414 | Parameters 415 | ---------- 416 | tmean: pandas.Series or xarray.DataArray 417 | average day temperature [°C]. 418 | lat: float or xarray.DataArray 419 | the site latitude [rad]. 420 | k1: float, optional 421 | calibration coefficient [-]. 422 | k2: float, optional 423 | calibration coefficient [-]. 424 | clip_zero: bool, optional 425 | if True, replace all negative values with 0. 426 | 427 | Returns 428 | ------- 429 | pandas.Series or xarray.DataArray containing the calculated potential 430 | evapotranspiration [mm d-1]. 431 | clip_zero: bool, optional 432 | if True, replace all negative values with 0. 433 | 434 | Examples 435 | -------- 436 | >>> pet_oudin = oudin(tmean, lat) 437 | 438 | Notes 439 | ----- 440 | Based on equation 3 in :cite:t:`oudin_which_2005`. 441 | 442 | .. math:: PET = \\frac{R_a (T_{mean} +5)}{\\lambda 100}; if T_{mean}+5>0 443 | else: PET = 0 444 | 445 | """ 446 | lambd = calc_lambda(tmean) 447 | index = get_index(tmean) 448 | ra = extraterrestrial_r(index, lat) 449 | pet = ra * (tmean + k2) / lambd / k1 450 | pet = pet.where((tmean + k2) >= 0, 0) 451 | pet = clip_zeros(pet, clip_zero) 452 | return pet_out(tmean, pet, "Oudin") 453 | -------------------------------------------------------------------------------- /pyet/temperature.py: -------------------------------------------------------------------------------- 1 | """The temperature module contains functions of temperature PET methods. 2 | 3 | """ 4 | 5 | from numpy import exp, pi, asarray, newaxis 6 | from pandas import date_range 7 | from xarray import DataArray 8 | 9 | from .meteo_utils import daylight_hours, calc_ea, calc_es, calc_e0 10 | from .utils import get_index, check_lat, clip_zeros, check_rh, pet_out 11 | 12 | 13 | def blaney_criddle( 14 | tmean, 15 | lat, 16 | a=-1.55, 17 | b=0.96, 18 | k=0.65, 19 | wind=None, 20 | rhmin=None, 21 | n=None, 22 | nn=None, 23 | py=None, 24 | method=0, 25 | clip_zero=True, 26 | ): 27 | """Potential evapotranspiration calculated according to 28 | :cite:t:`blaney_determining_1952`. 29 | 30 | Parameters 31 | ---------- 32 | tmean: pandas.Series or xarray.DataArray 33 | average day temperature [°C]. 34 | lat: float or xarray.DataArray, optional 35 | the site latitude [rad]. 36 | a: float, optional 37 | calibration coefficient for method 0 [-]. 38 | b: float, optional 39 | calibration coefficient for method 0 [-]. 40 | k: float, optional 41 | calibration coefficient for method 1 [-]. 42 | wind: float or pandas.Series or xarray.DataArray, optional 43 | mean day wind speed [m/s]. 44 | rhmin: float or pandas.Series or xarray.DataArray, optional 45 | mainimum daily relative humidity [%]. 46 | n: float or pandas.Series or xarray.DataArray, optional 47 | actual duration of sunshine [hour]. 48 | nn: float or pandas.Series or xarray.DataArray, optional 49 | maximum possible duration of sunshine or daylight hours [hour]. 50 | py: float or pandas.Series or xarray.DataArray, optional 51 | percentage of actual day-light hours for the day compared to the 52 | number of day-light hour during the entire year [-]. 53 | method: float, optional 54 | 0 => Blaney Criddle after :cite:t:`schrodter_hinweise_1985` 55 | 1 => Blaney Criddle after :cite:t:`xu_evaluation_2001` 56 | 2 => FAO-24 Blaney Criddle after :cite:t:`mcmahon_estimating_2013`. 57 | clip_zero: bool, optional 58 | if True, replace all negative values with 0. 59 | 60 | Returns 61 | ------- 62 | float or pandas.Series or xarray.DataArray containing the calculated 63 | Potential evapotranspiration [mm d-1]. 64 | 65 | Examples 66 | -------- 67 | >>> pet_blaney_criddle = blaney_criddle(tmean, lat) 68 | 69 | Notes 70 | ----- 71 | Method = 0; Based on :cite:p:`schrodter_hinweise_1985`. 72 | 73 | .. math:: PET=a+b(py(0.46 * T_{mean} + 8.13)) 74 | 75 | Method = 1; Based on :cite:p:`xu_evaluation_2001`. 76 | 77 | .. math:: PET=kpy(0.46 * T_{mean} + 8.13) 78 | 79 | Method = 2; Based on :cite:p:`mcmahon_estimating_2013`. 80 | 81 | .. math:: PET=k_1+b_{var}(py(0.46*T_{mean} + 8.13)) 82 | 83 | , where: 84 | 85 | .. math:: k1 = (0.0043RH_{min}-\\frac{n}{N}-1.41) 86 | 87 | .. math:: bvar =e_0+e1 RH_{min}+e_2 \\frac{n}{N} + e_3 u_2 + 88 | e_4 RH_{min} \\frac{n}{N} + e_5 * RH_{min} * u_2 89 | 90 | .. math:: e_0=0.81917, e_1 = -0.0040922, e_2 = 1.0705, e_3 = 0.065649, 91 | e_4 = -0.0059684, e_5 = -0.0005967. 92 | 93 | """ 94 | index = get_index(tmean) 95 | if nn is None: 96 | nn = daylight_hours(index, lat) 97 | if py is None: 98 | nn_sum = sum(daylight_hours(date_range("2000-1-1", "2000-12-31"), lat)) 99 | py = nn / nn_sum * 100 100 | if isinstance(tmean, DataArray) and len(py.shape) == 1: 101 | py = py[:, None, None] 102 | if method == 0: 103 | pet = a + b * (py * (0.457 * tmean + 8.128)) 104 | elif method == 1: 105 | pet = k * py * (0.46 * tmean + 8.13) 106 | elif method == 2: 107 | nn_sum = sum(daylight_hours(date_range("2000-1-1", "2000-12-31"), lat)) 108 | py = n / nn_sum * 100 109 | if isinstance(rhmin, DataArray) and len(nn.shape) == 1: 110 | nn = nn[:, None, None] 111 | k1 = 0.0043 * rhmin - n / nn - 1.41 112 | e0, e1, e2, e3, e4, e5 = ( 113 | 0.81917, 114 | -0.0040922, 115 | 1.0705, 116 | 0.065649, 117 | -0.0059684, 118 | -0.0005967, 119 | ) 120 | bvar = ( 121 | e0 122 | + e1 * rhmin 123 | + e2 * n / nn 124 | + e3 * wind 125 | + e4 * rhmin * n / nn 126 | + e5 * rhmin * wind 127 | ) 128 | pet = k1 + bvar * py * (0.46 * tmean + 8.13) 129 | else: 130 | raise Exception("Method can be either 0, 1 or 2.") 131 | pet = clip_zeros(pet, clip_zero) 132 | return pet_out(tmean, pet, "Blaney_Criddle") 133 | 134 | 135 | def haude(tmean, rh, k=1, clip_zero=True): 136 | """Potential evapotranspiration calculated according to 137 | :cite:t:`haude_determination_1955`. 138 | 139 | Parameters 140 | ---------- 141 | tmean: pandas.Series or xarray.DataArray 142 | temperature at 2pm or maximum dailty temperature [°C]. 143 | rh: float or pandas.Series or xarray.DataArray 144 | average relative humidity at 2pm [%]. 145 | k: float, optional 146 | calibration coefficient [-]. 147 | clip_zero: bool, optional 148 | if True, replace all negative values with 0. 149 | 150 | Returns 151 | ------- 152 | float or pandas.Series or xarray.DataArray containing the calculated potential 153 | evapotranspiration [mm d-1]. 154 | 155 | Examples 156 | -------- 157 | >>> pet_haude = haude(tmean, rh) 158 | 159 | Notes 160 | ----- 161 | Following :cite:t:`haude_determination_1955` and :cite:t:`schiff_berechnung_1975`. 162 | 163 | .. math:: PET = k * f * (e_s-e_a) 164 | 165 | """ 166 | e0 = calc_e0(tmean) 167 | ea = check_rh(rh) * e0 / 100 168 | # Haude coefficients from :cite:t:`schiff_berechnung_1975` 169 | fk = [0.27, 0.27, 0.28, 0.39, 0.39, 0.37, 0.35, 0.33, 0.31, 0.29, 0.27, 0.27] 170 | index = get_index(tmean) 171 | fk1 = asarray([fk[x - 1] for x in index.month]) 172 | if len(tmean.shape) > 1: 173 | f = fk1[:, newaxis, newaxis] * (tmean / tmean) 174 | else: 175 | f = fk1 176 | pet = k * f * (e0 - ea) * 10 # kPa to hPa 177 | pet = clip_zeros(pet, clip_zero) 178 | return pet_out(tmean, pet, "Haude") 179 | 180 | 181 | def hamon( 182 | tmean, 183 | lat, 184 | k=1, 185 | c=13.97, 186 | cc=218.527, 187 | n=None, 188 | tmax=None, 189 | tmin=None, 190 | method=0, 191 | clip_zero=True, 192 | ): 193 | """Potential evapotranspiration calculated according to 194 | :cite:t:`hamon_estimating_1963`. 195 | 196 | Parameters 197 | ---------- 198 | tmean: pandas.Series or xarray.DataArray 199 | average day temperature [°C]. 200 | lat: float or xarray.DataArray 201 | the site latitude [rad]. 202 | k: float, optional 203 | calibration coefficient if method = 0 [-]. 204 | c: float, optional 205 | c is a constant for calculation in mm per day if method = 1. 206 | cc: float, optional 207 | calibration coefficient if method = 2 [-]. 208 | n: float or pandas.Series or xarray.DataArray, optional 209 | actual duration of sunshine [hour]. 210 | tmax: float or pandas.Series or xarray.DataArray 211 | maximum day temperature [°C]. 212 | tmin: float or pandas.Series or xarray.DataArray 213 | minimum day temperature [°C]. 214 | 215 | method: float, optional 216 | 0 => Hamon after :cite:t:`oudin_which_2005` 217 | 1 => Hamon after equation 7 in :cite:t:`ansorge_performance_2019` 218 | 2 => Hamon after equation 12 in :cite:t:`ansorge_performance_2019`. 219 | 3 => Hamon after equation 12 in :cite:t:`rosenberry_comparison_2004`. 220 | clip_zero: bool, optional 221 | if True, replace all negative values with 0. 222 | 223 | Returns 224 | ------- 225 | float or pandas.Series or xarray.DataArray containing the calculated potential 226 | evapotranspiration [mm d-1]. 227 | 228 | Examples 229 | -------- 230 | >>> pet_hamon = hamon(tmean, lat) 231 | 232 | Notes 233 | ----- 234 | Method = 0; Based on cite:t:`oudin_which_2005`. 235 | 236 | .. math:: PET = k(\\frac{DL}{12})^2 exp(\\frac{T_{mean}}{16}) 237 | 238 | Method = 1; Based on equation 7 in cite:t:`ansorge_performance_2019`. 239 | 240 | .. math:: PET = c(\\frac{DL}{12})^2 pt 241 | 242 | where 243 | 244 | .. math:: pt = 4.95 \\frac{exp(0.062T_{mean})}{16} 245 | 246 | Method = 2; Based on equation 12 in cite:t:`ansorge_performance_2019`. 247 | 248 | .. math:: PET = cc\\frac{DL}{12} \\frac{1}{T_{mean} + 273.3} 249 | exp(\\frac{17.27T_{mean}}{T_{mean} + 273.3}) 250 | 251 | Method = 3; Based on cite:t:`rosenberry_comparison_2004`. 252 | 253 | .. math:: PET = 14 * (n / 12) ** 2 * (216.7 * e_s * 10 / 254 | (T_{mean} + 273.3)) / 100 255 | 256 | """ 257 | index = get_index(tmean) 258 | # Use transpose to work with lat either as int or xarray.DataArray 259 | dl = daylight_hours(index, lat) 260 | if len(dl.shape) < len(tmean.shape): 261 | dl = tmean / tmean * dl[:, newaxis, newaxis] 262 | if method == 0: 263 | pet = k * (dl / 12) ** 2 * exp(tmean / 16) 264 | elif method == 1: 265 | # saturated water content after Xu and Singh (2001) 266 | pt = 4.95 * exp(0.062 * tmean) / 100 267 | pet = c * (dl / 12) ** 2 * pt 268 | elif method == 2: 269 | pet = ( 270 | cc 271 | * (dl / 12) 272 | * 1 273 | / (tmean + 273.3) 274 | * exp((17.26939 * tmean) / (tmean + 273.3)) 275 | ) 276 | elif method == 3: 277 | es = calc_es(tmean, tmax, tmin) 278 | pet = k * 14 * (n / 12) ** 2 * (216.7 * es * 10 / (tmean + 273.3)) / 100 279 | else: 280 | raise Exception("method can be either 0, 1, 2 or 3.") 281 | pet = clip_zeros(pet, clip_zero) 282 | return pet_out(tmean, pet, "Hamon") 283 | 284 | 285 | def romanenko( 286 | tmean, rh, k=4.5, rhmax=None, rhmin=None, tmax=None, tmin=None, clip_zero=True 287 | ): 288 | """Potential evapotranspiration calculated according to 289 | :cite:t:`romanenko_computation_1961`. 290 | 291 | Parameters 292 | ---------- 293 | tmean: float or pandas.Series or xarray.DataArray 294 | average day temperature [°C]. 295 | rh: float or pandas.Series or xarray.DataArray 296 | mean daily relative humidity [%]. 297 | k: float, optional 298 | calibration coefficient [-]. 299 | tmax: float or pandas.Series or xarray.DataArray 300 | maximum day temperature [°C]. 301 | tmin: float or pandas.Series or xarray.DataArray 302 | minimum day temperature [°C]. 303 | rhmax: pandas.Series, optional 304 | maximum daily relative humidity [%]. 305 | rhmin: pandas.Series, optional 306 | mainimum daily relative humidity [%]. 307 | clip_zero: bool, optional 308 | if True, replace all negative values with 0. 309 | 310 | Returns 311 | ------- 312 | float or pandas.Series or xarray.DataArray containing the calculated potential 313 | evapotranspiration [mm d-1]. 314 | 315 | Examples 316 | -------- 317 | >>> pet_romanenko = romanenko(tmean, rh) 318 | 319 | Notes 320 | ----- 321 | Based on equation 11 in :cite:p:`xu_evaluation_2001`. 322 | 323 | .. math:: PET=k(1 + (\\frac{T_{mean}}{25})^2 (1 - \\frac{e_a}{e_s}) 324 | 325 | """ 326 | ea = calc_ea( 327 | tmean=tmean, 328 | tmax=tmax, 329 | tmin=tmin, 330 | rhmax=check_rh(rhmax), 331 | rhmin=check_rh(rhmin), 332 | rh=check_rh(rh), 333 | ) 334 | es = calc_es(tmean=tmean, tmax=tmax, tmin=tmin) 335 | pet = k * (1 + tmean / 25) ** 2 * (1 - ea / es) 336 | pet = clip_zeros(pet, clip_zero) 337 | return pet_out(tmean, pet, "Romanenko") 338 | 339 | 340 | def linacre(tmean, elevation, lat, tdew=None, tmax=None, tmin=None, clip_zero=True): 341 | """Potential evapotranspiration calculated according to 342 | :cite:t:`linacre_simple_1977`. 343 | 344 | Parameters 345 | ---------- 346 | tmean: pandas.Series or array_like 347 | average day temperature [°C]. 348 | elevation: array_like 349 | the site elevation [m]. 350 | lat: array_like, optional 351 | the site latitude [°]. 352 | tdew: pandas.Series or array_like, optional 353 | mean dew-point temperature [°C]. 354 | tmax: pandas.Series or array_like, optional 355 | maximum day temperature [°C]. 356 | tmin: pandas.Series or array_like, optional 357 | minimum day temperature [°C]. 358 | clip_zero: bool, optional 359 | if True, replace all negative values with 0. 360 | 361 | Returns 362 | ------- 363 | pandas.Series or array_like containing the calculated potential 364 | evapotranspiration [mm d-1]. 365 | 366 | Examples 367 | -------- 368 | >>> pet_linacre = linacre(tmean, elevation, lat) 369 | 370 | Notes 371 | ----- 372 | Based on equation 5 in :cite:p:`xu_evaluation_2001`. 373 | 374 | .. math:: PET = \\frac{\\frac{500 T_m}{(100-lat)}+15 (T_a-T_d)}{80-T_a} 375 | 376 | """ 377 | if tdew is None and tmax is None and tmin is None: 378 | raise Exception("Please provide either Tdew or Tmax and Tmin!") 379 | lat = check_lat(lat) 380 | lat_deg = lat / pi * 180 381 | if tdew is None: 382 | tmax, tmin = tmax.values, tmin.values 383 | tdew = 0.52 * tmin + 0.6 * tmax - 0.009 * tmax**2 - 2 384 | 385 | tm = tmean + 0.006 * elevation 386 | pet = (500 * tm / (100 - lat_deg) + 15 * (tmean - tdew)) / (80 - tmean) 387 | pet = clip_zeros(pet, clip_zero) 388 | return pet_out(tmean, pet, "Linacre") 389 | -------------------------------------------------------------------------------- /pyet/utils.py: -------------------------------------------------------------------------------- 1 | import numpy 2 | from pandas import Series, DatetimeIndex 3 | from xarray import DataArray 4 | 5 | 6 | def show_versions(): 7 | """Method to print the version of dependencies.""" 8 | from pyet import __version__ as ps_version 9 | from pandas import __version__ as pd_version 10 | from numpy import __version__ as np_version 11 | from sys import version as os_version 12 | from xarray import __version__ as xr_version 13 | 14 | msg = ( 15 | f"Python version: {os_version}\n" 16 | f"Numpy version: {np_version}\n" 17 | f"Pandas version: {pd_version}\n" 18 | f"xarray version: {xr_version}\n" 19 | f"Pyet version: {ps_version}" 20 | ) 21 | return print(msg) 22 | 23 | 24 | def deg_to_rad(lat): 25 | """Method to convert latitude in degrees to radians. 26 | 27 | Parameters 28 | ---------- 29 | lat: float or xarray.DataArray 30 | The site latitude [deg]. 31 | 32 | Returns 33 | ------- 34 | float or pandas.Series or xarray.DataArray containing the calculated latitude in 35 | radians [rad]. 36 | """ 37 | return lat * numpy.pi / 180 38 | 39 | 40 | def check_rad(rad): 41 | """Method to check if radiation was probably provided in MJ/m2d.""" 42 | if rad is not None: 43 | if numpy.nanmax(rad) < 100: 44 | return rad 45 | else: 46 | raise Exception( 47 | "The radiation input provided is greater than 100 MJ/m2d, " 48 | "which is not realistic. Please convert the radiation input" 49 | " to MJ/m2d." 50 | ) 51 | 52 | 53 | def check_rh(rh): 54 | """Method to check if relative humidity is provided in percentage.""" 55 | if rh is not None: 56 | if numpy.nanmax(rh) > 1.0: 57 | return rh 58 | else: 59 | raise Exception( 60 | "The maximum value of relative humidity provided is smaller " 61 | "than 1 [%], which is not realistic. Please convert the " 62 | "relative humidity to [%]." 63 | ) 64 | else: 65 | pass 66 | 67 | 68 | def check_lat(lat, shape=None): 69 | """Method to check if latitude was (most likely) given in radians.""" 70 | if not isinstance(lat, (float, int, DataArray)): 71 | raise TypeError("lat must be a float, int or DataArray") 72 | if isinstance(lat, (float, int)) and (shape is None or len(shape) == 1): 73 | lat1 = lat 74 | else: 75 | if isinstance(lat, (float, int)) and (len(shape) > 1): 76 | raise ValueError(f"lat must be a shaped as 2D DataArray") 77 | lat1 = lat.values 78 | if not (-1.6 < numpy.mean(lat1) < 1.6): 79 | raise Exception( 80 | "Latitude must be provided in radians! Use pyet.deg_to_rad()" 81 | "to convert from degrees to radians." 82 | ) 83 | return lat1 84 | 85 | 86 | def clip_zeros(s, clip_zero): 87 | """Method to replace negative values with 0 for Pandas.Series and xarray.DataArray.""" 88 | if clip_zero: 89 | s = s.where((s >= 0) | s.isnull(), 0) 90 | return s 91 | 92 | 93 | def pet_out(tmean, pet, name): 94 | """Method to create pandas.Series or xarray.DataArray from numpy.ndarray""" 95 | if isinstance(tmean, (Series, DataArray)): 96 | return pet.rename(name) 97 | else: 98 | raise TypeError("Input must be either pandas.Series or xarray.DataArray!") 99 | 100 | 101 | def get_index(df): 102 | """Method to return the index of the input data.""" 103 | try: 104 | index = DatetimeIndex(df.index) 105 | except AttributeError: 106 | index = DatetimeIndex(df.time) 107 | return index 108 | 109 | 110 | def vectorize(*arrays): 111 | """Vectorize pandas.Series or xarray.DataArray inputs.""" 112 | vec_arrays = [] 113 | for arr in arrays: 114 | if arr is None: 115 | vec_arr = None 116 | elif isinstance(arr, (int, float)): 117 | vec_arr = arr 118 | elif isinstance(arr, Series): 119 | vec_arr = arr.copy().values 120 | elif isinstance(arr, DataArray): 121 | vec_arr = arr.copy().values 122 | else: 123 | raise TypeError( 124 | f"Input must be a pandas.Series or xarray.DataArray, " 125 | f"but got {type(arr)}" 126 | ) 127 | vec_arrays.append(vec_arr) 128 | return vec_arrays 129 | -------------------------------------------------------------------------------- /pyet/version.py: -------------------------------------------------------------------------------- 1 | # This is the only location where the version will be written and changed. 2 | # Based on https://packaging.python.org/single_source_version/ 3 | __version__ = "1.3.1" 4 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "wheel"] 3 | 4 | [project] 5 | name = "pyet" 6 | dynamic = ["version"] 7 | description = "pyet - Estimation of Potential Evaporation" 8 | readme = "README.md" 9 | authors = [ 10 | {name = "Matevz Vremec", email = "matevz.vremec@uni-graz.at"}, 11 | {name = "Raoul Collenteur", email = "raoul.collenteur@eawag.ch"} 12 | ] 13 | license = { file = "LICENSE" } 14 | requires-python = ">=3.9" 15 | dependencies = [ 16 | "numpy >= 1.16", 17 | "xarray >= 0.18.0", 18 | "pandas >= 1.2", 19 | ] 20 | classifiers = [ 21 | 'Development Status :: 5 - Production/Stable', 22 | 'Intended Audience :: Science/Research', 23 | 'Intended Audience :: Other Audience', 24 | 'License :: OSI Approved :: MIT License', 25 | 'Programming Language :: Python :: 3 :: Only', 26 | 'Programming Language :: Python :: 3.9', 27 | 'Programming Language :: Python :: 3.10', 28 | 'Programming Language :: Python :: 3.11', 29 | 'Programming Language :: Python :: 3.12', 30 | 'Topic :: Scientific/Engineering :: Hydrology', 31 | ] 32 | 33 | [project.urls] 34 | Source = "https://github.com/phydrus/pyet" 35 | Tracker = "https://github.com/phydrus/pyet/issues" 36 | Help = "https://github.com/phydrus/pyet/discussions" 37 | homepage = "https://github.com/phydrus/pyet" 38 | repository = "https://github.com/phydrus/pyet" 39 | documentation = "https://github.com/phydrus/pyet/discussions" 40 | 41 | [tool.setuptools.dynamic] 42 | version = { attr = "pyet.version.__version__" } 43 | 44 | [tool.black] 45 | line-length = 88 46 | 47 | [tool.isort] 48 | profile = "black" 49 | 50 | [tool.pytest.ini_options] 51 | testpaths = ["tests"] 52 | 53 | [project.optional-dependencies] 54 | rtd = [ 55 | "sphinx-autodoc-typehints", 56 | "Ipython", 57 | "ipykernel", 58 | "pydata-sphinx-theme", 59 | "sphinx-gallery", 60 | "sphinx>=3.1", 61 | "sphinxcontrib-bibtex", 62 | "matplotlib", 63 | "myst-nb", 64 | "numpydoc", 65 | "sphinx-design", 66 | "seaborn", 67 | "netcdf4", 68 | "scikit-learn", 69 | "scipy", 70 | "spotpy", 71 | "openpyxl" 72 | ] -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy>=1.16 2 | xarray>=0.18.0 3 | pandas>=1.2 4 | flake8 5 | pytest -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from tests import testfao56 3 | from tests import testalternative 4 | from tests import test_all 5 | -------------------------------------------------------------------------------- /tests/readme.rst: -------------------------------------------------------------------------------- 1 | Tests 2 | ----- 3 | 4 | This folder contains all the tests that can be run to automatically test of 5 | pyet runs correctly. 6 | 7 | All tests in this folder can be run with the following command: 8 | >>> python -m unittest -v 9 | 10 | This requires the nosetests package to be installed. If not installed run: 11 | >>> pip install unittest -------------------------------------------------------------------------------- /tests/test_all.py: -------------------------------------------------------------------------------- 1 | """This file tests all methods for a minimal functioning.""" 2 | import unittest 3 | 4 | import numpy as np 5 | import pandas as pd 6 | 7 | import pyet as et 8 | 9 | tmean = pd.Series(data=20 * np.sin(np.linspace(0, 1, 365) * 2 * np.pi), 10 | index=pd.date_range("2001-01-01", "2001-12-31", freq="D")) 11 | tmax = tmean + 2 12 | tmin = tmean - 2 13 | rh = pd.Series(data=60 * np.sin(np.linspace(0, 1, 365) * np.pi), 14 | index=pd.date_range("2001-01-01", "2001-12-31", freq="D")) 15 | rs = pd.Series(data=10 * np.sin(np.linspace(0, 1, 365) * np.pi), 16 | index=pd.date_range("2001-01-01", "2001-12-31", freq="D")) 17 | wind = pd.Series(data=5 * np.sin(np.linspace(0, 1, 365) * np.pi), 18 | index=pd.date_range("2001-01-01", "2001-12-31", freq="D")) 19 | lat = 0.9 20 | elevation = 20 21 | 22 | 23 | class Testall(unittest.TestCase): 24 | def test_calculate_all(self): 25 | et_df = et.calculate_all(tmean, wind, rs, elevation, lat, tmax, tmin, 26 | rh) 27 | self.assertIsInstance(et_df, pd.DataFrame) 28 | -------------------------------------------------------------------------------- /tests/testfao56.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from numpy import pi, array, full, testing 3 | from pandas import date_range, DatetimeIndex, Series 4 | from xarray import DataArray, Dataset 5 | import pyet as et 6 | 7 | 8 | class TestFAO56(unittest.TestCase): 9 | def test_press_calc(self): 10 | # Based on Example 2 and 4, p. 32 FAO. 11 | elevation_s = Series( 12 | data=[1800, 1200, 1462.4], index=date_range(start="2020-1-1", periods=3) 13 | ) 14 | calculated_pressures0 = et.calc_press(elevation_s) 15 | expected_pressures = Series( 16 | data=[81.8, 87.9, 85.17], index=date_range(start="2020-1-1", periods=3) 17 | ) 18 | testing.assert_allclose( 19 | expected_pressures.round(1), calculated_pressures0.round(1) 20 | ) 21 | 22 | # Create a 3D DataArray with dimensions 'x', 'y', and 'time' 23 | elevation_xr = DataArray( 24 | full((2, 3, 3), 1800), 25 | coords=[ 26 | ("time", date_range(start="1/1/2020", periods=2)), 27 | ("y", [1, 2, 3]), 28 | ("x", [1, 2, 3]), 29 | ], 30 | ) 31 | elevation_xr.loc[{"time": "2020-01-02"}] = 1200 32 | # Apply the et.calc_press function to each element of the DataArray 33 | calculated_pressures = et.calc_press(elevation_xr) 34 | # Create expected results as DataArrays 35 | expected_pressures = DataArray( 36 | full((2, 3, 3), 81.8), 37 | coords=[ 38 | ("time", date_range(start="1/1/2020", periods=2)), 39 | ("y", [1, 2, 3]), 40 | ("x", [1, 2, 3]), 41 | ], 42 | ) 43 | expected_pressures.loc[{"time": "2020-01-02"}] = 87.9 44 | # Check that the results are as expected 45 | testing.assert_allclose(calculated_pressures.round(1), expected_pressures) 46 | 47 | def test_vpc(self): 48 | # Based on ASCE Table C-3 49 | tmean = [21.65, 22.9, 23.7, 22.8, 24.3, 26.0, 26.1, 26.4, 23.9, 24.2] 50 | tmean_s = Series(tmean, index=date_range(start="2020-1-1", periods=10)) 51 | calculated_vpc1 = et.calc_vpc(tmean_s) 52 | vpc1 = [ 53 | 0.1585, 54 | 0.1692, 55 | 0.1762, 56 | 0.1684, 57 | 0.1820, 58 | 0.199, 59 | 0.1996, 60 | 0.2027, 61 | 0.1781, 62 | 0.1809, 63 | ] 64 | expected_vpc1 = Series(vpc1, index=date_range(start="2020-1-1", periods=10)) 65 | testing.assert_allclose(expected_vpc1.round(2), calculated_vpc1.round(2)) 66 | 67 | # Create a 3D DataArray with dimensions 'x', 'y', and 'time' 68 | tmean_xr = DataArray( 69 | full((4, 3, 3), 21.65), 70 | coords=[ 71 | ("time", date_range(start="2020-1-1", periods=4)), 72 | ("y", [1, 2, 3]), 73 | ("x", [1, 2, 3]), 74 | ], 75 | ) 76 | tmean_xr.loc[{"time": ["2020-01-02", "2020-01-03", "2020-01-04"]}] = [ 77 | 22.9, 78 | 23.7, 79 | 22.8, 80 | ] 81 | # Apply the et.calc_press function to each element of the DataArray 82 | calculated_vpc = et.calc_vpc(tmean_xr) 83 | # Create expected results as DataArrays 84 | expected_vpc = DataArray( 85 | full((4, 3, 3), 0.158), 86 | coords=[ 87 | ("time", date_range(start="2020-1-1", periods=4)), 88 | ("y", [1, 2, 3]), 89 | ("x", [1, 2, 3]), 90 | ], 91 | ) 92 | expected_vpc.loc[{"time": ["2020-01-02", "2020-01-03", "2020-01-04"]}] = [ 93 | 0.1692, 94 | 0.1762, 95 | 0.1684, 96 | ] 97 | # Check that the results are as expected 98 | testing.assert_allclose(calculated_vpc.round(3), expected_vpc.round(3)) 99 | 100 | def test_psy_calc(self): 101 | # Based on Example 2, p. 32 FAO. and Table C-2 in ASCE(2001) 102 | p0 = Series([81.8, 85.17], index=date_range(start="2020-1-1", periods=2)) 103 | calculated_psy = et.calc_psy(p0) 104 | expected_psy = Series( 105 | [0.054, 0.0566], index=date_range(start="2020-1-1", periods=2) 106 | ) 107 | testing.assert_allclose(expected_psy.round(3), calculated_psy.round(3)) 108 | # Create a 3D DataArray with dimensions 'x', 'y', and 'time' 109 | press_xr = DataArray( 110 | full((2, 3, 3), 81.8), 111 | coords=[ 112 | ("time", date_range(start="2020-1-1", periods=2)), 113 | ("y", [1, 2, 3]), 114 | ("x", [1, 2, 3]), 115 | ], 116 | ) 117 | press_xr.loc[{"time": "2020-01-02"}] = 85.17 118 | # Apply the et.calc_press function to each element of the DataArray 119 | calculated_psy = et.calc_psy(press_xr) 120 | # Create expected results as DataArrays 121 | expected_psy = DataArray( 122 | full((2, 3, 3), 0.054), 123 | coords=[ 124 | ("time", date_range(start="2020-1-1", periods=2)), 125 | ("y", [1, 2, 3]), 126 | ("x", [1, 2, 3]), 127 | ], 128 | ) 129 | expected_psy.loc[{"time": "2020-01-02"}] = 0.0566 130 | # Check that the results are as expected 131 | testing.assert_allclose(calculated_psy.round(3), expected_psy.round(3)) 132 | 133 | def test_e0_calc(self): 134 | # Based on Example 3 and 4, p. 36 FAO. 135 | tmean0 = Series( 136 | [24.5, 15, 19.75, 19.5], index=date_range(start="2020-1-1", periods=4) 137 | ) 138 | calculated_e0 = et.calc_e0(tmean0) 139 | expected_e0 = Series( 140 | [3.075, 1.705, 2.3, 2.267], index=date_range(start="2020-1-1", periods=4) 141 | ) 142 | testing.assert_allclose(expected_e0.round(1), calculated_e0.round(1)) 143 | 144 | # Create a 3D DataArray with dimensions 'x', 'y', and 'time' 145 | tmean_xr = DataArray( 146 | full((4, 3, 3), 24.5), 147 | coords=[ 148 | ("time", date_range(start="2020-1-1", periods=4)), 149 | ("y", [1, 2, 3]), 150 | ("x", [1, 2, 3]), 151 | ], 152 | ) 153 | tmean_xr.loc[{"time": "2020-01-02"}] = 15 154 | tmean_xr.loc[{"time": "2020-01-03"}] = 19.75 155 | tmean_xr.loc[{"time": "2020-01-04"}] = 19.5 156 | # Apply the et.calc_press function to each element of the DataArray 157 | calculated_e0 = et.calc_e0(tmean_xr) 158 | # Create expected results as DataArrays 159 | expected_e0 = DataArray( 160 | full((4, 3, 3), 3.075), 161 | coords=[ 162 | ("time", date_range(start="2020-1-1", periods=4)), 163 | ("y", [1, 2, 3]), 164 | ("x", [1, 2, 3]), 165 | ], 166 | ) 167 | expected_e0.loc[{"time": "2020-01-02"}] = 1.705 168 | expected_e0.loc[{"time": "2020-01-03"}] = 2.3 169 | expected_e0.loc[{"time": "2020-01-04"}] = 2.267 170 | # Check that the results are as expected 171 | testing.assert_allclose(calculated_e0.round(1), expected_e0.round(1)) 172 | 173 | def test_es_calc(self): 174 | # Based on Example 3 and 6, p. 36 FAO. 175 | es = et.calc_es(tmax=24.5, tmin=15.0) 176 | self.assertAlmostEqual(es, 2.39, 3) 177 | 178 | def test_ea_calc(self): 179 | # Based on Example 5, p. 39 FAO. 180 | ea1 = et.calc_ea(tmax=25.0, tmin=18.0, rhmax=82.0, rhmin=54.0) 181 | rhmean = (82 + 54) / 2 182 | ea2 = et.calc_ea(tmax=25.0, tmin=18.0, rh=rhmean) 183 | self.assertAlmostEqual(ea1, 1.70, 2) 184 | self.assertAlmostEqual(ea2, 1.78, 2) 185 | 186 | def test_relative_distance(self): 187 | # Based on Example 8, p. 47 FAO. 188 | rd = et.relative_distance(246) 189 | self.assertAlmostEqual(rd, 0.985, 3) 190 | 191 | def test_solar_declination(self): 192 | # Based on Example 8, p. 47 FAO. 193 | sd = et.solar_declination(246) 194 | self.assertAlmostEqual(sd, 0.12, 2) 195 | 196 | def test_sunset_angle(self): 197 | # Based on Example 8, p. 47 FAO. 198 | sangle = et.sunset_angle(0.12, -0.35) 199 | self.assertAlmostEqual(sangle, 1.527, 3) 200 | 201 | def test_day_of_year(self): 202 | # Based on Example 8, p. 47 FAO. 203 | doy = et.day_of_year(DatetimeIndex(["2015-09-03"])) 204 | self.assertAlmostEqual(float(doy.iloc[0]), 246, 1) 205 | # Based on ASCD Table C-3 206 | dindex = date_range("2020-07-01", "2020-07-10") 207 | doy1 = et.day_of_year(dindex).tolist() 208 | doyr = [183, 184, 185, 186, 187, 188, 189, 190, 191, 192] 209 | self.assertEqual(doy1, doyr, 1) 210 | 211 | def test_extraterrestrial_r(self): 212 | # Based on Example 8, p. 47 FAO. 213 | extrar = et.extraterrestrial_r(DatetimeIndex(["2015-09-03"]), -0.35) 214 | self.assertAlmostEqual(float(extrar), 32.2, 1) 215 | 216 | def test_daylight_hours(self): 217 | # Based on Example 9, p. 47 FAO. 218 | dayhours = et.daylight_hours(DatetimeIndex(["2015-09-03"]), -0.35) 219 | self.assertAlmostEqual(float(dayhours), 11.7, 1) 220 | 221 | def test_calc_rad_long(self): 222 | # Based on Example 10, p. 52 FAO. 223 | rs = Series([14.5], index=DatetimeIndex(["2015-05-15"])) 224 | tmax = Series([25.1], index=DatetimeIndex(["2015-05-15"])) 225 | tmin = Series([19], index=DatetimeIndex(["2015-05-15"])) 226 | ea = Series([2.1], index=DatetimeIndex(["2015-05-15"])) 227 | rso = Series([18.8], index=DatetimeIndex(["2015-05-15"])) 228 | rnl = et.calc_rad_long(rs, tmax=tmax, tmin=tmin, ea=ea, rso=rso) 229 | self.assertAlmostEqual(float(rnl), 3.5, 1) 230 | 231 | def test_calc_rad_sol_in(self): 232 | # Based on example 10, p 50 TestFAO56 233 | lat = -22.9 * pi / 180 234 | n = Series(7.1, DatetimeIndex(["2021-5-15"])) 235 | rad_sol_in = et.calc_rad_sol_in(n, lat) 236 | self.assertAlmostEqual(float(rad_sol_in.iloc[0]), 14.5, 1) 237 | 238 | def test_et_fao56(self): 239 | # Based on Example 18, p. 72 FAO. 240 | wind = Series([2.078], index=DatetimeIndex(["2015-07-06"])) 241 | tmax = Series([21.5], index=DatetimeIndex(["2015-07-06"])) 242 | tmin = Series([12.3], index=DatetimeIndex(["2015-07-06"])) 243 | tmean = (tmax + tmin) / 2 244 | rhmax = Series([84], index=DatetimeIndex(["2015-07-06"])) 245 | rhmin = Series([63], index=DatetimeIndex(["2015-07-06"])) 246 | rs = Series([22.07], index=DatetimeIndex(["2015-07-06"])) 247 | n = 9.25 248 | nn = 16.1 249 | elevation = 100 250 | lat = 50.80 * pi / 180 251 | et56 = et.pm_fao56( 252 | tmean, 253 | wind, 254 | elevation=elevation, 255 | lat=lat, 256 | rs=rs, 257 | tmax=tmax, 258 | tmin=tmin, 259 | rhmax=rhmax, 260 | rhmin=rhmin, 261 | n=n, 262 | nn=nn, 263 | ) 264 | self.assertAlmostEqual(float(et56.iloc[0]), 3.9, 1) 265 | # Create an xarray Dataset with DataArrays 266 | # Create an xarray Dataset with DataArrays 267 | data = { 268 | "wind": DataArray(wind, dims=("time",)), 269 | "tmax": DataArray(tmax, dims=("time",)), 270 | "tmin": DataArray(tmin, dims=("time",)), 271 | "tmean": DataArray(tmean, dims=("time",)), 272 | "rhmax": DataArray(rhmax, dims=("time",)), 273 | "rhmin": DataArray(rhmin, dims=("time",)), 274 | "rs": DataArray(rs, dims=("time",)), 275 | "et0": DataArray([3.9], dims=("time",)), 276 | } 277 | 278 | # Create the xarray Dataset 279 | dataset = Dataset(data) 280 | # Add additional variables (n, nn, elevation, lat) 281 | dataset["n"] = n 282 | dataset["nn"] = nn 283 | dataset["elevation"] = elevation 284 | dataset["lat"] = lat 285 | et56_xr = et.pm_fao56( 286 | dataset["tmean"], 287 | dataset["wind"], 288 | elevation=dataset["elevation"], 289 | lat=dataset["lat"], 290 | rs=dataset["rs"], 291 | tmax=dataset["tmax"], 292 | tmin=dataset["tmin"], 293 | rhmax=dataset["rhmax"], 294 | rhmin=dataset["rhmin"], 295 | n=dataset["n"], 296 | nn=dataset["nn"], 297 | ) 298 | 299 | testing.assert_allclose(et56_xr.round(1), dataset["et0"]) 300 | --------------------------------------------------------------------------------