# Matrices: Numpy solutions¶

## Introduction¶

Previously we’ve seen Matrices as lists of lists, here we focus on matrices using Numpy library

There are substantially two ways to represent matrices in Python: as list of lists, or with the external library numpy. The most used is surely Numpy, let’s see the reason the principal differences:

List of lists - see separate notebook

native in Python

not efficient

lists are pervasive in Python, probably you will encounter matrices expressed as list of lists anyway

give an idea of how to build a nested data structure

may help in understanding important concepts like pointers to memory and copies

Numpy - this notebook

not natively available in Python

efficient

many libraries for scientific calculations are based on Numpy (scipy, pandas)

syntax to access elements is slightly different from list of lists

in rare cases might give problems of installation and/or conflicts (implementation is not pure Python)

Here we will see data types and essential commands of Numpy library, but we will not get into the details.

The idea is to simply pass using the the data format `ndarray`

without caring too much about performances: for example, even if `for`

cycles in Python are slow because they operate cell by cell, we will use them anyway. In case you actually need to execute calculations fast, you will want to use operators on vectors but for this we invite you to read links below

**ATTENTION**: if you want to use Numpy in Python tutor, instead of default interpreter `Python 3.6`

you will need to select `Python 3.6 with Anaconda`

(at May 2019 results marked as experimental)

### What to do¶

unzip exercises in a folder, you should get something like this:

```
matrices-numpy
matrices-numpy.ipynb
matrices-numpy-sol.ipynb
numpy-images.ipynb
numpy-images-sol.ipynb
jupman.py
```

**WARNING**: to correctly visualize the notebook, it MUST be in an unzipped folder !

open Jupyter Notebook from that folder. Two things should open, first a console and then browser. The browser should show a file list: navigate the list and open the notebook

`matrices-numpy/matrices-numpy.ipynb`

Go on reading that notebook, and follow instuctions inside.

Shortcut keys:

to execute Python code inside a Jupyter cell, press

`Control + Enter`

to execute Python code inside a Jupyter cell AND select next cell, press

`Shift + Enter`

to execute Python code inside a Jupyter cell AND a create a new cell aftwerwards, press

`Alt + Enter`

If the notebooks look stuck, try to select

`Kernel -> Restart`

## np.array¶

First of all, we import the library, and for convenience we rename it to ‘np’:

```
[2]:
```

```
import numpy as np
```

With lists of lists we have often built the matrices one row at a time, adding lists as needed. In Numpy instead we usually create in one shot the whole matrix, filling it with zeroes.

In particular, this command creates an `ndarray`

filled with zeroes:

```
[3]:
```

```
mat = np.zeros( (2,3) ) # 2 rows, 3 columns
```

```
[4]:
```

```
mat
```

```
[4]:
```

```
array([[0., 0., 0.],
[0., 0., 0.]])
```

Note like inside `array( )`

the content seems represented like a list of lists, BUT in reality in physical memory the data is structured in a linear sequence which allows Python to access numbers in a faster way.

To access data or overwrite square bracket notation is used, with the important difference that in Numpy you can write *bot* the indeces *inside* the same brackets, separated by a comma:

**ATTENTION**: notation `mat[i,j]`

is only for Numpy, with list of lists **does not** work!

Let’s put number `0`

in cell at row `0`

and column `1`

```
[5]:
```

```
mat[0,1] = 9
```

```
[6]:
```

```
mat
```

```
[6]:
```

```
array([[0., 9., 0.],
[0., 0., 0.]])
```

Let’s access cell at row `0`

and column `1`

```
[7]:
```

```
mat[0,1]
```

```
[7]:
```

```
9.0
```

We put number `7`

into cell at row `1`

and column `2`

```
[8]:
```

```
mat[1,2] = 7
```

```
[9]:
```

```
mat
```

```
[9]:
```

```
array([[0., 9., 0.],
[0., 0., 7.]])
```

To get the dimension, we write like the following:

**ATTENTION**: after `shape`

there are **no** round parenthesis !

`shape`

is an attribute, not a function to call

```
[10]:
```

```
mat.shape
```

```
[10]:
```

```
(2, 3)
```

If we want to memorize the dimension in separate variables, we can use thi more pythonic mode (note the comma between `num_rows`

and `num_cols`

:

```
[11]:
```

```
num_rows, num_cols = mat.shape
```

```
[12]:
```

```
num_rows
```

```
[12]:
```

```
2
```

```
[13]:
```

```
num_cols
```

```
[13]:
```

```
3
```

**✪ Exercise**: try to write like the following, what happens?

```
mat[0,0] = "c"
```

```
[14]:
```

```
# write here
```

We can also create an `ndarray`

starting from a list of lists:

```
[15]:
```

```
mat = np.array( [ [5.0,8.0,1.0],
[4.0,3.0,2.0]])
```

```
[16]:
```

```
mat
```

```
[16]:
```

```
array([[5., 8., 1.],
[4., 3., 2.]])
```

```
[17]:
```

```
type(mat)
```

```
[17]:
```

```
numpy.ndarray
```

```
[18]:
```

```
mat[1,1]
```

```
[18]:
```

```
3.0
```

**✪ Exercise**: Try to write like this and check what happens:

```
mat[1,1.0]
```

```
[19]:
```

```
# write here
```

## NaNs and infinities¶

Float numbers can be numbers and…. not numbers, and infinities. Sometimes during calculations extremal conditions may arise, like when dividing a small number by a huge number. In such cases, you might end up having a float which is a dreaded *Not a Number*, *NaN* for short, or you might get an infinity. This can lead to very awful unexpected behaviours, so you must be well aware of it.

Following behaviours are dictated by IEEE Standard for Binary Floating-Point for Arithmetic (IEEE 754) which Numpy uses and is implemented in all CPUs, so they actually regard all programming languages.

### NaNs¶

A NaN is *Not a Number*. Which is already a silly name, since a NaN is actually a very special member of floats, with this astonishing property:

**WARNING: NaN IS NOT EQUAL TO ITSELF** !!!!

Yes you read it right, NaN is really *not* equal to itself.

Even if your mind wants to refuse it, we are going to confirm it.

To get a NaN, you can use Python module `math`

which holds this alien item:

```
[20]:
```

```
import math
math.nan # notice it prints as 'nan' with lowercase n
```

```
[20]:
```

```
nan
```

As we said, a NaN is actually considered a float:

```
[21]:
```

```
type(math.nan)
```

```
[21]:
```

```
float
```

Still, it behaves very differently from its fellow floats, or any other object in the known universe:

```
[22]:
```

```
math.nan == math.nan # what the F... alse
```

```
[22]:
```

```
False
```

### Detecting NaN¶

Given the above, if you want to check if a variable `x`

is a NaN, you *cannot* write this:

```
[23]:
```

```
x = math.nan
if x == math.nan: # WRONG
print("I'm NaN ")
else:
print("x is something else ??")
```

```
x is something else ??
```

To correctly handle this situation, you need to use `math.isnan`

function:

```
[24]:
```

```
x = math.nan
if math.isnan(x): # CORRECT
print("x is NaN ")
else:
print("x is something else ??")
```

```
x is NaN
```

Notice `math.isnan`

also work with *negative* NaN:

```
[25]:
```

```
y = -math.nan
if math.isnan(y): # CORRECT
print("y is NaN ")
else:
print("y is something else ??")
```

```
y is NaN
```

### Sequences with NaNs¶

Still, not everything is completely crazy. If you compare a sequence holding NaNs to another one, you will get reasonable results:

```
[26]:
```

```
[math.nan, math.nan] == [math.nan, math.nan]
```

```
[26]:
```

```
True
```

### Exercise NaN: two vars¶

Given two number variables `x`

and `y`

, write some code that prints `"same"`

when they are the same, *even* when they are NaN. Otherwise, prints `”not the same”

```
[27]:
```

```
# expected output: same
x = math.nan
y = math.nan
# expected output: not the same
#x = 3
#y = math.nan
# expected output: not the same
#x = math.nan
#y = 5
# expected output: not the same
#x = 2
#y = 7
# expected output: same
#x = 4
#y = 4
# write here
```

```
same
```

### Operations on NaNs¶

Any operation on a NaN will generate another NaN:

```
[28]:
```

```
5 * math.nan
```

```
[28]:
```

```
nan
```

```
[29]:
```

```
math.nan + math.nan
```

```
[29]:
```

```
nan
```

```
[30]:
```

```
math.nan / math.nan
```

```
[30]:
```

```
nan
```

The only thing you cannot do is dividing by zero with an unboxed NaN:

```
math.nan / 0
```

```
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
<ipython-input-94-1da38377fac4> in <module>
----> 1 math.nan / 0
ZeroDivisionError: float division by zero
```

NaN corresponds to boolean value `True`

:

```
[31]:
```

```
if math.nan:
print("That's True")
```

```
That's True
```

### NaN and Numpy¶

When using Numpy you are quite likely to encounter NaNs, so much so they get redefined inside Numpy, but they are exactly the same as in `math`

module:

```
[32]:
```

```
np.nan
```

```
[32]:
```

```
nan
```

```
[33]:
```

```
math.isnan(np.nan)
```

```
[33]:
```

```
True
```

```
[34]:
```

```
np.isnan(math.nan)
```

```
[34]:
```

```
True
```

In Numpy when you have unknown numbers you might be tempted to put a `None`

. You can actually do it, but look closely at the result:

```
[35]:
```

```
import numpy as np
np.array([4.9,None,3.2,5.1])
```

```
[35]:
```

```
array([4.9, None, 3.2, 5.1], dtype=object)
```

The resulting array type is *not* an array of float64 which allows fast calculations, instead it is an array containing generic *objects*, as Numpy is assuming the array holds heterogenous data. So what you gain in generality you lose it in performance, which should actually be the whole point of using Numpy.

Despite being weird, NaNs are actually regular float citizen so they can be stored in the array:

```
[36]:
```

```
np.array([4.9,np.nan,3.2,5.1]) # Notice how the `dtype=object` has disappeared
```

```
[36]:
```

```
array([4.9, nan, 3.2, 5.1])
```

### Where are the NaNs ?¶

Let’s try to see where we can spot NaNs and other weird things such infinities in the wild

First, let check what happens when we call function `log`

of standard module `math`

. As we know, log function behaves like this:

\(x < 0\): not defined

\(x = 0\): tends to minus infinity

\(x > 0\): defined

So we might wonder what happens when we pass to it a value where it is not defined:

```
>>> math.log(-1)
```

```
ValueError Traceback (most recent call last)
<ipython-input-38-d6e02ba32da6> in <module>
----> 1 math.log(-1)
ValueError: math domain error
```

Let’s try the equivalent with Numpy:

```
[37]:
```

```
np.log(-1)
```

```
/home/da/.local/lib/python3.5/site-packages/ipykernel_launcher.py:1: RuntimeWarning: invalid value encountered in log
"""Entry point for launching an IPython kernel.
```

```
[37]:
```

```
nan
```

Notice we actually got as a result `np.nan`

, even if Jupyter is printing a warning.

The default behaviour of Numpy regarding dangerous calculations is to perform them anyway and storing the result in as a NaN or other limit objects. This also works for arrays calculations:

```
[38]:
```

```
np.log(np.array([3,7,-1,9]))
```

```
/home/da/.local/lib/python3.5/site-packages/ipykernel_launcher.py:1: RuntimeWarning: invalid value encountered in log
"""Entry point for launching an IPython kernel.
```

```
[38]:
```

```
array([1.09861229, 1.94591015, nan, 2.19722458])
```

### Infinities¶

As we said previously, NumPy uses the IEEE Standard for Binary Floating-Point for Arithmetic (IEEE 754). Since somebody at IEEE decided to capture the misteries of infinity into floating numbers, we have yet another citizen to take into account when performing calculations (for more info see Numpy documentation on constants):

### Positive infinity `np.inf`

¶

```
[39]:
```

```
np.array( [ 5 ] ) / 0
```

```
/home/da/.local/lib/python3.5/site-packages/ipykernel_launcher.py:1: RuntimeWarning: divide by zero encountered in true_divide
"""Entry point for launching an IPython kernel.
```

```
[39]:
```

```
array([inf])
```

```
[40]:
```

```
np.array( [ 6,9,5,7 ] ) / np.array( [ 2,0,0,4 ] )
```

```
/home/da/.local/lib/python3.5/site-packages/ipykernel_launcher.py:1: RuntimeWarning: divide by zero encountered in true_divide
"""Entry point for launching an IPython kernel.
```

```
[40]:
```

```
array([3. , inf, inf, 1.75])
```

Be aware that:

Not a Number is

**not**equivalent to infinitypositive infinity is

**not**equivalent to negative infinityinfinity is equivalent to positive infinity

This time, infinity is equal to infinity:

```
[41]:
```

```
np.inf == np.inf
```

```
[41]:
```

```
True
```

so we can safely detect infinity with `==`

:

```
[42]:
```

```
x = np.inf
if x == np.inf:
print("x is infinite")
else:
print("x is finite")
```

```
x is infinite
```

Alternatively, we can use the function `np.isinf`

:

```
[43]:
```

```
np.isinf(np.inf)
```

```
[43]:
```

```
True
```

### Negative infinity¶

We can also have negative infinity, which is different from positive infinity:

```
[44]:
```

```
-np.inf == np.inf
```

```
[44]:
```

```
False
```

Note that `isinf`

detects *both* positive and negative:

```
[45]:
```

```
np.isinf(-np.inf)
```

```
[45]:
```

```
True
```

To actually check for negative infinity you have to use `isneginf`

:

```
[46]:
```

```
np.isneginf(-np.inf)
```

```
[46]:
```

```
True
```

```
[47]:
```

```
np.isneginf(np.inf)
```

```
[47]:
```

```
False
```

Where do they appear? As an example, let’s try `np.log`

function:

```
[48]:
```

```
np.log(0)
```

```
/home/da/.local/lib/python3.5/site-packages/ipykernel_launcher.py:1: RuntimeWarning: divide by zero encountered in log
"""Entry point for launching an IPython kernel.
```

```
[48]:
```

```
-inf
```

### Combining infinities and NaNs¶

When performing operations involving infinities and NaNs, IEEE arithmetics tries to mimic classical analysis, sometimes including NaN as a result:

```
[49]:
```

```
np.inf + np.inf
```

```
[49]:
```

```
inf
```

```
[50]:
```

```
- np.inf - np.inf
```

```
[50]:
```

```
-inf
```

```
[51]:
```

```
np.inf * -np.inf
```

```
[51]:
```

```
-inf
```

What in classical analysis would be undefined, here becomes NaN:

```
[52]:
```

```
np.inf - np.inf
```

```
[52]:
```

```
nan
```

```
[53]:
```

```
np.inf / np.inf
```

```
[53]:
```

```
nan
```

As usual, combining with NaN results in NaN:

```
[54]:
```

```
np.inf + np.nan
```

```
[54]:
```

```
nan
```

```
[55]:
```

```
np.inf / np.nan
```

```
[55]:
```

```
nan
```

### Negative zero¶

We can even have a *negative* zero - who would have thought?

```
[56]:
```

```
np.NZERO
```

```
[56]:
```

```
-0.0
```

Negative zero of course pairs well with the more known and much appreciated *positive* zero:

```
[57]:
```

```
np.PZERO
```

```
[57]:
```

```
0.0
```

**NOTE**: Writing `np.NZERO`

or `-0.0`

is *exactly* the same thing. Same goes for positive zero.

At this point, you might start wondering with some concern if they are actually *equal*. Let’s try:

```
[58]:
```

```
0.0 == -0.0
```

```
[58]:
```

```
True
```

Great! Finally one thing that makes sense.

Given the above, you might think in a formula you can substitute one for the other one and get same results, in harmony with the rules of the universe.

Let’s make an attempt of substitution, as an example we first try dividing a number by positive zero (even if math teachers tell us such divisions are forbidden) - what will we ever get??

\(\frac{5.0}{0.0}=???\)

In Numpy terms, we might write like this to box everything in arrays:

```
[59]:
```

```
np.array( [ 5.0 ] ) / np.array( [ 0.0 ] )
```

```
/home/da/.local/lib/python3.5/site-packages/ipykernel_launcher.py:1: RuntimeWarning: divide by zero encountered in true_divide
"""Entry point for launching an IPython kernel.
```

```
[59]:
```

```
array([inf])
```

Hmm, we got an array holding an `np.inf`

.

If `0.0`

and `-0.0`

are actually the same, dividing a number by `-0.0`

we should get the very same result, shouldn’t we?

Let’s try:

```
[60]:
```

```
np.array( [ 5.0 ] ) / np.array( [ -0.0 ] )
```

```
/home/da/.local/lib/python3.5/site-packages/ipykernel_launcher.py:1: RuntimeWarning: divide by zero encountered in true_divide
"""Entry point for launching an IPython kernel.
```

```
[60]:
```

```
array([-inf])
```

Oh gosh. This time we got an array holding a *negative* infinity `-np.inf`

If all of this seems odd to you, do not bash at Numpy. This is the way pretty much any CPUs does floating point calculations so you will find it in almost ALL computer languages.

What programming languages can do is add further controls to protect you from paradoxical situations, for example when you directly write `1.0/0.0`

Python raises `ZeroDivisionError`

(blocking thus execution), and when you operate on arrays Numpy emits a warning (but doesn’t block execution).

### Exercise: detect proper numbers¶

Write some code that PRINTS `equal numbers`

if two numbers `x`

and `y`

passed are equal and actual numbers, and PRINTS `not equal numbers`

otherwise.

**NOTE**: `not equal numbers`

must be printed if any of the numbers is infinite or NaN.

To solve it, feel free to call functions indicated in Numpy documentation about costants

Show solution```
[61]:
```

```
# expected: equal numbers
x = 5
y = 5
# expected: not equal numbers
#x = np.inf
#y = 3
# expected: not equal numbers
#x = 3
#y = np.inf
# expected: not equal numbers
#x = np.inf
#y = np.nan
# expected: not equal numbers
#x = np.nan
#y = np.inf
# expected: not equal numbers
#x = np.nan
#y = 7
# expected: not equal numbers
#x = 9
#y = np.nan
# expected: not equal numbers
#x = np.nan
#y = np.nan
# write here
```

```
equal numbers
equal numbers
```

### Exercise: guess expressions¶

For each of the following expressions, try to guess the result

**WARNING: the following may cause severe convulsions and nausea.**

During clinical trials, both mathematically inclined and math-averse patients have experienced illness, for different reasons which are currently being investigated.

```
a. 0.0 * -0.0
b. (-0.0)**3
c. np.log(-7) == math.log(-7)
d. np.log(-7) == np.log(-7)
e. np.isnan( 1 / np.log(1) )
f. np.sqrt(-1) * np.sqrt(-1) # sqrt = square root
g. 3 ** np.inf
h 3 ** -np.inf
i. 1/np.sqrt(-3)
j. 1/np.sqrt(-0.0)
m. np.sqrt(np.inf) - np.sqrt(-np.inf)
n. np.sqrt(np.inf) + ( 1 / np.sqrt(-0.0) )
o. np.isneginf(np.log(np.e) / np.sqrt(-0.0))
p. np.isinf(np.log(np.e) / np.sqrt(-0.0))
q. [np.nan, np.inf] == [np.nan, np.inf]
r. [np.nan, -np.inf] == [np.nan, np.inf]
s. [np.nan, np.inf] == [-np.nan, np.inf]
```

## Verify comprehension¶

### odd¶

✪✪✪ Takes a Numpy matrix `mat`

of dimension `nrows`

by `ncols`

containing integer numbers and RETURN a NEW Numpy matrix of dimension `nrows`

by `ncols`

which is like the original, ma in the cells which contained even numbers now there will be odd numbers obtained by summing `1`

to the existing even number.

Example:

```
odd(np.array( [
[2,5,6,3],
[8,4,3,5],
[6,1,7,9]
]))
```

Must give as output

```
array([[ 3., 5., 7., 3.],
[ 9., 5., 3., 5.],
[ 7., 1., 7., 9.]])
```

Hints:

Since you need to return a matrix, start with creating an empty one

go through the whole input matrix with indeces

`i`

and`j`

```
[62]:
```

```
import numpy as np
def odd(mat):
raise Exception('TODO IMPLEMENT ME !')
# TEST START - DO NOT TOUCH!
# if you wrote the whole code correct, and execute the cell, Python shouldn't raise `AssertionError`
m1 = np.array([
[2],
])
m2 = np.array([
[3]
])
assert np.allclose(odd(m1),
m2)
assert m1[0][0] == 2 # checks we are not modifying original matrix
m3 = np.array( [
[2,5,6,3],
[8,4,3,5],
[6,1,7,9]
])
m4 = np.array( [
[3,5,7,3],
[9,5,3,5],
[7,1,7,9]
])
assert np.allclose(odd(m3),
m4)
# TEST END
```

### doublealt¶

✪✪✪ Takes a Numpy matrix `mat`

of dimensions `nrows`

x `ncols`

containing integer numbers and RETURN a NEW Numpy matrix of dimension `nrows`

x `ncols`

having at rows of even **index** the numbers of original matrix multiplied by two, and at rows of odd **index** the same numbers as the original matrix.

Example:

```
m = np.array( [ # index
[ 2, 5, 6, 3], # 0 even
[ 8, 4, 3, 5], # 1 odd
[ 7, 1, 6, 9], # 2 even
[ 5, 2, 4, 1], # 3 odd
[ 6, 3, 4, 3] # 4 even
])
```

A call to

```
doublealt(m)
```

will return the Numpy matrix:

```
array([[ 4, 10, 12, 6],
[ 8, 4, 3, 5],
[14, 2, 12, 18],
[ 5, 2, 4, 1],
[12, 6, 8, 6]])
```

```
[63]:
```

```
import numpy as np
def doublealt(mat):
raise Exception('TODO IMPLEMENT ME !')
# TEST START - DO NOT TOUCH!
# if you wrote the whole code correct, and execute the cell, Python shouldn't raise `AssertionError`
m1 = np.array([
[2],
])
m2 = np.array([
[4]
])
assert np.allclose(doublealt(m1),
m2)
assert m1[0][0] == 2 # checks we are not modifying original matrix
m3 = np.array( [
[ 2, 5, 6],
[ 8, 4, 3]
])
m4 = np.array( [
[ 4,10,12],
[ 8, 4, 3]
])
assert np.allclose(doublealt(m3),
m4)
m5 = np.array( [
[ 2, 5, 6, 3],
[ 8, 4, 3, 5],
[ 7, 1, 6, 9],
[ 5, 2, 4, 1],
[ 6, 3, 4, 3]
])
m6 = np.array( [
[ 4,10,12, 6],
[ 8, 4, 3, 5],
[14, 2,12,18],
[ 5, 2, 4, 1],
[12, 6, 8, 6]
])
assert np.allclose(doublealt(m5),
m6)
# TEST END
```

### frame¶

✪✪✪ RETURN a NEW Numpy matrix of `n`

rows and `n`

columns, in which all the values are zero except those on borders, which must be equal to a given `k`

For example, `frame(4, 7.0)`

must give:

```
array([[7.0, 7.0, 7.0, 7.0],
[7.0, 0.0, 0.0, 7.0],
[7.0, 0.0, 0.0, 7.0],
[7.0, 7.0, 7.0, 7.0]])
```

Ingredients:

create a matrix filled with zeros. ATTENTION: which dimensions does it have? Do you need

`n`

or`k`

? Read WELL the text.start by filling the cells of first row with

`k`

values. To iterate along the first row columns, use a`for j in range(n)`

fill other rows and columns, using appropriate

`for`

```
[64]:
```

```
def frame(n, k):
raise Exception('TODO IMPLEMENT ME !')
# TEST START - DO NOT TOUCH!
# if you wrote the whole code correct, and execute the cell, Python shouldn't raise `AssertionError`
expected_mat = np.array( [[7.0, 7.0, 7.0, 7.0],
[7.0, 0.0, 0.0, 7.0],
[7.0, 0.0, 0.0, 7.0],
[7.0, 7.0, 7., 7.0]])
# all_close return Ture if all the values in the first matrix are close enough
# (that is, within a given tolerance) to corresponding values in the second
assert np.allclose(frame(4, 7.0), expected_mat)
expected_mat = np.array( [ [7.0]
])
assert np.allclose(frame(1, 7.0), expected_mat)
expected_mat = np.array( [ [7.0, 7.0],
[7.0, 7.0]
])
assert np.allclose(frame(2, 7.0), expected_mat)
# TEST END
```

### chessboard¶

✪✪✪ RETURN a NEW Numpy matrix of `n`

rows and `n`

columns, in which all cells alternate zeros and ones.

For example, `chessboard(4)`

must give:

```
array([[1.0, 0.0, 1.0, 0.0],
[0.0, 1.0, 0.0, 1.0],
[1.0, 0.0, 1.0, 0.0],
[0.0, 1.0, 0.0, 1.0]])
```

Ingredients:

to alternate, you can use

`range`

in the form in which takes 3 parameters, for example`range(0,n,2)`

starts from 0, arrives to`n`

excluded by jumping one item at a time, generating 0,2,4,6,8, ….instead range(1,n,2) would generate 1,3,5,7, …

```
[65]:
```

```
def chessboard(n):
raise Exception('TODO IMPLEMENT ME !')
# TEST START - DO NOT TOUCH!
# if you wrote the whole code correct, and execute the cell, Python shouldn't raise `AssertionError`
expected_mat = np.array([[1.0, 0.0, 1.0, 0.0],
[0.0, 1.0, 0.0, 1.0],
[1.0, 0.0, 1.0, 0.0],
[0.0, 1.0, 0.0, 1.0]])
# all_close return True if all the values in the first matrix are close enough
# (that is, within a certain tolerance) to the corresponding ones in the second matrix
assert np.allclose(chessboard(4), expected_mat)
expected_mat = np.array( [ [1.0]
])
assert np.allclose(chessboard(1), expected_mat)
expected_mat = np.array( [ [1.0, 0.0],
[0.0, 1.0]
])
assert np.allclose(chessboard(2), expected_mat)
# TEST END
```

### altsum¶

✪✪✪ MODIFY the input Numpy matrix (n x n), by summing to all the odd rows the even rows. For example

```
m = [[1.0, 3.0, 2.0, 5.0],
[2.0, 8.0, 5.0, 9.0],
[6.0, 9.0, 7.0, 2.0],
[4.0, 7.0, 2.0, 4.0]]
altsum(m)
```

after the call to altsum `m`

should be:

```
m = [[1.0, 3.0, 2.0, 5.0],
[3.0, 11.0,7.0, 14.0],
[6.0, 9.0, 7.0, 2.0],
[10.0,16.0,9.0, 6.0]]
```

Ingredients:

to alternate, you can use

`range`

in the form in which takes 3 parameters, for example`range(0,n,2)`

starts from 0, arrives to`n`

excluded by jumping one item at a time, generating 0,2,4,6,8, ….instead

`range(1,n,2)`

would generate 1,3,5,7, ..

```
[66]:
```

```
def altsum(mat):
raise Exception('TODO IMPLEMENT ME !')
# TEST START - DO NOT TOUCH!
# if you wrote the whole code correct, and execute the cell, Python shouldn't raise `AssertionError`
m1 = np.array( [
[1.0, 3.0, 2.0, 5.0],
[2.0, 8.0, 5.0, 9.0],
[6.0, 9.0, 7.0, 2.0],
[4.0, 7.0, 2.0, 4.0]
])
r1 = np.array( [
[1.0, 3.0, 2.0, 5.0],
[3.0, 11.0,7.0, 14.0],
[6.0, 9.0, 7.0, 2.0],
[10.0,16.0,9.0, 6.0]
])
altsum(m1)
assert np.allclose(m1, r1)
m2 = np.array( [ [5.0] ])
r2 = np.array( [ [5.0] ])
altsum(m1)
assert np.allclose(m2, r2)
m3 = np.array( [ [6.0, 1.0],
[3.0, 2.0]
])
r3 = np.array( [ [6.0, 1.0],
[9.0, 3.0]
])
altsum(m3)
assert np.allclose(m3, r3)
# TEST END
```

### avg_rows¶

✪✪✪ Takes a Numpy matrix n x m and RETURN a NEW Numpy matrix consisting in a single column in which the values are the average of the values in corresponding rows of input matrix

Example:

Input: 5x4 matrix

```
3 2 1 4
6 2 3 5
4 3 6 2
4 6 5 4
7 2 9 3
```

Output: 5x1 matrix

```
(3+2+1+4)/4
(6+2+3+5)/4
(4+3+6+2)/4
(4+6+5+4)/4
(7+2+9+3)/4
```

Ingredients:

create a matrix n x 1 to return, filling it with zeros

visit all cells of original matrix with two nested fors

during visit, accumulate in the matrix to return the sum of elements takes from each row of original matrix

once completed the sum of a row, you can divide it by the dimension of columns of original matrix

return the matrix

```
[67]:
```

```
def avg_rows(mat):
raise Exception('TODO IMPLEMENT ME !')
return ret
# TEST START - DO NOT TOUCH!
# if you wrote the whole code correct, and execute the cell, Python shouldn't raise `AssertionError`
m1 = np.array([ [5.0] ])
r1 = np.array([ [5.0] ])
assert np.allclose(avg_rows(m1), r1)
m2 = np.array([ [5.0, 3.0] ])
r2 = np.array([ [4.0] ])
assert np.allclose(avg_rows(m2), r2)
m3 = np.array([ [3,2,1,4],
[6,2,3,5],
[4,3,6,2],
[4,6,5,4],
[7,2,9,3] ])
r3 = np.array([ [(3+2+1+4)/4],
[(6+2+3+5)/4],
[(4+3+6+2)/4],
[(4+6+5+4)/4],
[(7+2+9+3)/4] ])
assert np.allclose(avg_rows(m3), r3)
# TEST END
```

### avg_half¶

✪✪✪ Takes as input a Numpy matrix withan even number of columns, and RETURN as output a Numpy matrix 1x2, in which the first element will be the average of the left half of the matrix, and the second element will be the average of the right half.

Ingredients:

to obtain the number of columns divided by two as integer number, use

`//`

operator

```
[68]:
```

```
def avg_half(mat):
raise Exception('TODO IMPLEMENT ME !')
# TEST START - DO NOT TOUCH!
# if you wrote the whole code correct, and execute the cell, Python shouldn't raise `AssertionError`
m1 = np.array([[3,2,1,4],
[6,2,3,5],
[4,3,6,2],
[4,6,5,4],
[7,2,9,3]])
r1 = np.array([(3+2+6+2+4+3+4+6+7+2)/10, (1+4+3+5+6+2+5+4+9+3)/10 ])
assert np.allclose( avg_half(m1), r1)
# TEST END
```

### matxarr¶

✪✪✪ Takes a Numpy matrix `n`

x `m`

and an `ndarray`

of `m`

elements, and RETURN a NEW Numpy matrix in which the values of each column of input matrix are multiplied by the corresponding value in the `n`

elements array.

```
[69]:
```

```
def matxarr(mat, arr):
raise Exception('TODO IMPLEMENT ME !')
# TEST START - DO NOT TOUCH!
# if you wrote the whole code correct, and execute the cell, Python shouldn't raise `AssertionError`
m1 = np.array([ [3,2,1],
[6,2,3],
[4,3,6],
[4,6,5]])
a1 = [5, 2, 6]
r1 = [ [3*5, 2*2, 1*6],
[6*5, 2*2, 3*6],
[4*5, 3*2, 6*6],
[4*5, 6*2, 5*6]]
assert np.allclose(matxarr(m1,a1), r1)
# TEST END
```

### quadrants¶

✪✪✪ Given a matrix `2n * 2n`

, divide the matrix in 4 equal square parts (see example) and RETURN a NEW matrix `2 * 2`

containing the average of each quadrant.

We assume the matrix is always of even dimensions

HINT: to divide by two and obtain an integer number, use `//`

operator

Example:

```
1, 2 , 5 , 7
4, 1 , 8 , 0
2, 0 , 5 , 1
0, 2 , 1 , 1
```

can be divided in

```
1, 2 | 5 , 7
4, 1 | 8 , 0
-----------------
2, 0 | 5 , 1
0, 2 | 1 , 1
```

and returns

```
(1+2+4+1)/ 4 | (5+7+8+0)/4 2.0 , 5.0
----------------------------- => 1.0 , 2.0
(2+0+0+2)/4 | (5+1+1+1)/4
```

```
[70]:
```

```
import numpy as np
def quadrants(mat):
raise Exception('TODO IMPLEMENT ME !')
# TEST START - DO NOT TOUCH!
# if you wrote the whole code correct, and execute the cell, Python shouldn't raise `AssertionError`
assert np.allclose(
quadrants(np.array([
[3.0, 5.0],
[4.0, 9.0],
])),
np.array([
[3.0, 5.0],
[4.0, 9.0],
]))
assert np.allclose(
quadrants(np.array([
[1.0, 2.0 , 5.0 , 7.0],
[4.0, 1.0 , 8.0 , 0.0],
[2.0, 0.0 , 5.0 , 1.0],
[0.0, 2.0 , 1.0 , 1.0]
])),
np.array([
[2.0, 5.0],
[1.0, 2.0]
]))
# TEST END
```

### matrot¶

✪✪✪ RETURN a NEW Numpy matrix which has the numbers of input matrix rotated by a column.

With rotation we mean that:

if a number of input matrix is found in column

`j`

, in the output matrix it will be in the column`j+1`

in the same row.if a number is found in the last column, in the output matrix it will be in the zertoth column

Example:

If we have as input:

```
np.array( [
[0,1,0],
[1,1,0],
[0,0,0],
[0,1,1]
])
```

We expect as output:

```
np.array( [
[0,0,1],
[0,1,1],
[0,0,0],
[1,0,1]
])
```

```
[71]:
```

```
import numpy as np
def matrot(mat):
raise Exception('TODO IMPLEMENT ME !')
# TEST START - DO NOT TOUCH!
# if you wrote the whole code correct, and execute the cell, Python shouldn't raise `AssertionError`
m1 = np.array( [ [1] ])
r1 = np.array( [ [1] ])
assert np.allclose(matrot(m1), r1)
m2 = np.array( [ [0,1] ])
r2 = np.array( [ [1,0] ])
assert np.allclose(matrot(m2), r2)
m3 = np.array( [ [0,1,0] ])
r3 = np.array( [ [0,0,1] ])
assert np.allclose(matrot(m3), r3)
m4 = np.array( [
[0,1,0],
[1,1,0]
])
r4 = np.array( [
[0,0,1],
[0,1,1]
])
assert np.allclose(matrot(m4), r4)
m5 = np.array([
[0,1,0],
[1,1,0],
[0,0,0],
[0,1,1]
])
r5 = np.array([
[0,0,1],
[0,1,1],
[0,0,0],
[1,0,1]
])
assert np.allclose(matrot(m5), r5)
# TEST END
```

## References¶

### Other Numpy exercises¶

Try to do exercises from lists of lists using Numpy instead

try to make the exercises more performant by using Numpy features and functions (i.e.

`2*arr`

multiplies all numbers in arr without the need of a slow Python`for`

)machinelearningplus Numpy exercises - (difficulty L1, L2, if you wish try L3)

```
[ ]:
```

```
```