Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated irr function [issue 98] #99

Merged
merged 10 commits into from
Mar 21, 2024
Merged

Updated irr function [issue 98] #99

merged 10 commits into from
Mar 21, 2024

Conversation

yatshunlee
Copy link
Contributor

calculate IRR by calculating all its roots and select the most reasonable IRR among all the real roots

https://numpy.org/doc/stable/reference/generated/numpy.roots.html

calculate IRR by calculating all its real roots
@yatshunlee yatshunlee changed the title Updated irr function Updated irr function [issue 98] Jan 5, 2024
@yatshunlee
Copy link
Contributor Author

yatshunlee commented Jan 5, 2024

Sorry I found that I made a trival mistake: should be values instead of cash_flow

Corrected variable name `values` from ``cash_flow
- test_financial.py: Altered test cases
- _financial.py: Added optional argument `raise_exceptions`
@yatshunlee
Copy link
Contributor Author

Updated tests/test_financial.py and nump_financial/_financial.py

  • test_financial.py: Altered test cases
  • _financial.py: Added optional argument raise_exceptions

Copy link
Member

@Kai-Striega Kai-Striega left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks pretty good.

Could you add a test case where it would normally have returned a “worse” outcome, e.g. the one in your example?

I'm a bit hesitant to choose a solution for the user: I guess we are doing that implicitly anyway with the iterative solver, so this is an improvement.


with pytest.raises(npf.IterationsExceededError):
npf.irr(cashflows, maxiter=1, raise_exceptions=True)
npf.irr(cashflows, raise_exceptions=True)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a missing blank line here at the end of the file

Comment on lines 849 to 854
# if the signs of IRR solutions are not the same, first filter potential IRR
# by comparing the total positive and negative cash flows.
if not same_sign:
pos = sum(values[values>0])
neg = sum(values[values<0])
if pos > neg:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why you are trying to filter the by the total cashflow. Is this just some heuristic you have picked or is there a more formal process going on here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just some heuristic I have picked. It works when there are real roots with different signs.

I can give you a simple example and walk you through it:

real roots = -0.5 and 0.1
net sum of the cash flows = +10000

As the net sum > 0, we can kind of exclude the possibility that IRR is -0.5 and keep only 0.1.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense. Thanks for clarifying.

Comment on lines 856 to 857
else:
IRR = IRR[IRR < 0]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be indented further?

g = 1 + guess

g = np.roots(values)
IRR = np.real(g[np.isreal(g)]) - 1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: I don't like having all capital variable names, could you rename it to something meaningful?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IRR -> internal_rate_return

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would work. Or maybe something like irr_ if you wanted to be brief. The rest of the code seems to be brief, although I prefer being explicit in general.

@yatshunlee
Copy link
Contributor Author

yatshunlee commented Jan 6, 2024

image

image

Interestingly, I just pumped into a situation like this.

The real roots are 0.0 and 0.2136. Considering the sum of cashflow = 0, did Excel give an incorrect solution? But I personally prefer selecting the smallest root in terms of its magnitude more. So, I will prefer 0.0 rather than 0.2136.

I think we do have to choose a solution for users implicitly. But maybe also include arguments allowing them to choose their options? Like keep_smallest_root=True, keep_all_roots=False...

Any suggestion?

- Fixed error and changed IRR -> internal_rate_of_return
- Included 2 more test cases
@Kai-Striega
Copy link
Member

did Excel give an incorrect solution?

As you have previously noted, there is more than 1 valid solution. I believe that Excel uses an iterative solver to find a solution, with the initial guess being 10%. My guess is that, as both are mathematically valid, Excel simply chose the first solution it converged too.

I think we do have to choose a solution for users implicitly.

I don't think we have to decide a solution for the user explicitly. We could do it via our existing iterative solver. Although that has weaknesses (as you already know).

But maybe also include arguments allowing them to choose their options?

I'm -1 on giving more options to irr, it would make the function more complicated (and there are already a lot of options). I've already been considering adding a new function irr_full_compilment (or some shorter name) that returns the full complement of solutions and lets the user decide what to do with it.

- internal_rate_of_return -> eirr
- abs_internal_rate_of_return -> abs_eirr
@yatshunlee
Copy link
Contributor Author

yatshunlee commented Jan 18, 2024

I've finally updated the code.

here is what I did:

  1. removed the previous conflicted part in the code which lead a merging error (i did not realize it previously. sorry for being too junior to submit a pull request :( and need you to test my code for so many times~~~)

  2. removed one of my newly added test case:

  • ([-10000, 4000, 2000, 1000, 3000], 0.0000)

image

because I don't see why it shows an error here even if the error is actually small here.

therefore, i removed the test case that I made previously.

@yatshunlee
Copy link
Contributor Author

because I don't see why it shows an error here even if the error is actually small here.

therefore, i removed the test case that I made previously.

I just realized someone had previously made a pull request on changing the _test_financial.py and removed all the assert_almost_equal function, making my original function fail on those newly added sophisicated test cases, with a very small abs difference like 9e-16 which is not 0.

So, I've removed all the test cases that I added previously.

Copy link
Member

@Kai-Striega Kai-Striega left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for leaving this, I moved states and got distracted :/. I think your solution is better than the iterative approach we use now.

Thanks for taking the time to contribute. I'll play around with it this weekend some more - but it should be approved

numpy_financial/_financial.py Outdated Show resolved Hide resolved
@Kai-Striega
Copy link
Member

Changes look good.

I've asked a friend from industry to try and poke some holes into the new method, but I don't expect there to be anything seriously wrong here.

Thanks for taking the initiative!

@yatshunlee
Copy link
Contributor Author

Changes look good.

I've asked a friend from industry to try and poke some holes into the new method, but I don't expect there to be anything seriously wrong here.

Thanks for taking the initiative!

Np! My pleasure! I always enjoy making things like this.

@Kai-Striega
Copy link
Member

Np! My pleasure! I always enjoy making things like this.

That's great to hear. There are plenty of things that will still need to be done on NumPy-financial if you are interested

As an aside, for the future, could you please follow the guide writing you commit message from the NumPy docs?

@yatshunlee
Copy link
Contributor Author

yatshunlee commented Mar 12, 2024

That's great to hear. There are plenty of things that will still need to be done on NumPy-financial if you are interested

As an aside, for the future, could you please follow the guide writing you commit message from the NumPy docs?

Yea thanks for letting me know

@Kai-Striega Kai-Striega added the enhancement New feature or request label Mar 13, 2024
@Kai-Striega Kai-Striega added this to the 2.0 milestone Mar 13, 2024
Copy link
Member

@Kai-Striega Kai-Striega left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks excellent overall. There's one comment that I will need to think about. I think it's ready to merge. Are there still any changes you would like to do?

d_npv = npv_.deriv()
g = 1 + guess

g = np.roots(values)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the roots docs:

This forms part of the old polynomial API. Since version 1.4, the new polynomial API defined in numpy.polynomial is preferred. A summary of the differences can be found in the transition guide.

However I'm not sure if it's worth to construct a whole Polynomial object just for this one calculation...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm currently re-writing some of the functions to use numba, it looks like np.roots is supported, but the new Polynomial API is not. So I'd prefer this version.

@Kai-Striega Kai-Striega merged commit 50c0930 into numpy:main Mar 21, 2024
10 checks passed
@Kai-Striega
Copy link
Member

Merged, Thanks @yatshunlee for taking the time to contribute.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants