<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Introduction to NumPy
NumPy supports arrays which are very useful to numerical computations
Arrays are N dimensional
Step1:  Vectorised
<code>
array2 = array1*k+c
</code>
Non-vectorised, here with a list
<code>
for index in range(len(list1))
Step2:  Creating an array from a list
Step3:  The core class of NumPy is the ndarray (homogeneous n-dimensional array).
To find methods or attributes
Step4:  Functions for creating arrays
arange([start,] stop[, step,], dtype=None)
evenly spaced, defined by step
Step5:  linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None)
evenly spaced, defined by length
Step6:  Filled with zeros
Step7:  By default, the dtype of the created array is float64 but other dtypes can be used
Step8:  Filled with ones
Step9:  Create array with random numbers
Step10:  Grid generation
A common task is to generate a pair of arrays that represent data coordinates. 
Useful for interpolation of mapping contours.
When orthogonal 1D coordinate arrays already exist, NumPy's meshgrid function is very useful
Step11:  Transpose arrays
This can be very useful when dealing with grids
Step12:  Array attributes
Step13:  ndarray.ndim
the number of axes (dimensions) of the array. In NumPy, the number of dimensions is referred to as rank.
Step14:  ndarray.shape
the dimensions of the array
Step15:  This is a tuple of integers indicating the size of the array in each dimension. For a matrix with n rows and m columns, shape will be (n,m). The length of the shape tuple is therefore the rank, or number of dimensions, ndim.
ndarray.size
the total number of elements of the array
Step16:  Note that size is not equal to len(). The latter returns the length of the first dimension.
Step17:  Statistical methods of arrays
Step18:  Operations over a given axis
Step19:  Vectorisation
Step20:  Vectorization is generally faster than a for loop.
But it is not always possible or the most readable
Step21:  Using indexes
Step22:  Exercise 1
Create a 2D NumPy array from the following list and assign it to the variable "arr"
Step23:  Can you guess what the following slices are equal to? Print them to check your understanding.
Step24:  Fancy indexing
NumPy arrays can be indexed with slices, but also with boolean or
integer arrays (masks)
Step25:  Exercise 2
Consider an 4x5 2D array of negative integers
Step26:  Suppose you want to return an array result, which has the squared value when an element in array a is greater than -90 and less than -40, and is 1 otherwise.
With a For loop it would look like this
Step27:  But a more less verbose and quicker approach would be
Step28:  A one-liner using np.where
Step29:  Masked arrays - how to handle (propagating) missing values
All operations related to masked arrays live in numpy.ma submodule.
The simplest example of manual creation of a masked array
Step30:  Often, a task is to mask array depending on a criterion.
Step31:  Exercise 3
Create a "data" array of 30 linearly spaced numbers in the interval (1, 15)
Hint
Step32:  Shape manipulation
Step33:  Add a dimension
Step34:  Exercise 4
Generate a 2d array with 5x5. The first value is 0 and it grows left to right and top to bottom in increments on 0.1.
<ASSISTANT_TASK:>
Python Code:
import numpy as np
Explanation: Introduction to NumPy
NumPy supports arrays which are very useful to numerical computations
Arrays are N dimensional: 1d (vector), 2d (plane),...,N dim
Arrays are (generally) faster than lists
Many packages use numpy arrays to store data
Arrays can be used to make calculations in one command, without For loops or list compreension
Let's import it...
End of explanation
np.lookfor('weighted average')
Explanation: Vectorised
<code>
array2 = array1*k+c
</code>
Non-vectorised, here with a list
<code>
for index in range(len(list1)):
    list2[index] = list1[index]*k + c 
</code>
Do we still need lists?
Lists can have different objects as elements. Arrays are homogenous.
<code>
example_list = [number, string, cat, dog]
</code>
Both arrays and lists can be nested
<code>
nested_list = 
[[1,2],
[1,2,3],
[1]]
</code>
Looking for help?
Documentation: http://docs.scipy.org/doc/numpy/reference/
Use help function (tab will show options available)
<code>
    help(np.mean)
</code>
Interactive help: NumPy has an a built-in search engine
End of explanation
a1d = np.array([3, 4, 5, 6])
a1d
a2d = np.array([[10.,   20, 30], [9, 8, 7]])
a2d
print( type( a1d[0] ) )
print( type( a2d[0,0] ) )
type(a1d)
Explanation: Creating an array from a list
End of explanation
try:
    a = np.array(1,2,3,4)   # WRONG, only 2 non-keyword arguments accepted
except ValueError as err:
    print(err)
a = np.array([1,2,3,4]) # RIGHT
np.ndarray([1,2,3,4]) #  ndarray is is a low level method. Use np.array() instead
Explanation: The core class of NumPy is the ndarray (homogeneous n-dimensional array).
To find methods or attributes:
<code>
a1d.   ->tab
</code>
Common mistakes
End of explanation
np.arange(1, 9, 2)
# for integers, np.arange is same as range but returns an array insted of a list 
np.array( range(1,9,2) )
Explanation: Functions for creating arrays
arange([start,] stop[, step,], dtype=None)
evenly spaced, defined by step
End of explanation
np.linspace(0, 1, 10)   # start, end, num-points
Explanation: linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None)
evenly spaced, defined by length
End of explanation
np.zeros((2, 3))
Explanation: Filled with zeros
End of explanation
np.zeros((2,2),dtype=int)
Explanation: By default, the dtype of the created array is float64 but other dtypes can be used:
End of explanation
np.ones((2, 3))
Explanation: Filled with ones
End of explanation
np.random.rand(4)       # uniform in [0, 1]
np.random.normal(0,1,4)      # Gaussian (mean,std dev, num samples)
np.random.gamma(1,1,(2,2))      # Gamma (shape, scale , num samples)
Explanation: Create array with random numbers
End of explanation
x = np.linspace(-5, 5, 3)
y = np.linspace(10, 40, 4)
x2d, y2d = np.meshgrid(x, y)
print(x2d)
print(y2d)
Explanation: Grid generation
A common task is to generate a pair of arrays that represent data coordinates. 
Useful for interpolation of mapping contours.
When orthogonal 1D coordinate arrays already exist, NumPy's meshgrid function is very useful:
End of explanation
print(np.transpose(y2d)) # or equivalentely
print(y2d.transpose())   # using the method of y2d
print(y2d.T)             # using the property of y2d
Explanation: Transpose arrays
This can be very useful when dealing with grids
End of explanation
a2d
Explanation: Array attributes
End of explanation
a2d.ndim
Explanation: ndarray.ndim
the number of axes (dimensions) of the array. In NumPy, the number of dimensions is referred to as rank.
End of explanation
a2d.shape
Explanation: ndarray.shape
the dimensions of the array
End of explanation
a2d.size
Explanation: This is a tuple of integers indicating the size of the array in each dimension. For a matrix with n rows and m columns, shape will be (n,m). The length of the shape tuple is therefore the rank, or number of dimensions, ndim.
ndarray.size
the total number of elements of the array
End of explanation
len(a2d)
Explanation: Note that size is not equal to len(). The latter returns the length of the first dimension.
End of explanation
print('array a1d                       :',a1d)
print('Minimum and maximum             :', a1d.min(), a1d.max())
print('Sum and product of all elements :', a1d.sum(), a1d.prod())
print('Mean and standard deviation     :', a1d.mean(), a1d.std())
Explanation: Statistical methods of arrays
End of explanation
print(a2d)
print('sum  :',a2d.sum())
print('sum  :',a2d.sum(axis=0))
print('sum  :',a2d.sum(axis=1))
Explanation: Operations over a given axis
End of explanation
np.exp(a/100.)/a
# Non-vectorised
r=np.zeros(a.shape)   # create empy array for results
for i in range(len(a)):
    r[i] = np.exp(a[i]/100.)/a[i]
r
Explanation: Vectorisation: operations on whole arrays
End of explanation
a = np.arange(10, 100, 10)
a
a[2:9:3] # [start:end:step]
a[:3] # last is not included
a[-2] # negative index counts from the end
Explanation: Vectorization is generally faster than a for loop.
But it is not always possible or the most readable:
<code>
for i in range(len(a)):
    if isprime(i):
        r[i] = a[i]
    else
        r[i]= a[i]+a[i-1]
</code>
Array indexing
Indices begin at 0, like other Python sequences and C/C++. Note that many languages, such as Matlab, R and Fortran, start with 1
In 2D, the first dimension corresponds to rows, the second to columns.
The fastest varying dimension is the last dimension! The outer level of the hierarchy is the first dimension.
End of explanation
x = np.random.rand(6)
x
xs = np.sort(x)
xs
xs[1:] - xs[:-1]
Explanation: Using indexes: how to calculate x[i]-x[i-1] without a loop?
End of explanation
# [[2, 3.2, 5.5, -6.4, -2.2, 2.4],
#  [1, 22, 4, 0.1, 5.3, -9],
#  [3, 1, 2.1, 21, 1.1, -2]]
Explanation: Exercise 1
Create a 2D NumPy array from the following list and assign it to the variable "arr":
End of explanation
# a[:, 3]
# a[1:4, 0:4]
# a[1:, 2]
Explanation: Can you guess what the following slices are equal to? Print them to check your understanding.
End of explanation
a = np.random.randint(1, 100, 6) # array of 6 random integers between 1 and 100
a
mask = ( a % 3 == 0 ) # Where divisible by 3 (% is the modulus operator).
mask
a[mask]
Explanation: Fancy indexing
NumPy arrays can be indexed with slices, but also with boolean or
integer arrays (masks)
End of explanation
a = np.arange(-100, 0, 5).reshape(4, 5)
a
Explanation: Exercise 2
Consider an 4x5 2D array of negative integers:
End of explanation
result = np.zeros(a.shape, dtype=a.dtype)
for i in range(a.shape[0]):
    for j in range(a.shape[1]):
        if a[i, j] > -90 and a[i, j] < -40:
            result[i, j] = a[i, j]**2
        else:
            result[i, j] = 1
            
result
Explanation: Suppose you want to return an array result, which has the squared value when an element in array a is greater than -90 and less than -40, and is 1 otherwise.
With a For loop it would look like this:
End of explanation
condition = (a > -90) & (a < -40)
condition
result[condition] = a[condition]**2
result[~condition] = 1
print(result)
Explanation: But a more less verbose and quicker approach would be:
End of explanation
result = np.where(condition, a**2, 1)
print(result)
Explanation: A one-liner using np.where:
End of explanation
a = np.ma.masked_array(data=[1, 2, 3],
                       mask=[True, True, False],
                       fill_value=-999)
a
Explanation: Masked arrays - how to handle (propagating) missing values
All operations related to masked arrays live in numpy.ma submodule.
The simplest example of manual creation of a masked array:
End of explanation
a = np.linspace(1, 15, 15)
masked_a = np.ma.masked_greater_equal(a, 11)
masked_a
Explanation: Often, a task is to mask array depending on a criterion.
End of explanation
# Your code:
# arr = 
# Your code:
# condition = 
# masked_arr = np.ma.masked_where(condition, arr)
# print(masked_arr)
Explanation: Exercise 3
Create a "data" array of 30 linearly spaced numbers in the interval (1, 15)
Hint: use np.linspace or np.arange functions
Create a condition - a True/False (boolean) array, corresponding to the masked values
The data array should be masked where elements are
greater than 1
less or equal than 12
divisible by 2.
Mask the array depending on the condition
Hint: use np.where function
End of explanation
a = np.array([[1, 2, 3], [4, 5, 6]])
print('{} <-- array'.format(a))
print('{} <-- its shape'.format(a.shape))
a.flatten()
a.repeat(4)
a.reshape((3, 2))
print('Old shape: {}'.format(a.shape))
print('New shape: {}'.format(a.reshape((3, 2)).shape))
Explanation: Shape manipulation
End of explanation
a[..., np.newaxis].shape
Explanation: Add a dimension
End of explanation
e4 = np.arange(0.,2.5,.1)
e4
e4.reshape([5,5])
Explanation: Exercise 4
Generate a 2d array with 5x5. The first value is 0 and it grows left to right and top to bottom in increments on 0.1.
End of explanation
<END_TASK>