First, thanks to authors for such nicely paced yet simple lectures.

I have question regarding simulate Loan repayment function. It appears in Quantecon Datascience Lectures - Scientific Computing - Randomness section (For easy access to readers).

In function def simulate_loan_repayments_slow each value in the array repayment_sims is present valued using code line repayment_sims[i] = (1 / (1 + r)) * repaid.

However, in the vectorized version def simulate_loan_repayments, no such discounting is conducted for both repayment_sims arrays.

Have I missed any subtlety?

Also, while defining full in the vectorized version def simulate_loan_repayments, Bitwise NOT (~) was used(full = ~partial & (random_numbers <= 0.95). How exactly this operation works in this function? For, as I understand, this bitwise inversion works only on integral numbers whereas code generates random numbers between 0 and 1.

Also, correlated question is, how the vectorized function def simulate_loan_repayments covers entire range of 95% probability of both full and partial repayments, when it returns repayment_sims?.

Thank You for your assistance and I apologize if this is all very trivial.

Thanks for asking these questions – We’re glad that you’re using the material.

For the first question, you are correct that the payments should be discounted – This is a mistake in the lectures (which we will correct). We should update the simulate_loan_repayments function to discount future payments. This could be done simply by returning (1 / (1+r))*repayment_sims rather than just repayment_sims.

Good question on the ~ operator. We probably should have a little more explanation on what this does. In this case, we create a new array called partial = random_numbers <= 0.20 which is an array of booleans that denotes whether that particular loan was partially repaid. We then fill the repayment_sims array with the partial payment (repayment_part) everywhere that array has True values. Next, we want to determine whether there was full repayment. Partial repayment happened with probability 0.20 and full repayment occurs with probability 0.75. The ~ operator will simply act like not from the boolean lectures, so it will switch True to False and False to True. The & operator will act like the and operator f rom the boolean lecture – The loans that are fully repaid are the ones that happen when the random number is less than 0.95 (0.2 + 0.75) AND that aren’t partial repayments. This also could have been defined as full = (random_numbers > 0.20) & (random_numbers < 0.95).

Does this help? Please continue to ask questions if you have more questions on this or any other lecture.

Thank you for your reply. For (~) query creation of boolean array was the idea, I was missing. I should have used print statements at appropriate locations as shown below. However, I was able to guess the NOT operator is used to exclude probability with partial repayments.

Sorry to revive this topic, but I have a related question. On the profitability threshold section, your code use the 5th percentile of outcomes, but the code uses

I looked up to the definition of np.percentile. I think if you will use the 5th percentile, it should be this instead.

lro_5 = np.percentile(loan_repayment_outcomes, 5)

Please correct me if I am wrong.

Also, it would be great if I understand this logic correctly. If instead I use lro_5 = np.percentile(loan_repayment_outcomes, 95), it will mean the loan is profitable just 5% of time, isn’t it? (I kinda think like the higher the loan size, the less probable it will be profitable, on average.)

Another related point is in the lecture:

If a loan is currently in default, then it has a 10% chance of returning, 0% chance of delinquency, and a 100% probability of staying in default.

while in the code the Markov matrix for the last row is 0 0 1, I think 10% should then be 0%, no?

Thanks for catching this! I think what happened is that we typically have used np.quantile (which expects a number between 0 and 1) rather than np.percentile (which expects a number between 0 and 100) and so this is definitely a bug on our part.

Your thoughts on how the profitability of a loan relates to the size of the loan are exactly correct. The np.percentile(loan_repayment_outcomes, 95) will give you the 95th percentile of how much is repaid and so, if you used loans of this size with the specified repayment policy, then you would only expect to be profitable 5% of the time. Note that the fact that the repayment outcomes here don’t depend on the loan size is doing almost all of the work here!

Your point about the transition matrix is also correct. I think at one point we had wanted to allow for exiting default but then decided to keep it simple (and must have forgotten to update all of the text).