<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Welcome to the jupyter notebook! To run any cell, press Shit+Enter or Ctrl+Enter.
IMPORTANT 
Step1:  Notebook Basics
A cell contains any type of python inputs (expression, function definitions, etc...). Running a cell is equivalent to input this block in the python interpreter. The notebook will print the output of the last executed line.
Step2:  Numpy Basics
IMPORTANT 
Step3:  Creation of arrays
Creating ndarrays (np.zeros, np.ones) is done by giving the shape as an iterable (List or Tuple). An integer is also accepted for one-dimensional array.
np.eye creates an identity matrix.
You can also create an array by giving iterables to it.
(NB 
Step4:  ndarray basics
A ndarray python object is just a reference to the data location and its characteristics.
All numpy operations applying on an array can be called np.function(a) or a.function() (i.e np.sum(a) or a.sum())
It has an attribute shape that returns a tuple of the different dimensions of the ndarray. It also has an attribute dtype that describes the type of data of the object (default type is float64)
WARNING because of the object structure, unless you call copy() copying the reference is not copying the data.
Step5:  Basic operators are working element-wise (+, -, *, /)
When trying to apply operators for arrays with different sizes, they are very specific rules that you might want to understand in the future 
Step6:  Accessing elements and slicing
For people uncomfortable with the slicing of arrays, please have a look at the 'Indexing and Slicing' section of http
Step7:  Changing the shape of arrays
ravel creates a flattened view of an array (1-D representation) whereas flatten creates flattened copy of the array.
reshape allows in-place modification of the shape of the data. transpose shuffles the dimensions.
np.newaxis allows the creation of empty dimensions.
Step8:  Reduction operations
Reduction operations (np.sum, np.max, np.min, np.std) work on the flattened ndarray by default. You can specify the reduction axis as an argument
Step9:  Linear-algebra operations
Step10:  Grouping operations
Grouping operations (np.stack, np.hstack, np.vstack, np.concatenate) take an iterable of ndarrays and not ndarrays as separate arguments 
Step11:  Working on subset of the elements
We have two ways in order to apply operations on subparts of arrays (besides slicing).
Slicing reminders
Step12:  Binary masks
Using logical operations on arrays give a binary mask. Using a binary mask as indexing acts as a filter and outputs just the very elements where the value is True. This gives a memoryview of the array that can get modified.
Step13:  Working with indices
The second way to work on subpart of arrays are through indices. Usually you'd use one array per dimension with matching indices.
WARNING 
Step14:  Working with arrays, examples
Thanks to all these tools, you should be able to avoid writing almost any for-loops which are extremely costly in Python (even more than in Matlab, because good JIT engines are yet to come). In case you really need for-loops for array computation (usually not needed but it happens) have a look at http
Step15:  Compute polynomial for a lot of values
Step16:  Scipy
Scipy is a collection of libraries more specialized than Numpy. It is the equivalent of toolboxes in Matlab.
Have a look at their collection 
<ASSISTANT_TASK:>
Python Code:
# Useful starting lines
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
%load_ext autoreload
%autoreload 2
Explanation: Welcome to the jupyter notebook! To run any cell, press Shit+Enter or Ctrl+Enter.
IMPORTANT : Please have a look at Help-&gt;User Interface Tour and Help-&gt;Keyboard Shortcuts in the toolbar above that will help you get started.
End of explanation
1
x = [2,3,4]
def my_function(l):
    l.append(12)
my_function(x)
x
# Matplotlib is used for plotting, plots are directly embedded in the
# notebook thanks to the '%matplolib inline' command at the beginning
plt.hist(np.random.randn(10000), bins=40)
plt.xlabel('X label')
plt.ylabel('Y label')
Explanation: Notebook Basics
A cell contains any type of python inputs (expression, function definitions, etc...). Running a cell is equivalent to input this block in the python interpreter. The notebook will print the output of the last executed line.
End of explanation
np.multiply
Explanation: Numpy Basics
IMPORTANT : the numpy documentation is quite good. The Notebook system is really good to help you. Use the Auto-Completion with Tab, and use Shift+Tab to get the complete documentation about the current function (when the cursor is between the parenthesis of the function for instance).
For example, you want to multiply two arrays. np.mul + Tab complete to the only valid function np.multiply. Then using Shift+Tab you learn np.multiply is actually the element-wise multiplication and is equivalent to the * operator.
End of explanation
np.zeros(4)
np.eye(3)
np.array([[1,3,4],[2,5,6]])
np.arange(10)  # NB : np.array(range(10)) is a slightly more complicated equivalent
np.random.randn(3, 4) # normal distributed values
# 3-D tensor
tensor_3 = np.ones((2, 4, 2))
tensor_3
Explanation: Creation of arrays
Creating ndarrays (np.zeros, np.ones) is done by giving the shape as an iterable (List or Tuple). An integer is also accepted for one-dimensional array.
np.eye creates an identity matrix.
You can also create an array by giving iterables to it.
(NB : The random functions np.random.rand and np.random.randn are exceptions though)
End of explanation
tensor_3.shape, tensor_3.dtype
a = np.array([[1.0, 2.0], [5.0, 4.0]])
b = np.array([[4, 3], [2, 1]])
(b.dtype, a.dtype) # each array has a data type (casting rules apply for int -> float)
np.array(["Mickey", "Mouse"]) # can hold more than just numbers
a = np.array([[1.0, 2.0], [5.0, 4.0]])
b = a  # Copying the reference only
b[0,0] = 3
a
a = np.array([[1.0, 2.0], [5.0, 4.0]])
b = a.copy()  # Deep-copy of the data
b[0,0] = 3
a
Explanation: ndarray basics
A ndarray python object is just a reference to the data location and its characteristics.
All numpy operations applying on an array can be called np.function(a) or a.function() (i.e np.sum(a) or a.sum())
It has an attribute shape that returns a tuple of the different dimensions of the ndarray. It also has an attribute dtype that describes the type of data of the object (default type is float64)
WARNING because of the object structure, unless you call copy() copying the reference is not copying the data.
End of explanation
np.ones((2, 4)) * np.random.randn(2, 4)
np.eye(3) - np.ones((3,3))
print(a)
print(a.shape)  # Get shape
print(a.shape[0])  # Get size of first dimension
Explanation: Basic operators are working element-wise (+, -, *, /)
When trying to apply operators for arrays with different sizes, they are very specific rules that you might want to understand in the future : http://docs.scipy.org/doc/numpy/user/basics.broadcasting.html
End of explanation
print(a[0])  # Get first line (slice for the first dimension)
print(a[:, 1])  # Get second column (slice for the second dimension)
print(a[0, 1])  # Get first line second column element
Explanation: Accessing elements and slicing
For people uncomfortable with the slicing of arrays, please have a look at the 'Indexing and Slicing' section of http://www.python-course.eu/numpy.php
End of explanation
a = np.array([[1.0, 2.0], [5.0, 4.0]])
b = np.array([[4, 3], [2, 1]])
v = np.array([0.5, 2.0])
print(a)
print(a.T)  # Equivalent : a.tranpose(), np.transpose(a)
print(a.ravel())
c = np.random.randn(4,5)
print(c.shape)
print(c[np.newaxis].shape)  # Adding a dimension
print(c.T.shape)  
print(c.reshape([10,2]).shape)
print(c)
print(c.reshape([10,2]))
a.reshape((-1, 1)) # a[-1] means 'whatever needs to go there'
Explanation: Changing the shape of arrays
ravel creates a flattened view of an array (1-D representation) whereas flatten creates flattened copy of the array.
reshape allows in-place modification of the shape of the data. transpose shuffles the dimensions.
np.newaxis allows the creation of empty dimensions.
End of explanation
np.sum(a), np.sum(a, axis=0), np.sum(a, axis=1) # reduce-operations reduce the whole array if no axis is specified
Explanation: Reduction operations
Reduction operations (np.sum, np.max, np.min, np.std) work on the flattened ndarray by default. You can specify the reduction axis as an argument
End of explanation
np.dot(a, b) # matrix multiplication
# Other ways of writing matrix multiplication, the '@' operator for matrix multiplication
# was introduced in Python 3.5
np.allclose(a.dot(b), a @ b)
# For other linear algebra operations, use the np.linalg module
np.linalg.eig(a)  # Eigen-decomposition
print(np.linalg.inv(a))  # Inverse
np.allclose(np.linalg.inv(a) @ a, np.identity(a.shape[1]))  # a^-1 * a = Id
np.linalg.solve(a, v) # solves ax = v
Explanation: Linear-algebra operations
End of explanation
np.hstack([a, b])
np.vstack([a, b])
np.vstack([a, b]) + v # broadcasting
np.hstack([a, b]) + v # does not work
np.hstack([a, b]) + v.T # transposing a 1-D array achieves nothing
np.hstack([a, b]) + v.reshape((-1, 1)) # reshaping to convert v from a (2,) vector to a (2,1) matrix
np.hstack([a, b]) + v[:, np.newaxis] # equivalently, we can add an axis
Explanation: Grouping operations
Grouping operations (np.stack, np.hstack, np.vstack, np.concatenate) take an iterable of ndarrays and not ndarrays as separate arguments : np.concatenate([a,b]) and not np.concatenate(a,b).
End of explanation
r = np.random.random_integers(0, 9, size=(3, 4))
r
r[0], r[1]
r[0:2]
r[1][2] # regular python
r[1, 2] # numpy
r[:, 1:3]
Explanation: Working on subset of the elements
We have two ways in order to apply operations on subparts of arrays (besides slicing).
Slicing reminders
End of explanation
r > 5  # Binary element-wise result
r[r > 5]  # Use the binary mask as filter
r[r > 5] = 999  # Modify the corresponding values with a constant
r
Explanation: Binary masks
Using logical operations on arrays give a binary mask. Using a binary mask as indexing acts as a filter and outputs just the very elements where the value is True. This gives a memoryview of the array that can get modified.
End of explanation
# Get the indices where the condition is true, gives a tuple whose length
# is the number of dimensions of the input array
np.where(r == 999)
print(np.where(np.arange(10) < 5))  # Is a 1-tuple
np.where(np.arange(10) < 5)[0]  # Accessing the first element gives the indices array
np.where(r == 999, -10, r+1000)  # Ternary condition, if True take element from first array, otherwise from second
r[(np.array([1,2]), np.array([2,2]))]  # Gets the view corresponding to the indices. NB : iterable of arrays as indexing
Explanation: Working with indices
The second way to work on subpart of arrays are through indices. Usually you'd use one array per dimension with matching indices.
WARNING : indices are usually slower than binary masks because it is harder to be parallelized by the underlying BLAS library.
End of explanation
numbers = np.random.randn(1000, 1000)
%%timeit  # Naive version
my_sum = 0
for n in numbers.ravel():
    if n>0:
        my_sum += n
%timeit np.sum(numbers > 0)
Explanation: Working with arrays, examples
Thanks to all these tools, you should be able to avoid writing almost any for-loops which are extremely costly in Python (even more than in Matlab, because good JIT engines are yet to come). In case you really need for-loops for array computation (usually not needed but it happens) have a look at http://numba.pydata.org/ (For advanced users)
Counting the number of positive elements that satisfy a condition
End of explanation
X = np.random.randn(10000)
%%timeit  # Naive version
my_result = np.zeros(len(X))
for i, x in enumerate(X.ravel()):
    my_result[i] = 1 + x + x**2 + x**3 + x**4
%timeit 1 + X + X**2 + X**3 + X**4
Explanation: Compute polynomial for a lot of values
End of explanation
X = np.random.randn(1000)
from scipy.fftpack import fft
plt.plot(fft(X).real)
Explanation: Scipy
Scipy is a collection of libraries more specialized than Numpy. It is the equivalent of toolboxes in Matlab.
Have a look at their collection : http://docs.scipy.org/doc/scipy-0.18.0/reference/
Many traditionnal functions are coded there.
End of explanation
<END_TASK>