import random
import numpy as np

"""
Explanation: The SciPy Stack

NumPy: Base N-dimensional array package
SciPy: Fundamental library for scientific computing
Matplotlib: Comprehensive 2D Plotting
IPython: Enhanced Interactive Console
Sympy: Symbolic mathematics
pandas: Data structures & analysis
nose: Unit testing


This is one of the 100 recipes of the IPython Cookbook, the definitive guide to high-performance scientific computing and data science in Python.

NumPy
<img src="images/numpylogo.png">
Let's import the built-in random Python module and NumPy.
End of explanation
"""


%precision 3

"""
Explanation: We use the %precision magic (defined in IPython) to show only 3 decimals in the Python output. This is just to alleviate the text.
End of explanation
"""


n = 1000000
x = [random.random() for _ in range(n)]
y = [random.random() for _ in range(n)]

x[:3], y[:3]

"""
Explanation: We generate two Python lists x and y, each one containing one million random numbers between 0 and 1.
End of explanation
"""


z = [x[i] + y[i] for i in range(n)]
z[:3]

"""
Explanation: Let's compute the element-wise sum of all these numbers: the first element of x plus the first element of y, and so on. We use a for loop in a list comprehension.
End of explanation
"""


%timeit [x[i] + y[i] for i in range(n)]

"""
Explanation: How long does this computation take? IPython defines a handy %timeit magic command to quickly evaluate the time taken by a single command.
End of explanation
"""


xa = np.array(x)
ya = np.array(y)

xa[:3]

"""
Explanation: Now, we will perform the same operation with NumPy. NumPy works on multidimensional arrays, so we need to convert our lists to arrays. The np.array() function does just that.
End of explanation
"""


za = xa + ya
za[:3]

"""
Explanation: The arrays xa and ya contain the exact same numbers than our original lists x and y. Whereas those lists where instances of a built-in class list, our arrays are instances of a NumPy class ndarray. Those types are implemented very differently in Python and NumPy. We will see that, in this example, using arrays instead of lists leads to drastic performance improvements.
Now, to compute the element-wise sum of these arrays, we don't need to do a for loop anymore. In NumPy, adding two arrays means adding the elements of the arrays component by component.
End of explanation
"""


%timeit xa + ya

"""
Explanation: We see that the list z and the array za contain the same elements (the sum of the numbers in x and y).
Let's compare the performance of this NumPy operation with the native Python loop.
End of explanation
"""


%timeit sum(x)  # pure Python
%timeit np.sum(xa)  # NumPy

"""
Explanation: We observe that this operation is more than one order of magnitude faster in NumPy than in pure Python!
Now, we will compute something else: the sum of all elements in x or xa. Although this is not an element-wise operation, NumPy is still highly efficient here. The pure Python version uses the built-in sum function on an iterable. The NumPy version uses the np.sum() function on a NumPy array.
End of explanation
"""


# The IPython Notebook historically has a tight integration with
# Matplotlib. To display plots rendered inline in the notebook, the
# ipython magic can be called
%matplotlib inline

# While it is still possible to include expose the call-for-call%
# MATLAB-like API with
#   from pylab import *
# or 
#   %pylab inline
# polluting the working namespace in this way is now discouraged.
# Instead, the preferred import is
import matplotlib.pyplot as plt

x = np.linspace(-5, 5, 100)
y = x**3

plt.figure()
plt.plot(x, y, 'g')
plt.xlabel('x')
plt.ylabel('y')
plt.title('title')
plt.show()

# Even better is the object-oriented API
fig = plt.figure()
axes = fig.add_subplot(111)
axes.plot(x, y, 'g')
axes.set_xlabel('x')
axes.set_xlabel('y')
axes.set_title('title');

"""
Explanation: We also observe an impressive speedup here.
SciPy
<img src="images/scipylogo.png">
Consists of a number of more specific packages:

Special functions (scipy.special)
Integration (scipy.integrate)
Optimization (scipy.optimize)
Interpolation (scipy.interpolate)
Fourier Transforms (scipy.fftpack)
Signal Processing (scipy.signal)
Linear Algebra (scipy.linalg)
Sparse Eigenvalue Problems (scipy.sparse)
Statistics (scipy.stats)
Multi-dimensional image processing (scipy.ndimage)
File IO (scipy.io)

Matplotlib
<img src="images/matplotliblogo.png">
End of explanation
"""


from sympy import *
init_printing()

# Create symbolic variables
x, y = symbols('x y')

# Create mathematical expressions
expr1 = (x + 1)**2
expr2 = x**2 + 2*x + 1

expr1

expr2

expr1 == expr2

simplify(expr1-expr2)

# Substitution
expr1.subs(x, expr1)

expr1.subs(x, pi)

# S converts an arbitrary expression to a type that can be used inside
# SymPy
expr1.subs(x, S(1)/2)

# evalf() goes from symbolic to numerical representation
_.evalf()

# Create a function that evaluates SymPy expressions on NumPy arrays
f = lambdify(x, expr1)
f(np.linspace(-2., 2., 5))

"""
Explanation: This is one of the 100 recipes of the IPython Cookbook, the definitive guide to high-performance scientific computing and data science in Python.

SymPy
<img src="images/sympylogo.png">
First, we import SymPy, and enable rich display LaTeX-based printing in the IPython notebook (using the MathJax Javascript library).
End of explanation
"""


# Load a CSV file into a Pandas DataFrame
import pandas as pd
url = "http://donnees.ville.montreal.qc.ca/storage/f/2014-01-20T20%3A48%3A50.296Z/2013.csv"
df = pd.read_csv(url, index_col='Date', parse_dates=True, dayfirst=True)

df.head(2)

df.describe()

"""
Explanation: Pandas
<img src="images/pandaslogo.jpg">

This is one of the 100 recipes of the IPython Cookbook, the definitive guide to high-performance scientific computing and data science in Python.
End of explanation
"""


