```
In [2]:
```import numpy as np

Create two very long NumPy arrays `x`

and `y`

and sum the arrays using:

- The NumPy addition syntax,
`z = x + y`

; and - A
`for`

loop that computes the sum entry-by-entry

Compare the time required for the two approaches for vectors of different lengths. The values of the array entries are not important for this test.

*Hint:* To loop over an array using indices, try a construction like:

```
x = np.ones(100)
y = np.ones(len(x))
for i in range(len(x)):
print(x[i]*y[i])
```

Timing NumPy addition for 1 million elements arrays

```
In [2]:
```n = 1000000
x = np.random.rand(n)
y = np.random.rand(n)
%time z = x + y

```
```

Timing 1 million elements arrays addition using an entry-by-entry function

```
In [3]:
```def sum_vec(x, y):
"Sum two vectors entry by entry"
z = np.zeros(n)
for i in range(n):
z[i] = x[i] + y[i]
return z
%time w = sum_vec(x, y)

```
```

Anonymised scores (out of 60) for an examination are stored in a NumPy array. Write:

- A function that takes a NumPy array of the raw scores and returns the scores as a percentage sorted from
lowest to highest (try using
`scores.sort()`

, where`scores`

is a NumPy array holding the scores). - A function that returns the maximum, minimum and mean of the raw scores as a dictionary with the
keys '
`min`

', '`max`

' and '`mean`

'. Use the NumPy array functions`min()`

,`max()`

and`mean()`

to do the computation, e.g.`max = scores.max()`

. - Modify your function for the min, max and mean to optionally exclude the highest and lowest scores from the
computation of the min, max and mean.
*Hint:*sort the array of scores and use array slicing to exclude the first and the last entries.

Use the scores

```
scores = np.array([58.0, 35.0, 24.0, 42, 7.8])
```

```
In [4]:
```# Test scores
scores = np.array([58.0, 35.0, 24.0, 42, 7.8])

```
In [5]:
```def percentages(scores):
"Calculate percentages (max score = 60) from a list of scores and returns them sorted"
sorted_scores = scores / 60
sorted_scores.sort()
return sorted_scores
print(percentages(scores))

```
```

```
In [6]:
```def max_min_mean(scores):
"Return a dictionary with max, min and mean score from a list of scores"
out = {}
out['min'] = scores.min()
out['max'] = scores.max()
out['mean'] = scores.mean()
return out
print(max_min_mean(scores))

```
```

```
In [7]:
```def max_min_mean2(scores, exclude_extreme):
"Return a dictionary with max, min and mean score from a list of scores excluding extremes if exclude_extreme = 1"
out = {}
# If extremes are excluded filter the sorted list from second to second to last element
if exclude_extreme == 1:
scores.sort()
scores_filtered = scores[1:-1]
# Else use the entire list
elif exclude_extreme == 0:
scores_filtered = scores
# If exclude_extreme is not 0 nor 1 return a message
else:
return 'The second parameter should be either 0 (to include extremes) or 1 (to exclude them)'
out['min'] = scores_filtered.min()
out['max'] = scores_filtered.max()
out['mean'] = scores_filtered.mean()
return out
print(max_min_mean2(scores, 1))

```
```

```
In [8]:
```A = np.array([[4.0, 7.0, -2.43, 67.1],
[-4.0, 64.0, 54.7, -3.33],
[2.43, 23.2, 3.64, 4.11],
[1.2, 2.5, -113.2, 323.22]])
print(A)

```
```

use array slicing to

- Extract the third column as a 1D array
- Extract the first two rows as a 2D sub-array
- Extract the bottom-right $2 \times 2$ block as a 2D sub-array
- Sum the last column

Print the results to the screen to check. Try to use array slicing such that your code would still work if the dimensions of `A`

were enlarged.

Also, compute the tranpose of `A`

(search online to find the function/syntax to do this).

Third column as a 1D array

```
In [9]:
```print(A[:,2])

```
```

First two rows as a 2D sub-array

```
In [10]:
```print(A[:2,:])

```
```

Bottom right $2 \times 2$ block as a 2D sub-array

```
In [11]:
```print(A[-2:,-2:])

```
```

Sum of the last column

```
In [12]:
```print(A[:,-1].sum())

```
```

Transpose of A

```
In [13]:
```print(A.transpose())

```
```

In a previous exercise you implemented the bisection algorithm to find approximate roots of a mathematical function. Use the SciPy bisection function `optimize.bisect`

(http://docs.scipy.org/doc/scipy-0.14.0/reference/generated/scipy.optimize.bisect.html) to find roots of the mathematical function that was used in the previous exercise. Compare the results computed by SciPy and your program from the earlier exercise, and compare the computational time (using `%time`

).

```
In [3]:
```def f(x):
return x**3 - 6*x**2 + 4*x + 12
#return x**2 + x - 20 # Roots = -5, 4

```
In [6]:
```def compute_root(f, x0, x1, tol, max_it):
"""Computes the root of f between x0 and x1 using bisection,
stops if the value of f at the root is under tol or if max_it is reached
and returns the root, the value of f at the root and the number of iterations"""
for i in range(max_it):
# Compute x_mid
x_mid = (x0 + x1) / 2
# Compute f for the three values
f_0, f_1, f_mid = f(x0), f(x1), f(x_mid)
# Check the value of f_0*f_mid to determine how to update the endpoints
if f_0*f_mid < 0:
x1 = x_mid
else:
x0 = x_mid
# Check if f is under tol
if abs(f_mid) < tol:
return x_mid, f_mid, i+1
# Return the approximate root in case max_it is reached
return x_mid, f_mid, i+1
# Test for the function f
%time x, f_x, num_it = compute_root(f, x0=3, x1=6, tol=1.0e-6, max_it=1000)
print('Approximate root:', x)
print('Value of f:', f_x)
print('Number of iterations:', num_it)

```
```

```
In [7]:
```from scipy.optimize import bisect as bisect
# Compute the root of f using scipy function
%time x0 = bisect(f, a=3, b=6)
print('Approximate root:', x0)
print('Value of f:', f(x0))

```
```