<jupyter_start><jupyter_text>numpy<jupyter_code># A package widely used in the data science commpunity which lets us work effeciently with arrays 
# and metrices in python

# 

# Numpy is the core library for scientific computing in Python. 
# It provides a high-performance multidimensional array object, and tools for working with these arrays. 
# importing numpy as np. This lets us use the shortcut np to refer to numpy
import numpy as np 
# creating an array
mylist=[1,2,3] # create a list
x=np.array(mylist) # pass list to numpy to convert it to array
x # print x
# print mylist and see the difference between mylist and x
mylist
# we can also do this by passing the list to numpy directly
y=np.array([1,2,3])
y
# making multidimentional arrays by passing in a list of lists
# we pass 2 lists of 3 elements each and we get a 2x3 (2 by 3) array
m=np.array([[7,8,9],[10,11,12]])
m
# print list of lists and see how its different from a 2-d numpy array
mylist_multidim=[[7,8,9],[10,11,12]]
mylist_multidim
# you will notice that its the same, only difference is how python treats it internally
# we can check the dimentions by using the shape attribute
m.shape
# we can extract the row
rowcount_m=m.shape[0]
rowcount_m
# we can extract the column
colcount_m=m.shape[1]
colcount_m
# generating arrays
# for the arange function we pass in a start a stop and a step size
# and it returns you evenly spaced values within a given interval
n=np.arange(0,30,2)
n
# we can use reshape to convert this array of numbers into a 3x5 array
n=n.reshape(3,5)
n
n.shape
# linspace function is similar to arange, except we tell it how many numbers we want returned and 
# it will split up the interval accordingly
# so we pass it a start a stop and how many numbers we want returned
o=np.linspace(0,4,9)
o
# resize can be used to change the dimentions in place
o.resize(3,3)
o
# difference between reshape and resize
ar = np.random.rand(2,3)
ar
ar.reshape(1,6)
ar # After reshape the array didn't change, but only outputs a temporary array reshape.
ar.resize(1,6)
ar # After resize the array changes it's shape
# numpy also has several built in functions and shortcuts for creating array
np.ones((3,2)) # returns a 3x2 array of ones
np.zeros((2,3)) # returns a 2x3 array of zeros
np.eye(3) # returns a 3x3 array with ones on the diagonal and zeros everywhere else 
y=np.array([1,2,3])
np.diag(y) 
# to create an array with repeated values we can pass in a repeated list, or we can use numpy's repeat function
np.array([1,2,3]*3)  
np.repeat([1,2,3],3) 
# output: array([1, 1, 1, 2, 2, 2, 3, 3, 3])    
# notice difference between the two outputs
# we can also combine arrays to create new ones
# lets create a 2x3 array of ones 
p=np.ones([2,3],int)
p
# and stack it vertically with itself multiplied by 2
np.vstack([p,2*p])   
# and stack it horizontally
np.hstack([p,2*p])<jupyter_output><empty_output><jupyter_text>basic math<jupyter_code># operations that can be done with numpy arrays
# element wise +,-,*,/
x=np.array([1,2,3])
y=np.array([4,5,6])
x+y
x*y
y-x
# x to the power 2 (this is also element wize)
x**2 
# linear algebra dot product can be done using the dot function
x.dot(y) 
# lets create a new array using the previous array y and its squared values
y=np.array([4,5,6])
z=np.array([y,y**2,y*2,y-1])
z
# transposing an array using the t method which swaps the rows and columns
z.T
z.dtype # we can see the type of data the array has
z=z.astype('f') # casting and array to a different type, from int64 to float32 in this case
z.dtype 
z
# commonly used math functions
a=np.array([-4,-2,1,3,5])
a.sum()
z.sum()
z.sum(axis=0) 
z.sum(axis=1)
a.max()
a.min()
a.mean() # mean of all the values
a.std() # standard deviation of all the values
a.argmax() # index of the maximum value
a.argmin() # index of the minimum value<jupyter_output><empty_output><jupyter_text>accessing array elements - indexing and slicing - colon notation<jupyter_code># create array of squares of 0-12
s=np.arange(13)**2
s
# we can use [] notation to get the value at a particular index
s[4]   # first value is at position 0
# colon notation to get a range
# notation is starting index, ending index and step size
# specifying the staring or ending index is not necessary
# we can also use negatives to count back from the end of the array   
# a[start:end] - items start through end-1
s[0:3]    
# a[:end] - items from the beginning through end-1
s[:3] # this is same as s[0:3]   
# a[start:] - items start through the rest of the array
s[3:]
# a[:] - a copy of the whole array
s[:]
# a[start:end:step] - start through not past end, by step
s[::2]
s[:5:2]
# a[-1] - last item in the array
s[-1]
s[-1:] # slice of last 1 element of the array
s[-4] # 4th last item in the array
s[-4:] # slice of last 4 elements of the array
s[-5::-2] #starting 5th from the end to the beginning of the array and counting backwards by 2
s[-1:]
# a[:-2] - everything except the last two items
s[:-2]<jupyter_output><empty_output><jupyter_text>lets see how this extends to a 2 dimensional array<jupyter_code># lets create a 2 dimensional array from 0-35 and resize it as 6x6
r=np.arange(36)
r.resize((6,6))
r
r[2,2] # 14 is the value at second row second column
r[3,3:6] # colon notation used to get a slice of 3rd row column 3-6
r[:2,:-1] # first two rows and all the columns except the last
r[-1,::2] # select every second element from the last row
r[r>30] # use bracket operator to do conditional indexing and assignment
r[r>30]=30 # capping the max value of elements in the array to 30
r<jupyter_output><empty_output><jupyter_text>copying data<jupyter_code># first lets create a new array r2 which is the slice of the array r
r2=r[:3,:3]
r2
r2[:]=0 # sets all elements of this array to 0
r2
# now if we look at the original array r, we see that the slice of data in the original array has also been changed
# this is something to keep in mind and be careful about when working with numpy arrays
r
# copy can be used to avoid this
r_copy=r.copy()
r_copy[:]=10
r_copy
r # values of r are still intact<jupyter_output><empty_output><jupyter_text>iterating over arrays<jupyter_code># first lets create a 4x3 array of random nubers from 0-9
test=np.random.randint(0,10,(4,3))
test
# iterate by row
for row in test:
    print (row)
# iterate by row index using the length function on test which returns the number of rows
for i in range(len(test)):
    print (test[i])
# we can combines these two ways of iterating by using enumerate, which gives us the row and the index of the row
for i,row in enumerate(test):
    print('row',i,'is',row)    
# zip
test2=test**2
print(test2)
# if we wish to iterate through both arrays we can use zip
for i,j in zip(test,test2):
    print(i,'+',j,'=',i+j)<jupyter_output>[[25  0 25]
 [64 81 25]
 [81 36 49]
 [64 25 49]]
[5 0 5] + [25  0 25] = [30  0 30]
[8 9 5] + [64 81 25] = [72 90 30]
[9 6 7] + [81 36 49] = [90 42 56]
[8 5 7] + [64 25 49] = [72 30 56]<jupyter_text>range function explained<jupyter_code># range(stop) - number of integers to generate starting from zero, for example range(3) == [0,1,2]

# range(start,stop,step)
# Start is the starting number of the sequence, 
# generate number up to but not including stop, 
# Step is difference between each number in the sequence
for i in range(3):
    print(i)
for i in range(3,6):
    print(i)
for i in range(4,10,2):
    print(i)
for i in range(0,-8,-2):
    print(i)
# Thank you!<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10021464985242823, 'fraction_numerical': 0.040381003488060105, 'mean_word_length': 4.257404795486601, 'pattern_counts': {'":': 0, '<': 28, '<?xml version=': 0, '>': 30, 'https://': 0, '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': '7', 'dir': 'jupyter-structured-clean-dedup', 'id': '260074', 'license': 'MIT', 'n_tokens_mistral': 2686, 'n_tokens_neox': 2521, 'n_words': 1131, 'path': 'Python-Programming/Teaching-Material/intro-to-numpy.ipynb', 'repo_name': 'vivekparasharr/Learn-Programming', 'size': '7454'}