Skip to content

Commit 495a1dc

Browse files
committed
Merge branch 'feature/static-risk-traj' into feature/interpolated-trajectories
2 parents 50cc9ee + bb08305 commit 495a1dc

File tree

2 files changed

+19
-31
lines changed

2 files changed

+19
-31
lines changed

climada/trajectories/test/test_static_risk_trajectory.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -221,18 +221,18 @@ def test_calc_npv_cash_flows_logic(mock_disc_rates):
221221
)
222222
start_date = datetime.date(2023, 1, 1)
223223

224-
# NPV Factor: (1 / (1 + rate)) ^ year_delta
224+
# NPV Factor: Product[ (1 / (1 + rate_i))]
225+
# For a constant rate or 0.01
225226
# 2023: (1/1.01)^0 = 1.0 -> 100
226-
# 2024: (1/1.02)^1 = 0.98039... -> 196.078...
227-
# 2025: (1/1.03)^2 = 0.94259... -> 282.778...
227+
# 2024: (1/1.01)^1 = 0.99099... -> 198.019...
228+
# 2025: (1/1.01)^2 = 0.98029... -> 294.088...
228229

229230
result = RiskTrajectory._calc_npv_cash_flows(
230231
cash_flows, start_date, mock_disc_rates
231232
)
232-
233233
assert result.iloc[0] == pytest.approx(100.0)
234-
assert result.iloc[1] == pytest.approx(200 / 1.02)
235-
assert result.iloc[2] == pytest.approx(300 / (1.03**2))
234+
assert result.iloc[1] == pytest.approx(200 / (1.02))
235+
assert result.iloc[2] == pytest.approx(300 * (1 / 1.02) * (1 / 1.03))
236236

237237

238238
def test_calc_npv_cash_flows_invalid_index(mock_disc_rates):

climada/trajectories/trajectory.py

Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -217,56 +217,44 @@ def _npv_group(group, disc):
217217

218218
@staticmethod
219219
def _calc_npv_cash_flows(
220-
cash_flows: pd.DataFrame | pd.Series,
220+
cash_flows: pd.Series,
221221
start_date: datetime.date,
222222
disc_rates: DiscRates | None = None,
223-
):
223+
) -> pd.Series:
224224
"""Apply discount rate to cash flows.
225225
226226
If it is defined, applies a discount rate `disc` to a given cash flow
227-
`cash_flows` assuming present year corresponds to `start_date`.
227+
`cash_flows` using `start_date` as the reference year.
228228
229229
Parameters
230230
----------
231231
cash_flows : pd.DataFrame
232232
The cash flow to apply the discount rate to.
233233
start_date : datetime.date
234234
The date representing the present.
235-
end_date : datetime.date, optional
236235
disc : DiscRates, optional
237-
The discount rate to apply.
236+
The discount rates to apply.
238237
239238
Returns
240239
-------
241240
242-
A dataframe (copy) of `cash_flows` where values are discounted according to `disc`.
241+
A Series (copy) of `cash_flows` where values are discounted according to `disc`.
243242
244243
"""
245244

246-
if not disc_rates:
245+
if disc_rates is None:
247246
return cash_flows
248247

249248
if not isinstance(cash_flows.index, (pd.PeriodIndex, pd.DatetimeIndex)):
250249
raise ValueError(
251250
"cash_flows must be a pandas Series with a PeriodIndex or DatetimeIndex"
252251
)
253252

254-
metric_df = cash_flows.to_frame(name="cash_flow") # type: ignore
255-
metric_df["year"] = metric_df.index.year
256-
257-
# Merge with the discount rates based on the year
258-
tmp = pd.Series(index=disc_rates.years, data=disc_rates.rates, name="rate")
259-
tmp = tmp.loc[tmp.index >= start_date.year]
260-
tmp = 1 / tmp.shift(1, fill_value=0).add(1).cumprod()
261-
tmp = tmp.to_frame()
262-
tmp["year"] = tmp.index
263-
metric_df = metric_df.merge(
264-
tmp,
265-
on="year",
266-
how="left",
253+
growth_factors = (
254+
pd.Series(disc_rates.rates, index=disc_rates.years)
255+
.loc[lambda x: x.index > start_date.year]
256+
.add(1)
257+
.cumprod()
267258
)
268-
269-
# Apply the discount factors to the cash flows
270-
271-
metric_df["npv_cash_flow"] = metric_df["cash_flow"] * metric_df["rate"]
272-
return metric_df["npv_cash_flow"].values
259+
discount_factors = 1 / cash_flows.index.year.map(growth_factors).fillna(1.0)
260+
return cash_flows.multiply(discount_factors, axis=0)

0 commit comments

Comments
 (0)