Clean way to return default when taking minimum of empty NumPy array

Viewed 90

I have two arrays, one holding a series of years and another holding some quantities. I want to study for each year how long it takes for the quantity to double.

For this I wrote this code:

years = np.arange(2020, 2060)
qma = np.array([8.00000000e+13, 8.14928049e+13, 8.30370113e+13, 8.46353044e+13,
       8.62905581e+13, 8.80058517e+13, 8.97844887e+13, 9.16300175e+13,
       9.35462542e+13, 9.55373083e+13, 9.76076116e+13, 9.97619497e+13,
       1.02005499e+14, 1.04343864e+14, 1.06783128e+14, 1.09329900e+14,
       1.11991375e+14, 1.14775397e+14, 1.17690539e+14, 1.20746183e+14,
       1.23952624e+14, 1.27321176e+14, 1.30864305e+14, 1.34595778e+14,
       1.38530838e+14, 1.74048570e+14, 1.92205500e+14, 2.14405932e+14,
       2.42128686e+14, 2.77655470e+14, 3.24688168e+14, 3.89624819e+14,
       4.84468500e+14, 6.34373436e+14, 9.74364148e+14, 2.33901669e+15,
       1.78934647e+16, 4.85081278e+20, 8.63469750e+21, 2.08204297e+22])

def doubling_year(idx):
      try:
        return years[qma >= 2*qma[idx]].min()
      except ValueError:
        return np.nan
    
years_until_doubling = [doubling_year(idx) - years[idx] 
                        for idx in range(len(years))]

This works as I expect, but having to define a named function for what is essentially a one-liner feels wrong. Is there a cleaner and more succing way of replicating this behaviour?

1 Answers

For each year in the series, the number of years in which the quantity is double or more the original quantity can be computed through broadcasting. Then you simply have to subtract that number from the number of years remaining in the series and replace the 0's with np.nan's.

In [426]: n_doubled = np.sum(qma[None, :] >= 2*qma[:, None], axis=1)

In [427]: n_doubled
Out[427]: 
array([15, 15, 15, 15, 15, 14, 14, 14, 14, 14, 13, 13, 13, 13, 13, 12, 12,
       12, 12, 12, 11, 11, 11, 11, 11,  9,  9,  8,  8,  7,  6,  6,  6,  5,
        5,  4,  3,  2,  1,  0])

In [428]: np.where(n_doubled, np.arange(len(years), 0, -1) - n_doubled, np.nan)
Out[428]: 
array([25., 24., 23., 22., 21., 21., 20., 19., 18., 17., 17., 16., 15.,
       14., 13., 13., 12., 11., 10.,  9.,  9.,  8.,  7.,  6.,  5.,  6.,
        5.,  5.,  4.,  4.,  4.,  3.,  2.,  2.,  1.,  1.,  1.,  1.,  1.,
       nan])
Related