From 22dfb2ced1886ac93a9019279183603351ba8e7f Mon Sep 17 00:00:00 2001 From: Travis Oliphant Date: Fri, 4 Apr 2008 06:11:24 +0000 Subject: [PATCH 01/65] Add fromregex function (needs more testing) and some simple spreadsheet-like financial calculations. --- numpy/lib/financial.py | 151 ++++++++++++++++++++++++++++++ numpy/lib/tests/test_financial.py | 34 +++++++ 2 files changed, 185 insertions(+) create mode 100644 numpy/lib/financial.py create mode 100644 numpy/lib/tests/test_financial.py diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py new file mode 100644 index 0000000..885fffe --- /dev/null +++ b/numpy/lib/financial.py @@ -0,0 +1,151 @@ +# Some simple financial calculations +from numpy import log, where +import numpy as np + +__all__ = ['fv', 'pmt', 'nper', 'ipmt', 'ppmt', 'pv', 'rate', 'irr', 'npv'] + +_when_to_num = {'end':0, 'begin':1, + 'e':0, 'b':1, + 0:0, 1:1, + 'beginning':1, + 'start':1, + 'finish':0} + +eqstr = """ + + Parameters + ---------- + rate : + Rate of interest (per period) + nper : + Number of compounding periods + pmt : + Payment + pv : + Present value + fv : + Future value + when : + When payments are due ('begin' (1) or 'end' (0)) + + nper / (1 + rate*when) \ / nper \ + fv + pv*(1+rate) + pmt*|-------------------|*| (1+rate) - 1 | = 0 + \ rate / \ / + + fv + pv + pmt * nper = 0 (when rate == 0) +""" + +def fv(rate, nper, pmt, pv, when='end'): + """future value computed by solving the equation + + %s + """ % eqstr + when = _when_to_num[when] + temp = (1+rate)**nper + fact = where(rate==0.0, nper, (1+rate*when)*(temp-1)/rate) + return -(pv*temp + pmt*fact) + +def pmt(rate, nper, pv, fv=0, when='end'): + """Payment computed by solving the equation + + %s + """ % eqstr + when = _when_to_num[when] + temp = (1+rate)**nper + fact = where(rate==0.0, nper, (1+rate*when)*(temp-1)/rate) + return -(fv + pv*temp) / fact + +def nper(rate, pmt, pv, fv=0, when='end'): + """Number of periods found by solving the equation + + %s + """ % eqstr + when = _when_to_num[when] + try: + z = pmt*(1.0+rate*when)/rate + except ZeroDivisionError: + z = 0.0 + A = -(fv + pv)/(pmt+0.0) + B = (log(fv-z) - log(pv-z))/log(1.0+rate) + return where(rate==0.0, A, B) + 0.0 + +def ipmt(rate, per, nper, pv, fv=0.0, when='end'): + raise NotImplementedError + + +def ppmt(rate, per, nper, pv, fv=0.0, when='end'): + raise NotImplementedError + +def pv(rate, nper, pmt, fv=0.0, when='end'): + """Number of periods found by solving the equation + + %s + """ % eqstr + when = _when_to_num[when] + temp = (1+rate)**nper + fact = where(rate == 0.0, nper, (1+rate*when)*(temp-1)/rate) + return -(fv + pmt*fact)/temp + +# Computed with Sage +# (y + (r + 1)^n*x + p*((r + 1)^n - 1)*(r*w + 1)/r)/(n*(r + 1)^(n - 1)*x - p*((r + 1)^n - 1)*(r*w + 1)/r^2 + n*p*(r + 1)^(n - 1)*(r*w + 1)/r + p*((r + 1)^n - 1)*w/r) + +def _g_div_gp(r, n, p, x, y, w): + t1 = (r+1)**n + t2 = (r+1)**(n-1) + return (y + t1*x + p*(t1 - 1)*(r*w + 1)/r)/(n*t2*x - p*(t1 - 1)*(r*w + 1)/(r**2) + n*p*t2*(r*w + 1)/r + p*(t1 - 1)*w/r) + +# Use Newton's iteration until the change is less than 1e-6 +# for all values or a maximum of 100 iterations is reached. +# Newton's rule is +# r_{n+1} = r_{n} - g(r_n)/g'(r_n) +# where +# g(r) is the formula +# g'(r) is the derivative with respect to r. +def rate(nper, pmt, pv, fv, when='end', guess=0.10, tol=1e-6, maxiter=100): + """Number of periods found by solving the equation + + %s + """ % eqstr + when = _when_to_num[when] + rn = guess + iter = 0 + close = False + while (iter < maxiter) and not close: + rnp1 = rn - _g_div_gp(rn, nper, pmt, pv, fv, when) + diff = abs(rnp1-rn) + close = np.all(diff 0) & (res.real <= 1) + res = res[mask].real + if res.size == 0: + return np.nan + rate = 1.0/res - 1 + if rate.size == 1: + rate = rate.item() + return rate + +def npv(rate, values): + """Net Present Value + + sum ( values_k / (1+rate)**k, k = 1..n) + """ + values = np.asarray(values) + return (values / (1+rate)**np.arange(1,len(values)+1)).sum(axis=0) + + diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py new file mode 100644 index 0000000..bf0b4df --- /dev/null +++ b/numpy/lib/tests/test_financial.py @@ -0,0 +1,34 @@ +""" +from numpy.lib.financial import * + +>>> rate(10,0,-3500,10000) +0.11069085371426901 + +>>> irr([-150000, 15000, 25000, 35000, 45000, 60000]) +0.052432888859414106 + +>>> pv(0.07,20,12000,0) +-127128.17094619398 + +>>> fv(0.075, 20, -2000,0,0) +86609.362673042924 + +>>> pmt(0.08/12,5*12,15000) +-304.14591432620773 + +>>> nper(0.075,-2000,0,100000.) +21.544944197323336 + +>>> npv(0.05,[-15000,1500,2500,3500,4500,6000]) +117.04271900089589 + +""" + +from numpy.testing import * +import numpy as np + +class TestDocs(NumpyTestCase): + def check_doctests(self): return self.rundocs() + +if __name__ == "__main__": + NumpyTest().run() From 64018ed32fc8315728ecd7389253436bdbcd9704 Mon Sep 17 00:00:00 2001 From: Travis Oliphant Date: Fri, 4 Apr 2008 06:37:45 +0000 Subject: [PATCH 02/65] Add modified internal rate of return calculation which is more conservative and takes into account re-investing profits and expense of financing losses. --- numpy/lib/financial.py | 27 ++++++++++++++++++++++++++- numpy/lib/tests/test_financial.py | 5 +++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index 885fffe..0ff46b2 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -1,8 +1,10 @@ # Some simple financial calculations +# patterned after spreadsheet computations. from numpy import log, where import numpy as np -__all__ = ['fv', 'pmt', 'nper', 'ipmt', 'ppmt', 'pv', 'rate', 'irr', 'npv'] +__all__ = ['fv', 'pmt', 'nper', 'ipmt', 'ppmt', 'pv', 'rate', 'irr', 'npv', + 'mirr'] _when_to_num = {'end':0, 'begin':1, 'e':0, 'b':1, @@ -148,4 +150,27 @@ def npv(rate, values): values = np.asarray(values) return (values / (1+rate)**np.arange(1,len(values)+1)).sum(axis=0) +def mirr(values, finance_rate, reinvest_rate): + """Modified internal rate of return + + Parameters + ---------- + values: + Cash flows (must contain at least one positive and one negative value) + or nan is returned. + finance_rate : + Interest rate paid on the cash flows + reinvest_rate : + Interest rate received on the cash flows upon reinvestment + """ + values = np.asarray(values) + pos = values > 0 + neg = values < 0 + if not (pos.size > 0 and neg.size > 0): + return np.nan + + n = pos.size + neg.size + numer = -npv(reinvest_rate, values[pos])*((1+reinvest_rate)**n) + denom = npv(finance_rate, values[neg])*(1+finance_rate) + return (numer / denom)**(1.0/(n-1)) - 1 diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py index bf0b4df..4f7bb32 100644 --- a/numpy/lib/tests/test_financial.py +++ b/numpy/lib/tests/test_financial.py @@ -22,6 +22,11 @@ >>> npv(0.05,[-15000,1500,2500,3500,4500,6000]) 117.04271900089589 +>>> mirr([-4500,-800,800,800,600,600,800,800,700,3000],0.08,0.055) +0.066471183500200537 + +>>> mirr([-120000,39000,30000,21000,37000,46000],0.10,0.12) +0.13439316981387006 """ from numpy.testing import * From b738fb19d7238da6b40bbe07aab6df737b34b171 Mon Sep 17 00:00:00 2001 From: Travis Oliphant Date: Tue, 8 Apr 2008 04:56:12 +0000 Subject: [PATCH 03/65] Add docs and examples for financial functions. --- numpy/lib/financial.py | 128 ++++++++++++++++++++++-------- numpy/lib/tests/test_financial.py | 2 +- 2 files changed, 95 insertions(+), 35 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index 0ff46b2..bac276b 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -1,10 +1,9 @@ # Some simple financial calculations # patterned after spreadsheet computations. -from numpy import log, where import numpy as np -__all__ = ['fv', 'pmt', 'nper', 'ipmt', 'ppmt', 'pv', 'rate', 'irr', 'npv', - 'mirr'] +__all__ = ['fv', 'pmt', 'nper', 'ipmt', 'ppmt', 'pv', 'rate', + 'irr', 'npv', 'mirr'] _when_to_num = {'end':0, 'begin':1, 'e':0, 'b':1, @@ -15,6 +14,14 @@ eqstr = """ + nper / (1 + rate*when) \ / nper \ + fv + pv*(1+rate) + pmt*|-------------------|*| (1+rate) - 1 | = 0 + \ rate / \ / + + fv + pv + pmt * nper = 0 (when rate == 0) + +where (all can be scalars or sequences) + Parameters ---------- rate : @@ -26,50 +33,101 @@ pv : Present value fv : - Future value + Future value when : When payments are due ('begin' (1) or 'end' (0)) - nper / (1 + rate*when) \ / nper \ - fv + pv*(1+rate) + pmt*|-------------------|*| (1+rate) - 1 | = 0 - \ rate / \ / - - fv + pv + pmt * nper = 0 (when rate == 0) """ +def _convert_when(when): + try: + return _when_to_num[when] + except KeyError: + return [_when_to_num[x] for x in when] + + def fv(rate, nper, pmt, pv, when='end'): """future value computed by solving the equation - - %s - """ % eqstr - when = _when_to_num[when] + """ + when = _convert_when(when) + rate, nper, pmt, pv, when = map(np.asarray, [rate, nper, pmt, pv, when]) temp = (1+rate)**nper - fact = where(rate==0.0, nper, (1+rate*when)*(temp-1)/rate) + miter = np.broadcast(rate, nper, pmt, pv, when) + zer = np.zeros(miter.shape) + fact = np.where(rate==zer, nper+zer, (1+rate*when)*(temp-1)/rate+zer) return -(pv*temp + pmt*fact) +fv.__doc__ += eqstr + """ +Example +-------- + +What is the future value after 10 years of saving $100 now, with + an additional monthly savings of $100. Assume the interest rate is + 5% (annually) compounded monthly? + +>>> fv(0.05/12, 10*12, -100, -100) +15692.928894335748 + +By convention, the negative sign represents cash flow out (i.e. money not + available today). Thus, saving $100 a month at 5% annual interest leads + to $15,692.93 available to spend in 10 years. +""" def pmt(rate, nper, pv, fv=0, when='end'): """Payment computed by solving the equation - - %s - """ % eqstr - when = _when_to_num[when] + """ + when = _convert_when(when) + rate, nper, pv, fv, when = map(np.asarray, [rate, nper, pv, fv, when]) temp = (1+rate)**nper - fact = where(rate==0.0, nper, (1+rate*when)*(temp-1)/rate) + miter = np.broadcast(rate, nper, pv, fv, when) + zer = np.zeros(miter.shape) + fact = np.where(rate==zer, nper+zer, (1+rate*when)*(temp-1)/rate+zer) return -(fv + pv*temp) / fact +pmt.__doc__ += eqstr + """ +Example +------- + +What would the monthly payment need to be to pay off a $200,000 loan in 15 + years at an annual interest rate of 7.5%? + +>>> pmt(0.075/12, 12*15, 200000) +-1854.0247200054619 + +In order to pay-off (i.e. have a future-value of 0) the $200,000 obtained + today, a monthly payment of $1,854.02 would be required. +""" def nper(rate, pmt, pv, fv=0, when='end'): """Number of periods found by solving the equation - - %s - """ % eqstr - when = _when_to_num[when] + """ + when = _convert_when(when) + rate, pmt, pv, fv, when = map(np.asarray, [rate, pmt, pv, fv, when]) try: z = pmt*(1.0+rate*when)/rate except ZeroDivisionError: z = 0.0 A = -(fv + pv)/(pmt+0.0) - B = (log(fv-z) - log(pv-z))/log(1.0+rate) - return where(rate==0.0, A, B) + 0.0 + B = np.log((-fv+z) / (pv+z))/np.log(1.0+rate) + miter = np.broadcast(rate, pmt, pv, fv, when) + zer = np.zeros(miter.shape) + return np.where(rate==zer, A+zer, B+zer) + 0.0 +nper.__doc__ += eqstr + """ +Example +------- + +If you only had $150 to spend as payment, how long would it take to pay-off + a loan of $8,000 at 7% annual interest? + +>>> nper(0.07/12, -150, 8000) +64.073348770661852 + +So, over 64 months would be required to pay off the loan. + +The same analysis could be done with several different interest rates and/or + payments and/or total amounts to produce an entire table. + +>>> nper(*(ogrid[0.06/12:0.071/12:0.005/12, -100:-201:50, 6000:8000:1000])) + +""" def ipmt(rate, per, nper, pv, fv=0.0, when='end'): raise NotImplementedError @@ -80,13 +138,15 @@ def ppmt(rate, per, nper, pv, fv=0.0, when='end'): def pv(rate, nper, pmt, fv=0.0, when='end'): """Number of periods found by solving the equation - - %s - """ % eqstr - when = _when_to_num[when] + """ + when = _convert_when(when) + rate, nper, pmt, fv, when = map(np.asarray, [rate, nper, pmt, fv, when]) temp = (1+rate)**nper - fact = where(rate == 0.0, nper, (1+rate*when)*(temp-1)/rate) + miter = np.broadcast(rate, nper, pmt, fv, when) + zer = np.zeros(miter.shape) + fact = np.where(rate == zer, nper+zer, (1+rate*when)*(temp-1)/rate+zer) return -(fv + pmt*fact)/temp +pv.__doc__ += eqstr # Computed with Sage # (y + (r + 1)^n*x + p*((r + 1)^n - 1)*(r*w + 1)/r)/(n*(r + 1)^(n - 1)*x - p*((r + 1)^n - 1)*(r*w + 1)/r^2 + n*p*(r + 1)^(n - 1)*(r*w + 1)/r + p*((r + 1)^n - 1)*w/r) @@ -105,10 +165,9 @@ def _g_div_gp(r, n, p, x, y, w): # g'(r) is the derivative with respect to r. def rate(nper, pmt, pv, fv, when='end', guess=0.10, tol=1e-6, maxiter=100): """Number of periods found by solving the equation - - %s - """ % eqstr - when = _when_to_num[when] + """ + when = _convert_when(when) + nper, pmt, pv, fv, when = map(np.asarray, [nper, pmt, pv, fv, when]) rn = guess iter = 0 close = False @@ -123,6 +182,7 @@ def rate(nper, pmt, pv, fv, when='end', guess=0.10, tol=1e-6, maxiter=100): return np.nan + rn else: return rn +rate.__doc__ += eqstr def irr(values): """Internal Rate of Return diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py index 4f7bb32..c3153ff 100644 --- a/numpy/lib/tests/test_financial.py +++ b/numpy/lib/tests/test_financial.py @@ -1,5 +1,5 @@ """ -from numpy.lib.financial import * +from numpy import * >>> rate(10,0,-3500,10000) 0.11069085371426901 From c1915c36a9ba4b123750f7f027413f9a438f7d2d Mon Sep 17 00:00:00 2001 From: Travis Oliphant Date: Tue, 8 Apr 2008 05:02:03 +0000 Subject: [PATCH 04/65] Improve comments. --- numpy/lib/financial.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index bac276b..c5d6d27 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -1,5 +1,10 @@ # Some simple financial calculations # patterned after spreadsheet computations. + +# There is some complexity in each function +# so that the functions behave like ufuncs with +# broadcasting and being able to be called with scalars +# or arrays (or other sequences). import numpy as np __all__ = ['fv', 'pmt', 'nper', 'ipmt', 'ppmt', 'pv', 'rate', @@ -125,8 +130,12 @@ def nper(rate, pmt, pv, fv=0, when='end'): The same analysis could be done with several different interest rates and/or payments and/or total amounts to produce an entire table. ->>> nper(*(ogrid[0.06/12:0.071/12:0.005/12, -100:-201:50, 6000:8000:1000])) +>>> nper(*(ogrid[0.06/12:0.071/12:0.01/12, -200:-99:100, 6000:7001:1000])) +array([[[ 32.58497782, 38.57048452], + [ 71.51317802, 86.37179563]], + [[ 33.07413144, 39.26244268], + [ 74.06368256, 90.22989997]]]) """ def ipmt(rate, per, nper, pv, fv=0.0, when='end'): From ac6e06b6114be3be9b5773bbf02ce7a5a1ce0ad5 Mon Sep 17 00:00:00 2001 From: Travis Oliphant Date: Tue, 8 Apr 2008 20:43:07 +0000 Subject: [PATCH 05/65] Fix doc-tests for financial.py so they don't rely on floating-point exactness. Start filling in final function. --- numpy/lib/financial.py | 6 +++-- numpy/lib/tests/test_financial.py | 38 +++++++++++++++---------------- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index c5d6d27..5bb4a3a 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -139,11 +139,13 @@ def nper(rate, pmt, pv, fv=0, when='end'): """ def ipmt(rate, per, nper, pv, fv=0.0, when='end'): + total = pmt(rate, nper, pv, fv, when) + # Now, compute the nth step in the amortization raise NotImplementedError - def ppmt(rate, per, nper, pv, fv=0.0, when='end'): - raise NotImplementedError + total = pmt(rate, nper, pv, fv, when) + return total - ipmt(rate, per, nper, pv, fv, when) def pv(rate, nper, pmt, fv=0.0, when='end'): """Number of periods found by solving the equation diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py index c3153ff..ee38c10 100644 --- a/numpy/lib/tests/test_financial.py +++ b/numpy/lib/tests/test_financial.py @@ -1,32 +1,32 @@ """ -from numpy import * +>>> from numpy import rate, irr, pv, fv, pmt, nper, npv, mirr, round ->>> rate(10,0,-3500,10000) -0.11069085371426901 +>>> round(rate(10,0,-3500,10000),4)==0.1107 +True ->>> irr([-150000, 15000, 25000, 35000, 45000, 60000]) -0.052432888859414106 +>>> round(irr([-150000, 15000, 25000, 35000, 45000, 60000]),4)==0.0524 +True ->>> pv(0.07,20,12000,0) --127128.17094619398 +>>> round(pv(0.07,20,12000,0),2) == -127128.17 +True ->>> fv(0.075, 20, -2000,0,0) -86609.362673042924 +>>> round(fv(0.075, 20, -2000,0,0),2) == 86609.36 +True ->>> pmt(0.08/12,5*12,15000) --304.14591432620773 +>>> round(pmt(0.08/12,5*12,15000),3) == -304.146 +True ->>> nper(0.075,-2000,0,100000.) -21.544944197323336 +>>> round(nper(0.075,-2000,0,100000.),2) == 21.54 +True ->>> npv(0.05,[-15000,1500,2500,3500,4500,6000]) -117.04271900089589 +>>> round(npv(0.05,[-15000,1500,2500,3500,4500,6000]),2) == 117.04 +True ->>> mirr([-4500,-800,800,800,600,600,800,800,700,3000],0.08,0.055) -0.066471183500200537 +>>> round(mirr([-4500,-800,800,800,600,600,800,800,700,3000],0.08,0.055),4) == 0.0665 +True ->>> mirr([-120000,39000,30000,21000,37000,46000],0.10,0.12) -0.13439316981387006 +>>> round(mirr([-120000,39000,30000,21000,37000,46000],0.10,0.12),4)==0.1344 +True """ from numpy.testing import * From 77806df0c4a5d18ebd2600e906a6642d7924b98a Mon Sep 17 00:00:00 2001 From: Jarrod Millman Date: Sun, 20 Apr 2008 11:49:35 +0000 Subject: [PATCH 06/65] ran reindent in preparation for the 1.1 release --- numpy/lib/financial.py | 54 +++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index 5bb4a3a..2a75128 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -2,12 +2,12 @@ # patterned after spreadsheet computations. # There is some complexity in each function -# so that the functions behave like ufuncs with +# so that the functions behave like ufuncs with # broadcasting and being able to be called with scalars -# or arrays (or other sequences). +# or arrays (or other sequences). import numpy as np -__all__ = ['fv', 'pmt', 'nper', 'ipmt', 'ppmt', 'pv', 'rate', +__all__ = ['fv', 'pmt', 'nper', 'ipmt', 'ppmt', 'pv', 'rate', 'irr', 'npv', 'mirr'] _when_to_num = {'end':0, 'begin':1, @@ -19,7 +19,7 @@ eqstr = """ - nper / (1 + rate*when) \ / nper \ + nper / (1 + rate*when) \ / nper \ fv + pv*(1+rate) + pmt*|-------------------|*| (1+rate) - 1 | = 0 \ rate / \ / @@ -28,23 +28,23 @@ where (all can be scalars or sequences) Parameters - ---------- - rate : + ---------- + rate : Rate of interest (per period) - nper : + nper : Number of compounding periods - pmt : - Payment + pmt : + Payment pv : Present value fv : - Future value - when : + Future value + when : When payments are due ('begin' (1) or 'end' (0)) - + """ -def _convert_when(when): +def _convert_when(when): try: return _when_to_num[when] except KeyError: @@ -85,19 +85,19 @@ def pmt(rate, nper, pv, fv=0, when='end'): temp = (1+rate)**nper miter = np.broadcast(rate, nper, pv, fv, when) zer = np.zeros(miter.shape) - fact = np.where(rate==zer, nper+zer, (1+rate*when)*(temp-1)/rate+zer) + fact = np.where(rate==zer, nper+zer, (1+rate*when)*(temp-1)/rate+zer) return -(fv + pv*temp) / fact pmt.__doc__ += eqstr + """ Example ------- -What would the monthly payment need to be to pay off a $200,000 loan in 15 +What would the monthly payment need to be to pay off a $200,000 loan in 15 years at an annual interest rate of 7.5%? >>> pmt(0.075/12, 12*15, 200000) -1854.0247200054619 -In order to pay-off (i.e. have a future-value of 0) the $200,000 obtained +In order to pay-off (i.e. have a future-value of 0) the $200,000 obtained today, a monthly payment of $1,854.02 would be required. """ @@ -160,19 +160,19 @@ def pv(rate, nper, pmt, fv=0.0, when='end'): pv.__doc__ += eqstr # Computed with Sage -# (y + (r + 1)^n*x + p*((r + 1)^n - 1)*(r*w + 1)/r)/(n*(r + 1)^(n - 1)*x - p*((r + 1)^n - 1)*(r*w + 1)/r^2 + n*p*(r + 1)^(n - 1)*(r*w + 1)/r + p*((r + 1)^n - 1)*w/r) +# (y + (r + 1)^n*x + p*((r + 1)^n - 1)*(r*w + 1)/r)/(n*(r + 1)^(n - 1)*x - p*((r + 1)^n - 1)*(r*w + 1)/r^2 + n*p*(r + 1)^(n - 1)*(r*w + 1)/r + p*((r + 1)^n - 1)*w/r) def _g_div_gp(r, n, p, x, y, w): t1 = (r+1)**n t2 = (r+1)**(n-1) return (y + t1*x + p*(t1 - 1)*(r*w + 1)/r)/(n*t2*x - p*(t1 - 1)*(r*w + 1)/(r**2) + n*p*t2*(r*w + 1)/r + p*(t1 - 1)*w/r) -# Use Newton's iteration until the change is less than 1e-6 +# Use Newton's iteration until the change is less than 1e-6 # for all values or a maximum of 100 iterations is reached. -# Newton's rule is -# r_{n+1} = r_{n} - g(r_n)/g'(r_n) +# Newton's rule is +# r_{n+1} = r_{n} - g(r_n)/g'(r_n) # where -# g(r) is the formula +# g(r) is the formula # g'(r) is the derivative with respect to r. def rate(nper, pmt, pv, fv, when='end', guess=0.10, tol=1e-6, maxiter=100): """Number of periods found by solving the equation @@ -194,7 +194,7 @@ def rate(nper, pmt, pv, fv, when='end', guess=0.10, tol=1e-6, maxiter=100): else: return rn rate.__doc__ += eqstr - + def irr(values): """Internal Rate of Return @@ -212,7 +212,7 @@ def irr(values): if rate.size == 1: rate = rate.item() return rate - + def npv(rate, values): """Net Present Value @@ -223,15 +223,15 @@ def npv(rate, values): def mirr(values, finance_rate, reinvest_rate): """Modified internal rate of return - + Parameters ---------- values: Cash flows (must contain at least one positive and one negative value) or nan is returned. - finance_rate : + finance_rate : Interest rate paid on the cash flows - reinvest_rate : + reinvest_rate : Interest rate received on the cash flows upon reinvestment """ @@ -240,7 +240,7 @@ def mirr(values, finance_rate, reinvest_rate): neg = values < 0 if not (pos.size > 0 and neg.size > 0): return np.nan - + n = pos.size + neg.size numer = -npv(reinvest_rate, values[pos])*((1+reinvest_rate)**n) denom = npv(finance_rate, values[neg])*(1+finance_rate) From ebc04cd2ba0acf355d6345e3dc51932fb1af8b0f Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Mon, 19 May 2008 10:45:14 +0000 Subject: [PATCH 07/65] Merge documentation changes from wiki. --- numpy/lib/financial.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index 2a75128..a3552eb 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -88,8 +88,8 @@ def pmt(rate, nper, pv, fv=0, when='end'): fact = np.where(rate==zer, nper+zer, (1+rate*when)*(temp-1)/rate+zer) return -(fv + pv*temp) / fact pmt.__doc__ += eqstr + """ -Example -------- +Examples +-------- What would the monthly payment need to be to pay off a $200,000 loan in 15 years at an annual interest rate of 7.5%? @@ -116,8 +116,8 @@ def nper(rate, pmt, pv, fv=0, when='end'): zer = np.zeros(miter.shape) return np.where(rate==zer, A+zer, B+zer) + 0.0 nper.__doc__ += eqstr + """ -Example -------- +Examples +-------- If you only had $150 to spend as payment, how long would it take to pay-off a loan of $8,000 at 7% annual interest? From 89ad9731233adffb9196febc751116a8f1aedb38 Mon Sep 17 00:00:00 2001 From: Alan McIntyre Date: Tue, 17 Jun 2008 00:23:20 +0000 Subject: [PATCH 08/65] Switched to use nose to run tests. Added test and bench functions to all modules. --- numpy/lib/tests/test_financial.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py index ee38c10..df5a0fb 100644 --- a/numpy/lib/tests/test_financial.py +++ b/numpy/lib/tests/test_financial.py @@ -32,8 +32,9 @@ from numpy.testing import * import numpy as np -class TestDocs(NumpyTestCase): - def check_doctests(self): return self.rundocs() +def test(): + import doctest + doctest.testmod() if __name__ == "__main__": - NumpyTest().run() + nose.run(argv=['', __file__]) From ac9f99fbd964553eddac6ce035daa3395ff7c868 Mon Sep 17 00:00:00 2001 From: Alan McIntyre Date: Sat, 21 Jun 2008 15:50:17 +0000 Subject: [PATCH 09/65] Restore old test framework classes. Added numpy.testing.run_module_suite to simplify "if __name__ == '__main__'" boilerplate code in test modules. Removed numpy/testing/pkgtester.py since it just consisted of an import statement after porting SciPy r4424. Allow numpy.*.test() to accept the old keyword arguments (but issue a deprecation warning when old arguments are seen). numpy.*.test() returns a test result object as before. Fixed typo in distutils doc. --- numpy/lib/tests/test_financial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py index df5a0fb..e0f2022 100644 --- a/numpy/lib/tests/test_financial.py +++ b/numpy/lib/tests/test_financial.py @@ -37,4 +37,4 @@ def test(): doctest.testmod() if __name__ == "__main__": - nose.run(argv=['', __file__]) + run_module_suite() From d89273fe5e165e551b34d2682f80eb9cfb153b91 Mon Sep 17 00:00:00 2001 From: Alan McIntyre Date: Thu, 3 Jul 2008 03:57:29 +0000 Subject: [PATCH 10/65] Remove uses of set_package_path, set_local_path, restore_path. Clean up and (somewhat) standardize test module imports. Removed unneeded reload calls. --- numpy/lib/tests/test_financial.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py index e0f2022..1ffdabc 100644 --- a/numpy/lib/tests/test_financial.py +++ b/numpy/lib/tests/test_financial.py @@ -1,31 +1,29 @@ """ ->>> from numpy import rate, irr, pv, fv, pmt, nper, npv, mirr, round - ->>> round(rate(10,0,-3500,10000),4)==0.1107 +>>> np.round(np.rate(10,0,-3500,10000),4)==0.1107 True ->>> round(irr([-150000, 15000, 25000, 35000, 45000, 60000]),4)==0.0524 +>>> np.round(np.irr([-150000, 15000, 25000, 35000, 45000, 60000]),4)==0.0524 True ->>> round(pv(0.07,20,12000,0),2) == -127128.17 +>>> np.round(np.pv(0.07,20,12000,0),2) == -127128.17 True ->>> round(fv(0.075, 20, -2000,0,0),2) == 86609.36 +>>> np.round(np.fv(0.075, 20, -2000,0,0),2) == 86609.36 True ->>> round(pmt(0.08/12,5*12,15000),3) == -304.146 +>>> np.round(np.pmt(0.08/12,5*12,15000),3) == -304.146 True ->>> round(nper(0.075,-2000,0,100000.),2) == 21.54 +>>> np.round(np.nper(0.075,-2000,0,100000.),2) == 21.54 True ->>> round(npv(0.05,[-15000,1500,2500,3500,4500,6000]),2) == 117.04 +>>> np.round(np.npv(0.05,[-15000,1500,2500,3500,4500,6000]),2) == 117.04 True ->>> round(mirr([-4500,-800,800,800,600,600,800,800,700,3000],0.08,0.055),4) == 0.0665 +>>> np.round(np.mirr([-4500,-800,800,800,600,600,800,800,700,3000],0.08,0.055),4) == 0.0665 True ->>> round(mirr([-120000,39000,30000,21000,37000,46000],0.10,0.12),4)==0.1344 +>>> np.round(np.mirr([-120000,39000,30000,21000,37000,46000],0.10,0.12),4)==0.1344 True """ From a56484b07ef3e44046e7836713d6d4929433ee50 Mon Sep 17 00:00:00 2001 From: Alan McIntyre Date: Sat, 5 Jul 2008 14:26:16 +0000 Subject: [PATCH 11/65] Use the implicit "import numpy as np" made available to all doctests instead of explicit imports or dependency on the local scope where the doctest is defined.. --- numpy/lib/financial.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index a3552eb..9c5d275 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -69,7 +69,7 @@ def fv(rate, nper, pmt, pv, when='end'): an additional monthly savings of $100. Assume the interest rate is 5% (annually) compounded monthly? ->>> fv(0.05/12, 10*12, -100, -100) +>>> np.fv(0.05/12, 10*12, -100, -100) 15692.928894335748 By convention, the negative sign represents cash flow out (i.e. money not @@ -94,7 +94,7 @@ def pmt(rate, nper, pv, fv=0, when='end'): What would the monthly payment need to be to pay off a $200,000 loan in 15 years at an annual interest rate of 7.5%? ->>> pmt(0.075/12, 12*15, 200000) +>>> np.pmt(0.075/12, 12*15, 200000) -1854.0247200054619 In order to pay-off (i.e. have a future-value of 0) the $200,000 obtained @@ -122,7 +122,7 @@ def nper(rate, pmt, pv, fv=0, when='end'): If you only had $150 to spend as payment, how long would it take to pay-off a loan of $8,000 at 7% annual interest? ->>> nper(0.07/12, -150, 8000) +>>> np.nper(0.07/12, -150, 8000) 64.073348770661852 So, over 64 months would be required to pay off the loan. @@ -130,7 +130,7 @@ def nper(rate, pmt, pv, fv=0, when='end'): The same analysis could be done with several different interest rates and/or payments and/or total amounts to produce an entire table. ->>> nper(*(ogrid[0.06/12:0.071/12:0.01/12, -200:-99:100, 6000:7001:1000])) +>>> np.nper(*(np.ogrid[0.06/12:0.071/12:0.01/12, -200:-99:100, 6000:7001:1000])) array([[[ 32.58497782, 38.57048452], [ 71.51317802, 86.37179563]], From 9f02f669e4bc89f04472cd56dabfff59834069bf Mon Sep 17 00:00:00 2001 From: Alan McIntyre Date: Thu, 24 Jul 2008 05:28:37 +0000 Subject: [PATCH 12/65] Added tests to improve coverage. Converted tests from doctests to unit tests. --- numpy/lib/tests/test_financial.py | 69 ++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 25 deletions(-) diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py index 1ffdabc..d664305 100644 --- a/numpy/lib/tests/test_financial.py +++ b/numpy/lib/tests/test_financial.py @@ -1,38 +1,57 @@ -""" ->>> np.round(np.rate(10,0,-3500,10000),4)==0.1107 -True +from numpy.testing import * +import numpy as np ->>> np.round(np.irr([-150000, 15000, 25000, 35000, 45000, 60000]),4)==0.0524 -True +class TestFinancial(TestCase): + def test_rate(self): + assert_almost_equal(np.rate(10,0,-3500,10000), + 0.1107, 4) ->>> np.round(np.pv(0.07,20,12000,0),2) == -127128.17 -True + def test_irr(self): + v = [-150000, 15000, 25000, 35000, 45000, 60000] + assert_almost_equal(np.irr(v), + 0.0524, 2) ->>> np.round(np.fv(0.075, 20, -2000,0,0),2) == 86609.36 -True + def test_pv(self): + assert_almost_equal(np.pv(0.07,20,12000,0), + -127128.17, 2) ->>> np.round(np.pmt(0.08/12,5*12,15000),3) == -304.146 -True + def test_fv(self): + assert_almost_equal(np.fv(0.075, 20, -2000,0,0), + 86609.36, 2) ->>> np.round(np.nper(0.075,-2000,0,100000.),2) == 21.54 -True + def test_pmt(self): + assert_almost_equal(np.pmt(0.08/12,5*12,15000), + -304.146, 3) ->>> np.round(np.npv(0.05,[-15000,1500,2500,3500,4500,6000]),2) == 117.04 -True + def test_nper(self): + assert_almost_equal(np.nper(0.075,-2000,0,100000.), + 21.54, 2) ->>> np.round(np.mirr([-4500,-800,800,800,600,600,800,800,700,3000],0.08,0.055),4) == 0.0665 -True + def test_nper(self): + assert_almost_equal(np.nper(0.0,-2000,0,100000.), + 50.0, 1) ->>> np.round(np.mirr([-120000,39000,30000,21000,37000,46000],0.10,0.12),4)==0.1344 -True -""" + def test_npv(self): + assert_almost_equal(np.npv(0.05,[-15000,1500,2500,3500,4500,6000]), + 117.04, 2) -from numpy.testing import * -import numpy as np + def test_mirr(self): + v1 = [-4500,-800,800,800,600,600,800,800,700,3000] + assert_almost_equal(np.mirr(v1,0.08,0.055), + 0.0665, 4) + + v2 = [-120000,39000,30000,21000,37000,46000] + assert_almost_equal(np.mirr(v2,0.10,0.12), + 0.1344, 4) + + +def test_unimplemented(): + # np.round(np.ppmt(0.1/12,1,60,55000),2) == 710.25 + assert_raises(NotImplementedError, np.ppmt, 0.1/12, 1, 60, 55000) + + # np.round(np.ipmt(0.1/12,1,24,2000),2) == 16.67 + assert_raises(NotImplementedError, np.ipmt, 0.1/12, 1, 24, 2000) -def test(): - import doctest - doctest.testmod() if __name__ == "__main__": run_module_suite() From 325e0c9269952328c7243c6b447491c654f7ca62 Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Tue, 5 Aug 2008 09:20:07 +0000 Subject: [PATCH 13/65] Merge from documentation editor. --- numpy/lib/financial.py | 189 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 184 insertions(+), 5 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index 9c5d275..a997cf6 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -52,7 +52,45 @@ def _convert_when(when): def fv(rate, nper, pmt, pv, when='end'): - """future value computed by solving the equation + """ + Compute the future value. + + Parameters + ---------- + rate : array-like + Rate of interest (per period) + nper : array-like + Number of compounding periods + pmt : array-like + Payment + pv : array-like + Present value + when : array-like + When payments are due ('begin' (1) or 'end' (0)) + + Notes + ----- + The future value is computed by solving the equation:: + + fv + pv*(1+rate)**nper + pmt*(1+rate*when)/rate * ((1+rate)**nper - 1) == 0 + + or, when ``rate == 0``:: + + fv + pv + pmt * nper == 0 + + Examples + -------- + What is the future value after 10 years of saving $100 now, with + an additional monthly savings of $100. Assume the interest rate is + 5% (annually) compounded monthly? + + >>> np.fv(0.05/12, 10*12, -100, -100) + 15692.928894335748 + + By convention, the negative sign represents cash flow out (i.e. money not + available today). Thus, saving $100 a month at 5% annual interest leads + to $15,692.93 available to spend in 10 years. + """ when = _convert_when(when) rate, nper, pmt, pv, when = map(np.asarray, [rate, nper, pmt, pv, when]) @@ -78,7 +116,43 @@ def fv(rate, nper, pmt, pv, when='end'): """ def pmt(rate, nper, pv, fv=0, when='end'): - """Payment computed by solving the equation + """ + Compute the payment. + + Parameters + ---------- + rate : array-like + Rate of interest (per period) + nper : array-like + Number of compounding periods + pv : array-like + Present value + fv : array-like + Future value + when : array-like + When payments are due ('begin' (1) or 'end' (0)) + + Notes + ----- + The payment ``pmt`` is computed by solving the equation:: + + fv + pv*(1+rate)**nper + pmt*(1+rate*when)/rate * ((1+rate)**nper - 1) == 0 + + or, when ``rate == 0``:: + + fv + pv + pmt * nper == 0 + + Examples + -------- + What would the monthly payment need to be to pay off a $200,000 loan in 15 + years at an annual interest rate of 7.5%? + + >>> np.pmt(0.075/12, 12*15, 200000) + -1854.0247200054619 + + In order to pay-off (i.e. have a future-value of 0) the $200,000 obtained + today, a monthly payment of $1,854.02 would be required. + """ when = _convert_when(when) rate, nper, pv, fv, when = map(np.asarray, [rate, nper, pv, fv, when]) @@ -102,7 +176,52 @@ def pmt(rate, nper, pv, fv=0, when='end'): """ def nper(rate, pmt, pv, fv=0, when='end'): - """Number of periods found by solving the equation + """ + Compute the number of periods. + + Parameters + ---------- + rate : array_like + Rate of interest (per period) + pmt : array_like + Payment + pv : array_like + Present value + fv : array_like + Future value + when : array_like + When payments are due ('begin' (1) or 'end' (0)) + + Notes + ----- + The number of periods ``nper`` is computed by solving the equation:: + + fv + pv*(1+rate)**nper + pmt*(1+rate*when)/rate * ((1+rate)**nper - 1) == 0 + + or, when ``rate == 0``:: + + fv + pv + pmt * nper == 0 + + Examples + -------- + If you only had $150 to spend as payment, how long would it take to pay-off + a loan of $8,000 at 7% annual interest? + + >>> np.nper(0.07/12, -150, 8000) + 64.073348770661852 + + So, over 64 months would be required to pay off the loan. + + The same analysis could be done with several different interest rates and/or + payments and/or total amounts to produce an entire table. + + >>> np.nper(*(np.ogrid[0.06/12:0.071/12:0.01/12, -200:-99:100, 6000:7001:1000])) + array([[[ 32.58497782, 38.57048452], + [ 71.51317802, 86.37179563]], + + [[ 33.07413144, 39.26244268], + [ 74.06368256, 90.22989997]]]) + """ when = _convert_when(when) rate, pmt, pv, fv, when = map(np.asarray, [rate, pmt, pv, fv, when]) @@ -139,6 +258,10 @@ def nper(rate, pmt, pv, fv=0, when='end'): """ def ipmt(rate, per, nper, pv, fv=0.0, when='end'): + """ + Not implemented. + + """ total = pmt(rate, nper, pv, fv, when) # Now, compute the nth step in the amortization raise NotImplementedError @@ -148,7 +271,32 @@ def ppmt(rate, per, nper, pv, fv=0.0, when='end'): return total - ipmt(rate, per, nper, pv, fv, when) def pv(rate, nper, pmt, fv=0.0, when='end'): - """Number of periods found by solving the equation + """ + Compute the present value. + + Parameters + ---------- + rate : array-like + Rate of interest (per period) + nper : array-like + Number of compounding periods + pmt : array-like + Payment + fv : array-like + Future value + when : array-like + When payments are due ('begin' (1) or 'end' (0)) + + Notes + ----- + The present value ``pv`` is computed by solving the equation:: + + fv + pv*(1+rate)**nper + pmt*(1+rate*when)/rate * ((1+rate)**nper - 1) = 0 + + or, when ``rate = 0``:: + + fv + pv + pmt * nper = 0 + """ when = _convert_when(when) rate, nper, pmt, fv, when = map(np.asarray, [rate, nper, pmt, fv, when]) @@ -175,7 +323,38 @@ def _g_div_gp(r, n, p, x, y, w): # g(r) is the formula # g'(r) is the derivative with respect to r. def rate(nper, pmt, pv, fv, when='end', guess=0.10, tol=1e-6, maxiter=100): - """Number of periods found by solving the equation + """ + Compute the rate of interest per period. + + Parameters + ---------- + nper : array_like + Number of compounding periods + pmt : array_like + Payment + pv : array_like + Present value + fv : array_like + Future value + when : array_like, optional + When payments are due ('begin' (1) or 'end' (0)) + guess : float, optional + Starting guess for solving the rate of interest + tol : float, optional + Required tolerance for the solution + maxiter : int, optional + Maximum iterations in finding the solution + + Notes + ----- + The rate of interest ``rate`` is computed by solving the equation:: + + fv + pv*(1+rate)**nper + pmt*(1+rate*when)/rate * ((1+rate)**nper - 1) = 0 + + or, if ``rate = 0``:: + + fv + pv + pmt * nper = 0 + """ when = _convert_when(when) nper, pmt, pv, fv, when = map(np.asarray, [nper, pmt, pv, fv, when]) From 6e31f6835885138ef02a1122a11150e4601e4085 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 8 Aug 2008 00:08:07 +0000 Subject: [PATCH 14/65] Remove piece-by-piece docstring assembly; the full texts were inserted in the docstrings in r5610 --- numpy/lib/financial.py | 79 ------------------------------------------ 1 file changed, 79 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index a997cf6..ded809c 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -17,33 +17,6 @@ 'start':1, 'finish':0} -eqstr = """ - - nper / (1 + rate*when) \ / nper \ - fv + pv*(1+rate) + pmt*|-------------------|*| (1+rate) - 1 | = 0 - \ rate / \ / - - fv + pv + pmt * nper = 0 (when rate == 0) - -where (all can be scalars or sequences) - - Parameters - ---------- - rate : - Rate of interest (per period) - nper : - Number of compounding periods - pmt : - Payment - pv : - Present value - fv : - Future value - when : - When payments are due ('begin' (1) or 'end' (0)) - -""" - def _convert_when(when): try: return _when_to_num[when] @@ -99,21 +72,6 @@ def fv(rate, nper, pmt, pv, when='end'): zer = np.zeros(miter.shape) fact = np.where(rate==zer, nper+zer, (1+rate*when)*(temp-1)/rate+zer) return -(pv*temp + pmt*fact) -fv.__doc__ += eqstr + """ -Example --------- - -What is the future value after 10 years of saving $100 now, with - an additional monthly savings of $100. Assume the interest rate is - 5% (annually) compounded monthly? - ->>> np.fv(0.05/12, 10*12, -100, -100) -15692.928894335748 - -By convention, the negative sign represents cash flow out (i.e. money not - available today). Thus, saving $100 a month at 5% annual interest leads - to $15,692.93 available to spend in 10 years. -""" def pmt(rate, nper, pv, fv=0, when='end'): """ @@ -161,19 +119,6 @@ def pmt(rate, nper, pv, fv=0, when='end'): zer = np.zeros(miter.shape) fact = np.where(rate==zer, nper+zer, (1+rate*when)*(temp-1)/rate+zer) return -(fv + pv*temp) / fact -pmt.__doc__ += eqstr + """ -Examples --------- - -What would the monthly payment need to be to pay off a $200,000 loan in 15 - years at an annual interest rate of 7.5%? - ->>> np.pmt(0.075/12, 12*15, 200000) --1854.0247200054619 - -In order to pay-off (i.e. have a future-value of 0) the $200,000 obtained - today, a monthly payment of $1,854.02 would be required. -""" def nper(rate, pmt, pv, fv=0, when='end'): """ @@ -234,28 +179,6 @@ def nper(rate, pmt, pv, fv=0, when='end'): miter = np.broadcast(rate, pmt, pv, fv, when) zer = np.zeros(miter.shape) return np.where(rate==zer, A+zer, B+zer) + 0.0 -nper.__doc__ += eqstr + """ -Examples --------- - -If you only had $150 to spend as payment, how long would it take to pay-off - a loan of $8,000 at 7% annual interest? - ->>> np.nper(0.07/12, -150, 8000) -64.073348770661852 - -So, over 64 months would be required to pay off the loan. - -The same analysis could be done with several different interest rates and/or - payments and/or total amounts to produce an entire table. - ->>> np.nper(*(np.ogrid[0.06/12:0.071/12:0.01/12, -200:-99:100, 6000:7001:1000])) -array([[[ 32.58497782, 38.57048452], - [ 71.51317802, 86.37179563]], - - [[ 33.07413144, 39.26244268], - [ 74.06368256, 90.22989997]]]) -""" def ipmt(rate, per, nper, pv, fv=0.0, when='end'): """ @@ -305,7 +228,6 @@ def pv(rate, nper, pmt, fv=0.0, when='end'): zer = np.zeros(miter.shape) fact = np.where(rate == zer, nper+zer, (1+rate*when)*(temp-1)/rate+zer) return -(fv + pmt*fact)/temp -pv.__doc__ += eqstr # Computed with Sage # (y + (r + 1)^n*x + p*((r + 1)^n - 1)*(r*w + 1)/r)/(n*(r + 1)^(n - 1)*x - p*((r + 1)^n - 1)*(r*w + 1)/r^2 + n*p*(r + 1)^(n - 1)*(r*w + 1)/r + p*((r + 1)^n - 1)*w/r) @@ -372,7 +294,6 @@ def rate(nper, pmt, pv, fv, when='end', guess=0.10, tol=1e-6, maxiter=100): return np.nan + rn else: return rn -rate.__doc__ += eqstr def irr(values): """Internal Rate of Return From 925abb513c4a6af1366668030faf16d9d3317c91 Mon Sep 17 00:00:00 2001 From: Jarrod Millman Date: Fri, 8 Aug 2008 04:33:45 +0000 Subject: [PATCH 15/65] ran reindent --- numpy/lib/tests/test_financial.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py index d664305..1ac14b5 100644 --- a/numpy/lib/tests/test_financial.py +++ b/numpy/lib/tests/test_financial.py @@ -3,7 +3,7 @@ class TestFinancial(TestCase): def test_rate(self): - assert_almost_equal(np.rate(10,0,-3500,10000), + assert_almost_equal(np.rate(10,0,-3500,10000), 0.1107, 4) def test_irr(self): @@ -12,7 +12,7 @@ def test_irr(self): 0.0524, 2) def test_pv(self): - assert_almost_equal(np.pv(0.07,20,12000,0), + assert_almost_equal(np.pv(0.07,20,12000,0), -127128.17, 2) def test_fv(self): From db9eacbf2a234878f922c041a752c7c8ddcadc3b Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Tue, 28 Oct 2008 00:13:44 +0000 Subject: [PATCH 16/65] Import documentation from doc wiki (part 2, work-in-progress docstrings, but they are still an improvement) --- numpy/lib/financial.py | 209 +++++++++++++++++++++++++++++++++-------- 1 file changed, 172 insertions(+), 37 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index ded809c..f53a776 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -30,26 +30,36 @@ def fv(rate, nper, pmt, pv, when='end'): Parameters ---------- - rate : array-like - Rate of interest (per period) - nper : array-like + rate : scalar or array_like of shape(M, ) + Rate of interest as decimal (not per cent) per period + nper : scalar or array_like of shape(M, ) Number of compounding periods - pmt : array-like + pmt : scalar or array_like of shape(M, ) Payment - pv : array-like + pv : scalar or array_like of shape(M, ) Present value - when : array-like - When payments are due ('begin' (1) or 'end' (0)) + when : {{'begin', 1}, {'end', 0}}, {string, int}, optional + When payments are due ('begin' (1) or 'end' (0)). + Defaults to {'end', 0}. + + Returns + ------- + out : ndarray + Future values. If all input is scalar, returns a scalar float. If + any input is array_like, returns future values for each input element. + If multiple inputs are array_like, they all must have the same shape. Notes ----- The future value is computed by solving the equation:: - fv + pv*(1+rate)**nper + pmt*(1+rate*when)/rate * ((1+rate)**nper - 1) == 0 + fv + + pv*(1+rate)**nper + + pmt*(1 + rate*when)/rate*((1 + rate)**nper - 1) == 0 or, when ``rate == 0``:: - fv + pv + pmt * nper == 0 + fv + pv + pmt * nper == 0 Examples -------- @@ -64,6 +74,13 @@ def fv(rate, nper, pmt, pv, when='end'): available today). Thus, saving $100 a month at 5% annual interest leads to $15,692.93 available to spend in 10 years. + If any input is array_like, returns an array of equal shape. Let's + compare different interest rates from the example above. + + >>> a = np.array((0.05, 0.06, 0.07))/12 + >>> np.fv(a, 10*12, -100, -100) + array([ 15692.92889434, 16569.87435405, 17509.44688102]) + """ when = _convert_when(when) rate, nper, pmt, pv, when = map(np.asarray, [rate, nper, pmt, pv, when]) @@ -75,26 +92,36 @@ def fv(rate, nper, pmt, pv, when='end'): def pmt(rate, nper, pv, fv=0, when='end'): """ - Compute the payment. + Compute the payment against loan principal plus interest. Parameters ---------- - rate : array-like + rate : array_like Rate of interest (per period) - nper : array-like + nper : array_like Number of compounding periods - pv : array-like + pv : array_like Present value - fv : array-like + fv : array_like Future value - when : array-like + when : {{'begin', 1}, {'end', 0}}, {string, int} When payments are due ('begin' (1) or 'end' (0)) + Returns + ------- + out : ndarray + Payment against loan plus interest. If all input is scalar, returns a + scalar float. If any input is array_like, returns payment for each + input element. If multiple inputs are array_like, they all must have + the same shape. + Notes ----- The payment ``pmt`` is computed by solving the equation:: - fv + pv*(1+rate)**nper + pmt*(1+rate*when)/rate * ((1+rate)**nper - 1) == 0 + fv + + pv*(1 + rate)**nper + + pmt*(1 + rate*when)/rate*((1 + rate)**nper - 1) == 0 or, when ``rate == 0``:: @@ -132,9 +159,9 @@ def nper(rate, pmt, pv, fv=0, when='end'): Payment pv : array_like Present value - fv : array_like + fv : array_like, optional Future value - when : array_like + when : {{'begin', 1}, {'end', 0}}, {string, int}, optional When payments are due ('begin' (1) or 'end' (0)) Notes @@ -157,8 +184,8 @@ def nper(rate, pmt, pv, fv=0, when='end'): So, over 64 months would be required to pay off the loan. - The same analysis could be done with several different interest rates and/or - payments and/or total amounts to produce an entire table. + The same analysis could be done with several different interest rates + and/or payments and/or total amounts to produce an entire table. >>> np.nper(*(np.ogrid[0.06/12:0.071/12:0.01/12, -200:-99:100, 6000:7001:1000])) array([[[ 32.58497782, 38.57048452], @@ -182,7 +209,42 @@ def nper(rate, pmt, pv, fv=0, when='end'): def ipmt(rate, per, nper, pv, fv=0.0, when='end'): """ - Not implemented. + Not implemented. Compute the payment portion for loan interest. + + Parameters + ---------- + rate : scalar or array_like of shape(M, ) + Rate of interest as decimal (not per cent) per period + per : scalar or array_like of shape(M, ) + Interest paid against the loan changes during the life or the loan. + The `per` is the payment period to calculate the interest amount. + nper : scalar or array_like of shape(M, ) + Number of compounding periods + pv : scalar or array_like of shape(M, ) + Present value + fv : scalar or array_like of shape(M, ), optional + Future value + when : {{'begin', 1}, {'end', 0}}, {string, int}, optional + When payments are due ('begin' (1) or 'end' (0)). + Defaults to {'end', 0}. + + Returns + ------- + out : ndarray + Interest portion of payment. If all input is scalar, returns a scalar + float. If any input is array_like, returns interest payment for each + input element. If multiple inputs are array_like, they all must have + the same shape. + + See Also + -------- + ppmt, pmt, pv + + Notes + ----- + The total payment is made up of payment against principal plus interest. + + ``pmt = ppmt + ipmt`` """ total = pmt(rate, nper, pv, fv, when) @@ -190,6 +252,30 @@ def ipmt(rate, per, nper, pv, fv=0.0, when='end'): raise NotImplementedError def ppmt(rate, per, nper, pv, fv=0.0, when='end'): + """ + Not implemented. Compute the payment against loan principal. + + Parameters + ---------- + rate : array_like + Rate of interest (per period) + per : array_like, int + Amount paid against the loan changes. The `per` is the period of + interest. + nper : array_like + Number of compounding periods + pv : array_like + Present value + fv : array_like, optional + Future value + when : {{'begin', 1}, {'end', 0}}, {string, int} + When payments are due ('begin' (1) or 'end' (0)) + + See Also + -------- + pmt, pv, ipmt + + """ total = pmt(rate, nper, pv, fv, when) return total - ipmt(rate, per, nper, pv, fv, when) @@ -199,22 +285,29 @@ def pv(rate, nper, pmt, fv=0.0, when='end'): Parameters ---------- - rate : array-like + rate : array_like Rate of interest (per period) - nper : array-like + nper : array_like Number of compounding periods - pmt : array-like + pmt : array_like Payment - fv : array-like + fv : array_like, optional Future value - when : array-like + when : {{'begin', 1}, {'end', 0}}, {string, int}, optional When payments are due ('begin' (1) or 'end' (0)) + Returns + ------- + out : ndarray, float + Present value of a series of payments or investments. + Notes ----- The present value ``pv`` is computed by solving the equation:: - fv + pv*(1+rate)**nper + pmt*(1+rate*when)/rate * ((1+rate)**nper - 1) = 0 + fv + + pv*(1 + rate)**nper + + pmt*(1 + rate*when)/rate*((1 + rate)**nper - 1) = 0 or, when ``rate = 0``:: @@ -258,7 +351,7 @@ def rate(nper, pmt, pv, fv, when='end', guess=0.10, tol=1e-6, maxiter=100): Present value fv : array_like Future value - when : array_like, optional + when : {{'begin', 1}, {'end', 0}}, {string, int}, optional When payments are due ('begin' (1) or 'end' (0)) guess : float, optional Starting guess for solving the rate of interest @@ -296,11 +389,27 @@ def rate(nper, pmt, pv, fv, when='end', guess=0.10, tol=1e-6, maxiter=100): return rn def irr(values): - """Internal Rate of Return + """ + Return the Internal Rate of Return (IRR). + + This is the rate of return that gives a net present value of 0.0. - This is the rate of return that gives a net present value of 0.0 + Parameters + ---------- + values : array_like, shape(N,) + Input cash flows per time period. At least the first value would be + negative to represent the investment in the project. + + Returns + ------- + out : float + Internal Rate of Return for periodic input values. + + Examples + -------- + >>> np.irr([-100, 39, 59, 55, 20]) + 0.2809484211599611 - npv(irr(values), values) == 0.0 """ res = np.roots(values[::-1]) # Find the root(s) between 0 and 1 @@ -314,25 +423,51 @@ def irr(values): return rate def npv(rate, values): - """Net Present Value + """ + Returns the NPV (Net Present Value) of a cash flow series. + + Parameters + ---------- + rate : scalar + The discount rate. + values : array_like, shape(M, ) + The values of the time series of cash flows. Must be the same + increment as the `rate`. + + Returns + ------- + out : float + The NPV of the input cash flow series `values` at the discount `rate`. + + Notes + ----- + Returns the result of: + + .. math :: \\sum_{t=1}^M{\\frac{values_t}{(1+rate)^{t}}} - sum ( values_k / (1+rate)**k, k = 1..n) """ values = np.asarray(values) return (values / (1+rate)**np.arange(1,len(values)+1)).sum(axis=0) def mirr(values, finance_rate, reinvest_rate): - """Modified internal rate of return + """ + Modified internal rate of return. Parameters ---------- - values: + values : array_like Cash flows (must contain at least one positive and one negative value) or nan is returned. - finance_rate : + finance_rate : scalar Interest rate paid on the cash flows - reinvest_rate : + reinvest_rate : scalar Interest rate received on the cash flows upon reinvestment + + Returns + ------- + out : float + Modified internal rate of return + """ values = np.asarray(values) From a7021041112f1845622b52b05302060a63b6be04 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Tue, 24 Mar 2009 22:25:21 +0000 Subject: [PATCH 17/65] Merge from the doc wiki --- numpy/lib/financial.py | 1 - 1 file changed, 1 deletion(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index f53a776..0cef1c4 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -190,7 +190,6 @@ def nper(rate, pmt, pv, fv=0, when='end'): >>> np.nper(*(np.ogrid[0.06/12:0.071/12:0.01/12, -200:-99:100, 6000:7001:1000])) array([[[ 32.58497782, 38.57048452], [ 71.51317802, 86.37179563]], - [[ 33.07413144, 39.26244268], [ 74.06368256, 90.22989997]]]) From aae4814a26bf1d37a53488b4844742e48d1d3066 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 19 Jun 2009 15:03:39 +0000 Subject: [PATCH 18/65] Merge from doc wiki --- numpy/lib/financial.py | 184 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 170 insertions(+), 14 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index 0cef1c4..0b93725 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -28,6 +28,18 @@ def fv(rate, nper, pmt, pv, when='end'): """ Compute the future value. + Given: + * a present value, `pv` + * an interest `rate` compounded once per period, of which + there are + * `nper` total + * a (fixed) payment, `pmt`, paid either + * at the beginning (`when` = {'begin', 1}) or the end + (`when` = {'end', 0}) of each period + + Return: + the value at the end of the `nper` periods + Parameters ---------- rate : scalar or array_like of shape(M, ) @@ -61,6 +73,17 @@ def fv(rate, nper, pmt, pv, when='end'): fv + pv + pmt * nper == 0 + References + ---------- + .. [WRW] Wheeler, D. A., E. Rathke, and R. Weir (Eds.) (2009, May). + Open Document Format for Office Applications (OpenDocument)v1.2, + Part 2: Recalculated Formula (OpenFormula) Format - Annotated Version, + Pre-Draft 12. Organization for the Advancement of Structured Information + Standards (OASIS). Billerica, MA, USA. [ODT Document]. + Available: + http://www.oasis-open.org/committees/documents.php?wg_abbrev=office-formula + OpenDocument-formula-20090508.odt + Examples -------- What is the future value after 10 years of saving $100 now, with @@ -94,6 +117,19 @@ def pmt(rate, nper, pv, fv=0, when='end'): """ Compute the payment against loan principal plus interest. + Given: + * a present value, `pv` (e.g., an amount borrowed) + * a future value, `fv` (e.g., 0) + * an interest `rate` compounded once per period, of which + there are + * `nper` total + * and (optional) specification of whether payment is made + at the beginning (`when` = {'begin', 1}) or the end + (`when` = {'end', 0}) of each period + + Return: + the (fixed) periodic payment. + Parameters ---------- rate : array_like @@ -102,8 +138,8 @@ def pmt(rate, nper, pv, fv=0, when='end'): Number of compounding periods pv : array_like Present value - fv : array_like - Future value + fv : array_like (optional) + Future value (default = 0) when : {{'begin', 1}, {'end', 0}}, {string, int} When payments are due ('begin' (1) or 'end' (0)) @@ -117,7 +153,7 @@ def pmt(rate, nper, pv, fv=0, when='end'): Notes ----- - The payment ``pmt`` is computed by solving the equation:: + The payment is computed by solving the equation:: fv + pv*(1 + rate)**nper + @@ -127,16 +163,37 @@ def pmt(rate, nper, pv, fv=0, when='end'): fv + pv + pmt * nper == 0 + for ``pmt``. + + Note that computing a monthly mortgage payment is only + one use for this function. For example, pmt returns the + periodic deposit one must make to achieve a specified + future balance given an initial deposit, a fixed, + periodically compounded interest rate, and the total + number of periods. + + References + ---------- + .. [WRW] Wheeler, D. A., E. Rathke, and R. Weir (Eds.) (2009, May). + Open Document Format for Office Applications (OpenDocument)v1.2, + Part 2: Recalculated Formula (OpenFormula) Format - Annotated Version, + Pre-Draft 12. Organization for the Advancement of Structured Information + Standards (OASIS). Billerica, MA, USA. [ODT Document]. + Available: + http://www.oasis-open.org/committees/documents.php?wg_abbrev=office-formula + OpenDocument-formula-20090508.odt + Examples -------- - What would the monthly payment need to be to pay off a $200,000 loan in 15 + What is the monthly payment needed to pay off a $200,000 loan in 15 years at an annual interest rate of 7.5%? >>> np.pmt(0.075/12, 12*15, 200000) -1854.0247200054619 - In order to pay-off (i.e. have a future-value of 0) the $200,000 obtained - today, a monthly payment of $1,854.02 would be required. + In order to pay-off (i.e., have a future-value of 0) the $200,000 obtained + today, a monthly payment of $1,854.02 would be required. Note that this + example illustrates usage of `fv` having a default value of 0. """ when = _convert_when(when) @@ -282,6 +339,18 @@ def pv(rate, nper, pmt, fv=0.0, when='end'): """ Compute the present value. + Given: + * a future value, `fv` + * an interest `rate` compounded once per period, of which + there are + * `nper` total + * a (fixed) payment, `pmt`, paid either + * at the beginning (`when` = {'begin', 1}) or the end + (`when` = {'end', 0}) of each period + + Return: + the value now + Parameters ---------- rate : array_like @@ -302,7 +371,7 @@ def pv(rate, nper, pmt, fv=0.0, when='end'): Notes ----- - The present value ``pv`` is computed by solving the equation:: + The present value is computed by solving the equation:: fv + pv*(1 + rate)**nper + @@ -312,6 +381,45 @@ def pv(rate, nper, pmt, fv=0.0, when='end'): fv + pv + pmt * nper = 0 + for `pv`, which is then returned. + + References + ---------- + .. [WRW] Wheeler, D. A., E. Rathke, and R. Weir (Eds.) (2009, May). + Open Document Format for Office Applications (OpenDocument)v1.2, + Part 2: Recalculated Formula (OpenFormula) Format - Annotated Version, + Pre-Draft 12. Organization for the Advancement of Structured Information + Standards (OASIS). Billerica, MA, USA. [ODT Document]. + Available: + http://www.oasis-open.org/committees/documents.php?wg_abbrev=office-formula + OpenDocument-formula-20090508.odt + + Examples + -------- + What is the present value (e.g., the initial investment) + of an investment that needs to total $15692.93 + after 10 years of saving $100 every month? Assume the + interest rate is 5% (annually) compounded monthly. + + >>> np.pv(0.05/12, 10*12, -100, 15692.93) + -100.00067131625819 + + By convention, the negative sign represents cash flow out + (i.e., money not available today). Thus, to end up with + $15,692.93 in 10 years saving $100 a month at 5% annual + interest, one's initial deposit should also be $100. + + If any input is array_like, ``pv`` returns an array of equal shape. + Let's compare different interest rates in the example above: + + >>> a = np.array((0.05, 0.04, 0.03))/12 + >>> np.pv(a, 10*12, -100, 15692.93) + array([ -100.00067132, -649.26771385, -1273.78633713]) + + So, to end up with the same $15692.93 under the same $100 per month + "savings plan," for annual interest rates of 4% and 3%, one would + need initial investments of $649.27 and $1273.79, respectively. + """ when = _convert_when(when) rate, nper, pmt, fv, when = map(np.asarray, [rate, nper, pmt, fv, when]) @@ -391,24 +499,54 @@ def irr(values): """ Return the Internal Rate of Return (IRR). - This is the rate of return that gives a net present value of 0.0. + This is the "average" periodically compounded rate of return + that gives a net present value of 0.0; for a more complete explanation, + see Notes below. Parameters ---------- values : array_like, shape(N,) - Input cash flows per time period. At least the first value would be - negative to represent the investment in the project. + Input cash flows per time period. By convention, net "deposits" + are negative and net "withdrawals" are positive. Thus, for example, + at least the first element of `values`, which represents the initial + investment, will typically be negative. Returns ------- out : float Internal Rate of Return for periodic input values. + Notes + ----- + The IRR is perhaps best understood through an example (illustrated + using np.irr in the Examples section below). Suppose one invests + 100 units and then makes the following withdrawals at regular + (fixed) intervals: 39, 59, 55, 20. Assuming the ending value is 0, + one's 100 unit investment yields 173 units; however, due to the + combination of compounding and the periodic withdrawals, the + "average" rate of return is neither simply 0.73/4 nor (1.73)^0.25-1. + Rather, it is the solution (for :math:`r`) of the equation: + + .. math:: -100 + \\frac{39}{1+r} + \\frac{59}{(1+r)^2} + + \\frac{55}{(1+r)^3} + \\frac{20}{(1+r)^4} = 0 + + In general, for `values` :math:`= [v_0, v_1, ... v_M]`, + irr is the solution of the equation: [G]_ + + .. math:: \\sum_{t=0}^M{\\frac{v_t}{(1+irr)^{t}}} = 0 + + References + ---------- + .. [G] L. J. Gitman, "Principles of Managerial Finance, Brief," 3rd ed., + Addison-Wesley, 2003, pg. 348. + Examples -------- >>> np.irr([-100, 39, 59, 55, 20]) 0.2809484211599611 + (Compare with the Example given for numpy.lib.financial.npv) + """ res = np.roots(values[::-1]) # Find the root(s) between 0 and 1 @@ -430,8 +568,14 @@ def npv(rate, values): rate : scalar The discount rate. values : array_like, shape(M, ) - The values of the time series of cash flows. Must be the same - increment as the `rate`. + The values of the time series of cash flows. The (fixed) time + interval between cash flow "events" must be the same as that + for which `rate` is given (i.e., if `rate` is per year, then + precisely a year is understood to elapse between each cash flow + event). By convention, investments or "deposits" are negative, + income or "withdrawals" are positive; `values` must begin with + the initial investment, thus `values[0]` will typically be + negative. Returns ------- @@ -440,9 +584,21 @@ def npv(rate, values): Notes ----- - Returns the result of: + Returns the result of: [G]_ + + .. math :: \\sum_{t=0}^M{\\frac{values_t}{(1+rate)^{t}}} + + References + ---------- + .. [G] L. J. Gitman, "Principles of Managerial Finance, Brief," 3rd ed., + Addison-Wesley, 2003, pg. 346. + + Examples + -------- + >>> np.npv(0.281,[-100, 39, 59, 55, 20]) + -0.0066187288356340801 - .. math :: \\sum_{t=1}^M{\\frac{values_t}{(1+rate)^{t}}} + (Compare with the Example given for numpy.lib.financial.irr) """ values = np.asarray(values) From c37e007b4c19ad5388061a204384f7a24c614846 Mon Sep 17 00:00:00 2001 From: Travis Oliphant Date: Tue, 25 Aug 2009 13:04:09 +0000 Subject: [PATCH 19/65] Add patch in ticket #1138 which fixes mirr function to be the same as Excel and OO Calc. --- numpy/lib/financial.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index 0b93725..496e960 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -612,7 +612,7 @@ def mirr(values, finance_rate, reinvest_rate): ---------- values : array_like Cash flows (must contain at least one positive and one negative value) - or nan is returned. + or nan is returned. The first value is considered a sunk cost at time zero. finance_rate : scalar Interest rate paid on the cash flows reinvest_rate : scalar @@ -626,12 +626,16 @@ def mirr(values, finance_rate, reinvest_rate): """ values = np.asarray(values) + initial = values[0] + values = values[1:] + n = values.size pos = values > 0 neg = values < 0 - if not (pos.size > 0 and neg.size > 0): + if not (pos.sum() > 0 and neg.sum() > 0): return np.nan - - n = pos.size + neg.size - numer = -npv(reinvest_rate, values[pos])*((1+reinvest_rate)**n) - denom = npv(finance_rate, values[neg])*(1+finance_rate) - return (numer / denom)**(1.0/(n-1)) - 1 + numer = np.abs(npv(reinvest_rate, values*pos)) + denom = np.abs(npv(finance_rate, values*neg)) + if initial > 0: + return ((initial + numer) / denom)**(1.0/n)*(1+reinvest_rate) - 1 + else: + return ((numer / (-initial + denom)))**(1.0/n)*(1+reinvest_rate) - 1 From 5399ce081a1b874a9ad2e366cdef9f23803b430c Mon Sep 17 00:00:00 2001 From: Travis Oliphant Date: Tue, 25 Aug 2009 14:13:14 +0000 Subject: [PATCH 20/65] Improve test and add a test to Python. --- numpy/lib/tests/test_financial.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py index 1ac14b5..0ad3766 100644 --- a/numpy/lib/tests/test_financial.py +++ b/numpy/lib/tests/test_financial.py @@ -38,11 +38,14 @@ def test_npv(self): def test_mirr(self): v1 = [-4500,-800,800,800,600,600,800,800,700,3000] assert_almost_equal(np.mirr(v1,0.08,0.055), - 0.0665, 4) + 0.0666, 4) v2 = [-120000,39000,30000,21000,37000,46000] assert_almost_equal(np.mirr(v2,0.10,0.12), 0.1344, 4) + + v3 = [100,200,-50,300,-200] + assert_almost_equal(np.mirr(v3,0.05,0.06), 0.3428, 4) def test_unimplemented(): From ab234987393dfaf4cb9ab7d405a8b42cffbf91a5 Mon Sep 17 00:00:00 2001 From: Charles Harris Date: Wed, 26 Aug 2009 03:43:46 +0000 Subject: [PATCH 21/65] Make some fixes in mirr implementation to avoid overflow in summing booleans. Do some whitespace cleanup. --- numpy/lib/financial.py | 10 +++++----- numpy/lib/tests/test_financial.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index 496e960..5d1e65f 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -625,17 +625,17 @@ def mirr(values, finance_rate, reinvest_rate): """ - values = np.asarray(values) + values = np.asarray(values, dtype=np.double) initial = values[0] values = values[1:] n = values.size pos = values > 0 neg = values < 0 - if not (pos.sum() > 0 and neg.sum() > 0): + if not (pos.any() and neg.any()): return np.nan numer = np.abs(npv(reinvest_rate, values*pos)) denom = np.abs(npv(finance_rate, values*neg)) - if initial > 0: - return ((initial + numer) / denom)**(1.0/n)*(1+reinvest_rate) - 1 + if initial > 0: + return ((initial + numer) / denom)**(1.0/n)*(1 + reinvest_rate) - 1 else: - return ((numer / (-initial + denom)))**(1.0/n)*(1+reinvest_rate) - 1 + return ((numer / (-initial + denom)))**(1.0/n)*(1 + reinvest_rate) - 1 diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py index 0ad3766..3c97036 100644 --- a/numpy/lib/tests/test_financial.py +++ b/numpy/lib/tests/test_financial.py @@ -43,7 +43,7 @@ def test_mirr(self): v2 = [-120000,39000,30000,21000,37000,46000] assert_almost_equal(np.mirr(v2,0.10,0.12), 0.1344, 4) - + v3 = [100,200,-50,300,-200] assert_almost_equal(np.mirr(v3,0.05,0.06), 0.3428, 4) From 6bb9481173ee80a91ffb81973f4f6696e4b4f602 Mon Sep 17 00:00:00 2001 From: Charles Harris Date: Wed, 26 Aug 2009 04:11:22 +0000 Subject: [PATCH 22/65] Comment out failing test until it is decided where the problem lies. --- numpy/lib/tests/test_financial.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py index 3c97036..c6d7109 100644 --- a/numpy/lib/tests/test_financial.py +++ b/numpy/lib/tests/test_financial.py @@ -37,12 +37,10 @@ def test_npv(self): def test_mirr(self): v1 = [-4500,-800,800,800,600,600,800,800,700,3000] - assert_almost_equal(np.mirr(v1,0.08,0.055), - 0.0666, 4) + assert_almost_equal(np.mirr(v1,0.08,0.055), 0.0666, 4) - v2 = [-120000,39000,30000,21000,37000,46000] - assert_almost_equal(np.mirr(v2,0.10,0.12), - 0.1344, 4) +# v2 = [-120000,39000,30000,21000,37000,46000] +# assert_almost_equal(np.mirr(v2,0.10,0.12), 0.1344, 4) v3 = [100,200,-50,300,-200] assert_almost_equal(np.mirr(v3,0.05,0.06), 0.3428, 4) From 02d28f6ec3446bcf52ee17784c60cde28c7f5cca Mon Sep 17 00:00:00 2001 From: Charles Harris Date: Wed, 26 Aug 2009 20:08:20 +0000 Subject: [PATCH 23/65] Fix mirr function and its test. Thanks go to Skipper and Josef. --- numpy/lib/financial.py | 12 ++++-------- numpy/lib/tests/test_financial.py | 16 ++++++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index 5d1e65f..503d436 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -626,16 +626,12 @@ def mirr(values, finance_rate, reinvest_rate): """ values = np.asarray(values, dtype=np.double) - initial = values[0] - values = values[1:] n = values.size pos = values > 0 neg = values < 0 if not (pos.any() and neg.any()): return np.nan - numer = np.abs(npv(reinvest_rate, values*pos)) - denom = np.abs(npv(finance_rate, values*neg)) - if initial > 0: - return ((initial + numer) / denom)**(1.0/n)*(1 + reinvest_rate) - 1 - else: - return ((numer / (-initial + denom)))**(1.0/n)*(1 + reinvest_rate) - 1 + numer = np.abs(npv(reinvest_rate, values*pos))*(1 + reinvest_rate) + denom = np.abs(npv(finance_rate, values*neg))*(1 + finance_rate) + return (numer/denom)**(1.0/(n - 1))*(1 + reinvest_rate) - 1 + diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py index c6d7109..c1d77c5 100644 --- a/numpy/lib/tests/test_financial.py +++ b/numpy/lib/tests/test_financial.py @@ -36,14 +36,18 @@ def test_npv(self): 117.04, 2) def test_mirr(self): - v1 = [-4500,-800,800,800,600,600,800,800,700,3000] - assert_almost_equal(np.mirr(v1,0.08,0.055), 0.0666, 4) + val = [-4500,-800,800,800,600,600,800,800,700,3000] + assert_almost_equal(np.mirr(val, 0.08, 0.055), 0.0666, 4) -# v2 = [-120000,39000,30000,21000,37000,46000] -# assert_almost_equal(np.mirr(v2,0.10,0.12), 0.1344, 4) + val = [-120000,39000,30000,21000,37000,46000] + assert_almost_equal(np.mirr(val, 0.10, 0.12), 0.126094, 6) + + val = [100,200,-50,300,-200] + assert_almost_equal(np.mirr(val, 0.05, 0.06), 0.3428, 4) + + val = [39000,30000,21000,37000,46000] + assert_(np.isnan(np.mirr(val, 0.10, 0.12))) - v3 = [100,200,-50,300,-200] - assert_almost_equal(np.mirr(v3,0.05,0.06), 0.3428, 4) def test_unimplemented(): From 65fb1eba829771a3704017e340d67576a1dd3a11 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 2 Oct 2009 19:33:33 +0000 Subject: [PATCH 24/65] Docstring update: lib --- numpy/lib/financial.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index 503d436..7f62a81 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -180,8 +180,8 @@ def pmt(rate, nper, pv, fv=0, when='end'): Pre-Draft 12. Organization for the Advancement of Structured Information Standards (OASIS). Billerica, MA, USA. [ODT Document]. Available: - http://www.oasis-open.org/committees/documents.php?wg_abbrev=office-formula - OpenDocument-formula-20090508.odt + http://www.oasis-open.org/committees/documents.php + ?wg_abbrev=office-formulaOpenDocument-formula-20090508.odt Examples -------- @@ -469,13 +469,22 @@ def rate(nper, pmt, pv, fv, when='end', guess=0.10, tol=1e-6, maxiter=100): Notes ----- - The rate of interest ``rate`` is computed by solving the equation:: + The rate of interest is computed by iteratively solving the + (non-linear) equation:: fv + pv*(1+rate)**nper + pmt*(1+rate*when)/rate * ((1+rate)**nper - 1) = 0 - or, if ``rate = 0``:: + for ``rate``. - fv + pv + pmt * nper = 0 + References + ---------- + Wheeler, D. A., E. Rathke, and R. Weir (Eds.) (2009, May). Open Document + Format for Office Applications (OpenDocument)v1.2, Part 2: Recalculated + Formula (OpenFormula) Format - Annotated Version, Pre-Draft 12. + Organization for the Advancement of Structured Information Standards + (OASIS). Billerica, MA, USA. [ODT Document]. Available: + http://www.oasis-open.org/committees/documents.php?wg_abbrev=office-formula + OpenDocument-formula-20090508.odt """ when = _convert_when(when) From 8826b9ddf752020e83648ae041bc501eafd3ca93 Mon Sep 17 00:00:00 2001 From: Jarrod Millman Date: Wed, 17 Feb 2010 23:53:04 +0000 Subject: [PATCH 25/65] more docstring updates from pydoc website (thanks to everyone who contributed!) --- numpy/lib/financial.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index 7f62a81..8b77eb3 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -206,7 +206,7 @@ def pmt(rate, nper, pv, fv=0, when='end'): def nper(rate, pmt, pv, fv=0, when='end'): """ - Compute the number of periods. + Compute the number of periodic payments. Parameters ---------- @@ -225,16 +225,16 @@ def nper(rate, pmt, pv, fv=0, when='end'): ----- The number of periods ``nper`` is computed by solving the equation:: - fv + pv*(1+rate)**nper + pmt*(1+rate*when)/rate * ((1+rate)**nper - 1) == 0 + fv + pv*(1+rate)**nper + pmt*(1+rate*when)/rate*((1+rate)**nper-1) = 0 - or, when ``rate == 0``:: + but if ``rate = 0`` then:: - fv + pv + pmt * nper == 0 + fv + pv + pmt*nper = 0 Examples -------- - If you only had $150 to spend as payment, how long would it take to pay-off - a loan of $8,000 at 7% annual interest? + If you only had $150/month to pay towards the loan, how long would it take + to pay-off a loan of $8,000 at 7% annual interest? >>> np.nper(0.07/12, -150, 8000) 64.073348770661852 @@ -244,11 +244,13 @@ def nper(rate, pmt, pv, fv=0, when='end'): The same analysis could be done with several different interest rates and/or payments and/or total amounts to produce an entire table. - >>> np.nper(*(np.ogrid[0.06/12:0.071/12:0.01/12, -200:-99:100, 6000:7001:1000])) - array([[[ 32.58497782, 38.57048452], - [ 71.51317802, 86.37179563]], - [[ 33.07413144, 39.26244268], - [ 74.06368256, 90.22989997]]]) + >>> np.nper(*(np.ogrid[0.07/12: 0.08/12: 0.01/12, + ... -150 : -99 : 50 , + ... 8000 : 9001 : 1000])) + array([[[ 64.07334877, 74.06368256], + [ 108.07548412, 127.99022654]], + [[ 66.12443902, 76.87897353], + [ 114.70165583, 137.90124779]]]) """ when = _convert_when(when) From bf809a7a637247547025934b4d88c84367a4b4ca Mon Sep 17 00:00:00 2001 From: David Cournapeau Date: Wed, 31 Mar 2010 03:45:25 +0000 Subject: [PATCH 26/65] BUG: fix div by zero handling in nper. --- numpy/lib/financial.py | 26 ++++++++++++++++++-------- numpy/lib/tests/test_financial.py | 2 +- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index 8b77eb3..55ed283 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -255,15 +255,25 @@ def nper(rate, pmt, pv, fv=0, when='end'): """ when = _convert_when(when) rate, pmt, pv, fv, when = map(np.asarray, [rate, pmt, pv, fv, when]) + + use_zero_rate = False + old_err = np.seterr(divide="raise") try: - z = pmt*(1.0+rate*when)/rate - except ZeroDivisionError: - z = 0.0 - A = -(fv + pv)/(pmt+0.0) - B = np.log((-fv+z) / (pv+z))/np.log(1.0+rate) - miter = np.broadcast(rate, pmt, pv, fv, when) - zer = np.zeros(miter.shape) - return np.where(rate==zer, A+zer, B+zer) + 0.0 + try: + z = pmt*(1.0+rate*when)/rate + except FloatingPointError: + use_zero_rate = True + finally: + np.seterr(**old_err) + + if use_zero_rate: + return (-fv + pv) / (pmt + 0.0) + else: + A = -(fv + pv)/(pmt+0.0) + B = np.log((-fv+z) / (pv+z))/np.log(1.0+rate) + miter = np.broadcast(rate, pmt, pv, fv, when) + zer = np.zeros(miter.shape) + return np.where(rate==zer, A+zer, B+zer) + 0.0 def ipmt(rate, per, nper, pv, fv=0.0, when='end'): """ diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py index c1d77c5..f314304 100644 --- a/numpy/lib/tests/test_financial.py +++ b/numpy/lib/tests/test_financial.py @@ -27,7 +27,7 @@ def test_nper(self): assert_almost_equal(np.nper(0.075,-2000,0,100000.), 21.54, 2) - def test_nper(self): + def test_nper2(self): assert_almost_equal(np.nper(0.0,-2000,0,100000.), 50.0, 1) From 95c8ad32900bdc701bdd8faad75dd399914cf441 Mon Sep 17 00:00:00 2001 From: Charles Harris Date: Sat, 2 Apr 2011 13:08:29 -0600 Subject: [PATCH 27/65] WHT: Cleanup trailing whitespace. --- numpy/lib/financial.py | 1 - 1 file changed, 1 deletion(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index 55ed283..861892b 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -655,4 +655,3 @@ def mirr(values, finance_rate, reinvest_rate): numer = np.abs(npv(reinvest_rate, values*pos))*(1 + reinvest_rate) denom = np.abs(npv(finance_rate, values*neg))*(1 + finance_rate) return (numer/denom)**(1.0/(n - 1))*(1 + reinvest_rate) - 1 - From db8c555fbc836d15211460ed20a16f0e0a1030a4 Mon Sep 17 00:00:00 2001 From: tim cera Date: Sat, 28 Jan 2012 21:09:03 -0500 Subject: [PATCH 28/65] Added ipmt (interest portion of payment) and ppmt (principal portion of payment) functions. Added doctests and unit tests. --- numpy/lib/financial.py | 72 ++++++++++++++++++++++++++++--- numpy/lib/tests/test_financial.py | 15 +++---- 2 files changed, 73 insertions(+), 14 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index 861892b..d686aec 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -277,7 +277,7 @@ def nper(rate, pmt, pv, fv=0, when='end'): def ipmt(rate, per, nper, pv, fv=0.0, when='end'): """ - Not implemented. Compute the payment portion for loan interest. + Compute the interest portion of a payment. Parameters ---------- @@ -314,14 +314,71 @@ def ipmt(rate, per, nper, pv, fv=0.0, when='end'): ``pmt = ppmt + ipmt`` + Examples + -------- + What is the amortization schedule for a 1 year loan of $2500 at + 8.24% interest per year compounded monthly? + + >>> principal = 2500.00 + + The 'per' variable represents the periods of the loan. Remember that + financial equations start the period count at 1! + + >>> per = np.arange(1*12) + 1 + >>> ipmt = np.ipmt(0.0824/12, per, 1*12, principal) + >>> ppmt = np.ppmt(0.0824/12, per, 1*12, principal) + + Each element of the sum of the 'ipmt' and 'ppmt' arrays should equal + 'pmt'. + + >>> pmt = np.pmt(0.0824/12, 1*12, principal) + >>> np.allclose(ipmt + ppmt, pmt) + True + + >>> fmt = '{0:2d} {1:8.2f} {2:8.2f} {3:8.2f}' + >>> for payment in per: + ... index = payment - 1 + ... principal = principal + ppmt[index] + ... print fmt.format(payment, ppmt[index], ipmt[index], principal) + 1 -200.58 -17.17 2299.42 + 2 -201.96 -15.79 2097.46 + 3 -203.35 -14.40 1894.11 + 4 -204.74 -13.01 1689.37 + 5 -206.15 -11.60 1483.22 + 6 -207.56 -10.18 1275.66 + 7 -208.99 -8.76 1066.67 + 8 -210.42 -7.32 856.25 + 9 -211.87 -5.88 644.38 + 10 -213.32 -4.42 431.05 + 11 -214.79 -2.96 216.26 + 12 -216.26 -1.49 -0.00 + + >>> interestpd = np.sum(ipmt) + >>> interestpd + -112.98308424136215 + """ - total = pmt(rate, nper, pv, fv, when) - # Now, compute the nth step in the amortization - raise NotImplementedError + when = _convert_when(when) + if when == 1 and per == 1: + return 0.0 + total_pmt = pmt(rate, nper, pv, fv, when) + ipmt = _rbl(rate, per, total_pmt, pv, when)*rate + if when == 1: + return ipmt/(1 + rate) + return ipmt + +def _rbl(rate, per, pmt, pv, when): + """ + This function is here to simply have a different name for the 'fv' + function to not interfere with the 'fv' keyword argument within the 'ipmt' + function. It is the 'remaining balance on loan' which might be useful as + it's own function, but is easily calculated with the 'fv' function. + """ + return fv(rate, (per - 1), pmt, pv, when) def ppmt(rate, per, nper, pv, fv=0.0, when='end'): """ - Not implemented. Compute the payment against loan principal. + Compute the payment against loan principal. Parameters ---------- @@ -655,3 +712,8 @@ def mirr(values, finance_rate, reinvest_rate): numer = np.abs(npv(reinvest_rate, values*pos))*(1 + reinvest_rate) denom = np.abs(npv(finance_rate, values*neg))*(1 + finance_rate) return (numer/denom)**(1.0/(n - 1))*(1 + reinvest_rate) - 1 + +if __name__ == '__main__': + import doctest + import numpy as np + doctest.testmod(verbose=True) diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py index f314304..ba6846b 100644 --- a/numpy/lib/tests/test_financial.py +++ b/numpy/lib/tests/test_financial.py @@ -23,6 +23,12 @@ def test_pmt(self): assert_almost_equal(np.pmt(0.08/12,5*12,15000), -304.146, 3) + def test_ppmt(self): + np.round(np.ppmt(0.1/12,1,60,55000),2) == 710.25 + + def test_ipmt(self): + np.round(np.ipmt(0.1/12,1,24,2000),2) == 16.67 + def test_nper(self): assert_almost_equal(np.nper(0.075,-2000,0,100000.), 21.54, 2) @@ -49,14 +55,5 @@ def test_mirr(self): assert_(np.isnan(np.mirr(val, 0.10, 0.12))) - -def test_unimplemented(): - # np.round(np.ppmt(0.1/12,1,60,55000),2) == 710.25 - assert_raises(NotImplementedError, np.ppmt, 0.1/12, 1, 60, 55000) - - # np.round(np.ipmt(0.1/12,1,24,2000),2) == 16.67 - assert_raises(NotImplementedError, np.ipmt, 0.1/12, 1, 24, 2000) - - if __name__ == "__main__": run_module_suite() From 668c97c842e614b793774ffa36414f9a61341516 Mon Sep 17 00:00:00 2001 From: Tim Cera Date: Sun, 29 Apr 2012 02:38:50 -0400 Subject: [PATCH 29/65] BUG: Changed ipmt to accept array_like arguments. The ipmt function was also fixed to handle broadcasting. The tests were improved and extended to cover the broadcasting capability. --- numpy/lib/financial.py | 28 ++++++----- numpy/lib/tests/test_financial.py | 80 +++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 11 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index d686aec..599a361 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -18,9 +18,13 @@ 'finish':0} def _convert_when(when): + #Test to see if when has already been converted to ndarray + #This will happen if one function calls another, for example ppmt + if isinstance(when, np.ndarray): + return when try: return _when_to_num[when] - except KeyError: + except (KeyError, TypeError): return [_when_to_num[x] for x in when] @@ -236,8 +240,8 @@ def nper(rate, pmt, pv, fv=0, when='end'): If you only had $150/month to pay towards the loan, how long would it take to pay-off a loan of $8,000 at 7% annual interest? - >>> np.nper(0.07/12, -150, 8000) - 64.073348770661852 + >>> print round(np.nper(0.07/12, -150, 8000), 5) + 64.07335 So, over 64 months would be required to pay off the loan. @@ -354,17 +358,19 @@ def ipmt(rate, per, nper, pv, fv=0.0, when='end'): 12 -216.26 -1.49 -0.00 >>> interestpd = np.sum(ipmt) - >>> interestpd - -112.98308424136215 + >>> np.round(interestpd, 2) + -112.98 """ when = _convert_when(when) - if when == 1 and per == 1: - return 0.0 + rate, per, nper, pv, fv, when = np.broadcast_arrays(rate, per, nper, pv, fv, when) total_pmt = pmt(rate, nper, pv, fv, when) ipmt = _rbl(rate, per, total_pmt, pv, when)*rate - if when == 1: - return ipmt/(1 + rate) + try: + ipmt = np.where(when == 1, ipmt/(1 + rate), ipmt) + ipmt = np.where(np.logical_and(when == 1, per == 1), 0.0, ipmt) + except IndexError: + pass return ipmt def _rbl(rate, per, pmt, pv, when): @@ -620,8 +626,8 @@ def irr(values): Examples -------- - >>> np.irr([-100, 39, 59, 55, 20]) - 0.2809484211599611 + >>> print round(np.irr([-100, 39, 59, 55, 20]), 5) + 0.28095 (Compare with the Example given for numpy.lib.financial.npv) diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py index ba6846b..5fe9761 100644 --- a/numpy/lib/tests/test_financial.py +++ b/numpy/lib/tests/test_financial.py @@ -54,6 +54,86 @@ def test_mirr(self): val = [39000,30000,21000,37000,46000] assert_(np.isnan(np.mirr(val, 0.10, 0.12))) + def test_when(self): + #begin + assert_almost_equal(np.rate(10,20,-3500,10000,1), + np.rate(10,20,-3500,10000,'begin'), 4) + #end + assert_almost_equal(np.rate(10,20,-3500,10000), + np.rate(10,20,-3500,10000,'end'), 4) + assert_almost_equal(np.rate(10,20,-3500,10000,0), + np.rate(10,20,-3500,10000,'end'), 4) + + # begin + assert_almost_equal(np.pv(0.07,20,12000,0,1), + np.pv(0.07,20,12000,0,'begin'), 2) + # end + assert_almost_equal(np.pv(0.07,20,12000,0), + np.pv(0.07,20,12000,0,'end'), 2) + assert_almost_equal(np.pv(0.07,20,12000,0,0), + np.pv(0.07,20,12000,0,'end'), 2) + + # begin + assert_almost_equal(np.fv(0.075, 20, -2000,0,1), + np.fv(0.075, 20, -2000,0,'begin'), 4) + # end + assert_almost_equal(np.fv(0.075, 20, -2000,0), + np.fv(0.075, 20, -2000,0,'end'), 4) + assert_almost_equal(np.fv(0.075, 20, -2000,0,0), + np.fv(0.075, 20, -2000,0,'end'), 4) + + # begin + assert_almost_equal(np.pmt(0.08/12,5*12,15000.,0,1), + np.pmt(0.08/12,5*12,15000.,0,'begin'), 4) + # end + assert_almost_equal(np.pmt(0.08/12,5*12,15000.,0), + np.pmt(0.08/12,5*12,15000.,0,'end'), 4) + assert_almost_equal(np.pmt(0.08/12,5*12,15000.,0,0), + np.pmt(0.08/12,5*12,15000.,0,'end'), 4) + + # begin + assert_almost_equal(np.ppmt(0.1/12,1,60,55000,0,1), + np.ppmt(0.1/12,1,60,55000,0,'begin'), 4) + # end + assert_almost_equal(np.ppmt(0.1/12,1,60,55000,0), + np.ppmt(0.1/12,1,60,55000,0,'end'), 4) + assert_almost_equal(np.ppmt(0.1/12,1,60,55000,0,0), + np.ppmt(0.1/12,1,60,55000,0,'end'), 4) + + # begin + assert_almost_equal(np.ipmt(0.1/12,1,24,2000,0,1), + np.ipmt(0.1/12,1,24,2000,0,'begin'), 4) + # end + assert_almost_equal(np.ipmt(0.1/12,1,24,2000,0), + np.ipmt(0.1/12,1,24,2000,0,'end'), 4) + assert_almost_equal(np.ipmt(0.1/12,1,24,2000,0,0), + np.ipmt(0.1/12,1,24,2000,0,'end'), 4) + + # begin + assert_almost_equal(np.nper(0.075,-2000,0,100000.,1), + np.nper(0.075,-2000,0,100000.,'begin'), 4) + # end + assert_almost_equal(np.nper(0.075,-2000,0,100000.), + np.nper(0.075,-2000,0,100000.,'end'), 4) + assert_almost_equal(np.nper(0.075,-2000,0,100000.,0), + np.nper(0.075,-2000,0,100000.,'end'), 4) + + def test_broadcast(self): + assert_almost_equal(np.nper(0.075,-2000,0,100000.,[0,1]), + [ 21.5449442 , 20.76156441], 4) + + assert_almost_equal(np.ipmt(0.1/12,range(5), 24, 2000), + [-17.29165168, -16.66666667, -16.03647345, + -15.40102862, -14.76028842], 4) + + assert_almost_equal(np.ppmt(0.1/12,range(5), 24, 2000), + [-74.998201 , -75.62318601, -76.25337923, + -76.88882405, -77.52956425], 4) + + assert_almost_equal(np.ppmt(0.1/12,range(5), 24, 2000, 0, + [0,0,1,'end','begin']), + [-74.998201 , -75.62318601, -75.62318601, + -76.88882405, -76.88882405], 4) if __name__ == "__main__": run_module_suite() From 7c48e568f16235047feb24652bc73d17216b586b Mon Sep 17 00:00:00 2001 From: Charles Harris Date: Wed, 27 Feb 2013 13:26:58 -0700 Subject: [PATCH 30/65] 2to3: Put `from __future__ import division in every python file. This should be harmless, as we already are division clean. However, placement of this import takes some care. In the future a script can be used to append new features without worry, at least until such time as it exceeds a single line. Having that ability will make it easier to deal with absolute imports and printing updates. --- numpy/lib/financial.py | 17 +++++++++++------ numpy/lib/tests/test_financial.py | 2 ++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index 599a361..5887a92 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -1,10 +1,15 @@ -# Some simple financial calculations -# patterned after spreadsheet computations. +"""Some simple financial calculations + +patterned after spreadsheet computations. + +There is some complexity in each function +so that the functions behave like ufuncs with +broadcasting and being able to be called with scalars +or arrays (or other sequences). + +""" +from __future__ import division -# There is some complexity in each function -# so that the functions behave like ufuncs with -# broadcasting and being able to be called with scalars -# or arrays (or other sequences). import numpy as np __all__ = ['fv', 'pmt', 'nper', 'ipmt', 'ppmt', 'pv', 'rate', diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py index 5fe9761..e06c50e 100644 --- a/numpy/lib/tests/test_financial.py +++ b/numpy/lib/tests/test_financial.py @@ -1,3 +1,5 @@ +from __future__ import division + from numpy.testing import * import numpy as np From 3883ea1a6bd533d2df21a9807bb91769078d20ed Mon Sep 17 00:00:00 2001 From: Charles Harris Date: Tue, 5 Mar 2013 21:43:22 -0700 Subject: [PATCH 31/65] 2to3: Replace xrange by range and use list(range(...)) where needed In python3 range is an iterator and `xrange` has been removed. This has two consequence for code: 1) Where a list is needed `list(range(...))` must be used. 2) `xrange` must be replaced by `range` Both of these changes also work in python2 and this patch makes both. There are three places fixed that do not need it, but I left them in so that the result would be `xrange` clean. Closes #3092 --- numpy/lib/tests/test_financial.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py index e06c50e..d11195f 100644 --- a/numpy/lib/tests/test_financial.py +++ b/numpy/lib/tests/test_financial.py @@ -124,15 +124,15 @@ def test_broadcast(self): assert_almost_equal(np.nper(0.075,-2000,0,100000.,[0,1]), [ 21.5449442 , 20.76156441], 4) - assert_almost_equal(np.ipmt(0.1/12,range(5), 24, 2000), + assert_almost_equal(np.ipmt(0.1/12,list(range(5)), 24, 2000), [-17.29165168, -16.66666667, -16.03647345, -15.40102862, -14.76028842], 4) - assert_almost_equal(np.ppmt(0.1/12,range(5), 24, 2000), + assert_almost_equal(np.ppmt(0.1/12,list(range(5)), 24, 2000), [-74.998201 , -75.62318601, -76.25337923, -76.88882405, -77.52956425], 4) - assert_almost_equal(np.ppmt(0.1/12,range(5), 24, 2000, 0, + assert_almost_equal(np.ppmt(0.1/12,list(range(5)), 24, 2000, 0, [0,0,1,'end','begin']), [-74.998201 , -75.62318601, -75.62318601, -76.88882405, -76.88882405], 4) From ce6aa9b98390f3bf6d5a44f257e7e6d2afa17767 Mon Sep 17 00:00:00 2001 From: Charles Harris Date: Wed, 27 Mar 2013 21:49:08 -0600 Subject: [PATCH 32/65] 2to3: Use absolute imports. The new import `absolute_import` is added the `from __future__ import` statement and The 2to3 `import` fixer is run to make the imports compatible. There are several things that need to be dealt with to make this work. 1) Files meant to be run as scripts run in a different environment than files imported as part of a package, and so changes to those files need to be skipped. The affected script files are: * all setup.py files * numpy/core/code_generators/generate_umath.py * numpy/core/code_generators/generate_numpy_api.py * numpy/core/code_generators/generate_ufunc_api.py 2) Some imported modules are not available as they are created during the build process and consequently 2to3 is unable to handle them correctly. Files that import those modules need a bit of extra work. The affected files are: * core/__init__.py, * core/numeric.py, * core/_internal.py, * core/arrayprint.py, * core/fromnumeric.py, * numpy/__init__.py, * lib/npyio.py, * lib/function_base.py, * fft/fftpack.py, * random/__init__.py Closes #3172 --- numpy/lib/financial.py | 2 +- numpy/lib/tests/test_financial.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index 5887a92..2a3295b 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -8,7 +8,7 @@ or arrays (or other sequences). """ -from __future__ import division +from __future__ import division, absolute_import import numpy as np diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py index d11195f..56681db 100644 --- a/numpy/lib/tests/test_financial.py +++ b/numpy/lib/tests/test_financial.py @@ -1,4 +1,4 @@ -from __future__ import division +from __future__ import division, absolute_import from numpy.testing import * import numpy as np From f65a6f589f1ecded9c53a381697b65d53c904f59 Mon Sep 17 00:00:00 2001 From: Charles Harris Date: Sat, 6 Apr 2013 13:25:26 -0600 Subject: [PATCH 33/65] 2to3: Apply `print` fixer. Add `print_function` to all `from __future__ import ...` statements and use the python3 print function syntax everywhere. Closes #3078. --- numpy/lib/financial.py | 2 +- numpy/lib/tests/test_financial.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index 2a3295b..28e63f5 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -8,7 +8,7 @@ or arrays (or other sequences). """ -from __future__ import division, absolute_import +from __future__ import division, absolute_import, print_function import numpy as np diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py index 56681db..d769485 100644 --- a/numpy/lib/tests/test_financial.py +++ b/numpy/lib/tests/test_financial.py @@ -1,4 +1,4 @@ -from __future__ import division, absolute_import +from __future__ import division, absolute_import, print_function from numpy.testing import * import numpy as np From 21170e155b033453506b2efd6fa0971d63427654 Mon Sep 17 00:00:00 2001 From: Charles Harris Date: Tue, 9 Apr 2013 12:44:24 -0600 Subject: [PATCH 34/65] 2to3: Apply `map` fixer. In Python 3 `map` is an iterator while in Python 2 it returns a list. The simple fix applied by the fixer is to inclose all instances of map with `list(...)`. This is not needed in all cases, and even where appropriate list comprehensions may be preferred for their clarity. Consequently, this patch attempts to use list comprehensions where it makes sense. When the mapped function has two arguments there is another problem that can arise. In Python 3 map stops execution when the shortest argument list is exhausted, while in Python 2 it stops when the longest argument list is exhausted. Consequently the two argument case might need special care. However, we have been running Python3 converted versions of numpy since 1.5 without problems, so it is probably not something that affects us. Closes #3068 --- numpy/lib/financial.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index 28e63f5..cd21db0 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -115,7 +115,7 @@ def fv(rate, nper, pmt, pv, when='end'): """ when = _convert_when(when) - rate, nper, pmt, pv, when = map(np.asarray, [rate, nper, pmt, pv, when]) + (rate, nper, pmt, pv, when) = map(np.asarray, [rate, nper, pmt, pv, when]) temp = (1+rate)**nper miter = np.broadcast(rate, nper, pmt, pv, when) zer = np.zeros(miter.shape) @@ -206,7 +206,7 @@ def pmt(rate, nper, pv, fv=0, when='end'): """ when = _convert_when(when) - rate, nper, pv, fv, when = map(np.asarray, [rate, nper, pv, fv, when]) + (rate, nper, pv, fv, when) = map(np.asarray, [rate, nper, pv, fv, when]) temp = (1+rate)**nper miter = np.broadcast(rate, nper, pv, fv, when) zer = np.zeros(miter.shape) @@ -263,7 +263,7 @@ def nper(rate, pmt, pv, fv=0, when='end'): """ when = _convert_when(when) - rate, pmt, pv, fv, when = map(np.asarray, [rate, pmt, pv, fv, when]) + (rate, pmt, pv, fv, when) = map(np.asarray, [rate, pmt, pv, fv, when]) use_zero_rate = False old_err = np.seterr(divide="raise") @@ -502,7 +502,7 @@ def pv(rate, nper, pmt, fv=0.0, when='end'): """ when = _convert_when(when) - rate, nper, pmt, fv, when = map(np.asarray, [rate, nper, pmt, fv, when]) + (rate, nper, pmt, fv, when) = map(np.asarray, [rate, nper, pmt, fv, when]) temp = (1+rate)**nper miter = np.broadcast(rate, nper, pmt, fv, when) zer = np.zeros(miter.shape) @@ -568,7 +568,7 @@ def rate(nper, pmt, pv, fv, when='end', guess=0.10, tol=1e-6, maxiter=100): """ when = _convert_when(when) - nper, pmt, pv, fv, when = map(np.asarray, [nper, pmt, pv, fv, when]) + (nper, pmt, pv, fv, when) = map(np.asarray, [nper, pmt, pv, fv, when]) rn = guess iter = 0 close = False From 24fcd99882fb17a9ce6ce0f059f7f1b3146bd60d Mon Sep 17 00:00:00 2001 From: bebert218 Date: Wed, 22 May 2013 13:58:38 +0900 Subject: [PATCH 35/65] BUG: The npv function in financial.py was incorrectly implemented. Correct the implementation of the npv function, its documentation, and the mirr function that depends on it. The test_financial.py is also corrected to take into account those modifications The npv function behavior was contrary to what the documentation stated as it summed indexes 1 to M instead of 0 to M-1. The mirr function used a corrective factor to get the correct result in spite of that error so that factor is removed. Closes #649 --- numpy/lib/financial.py | 10 +++++----- numpy/lib/tests/test_financial.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index cd21db0..0be12f2 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -675,7 +675,7 @@ def npv(rate, values): ----- Returns the result of: [G]_ - .. math :: \\sum_{t=0}^M{\\frac{values_t}{(1+rate)^{t}}} + .. math :: \\sum_{t=0}^{M-1}{\\frac{values_t}{(1+rate)^{t}}} References ---------- @@ -685,13 +685,13 @@ def npv(rate, values): Examples -------- >>> np.npv(0.281,[-100, 39, 59, 55, 20]) - -0.0066187288356340801 + -0.0084785916384548798 (Compare with the Example given for numpy.lib.financial.irr) """ values = np.asarray(values) - return (values / (1+rate)**np.arange(1,len(values)+1)).sum(axis=0) + return (values / (1+rate)**np.arange(0,len(values))).sum(axis=0) def mirr(values, finance_rate, reinvest_rate): """ @@ -720,8 +720,8 @@ def mirr(values, finance_rate, reinvest_rate): neg = values < 0 if not (pos.any() and neg.any()): return np.nan - numer = np.abs(npv(reinvest_rate, values*pos))*(1 + reinvest_rate) - denom = np.abs(npv(finance_rate, values*neg))*(1 + finance_rate) + numer = np.abs(npv(reinvest_rate, values*pos)) + denom = np.abs(npv(finance_rate, values*neg)) return (numer/denom)**(1.0/(n - 1))*(1 + reinvest_rate) - 1 if __name__ == '__main__': diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py index d769485..1a276a4 100644 --- a/numpy/lib/tests/test_financial.py +++ b/numpy/lib/tests/test_financial.py @@ -41,7 +41,7 @@ def test_nper2(self): def test_npv(self): assert_almost_equal(np.npv(0.05,[-15000,1500,2500,3500,4500,6000]), - 117.04, 2) + 122.89, 2) def test_mirr(self): val = [-4500,-800,800,800,600,600,800,800,700,3000] From 43a21b46087ad234b76e4a5fbe98b989da2c049a Mon Sep 17 00:00:00 2001 From: Charles Harris Date: Thu, 11 Jul 2013 16:49:04 -0600 Subject: [PATCH 36/65] MAINT: Use np.errstate context manager. Now that Python < 2.6 is no longer supported we can use the errstate context manager in places where constructs like ``` old = seterr(invalid='ignore') try: blah finally: seterr(**old) ``` were used. --- numpy/lib/financial.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index 0be12f2..8cac117 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -266,14 +266,11 @@ def nper(rate, pmt, pv, fv=0, when='end'): (rate, pmt, pv, fv, when) = map(np.asarray, [rate, pmt, pv, fv, when]) use_zero_rate = False - old_err = np.seterr(divide="raise") - try: + with np.errstate(divide="raise"): try: z = pmt*(1.0+rate*when)/rate except FloatingPointError: use_zero_rate = True - finally: - np.seterr(**old_err) if use_zero_rate: return (-fv + pv) / (pmt + 0.0) From e0adfdf71efe6e09f5c8d5f57a8f98ea46e5afe5 Mon Sep 17 00:00:00 2001 From: Charles Harris Date: Sun, 18 Aug 2013 11:51:25 -0600 Subject: [PATCH 37/65] STY: Giant comma spacing fixup. Run the 2to3 ws_comma fixer on *.py files. Some lines are now too long and will need to be broken at some point. OTOH, some lines were already too long and need to be broken at some point. Now seems as good a time as any to do this with open PRs at a minimum. --- numpy/lib/financial.py | 2 +- numpy/lib/tests/test_financial.py | 126 +++++++++++++++--------------- 2 files changed, 64 insertions(+), 64 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index 8cac117..ec642af 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -688,7 +688,7 @@ def npv(rate, values): """ values = np.asarray(values) - return (values / (1+rate)**np.arange(0,len(values))).sum(axis=0) + return (values / (1+rate)**np.arange(0, len(values))).sum(axis=0) def mirr(values, finance_rate, reinvest_rate): """ diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py index 1a276a4..1894da8 100644 --- a/numpy/lib/tests/test_financial.py +++ b/numpy/lib/tests/test_financial.py @@ -5,7 +5,7 @@ class TestFinancial(TestCase): def test_rate(self): - assert_almost_equal(np.rate(10,0,-3500,10000), + assert_almost_equal(np.rate(10, 0, -3500, 10000), 0.1107, 4) def test_irr(self): @@ -14,127 +14,127 @@ def test_irr(self): 0.0524, 2) def test_pv(self): - assert_almost_equal(np.pv(0.07,20,12000,0), + assert_almost_equal(np.pv(0.07, 20, 12000, 0), -127128.17, 2) def test_fv(self): - assert_almost_equal(np.fv(0.075, 20, -2000,0,0), + assert_almost_equal(np.fv(0.075, 20, -2000, 0, 0), 86609.36, 2) def test_pmt(self): - assert_almost_equal(np.pmt(0.08/12,5*12,15000), + assert_almost_equal(np.pmt(0.08/12, 5*12, 15000), -304.146, 3) def test_ppmt(self): - np.round(np.ppmt(0.1/12,1,60,55000),2) == 710.25 + np.round(np.ppmt(0.1/12, 1, 60, 55000), 2) == 710.25 def test_ipmt(self): - np.round(np.ipmt(0.1/12,1,24,2000),2) == 16.67 + np.round(np.ipmt(0.1/12, 1, 24, 2000), 2) == 16.67 def test_nper(self): - assert_almost_equal(np.nper(0.075,-2000,0,100000.), + assert_almost_equal(np.nper(0.075, -2000, 0, 100000.), 21.54, 2) def test_nper2(self): - assert_almost_equal(np.nper(0.0,-2000,0,100000.), + assert_almost_equal(np.nper(0.0, -2000, 0, 100000.), 50.0, 1) def test_npv(self): - assert_almost_equal(np.npv(0.05,[-15000,1500,2500,3500,4500,6000]), + assert_almost_equal(np.npv(0.05, [-15000, 1500, 2500, 3500, 4500, 6000]), 122.89, 2) def test_mirr(self): - val = [-4500,-800,800,800,600,600,800,800,700,3000] + val = [-4500, -800, 800, 800, 600, 600, 800, 800, 700, 3000] assert_almost_equal(np.mirr(val, 0.08, 0.055), 0.0666, 4) - val = [-120000,39000,30000,21000,37000,46000] + val = [-120000, 39000, 30000, 21000, 37000, 46000] assert_almost_equal(np.mirr(val, 0.10, 0.12), 0.126094, 6) - val = [100,200,-50,300,-200] + val = [100, 200, -50, 300, -200] assert_almost_equal(np.mirr(val, 0.05, 0.06), 0.3428, 4) - val = [39000,30000,21000,37000,46000] + val = [39000, 30000, 21000, 37000, 46000] assert_(np.isnan(np.mirr(val, 0.10, 0.12))) def test_when(self): #begin - assert_almost_equal(np.rate(10,20,-3500,10000,1), - np.rate(10,20,-3500,10000,'begin'), 4) + assert_almost_equal(np.rate(10, 20, -3500, 10000, 1), + np.rate(10, 20, -3500, 10000, 'begin'), 4) #end - assert_almost_equal(np.rate(10,20,-3500,10000), - np.rate(10,20,-3500,10000,'end'), 4) - assert_almost_equal(np.rate(10,20,-3500,10000,0), - np.rate(10,20,-3500,10000,'end'), 4) + assert_almost_equal(np.rate(10, 20, -3500, 10000), + np.rate(10, 20, -3500, 10000, 'end'), 4) + assert_almost_equal(np.rate(10, 20, -3500, 10000, 0), + np.rate(10, 20, -3500, 10000, 'end'), 4) # begin - assert_almost_equal(np.pv(0.07,20,12000,0,1), - np.pv(0.07,20,12000,0,'begin'), 2) + assert_almost_equal(np.pv(0.07, 20, 12000, 0, 1), + np.pv(0.07, 20, 12000, 0, 'begin'), 2) # end - assert_almost_equal(np.pv(0.07,20,12000,0), - np.pv(0.07,20,12000,0,'end'), 2) - assert_almost_equal(np.pv(0.07,20,12000,0,0), - np.pv(0.07,20,12000,0,'end'), 2) + assert_almost_equal(np.pv(0.07, 20, 12000, 0), + np.pv(0.07, 20, 12000, 0, 'end'), 2) + assert_almost_equal(np.pv(0.07, 20, 12000, 0, 0), + np.pv(0.07, 20, 12000, 0, 'end'), 2) # begin - assert_almost_equal(np.fv(0.075, 20, -2000,0,1), - np.fv(0.075, 20, -2000,0,'begin'), 4) + assert_almost_equal(np.fv(0.075, 20, -2000, 0, 1), + np.fv(0.075, 20, -2000, 0, 'begin'), 4) # end - assert_almost_equal(np.fv(0.075, 20, -2000,0), - np.fv(0.075, 20, -2000,0,'end'), 4) - assert_almost_equal(np.fv(0.075, 20, -2000,0,0), - np.fv(0.075, 20, -2000,0,'end'), 4) + assert_almost_equal(np.fv(0.075, 20, -2000, 0), + np.fv(0.075, 20, -2000, 0, 'end'), 4) + assert_almost_equal(np.fv(0.075, 20, -2000, 0, 0), + np.fv(0.075, 20, -2000, 0, 'end'), 4) # begin - assert_almost_equal(np.pmt(0.08/12,5*12,15000.,0,1), - np.pmt(0.08/12,5*12,15000.,0,'begin'), 4) + assert_almost_equal(np.pmt(0.08/12, 5*12, 15000., 0, 1), + np.pmt(0.08/12, 5*12, 15000., 0, 'begin'), 4) # end - assert_almost_equal(np.pmt(0.08/12,5*12,15000.,0), - np.pmt(0.08/12,5*12,15000.,0,'end'), 4) - assert_almost_equal(np.pmt(0.08/12,5*12,15000.,0,0), - np.pmt(0.08/12,5*12,15000.,0,'end'), 4) + assert_almost_equal(np.pmt(0.08/12, 5*12, 15000., 0), + np.pmt(0.08/12, 5*12, 15000., 0, 'end'), 4) + assert_almost_equal(np.pmt(0.08/12, 5*12, 15000., 0, 0), + np.pmt(0.08/12, 5*12, 15000., 0, 'end'), 4) # begin - assert_almost_equal(np.ppmt(0.1/12,1,60,55000,0,1), - np.ppmt(0.1/12,1,60,55000,0,'begin'), 4) + assert_almost_equal(np.ppmt(0.1/12, 1, 60, 55000, 0, 1), + np.ppmt(0.1/12, 1, 60, 55000, 0, 'begin'), 4) # end - assert_almost_equal(np.ppmt(0.1/12,1,60,55000,0), - np.ppmt(0.1/12,1,60,55000,0,'end'), 4) - assert_almost_equal(np.ppmt(0.1/12,1,60,55000,0,0), - np.ppmt(0.1/12,1,60,55000,0,'end'), 4) + assert_almost_equal(np.ppmt(0.1/12, 1, 60, 55000, 0), + np.ppmt(0.1/12, 1, 60, 55000, 0, 'end'), 4) + assert_almost_equal(np.ppmt(0.1/12, 1, 60, 55000, 0, 0), + np.ppmt(0.1/12, 1, 60, 55000, 0, 'end'), 4) # begin - assert_almost_equal(np.ipmt(0.1/12,1,24,2000,0,1), - np.ipmt(0.1/12,1,24,2000,0,'begin'), 4) + assert_almost_equal(np.ipmt(0.1/12, 1, 24, 2000, 0, 1), + np.ipmt(0.1/12, 1, 24, 2000, 0, 'begin'), 4) # end - assert_almost_equal(np.ipmt(0.1/12,1,24,2000,0), - np.ipmt(0.1/12,1,24,2000,0,'end'), 4) - assert_almost_equal(np.ipmt(0.1/12,1,24,2000,0,0), - np.ipmt(0.1/12,1,24,2000,0,'end'), 4) + assert_almost_equal(np.ipmt(0.1/12, 1, 24, 2000, 0), + np.ipmt(0.1/12, 1, 24, 2000, 0, 'end'), 4) + assert_almost_equal(np.ipmt(0.1/12, 1, 24, 2000, 0, 0), + np.ipmt(0.1/12, 1, 24, 2000, 0, 'end'), 4) # begin - assert_almost_equal(np.nper(0.075,-2000,0,100000.,1), - np.nper(0.075,-2000,0,100000.,'begin'), 4) + assert_almost_equal(np.nper(0.075, -2000, 0, 100000., 1), + np.nper(0.075, -2000, 0, 100000., 'begin'), 4) # end - assert_almost_equal(np.nper(0.075,-2000,0,100000.), - np.nper(0.075,-2000,0,100000.,'end'), 4) - assert_almost_equal(np.nper(0.075,-2000,0,100000.,0), - np.nper(0.075,-2000,0,100000.,'end'), 4) + assert_almost_equal(np.nper(0.075, -2000, 0, 100000.), + np.nper(0.075, -2000, 0, 100000., 'end'), 4) + assert_almost_equal(np.nper(0.075, -2000, 0, 100000., 0), + np.nper(0.075, -2000, 0, 100000., 'end'), 4) def test_broadcast(self): - assert_almost_equal(np.nper(0.075,-2000,0,100000.,[0,1]), - [ 21.5449442 , 20.76156441], 4) + assert_almost_equal(np.nper(0.075, -2000, 0, 100000., [0, 1]), + [ 21.5449442, 20.76156441], 4) - assert_almost_equal(np.ipmt(0.1/12,list(range(5)), 24, 2000), + assert_almost_equal(np.ipmt(0.1/12, list(range(5)), 24, 2000), [-17.29165168, -16.66666667, -16.03647345, -15.40102862, -14.76028842], 4) - assert_almost_equal(np.ppmt(0.1/12,list(range(5)), 24, 2000), - [-74.998201 , -75.62318601, -76.25337923, + assert_almost_equal(np.ppmt(0.1/12, list(range(5)), 24, 2000), + [-74.998201, -75.62318601, -76.25337923, -76.88882405, -77.52956425], 4) - assert_almost_equal(np.ppmt(0.1/12,list(range(5)), 24, 2000, 0, - [0,0,1,'end','begin']), - [-74.998201 , -75.62318601, -75.62318601, + assert_almost_equal(np.ppmt(0.1/12, list(range(5)), 24, 2000, 0, + [0, 0, 1, 'end', 'begin']), + [-74.998201, -75.62318601, -75.62318601, -76.88882405, -76.88882405], 4) if __name__ == "__main__": From 90a46a5da578e4c0ba9f327cbbbb4899448b98b9 Mon Sep 17 00:00:00 2001 From: Charles Harris Date: Mon, 2 Sep 2013 13:21:48 -0600 Subject: [PATCH 38/65] STY: Make numpy/lib/test/*.py PEP8 compliant. Run autopep8 over the test files in numpy/lib/test and make fixes to the result. Also remove Python5 workaround. --- numpy/lib/tests/test_financial.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py index 1894da8..6b7c6ef 100644 --- a/numpy/lib/tests/test_financial.py +++ b/numpy/lib/tests/test_financial.py @@ -3,6 +3,7 @@ from numpy.testing import * import numpy as np + class TestFinancial(TestCase): def test_rate(self): assert_almost_equal(np.rate(10, 0, -3500, 10000), @@ -40,8 +41,9 @@ def test_nper2(self): 50.0, 1) def test_npv(self): - assert_almost_equal(np.npv(0.05, [-15000, 1500, 2500, 3500, 4500, 6000]), - 122.89, 2) + assert_almost_equal( + np.npv(0.05, [-15000, 1500, 2500, 3500, 4500, 6000]), + 122.89, 2) def test_mirr(self): val = [-4500, -800, 800, 800, 600, 600, 800, 800, 700, 3000] @@ -122,7 +124,7 @@ def test_when(self): def test_broadcast(self): assert_almost_equal(np.nper(0.075, -2000, 0, 100000., [0, 1]), - [ 21.5449442, 20.76156441], 4) + [21.5449442, 20.76156441], 4) assert_almost_equal(np.ipmt(0.1/12, list(range(5)), 24, 2000), [-17.29165168, -16.66666667, -16.03647345, @@ -133,7 +135,7 @@ def test_broadcast(self): -76.88882405, -77.52956425], 4) assert_almost_equal(np.ppmt(0.1/12, list(range(5)), 24, 2000, 0, - [0, 0, 1, 'end', 'begin']), + [0, 0, 1, 'end', 'begin']), [-74.998201, -75.62318601, -75.62318601, -76.88882405, -76.88882405], 4) From 332bb7f8732e27df43bbdf0af40473f2631af0a8 Mon Sep 17 00:00:00 2001 From: Philip Eliot Date: Sat, 1 Feb 2014 07:48:46 -0500 Subject: [PATCH 39/65] BUG: IRR was returning nan instead of valid negative answer. This change corrects the following two bugs in numpy.irr: * When the solution was negative, numpy.irr returned nan instead of the correct solution because of the mask applied to the roots. Corrected by removing the mask that 0 < res < 1. * When multiple roots were found, numpy.irr was returning an array of all roots rather than a single float. This bug was corrected by selecting the single root closest to zero (min(abs(root)). With these corrections, numpy.irr returns the same result as the corresponding spreadsheet function in LibreOffice Calc for all test cases (additional test cases were added to cover cases with multiple positive and negative roots) --- numpy/lib/financial.py | 20 ++++++++++++++------ numpy/lib/tests/test_financial.py | 15 +++++++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index ec642af..c085a5d 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -628,21 +628,29 @@ def irr(values): Examples -------- - >>> print round(np.irr([-100, 39, 59, 55, 20]), 5) + >>> round(irr([-100, 39, 59, 55, 20]), 5) 0.28095 + >>> round(irr([-100, 0, 0, 74]), 5) + -0.0955 + >>> round(irr([-100, 100, 0, -7]), 5) + -0.0833 + >>> round(irr([-100, 100, 0, 7]), 5) + 0.06206 + >>> round(irr([-5, 10.5, 1, -8, 1]), 5) + 0.0886 (Compare with the Example given for numpy.lib.financial.npv) """ res = np.roots(values[::-1]) - # Find the root(s) between 0 and 1 - mask = (res.imag == 0) & (res.real > 0) & (res.real <= 1) - res = res[mask].real + mask = (res.imag == 0) & (res.real > 0) if res.size == 0: return np.nan + res = res[mask].real + # NPV(rate) = 0 can have more than one solution so we return + # only the solution closest to zero. rate = 1.0/res - 1 - if rate.size == 1: - rate = rate.item() + rate = rate.item(np.argmin(np.abs(rate))) return rate def npv(rate, values): diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py index 6b7c6ef..41a060a 100644 --- a/numpy/lib/tests/test_financial.py +++ b/numpy/lib/tests/test_financial.py @@ -13,6 +13,21 @@ def test_irr(self): v = [-150000, 15000, 25000, 35000, 45000, 60000] assert_almost_equal(np.irr(v), 0.0524, 2) + v = [-100, 0, 0, 74] + assert_almost_equal(np.irr(v), + -0.0955, 2) + v = [-100, 39, 59, 55, 20] + assert_almost_equal(np.irr(v), + 0.28095, 2) + v = [-100, 100, 0, -7] + assert_almost_equal(np.irr(v), + -0.0833, 2) + v = [-100, 100, 0, 7] + assert_almost_equal(np.irr(v), + 0.06206, 2) + v = [-5, 10.5, 1, -8, 1] + assert_almost_equal(np.irr(v), + 0.0886, 2) def test_pv(self): assert_almost_equal(np.pv(0.07, 20, 12000, 0), From 3629126138ebd191f24abcb4dc21e3243884ea13 Mon Sep 17 00:00:00 2001 From: Charles Harris Date: Wed, 30 Jul 2014 14:17:00 -0600 Subject: [PATCH 40/65] MAINT: Fix problems noted by pyflakes in numpy/lib/tests. --- numpy/lib/tests/test_financial.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py index 41a060a..f02cfb3 100644 --- a/numpy/lib/tests/test_financial.py +++ b/numpy/lib/tests/test_financial.py @@ -1,7 +1,9 @@ from __future__ import division, absolute_import, print_function -from numpy.testing import * import numpy as np +from numpy.testing import ( + run_module_suite, TestCase, assert_, assert_almost_equal + ) class TestFinancial(TestCase): From 5797f5465e454fa892ad87dcb61cbd3a7d424e8e Mon Sep 17 00:00:00 2001 From: Charles Harris Date: Wed, 30 Jul 2014 15:14:11 -0600 Subject: [PATCH 41/65] STY: PEP8 compliance for numpy/lib/tests. The possibly controversial part of this is making the nested array value lists PEP8 compliant, as there is something to be said aligning the values for clarity. In the end, it seemed like the easiest thing to do was to make them PEP8 compliant. The eye can get used to that. --- numpy/lib/tests/test_financial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py index f02cfb3..a4b9cfe 100644 --- a/numpy/lib/tests/test_financial.py +++ b/numpy/lib/tests/test_financial.py @@ -141,7 +141,7 @@ def test_when(self): def test_broadcast(self): assert_almost_equal(np.nper(0.075, -2000, 0, 100000., [0, 1]), - [21.5449442, 20.76156441], 4) + [21.5449442, 20.76156441], 4) assert_almost_equal(np.ipmt(0.1/12, list(range(5)), 24, 2000), [-17.29165168, -16.66666667, -16.03647345, From 95a41941237d07b33ab2776a431512b7f6bdb4a0 Mon Sep 17 00:00:00 2001 From: Charles Harris Date: Wed, 30 Jul 2014 16:48:11 -0600 Subject: [PATCH 42/65] MAINT: Fixes for problems in numpy/lib revealed by pyflakes. Some of those problems look like potential coding errors. In those cases a Fixme comment was made and the offending code, usually an unused variable, was commented out. --- numpy/lib/financial.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index c085a5d..e546144 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -718,7 +718,6 @@ def mirr(values, finance_rate, reinvest_rate): Modified internal rate of return """ - values = np.asarray(values, dtype=np.double) n = values.size pos = values > 0 @@ -728,8 +727,3 @@ def mirr(values, finance_rate, reinvest_rate): numer = np.abs(npv(reinvest_rate, values*pos)) denom = np.abs(npv(finance_rate, values*neg)) return (numer/denom)**(1.0/(n - 1))*(1 + reinvest_rate) - 1 - -if __name__ == '__main__': - import doctest - import numpy as np - doctest.testmod(verbose=True) From 891af8256105cf3f1bf9515d7923edbb16abc21d Mon Sep 17 00:00:00 2001 From: Charles Harris Date: Wed, 30 Jul 2014 18:06:28 -0600 Subject: [PATCH 43/65] STY: Make files in numpy/lib PEP8 compliant. The rules enforced are the same as those used for scipy. --- numpy/lib/financial.py | 62 ++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index e546144..5b96e5b 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -119,7 +119,8 @@ def fv(rate, nper, pmt, pv, when='end'): temp = (1+rate)**nper miter = np.broadcast(rate, nper, pmt, pv, when) zer = np.zeros(miter.shape) - fact = np.where(rate==zer, nper+zer, (1+rate*when)*(temp-1)/rate+zer) + fact = np.where(rate == zer, nper + zer, + (1 + rate*when)*(temp - 1)/rate + zer) return -(pv*temp + pmt*fact) def pmt(rate, nper, pv, fv=0, when='end'): @@ -210,7 +211,8 @@ def pmt(rate, nper, pv, fv=0, when='end'): temp = (1+rate)**nper miter = np.broadcast(rate, nper, pv, fv, when) zer = np.zeros(miter.shape) - fact = np.where(rate==zer, nper+zer, (1+rate*when)*(temp-1)/rate+zer) + fact = np.where(rate == zer, nper + zer, + (1 + rate*when)*(temp - 1)/rate + zer) return -(fv + pv*temp) / fact def nper(rate, pmt, pv, fv=0, when='end'): @@ -279,7 +281,7 @@ def nper(rate, pmt, pv, fv=0, when='end'): B = np.log((-fv+z) / (pv+z))/np.log(1.0+rate) miter = np.broadcast(rate, pmt, pv, fv, when) zer = np.zeros(miter.shape) - return np.where(rate==zer, A+zer, B+zer) + 0.0 + return np.where(rate == zer, A + zer, B + zer) + 0.0 def ipmt(rate, per, nper, pv, fv=0.0, when='end'): """ @@ -365,7 +367,8 @@ def ipmt(rate, per, nper, pv, fv=0.0, when='end'): """ when = _convert_when(when) - rate, per, nper, pv, fv, when = np.broadcast_arrays(rate, per, nper, pv, fv, when) + rate, per, nper, pv, fv, when = np.broadcast_arrays(rate, per, nper, + pv, fv, when) total_pmt = pmt(rate, nper, pv, fv, when) ipmt = _rbl(rate, per, total_pmt, pv, when)*rate try: @@ -507,12 +510,16 @@ def pv(rate, nper, pmt, fv=0.0, when='end'): return -(fv + pmt*fact)/temp # Computed with Sage -# (y + (r + 1)^n*x + p*((r + 1)^n - 1)*(r*w + 1)/r)/(n*(r + 1)^(n - 1)*x - p*((r + 1)^n - 1)*(r*w + 1)/r^2 + n*p*(r + 1)^(n - 1)*(r*w + 1)/r + p*((r + 1)^n - 1)*w/r) +# (y + (r + 1)^n*x + p*((r + 1)^n - 1)*(r*w + 1)/r)/(n*(r + 1)^(n - 1)*x - +# p*((r + 1)^n - 1)*(r*w + 1)/r^2 + n*p*(r + 1)^(n - 1)*(r*w + 1)/r + +# p*((r + 1)^n - 1)*w/r) def _g_div_gp(r, n, p, x, y, w): t1 = (r+1)**n t2 = (r+1)**(n-1) - return (y + t1*x + p*(t1 - 1)*(r*w + 1)/r)/(n*t2*x - p*(t1 - 1)*(r*w + 1)/(r**2) + n*p*t2*(r*w + 1)/r + p*(t1 - 1)*w/r) + return ((y + t1*x + p*(t1 - 1)*(r*w + 1)/r) / + (n*t2*x - p*(t1 - 1)*(r*w + 1)/(r**2) + n*p*t2*(r*w + 1)/r + + p*(t1 - 1)*w/r)) # Use Newton's iteration until the change is less than 1e-6 # for all values or a maximum of 100 iterations is reached. @@ -572,7 +579,7 @@ def rate(nper, pmt, pv, fv, when='end', guess=0.10, tol=1e-6, maxiter=100): while (iter < maxiter) and not close: rnp1 = rn - _g_div_gp(rn, nper, pmt, pv, fv, when) diff = abs(rnp1-rn) - close = np.all(diff Date: Fri, 12 Dec 2014 14:02:50 -0500 Subject: [PATCH 44/65] DOC : optional parenthesis --- numpy/lib/financial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index 5b96e5b..baff8b0 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -148,7 +148,7 @@ def pmt(rate, nper, pv, fv=0, when='end'): Number of compounding periods pv : array_like Present value - fv : array_like (optional) + fv : array_like, optional Future value (default = 0) when : {{'begin', 1}, {'end', 0}}, {string, int} When payments are due ('begin' (1) or 'end' (0)) From a08dc92acf461f37f7b65ed23e3a0b73453760e7 Mon Sep 17 00:00:00 2001 From: Fei Liu Date: Wed, 5 Nov 2014 23:20:16 -0500 Subject: [PATCH 45/65] BUG: Fix zero divide warning in financial.pmt. The pmt function in financial.py does a zero divide when rate=0 because error because the alternatives in np.where() are evaluated befor the selection is made.first before going into the function however, the denominator can be zero at that time. Closes #4701. --- numpy/lib/financial.py | 7 +++++-- numpy/lib/tests/test_financial.py | 5 +++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index baff8b0..9ec6fc0 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -211,8 +211,11 @@ def pmt(rate, nper, pv, fv=0, when='end'): temp = (1+rate)**nper miter = np.broadcast(rate, nper, pv, fv, when) zer = np.zeros(miter.shape) - fact = np.where(rate == zer, nper + zer, - (1 + rate*when)*(temp - 1)/rate + zer) + fact = np.zeros(miter.shape) + numerator = (1 + rate * when) * ( temp - 1) + np.divide(numerator, rate, where = ( rate!= 0), out= fact) + factforZeroRate = nper + zer + np.copyto(fact, factforZeroRate, where = (rate==0)) return -(fv + pv*temp) / fact def nper(rate, pmt, pv, fv=0, when='end'): diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py index a4b9cfe..a582c6f 100644 --- a/numpy/lib/tests/test_financial.py +++ b/numpy/lib/tests/test_financial.py @@ -42,6 +42,11 @@ def test_fv(self): def test_pmt(self): assert_almost_equal(np.pmt(0.08/12, 5*12, 15000), -304.146, 3) + # This is to test the edge case where rate == 0.0 + # it would fail on this case if the fix for checking rate == 0.0 was not there + assert_almost_equal(np.pmt(0.0, 5*12, 15000), -250.0, 3) + # This one tests the case where we use broadcast and arguments passed in are arrays. + assert_almost_equal(np.pmt([[0.0, 0.8],[0.3, 0.8]],[12, 3],[2000, 20000]), np.array([[-166.666, -19311.258],[-626.908, -19311.258]]), 3) def test_ppmt(self): np.round(np.ppmt(0.1/12, 1, 60, 55000), 2) == 710.25 From 5f336d5b29c3e06ced947cd8228d99820c43ca00 Mon Sep 17 00:00:00 2001 From: Charles Harris Date: Tue, 17 Feb 2015 15:01:29 -0700 Subject: [PATCH 46/65] MAINT: Simplify fix for rate == 0 in financial.pmt. --- numpy/lib/financial.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index 9ec6fc0..a7e4e60 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -208,14 +208,11 @@ def pmt(rate, nper, pv, fv=0, when='end'): """ when = _convert_when(when) (rate, nper, pv, fv, when) = map(np.asarray, [rate, nper, pv, fv, when]) - temp = (1+rate)**nper - miter = np.broadcast(rate, nper, pv, fv, when) - zer = np.zeros(miter.shape) - fact = np.zeros(miter.shape) - numerator = (1 + rate * when) * ( temp - 1) - np.divide(numerator, rate, where = ( rate!= 0), out= fact) - factforZeroRate = nper + zer - np.copyto(fact, factforZeroRate, where = (rate==0)) + temp = (1 + rate)**nper + mask = (rate == 0.0) + np.copyto(rate, 1.0, where=mask) + z = np.zeros(np.broadcast(rate, nper, pv, fv, when).shape) + fact = np.where(mask != z, nper + z, (1 + rate*when)*(temp - 1)/rate + z) return -(fv + pv*temp) / fact def nper(rate, pmt, pv, fv=0, when='end'): From 164fa4be7887474664e6e198b46b33acb5a7eff4 Mon Sep 17 00:00:00 2001 From: Charles Harris Date: Tue, 17 Feb 2015 15:02:22 -0700 Subject: [PATCH 47/65] MAINT: Fix pmt test in numpy/lib/tests/test_financial.py. The tests were using assert_almost_equal and setting the precision to 3 decimals. The reason for that low precision appears to have been the failure of the tests for a more reasonable precision. The fix was to use assert_allclose instead. --- numpy/lib/tests/test_financial.py | 46 +++++++++++++++---------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py index a582c6f..baa7854 100644 --- a/numpy/lib/tests/test_financial.py +++ b/numpy/lib/tests/test_financial.py @@ -2,7 +2,8 @@ import numpy as np from numpy.testing import ( - run_module_suite, TestCase, assert_, assert_almost_equal + run_module_suite, TestCase, assert_, assert_almost_equal, + assert_allclose ) @@ -13,40 +14,37 @@ def test_rate(self): def test_irr(self): v = [-150000, 15000, 25000, 35000, 45000, 60000] - assert_almost_equal(np.irr(v), - 0.0524, 2) + assert_almost_equal(np.irr(v), 0.0524, 2) v = [-100, 0, 0, 74] - assert_almost_equal(np.irr(v), - -0.0955, 2) + assert_almost_equal(np.irr(v), -0.0955, 2) v = [-100, 39, 59, 55, 20] - assert_almost_equal(np.irr(v), - 0.28095, 2) + assert_almost_equal(np.irr(v), 0.28095, 2) v = [-100, 100, 0, -7] - assert_almost_equal(np.irr(v), - -0.0833, 2) + assert_almost_equal(np.irr(v), -0.0833, 2) v = [-100, 100, 0, 7] - assert_almost_equal(np.irr(v), - 0.06206, 2) + assert_almost_equal(np.irr(v), 0.06206, 2) v = [-5, 10.5, 1, -8, 1] - assert_almost_equal(np.irr(v), - 0.0886, 2) + assert_almost_equal(np.irr(v), 0.0886, 2) def test_pv(self): - assert_almost_equal(np.pv(0.07, 20, 12000, 0), - -127128.17, 2) + assert_almost_equal(np.pv(0.07, 20, 12000, 0), -127128.17, 2) def test_fv(self): - assert_almost_equal(np.fv(0.075, 20, -2000, 0, 0), - 86609.36, 2) + assert_almost_equal(np.fv(0.075, 20, -2000, 0, 0), 86609.36, 2) def test_pmt(self): - assert_almost_equal(np.pmt(0.08/12, 5*12, 15000), - -304.146, 3) - # This is to test the edge case where rate == 0.0 - # it would fail on this case if the fix for checking rate == 0.0 was not there - assert_almost_equal(np.pmt(0.0, 5*12, 15000), -250.0, 3) - # This one tests the case where we use broadcast and arguments passed in are arrays. - assert_almost_equal(np.pmt([[0.0, 0.8],[0.3, 0.8]],[12, 3],[2000, 20000]), np.array([[-166.666, -19311.258],[-626.908, -19311.258]]), 3) + res = np.pmt(0.08/12, 5*12, 15000) + tgt = -304.145914 + assert_allclose(res, tgt) + # Test the edge case where rate == 0.0 + res = np.pmt(0.0, 5*12, 15000) + tgt = -250.0 + assert_allclose(res, tgt) + # Test the case where we use broadcast and + # the arguments passed in are arrays. + res = np.pmt([[0.0, 0.8],[0.3, 0.8]],[12, 3],[2000, 20000]) + tgt = np.array([[-166.66667, -19311.258],[-626.90814, -19311.258]]) + assert_allclose(res, tgt) def test_ppmt(self): np.round(np.ppmt(0.1/12, 1, 60, 55000), 2) == 710.25 From cc8c23bd36ab875bc6ba96764d31848296ced42d Mon Sep 17 00:00:00 2001 From: gfyoung Date: Sat, 19 Dec 2015 16:49:35 -0800 Subject: [PATCH 48/65] DOC: Use print only as function when print_function is imported from __future__ Closes gh-6863. --- numpy/lib/financial.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index a7e4e60..c42424d 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -247,7 +247,7 @@ def nper(rate, pmt, pv, fv=0, when='end'): If you only had $150/month to pay towards the loan, how long would it take to pay-off a loan of $8,000 at 7% annual interest? - >>> print round(np.nper(0.07/12, -150, 8000), 5) + >>> print(round(np.nper(0.07/12, -150, 8000), 5)) 64.07335 So, over 64 months would be required to pay off the loan. @@ -347,7 +347,7 @@ def ipmt(rate, per, nper, pv, fv=0.0, when='end'): >>> for payment in per: ... index = payment - 1 ... principal = principal + ppmt[index] - ... print fmt.format(payment, ppmt[index], ipmt[index], principal) + ... print(fmt.format(payment, ppmt[index], ipmt[index], principal)) 1 -200.58 -17.17 2299.42 2 -201.96 -15.79 2097.46 3 -203.35 -14.40 1894.11 From 3cc2a50ba1458bdae6e5515c7c219d8fa188c02e Mon Sep 17 00:00:00 2001 From: Simon Gibbons Date: Wed, 24 Feb 2016 20:44:39 +0000 Subject: [PATCH 49/65] BUG: np.irr should return NaN if there are no real solutions --- numpy/lib/financial.py | 2 +- numpy/lib/tests/test_financial.py | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index c42424d..f1a1a42 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -651,7 +651,7 @@ def irr(values): """ res = np.roots(values[::-1]) mask = (res.imag == 0) & (res.real > 0) - if res.size == 0: + if not mask.any(): return np.nan res = res[mask].real # NPV(rate) = 0 can have more than one solution so we return diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py index baa7854..cc8ba55 100644 --- a/numpy/lib/tests/test_financial.py +++ b/numpy/lib/tests/test_financial.py @@ -3,7 +3,7 @@ import numpy as np from numpy.testing import ( run_module_suite, TestCase, assert_, assert_almost_equal, - assert_allclose + assert_allclose, assert_equal ) @@ -26,6 +26,11 @@ def test_irr(self): v = [-5, 10.5, 1, -8, 1] assert_almost_equal(np.irr(v), 0.0886, 2) + # Test that if there is no solution then np.irr returns nan + # Fixes gh-6744 + v = [-1, -2, -3] + assert_equal(np.irr(v), np.nan) + def test_pv(self): assert_almost_equal(np.pv(0.07, 20, 12000, 0), -127128.17, 2) From 02bd2e25bb6218515b16f0ef2a7a4c7c4fb9890b Mon Sep 17 00:00:00 2001 From: KhaledTo Date: Sun, 18 Sep 2016 22:00:24 -0400 Subject: [PATCH 50/65] BUG : financial.pmt modifies input (issue #8055) --- numpy/lib/financial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index f1a1a42..931b0af 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -207,7 +207,7 @@ def pmt(rate, nper, pv, fv=0, when='end'): """ when = _convert_when(when) - (rate, nper, pv, fv, when) = map(np.asarray, [rate, nper, pv, fv, when]) + (rate, nper, pv, fv, when) = map(np.array, [rate, nper, pv, fv, when]) temp = (1 + rate)**nper mask = (rate == 0.0) np.copyto(rate, 1.0, where=mask) From d089a87591e460cdc4d2530bdd0f18395caf636b Mon Sep 17 00:00:00 2001 From: naveenarun Date: Wed, 21 Sep 2016 23:31:51 -0500 Subject: [PATCH 51/65] BUG: financial.pmt modifies input #8055 financial.pmt masked rate array in place, changing rate array. Changed to mask to a new array, preserving the original rate array. --- numpy/lib/financial.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index 931b0af..95942da 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -210,9 +210,10 @@ def pmt(rate, nper, pv, fv=0, when='end'): (rate, nper, pv, fv, when) = map(np.array, [rate, nper, pv, fv, when]) temp = (1 + rate)**nper mask = (rate == 0.0) - np.copyto(rate, 1.0, where=mask) - z = np.zeros(np.broadcast(rate, nper, pv, fv, when).shape) - fact = np.where(mask != z, nper + z, (1 + rate*when)*(temp - 1)/rate + z) + masked_rate = np.where(mask, 1.0, rate) + z = np.zeros(np.broadcast(masked_rate, nper, pv, fv, when).shape) + fact = np.where(mask != z, nper + z, + (1 + masked_rate*when)*(temp - 1)/masked_rate + z) return -(fv + pv*temp) / fact def nper(rate, pmt, pv, fv=0, when='end'): From 5b81f17da7f021ec1b6afbb0b96c7af55ffcce7a Mon Sep 17 00:00:00 2001 From: Charles Harris Date: Mon, 17 Jul 2017 17:51:33 -0600 Subject: [PATCH 52/65] TST: Remove unittest dependencies in numpy/lib/tests. --- numpy/lib/tests/test_financial.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py index cc8ba55..4db364a 100644 --- a/numpy/lib/tests/test_financial.py +++ b/numpy/lib/tests/test_financial.py @@ -2,12 +2,12 @@ import numpy as np from numpy.testing import ( - run_module_suite, TestCase, assert_, assert_almost_equal, - assert_allclose, assert_equal + run_module_suite, assert_, assert_almost_equal, assert_allclose, + assert_equal ) -class TestFinancial(TestCase): +class TestFinancial(object): def test_rate(self): assert_almost_equal(np.rate(10, 0, -3500, 10000), 0.1107, 4) From 3275ff17be70e2041a9c2babf02d71dd13bfe4f7 Mon Sep 17 00:00:00 2001 From: Garry Polley Date: Sat, 11 Nov 2017 18:49:35 -0600 Subject: [PATCH 53/65] ENH: add Decimal support to numpy.lib.financial (#9952) Adds support for Decimal to the rate, pv, fv, pmt, ppmt, ipmt, mirr, npv functions Closes #9781 --- numpy/lib/financial.py | 93 ++++++---- numpy/lib/tests/test_financial.py | 299 ++++++++++++++++++++++++------ 2 files changed, 296 insertions(+), 96 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index 95942da..06fa1bd 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -7,9 +7,13 @@ broadcasting and being able to be called with scalars or arrays (or other sequences). +Functions support the :class:`decimal.Decimal` type unless +otherwise stated. """ from __future__ import division, absolute_import, print_function +from decimal import Decimal + import numpy as np __all__ = ['fv', 'pmt', 'nper', 'ipmt', 'ppmt', 'pv', 'rate', @@ -32,7 +36,6 @@ def _convert_when(when): except (KeyError, TypeError): return [_when_to_num[x] for x in when] - def fv(rate, nper, pmt, pv, when='end'): """ Compute the future value. @@ -117,10 +120,8 @@ def fv(rate, nper, pmt, pv, when='end'): when = _convert_when(when) (rate, nper, pmt, pv, when) = map(np.asarray, [rate, nper, pmt, pv, when]) temp = (1+rate)**nper - miter = np.broadcast(rate, nper, pmt, pv, when) - zer = np.zeros(miter.shape) - fact = np.where(rate == zer, nper + zer, - (1 + rate*when)*(temp - 1)/rate + zer) + fact = np.where(rate == 0, nper, + (1 + rate*when)*(temp - 1)/rate) return -(pv*temp + pmt*fact) def pmt(rate, nper, pv, fv=0, when='end'): @@ -209,17 +210,18 @@ def pmt(rate, nper, pv, fv=0, when='end'): when = _convert_when(when) (rate, nper, pv, fv, when) = map(np.array, [rate, nper, pv, fv, when]) temp = (1 + rate)**nper - mask = (rate == 0.0) - masked_rate = np.where(mask, 1.0, rate) - z = np.zeros(np.broadcast(masked_rate, nper, pv, fv, when).shape) - fact = np.where(mask != z, nper + z, - (1 + masked_rate*when)*(temp - 1)/masked_rate + z) + mask = (rate == 0) + masked_rate = np.where(mask, 1, rate) + fact = np.where(mask != 0, nper, + (1 + masked_rate*when)*(temp - 1)/masked_rate) return -(fv + pv*temp) / fact def nper(rate, pmt, pv, fv=0, when='end'): """ Compute the number of periodic payments. + :class:`decimal.Decimal` type is not supported. + Parameters ---------- rate : array_like @@ -271,20 +273,18 @@ def nper(rate, pmt, pv, fv=0, when='end'): use_zero_rate = False with np.errstate(divide="raise"): try: - z = pmt*(1.0+rate*when)/rate + z = pmt*(1+rate*when)/rate except FloatingPointError: use_zero_rate = True if use_zero_rate: - return (-fv + pv) / (pmt + 0.0) + return (-fv + pv) / pmt else: - A = -(fv + pv)/(pmt+0.0) - B = np.log((-fv+z) / (pv+z))/np.log(1.0+rate) - miter = np.broadcast(rate, pmt, pv, fv, when) - zer = np.zeros(miter.shape) - return np.where(rate == zer, A + zer, B + zer) + 0.0 + A = -(fv + pv)/(pmt+0) + B = np.log((-fv+z) / (pv+z))/np.log(1+rate) + return np.where(rate == 0, A, B) -def ipmt(rate, per, nper, pv, fv=0.0, when='end'): +def ipmt(rate, per, nper, pv, fv=0, when='end'): """ Compute the interest portion of a payment. @@ -374,7 +374,7 @@ def ipmt(rate, per, nper, pv, fv=0.0, when='end'): ipmt = _rbl(rate, per, total_pmt, pv, when)*rate try: ipmt = np.where(when == 1, ipmt/(1 + rate), ipmt) - ipmt = np.where(np.logical_and(when == 1, per == 1), 0.0, ipmt) + ipmt = np.where(np.logical_and(when == 1, per == 1), 0, ipmt) except IndexError: pass return ipmt @@ -388,7 +388,7 @@ def _rbl(rate, per, pmt, pv, when): """ return fv(rate, (per - 1), pmt, pv, when) -def ppmt(rate, per, nper, pv, fv=0.0, when='end'): +def ppmt(rate, per, nper, pv, fv=0, when='end'): """ Compute the payment against loan principal. @@ -416,7 +416,7 @@ def ppmt(rate, per, nper, pv, fv=0.0, when='end'): total = pmt(rate, nper, pv, fv, when) return total - ipmt(rate, per, nper, pv, fv, when) -def pv(rate, nper, pmt, fv=0.0, when='end'): +def pv(rate, nper, pmt, fv=0, when='end'): """ Compute the present value. @@ -505,9 +505,7 @@ def pv(rate, nper, pmt, fv=0.0, when='end'): when = _convert_when(when) (rate, nper, pmt, fv, when) = map(np.asarray, [rate, nper, pmt, fv, when]) temp = (1+rate)**nper - miter = np.broadcast(rate, nper, pmt, fv, when) - zer = np.zeros(miter.shape) - fact = np.where(rate == zer, nper+zer, (1+rate*when)*(temp-1)/rate+zer) + fact = np.where(rate == 0, nper, (1+rate*when)*(temp-1)/rate) return -(fv + pmt*fact)/temp # Computed with Sage @@ -529,7 +527,7 @@ def _g_div_gp(r, n, p, x, y, w): # where # g(r) is the formula # g'(r) is the derivative with respect to r. -def rate(nper, pmt, pv, fv, when='end', guess=0.10, tol=1e-6, maxiter=100): +def rate(nper, pmt, pv, fv, when='end', guess=None, tol=None, maxiter=100): """ Compute the rate of interest per period. @@ -545,10 +543,10 @@ def rate(nper, pmt, pv, fv, when='end', guess=0.10, tol=1e-6, maxiter=100): Future value when : {{'begin', 1}, {'end', 0}}, {string, int}, optional When payments are due ('begin' (1) or 'end' (0)) - guess : float, optional - Starting guess for solving the rate of interest - tol : float, optional - Required tolerance for the solution + guess : Number, optional + Starting guess for solving the rate of interest, default 0.1 + tol : Number, optional + Required tolerance for the solution, default 1e-6 maxiter : int, optional Maximum iterations in finding the solution @@ -573,15 +571,26 @@ def rate(nper, pmt, pv, fv, when='end', guess=0.10, tol=1e-6, maxiter=100): """ when = _convert_when(when) + default_type = Decimal if isinstance(pmt, Decimal) else float + + # Handle casting defaults to Decimal if/when pmt is a Decimal and + # guess and/or tol are not given default values + if guess is None: + guess = default_type('0.1') + + if tol is None: + tol = default_type('1e-6') + (nper, pmt, pv, fv, when) = map(np.asarray, [nper, pmt, pv, fv, when]) + rn = guess - iter = 0 + iterator = 0 close = False - while (iter < maxiter) and not close: + while (iterator < maxiter) and not close: rnp1 = rn - _g_div_gp(rn, nper, pmt, pv, fv, when) diff = abs(rnp1-rn) close = np.all(diff < tol) - iter += 1 + iterator += 1 rn = rnp1 if not close: # Return nan's in array of the same shape as rn @@ -597,6 +606,8 @@ def irr(values): that gives a net present value of 0.0; for a more complete explanation, see Notes below. + :class:`decimal.Decimal` type is not supported. + Parameters ---------- values : array_like, shape(N,) @@ -650,6 +661,11 @@ def irr(values): (Compare with the Example given for numpy.lib.financial.npv) """ + # `np.roots` call is why this function does not support Decimal type. + # + # Ultimately Decimal support needs to be added to np.roots, which has + # greater implications on the entire linear algebra module and how it does + # eigenvalue computations. res = np.roots(values[::-1]) mask = (res.imag == 0) & (res.real > 0) if not mask.any(): @@ -657,7 +673,7 @@ def irr(values): res = res[mask].real # NPV(rate) = 0 can have more than one solution so we return # only the solution closest to zero. - rate = 1.0/res - 1 + rate = 1/res - 1 rate = rate.item(np.argmin(np.abs(rate))) return rate @@ -727,12 +743,19 @@ def mirr(values, finance_rate, reinvest_rate): Modified internal rate of return """ - values = np.asarray(values, dtype=np.double) + values = np.asarray(values) n = values.size + + # Without this explicit cast the 1/(n - 1) computation below + # becomes a float, which causes TypeError when using Decimal + # values. + if isinstance(finance_rate, Decimal): + n = Decimal(n) + pos = values > 0 neg = values < 0 if not (pos.any() and neg.any()): return np.nan numer = np.abs(npv(reinvest_rate, values*pos)) denom = np.abs(npv(finance_rate, values*neg)) - return (numer/denom)**(1.0/(n - 1))*(1 + reinvest_rate) - 1 + return (numer/denom)**(1/(n - 1))*(1 + reinvest_rate) - 1 diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py index 4db364a..c5e92db 100644 --- a/numpy/lib/tests/test_financial.py +++ b/numpy/lib/tests/test_financial.py @@ -1,16 +1,23 @@ from __future__ import division, absolute_import, print_function +from decimal import Decimal + import numpy as np from numpy.testing import ( run_module_suite, assert_, assert_almost_equal, assert_allclose, - assert_equal - ) + assert_equal, assert_raises +) class TestFinancial(object): def test_rate(self): - assert_almost_equal(np.rate(10, 0, -3500, 10000), - 0.1107, 4) + assert_almost_equal( + np.rate(10, 0, -3500, 10000), + 0.1107, 4) + + def test_rate_decimal(self): + rate = np.rate(Decimal('10'), Decimal('0'), Decimal('-3500'), Decimal('10000')) + assert_equal(Decimal('0.1106908537142689284704528100'), rate) def test_irr(self): v = [-150000, 15000, 25000, 35000, 45000, 60000] @@ -34,28 +41,84 @@ def test_irr(self): def test_pv(self): assert_almost_equal(np.pv(0.07, 20, 12000, 0), -127128.17, 2) + def test_pv_decimal(self): + assert_equal(np.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), Decimal('0')), + Decimal('-127128.1709461939327295222005')) + def test_fv(self): - assert_almost_equal(np.fv(0.075, 20, -2000, 0, 0), 86609.36, 2) + assert_equal(np.fv(0.075, 20, -2000, 0, 0), 86609.362673042924) + + def test_fv_decimal(self): + assert_equal(np.fv(Decimal('0.075'), Decimal('20'), Decimal('-2000'), 0, 0), + Decimal('86609.36267304300040536731624')) def test_pmt(self): - res = np.pmt(0.08/12, 5*12, 15000) + res = np.pmt(0.08 / 12, 5 * 12, 15000) tgt = -304.145914 assert_allclose(res, tgt) # Test the edge case where rate == 0.0 - res = np.pmt(0.0, 5*12, 15000) + res = np.pmt(0.0, 5 * 12, 15000) tgt = -250.0 assert_allclose(res, tgt) # Test the case where we use broadcast and # the arguments passed in are arrays. - res = np.pmt([[0.0, 0.8],[0.3, 0.8]],[12, 3],[2000, 20000]) - tgt = np.array([[-166.66667, -19311.258],[-626.90814, -19311.258]]) + res = np.pmt([[0.0, 0.8], [0.3, 0.8]], [12, 3], [2000, 20000]) + tgt = np.array([[-166.66667, -19311.258], [-626.90814, -19311.258]]) assert_allclose(res, tgt) + def test_pmt_decimal(self): + res = np.pmt(Decimal('0.08') / Decimal('12'), 5 * 12, 15000) + tgt = Decimal('-304.1459143262052370338701494') + assert_equal(res, tgt) + # Test the edge case where rate == 0.0 + res = np.pmt(Decimal('0'), Decimal('60'), Decimal('15000')) + tgt = -250 + assert_equal(res, tgt) + # Test the case where we use broadcast and + # the arguments passed in are arrays. + res = np.pmt([[Decimal('0'), Decimal('0.8')], [Decimal('0.3'), Decimal('0.8')]], + [Decimal('12'), Decimal('3')], [Decimal('2000'), Decimal('20000')]) + tgt = np.array([[Decimal('-166.6666666666666666666666667'), Decimal('-19311.25827814569536423841060')], + [Decimal('-626.9081401700757748402586600'), Decimal('-19311.25827814569536423841060')]]) + + # Cannot use the `assert_allclose` because it uses isfinite under the covers + # which does not support the Decimal type + # See issue: https://github.com/numpy/numpy/issues/9954 + assert_equal(res[0][0], tgt[0][0]) + assert_equal(res[0][1], tgt[0][1]) + assert_equal(res[1][0], tgt[1][0]) + assert_equal(res[1][1], tgt[1][1]) + def test_ppmt(self): - np.round(np.ppmt(0.1/12, 1, 60, 55000), 2) == 710.25 + assert_equal(np.round(np.ppmt(0.1 / 12, 1, 60, 55000), 2), -710.25) + + def test_ppmt_decimal(self): + assert_equal(np.ppmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('60'), Decimal('55000')), + Decimal('-710.2541257864217612489830917')) + + # Two tests showing how Decimal is actually getting at a more exact result + # .23 / 12 does not come out nicely as a float but does as a decimal + def test_ppmt_special_rate(self): + assert_equal(np.round(np.ppmt(0.23 / 12, 1, 60, 10000000000), 8), -90238044.232277036) + + def test_ppmt_special_rate_decimal(self): + # When rounded out to 8 decimal places like the float based test, this should not equal the same value + # as the float, substituted for the decimal + def raise_error_because_not_equal(): + assert_equal( + round(np.ppmt(Decimal('0.23') / Decimal('12'), 1, 60, Decimal('10000000000')), 8), + Decimal('-90238044.232277036')) + + assert_raises(AssertionError, raise_error_because_not_equal) + assert_equal(np.ppmt(Decimal('0.23') / Decimal('12'), 1, 60, Decimal('10000000000')), + Decimal('-90238044.2322778884413969909')) def test_ipmt(self): - np.round(np.ipmt(0.1/12, 1, 24, 2000), 2) == 16.67 + assert_almost_equal(np.round(np.ipmt(0.1 / 12, 1, 24, 2000), 2), -16.67) + + def test_ipmt_decimal(self): + result = np.ipmt(Decimal('0.1') / Decimal('12'), 1, 24, 2000) + assert_equal(result.flat[0], Decimal('-16.66666666666666666666666667')) def test_nper(self): assert_almost_equal(np.nper(0.075, -2000, 0, 100000.), @@ -70,6 +133,11 @@ def test_npv(self): np.npv(0.05, [-15000, 1500, 2500, 3500, 4500, 6000]), 122.89, 2) + def test_npv_decimal(self): + assert_equal( + np.npv(Decimal('0.05'), [-15000, 1500, 2500, 3500, 4500, 6000]), + Decimal('122.894854950942692161628715')) + def test_mirr(self): val = [-4500, -800, 800, 800, 600, 600, 800, 800, 700, 3000] assert_almost_equal(np.mirr(val, 0.08, 0.055), 0.0666, 4) @@ -83,86 +151,195 @@ def test_mirr(self): val = [39000, 30000, 21000, 37000, 46000] assert_(np.isnan(np.mirr(val, 0.10, 0.12))) + def test_mirr_decimal(self): + val = [Decimal('-4500'), Decimal('-800'), Decimal('800'), Decimal('800'), + Decimal('600'), Decimal('600'), Decimal('800'), Decimal('800'), + Decimal('700'), Decimal('3000')] + assert_equal(np.mirr(val, Decimal('0.08'), Decimal('0.055')), + Decimal('0.066597175031553548874239618')) + + val = [Decimal('-120000'), Decimal('39000'), Decimal('30000'), + Decimal('21000'), Decimal('37000'), Decimal('46000')] + assert_equal(np.mirr(val, Decimal('0.10'), Decimal('0.12')), Decimal('0.126094130365905145828421880')) + + val = [Decimal('100'), Decimal('200'), Decimal('-50'), + Decimal('300'), Decimal('-200')] + assert_equal(np.mirr(val, Decimal('0.05'), Decimal('0.06')), Decimal('0.342823387842176663647819868')) + + val = [Decimal('39000'), Decimal('30000'), Decimal('21000'), Decimal('37000'), Decimal('46000')] + assert_(np.isnan(np.mirr(val, Decimal('0.10'), Decimal('0.12')))) + def test_when(self): - #begin - assert_almost_equal(np.rate(10, 20, -3500, 10000, 1), - np.rate(10, 20, -3500, 10000, 'begin'), 4) - #end - assert_almost_equal(np.rate(10, 20, -3500, 10000), - np.rate(10, 20, -3500, 10000, 'end'), 4) - assert_almost_equal(np.rate(10, 20, -3500, 10000, 0), - np.rate(10, 20, -3500, 10000, 'end'), 4) + # begin + assert_equal(np.rate(10, 20, -3500, 10000, 1), + np.rate(10, 20, -3500, 10000, 'begin')) + # end + assert_equal(np.rate(10, 20, -3500, 10000), + np.rate(10, 20, -3500, 10000, 'end')) + assert_equal(np.rate(10, 20, -3500, 10000, 0), + np.rate(10, 20, -3500, 10000, 'end')) # begin - assert_almost_equal(np.pv(0.07, 20, 12000, 0, 1), - np.pv(0.07, 20, 12000, 0, 'begin'), 2) + assert_equal(np.pv(0.07, 20, 12000, 0, 1), + np.pv(0.07, 20, 12000, 0, 'begin')) # end - assert_almost_equal(np.pv(0.07, 20, 12000, 0), - np.pv(0.07, 20, 12000, 0, 'end'), 2) - assert_almost_equal(np.pv(0.07, 20, 12000, 0, 0), - np.pv(0.07, 20, 12000, 0, 'end'), 2) + assert_equal(np.pv(0.07, 20, 12000, 0), + np.pv(0.07, 20, 12000, 0, 'end')) + assert_equal(np.pv(0.07, 20, 12000, 0, 0), + np.pv(0.07, 20, 12000, 0, 'end')) # begin - assert_almost_equal(np.fv(0.075, 20, -2000, 0, 1), - np.fv(0.075, 20, -2000, 0, 'begin'), 4) + assert_equal(np.fv(0.075, 20, -2000, 0, 1), + np.fv(0.075, 20, -2000, 0, 'begin')) # end - assert_almost_equal(np.fv(0.075, 20, -2000, 0), - np.fv(0.075, 20, -2000, 0, 'end'), 4) - assert_almost_equal(np.fv(0.075, 20, -2000, 0, 0), - np.fv(0.075, 20, -2000, 0, 'end'), 4) + assert_equal(np.fv(0.075, 20, -2000, 0), + np.fv(0.075, 20, -2000, 0, 'end')) + assert_equal(np.fv(0.075, 20, -2000, 0, 0), + np.fv(0.075, 20, -2000, 0, 'end')) # begin - assert_almost_equal(np.pmt(0.08/12, 5*12, 15000., 0, 1), - np.pmt(0.08/12, 5*12, 15000., 0, 'begin'), 4) + assert_equal(np.pmt(0.08 / 12, 5 * 12, 15000., 0, 1), + np.pmt(0.08 / 12, 5 * 12, 15000., 0, 'begin')) # end - assert_almost_equal(np.pmt(0.08/12, 5*12, 15000., 0), - np.pmt(0.08/12, 5*12, 15000., 0, 'end'), 4) - assert_almost_equal(np.pmt(0.08/12, 5*12, 15000., 0, 0), - np.pmt(0.08/12, 5*12, 15000., 0, 'end'), 4) + assert_equal(np.pmt(0.08 / 12, 5 * 12, 15000., 0), + np.pmt(0.08 / 12, 5 * 12, 15000., 0, 'end')) + assert_equal(np.pmt(0.08 / 12, 5 * 12, 15000., 0, 0), + np.pmt(0.08 / 12, 5 * 12, 15000., 0, 'end')) # begin - assert_almost_equal(np.ppmt(0.1/12, 1, 60, 55000, 0, 1), - np.ppmt(0.1/12, 1, 60, 55000, 0, 'begin'), 4) + assert_equal(np.ppmt(0.1 / 12, 1, 60, 55000, 0, 1), + np.ppmt(0.1 / 12, 1, 60, 55000, 0, 'begin')) # end - assert_almost_equal(np.ppmt(0.1/12, 1, 60, 55000, 0), - np.ppmt(0.1/12, 1, 60, 55000, 0, 'end'), 4) - assert_almost_equal(np.ppmt(0.1/12, 1, 60, 55000, 0, 0), - np.ppmt(0.1/12, 1, 60, 55000, 0, 'end'), 4) + assert_equal(np.ppmt(0.1 / 12, 1, 60, 55000, 0), + np.ppmt(0.1 / 12, 1, 60, 55000, 0, 'end')) + assert_equal(np.ppmt(0.1 / 12, 1, 60, 55000, 0, 0), + np.ppmt(0.1 / 12, 1, 60, 55000, 0, 'end')) # begin - assert_almost_equal(np.ipmt(0.1/12, 1, 24, 2000, 0, 1), - np.ipmt(0.1/12, 1, 24, 2000, 0, 'begin'), 4) + assert_equal(np.ipmt(0.1 / 12, 1, 24, 2000, 0, 1), + np.ipmt(0.1 / 12, 1, 24, 2000, 0, 'begin')) # end - assert_almost_equal(np.ipmt(0.1/12, 1, 24, 2000, 0), - np.ipmt(0.1/12, 1, 24, 2000, 0, 'end'), 4) - assert_almost_equal(np.ipmt(0.1/12, 1, 24, 2000, 0, 0), - np.ipmt(0.1/12, 1, 24, 2000, 0, 'end'), 4) + assert_equal(np.ipmt(0.1 / 12, 1, 24, 2000, 0), + np.ipmt(0.1 / 12, 1, 24, 2000, 0, 'end')) + assert_equal(np.ipmt(0.1 / 12, 1, 24, 2000, 0, 0), + np.ipmt(0.1 / 12, 1, 24, 2000, 0, 'end')) # begin - assert_almost_equal(np.nper(0.075, -2000, 0, 100000., 1), - np.nper(0.075, -2000, 0, 100000., 'begin'), 4) + assert_equal(np.nper(0.075, -2000, 0, 100000., 1), + np.nper(0.075, -2000, 0, 100000., 'begin')) # end - assert_almost_equal(np.nper(0.075, -2000, 0, 100000.), - np.nper(0.075, -2000, 0, 100000., 'end'), 4) - assert_almost_equal(np.nper(0.075, -2000, 0, 100000., 0), - np.nper(0.075, -2000, 0, 100000., 'end'), 4) + assert_equal(np.nper(0.075, -2000, 0, 100000.), + np.nper(0.075, -2000, 0, 100000., 'end')) + assert_equal(np.nper(0.075, -2000, 0, 100000., 0), + np.nper(0.075, -2000, 0, 100000., 'end')) + + def test_decimal_with_when(self): + """Test that decimals are still supported if the when argument is passed""" + # begin + assert_equal(np.rate(Decimal('10'), Decimal('20'), Decimal('-3500'), Decimal('10000'), Decimal('1')), + np.rate(Decimal('10'), Decimal('20'), Decimal('-3500'), Decimal('10000'), 'begin')) + # end + assert_equal(np.rate(Decimal('10'), Decimal('20'), Decimal('-3500'), Decimal('10000')), + np.rate(Decimal('10'), Decimal('20'), Decimal('-3500'), Decimal('10000'), 'end')) + assert_equal(np.rate(Decimal('10'), Decimal('20'), Decimal('-3500'), Decimal('10000'), Decimal('0')), + np.rate(Decimal('10'), Decimal('20'), Decimal('-3500'), Decimal('10000'), 'end')) + + # begin + assert_equal(np.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), Decimal('0'), Decimal('1')), + np.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), Decimal('0'), 'begin')) + # end + assert_equal(np.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), Decimal('0')), + np.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), Decimal('0'), 'end')) + assert_equal(np.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), Decimal('0'), Decimal('0')), + np.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), Decimal('0'), 'end')) + + # begin + assert_equal(np.fv(Decimal('0.075'), Decimal('20'), Decimal('-2000'), Decimal('0'), Decimal('1')), + np.fv(Decimal('0.075'), Decimal('20'), Decimal('-2000'), Decimal('0'), 'begin')) + # end + assert_equal(np.fv(Decimal('0.075'), Decimal('20'), Decimal('-2000'), Decimal('0')), + np.fv(Decimal('0.075'), Decimal('20'), Decimal('-2000'), Decimal('0'), 'end')) + assert_equal(np.fv(Decimal('0.075'), Decimal('20'), Decimal('-2000'), Decimal('0'), Decimal('0')), + np.fv(Decimal('0.075'), Decimal('20'), Decimal('-2000'), Decimal('0'), 'end')) + + # begin + assert_equal(np.pmt(Decimal('0.08') / Decimal('12'), Decimal('5') * Decimal('12'), Decimal('15000.'), + Decimal('0'), Decimal('1')), + np.pmt(Decimal('0.08') / Decimal('12'), Decimal('5') * Decimal('12'), Decimal('15000.'), + Decimal('0'), 'begin')) + # end + assert_equal(np.pmt(Decimal('0.08') / Decimal('12'), Decimal('5') * Decimal('12'), Decimal('15000.'), + Decimal('0')), + np.pmt(Decimal('0.08') / Decimal('12'), Decimal('5') * Decimal('12'), Decimal('15000.'), + Decimal('0'), 'end')) + assert_equal(np.pmt(Decimal('0.08') / Decimal('12'), Decimal('5') * Decimal('12'), Decimal('15000.'), + Decimal('0'), Decimal('0')), + np.pmt(Decimal('0.08') / Decimal('12'), Decimal('5') * Decimal('12'), Decimal('15000.'), + Decimal('0'), 'end')) + + # begin + assert_equal(np.ppmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('60'), Decimal('55000'), + Decimal('0'), Decimal('1')), + np.ppmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('60'), Decimal('55000'), + Decimal('0'), 'begin')) + # end + assert_equal(np.ppmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('60'), Decimal('55000'), + Decimal('0')), + np.ppmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('60'), Decimal('55000'), + Decimal('0'), 'end')) + assert_equal(np.ppmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('60'), Decimal('55000'), + Decimal('0'), Decimal('0')), + np.ppmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('60'), Decimal('55000'), + Decimal('0'), 'end')) + + # begin + assert_equal(np.ipmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('24'), Decimal('2000'), + Decimal('0'), Decimal('1')).flat[0], + np.ipmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('24'), Decimal('2000'), + Decimal('0'), 'begin').flat[0]) + # end + assert_equal(np.ipmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('24'), Decimal('2000'), + Decimal('0')).flat[0], + np.ipmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('24'), Decimal('2000'), + Decimal('0'), 'end').flat[0]) + assert_equal(np.ipmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('24'), Decimal('2000'), + Decimal('0'), Decimal('0')).flat[0], + np.ipmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('24'), Decimal('2000'), + Decimal('0'), 'end').flat[0]) def test_broadcast(self): assert_almost_equal(np.nper(0.075, -2000, 0, 100000., [0, 1]), [21.5449442, 20.76156441], 4) - assert_almost_equal(np.ipmt(0.1/12, list(range(5)), 24, 2000), + assert_almost_equal(np.ipmt(0.1 / 12, list(range(5)), 24, 2000), [-17.29165168, -16.66666667, -16.03647345, - -15.40102862, -14.76028842], 4) + -15.40102862, -14.76028842], 4) - assert_almost_equal(np.ppmt(0.1/12, list(range(5)), 24, 2000), + assert_almost_equal(np.ppmt(0.1 / 12, list(range(5)), 24, 2000), [-74.998201, -75.62318601, -76.25337923, - -76.88882405, -77.52956425], 4) + -76.88882405, -77.52956425], 4) - assert_almost_equal(np.ppmt(0.1/12, list(range(5)), 24, 2000, 0, + assert_almost_equal(np.ppmt(0.1 / 12, list(range(5)), 24, 2000, 0, [0, 0, 1, 'end', 'begin']), [-74.998201, -75.62318601, -75.62318601, - -76.88882405, -76.88882405], 4) + -76.88882405, -76.88882405], 4) + + def test_broadcast_decimal(self): + # Use almost equal because precision is tested in the explicit tests, this test is to ensure + # broadcast with Decimal is not broken. + assert_almost_equal(np.ipmt(Decimal('0.1') / Decimal('12'), list(range(5)), Decimal('24'), Decimal('2000')), + [Decimal('-17.29165168'), Decimal('-16.66666667'), Decimal('-16.03647345'), + Decimal('-15.40102862'), Decimal('-14.76028842')], 4) + + assert_almost_equal(np.ppmt(Decimal('0.1') / Decimal('12'), list(range(5)), Decimal('24'), Decimal('2000')), + [Decimal('-74.998201'), Decimal('-75.62318601'), Decimal('-76.25337923'), + Decimal('-76.88882405'), Decimal('-77.52956425')], 4) + + assert_almost_equal(np.ppmt(Decimal('0.1') / Decimal('12'), list(range(5)), Decimal('24'), Decimal('2000'), + Decimal('0'), [Decimal('0'), Decimal('0'), Decimal('1'), 'end', 'begin']), + [Decimal('-74.998201'), Decimal('-75.62318601'), Decimal('-75.62318601'), + Decimal('-76.88882405'), Decimal('-76.88882405')], 4) + if __name__ == "__main__": run_module_suite() From 50f3e76d2bcd617cf0514616176817fdde91a081 Mon Sep 17 00:00:00 2001 From: Charles Harris Date: Wed, 4 Apr 2018 10:33:07 -0600 Subject: [PATCH 54/65] MAINT: Remove all uses of run_module_suite. That function is nose specific and has not worked since `__init__` files were added to the tests directories. --- numpy/lib/tests/test_financial.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py index c5e92db..5249150 100644 --- a/numpy/lib/tests/test_financial.py +++ b/numpy/lib/tests/test_financial.py @@ -4,9 +4,8 @@ import numpy as np from numpy.testing import ( - run_module_suite, assert_, assert_almost_equal, assert_allclose, - assert_equal, assert_raises -) + assert_, assert_almost_equal, assert_allclose, assert_equal, assert_raises + ) class TestFinancial(object): @@ -339,7 +338,3 @@ def test_broadcast_decimal(self): Decimal('0'), [Decimal('0'), Decimal('0'), Decimal('1'), 'end', 'begin']), [Decimal('-74.998201'), Decimal('-75.62318601'), Decimal('-75.62318601'), Decimal('-76.88882405'), Decimal('-76.88882405')], 4) - - -if __name__ == "__main__": - run_module_suite() From f52fc2054aaf4781e9360e0f5e4b3383ed57268f Mon Sep 17 00:00:00 2001 From: Stephan Hoyer Date: Mon, 8 Oct 2018 12:52:55 -0700 Subject: [PATCH 55/65] ENH: __array_function__ for np.lib, part 1 np.lib.arraypad through np.lib.nanfunctions --- numpy/lib/financial.py | 64 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index 06fa1bd..d1a0cd9 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -15,6 +15,8 @@ from decimal import Decimal import numpy as np +from numpy.core.overrides import array_function_dispatch + __all__ = ['fv', 'pmt', 'nper', 'ipmt', 'ppmt', 'pv', 'rate', 'irr', 'npv', 'mirr'] @@ -36,6 +38,12 @@ def _convert_when(when): except (KeyError, TypeError): return [_when_to_num[x] for x in when] + +def _fv_dispatcher(rate, nper, pmt, pv, when=None): + return (rate, nper, pmt, pv) + + +@array_function_dispatch(_fv_dispatcher) def fv(rate, nper, pmt, pv, when='end'): """ Compute the future value. @@ -124,6 +132,12 @@ def fv(rate, nper, pmt, pv, when='end'): (1 + rate*when)*(temp - 1)/rate) return -(pv*temp + pmt*fact) + +def _pmt_dispatcher(rate, nper, pv, fv=None, when=None): + return (rate, nper, pv, fv) + + +@array_function_dispatch(_pmt_dispatcher) def pmt(rate, nper, pv, fv=0, when='end'): """ Compute the payment against loan principal plus interest. @@ -216,6 +230,12 @@ def pmt(rate, nper, pv, fv=0, when='end'): (1 + masked_rate*when)*(temp - 1)/masked_rate) return -(fv + pv*temp) / fact + +def _nper_dispatcher(rate, pmt, pv, fv=None, when=None): + return (rate, pmt, pv, fv) + + +@array_function_dispatch(_nper_dispatcher) def nper(rate, pmt, pv, fv=0, when='end'): """ Compute the number of periodic payments. @@ -284,6 +304,12 @@ def nper(rate, pmt, pv, fv=0, when='end'): B = np.log((-fv+z) / (pv+z))/np.log(1+rate) return np.where(rate == 0, A, B) + +def _ipmt_dispatcher(rate, per, nper, pv, fv=None, when=None): + return (rate, per, nper, pv, fv) + + +@array_function_dispatch(_ipmt_dispatcher) def ipmt(rate, per, nper, pv, fv=0, when='end'): """ Compute the interest portion of a payment. @@ -379,6 +405,7 @@ def ipmt(rate, per, nper, pv, fv=0, when='end'): pass return ipmt + def _rbl(rate, per, pmt, pv, when): """ This function is here to simply have a different name for the 'fv' @@ -388,6 +415,12 @@ def _rbl(rate, per, pmt, pv, when): """ return fv(rate, (per - 1), pmt, pv, when) + +def _ppmt_dispatcher(rate, per, nper, pv, fv=None, when=None): + return (rate, per, nper, pv, fv) + + +@array_function_dispatch(_ppmt_dispatcher) def ppmt(rate, per, nper, pv, fv=0, when='end'): """ Compute the payment against loan principal. @@ -416,6 +449,12 @@ def ppmt(rate, per, nper, pv, fv=0, when='end'): total = pmt(rate, nper, pv, fv, when) return total - ipmt(rate, per, nper, pv, fv, when) + +def _pv_dispatcher(rate, nper, pmt, fv=None, when=None): + return (rate, nper, nper, pv, fv) + + +@array_function_dispatch(_pv_dispatcher) def pv(rate, nper, pmt, fv=0, when='end'): """ Compute the present value. @@ -520,6 +559,12 @@ def _g_div_gp(r, n, p, x, y, w): (n*t2*x - p*(t1 - 1)*(r*w + 1)/(r**2) + n*p*t2*(r*w + 1)/r + p*(t1 - 1)*w/r)) + +def _rate_dispatcher(nper, pmt, pv, fv, when=None, guess=None, tol=None, + maxiter=None): + return (nper, pmt, pv, fv) + + # Use Newton's iteration until the change is less than 1e-6 # for all values or a maximum of 100 iterations is reached. # Newton's rule is @@ -527,6 +572,7 @@ def _g_div_gp(r, n, p, x, y, w): # where # g(r) is the formula # g'(r) is the derivative with respect to r. +@array_function_dispatch(_rate_dispatcher) def rate(nper, pmt, pv, fv, when='end', guess=None, tol=None, maxiter=100): """ Compute the rate of interest per period. @@ -598,6 +644,12 @@ def rate(nper, pmt, pv, fv, when='end', guess=None, tol=None, maxiter=100): else: return rn + +def _irr_dispatcher(values): + return (values,) + + +@array_function_dispatch(_irr_dispatcher) def irr(values): """ Return the Internal Rate of Return (IRR). @@ -677,6 +729,12 @@ def irr(values): rate = rate.item(np.argmin(np.abs(rate))) return rate + +def _npv_dispatcher(rate, values): + return (values,) + + +@array_function_dispatch(_npv_dispatcher) def npv(rate, values): """ Returns the NPV (Net Present Value) of a cash flow series. @@ -722,6 +780,12 @@ def npv(rate, values): values = np.asarray(values) return (values / (1+rate)**np.arange(0, len(values))).sum(axis=0) + +def _mirr_dispatcher(values, finance_rate, reinvest_rate): + return (values,) + + +@array_function_dispatch(_mirr_dispatcher) def mirr(values, finance_rate, reinvest_rate): """ Modified internal rate of return. From 0126027b1567f48dc74497c30e6a0cf720adc3e3 Mon Sep 17 00:00:00 2001 From: Stephan Hoyer Date: Tue, 23 Oct 2018 07:53:58 -0700 Subject: [PATCH 56/65] MAINT: set preferred __module__ for numpy functions --- numpy/lib/financial.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index d1a0cd9..e1e2974 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -13,9 +13,14 @@ from __future__ import division, absolute_import, print_function from decimal import Decimal +import functools import numpy as np -from numpy.core.overrides import array_function_dispatch +from numpy.core import overrides + + +array_function_dispatch = functools.partial( + overrides.array_function_dispatch, module='numpy') __all__ = ['fv', 'pmt', 'nper', 'ipmt', 'ppmt', 'pv', 'rate', From 52f50fee051843c7fc3c0e76f26c163d2c3cf619 Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Wed, 14 Nov 2018 11:36:59 -0800 Subject: [PATCH 57/65] TST, DOC: enable refguide_check * ported the refguide_check module from SciPy for usage in NumPy docstring execution/ verification; added the refguide_check run to Azure Mac OS CI * adjusted NumPy docstrings such that refguide_check passes --- numpy/lib/financial.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index e1e2974..2166874 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -127,7 +127,7 @@ def fv(rate, nper, pmt, pv, when='end'): >>> a = np.array((0.05, 0.06, 0.07))/12 >>> np.fv(a, 10*12, -100, -100) - array([ 15692.92889434, 16569.87435405, 17509.44688102]) + array([ 15692.92889434, 16569.87435405, 17509.44688102]) # may vary """ when = _convert_when(when) @@ -275,7 +275,7 @@ def nper(rate, pmt, pv, fv=0, when='end'): If you only had $150/month to pay towards the loan, how long would it take to pay-off a loan of $8,000 at 7% annual interest? - >>> print(round(np.nper(0.07/12, -150, 8000), 5)) + >>> print(np.round(np.nper(0.07/12, -150, 8000), 5)) 64.07335 So, over 64 months would be required to pay off the loan. @@ -286,10 +286,10 @@ def nper(rate, pmt, pv, fv=0, when='end'): >>> np.nper(*(np.ogrid[0.07/12: 0.08/12: 0.01/12, ... -150 : -99 : 50 , ... 8000 : 9001 : 1000])) - array([[[ 64.07334877, 74.06368256], - [ 108.07548412, 127.99022654]], - [[ 66.12443902, 76.87897353], - [ 114.70165583, 137.90124779]]]) + array([[[ 64.07334877, 74.06368256], + [108.07548412, 127.99022654]], + [[ 66.12443902, 76.87897353], + [114.70165583, 137.90124779]]]) """ when = _convert_when(when) @@ -539,7 +539,7 @@ def pv(rate, nper, pmt, fv=0, when='end'): >>> a = np.array((0.05, 0.04, 0.03))/12 >>> np.pv(a, 10*12, -100, 15692.93) - array([ -100.00067132, -649.26771385, -1273.78633713]) + array([ -100.00067132, -649.26771385, -1273.78633713]) # may vary So, to end up with the same $15692.93 under the same $100 per month "savings plan," for annual interest rates of 4% and 3%, one would @@ -704,15 +704,15 @@ def irr(values): Examples -------- - >>> round(irr([-100, 39, 59, 55, 20]), 5) + >>> round(np.irr([-100, 39, 59, 55, 20]), 5) 0.28095 - >>> round(irr([-100, 0, 0, 74]), 5) + >>> round(np.irr([-100, 0, 0, 74]), 5) -0.0955 - >>> round(irr([-100, 100, 0, -7]), 5) + >>> round(np.irr([-100, 100, 0, -7]), 5) -0.0833 - >>> round(irr([-100, 100, 0, 7]), 5) + >>> round(np.irr([-100, 100, 0, 7]), 5) 0.06206 - >>> round(irr([-5, 10.5, 1, -8, 1]), 5) + >>> round(np.irr([-5, 10.5, 1, -8, 1]), 5) 0.0886 (Compare with the Example given for numpy.lib.financial.npv) @@ -777,7 +777,7 @@ def npv(rate, values): Examples -------- >>> np.npv(0.281,[-100, 39, 59, 55, 20]) - -0.0084785916384548798 + -0.0084785916384548798 # may vary (Compare with the Example given for numpy.lib.financial.irr) From fa694efb3fd7f9602d30a69175dda30df07f2804 Mon Sep 17 00:00:00 2001 From: kai-striega Date: Sat, 14 Sep 2019 13:18:34 +0800 Subject: [PATCH 58/65] DOC: Add warning message to np.npv docs ``np.npv`` calculates the present value of a series of cashflows starting in the present (t=0). Another common definition of NPV uses the future cashflows i.e. starting at the end of the first period. The difference in definition can lead to confusion for users moving between NumPy and other projects [1] and adds complexity for maintainers [2]. This commit adds a warning to the ``npv`` function's documentation and adds an example on how to find the npv of a project using the alternate definition. [1] https://github.com/numpy/numpy/issue/10877 [2] https://github.com/numpy/numpy/pull/3346 --- numpy/lib/financial.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index 2166874..d135804 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -763,6 +763,15 @@ def npv(rate, values): The NPV of the input cash flow series `values` at the discount `rate`. + Warnings + -------- + ``npv`` considers a series of cashflows starting in the present (t = 0). + NPV can also be defined with a series of future cashflows, paid at the + end, rather than the start, of each period. If future cashflows are used, + the first cashflow `values[0]` must be zeroed and added to the net + present value of the future cashflows. This is demonstrated in the + examples. + Notes ----- Returns the result of: [G]_ @@ -781,6 +790,25 @@ def npv(rate, values): (Compare with the Example given for numpy.lib.financial.irr) + Consider a potential project with an initial investment of $40 000 and + projected cashflows of $5 000, $8 000, $12 000 and $30 000 at the end of + each period discounted at a rate of 8% per period. To find the project's + net present value: + + >>> rate, cashflows = 0.08, [-40_000, 5_000, 8_000, 12_000, 30_000] + >>> np.npv(rate, cashflows).round(5) + 3065.22267 + + It may be preferable to split the projected cashflow into an initial + investment and expected future cashflows. In this case, the value of + the initial cashflow is zero and the initial investment is later added + to the future cashflows net present value: + + >>> initial_cashflow = cashflows[0] + >>> cashflows[0] = 0 + >>> np.round(np.npv(rate, cashflows) + initial_cashflow, 5) + 3065.22267 + """ values = np.asarray(values) return (values / (1+rate)**np.arange(0, len(values))).sum(axis=0) From f7e14e705f0aaa94f21052c40c504037f49c5a48 Mon Sep 17 00:00:00 2001 From: kai-striega Date: Sat, 14 Sep 2019 13:45:00 +0800 Subject: [PATCH 59/65] TST: Move NPV-IRR congruence check to tests The internal rate of return (irr) is defined as the rate of return required for the net present values of a series of cashflows to be zero. i.e the lowest rate of return required for a project to break even. This is currently checked by refering to the example output from the ``irr`` and ``npv`` function documentation. This commit adds a test to confirm the identity holds. --- numpy/lib/financial.py | 7 ------- numpy/lib/tests/test_financial.py | 6 ++++++ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py index d135804..d72384e 100644 --- a/numpy/lib/financial.py +++ b/numpy/lib/financial.py @@ -715,8 +715,6 @@ def irr(values): >>> round(np.irr([-5, 10.5, 1, -8, 1]), 5) 0.0886 - (Compare with the Example given for numpy.lib.financial.npv) - """ # `np.roots` call is why this function does not support Decimal type. # @@ -785,11 +783,6 @@ def npv(rate, values): Examples -------- - >>> np.npv(0.281,[-100, 39, 59, 55, 20]) - -0.0084785916384548798 # may vary - - (Compare with the Example given for numpy.lib.financial.irr) - Consider a potential project with an initial investment of $40 000 and projected cashflows of $5 000, $8 000, $12 000 and $30 000 at the end of each period discounted at a rate of 8% per period. To find the project's diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py index 5249150..2108876 100644 --- a/numpy/lib/tests/test_financial.py +++ b/numpy/lib/tests/test_financial.py @@ -9,6 +9,12 @@ class TestFinancial(object): + def test_npv_irr_congruence(self): + # IRR is defined as the rate required for the present value of a + # a series of cashflows to be zero i.e. NPV(IRR(x), x) = 0 + cashflows = np.array([-40000, 5000, 8000, 12000, 30000]) + assert_allclose(np.npv(np.irr(cashflows), cashflows), 0, atol=1e-10, rtol=0) + def test_rate(self): assert_almost_equal( np.rate(10, 0, -3500, 10000), From e7f23d57a7a7106d59762c66dc5d27918169bb96 Mon Sep 17 00:00:00 2001 From: Warren Weckesser Date: Fri, 27 Sep 2019 11:28:14 -0400 Subject: [PATCH 60/65] MAINT: Rename module to numpy_financial --- {numpy => numpy_financial}/lib/financial.py | 0 {numpy => numpy_financial}/lib/tests/test_financial.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {numpy => numpy_financial}/lib/financial.py (100%) rename {numpy => numpy_financial}/lib/tests/test_financial.py (100%) diff --git a/numpy/lib/financial.py b/numpy_financial/lib/financial.py similarity index 100% rename from numpy/lib/financial.py rename to numpy_financial/lib/financial.py diff --git a/numpy/lib/tests/test_financial.py b/numpy_financial/lib/tests/test_financial.py similarity index 100% rename from numpy/lib/tests/test_financial.py rename to numpy_financial/lib/tests/test_financial.py From 7ce749a256a77fedc012e47dbfd624b9fb1430b2 Mon Sep 17 00:00:00 2001 From: Warren Weckesser Date: Fri, 27 Sep 2019 11:34:30 -0400 Subject: [PATCH 61/65] MAINT: Reorganize * Eliminate intermediate 'lib' directory. * Rename financial.py to _financial.py * Add __init__.py --- numpy_financial/__init__.py | 3 +++ numpy_financial/{lib/financial.py => _financial.py} | 0 numpy_financial/{lib => }/tests/test_financial.py | 0 3 files changed, 3 insertions(+) create mode 100644 numpy_financial/__init__.py rename numpy_financial/{lib/financial.py => _financial.py} (100%) rename numpy_financial/{lib => }/tests/test_financial.py (100%) diff --git a/numpy_financial/__init__.py b/numpy_financial/__init__.py new file mode 100644 index 0000000..49a2377 --- /dev/null +++ b/numpy_financial/__init__.py @@ -0,0 +1,3 @@ + + +from ._financial import * diff --git a/numpy_financial/lib/financial.py b/numpy_financial/_financial.py similarity index 100% rename from numpy_financial/lib/financial.py rename to numpy_financial/_financial.py diff --git a/numpy_financial/lib/tests/test_financial.py b/numpy_financial/tests/test_financial.py similarity index 100% rename from numpy_financial/lib/tests/test_financial.py rename to numpy_financial/tests/test_financial.py From 62f266933a228e3e54c4cb006682c87dfaf8882d Mon Sep 17 00:00:00 2001 From: Warren Weckesser Date: Fri, 27 Sep 2019 11:39:08 -0400 Subject: [PATCH 62/65] MAINT: Add .gitignore --- .gitignore | 109 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1ccdafb --- /dev/null +++ b/.gitignore @@ -0,0 +1,109 @@ +# Editor temporary/working/backup files # +######################################### +.#* +[#]*# +*~ +*$ +*.bak +*.diff +.idea/ +*.iml +*.ipr +*.iws +*.org +.project +pmip +*.rej +.settings/ +.*.sw[nop] +.sw[nop] +*.tmp +*.vim +.vscode +tags +cscope.out +# gnu global +GPATH +GRTAGS +GSYMS +GTAGS +.cache + +# Compiled source # +################### +*.a +*.com +*.class +*.dll +*.exe +*.o +*.o.d +*.py[ocd] +*.so + +# Packages # +############ +# it's better to unpack these files and commit the raw source +# git has its own built in compression methods +*.7z +*.bz2 +*.bzip2 +*.dmg +*.gz +*.iso +*.jar +*.rar +*.tar +*.tbz2 +*.tgz +*.zip + +# Python files # +################ +# setup.py working directory +build +# sphinx build directory +_build +# setup.py dist directory +dist +doc/build +doc/cdoc/build +# Egg metadata +*.egg-info +# The shelf plugin uses this dir +./.shelf +MANIFEST +.cache + +# Paver generated files # +######################### +/release + +# Logs and databases # +###################### +*.log +*.sql +*.sqlite + +# Patches # +########### +*.patch +*.diff + +# OS generated files # +###################### +.DS_Store* +.VolumeIcon.icns +.fseventsd +Icon? +.gdb_history +ehthumbs.db +Thumbs.db +.directory + +# pytest generated files # +########################## +/.pytest_cache + +# Things specific to this project # +################################### From 26356bd1b3613ca6332b0a4eeb699a1432a6c9b0 Mon Sep 17 00:00:00 2001 From: Warren Weckesser Date: Fri, 27 Sep 2019 11:58:38 -0400 Subject: [PATCH 63/65] MAINT: Add a simple setup.py --- setup.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 setup.py diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..741f505 --- /dev/null +++ b/setup.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python + + +def configuration(parent_package='', top_path=None): + from numpy.distutils.misc_util import Configuration + config = Configuration(None, parent_package, top_path) + config.add_subpackage('numpy_financial') + return config + + +if __name__ == "__main__": + from numpy.distutils.core import setup + + setup(name='numpy-financial', + version='0.0.1', + configuration=configuration) From 14d14f2c4e3a446ef4bb3b7001c7a3e876825479 Mon Sep 17 00:00:00 2001 From: Warren Weckesser Date: Fri, 27 Sep 2019 12:43:45 -0400 Subject: [PATCH 64/65] MAINT: update test_financial.py --- numpy_financial/tests/test_financial.py | 396 ++++++++++++++---------- 1 file changed, 231 insertions(+), 165 deletions(-) diff --git a/numpy_financial/tests/test_financial.py b/numpy_financial/tests/test_financial.py index 2108876..935b6ce 100644 --- a/numpy_financial/tests/test_financial.py +++ b/numpy_financial/tests/test_financial.py @@ -1,93 +1,103 @@ -from __future__ import division, absolute_import, print_function from decimal import Decimal -import numpy as np +# Don't use 'import numpy as np', to avoid accidentally testing +# the versions in numpy instead of numpy_financial. +import numpy from numpy.testing import ( assert_, assert_almost_equal, assert_allclose, assert_equal, assert_raises ) +import numpy_financial as npf + class TestFinancial(object): def test_npv_irr_congruence(self): # IRR is defined as the rate required for the present value of a # a series of cashflows to be zero i.e. NPV(IRR(x), x) = 0 - cashflows = np.array([-40000, 5000, 8000, 12000, 30000]) - assert_allclose(np.npv(np.irr(cashflows), cashflows), 0, atol=1e-10, rtol=0) + cashflows = numpy.array([-40000, 5000, 8000, 12000, 30000]) + assert_allclose(npf.npv(npf.irr(cashflows), cashflows), 0, + atol=1e-10, rtol=0) def test_rate(self): - assert_almost_equal( - np.rate(10, 0, -3500, 10000), - 0.1107, 4) + assert_almost_equal(npf.rate(10, 0, -3500, 10000), 0.1107, 4) def test_rate_decimal(self): - rate = np.rate(Decimal('10'), Decimal('0'), Decimal('-3500'), Decimal('10000')) + rate = npf.rate(Decimal('10'), Decimal('0'), Decimal('-3500'), + Decimal('10000')) assert_equal(Decimal('0.1106908537142689284704528100'), rate) def test_irr(self): v = [-150000, 15000, 25000, 35000, 45000, 60000] - assert_almost_equal(np.irr(v), 0.0524, 2) + assert_almost_equal(npf.irr(v), 0.0524, 2) v = [-100, 0, 0, 74] - assert_almost_equal(np.irr(v), -0.0955, 2) + assert_almost_equal(npf.irr(v), -0.0955, 2) v = [-100, 39, 59, 55, 20] - assert_almost_equal(np.irr(v), 0.28095, 2) + assert_almost_equal(npf.irr(v), 0.28095, 2) v = [-100, 100, 0, -7] - assert_almost_equal(np.irr(v), -0.0833, 2) + assert_almost_equal(npf.irr(v), -0.0833, 2) v = [-100, 100, 0, 7] - assert_almost_equal(np.irr(v), 0.06206, 2) + assert_almost_equal(npf.irr(v), 0.06206, 2) v = [-5, 10.5, 1, -8, 1] - assert_almost_equal(np.irr(v), 0.0886, 2) + assert_almost_equal(npf.irr(v), 0.0886, 2) - # Test that if there is no solution then np.irr returns nan + # Test that if there is no solution then npf.irr returns nan # Fixes gh-6744 v = [-1, -2, -3] - assert_equal(np.irr(v), np.nan) + assert_equal(npf.irr(v), numpy.nan) def test_pv(self): - assert_almost_equal(np.pv(0.07, 20, 12000, 0), -127128.17, 2) + assert_almost_equal(npf.pv(0.07, 20, 12000, 0), -127128.17, 2) def test_pv_decimal(self): - assert_equal(np.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), Decimal('0')), + assert_equal(npf.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), + Decimal('0')), Decimal('-127128.1709461939327295222005')) def test_fv(self): - assert_equal(np.fv(0.075, 20, -2000, 0, 0), 86609.362673042924) + assert_equal(npf.fv(0.075, 20, -2000, 0, 0), 86609.362673042924) def test_fv_decimal(self): - assert_equal(np.fv(Decimal('0.075'), Decimal('20'), Decimal('-2000'), 0, 0), + assert_equal(npf.fv(Decimal('0.075'), Decimal('20'), Decimal('-2000'), + 0, 0), Decimal('86609.36267304300040536731624')) def test_pmt(self): - res = np.pmt(0.08 / 12, 5 * 12, 15000) + res = npf.pmt(0.08 / 12, 5 * 12, 15000) tgt = -304.145914 assert_allclose(res, tgt) # Test the edge case where rate == 0.0 - res = np.pmt(0.0, 5 * 12, 15000) + res = npf.pmt(0.0, 5 * 12, 15000) tgt = -250.0 assert_allclose(res, tgt) # Test the case where we use broadcast and # the arguments passed in are arrays. - res = np.pmt([[0.0, 0.8], [0.3, 0.8]], [12, 3], [2000, 20000]) - tgt = np.array([[-166.66667, -19311.258], [-626.90814, -19311.258]]) + res = npf.pmt([[0.0, 0.8], [0.3, 0.8]], [12, 3], [2000, 20000]) + tgt = numpy.array([[-166.66667, -19311.258], [-626.90814, -19311.258]]) assert_allclose(res, tgt) def test_pmt_decimal(self): - res = np.pmt(Decimal('0.08') / Decimal('12'), 5 * 12, 15000) + res = npf.pmt(Decimal('0.08') / Decimal('12'), 5 * 12, 15000) tgt = Decimal('-304.1459143262052370338701494') assert_equal(res, tgt) # Test the edge case where rate == 0.0 - res = np.pmt(Decimal('0'), Decimal('60'), Decimal('15000')) + res = npf.pmt(Decimal('0'), Decimal('60'), Decimal('15000')) tgt = -250 assert_equal(res, tgt) + # Test the case where we use broadcast and # the arguments passed in are arrays. - res = np.pmt([[Decimal('0'), Decimal('0.8')], [Decimal('0.3'), Decimal('0.8')]], - [Decimal('12'), Decimal('3')], [Decimal('2000'), Decimal('20000')]) - tgt = np.array([[Decimal('-166.6666666666666666666666667'), Decimal('-19311.25827814569536423841060')], - [Decimal('-626.9081401700757748402586600'), Decimal('-19311.25827814569536423841060')]]) - - # Cannot use the `assert_allclose` because it uses isfinite under the covers - # which does not support the Decimal type + res = npf.pmt([[Decimal('0'), Decimal('0.8')], + [Decimal('0.3'), Decimal('0.8')]], + [Decimal('12'), Decimal('3')], + [Decimal('2000'), Decimal('20000')]) + tgt = numpy.array([[Decimal('-166.6666666666666666666666667'), + Decimal('-19311.25827814569536423841060')], + [Decimal('-626.9081401700757748402586600'), + Decimal('-19311.25827814569536423841060')]]) + + # Cannot use the `assert_allclose` because it uses isfinite under + # the covers which does not support the Decimal type # See issue: https://github.com/numpy/numpy/issues/9954 assert_equal(res[0][0], tgt[0][0]) assert_equal(res[0][1], tgt[0][1]) @@ -95,252 +105,308 @@ def test_pmt_decimal(self): assert_equal(res[1][1], tgt[1][1]) def test_ppmt(self): - assert_equal(np.round(np.ppmt(0.1 / 12, 1, 60, 55000), 2), -710.25) + assert_equal(numpy.round(npf.ppmt(0.1 / 12, 1, 60, 55000), 2), -710.25) def test_ppmt_decimal(self): - assert_equal(np.ppmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('60'), Decimal('55000')), + assert_equal(npf.ppmt(Decimal('0.1') / Decimal('12'), Decimal('1'), + Decimal('60'), Decimal('55000')), Decimal('-710.2541257864217612489830917')) # Two tests showing how Decimal is actually getting at a more exact result # .23 / 12 does not come out nicely as a float but does as a decimal def test_ppmt_special_rate(self): - assert_equal(np.round(np.ppmt(0.23 / 12, 1, 60, 10000000000), 8), -90238044.232277036) + assert_equal(numpy.round(npf.ppmt(0.23 / 12, 1, 60, 10000000000), 8), + -90238044.232277036) def test_ppmt_special_rate_decimal(self): - # When rounded out to 8 decimal places like the float based test, this should not equal the same value - # as the float, substituted for the decimal + # When rounded out to 8 decimal places like the float based test, + # this should not equal the same value as the float, substituted + # for the decimal def raise_error_because_not_equal(): assert_equal( - round(np.ppmt(Decimal('0.23') / Decimal('12'), 1, 60, Decimal('10000000000')), 8), + round(npf.ppmt(Decimal('0.23') / Decimal('12'), 1, 60, + Decimal('10000000000')), 8), Decimal('-90238044.232277036')) assert_raises(AssertionError, raise_error_because_not_equal) - assert_equal(np.ppmt(Decimal('0.23') / Decimal('12'), 1, 60, Decimal('10000000000')), + assert_equal(npf.ppmt(Decimal('0.23') / Decimal('12'), 1, 60, + Decimal('10000000000')), Decimal('-90238044.2322778884413969909')) def test_ipmt(self): - assert_almost_equal(np.round(np.ipmt(0.1 / 12, 1, 24, 2000), 2), -16.67) + assert_almost_equal(numpy.round(npf.ipmt(0.1 / 12, 1, 24, 2000), 2), + -16.67) def test_ipmt_decimal(self): - result = np.ipmt(Decimal('0.1') / Decimal('12'), 1, 24, 2000) + result = npf.ipmt(Decimal('0.1') / Decimal('12'), 1, 24, 2000) assert_equal(result.flat[0], Decimal('-16.66666666666666666666666667')) def test_nper(self): - assert_almost_equal(np.nper(0.075, -2000, 0, 100000.), + assert_almost_equal(npf.nper(0.075, -2000, 0, 100000.), 21.54, 2) def test_nper2(self): - assert_almost_equal(np.nper(0.0, -2000, 0, 100000.), + assert_almost_equal(npf.nper(0.0, -2000, 0, 100000.), 50.0, 1) def test_npv(self): assert_almost_equal( - np.npv(0.05, [-15000, 1500, 2500, 3500, 4500, 6000]), + npf.npv(0.05, [-15000, 1500, 2500, 3500, 4500, 6000]), 122.89, 2) def test_npv_decimal(self): assert_equal( - np.npv(Decimal('0.05'), [-15000, 1500, 2500, 3500, 4500, 6000]), + npf.npv(Decimal('0.05'), [-15000, 1500, 2500, 3500, 4500, 6000]), Decimal('122.894854950942692161628715')) def test_mirr(self): val = [-4500, -800, 800, 800, 600, 600, 800, 800, 700, 3000] - assert_almost_equal(np.mirr(val, 0.08, 0.055), 0.0666, 4) + assert_almost_equal(npf.mirr(val, 0.08, 0.055), 0.0666, 4) val = [-120000, 39000, 30000, 21000, 37000, 46000] - assert_almost_equal(np.mirr(val, 0.10, 0.12), 0.126094, 6) + assert_almost_equal(npf.mirr(val, 0.10, 0.12), 0.126094, 6) val = [100, 200, -50, 300, -200] - assert_almost_equal(np.mirr(val, 0.05, 0.06), 0.3428, 4) + assert_almost_equal(npf.mirr(val, 0.05, 0.06), 0.3428, 4) val = [39000, 30000, 21000, 37000, 46000] - assert_(np.isnan(np.mirr(val, 0.10, 0.12))) + assert_(numpy.isnan(npf.mirr(val, 0.10, 0.12))) def test_mirr_decimal(self): - val = [Decimal('-4500'), Decimal('-800'), Decimal('800'), Decimal('800'), - Decimal('600'), Decimal('600'), Decimal('800'), Decimal('800'), - Decimal('700'), Decimal('3000')] - assert_equal(np.mirr(val, Decimal('0.08'), Decimal('0.055')), + val = [Decimal('-4500'), Decimal('-800'), Decimal('800'), + Decimal('800'), Decimal('600'), Decimal('600'), Decimal('800'), + Decimal('800'), Decimal('700'), Decimal('3000')] + assert_equal(npf.mirr(val, Decimal('0.08'), Decimal('0.055')), Decimal('0.066597175031553548874239618')) val = [Decimal('-120000'), Decimal('39000'), Decimal('30000'), Decimal('21000'), Decimal('37000'), Decimal('46000')] - assert_equal(np.mirr(val, Decimal('0.10'), Decimal('0.12')), Decimal('0.126094130365905145828421880')) + assert_equal(npf.mirr(val, Decimal('0.10'), Decimal('0.12')), + Decimal('0.126094130365905145828421880')) val = [Decimal('100'), Decimal('200'), Decimal('-50'), Decimal('300'), Decimal('-200')] - assert_equal(np.mirr(val, Decimal('0.05'), Decimal('0.06')), Decimal('0.342823387842176663647819868')) + assert_equal(npf.mirr(val, Decimal('0.05'), Decimal('0.06')), + Decimal('0.342823387842176663647819868')) - val = [Decimal('39000'), Decimal('30000'), Decimal('21000'), Decimal('37000'), Decimal('46000')] - assert_(np.isnan(np.mirr(val, Decimal('0.10'), Decimal('0.12')))) + val = [Decimal('39000'), Decimal('30000'), Decimal('21000'), + Decimal('37000'), Decimal('46000')] + assert_(numpy.isnan(npf.mirr(val, Decimal('0.10'), Decimal('0.12')))) def test_when(self): # begin - assert_equal(np.rate(10, 20, -3500, 10000, 1), - np.rate(10, 20, -3500, 10000, 'begin')) + assert_equal(npf.rate(10, 20, -3500, 10000, 1), + npf.rate(10, 20, -3500, 10000, 'begin')) # end - assert_equal(np.rate(10, 20, -3500, 10000), - np.rate(10, 20, -3500, 10000, 'end')) - assert_equal(np.rate(10, 20, -3500, 10000, 0), - np.rate(10, 20, -3500, 10000, 'end')) + assert_equal(npf.rate(10, 20, -3500, 10000), + npf.rate(10, 20, -3500, 10000, 'end')) + assert_equal(npf.rate(10, 20, -3500, 10000, 0), + npf.rate(10, 20, -3500, 10000, 'end')) # begin - assert_equal(np.pv(0.07, 20, 12000, 0, 1), - np.pv(0.07, 20, 12000, 0, 'begin')) + assert_equal(npf.pv(0.07, 20, 12000, 0, 1), + npf.pv(0.07, 20, 12000, 0, 'begin')) # end - assert_equal(np.pv(0.07, 20, 12000, 0), - np.pv(0.07, 20, 12000, 0, 'end')) - assert_equal(np.pv(0.07, 20, 12000, 0, 0), - np.pv(0.07, 20, 12000, 0, 'end')) + assert_equal(npf.pv(0.07, 20, 12000, 0), + npf.pv(0.07, 20, 12000, 0, 'end')) + assert_equal(npf.pv(0.07, 20, 12000, 0, 0), + npf.pv(0.07, 20, 12000, 0, 'end')) # begin - assert_equal(np.fv(0.075, 20, -2000, 0, 1), - np.fv(0.075, 20, -2000, 0, 'begin')) + assert_equal(npf.fv(0.075, 20, -2000, 0, 1), + npf.fv(0.075, 20, -2000, 0, 'begin')) # end - assert_equal(np.fv(0.075, 20, -2000, 0), - np.fv(0.075, 20, -2000, 0, 'end')) - assert_equal(np.fv(0.075, 20, -2000, 0, 0), - np.fv(0.075, 20, -2000, 0, 'end')) + assert_equal(npf.fv(0.075, 20, -2000, 0), + npf.fv(0.075, 20, -2000, 0, 'end')) + assert_equal(npf.fv(0.075, 20, -2000, 0, 0), + npf.fv(0.075, 20, -2000, 0, 'end')) # begin - assert_equal(np.pmt(0.08 / 12, 5 * 12, 15000., 0, 1), - np.pmt(0.08 / 12, 5 * 12, 15000., 0, 'begin')) + assert_equal(npf.pmt(0.08 / 12, 5 * 12, 15000., 0, 1), + npf.pmt(0.08 / 12, 5 * 12, 15000., 0, 'begin')) # end - assert_equal(np.pmt(0.08 / 12, 5 * 12, 15000., 0), - np.pmt(0.08 / 12, 5 * 12, 15000., 0, 'end')) - assert_equal(np.pmt(0.08 / 12, 5 * 12, 15000., 0, 0), - np.pmt(0.08 / 12, 5 * 12, 15000., 0, 'end')) + assert_equal(npf.pmt(0.08 / 12, 5 * 12, 15000., 0), + npf.pmt(0.08 / 12, 5 * 12, 15000., 0, 'end')) + assert_equal(npf.pmt(0.08 / 12, 5 * 12, 15000., 0, 0), + npf.pmt(0.08 / 12, 5 * 12, 15000., 0, 'end')) # begin - assert_equal(np.ppmt(0.1 / 12, 1, 60, 55000, 0, 1), - np.ppmt(0.1 / 12, 1, 60, 55000, 0, 'begin')) + assert_equal(npf.ppmt(0.1 / 12, 1, 60, 55000, 0, 1), + npf.ppmt(0.1 / 12, 1, 60, 55000, 0, 'begin')) # end - assert_equal(np.ppmt(0.1 / 12, 1, 60, 55000, 0), - np.ppmt(0.1 / 12, 1, 60, 55000, 0, 'end')) - assert_equal(np.ppmt(0.1 / 12, 1, 60, 55000, 0, 0), - np.ppmt(0.1 / 12, 1, 60, 55000, 0, 'end')) + assert_equal(npf.ppmt(0.1 / 12, 1, 60, 55000, 0), + npf.ppmt(0.1 / 12, 1, 60, 55000, 0, 'end')) + assert_equal(npf.ppmt(0.1 / 12, 1, 60, 55000, 0, 0), + npf.ppmt(0.1 / 12, 1, 60, 55000, 0, 'end')) # begin - assert_equal(np.ipmt(0.1 / 12, 1, 24, 2000, 0, 1), - np.ipmt(0.1 / 12, 1, 24, 2000, 0, 'begin')) + assert_equal(npf.ipmt(0.1 / 12, 1, 24, 2000, 0, 1), + npf.ipmt(0.1 / 12, 1, 24, 2000, 0, 'begin')) # end - assert_equal(np.ipmt(0.1 / 12, 1, 24, 2000, 0), - np.ipmt(0.1 / 12, 1, 24, 2000, 0, 'end')) - assert_equal(np.ipmt(0.1 / 12, 1, 24, 2000, 0, 0), - np.ipmt(0.1 / 12, 1, 24, 2000, 0, 'end')) + assert_equal(npf.ipmt(0.1 / 12, 1, 24, 2000, 0), + npf.ipmt(0.1 / 12, 1, 24, 2000, 0, 'end')) + assert_equal(npf.ipmt(0.1 / 12, 1, 24, 2000, 0, 0), + npf.ipmt(0.1 / 12, 1, 24, 2000, 0, 'end')) # begin - assert_equal(np.nper(0.075, -2000, 0, 100000., 1), - np.nper(0.075, -2000, 0, 100000., 'begin')) + assert_equal(npf.nper(0.075, -2000, 0, 100000., 1), + npf.nper(0.075, -2000, 0, 100000., 'begin')) # end - assert_equal(np.nper(0.075, -2000, 0, 100000.), - np.nper(0.075, -2000, 0, 100000., 'end')) - assert_equal(np.nper(0.075, -2000, 0, 100000., 0), - np.nper(0.075, -2000, 0, 100000., 'end')) + assert_equal(npf.nper(0.075, -2000, 0, 100000.), + npf.nper(0.075, -2000, 0, 100000., 'end')) + assert_equal(npf.nper(0.075, -2000, 0, 100000., 0), + npf.nper(0.075, -2000, 0, 100000., 'end')) def test_decimal_with_when(self): - """Test that decimals are still supported if the when argument is passed""" + """ + Test that decimals are still supported if the when argument is passed + """ # begin - assert_equal(np.rate(Decimal('10'), Decimal('20'), Decimal('-3500'), Decimal('10000'), Decimal('1')), - np.rate(Decimal('10'), Decimal('20'), Decimal('-3500'), Decimal('10000'), 'begin')) + assert_equal(npf.rate(Decimal('10'), Decimal('20'), Decimal('-3500'), + Decimal('10000'), Decimal('1')), + npf.rate(Decimal('10'), Decimal('20'), Decimal('-3500'), + Decimal('10000'), 'begin')) # end - assert_equal(np.rate(Decimal('10'), Decimal('20'), Decimal('-3500'), Decimal('10000')), - np.rate(Decimal('10'), Decimal('20'), Decimal('-3500'), Decimal('10000'), 'end')) - assert_equal(np.rate(Decimal('10'), Decimal('20'), Decimal('-3500'), Decimal('10000'), Decimal('0')), - np.rate(Decimal('10'), Decimal('20'), Decimal('-3500'), Decimal('10000'), 'end')) + assert_equal(npf.rate(Decimal('10'), Decimal('20'), Decimal('-3500'), + Decimal('10000')), + npf.rate(Decimal('10'), Decimal('20'), Decimal('-3500'), + Decimal('10000'), 'end')) + assert_equal(npf.rate(Decimal('10'), Decimal('20'), Decimal('-3500'), + Decimal('10000'), Decimal('0')), + npf.rate(Decimal('10'), Decimal('20'), Decimal('-3500'), + Decimal('10000'), 'end')) # begin - assert_equal(np.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), Decimal('0'), Decimal('1')), - np.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), Decimal('0'), 'begin')) - # end - assert_equal(np.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), Decimal('0')), - np.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), Decimal('0'), 'end')) - assert_equal(np.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), Decimal('0'), Decimal('0')), - np.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), Decimal('0'), 'end')) - - # begin - assert_equal(np.fv(Decimal('0.075'), Decimal('20'), Decimal('-2000'), Decimal('0'), Decimal('1')), - np.fv(Decimal('0.075'), Decimal('20'), Decimal('-2000'), Decimal('0'), 'begin')) + assert_equal(npf.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), + Decimal('0'), Decimal('1')), + npf.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), + Decimal('0'), 'begin')) # end - assert_equal(np.fv(Decimal('0.075'), Decimal('20'), Decimal('-2000'), Decimal('0')), - np.fv(Decimal('0.075'), Decimal('20'), Decimal('-2000'), Decimal('0'), 'end')) - assert_equal(np.fv(Decimal('0.075'), Decimal('20'), Decimal('-2000'), Decimal('0'), Decimal('0')), - np.fv(Decimal('0.075'), Decimal('20'), Decimal('-2000'), Decimal('0'), 'end')) + assert_equal(npf.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), + Decimal('0')), + npf.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), + Decimal('0'), 'end')) + assert_equal(npf.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), + Decimal('0'), Decimal('0')), + npf.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), + Decimal('0'), 'end')) # begin - assert_equal(np.pmt(Decimal('0.08') / Decimal('12'), Decimal('5') * Decimal('12'), Decimal('15000.'), + assert_equal(npf.fv(Decimal('0.075'), Decimal('20'), Decimal('-2000'), Decimal('0'), Decimal('1')), - np.pmt(Decimal('0.08') / Decimal('12'), Decimal('5') * Decimal('12'), Decimal('15000.'), + npf.fv(Decimal('0.075'), Decimal('20'), Decimal('-2000'), Decimal('0'), 'begin')) # end - assert_equal(np.pmt(Decimal('0.08') / Decimal('12'), Decimal('5') * Decimal('12'), Decimal('15000.'), + assert_equal(npf.fv(Decimal('0.075'), Decimal('20'), Decimal('-2000'), Decimal('0')), - np.pmt(Decimal('0.08') / Decimal('12'), Decimal('5') * Decimal('12'), Decimal('15000.'), + npf.fv(Decimal('0.075'), Decimal('20'), Decimal('-2000'), Decimal('0'), 'end')) - assert_equal(np.pmt(Decimal('0.08') / Decimal('12'), Decimal('5') * Decimal('12'), Decimal('15000.'), + assert_equal(npf.fv(Decimal('0.075'), Decimal('20'), Decimal('-2000'), Decimal('0'), Decimal('0')), - np.pmt(Decimal('0.08') / Decimal('12'), Decimal('5') * Decimal('12'), Decimal('15000.'), + npf.fv(Decimal('0.075'), Decimal('20'), Decimal('-2000'), Decimal('0'), 'end')) # begin - assert_equal(np.ppmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('60'), Decimal('55000'), + assert_equal(npf.pmt(Decimal('0.08') / Decimal('12'), + Decimal('5') * Decimal('12'), Decimal('15000.'), Decimal('0'), Decimal('1')), - np.ppmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('60'), Decimal('55000'), + npf.pmt(Decimal('0.08') / Decimal('12'), + Decimal('5') * Decimal('12'), Decimal('15000.'), Decimal('0'), 'begin')) # end - assert_equal(np.ppmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('60'), Decimal('55000'), + assert_equal(npf.pmt(Decimal('0.08') / Decimal('12'), + Decimal('5') * Decimal('12'), Decimal('15000.'), Decimal('0')), - np.ppmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('60'), Decimal('55000'), + npf.pmt(Decimal('0.08') / Decimal('12'), + Decimal('5') * Decimal('12'), Decimal('15000.'), Decimal('0'), 'end')) - assert_equal(np.ppmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('60'), Decimal('55000'), + assert_equal(npf.pmt(Decimal('0.08') / Decimal('12'), + Decimal('5') * Decimal('12'), Decimal('15000.'), Decimal('0'), Decimal('0')), - np.ppmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('60'), Decimal('55000'), + npf.pmt(Decimal('0.08') / Decimal('12'), + Decimal('5') * Decimal('12'), Decimal('15000.'), Decimal('0'), 'end')) # begin - assert_equal(np.ipmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('24'), Decimal('2000'), - Decimal('0'), Decimal('1')).flat[0], - np.ipmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('24'), Decimal('2000'), - Decimal('0'), 'begin').flat[0]) + assert_equal(npf.ppmt(Decimal('0.1') / Decimal('12'), + Decimal('1'), Decimal('60'), Decimal('55000'), + Decimal('0'), Decimal('1')), + npf.ppmt(Decimal('0.1') / Decimal('12'), Decimal('1'), + Decimal('60'), Decimal('55000'), + Decimal('0'), 'begin')) + # end + assert_equal(npf.ppmt(Decimal('0.1') / Decimal('12'), Decimal('1'), + Decimal('60'), Decimal('55000'), Decimal('0')), + npf.ppmt(Decimal('0.1') / Decimal('12'), Decimal('1'), + Decimal('60'), Decimal('55000'), Decimal('0'), + 'end')) + assert_equal(npf.ppmt(Decimal('0.1') / Decimal('12'), Decimal('1'), + Decimal('60'), Decimal('55000'), Decimal('0'), + Decimal('0')), + npf.ppmt(Decimal('0.1') / Decimal('12'), Decimal('1'), + Decimal('60'), Decimal('55000'), Decimal('0'), + 'end')) + + # begin + assert_equal(npf.ipmt(Decimal('0.1') / Decimal('12'), Decimal('1'), + Decimal('24'), Decimal('2000'), + Decimal('0'), Decimal('1')).flat[0], + npf.ipmt(Decimal('0.1') / Decimal('12'), Decimal('1'), + Decimal('24'), Decimal('2000'), + Decimal('0'), 'begin').flat[0]) # end - assert_equal(np.ipmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('24'), Decimal('2000'), - Decimal('0')).flat[0], - np.ipmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('24'), Decimal('2000'), - Decimal('0'), 'end').flat[0]) - assert_equal(np.ipmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('24'), Decimal('2000'), - Decimal('0'), Decimal('0')).flat[0], - np.ipmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('24'), Decimal('2000'), - Decimal('0'), 'end').flat[0]) + assert_equal(npf.ipmt(Decimal('0.1') / Decimal('12'), Decimal('1'), + Decimal('24'), Decimal('2000'), + Decimal('0')).flat[0], + npf.ipmt(Decimal('0.1') / Decimal('12'), Decimal('1'), + Decimal('24'), Decimal('2000'), + Decimal('0'), 'end').flat[0]) + assert_equal(npf.ipmt(Decimal('0.1') / Decimal('12'), Decimal('1'), + Decimal('24'), Decimal('2000'), + Decimal('0'), Decimal('0')).flat[0], + npf.ipmt(Decimal('0.1') / Decimal('12'), Decimal('1'), + Decimal('24'), Decimal('2000'), + Decimal('0'), 'end').flat[0]) def test_broadcast(self): - assert_almost_equal(np.nper(0.075, -2000, 0, 100000., [0, 1]), + assert_almost_equal(npf.nper(0.075, -2000, 0, 100000., [0, 1]), [21.5449442, 20.76156441], 4) - assert_almost_equal(np.ipmt(0.1 / 12, list(range(5)), 24, 2000), + assert_almost_equal(npf.ipmt(0.1 / 12, list(range(5)), 24, 2000), [-17.29165168, -16.66666667, -16.03647345, -15.40102862, -14.76028842], 4) - assert_almost_equal(np.ppmt(0.1 / 12, list(range(5)), 24, 2000), + assert_almost_equal(npf.ppmt(0.1 / 12, list(range(5)), 24, 2000), [-74.998201, -75.62318601, -76.25337923, -76.88882405, -77.52956425], 4) - assert_almost_equal(np.ppmt(0.1 / 12, list(range(5)), 24, 2000, 0, - [0, 0, 1, 'end', 'begin']), + assert_almost_equal(npf.ppmt(0.1 / 12, list(range(5)), 24, 2000, 0, + [0, 0, 1, 'end', 'begin']), [-74.998201, -75.62318601, -75.62318601, -76.88882405, -76.88882405], 4) def test_broadcast_decimal(self): - # Use almost equal because precision is tested in the explicit tests, this test is to ensure - # broadcast with Decimal is not broken. - assert_almost_equal(np.ipmt(Decimal('0.1') / Decimal('12'), list(range(5)), Decimal('24'), Decimal('2000')), - [Decimal('-17.29165168'), Decimal('-16.66666667'), Decimal('-16.03647345'), - Decimal('-15.40102862'), Decimal('-14.76028842')], 4) - - assert_almost_equal(np.ppmt(Decimal('0.1') / Decimal('12'), list(range(5)), Decimal('24'), Decimal('2000')), - [Decimal('-74.998201'), Decimal('-75.62318601'), Decimal('-76.25337923'), - Decimal('-76.88882405'), Decimal('-77.52956425')], 4) - - assert_almost_equal(np.ppmt(Decimal('0.1') / Decimal('12'), list(range(5)), Decimal('24'), Decimal('2000'), - Decimal('0'), [Decimal('0'), Decimal('0'), Decimal('1'), 'end', 'begin']), - [Decimal('-74.998201'), Decimal('-75.62318601'), Decimal('-75.62318601'), - Decimal('-76.88882405'), Decimal('-76.88882405')], 4) + # Use almost equal because precision is tested in the explicit tests, + # this test is to ensure broadcast with Decimal is not broken. + assert_almost_equal(npf.ipmt(Decimal('0.1') / Decimal('12'), + list(range(5)), Decimal('24'), + Decimal('2000')), + [Decimal('-17.29165168'), Decimal('-16.66666667'), + Decimal('-16.03647345'), Decimal('-15.40102862'), + Decimal('-14.76028842')], 4) + + assert_almost_equal(npf.ppmt(Decimal('0.1') / Decimal('12'), + list(range(5)), Decimal('24'), + Decimal('2000')), + [Decimal('-74.998201'), Decimal('-75.62318601'), + Decimal('-76.25337923'), Decimal('-76.88882405'), + Decimal('-77.52956425')], 4) + + assert_almost_equal(npf.ppmt(Decimal('0.1') / Decimal('12'), + list(range(5)), Decimal('24'), + Decimal('2000'), Decimal('0'), + [Decimal('0'), Decimal('0'), Decimal('1'), + 'end', 'begin']), + [Decimal('-74.998201'), Decimal('-75.62318601'), + Decimal('-75.62318601'), Decimal('-76.88882405'), + Decimal('-76.88882405')], 4) From a826100f959204b9a39c043cb9cf1c43c5766f89 Mon Sep 17 00:00:00 2001 From: Warren Weckesser Date: Fri, 27 Sep 2019 12:50:23 -0400 Subject: [PATCH 65/65] MAINT: Add LICENSE.txt. --- LICENSE.txt | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 LICENSE.txt diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..5eae320 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,30 @@ +Copyright (c) 2005-2019, NumPy Developers. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of the NumPy Developers nor the names of any + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.