Repository: pvlib/pvlib-python
Pull request #820 (2019-11-13 20:26:43)

Add shield organization table with download shields
 - [x] Closes  #813
 - [x] I am familiar with the [contributing guidelines](http://pvlib-python.readthedocs.io/en/latest/contributing.html).
 - [x] Fully tested. Added and/or modified tests to ensure correct behavior for all reasonable inputs. Tests (usually) must pass on the TravisCI and Appveyor testing services.
* N/A
 - [x] Updates entries to `docs/sphinx/source/api.rst` for API changes.
* N/A
 - [X] Adds description and name entries in the appropriate `docs/sphinx/source/whatsnew` file for all changes.
* N/A
 - [x] Code quality and style is sufficient. Passes LGTM and SticklerCI checks.
 - [x] New code is fully documented. Includes sphinx/numpydoc compliant docstrings and comments in the code where necessary.
* N/A
 - [x] Pull request is nearly complete and ready for detailed review.

Brief description of the problem and proposed solution (if not already fully described in the issue linked to above):

This PR addresses  #813 to add download counter shields for the README. It also implements a table organizing the shields by a descriptive title. 

I've included a placeholder shield for Azure Pipelines from Pandas as an example of a new shield to add. @wholmgren if adding this Azure Pipelines shield is desired it needs to be activated according to [this guide](https://docs.microsoft.com/en-us/azure/devops/pipelines/create-first-pipeline?view=azure-devops&tabs=tfs-2018-2#add-a-status-badge-to-your-repository).

Please let me know what you all think about the table format, titles and general organization.

wholmgren: @CameronTStark can you rebase on pvlib/master?
wholmgren: I really like the table! (other reviewers: see the branch's readme [here](https://github.com/CameronTStark/pvlib-python/tree/shield_org))

I couldn't follow the linked Azure Pipelines instructions. Its interface didn't seem to line up with the documentation. But I think the following badge works:

[![Build Status](https://dev.azure.com/solararbiter/pvlib%20python/_apis/build/status/pvlib.pvlib-python?branchName=master)](https://dev.azure.com/solararbiter/pvlib%20python/_build/latest?definitionId=4&branchName=master)

```
[![Build Status](https://dev.azure.com/solararbiter/pvlib%20python/_apis/build/status/pvlib.pvlib-python?branchName=master)](https://dev.azure.com/solararbiter/pvlib%20python/_build/latest?definitionId=4&branchName=master)
```
CameronTStark: > @CameronTStark can you rebase on pvlib/master?

Done. Thanks for the warning.

I've included the Azure Pipelines urls for pvlib. Thanks for that.

I'm glad you like the format. :smile
cwhanse: The table looks great!  

Is there a conda badge to go with the pypi badge?

How should I interpret the downloads?  Since some date?  Wondering if it is possible and useful to add dates to the Latest Release, and a "Downloads since" date.
CameronTStark: > Is there a conda badge to go with the pypi badge?

Good idea. [Here's a link to more of the Anaconda badges.](https://anaconda.org/conda-forge/pvlib-python/badges) We could put this one next to PyPI: [![Anaconda-Server Badge](https://anaconda.org/conda-forge/pvlib-python/badges/version.svg)](https://anaconda.org/conda-forge/pvlib-python)

 
> How should I interpret the downloads? Since some date? Wondering if it is possible and useful to add dates to the Latest Release, and a "Downloads since" date.

The PyPI one is per month and the conda-forge  is total. The PyPI appears disproportionate to the conda-forge so I'm wondering where that could come from. I've looked at the numbers on [PyPIstats.org](https://pypistats.org/packages/pvlib) and there's a big jump in Linux Python 3.6 users downloading pvlib-python in late June. Any ideas what that could be from? Was there a CI change then? I think legacy Python(2) support was dropped then, maybe it nudged people into staying current?

PyPI logs metrics to Google BigQuery and the badge/shield providers crunch that data depending on the time scale chosen either daily, weekly or monthly. BigQuery is free so we could go into there and crunch the numbers in any way we want but we'd probably have to build a means of calculating from a previous release unless we found a badge provider that would do that for us.  I could imagine doing that crunch manually before a conference presentation as a means of showing usage growth.

Here's a few more links:
https://pypi.org/help/ # search page for statistics
https://pypistats.org/faqs
cwhanse: > The PyPI one is per month and the conda-forge  is total. The PyPI appears disproportionate to the conda-forge so I'm wondering where that could come from. I've looked at the numbers on PyPIstats.org and there's a big jump in Linux Python 3.6 users downloading pvlib-python in late June. Any ideas what that could be from? Was there a CI change then? 

Something changed. The NumFocus affiliated status was in April 2019. I looked at the commit history and didn't recognize a change that could explain the surge in downloads that started in June 2019.

wholmgren: Interesting that there is a gradual build up over a month instead of a step change over a single day. I'm almost certain the change does not reflect a dramatic increase in the number of users. I'd like to think that someone like Google or Facebook started using pvlib at some bigger scale for something interesting, but I'm guessing the answer is much less exciting.

Patch:
diff --git a/README.md b/README.md
index 6ce2d64..a8fd264 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,86 @@
 <img src="docs/sphinx/source/_images/pvlib_logo_horiz.png" width="600">
 
-[![TravisCI](https://travis-ci.org/pvlib/pvlib-python.svg?branch=master)](https://travis-ci.org/pvlib/pvlib-python)
-[![Coverage Status](https://img.shields.io/coveralls/pvlib/pvlib-python.svg)](https://coveralls.io/r/pvlib/pvlib-python)
-[![codecov](https://codecov.io/gh/pvlib/pvlib-python/branch/master/graph/badge.svg)](https://codecov.io/gh/pvlib/pvlib-python)
-[![Documentation Status](https://readthedocs.org/projects/pvlib-python/badge/?version=stable)](http://pvlib-python.readthedocs.org/en/stable/)
-[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.2554311.svg)](https://doi.org/10.5281/zenodo.2554311)
-[![status](http://joss.theoj.org/papers/41187535cad22dd4b076c89b72f874b1/status.svg)](http://joss.theoj.org/papers/41187535cad22dd4b076c89b72f874b1)
-[![Code Quality: Python](https://img.shields.io/lgtm/grade/python/g/pvlib/pvlib-python.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/pvlib/pvlib-python/context:python)
-[![Total Alerts](https://img.shields.io/lgtm/alerts/g/pvlib/pvlib-python.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/pvlib/pvlib-python/alerts)
+<table>
+<tr>
+  <td>Latest Release</td>
+  <td>
+    <a href="https://pypi.org/project/pvlib/">
+    <img src="https://img.shields.io/pypi/v/pvlib.svg" alt="latest release" />
+    </a>
+    <a href="https://anaconda.org/conda-forge/pvlib-python">
+    <img src="https://anaconda.org/conda-forge/pvlib-python/badges/version.svg" />
+    </a>
+    <a href="https://anaconda.org/conda-forge/pvlib-python">
+    <img src="https://anaconda.org/conda-forge/pvlib-python/badges/latest_release_date.svg" />
+    </a>
+</tr>
+<tr>
+  <td>License</td>
+  <td>
+    <a href="https://github.com/pvlib/pvlib-python/blob/master/LICENSE">
+    <img src="https://img.shields.io/pypi/l/pvlib.svg" alt="license" />
+    </a>
+</td>
+</tr>
+<tr>
+  <td>Build Status</td>
+  <td>
+    <a href="https://travis-ci.org/pvlib/pvlib-python">
+    <img src="https://travis-ci.org/pvlib/pvlib-python.svg?branch=master" alt="travis build status" />
+    </a>
+    <a href="http://pvlib-python.readthedocs.org/en/stable/">
+    <img src="https://readthedocs.org/projects/pvlib-python/badge/?version=stable" alt="documentation build status" />
+    </a>
+    <a href="https://dev.azure.com/solararbiter/pvlib%20python/_build/latest?definitionId=4&branchName=master">
+      <img src="https://dev.azure.com/solararbiter/pvlib%20python/_apis/build/status/pvlib.pvlib-python?branchName=master" alt="Azure Pipelines build status" />
+    </a>
+  </td>
+</tr>
+<tr>
+  <td>Code Quality</td>
+  <td>
+    <a href="https://lgtm.com/projects/g/pvlib/pvlib-python/context:python">
+    <img src="https://img.shields.io/lgtm/grade/python/g/pvlib/pvlib-python.svg?logo=lgtm&logoWidth=18" alt="lgtm quality grade" />
+    </a>
+    <a href="https://lgtm.com/projects/g/pvlib/pvlib-python/alerts">
+    <img src="https://img.shields.io/lgtm/alerts/g/pvlib/pvlib-python.svg?logo=lgtm&logoWidth=18" alt="lgtm alters" />
+    </a>
+  </td>
+</tr>
+<tr>
+  <td>Coverage</td>
+  <td>
+    <a href="https://coveralls.io/r/pvlib/pvlib-python">
+    <img src="https://img.shields.io/coveralls/pvlib/pvlib-python.svg" alt="coveralls coverage" />
+    </a>
+    <a href="https://codecov.io/gh/pvlib/pvlib-python">
+    <img src="https://codecov.io/gh/pvlib/pvlib-python/branch/master/graph/badge.svg" alt="codecov coverage" />
+    </a>
+  </td>
+</tr>
+<tr>
+  <td>Publications</td>
+  <td>
+    <a href="https://doi.org/10.5281/zenodo.2554311">
+    <img src="https://zenodo.org/badge/DOI/10.5281/zenodo.2554311.svg" alt="zenodo reference" />
+    </a>
+    <a href="http://joss.theoj.org/papers/41187535cad22dd4b076c89b72f874b1">
+    <img src="http://joss.theoj.org/papers/41187535cad22dd4b076c89b72f874b1/status.svg" alt="JOSS reference" />
+    </a>
+  </td>
+</tr>
+<tr>
+  <td>Downloads</td>
+  <td>
+    <a href="https://pypi.org/project/pvlib/">
+    <img src="https://img.shields.io/pypi/dm/pvlib" alt="PyPI downloads" />
+    </a>
+    <a href="https://anaconda.org/conda-forge/pvlib-python">
+    <img src="https://anaconda.org/conda-forge/pvlib-python/badges/downloads.svg" alt="conda-forge downloads" />
+    </a>
+  </td>
+</tr>
+</table>
 
 
 pvlib python is a community supported tool that provides a set of
diff --git a/docs/sphinx/source/whatsnew/v0.7.0.rst b/docs/sphinx/source/whatsnew/v0.7.0.rst
index 8e32278..613b458 100644
--- a/docs/sphinx/source/whatsnew/v0.7.0.rst
+++ b/docs/sphinx/source/whatsnew/v0.7.0.rst
@@ -82,6 +82,12 @@ API Changes
       - Methods `PVSystem.ashraeiam`, `PVSystem.physicaliam` and
        `PVSystem.sapm_aoi_loss` are deprecated and will be removed in v0.8.
 
+* Changes related to spectral modifier (:issue:`782`):
+   * Changes to functions
+      - Added the argument `pw_min` and `pw_max`, default values 0.1 and 8 resp., 
+	to `atmosphere.first_solar_spectral_correction`. This function now returns NaN
+	if pw value higher than `pw_max`.
+
 * Calling :py:func:`pvlib.pvsystem.retrieve_sam` with no parameters will raise
   an exception instead of displaying a dialog.
 * The `times` keyword argument has been deprecated in the
@@ -146,6 +152,8 @@ Documentation
 * Edited docstring for `pvsystem.sapm` to remove DataFrame option for input
   `module`. The DataFrame option was never tested and would cause an error if
   used. (:issue:`785`)
+* Note warning about _TMY3.epw files retrieved from energyplus.net in docstring 
+  of `epw.read_epw`
 
 Removal of prior version deprecations
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/pvlib/atmosphere.py b/pvlib/atmosphere.py
index 6be7a33..aca7449 100644
--- a/pvlib/atmosphere.py
+++ b/pvlib/atmosphere.py
@@ -320,8 +320,9 @@ def gueymard94_pw(temp_air, relative_humidity):
     return pw
 
 
-def first_solar_spectral_correction(pw, airmass_absolute, module_type=None,
-                                    coefficients=None):
+def first_solar_spectral_correction(pw, airmass_absolute,
+                                    module_type=None, coefficients=None,
+                                    min_pw=0.1, max_pw=8):
     r"""
     Spectral mismatch modifier based on precipitable water and absolute
     (pressure corrected) airmass.
@@ -364,6 +365,14 @@ def first_solar_spectral_correction(pw, airmass_absolute, module_type=None,
     airmass_absolute : array-like
         absolute (pressure corrected) airmass.
 
+    min_pw : float, default 0.1
+        minimum atmospheric precipitable water (cm). A lower pw value will be
+        automatically set to this minimum value to avoid model divergence.
+
+    max_pw : float, default 8
+        maximum atmospheric precipitable water (cm). If a higher value is
+        encountered it will be set to np.nan to avoid model divergence.
+
     module_type : None or string, default None
         a string specifying a cell type. Can be lower or upper case
         letters. Admits values of 'cdte', 'monosi', 'xsi', 'multisi',
@@ -422,16 +431,18 @@ def first_solar_spectral_correction(pw, airmass_absolute, module_type=None,
     # *** Pwat ***
     # Replace Pwat Values below 0.1 cm with 0.1 cm to prevent model from
     # diverging"
-
-    if np.min(pw) < 0.1:
-        pw = np.maximum(pw, 0.1)
-        warn('Exceptionally low Pwat values replaced with 0.1 cm to prevent' +
-             ' model divergence')
+    pw = np.atleast_1d(pw)
+    pw = pw.astype('float64')
+    if np.min(pw) < min_pw:
+        pw = np.maximum(pw, min_pw)
+        warn('Exceptionally low pw values replaced with {0} cm to prevent '
+             'model divergence'.format(min_pw))
 
     # Warn user about Pwat data that is exceptionally high
-    if np.max(pw) > 8:
-        warn('Exceptionally high Pwat values. Check input data:' +
-             ' model may diverge in this range')
+    if np.max(pw) > max_pw:
+        pw[pw > max_pw] = np.nan
+        warn('Exceptionally high pw values replaced by np.nan: '
+             'check input data.')
 
     # *** AMa ***
     # Replace Extremely High AM with AM 10 to prevent model divergence
diff --git a/pvlib/iotools/epw.py b/pvlib/iotools/epw.py
index 4a714d0..3fe7e8a 100644
--- a/pvlib/iotools/epw.py
+++ b/pvlib/iotools/epw.py
@@ -67,9 +67,9 @@ def read_epw(filename, coerce_year=None):
     ===============   ======  =========================================
 
 
-    =============================       ==============================================================================================================================================================
+    =============================       ==================================================================================================================================================================================================================  # noqa: E501
     EPWData field                       description
-    =============================       ==============================================================================================================================================================
+    =============================       ==================================================================================================================================================================================================================
     index                               A pandas datetime index. NOTE, times are set to local standard time (daylight savings is not included). Days run from 0-23h to comply with PVLIB's convention
     year                                Year, from original EPW file. Can be overwritten using coerce function.
     month                               Month, from original EPW file
@@ -99,14 +99,14 @@ def read_epw(filename, coerce_year=None):
     ceiling_height                      Height of cloud base above local terrain (7777=unlimited), meter
     present_weather_observation         Indicator for remaining fields: If 0, then the observed weather codes are taken from the following field. If 9, then missing weather is assumed.
     present_weather_codes               Present weather code, see [1], chapter 2.9.1.28
-    precipitable_water                  Total precipitable water contained in a column of unit cross section from earth to top of atmosphere, cm
+    precipitable_water                  Total precipitable water contained in a column of unit cross section from earth to top of atmosphere, cm. Note that some old *_TMY3.epw files may have incorrect unit if it was retrieved from www.energyplus.net.
     aerosol_optical_depth               The broadband aerosol optical depth per unit of air mass due to extinction by aerosol component of atmosphere, unitless
     snow_depth                          Snow depth in centimeters on the day indicated, (999 = missing data)
     days_since_last_snowfall            Number of days since last snowfall (maximum value of 88, where 88 = 88 or greater days; 99 = missing data)
     albedo                              The ratio of reflected solar irradiance to global horizontal irradiance, unitless
     liquid_precipitation_depth          The amount of liquid precipitation observed at indicated time for the period indicated in the liquid precipitation quantity field, millimeter
     liquid_precipitation_quantity       The period of accumulation for the liquid precipitation depth field, hour
-    =============================       ==============================================================================================================================================================
+    =============================       ==================================================================================================================================================================================================================
 
     References
     ----------


Test patch:
diff --git a/pvlib/test/test_atmosphere.py b/pvlib/test/test_atmosphere.py
index ee4b17a..0bf7434 100644
--- a/pvlib/test/test_atmosphere.py
+++ b/pvlib/test/test_atmosphere.py
@@ -128,6 +128,27 @@ def test_first_solar_spectral_correction_ambiguous():
         atmosphere.first_solar_spectral_correction(1, 1)
 
 
+def test_first_solar_spectral_correction_range():
+    with pytest.warns(UserWarning, match='Exceptionally high pw values'):
+        out = atmosphere.first_solar_spectral_correction(np.array([.1, 3, 10]),
+                                                         np.array([1, 3, 5]),
+                                                         module_type='monosi')
+    expected = np.array([0.96080878, 1.03055092,        nan])
+    assert_allclose(out, expected, atol=1e-3)
+    with pytest.warns(UserWarning, match='Exceptionally high pw values'):
+        out = atmosphere.first_solar_spectral_correction(6, 1.5, max_pw=5,
+                                                         module_type='monosi')
+    with pytest.warns(UserWarning, match='Exceptionally low pw values'):
+        out = atmosphere.first_solar_spectral_correction(np.array([0, 3, 8]),
+                                                         np.array([1, 3, 5]),
+                                                         module_type='monosi')
+    expected = np.array([0.96080878, 1.03055092, 1.04932727])
+    assert_allclose(out, expected, atol=1e-3)
+    with pytest.warns(UserWarning, match='Exceptionally low pw values'):
+        out = atmosphere.first_solar_spectral_correction(0.2, 1.5, min_pw=1,
+                                                         module_type='monosi')
+
+
 def test_kasten96_lt():
     """Test Linke turbidity factor calculated from AOD, Pwat and AM"""
     amp = np.array([1, 3, 5])