📓 About This Tutorial

This webpage is a static view of an interactive Jupyter Notebook tutorial. To get the full interactive experience where you can run Python code, modify examples and complete exercises, click the "Open in Colab" button. This will access the Google Colab notebook from a GitHub repository github.com/dchappell2/Computational-Physics and open it in Google Colab. You will need a Google account if you want to open it in Colab.

Note: You can download a pdf of the lecture slides for this chapter here: Chapter 7 slides

Chapter 7 - NumPy Arrays (1D)

Goals

7.0 Similarities and differences between NumPy Arrays and Python Lists

Similarities:

Differences:

Bottom Line: Use NumPy arrays instead of Python Lists when you need to perform mathematical calculations, need multidimensional support or want access to NumPy merthods and functionality.

NumPy arrays can be used to store any sequence of numbers. Here are some examples of the many ways they might be used:

7.1 - Use the NumPy .array() method to create a NumPy array from a list

import numpy as np                                  # import the NumPy library and assign nickname "np"

my_array = np.array([10, 20, 30, 40, 50])           # create a NumPy array from a Python List
print("my_array has data type = ",type(my_array))
print("first element of my_array (index = 0) is ",my_array[0])

7.2 Use .copy() to copy NumPy arrays

#####  Create an Alias
A = np.array([1, 1, 1, 1])
B = A                        # B is an alias of A
B[-1] = 2                    # modifying B affects A
print()
print("B is alias of A:")
print("A = ",A)              # A will show changes applied to B
print("B = ",B)

#####  Deep Copy Example
A = np.array([1, 1, 1, 1])
B = A.copy()                 # B and A are independent arrays (a deep copy)
B[-1] = 2                    # modifying B does not affect A
print()
print("Deep copy:")
print("A = ",A)              # A will not show changes applied to B
print("B = ",B)

7.3 - NumPy array elements can be accessed and modified using square brackets and slicing

# display the first n elements of my_array
my_array = np.array([10, 20, 30, 40, 50])
n = 3
print("first",n,"elements of my_array are",my_array[:n])
# reassign the first element of my_array
my_array[0] = 100
print("my_array is now",my_array)

✅ Skill Check 1

Given the following list, do the following:

my_list = [3,6,9,12]

# Enter your code here

7.4 Basic operations on NumPy arrays will be applied to all elements of the array

A = np.array([3, 6, 9, 12])   # define a NumPy array from a list
A2 = A**2                     # square each element
print("square of A = ",A2)    # display the result

🔆 Example: Squaring elements in an array

##### The following two code snippets perform the same operation:

# define a NumPy array from a list
A = np.array([1, 2, 3, 4])

#####  vectorized method:  square each term in A with single command. FASTER
A2 = A**2
print("method 1:  A^2 = ",A2)

#####  Python loop:  each element squared "by hand". SLOWER
for i in range(len(A)):
    A[i] = A[i]**2
print("method 2:  A^2 = ",A)

🔆 Example: Multiplying NumPy arrays

---

If x and y are two equal-length NumPy arrays, we can calculate the element-wise product by simply multiplying the arrays:

x = np.array([1,2,4,8])  # NumPy array (length 4)
y = np.array([3,0,1,1])  # NumPy array (length 4)

z = x*y                  # element-wise product of x and y
print("x*y = ",z)        # result is also length 4
x = np.array([1,2,4,8])  # NumPy array (length 4)
a = 10.                  # variable containing a floating-point number

z = a*x                  # multiply each element of x by a
print("x*y = ",z)        # result is also length 4

Note: Multiplying two arrays that have different lengths will produce an error:

```

x = np.array([1, 2, 0, 2]) # array containing 4 elements

y = np.array([1, 3]) # array containing 2 elements

z = xy # attempting to multiply xy gives an error!!!

```

✅ Skill Check 2

Without running the following code, try to predict what the print statement will display. Enter your answer in a Text Cell below. If you want, you can copy and paste the code in a Code Cell to see if your calculations are correct:

```

x = np.array([1, 2, 1, 2])

y = np.array([1, 0, 2, 0])

a1 = 2

a2 = 4

z = a1*x**2 + a2xy

print("The array z = ",z)

```

Enter your response here

7.5 Common NumPy Math Functions

The following list of mathematical NumPy functions were given in Chapter 3. We repeat the list here because they are vectorized and operation on NumPy arrays in additon to scalar numbers.

x = [.1, .2, .3, .4, .5]   # define a NumPy array for x

# constants
np.pi          # pi
np.e           # e
np.inf         # infinity
np.nan         # not a number

# logarithmic and exponential functions
np.sqrt(x)     # square root(x)
np.exp(x)      # e^x
np.log(x)      # ln(x)
np.log10(x)    # log base 10(x)
np.log2(x)     # log base 2(x)

# trigonometric functions
np.sin(x)      # sin(x)
np.cos(x)      # cos(x)
np.tan(x)      # tan(x)

# degree-radian conversions
np.deg2rad(x)  # converts degrees to radians
np.rad2deg(x)  # converts radians to degrees

# inverse trigonometric functions
np.asin(x)     # np.arcsin(x) also works for the arcsin() of a value
np.acos(x)     # np.arccos(x) also works
np.atan(x)     # np.arctan(x) also works
np.atan2(y,x)  # passing the x and y vector components lets the atan2()
               # function return an angle in the correct quadrant

# hyperbolic functions
np.sinh(x)     # hyperbolic sin
np.cosh(x)     # hyperbolic cos
np.tanh(x)     # hyperbolic tan

🔆 Example: Applying the sin() function to a NumPy array

theta_deg = np.array([0, 10, 20, 30, 40, 50])   # angles in degrees
theta_rad = np.deg2rad(theta_deg)               # convert the anges to radians
y_val = np.sin(theta_rad)                       # take the sine of each angle

for th,y in zip(theta_deg,y_val):               # print results
    print(f"sin({th:2.0f}) = {y:6.4f}")

✅ Skill Check 3

Vector components.

vx = np.array([ 5.1, 4.0,  2.9, 0.1, 3.2])   # x measurements (meters)
vy = np.array([-4.5, 1.6, -3.7, 4.2, 4.3])   # y measurements (meters)

# Enter your code here

7.4 NumPy Statistics

NumPy arrays have both methods and attributes defined:

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

##### Attributes do not take parentheses
A.size        # number of elements
A.dtype       # data type of the array

##### Methods require parentheses
A.sum()       # sum of the elements
A.prod()      # product of the elements
A.mean()      # mean of the elements
A.std()       # standard deviation
A.var()       # variance
A.min()       # minimum value
A.max()       # maximum value
A.cumsum()    # cumulative sum (result will have same length as A)
A.cumprod()   # cumulative product (result will have same length as A)

🔆 Example: Array stats

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

N = A.size
print(f"A contains {N} elements")

A_mean = A.mean()
A_std  = A.std()
print(f"The mean and standard deviation of A are {A_mean:.2f} ± {A_std:.2f}")

A_min = A.min()
A_max = A.max()
print(f"The min and max values are {A_min:.2f} and {A_max:.2f}")

✅ Skill Check 4

The following Code Cell contains a NumPy array with multiple temperature readings of a liquid nitrogen cryostat (in K). Calculate and display the following (with labels):

T = np.array([ 77.1, 77.2, 77.0, 77.8, 77.1, 77.0, 77.9, 77.1])   # x measurements (meters)

# Enter your code here

7.4 Creating NumPy arrays

7.4.1 Use np.linspace() to generate N evenly spaced numbers

use .linspace() when you know the total number elements

my_array = np.linspace(100,1000,10)
print("my_array is",my_array)

7.4.2 Use np.arange() to generate evenly spaced numbers with a known spacing

my_array = np.arange(0,5.5,0.5)
print("my_array is",my_array)

Notice that we had to set the stop value to 5.5 for the 5.0 value to be included in the list. In general the the stop value must be set to the desired max value + interval value, i.e. $5.0+0.5=5.5$ in this example.

🔆 Example: Falling rock

A common application of np.linspace() and np.arange() is to create an array of evenly spaced time or space values.

In this example, we calculate the distance a rock has fallen every 0.5 seconds up to a total of 3 seconds. The distance fallen is given by $d=\frac{1}{2}g t^2$, where $g=9.8$ m/s$^2$.

Solution

times = np.arange(0,3.5,0.5)   # create equally spaced times every 0.5 seconds
g = 9.8                        # acceleration of gravity
dist = 0.5 * g * times**2      # calculate distance fallen for every time in times array

for t,d in zip(times,dist):    # loop over the time and distance arrays to print them
    print(f"t = {t:3.1f} s    d = {d:5.2f} m")

We see that at time $t=0$, the rock hasn't moved, which gives us some confidence that our code is working at least for this simple case. At time $t=3$ s, the rock has fallen 44.1 meters.

✅ Skill Check 5

The international space station orbits the Earth approximately 400 km above the Earth's surface. Calculate the acceleration of gravity (g) every 100 km from the sea level up to the space station.

# Enter your code here

7.4.3 Use np.zeros() or np.ones() to generate a uniform array filled with zeros or ones

A = np.zeros(5)     # create an NumPy array of length 5 filled with 0's
print("A = ",A)

B = np.ones(5)      # create an NumPy array of length 5 filled with 1's
print("B = ",B)

C = 9*np.ones(5)    # create an NumPy array of length 5 filled with 9's
print("C = ",C)

🔆 Example

Let's create a sequence of numbers where each element equals the square of the previous element. This is equivalent to repeatedly pressing the ^2 button on your calculator.

N = 10                 # number of elements in our sequence (i.e. array)
y0 = 2                 # initial number

y = np.zeros(N)        # preallocate array to hold the sequence
y[0] = y0              # set first number in the sequence

for n in range(1,N):   # calculate next element in series based on previous element
    y[n] = y[n-1]**2

for n in range(N):     # print result
    print(f"{y[n]:.10g}")

✅ Skill Check 6

Back in Chapter 5, you wrote some code to produce the Fibonacci sequence using lists. In this Skill Check, you will rewrite your code using a NumPy array.

# Enter your code here

7.4.4 Create NumPy arrays filled with random numbers

``rng = np.random.default_rng()``

import numpy as np

rng = np.random.default_rng()        # create random number Generator
N = 5                                # size of random array

# Create array of random integers between 0 <= z < 10
z = rng.integers(0, 10, size=N)
print("random integers:       ",z)
print()

# Create array of random integers between 0 <= z <= 10 (includes 10 endpoint)
z = rng.integers(0, 10, size=N, endpoint=True)
print("random integers (v2):  ",z)
print()

# Create array of random numbers drawn from Gaussian distribution with mean 0 and sdev = 1
z = rng.normal(size=N)
print("Gaussian distribution: ",z)
print()

# Create array of random numbers drawn from a uniform distribution with 1 <= z < 2
z = rng.uniform(1,2, size=N)
print("Uniform distribution:  ",z)
print()

# Choose a random sampling from a list or array of items
my_list = ['red', 'green', 'blue']
z = rng.choice(my_list, size=N)
print("Uniform Sampling:      ",z)
print()

# Choose 5 random numbers from 0-9, with no duplicates
z = rng.choice(10,size=5,replace=False)
print("No duplicates:         ",z)

✅ Skill Check 7

Use the random choice number generator to draw 5 unique random cards from a 52-card card deck and display them.

Run the following code to see how it works. It defines numbers representing the suit and face value of a card, uses the dictionaries to translate them to actual face value and suit, and prints the result.

# Dictionary defining suit
# example:  0 is clubs, 1 is diamonds, etc
suit_lookup = {
    0:"♣️",
    1:"♦️",
    2:"❤️",
    3:"♠️"
}

# dictionary defining face value
# example:  0 has face value 2, 1 has face value 3,... 11 is a king, 12 is an Ace
card_lookup = {
    0:" 2",
    1:" 3",
    2:" 4",
    3:" 5",
    4:" 6",
    5:" 7",
    6:" 8",
    7:" 9",
    8:"10",
    9:" J",
    10:" Q",
    11:" K",
    12:" A"
}

# suit is a number between 0-3
suit = 2
# face value is a number between 0-12
face_value = 12

my_card = card_lookup[face_value] + suit_lookup[suit]

print("my card is ",my_card)

7.5 Logical Indexing

logical indexing (also called boolean indexing) is one of NumPy’s most powerful features. It lets you select elements from an array using conditions rather than explicit indices.

7.5.1 Using a mask to select array elements

# Create an array of squares from 1 to 100
A = np.linspace(1,10,10)**2
print("A = ",A)

# select only the array elements > 50
A_sample = A[A>50]

print()
print("A values > 50", A_sample)

🔆 Example: Use a mask with multiple arrays

# Create an array of squares from 1 to 10^2
A = np.linspace(1,10,10)**2
print("A = ",A)

# Create an array of cubes from 1 to 10^3
B = np.linspace(1,10,10)**3
print("B = ",B)

mask = A > 50    # list of indices where array A is > 50

print("A values where A > 50", A[mask])
print("B values where A > 50", B[mask])

7.5.2 Using a mask with multiple conditions

To combine multiple conditions in the mask, we must use "bit-wise" operators:

# select the array elements with values > 10 and < 50
A_sample = A[(A>10) & (A<50)]

print("A values > 10 and < 50:  ", A_sample)

7.5.3 Use logical indexing to assign values in an array

A = np.array([1,3,-1,3,9,-2,3,5])
print("A before:  ",A)

A[A<0] = 0
print("A after:   ",A)

7.5.4 Use where() to assign elements based on a condition

The statement np.where(condition, x, y) returns an array choosing elements from x where condition is True and frpm y where condition is False.

A = np.array([1, -2, -3, 4, 5])

result = np.where(A > 0, 1, -1)

print("A      = ",A)
print("result = ",result)

✅ Skill Check 8

The following Code Cell contains an array of x values. Missing data points have been set to 999.

x = np.array([1,2,3,999,5,6,7,999])

# Enter your code here

✅ Skill Check 9

Flipping a coin

# Enter your code here


✅ Skill Check 10

Modify your coin flip code as follows:

# Enter your code here

Key Points

This tutorial is an adaptation of "Python for Physicists"

© Software Carpentry