<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 NumPy cheat sheet 2
Array slicing and searching
ToC
 - Array slicing
   - nD array slicing
   - Array dicing
 - Array broadcasting
   - Deep copy
 - Array searching
 - Array operations
  - NumPy Caveats
  - Universal functions
Step1:  Array slicing
get elements using index like in a List
Step2:  nD array slicing
Step3:  Array dicing
Step4:  Thus, you specify 
Step5:  Array broadcasting
NumPy allows bulk assigning values, just like in matlab
Step6:  Deep copy
NumPy Arrays like Python objects are always shallow copied. Hence any modification made in derivative affects the source.
Make deep copies using copy() method
Step7:  Notice they are same obj in memory
Step8:  Notice they are different objects in memory. Thus changing arr3_subset will not affect its source
Step9:  Array searching
Use matlab style array searching
Step10:  Get all numbers greater than 15
Step11:  just the condition returns a boolean matrix of same dimension as the one being queried
Step12:  Array operations
NumPy has operators like +, -, /, * overloaded so you can add two matrices like scalars
Step13:  Similarly, you can add a scalar to an array and NumPy will broadcast that operation on all the elements.
Step14:  Caveats
Numpy does not throw errors for divide by zero or for 0/0. Intead it sets value to inf and nan.
<ASSISTANT_TASK:>
Python Code:
import numpy as np
arr1 = np.random.randint(10,30, size=8)
arr1
arr2 = np.random.randint(20,200,size=50).reshape(5,10)  #method chaining - numbers from 0 to 50
arr2
Explanation: NumPy cheat sheet 2
Array slicing and searching
ToC
 - Array slicing
   - nD array slicing
   - Array dicing
 - Array broadcasting
   - Deep copy
 - Array searching
 - Array operations
  - NumPy Caveats
  - Universal functions
End of explanation
arr1[0]
arr1[3]
arr1[:3] #get the first 3 elements. Gets lower bounds inclusive, upper bound exclusive
arr1[2:] #lower bound inclusive
arr1[2:5] #get elements at index 2,3,4
Explanation: Array slicing
get elements using index like in a List
End of explanation
arr2
arr2[0,0] #style 1 - you pass in a list of indices
arr2[0][0] #style 2 - parse it as list of lists - not so popular
arr2[1] # get a full row
Explanation: nD array slicing
End of explanation
#get the second column
arr2[:,1]
Explanation: Array dicing
End of explanation
#get the 3rd row
arr2[2,:] #which is same as arr2[2]
#get the center 3,3 elements - columns 4,5,6 and rows 1,2,3
arr2[1:4, 4:7]
Explanation: Thus, you specify : for all columns, followed by 1 for column. And you get a 1D array of the result
End of explanation
arr2
arr2_subset = arr2[1:4, 4:7]
arr2_subset
arr2_subset[:,:] = 999 #assign this entire numpy the same values
arr2_subset
Explanation: Array broadcasting
NumPy allows bulk assigning values, just like in matlab
End of explanation
arr2 #notice the 999 in the middle
arr2_subset_a = arr2_subset
arr2_subset_a is arr2_subset
Explanation: Deep copy
NumPy Arrays like Python objects are always shallow copied. Hence any modification made in derivative affects the source.
Make deep copies using copy() method
End of explanation
arr3_subset = arr2_subset.copy()
arr3_subset
arr3_subset is arr2_subset
Explanation: Notice they are same obj in memory
End of explanation
arr3_subset[:,:] = 0.1
arr2_subset
Explanation: Notice they are different objects in memory. Thus changing arr3_subset will not affect its source
End of explanation
arr1
Explanation: Array searching
Use matlab style array searching
End of explanation
arr1[arr1 > 15]
arr1[arr1 > 12]
Explanation: Get all numbers greater than 15
End of explanation
arr1 > 12
arr2[arr2 > 50] #looses the original shape as its impossible to keep the 2D shape
arr2[arr2 < 30]
Explanation: just the condition returns a boolean matrix of same dimension as the one being queried
End of explanation
arr_sum = arr1 + arr1
arr_sum
arr_cubed = arr1 ** 2
arr_cubed
Explanation: Array operations
NumPy has operators like +, -, /, * overloaded so you can add two matrices like scalars
End of explanation
arr_cubed - 100
Explanation: Similarly, you can add a scalar to an array and NumPy will broadcast that operation on all the elements.
End of explanation
arr_cubed[0] = 0
arr_cubed
arr_cubed / 0
Explanation: Caveats
Numpy does not throw errors for divide by zero or for 0/0. Intead it sets value to inf and nan.
End of explanation
<END_TASK>