@@ -281,11 +281,13 @@ We will say more about this {doc}`later <writing_good_code>`.
281281Consider again this code from the {doc}`previous lecture <python_by_example>`
282282283283```{code-cell} python3
284+rng = np.random.default_rng()
285+284286ts_length = 100
285287ϵ_values = [] # empty list
286288287289for i in range(ts_length):
288- e = np.random.randn()
290+ e = rng.standard_normal()
289291 ϵ_values.append(e)
290292291293plt.plot(ϵ_values)
@@ -306,7 +308,7 @@ This is accomplished in the next program
306308def generate_data(n):
307309 ϵ_values = []
308310 for i in range(n):
309- e = np.random.randn()
311+ e = rng.standard_normal()
310312 ϵ_values.append(e)
311313 return ϵ_values
312314@@ -336,9 +338,9 @@ def generate_data(n, generator_type):
336338 ϵ_values = []
337339 for i in range(n):
338340 if generator_type == 'U':
339- e = np.random.uniform(0, 1)
341+ e = rng.uniform(0, 1)
340342 else:
341- e = np.random.randn()
343+ e = rng.standard_normal()
342344 ϵ_values.append(e)
343345 return ϵ_values
344346@@ -358,7 +360,7 @@ Notes
358360359361Now, there are several ways that we can simplify the code above.
360362361-For example, we can get rid of the conditionals all together by just passing the desired generator type *as a function*.
363+For example, we can get rid of the conditionals all together by just passing the desired generator type as a function, method, or other [callable](https://typing.python.org/en/latest/spec/callables.html) object.
362364363365To understand this, consider the following version.
364366@@ -371,19 +373,19 @@ def generate_data(n, generator_type):
371373 ϵ_values.append(e)
372374 return ϵ_values
373375374-data = generate_data(100, np.random.uniform)
376+data = generate_data(100, rng.uniform)
375377plt.plot(data)
376378plt.show()
377379```
378380379-Now, when we call the function `generate_data()`, we pass `np.random.uniform`
381+Now, when we call the function `generate_data()`, we pass `rng.uniform`
380382as the second argument.
381383382-This object is a *function*.
384+This object is a *callable* — that is, an object that can be called using parentheses.
383385384-When the function call `generate_data(100, np.random.uniform)` is executed, Python runs the function code block with `n` equal to 100 and the name `generator_type` "bound" to the function `np.random.uniform`.
386+When the function call `generate_data(100, rng.uniform)` is executed, Python runs the function code block with `n` equal to 100 and the name `generator_type` "bound" to the callable `rng.uniform`.
385387386-* While these lines are executed, the names `generator_type` and `np.random.uniform` are "synonyms", and can be used in identical ways.
388+* While these lines are executed, the names `generator_type` and `rng.uniform` are "synonyms", and can be used in identical ways.
387389388390This principle works more generally---for example, consider the following piece of code
389391@@ -399,9 +401,7 @@ m(7, 2, 4)
399401Here we created another name for the built-in function `max()`, which could
400402then be used in identical ways.
401403402-In the context of our program, the ability to bind new names to functions
403-means that there is no problem *passing a function as an argument to another
404-function*---as we did above.
404+In the context of our program, the ability to bind names to functions, or more generally to callable objects, means that there is no problem passing one callable object as an argument to another callable --- as we did with `rng.uniform` above.
405405406406407407(recursive_functions)=
@@ -507,7 +507,7 @@ factorial(4)
507507508508The [binomial random variable](https://en.wikipedia.org/wiki/Binomial_distribution) $Y \sim Bin(n, p)$ represents the number of successes in $n$ binary trials, where each trial succeeds with probability $p$.
509509510-Without any import besides `from numpy.random import uniform`, write a function
510+Using `rng = np.random.default_rng()`, write a function
511511`binomial_rv` such that `binomial_rv(n, p)` generates one draw of $Y$.
512512513513```{hint}
@@ -527,12 +527,12 @@ If $U$ is uniform on $(0, 1)$ and $p \in (0,1)$, then the expression `U < p` eva
527527Here is one solution:
528528529529```{code-cell} python3
530-from numpy.random import uniform
530+rng = np.random.default_rng()
531531532532def binomial_rv(n, p):
533533 count = 0
534534 for i in range(n):
535- U = uniform()
535+ U = rng.uniform()
536536 if U < p:
537537 count = count + 1 # Or count += 1
538538 return count
@@ -558,7 +558,7 @@ Second, write another function that does the same task except that the second ru
558558559559- If a head occurs `k` or more times within this sequence, pay one dollar.
560560561-Use no import besides `from numpy.random import uniform`.
561+Use `rng = np.random.default_rng()` to generate random numbers.
562562563563```{exercise-end}
564564```
@@ -573,15 +573,15 @@ Here's a function for the first random device.
573573574574575575```{code-cell} python3
576-from numpy.random import uniform
576+rng = np.random.default_rng()
577577578578def draw(k): # pays if k consecutive successes in a sequence
579579580580 payoff = 0
581581 count = 0
582582583583 for i in range(10):
584- U = uniform()
584+ U = rng.uniform()
585585 count = count + 1 if U < 0.5 else 0
586586 print(count) # print counts for clarity
587587 if count == k:
@@ -601,7 +601,7 @@ def draw_new(k): # pays if k successes in a sequence
601601 count = 0
602602603603 for i in range(10):
604- U = uniform()
604+ U = rng.uniform()
605605 count = count + ( 1 if U < 0.5 else 0 )
606606 print(count)
607607 if count == k: