# Numpy 2D arrays - some examples

This notebook demonstrates how to work with 2D numpy arrays, including array slicing, random numbers, and making plots with them. Note that this works with higher-dimensional arrays as well!

``````

In [ ]:

%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
import random

``````

# Array creation and basic properties

The line below creates an 8x10 array of zeros called `my_array`. Note that you can do this with any numpy array method (`ones`, `zeros_like`, `ones_like`, etc.). See this page for a full list of routines for array creation. You can also specify the array data type (float, int, etc.) by using the `dtype` argument, i.e., `dtype='float'` or `dtype='int'`. By default, Numpy creates arrays of floating-point numbers.

``````

In [ ]:

a = np.zeros([8,10])
print(a)

anew = np.zeros([8,10],dtype='int')
print("new array:\n", anew)

``````

In this class, you have already created a 1D numpy array of predetermined values by giving `np.array` a list. You can make a multi-dimensional numpy array by giving `np.array` a set of nested lists (i.e., a list of lists). The following will create a 3x3 array with predetermined values:

``````

In [ ]:

b = np.array([[1,2,3],[4,5,6],[7,8,9]])
print(b)

``````

The array `.shape` property tells you how large the array is in each dimension, `.ndim` tells you the number of dimensions, and `.size` tells you the total number of elements in the array. You can access each of the dimensions `dim` by `.shape[dim]`.

``````

In [ ]:

print("the shape of this array is:", a.shape)
print("there are:", a.ndim, "dimensions")
print("there are", a.size, "total elements")

for i in range(a.ndim):
print("the size of dimension", i, "is", a.shape[i])

``````

You can manipulate individual cells of a 2D array by:

`a[index_1,index_2]`

Note that when you print it, the first index corresponds to rows (counting down from the top) and the second index corresponds to columns (counting from the left). Indices in both directions count by zeros.

``````

In [ ]:

a[2,6]=11

#print entire array
print(a)

#print a single element of the array
print(a[2,6])

``````

# Slicing arrays

You can also use the same type of slicing that you use with lists - in other words, python allows you to select some subset of the elements in a list or an array to manipulate or copy. With slicing, there are three values that can be used along each dimension: `start`,`end`, and `step`, separated by colons. Here are some examples in 1D:

``````myarray[start,end]   # items start through end-1
myarray[start:]      # items start through the end of the array
myarray[:end]        # items from the beginning of the array through end-1
myarray[:]           # a copy of the whole array
myarray[start,end,step]  # every "step" item from start to end-1
myarray[::step]      # every "step" item over the whole array, starting with the first element.``````

Note that negative indices count from the end of the array, so `myarray[-1]` is the last element in the array, `myarray[-2]` is the second-to-last element, etc. You can also reverse the order of the array by starting at the end and counting to the beginning by negative numbers -- in other words, `myarray[-1::-1]` starts at the end of the array and goes to the first element by counting down by one each time.

``````

In [ ]:

# create a 1D array with values 0...10
c = np.arange(0,10)

# note: the '\n' at the beginning of many of the print statements
# adds a carriage return (blank line)

print("the whole array:", c)
print("\nsome elements from the middle of the array:",c[3:7] )
print("\nthe second element through the second-to-last element:", c[1:-1])
print("\nthe first half of the array:", c[:5])
print("\nthe second half of the array:", c[5:])
print("\nevery other element from 2-8 (inclusive):",c[2:9:2])
print("\nevery third element in the array:",c[::3])
print("\nreverse the array:",c[-1::-1])

``````

The same sort of technique can be used with a multi-dimensional array, with `start`, `stop`, and (optionally) `step` specified along each dimension, with the dimensions separated by a comma. The syntax would be:

``my2Darray[start1:stop1:step1, start2:stop2:step2]``

With the same rules as above. You can also combine slicing with fixed indices to get some or all elements from a single row or column of your array.

For example, array `b` created above is a 3x3 array with the values 1-9 stored in it. We can do several different things:

``````b[0,:]     # get the first row
b[:,2]     # get the third column
b[1,::2]   # get every other element of the first row, starting at element 0
b[:2,:2]   # get a square array containing the first two elements along each dimension
b[-2:,-2:] # get a square array containing the last two elements along each dimension
b[::2,::2] # get a square array of every other element along each dimension
b[-1::-1,-1::-1]  # original sized array, but reversed along both dimensions``````
``````

In [ ]:

print("the array b:\n",b,"\n")
#  To get a square array containing the first two elements along each dimension:
print("the first row:", b[0,:])

print("\nthe third column:",b[:,2])

print("\nevery other element of the second row, starting with element 0:",b[1,::2])

print("\nsquare array of first two elements along each dimension:\n",b[:2,:2])

print("\nsquare array of last two elements along each dimension:\n",b[-2:,-2:])

print("\nsquare array of every other element along each dimension:\n",b[::2,::2])

print("\nreversed array:\n",b[-1::-1,-1::-1])

``````

# Copying arrays

So far, we've only shown you how to create arrays and manipulate subsets of arrays. But what about copying arrays? What happens when you create an array c, and set d=c?

``````

In [ ]:

c = np.full((4,4),10.0)  # makes an array of shape (4,4) where all elements are value 10.0

d = c

print("c:\n",c, "\nd:\n", d)

``````

The two arrays are the same, which is what you would expect. But, what happens if we make changes to array d?

``````

In [ ]:

d[:,0] = -1.0  # make column 0 equal to -1
d[:,2] = -6.0  # make column 2 equal to -6

print("c:\n",c, "\nd:\n", d)

``````

Arrays `c` and `d` are identical, even though you only changed `d`!

So what's going on here? When you equate arrays in Numpy (i.e., `d = c`), you create a reference, rather than copying the array -- in other words, the array `d` is not a distinct array, but rather points to the array `c` in memory. Any modification to either `c` or `d` will be seen by both. To actually make a copy, you have to use the `np.copy()` method:

``````

In [ ]:

e = np.full((4,4),10.0)  # makes an array of shape (4,4) where all elements are value 10.0

f = np.copy(e)

f[:,0] = -1.0  # make column 0 equal to -1
f[:,2] = -6.0  # make column 2 equal to -6

print("e:\n",e, "\nf:\n", f)

``````

You can also make a copy of a subset of an arrays:

``````

In [ ]:

g = np.full((4,4),10.0)  # makes an array of shape (4,4) where all elements are value 10.0

h = np.copy(g[0:2,0:2])

print("g:\n",g, "\nh:\n", h)

``````

Note that you can also create an array that references a subset of another array rather than copies it, and manipulate that in any way you want. The changes will then appear in both the new array and your original array. For example:

``````

In [ ]:

i = np.full((4,4),10.0)  # makes an array of shape (4,4) where all elements are value 10.0

j = i[0:2,0:2]

print("\nunmodified arrays:\n")

print("i:\n",i, "\nj:\n", j)

print("\narrays after modification:\n")

j[1,1]=-999.0

print("i:\n",i, "\nj:\n", j)

``````

# Numpy and random numbers

Numpy has a `random` module that can be used to generate random numbers in a similar way to the standard Python `random` module, but with the added advantage that it can do so for arrays of values. Two commonly-used methods are:

• `random`, which generates an array with user-specified dimensions (1D, 2D, or more dimensions!) and fills it with random floating-point values in the interval [0,1).
• `randint`, which generates an array with user-specified dimensions and fills it with random integers in a user-specified interval.
``````

In [ ]:

random_float_array = np.random.random((5,5))

print(random_float_array)

random_int_array = np.random.randint(0,10,(5,5))

print("\n",random_int_array)

``````

# Plotting 2D numpy arrays

It's easy to plot 2D Numpy arrays in matplotlib using the pyplot `matshow` method:

``````

In [ ]:

new_rand_array = np.random.random((100,100))

plt.matshow(new_rand_array)

# uncomment the following line to save the figure to your hard drive!
#plt.savefig("myimage.png")

``````

And you can turn off the array axes with the following incantation:

``````

In [ ]:

myplot = plt.matshow(new_rand_array)
myplot.axes.get_xaxis().set_visible(False)
myplot.axes.get_yaxis().set_visible(False)

``````

(See this page for a more complex example.) Finally, you can use the pyplot `imshow` method to control many aspects of a plotted array, including things like the color map, opacity, and the minimum and maximum range.

``````

In [ ]:

# interpolation='none' keeps imshow() from trying to interpolate between values and
#   making it look fuzzy.
# cmap='mapname' changes the color map.
# vmin, vmax sets the range of the color bar (from 0.0 - 0.5 in this example)

myplot = plt.imshow(new_rand_array, interpolation='none',cmap='hot',vmin=0.0,vmax=0.5)

# uncomment the following lines to remove the axis labels
#myplot.axes.get_xaxis().set_visible(False)
#myplot.axes.get_yaxis().set_visible(False)

# uncomment the following line to save the figure to your hard drive!
#plt.savefig("myimage.png")

``````

# Making animations of 2D arrays with matplotlib

This example shows one possible way to make an animation using Matplotlib in a Jupyter notebook. In addition to the usual matplotlib and pyplot modules, we also need to import a couple of extra things:

``````

In [ ]:

# This lets us make and clear plots without creating new ones
from IPython.display import display, clear_output

# We can use this to have images show up with some user-specified spacing in time
import time

``````

### A function to make plots of 2D arrays

Next I will create a function that takes an array as an input and generates a plot using the `plt.plot()` function (as opposed to `plt.imshow()`). We have chosen to have a value of zero (0) represented by an empty cell, a one (1) to be represented by a square, and a (2) to be represented by a triangle in this plot.

``````

In [ ]:

# function plotgrid() takes in a 2D array and uses pyplot to make a plot.
# this function returns no values!

def plotgrid(myarray):

# first create two vectors based on the x and y sizes of the grid
x_range = np.linspace(0, myarray.shape, myarray.shape)
y_range = np.linspace(0, myarray.shape, myarray.shape)

# use the numpy meshgrid function to create two matrices
# of the same size as myarray with x and y indexes
x_indexes, y_indexes = np.meshgrid(x_range, y_range)

# make a list of all the x and y indexes that are either squares or triangles.
# the notation below is relatively new to us; it means that when myarray==(value),
# only record those values.
sq_x = x_indexes[myarray == 1];
sq_y = y_indexes[myarray == 1];
tr_x = x_indexes[myarray == 2];
tr_y = y_indexes[myarray == 2];

# plot the squares and triangles.  make the size of the polygons
# larger than the default so they're easy to see!
plt.plot(sq_x,sq_y, 'bs',markersize=20)
plt.plot(tr_x,tr_y, '^g',markersize=20)

#Set the x and y limits to include half a space overlap so we don't cut off the shapes
plt.ylim([-0.5,myarray.shape + 0.5])
plt.xlim([-0.5,myarray.shape + 0.5])

``````

### Now, let's test this plotting function!

It's always a good idea to test the function by creating a random neighborhood array with zeros, ones, and twos.

``````

In [ ]:

# Generate a random grid of points, distributed uniformly between 0, 1, 2
neighborhood_array = np.random.random_integers(0,2,size=[20,20])

# Creates a figure and controls the figure size so it doesn't look too crowded.
# Try commenting it out to see what happens!
plt.figure(figsize=(10,10))

# now make a plot.
plotgrid(neighborhood_array)

``````

### Making a moving animation

So far, so good! Finally, we are going to animate the loop using a dynamic display trick. What we will do is create a figure and keep clearing the figure and overwriting it with a new figure. In this example we just keep making a new random neighborhood 10 times while pausing a half second between each neighbornood. If you were to do this for your own purposes, you'd probably do something different.

``````

In [ ]:

# Create a figure
fig = plt.figure(figsize=(10,10))

# Run animation for 10 iterations
for i in range(10):

# Generate the random neighborhood, as in previous cells
myarray = np.random.random_integers(0,2,size=[20,20])

# Put display code here
plotgrid(myarray)

# Animation part (dosn't change)
time.sleep(0.5)         # Sleep for half a second to slow down the animation
clear_output(wait=True) # Clear output for dynamic display
display(fig)            # Reset display
fig.clear()             # Prevent overlapping and layered plots

plt.close()                 # Close dynamic display

``````