Skip to content

Commit 442329b

Browse files
Fix delta_kt_prime calculation for series with internal NaN values
The previous implementation of _delta_kt_prime_dirint only handled NaN at the very first and last positions of the series. For multi-day data with nighttime NaN gaps, the edge positions adjacent to internal NaN boundaries had their delta values incorrectly halved (the 0.5 factor was applied even when only one neighbor was valid). Replace the manual NaN-filling approach with pd.DataFrame.mean(axis=1), which naturally skips NaN values. This correctly implements both Perez eqn 2 (average of both deltas when two neighbors exist) and eqn 3 (single delta when only one neighbor exists). Fixes #1847
1 parent 010cff7 commit 442329b

File tree

3 files changed

+45
-8
lines changed

3 files changed

+45
-8
lines changed

docs/sphinx/source/whatsnew/v0.15.1.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ Deprecations
1414

1515
Bug fixes
1616
~~~~~~~~~
17+
* Fix incorrect ``delta_kt_prime`` calculation in
18+
:py:func:`pvlib.irradiance.dirint` for series containing internal NaN
19+
values (e.g. multi-day data with nighttime gaps). Edge positions with
20+
only one valid neighbor now correctly use that single delta value
21+
(Perez eqn 3) instead of halving it.
22+
(:issue:`1847`, :pull:`xxxx`)
1723
* Fix a division-by-zero condition in
1824
:py:func:`pvlib.transformer.simple_efficiency` when ``load_loss = 0``.
1925
(:issue:`2645`, :pull:`2646`)

pvlib/irradiance.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2026,16 +2026,15 @@ def _delta_kt_prime_dirint(kt_prime, use_delta_kt_prime, times):
20262026
for use with :py:func:`_dirint_bins`.
20272027
"""
20282028
if use_delta_kt_prime:
2029-
# Perez eqn 2
2029+
# Perez eqn 2 (both neighbors) and eqn 3 (one neighbor).
2030+
# mean(axis=1) skips NaN so that edge positions with only one
2031+
# valid neighbor return that single delta instead of halving it.
20302032
kt_next = kt_prime.shift(-1)
20312033
kt_previous = kt_prime.shift(1)
2032-
# replace nan with values that implement Perez Eq 3 for first and last
2033-
# positions. Use kt_previous and kt_next to handle series of length 1
2034-
kt_next.iloc[-1] = kt_previous.iloc[-1]
2035-
kt_previous.iloc[0] = kt_next.iloc[0]
2036-
delta_kt_prime = 0.5 * ((kt_prime - kt_next).abs().add(
2037-
(kt_prime - kt_previous).abs(),
2038-
fill_value=0))
2034+
delta_kt_prime = pd.DataFrame({
2035+
'next': (kt_prime - kt_next).abs(),
2036+
'prev': (kt_prime - kt_previous).abs(),
2037+
}).mean(axis=1)
20392038
else:
20402039
# do not change unless also modifying _dirint_bins
20412040
delta_kt_prime = pd.Series(-1, index=times)

tests/test_irradiance.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,38 @@ def test_dirint_no_delta_kt():
743743
np.array([861.9, 670.4]), 1)
744744

745745

746+
def test_delta_kt_prime_dirint_multiday():
747+
# GH 1847: _delta_kt_prime_dirint mishandled NaN boundaries between
748+
# days, halving the delta at edge positions instead of using the
749+
# single available neighbor value (Perez eqn 3).
750+
times = pd.date_range('2014-01-01T05', periods=15, freq='1h',
751+
tz='Etc/GMT+0')
752+
kt_prime = pd.Series(
753+
[np.nan, np.nan, np.nan,
754+
0.29458475, 0.21863506, 0.37650014,
755+
0.41238529, 0.23375275, 0.23363453,
756+
0.26348652, 0.25412631, 0.43794681,
757+
np.nan, np.nan, np.nan],
758+
index=times)
759+
result = irradiance._delta_kt_prime_dirint(kt_prime, True, times)
760+
# first valid value (index 3): only forward neighbor exists,
761+
# delta_kt_prime == |kt[3] - kt[4]| (Perez eqn 3)
762+
expected_first = abs(kt_prime.iloc[3] - kt_prime.iloc[4])
763+
assert_almost_equal(result.iloc[3], expected_first)
764+
# last valid value (index 11): only backward neighbor exists,
765+
# delta_kt_prime == |kt[11] - kt[10]| (Perez eqn 3)
766+
expected_last = abs(kt_prime.iloc[11] - kt_prime.iloc[10])
767+
assert_almost_equal(result.iloc[11], expected_last)
768+
# interior valid value (index 6): both neighbors exist,
769+
# delta_kt_prime == mean of both deltas (Perez eqn 2)
770+
expected_mid = 0.5 * (abs(kt_prime.iloc[6] - kt_prime.iloc[7])
771+
+ abs(kt_prime.iloc[6] - kt_prime.iloc[5]))
772+
assert_almost_equal(result.iloc[6], expected_mid)
773+
# NaN positions should remain NaN
774+
assert np.isnan(result.iloc[0])
775+
assert np.isnan(result.iloc[-1])
776+
777+
746778
def test_dirint_coeffs():
747779
coeffs = irradiance._get_dirint_coeffs()
748780
assert coeffs[0, 0, 0, 0] == 0.385230

0 commit comments

Comments
 (0)