[Numpy-discussion] mirr test correctly fails for given input.

josef.pktd@gmai... josef.pktd@gmai...
Wed Aug 26 10:44:41 CDT 2009


On Wed, Aug 26, 2009 at 11:25 AM, <josef.pktd@gmail.com> wrote:
> On Wed, Aug 26, 2009 at 10:08 AM, Skipper Seabold<jsseabold@gmail.com> wrote:
>> On Wed, Aug 26, 2009 at 1:45 AM, <josef.pktd@gmail.com> wrote:
>>> On Tue, Aug 25, 2009 at 11:38 PM, Charles R
>>> Harris<charlesr.harris@gmail.com> wrote:
>>>> So is it a bug in the test or a bug in the implementation? The problem is
>>>> that the slice values[1:] when
>>>> values =  [-120000,39000,30000,21000,37000,46000] contains no negative
>>>> number and a nan is returned. This looks like a bug in the test. The
>>>> documentation also probably needs fixing.
>>>>
>>>> Chuck
>>>
>>> There is a bug in the code, the nan is incorrectly raised. After
>>> correcting the nan (checking on the original, instead of shortened
>>> values), I got one failing test, that I corrected with the matching
>>> number from Openoffice.
>>>
>>> (The main problem that the function is more complicated than
>>> necessary, is because np.npv doesn't allow the inclusion of the
>>> investment in the initial period)
>>>
>>> This needs reviewing, since it's late here.
>>>
>>> Josef
>>>
>>>
>>> import numpy as np
>>> from numpy.testing import assert_almost_equal, assert_
>>>
>>> from numpy import npv
>>>
>>> def mirr(values, finance_rate, reinvest_rate):
>>>    """
>>>    Modified internal rate of return.
>>>
>>>    Parameters
>>>    ----------
>>>    values : array_like
>>>        Cash flows (must contain at least one positive and one negative value)
>>>        or nan is returned.
>>>    finance_rate : scalar
>>>        Interest rate paid on the cash flows
>>>    reinvest_rate : scalar
>>>        Interest rate received on the cash flows upon reinvestment
>>>
>>>    Returns
>>>    -------
>>>    out : float
>>>        Modified internal rate of return
>>>
>>>    """
>>>
>>>    values = np.asarray(values, dtype=np.double)
>>>    initial = values[0]
>>>    values1 = values[1:]
>>>    n = values1.size
>>>    pos = values1 > 0
>>>    neg = values1 < 0
>>>    if not (np.sum(values[values>0]) > 0 and np.sum(values[values<0]) < 0):
>>>        return np.nan
>>>    numer = np.abs(npv(reinvest_rate, values1*pos))
>>>    denom = np.abs(npv(finance_rate, values1*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
>>>
>>>
>>>
>>>
>>>
>>> #tests from testsuite and Skipper plus isnan test
>>>
>>> v1 = [-4500,-800,800,800,600,600,800,800,700,3000]
>>> print mirr(v1,0.08,0.055)
>>> assert_almost_equal(mirr(v1,0.08,0.055),
>>>                    0.0666, 4)
>>>
>>> #incorrect test ? corrected
>>> v2 = [-120000,39000,30000,21000,37000,46000]
>>> print mirr(v2,0.10,0.12)
>>> assert_almost_equal(mirr(v2,0.10,0.12), 0.126094, 6)  # corrected from OO
>>>
>>
>> Yes, the value in the tests that this v2 tests against is wrong.  It
>> was the value returned by the old mirr but not excel or oocalc.  This
>> is the correct one.  I noted it in my patch, but it was hard to catch
>> since I didn't supply a diff.  Now, I know...
>>
>>>
>>> v2 = [39000,30000,21000,37000,46000]
>>> assert_(np.isnan(mirr(v2,0.10,0.12)))
>>>
>>>
>>> v3 = [100,200,-50,300,-200]
>>> print mirr(v3,0.05,0.06)
>>> assert_almost_equal(mirr(v3,0.05,0.06), 0.3428, 4)
>>>
>>>
>>> #--------------
>>> print mirr([100, 200, -50, 300, -200], .05, .06)
>>> assert_almost_equal(mirr((100, 200,-50, 300,-200), .05, .06),
>>>                    0.342823387842, 4)
>>>
>>> V2 = [-4500,-800,800,800,600,600,800,800,700,3000]
>>> print mirr(V2, 0.08, 0.055)
>>> assert_almost_equal(mirr(V2, 0.08, 0.055), 0.06659718, 4)
>>
>> Skipper
>> _______________________________________________
>> NumPy-Discussion mailing list
>> NumPy-Discussion@scipy.org
>> http://mail.scipy.org/mailman/listinfo/numpy-discussion
>>
>
> Here is a shortened version, that uses Skippers corrections, but
> avoids splitting the values array, by working around npv not starting
> with the initial investment. It passes the same tests as the corrected
> version.
>
> Josef
>
> def mirr(values, finance_rate, reinvest_rate):
>    values = np.asarray(values, dtype=np.double)
>    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)) * (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
>

a comment on the function

>From a theoretical perspective returning nan wouldn't be necessary.
The rate of return would be well defined:

-1:  you only pay and get nothing back (you lose 100%)
inf:  you only receive and have nothing to pay
0/0 = nan : you don't pay and you get nothing back

for practical purposes, the nan might signal better that the user
might have made a mistake

Josef


More information about the NumPy-Discussion mailing list