<jupyter_start><jupyter_text>Introduction To NumPy and SciPyNumPy (Numerical Python) and SciPy (Scientific Python) are Python libraries designed for numerical computing and scientific applications, respectively. These libraries are very popular and provide a lot of domain-specific functions for different applications. You may want to look at the following links:- NumPy User Guide: https://docs.scipy.org/doc/numpy/user/index.html- NumPy Tutorial: https://docs.scipy.org/doc/numpy-dev/user/quickstart.html- NumPy for Matlab Users: https://docs.scipy.org/doc/numpy/user/numpy-for-matlab-users.html- SciPy Tutorial: https://docs.scipy.org/doc/scipy/reference/tutorial/index.html  Intro to NumPyWe will look at basic NumPy functionality:- Working with data types and arrays- Differences with NumPy arrays and NumPy matrices- I/O with NumPy arrays  Working with arrays in NumPyNumPy is similar to Matlab/Octave because it focuses on arrays of data. We will look at how to create NumPy arrays from Python lists as well as some functions for natively creating arrays.The main thing to know about NumPy arrays is that they are really just dumb collections of numbers which appear together in memory. Their syntax is very similar at a high level to working with lists in Python, where one uses the command a[i-1] to access the ith entry of a list/array `a`.There are a few caveats to NumPy arrays that make them different from other Python data types such as lists.1. Every entry in a NumPy array will have the same type. That means that arrays don't mix up Integers with Floats, Strings, or other types.  - In contrast, Python lists can mix different types. This makes them very flexible for general purpose programming but not very good for numerical programming.2. NumPy arrays facilitate numerical computation in a manner similar to Matlab/octave.  - NumPy arrays allow all Python arithmetic operators (`+`, `-`, `*`, `/`, `//`, `**`) to act on arrays. By default, these all operate elementwise.  - Matlab arrays have similar operations, but require the special elementwise operators .`*`, `./`, `.^`.The easiest way to create NumPy arrays is by passing a Python list and creating an array object.<jupyter_code>import numpy as np  # Imports the NumPy module

# Create two NumPy arrays of 5 numbers
a = np.array([1, 2, -3.2, 5, 7])  # An array of floats. Why do these have to be floats?
b = np.array([2, -4, 6, 7, 10])   # An array of integers

print("a =")
print(a)
print("b =")
print(b)<jupyter_output><empty_output><jupyter_text>Arithmetic in NumPy arraysNumPy arrays allow you to use simple arithmetic syntax directly on the NumPy arrays themselves. These operations are basically the same as Matlab's elementwise operators<jupyter_code># Performing arithmetic operations on NumPy arrays
print("sum:\n",a+b)               # Equivalent Matlab syntax: a+b
print("difference:\n",a-b)        # Equivalent Matlab syntax: a-b
print("product:\n",a*b)           # Equivalent Matlab syntax: a.*b
print("quotient:\n",a/b)          # Equivalent Matlab syntax: a./b
print("Exponentiation:\n",a**b)   # Equivalent Matlab syntax: a.^b<jupyter_output><empty_output><jupyter_text>BroadcastingNumPy arrays also let you use a concept called "broadcasting" which is essentially performing the same arithmetic operation over all of the arrays elements. There are two rules:1. Either one of the NumPy arrays must be a "scalar" or a "singleton"2. Both NumPy arrays must have the same "shape"<jupyter_code>print("Two times a: \n2*a =\n",2*a,"\n")
print("A linear combination: \nb[0]*a - a[-1]*b =\n",b[0]*a - a[-1]*b)

# If I have an array of a different size, then broadcasting does not work
# print(a+np.array([-3,2])) # Raises a ValueError<jupyter_output><empty_output><jupyter_text>Accessing Entries in NumPy arraysAccessing NumPy arrays is done similarly to Python lists. The process is very similar to Matlab array access, but also has some more convenient features which Matlab's syntax does not have.<jupyter_code># Accessing entries in arrays
print("First element of a:",a[0]) # Access first element of a
print("Last element of a:",a[-1]) # Access last element of a

# Write to b
print("This is how b looks now:\n",b)
b[1] = -1
print("What has changed about b?\n",b)

# What about if I try to set one of the entries equal to a floating point number?
b[-1] = 55/33
print("b now looks like:\n",b)  # What explains this behavior?
# b[0] = "foo" # What about this?<jupyter_output><empty_output><jupyter_text>You can also access multiple entries at once by using "slices" of arrays. The syntax works similar to Matlab slices but has some more flexible ``` a[start:stop]  Get every entry between index start and index start-1a[start:stop:stride]  Same as above but picks skips over indices```If `stride`, `stop`, or `start` are left out, then the Python chooses the sanest defaults (`start=0`, `stop=len(array)`, `stride=1`).<jupyter_code># Get every the first four elements of a
print("First four entries of a:\n",a[0:4])
print(a[:4])    # Equivalent and short. 
print(a[:-1])   # Works because a has 5 entries

# Get every other element in b starting at b[1]
print("\nEvery other entry starting from b[1]:\n",b[1::2])   # Note that stop = len(b)

# We can also use slices to alter entries in an array
c = np.array([-5,6,20, -300, 5000,2000])
print("c =\n",c)

c[2:6:2] = [-20,3] # Sets c[2] = -20, c[4] = 3
print("Now c=\n",c)<jupyter_output><empty_output><jupyter_text>NumPy also lets you use index arrays to select elements of the array in any order (say a permutation or some sort of "map" that you are working with). Index arrays must be arrays of integers!<jupyter_code># Index arrays to access data
d = a[np.array([2,3,4,1,0])]
print("d = \n",d)

# Use index arrays to manipulate data
d[np.array([-1,2])] = np.array([-5.0,333.3])
print("\nd = \n",d)<jupyter_output><empty_output><jupyter_text>NumPy also lets you use Boolean arrays or "masks" to access elements of an array which satisfy some sort of property<jupyter_code>print("All positive entries of a:\n",a[a>0])
print("The corresponding mask:\n",a>0)
print("Compare with a:\n",a)<jupyter_output><empty_output><jupyter_text>Matlab-like FunctionsNumPy also has several Matlab-like functions which can be helpful when you want to initialize simple arrays.<jupyter_code># NumPy specific functions
arr1 = np.zeros(10) # Initialize an array of ten zeros (like Matlab's zeros() function)

arr2 = np.ones(10)  # Initialize an array of ten ones

ZeroThruNine = np.arange(10) # Basically the same thing as Matlab command 0:9
FiveThruNine = np.arange(5,10) # This is the same as 5:10
EvensLessThanTen = np.arange(0,10,2)  # Same as Matlab command 0:2:8
SmallSteps = np.arange(0,10,0.1)      # Same as Matlab command 0:0.1:0.9<jupyter_output><empty_output><jupyter_text>Multi-dimensional ArraysNumPy arrays can also have shapes, similar to Matlab arrays. However, NumPy arrays have simpler behavior by default than Matlab multi-dimensional arrays (which behave like literal matrices)<jupyter_code>multiarray = np.array([[-1,2,3],[4.5, 3.0, 5.5]]) # A multi-array with two rows and three columns
print("multiarray =\n",multiarray)

# To get the "shape" of an array, use the shape property
print("\nmultiarray.shape =",multiarray.shape)

# To access data entries there are two different ways
print("multiarray[0,1] =",multiarray[0,1]) # Use [i-1,j-1] to access (i,j)th entry
print("multiarray[0][1] =",multiarray[0][1]) # Accesses jth entry in ith row

# Either way lets you change individual elements
print("Changing (1,2) entry to -5:")
multiarray[0,1] = -5    
print("multiarray =\n",multiarray,"\n")

print("Now try to change (2,3) entry to 100.3\n")
multiarray[1][2] = 100.3 # This *doesn't* change the data
print("multiarray =\n",multiarray,"\n")

# Or you can change a single row of the multidimensional array
multiarray[0] = np.array([-3.14, 0, 0])
print(multiarray)<jupyter_output><empty_output><jupyter_text>Multiarrays can be accessed the same way as regular NumPy arrays. So you can use all of the concepts of<jupyter_code># Access multiarray elements using 
submultiarray = multiarray[:,0:2]
print(submultiarray)
submultiarray[:,1] = np.array([-2,4])
print(submultiarray)
print(multiarray)<jupyter_output><empty_output><jupyter_text>Multidimensional array shapesMultidimensional arrays have "shapes" which allow for you to work with data in a manner similar to Matlab. Many functions are very similar to Matlab's builtin functions. - The `shape` gives the layout of the memory as a tuple.- The `reshape` method allows one to view an array as a different shape without changing the original array.- The `resize` method allows one to change the shape of an array.- The `ravel` method is similar to reshape but changes a multidimensional array to a flat array<jupyter_code># Get the shape information of an array
example_array = np.arange(0,10)
print("example_array shape:",example_array.shape) # shape method works like Matlab's size() function
print("example_array as a single-dimension array:\n",example_array)

# The reshape command keeps allows one to "reshape" the array without changing information.
ex2 = example_array.reshape(2,5) # We have to store the reshaped object in another variable
print("ex2.shape =",ex2.shape)
print("ex2 =\n",ex2,"\n")
print("example_array =\n",example_array) # Original array still has the same shape

# You can still modify the underlying content by ex2. 
print('Modifying ex2[0,0] = -1')
ex2[0,0] = -1
print("\nex2 =\n",ex2)
print("\nexample_array =\n",example_array)

# Use resize to change the shape of example_array
example_array.resize(5,2)
print('\nexample_array.shape =',example_array.shape)
print("\nexample_array =\n",example_array)
print("ex2 = \n",ex2)   # Note that ex2 is not affected

# Now flatten example_array back to a flat array
ex3 = example_array.ravel()
print("example_array.shape =",example_array.shape) # example_array's shape is unchanged!
print("ex3 =\n",ex3)<jupyter_output><empty_output><jupyter_text>Linear Algebra in NumPyLinear algebra in NumPy is slightly more involved than in Matlab to preserve Python's general purpose syntax. The NumPy linear algebra functions are stored inside of the `numpy.linalg` submodule which contains functions like the following- `dot()` Matrix-matrix or matrix-vector product- `transpose()` compute matrix transpose- `inv()` compute the inverse of a matrix- `solve()` solve a linear system- `eig()` compute the eigenvalue decomposition of a matrix<jupyter_code># Create two 2-by-2 matrices
A = np.array([[0, -1],[1,0]])
B = np.array([[3,4],[-5,6]])

print('A*B = \n',A*B,"\n") # Element-wise multiplication
print('A.dot(B) =\n',A.dot(B),"\n") # matrix-matrix product
print('B.dot(A) =\n',B.dot(A),"\n") # matrix-matrix multiplication is not commutative

# Now create a "vector" c as a single-dimensional array. This is different from Matlab!
c = np.array([5,10])
print("A.dot(c) =\n",A.dot(c)) 
print("c.dot(A) =\n",c.dot(A))

# Solve B*x = c
x = np.linalg.solve(B,c)
print("x = \n",x)
print("np.linalg.norm(B.dot(x) - c) = ",np.linalg.norm(B.dot(x)-c)) # Compute residual norm to verify solution<jupyter_output><empty_output><jupyter_text>NumPy MatricesNumPy has a special class of multidimensional arrays given by the `np.matrix` class. These allow a more Matlab-like syntax for linear algebra.<jupyter_code># Initialize a 3-by-3 matrix
Amat = np.matrix(A)
Bmat = np.matrix(B)

print('Amat*Bmat =\n',Amat*Bmat)
print("Bmat*Amat =\n",Bmat*Amat)

print("Transpose of Amat:\n",Amat.transpose())<jupyter_output><empty_output><jupyter_text>Data Types and Arrays in NumPyNumPy has several different "sizes" of data types similar to lower level languages like C, C++, or Fortran. NumPy supports the following data types- Integers  - Signed integers (range)    - int8 (-128 to 127)    - int16 (-32768 to 32768)    - int32 (-2147483648 to 2147483647)    - int64 (-9223372036854775808 to 9223372036854775807)    - int\_ (default "long" integer, usually int64 but could be int32 for some computer architectures)  - Unsigned integers (range)    - uint8 (0 to 256)    - uint16 (0 to 65535)    - uint32 (0 to 4294967295)    - uint64 (0 to 18446744073709551615)- Floating Point   - float16 (Half precision)   - float32 (Single precision)  - float64 (Double precision)  - float\_ (defaults to double precision, float64)- Complex Numbers  - complex64 (two 32 bit single precision floats)  - complex128 (two 64 bit double precision floats)  - complex\_ (defaults to complex128)<jupyter_code>x = np.array([[250, 300, 512],[-300, 500, 216],[541, -128, -256]],dtype='i4') # Initialize x as a 32-bit unsigned integer
print(x)
y = np.array([[3.14,-28e4,2.54e-3,2.5e10],
              [-0,1.5e-2,2.222,5/4]],dtype='complex128') # Initialize y as a 128-bit complex number
print(y)<jupyter_output><empty_output><jupyter_text>Reading Files into NumPy ArraysNumPy provides a couple functions for reading text files. The simplest one is `np.loadtxt` which lets you read in text files for multi-dimensional arrays. This is a fast reader so it requires that every row have the same number of entries.We will use the StringIO module to allow strings to have be used in place of file IO.<jupyter_code>from io import StringIO # 
c = StringIO("0 1\n2 3\n3 4\n5 6")
x = np.loadtxt(c,dtype='int32') # Load as 32-bit integers
# Make sure that "sample.txt" is in the same folder as this notebook!
y = np.loadtxt("sample.txt") # Have NumPy load the text using default parameters
print(y)

row1 = y[0,:]
print("Mean of row 1:",np.mean(row1))
print("Mean of y:",np.mean(y))<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11494928707922974, 'fraction_numerical': 0.03263266206085551, 'mean_word_length': 4.910947002606429, 'pattern_counts': {'":': 0, '<': 61, '<?xml version=': 0, '>': 63, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '15', 'dir': 'jupyter-structured-clean-dedup', 'id': '40848', 'license': 'MIT', 'n_tokens_mistral': 4540, 'n_tokens_neox': 4256, 'n_words': 1644, 'path': 'Intro_to_NumPy_and_SciPy.ipynb', 'repo_name': 'compdatasci/python-notes', 'size': '13606'}