x = list(range(10))
print(x)
x.reverse()
print(x)

x = [3,42,8,5,42,0.4,246]
x.sort()
print(x)
x = ['A', 'C', 'B']
x.sort()
print(x)

x = list(range(4))
print(x)
x.append(5)
print(x)

"""
Explanation: Python Lists and Arrays
Unit 4, Lecture 1
Numerical Methods and Statistics

Prof. Andrew White, Feb 8 2016

Suggested Reading:

https://docs.scipy.org/doc/numpy-1.12.0/user/whatisnumpy.html
https://matplotlib.org/users/pyplot_tutorial.html

List Methods
A method is a function, but it is associated with some data. We can put a . after a list to call methods associated with the list. It's best to see some examples. Notice that the methods all modify the list, hence they are methods and not functions
End of explanation
"""


x = list(range(10))
x

x = list(range(5,10))
x

x = list(range(0,10,2))
x

x = list(range(10,0,-1))
x

"""
Explanation: Creating Python Lists
We saw you can explicitly declare all elements of a list, like [5,3,2]. You can also create lists in the following other ways:
Using the range function
End of explanation
"""


x = [4,3,41]
x[2] = "Let's put a string here instead"
x.append("And another for demonstration porpoises")
print(x)

x = [0, 1]
x.append("Look! I'm putting a string in here :)")
print(x)

x = []
x.append(43)
x.append('X')
print(x)

x = []
x.append([5,3,4])
x.append('ᕙ(˵ ಠ ਊ ಠ ˵)ᕗ')
print(x)
x.append([[54,3]])
print(x)
x.append(x)
print(x)
print(x[-1])

"""
Explanation: Notice that the second argument to range is not inclusive, even if counting downwards.
Creating Python Lists as needed
You can use the append function to add elements as you need them
End of explanation
"""


x[-1] = 'end of list'
x

"""
Explanation: List Assignment
Lists can be assigned when [] are used on the left-hand side of the assignment operator(=)
You can assign a single element:
End of explanation
"""


x[0:1] = ['new 0', 'new 1']
x

"""
Explanation: You can assign a slice, but the right-hand side should be a list with the same number of elements as the slice.
End of explanation
"""


del x[3]
x

"""
Explanation: You can also delete items instead of assigning them:
End of explanation
"""


import numpy as np

x = np.zeros(5)
print(x)

"""
Explanation: NumPy Arrays
Python lists are great. They can store strings, integers, or mixtures. You can even put lists into lists! NumPy arrays though are multidimensional and most scientific/engineering python libraries use them instead. They store the same type of data in each element and cannot change size.
End of explanation
"""


x = np.zeros( (5,2) )
print(x)


x = np.zeros( (2,3,5))
print(x)

"""
Explanation: Notice as opposed to lists, we have to state the dimension. The dimensions are passed either as a single number, like 5, or as a tuple, like (5,2) creates a $5\times 2$ array.
End of explanation
"""


x = np.ones(5)
print(x)

print(np.linspace(0,1,25))

"""
Explanation: Creating NumPy Arrays
There are many convienent methods in numpy to create arrays. You saw zeros. Here are others:
Ones
End of explanation
"""


from math import pi
x = np.linspace(-pi,pi,4)
print(np.cos(x))


x = np.arange(5)
print(x**2)

x = np.arange(5)
print(2 ** x)

np.sum(x)

np.mean(x)

np.max(x)

"""
Explanation: Notice linspace includes the end point and arange does not include the endpoint!!
Remember: we cannot append to NumPy arrays because their size is set once and never changed.
NumPy Functions
Functions from the numpy module all take arrays as arguments, so whereas we cannot call math.cos on a list, we can call np.cos on an array. This is very useful for working with probability distributions. Numpy also treats *, ** and all other arithmetic operations as per-element calculations.
End of explanation
"""


x = np.arange(0,10,0.1)
print(x.argmax())
print(x.mean())
print(x.var())

"""
Explanation: This only scratches the surface of functions that take numpy arrays. There are many more that do things from computing integrals to evaluating boolean expressions to writing out files.
NumPy Methods
You can always play with these by using TAB in your jupyter notebook. Let's see a few.
End of explanation
"""


%matplotlib inline
#the line above is for jupyter notebooks only
import matplotlib.pyplot as plt #we import a sub-module called pyplot and call it plt for short

x = np.linspace(-2*pi, 2*pi, 500)
y = np.sin(x)
plt.plot(x,y)

"""
Explanation: Plotting
Just like we use the NumPy library for working with arrays, there is a library for plotting called Matplotlib. It's import syntax is a little funny. This is how you activiate it:
End of explanation
"""


x = 5
x

x = np.linspace(-2*pi, 2*pi, 500)
y = np.sin(x)
plt.plot(x,y)
plt.show()

"""
Explanation: To get rid of that extra line at the top, we use the show command. This is sort of like the difference between using print vs just making a variable the last line.
End of explanation
"""


plt.style.use('ggplot')
plt.figure(figsize=(8,6))

x = np.linspace(-2*pi, 2*pi, 500)
y = np.sin(x)
plt.plot(x,y)
plt.show()

plt.style.use('fivethirtyeight')
plt.figure(figsize=(4,3))

x = np.linspace(-2*pi, 2*pi, 500)
y = np.sin(x)
plt.plot(x,y)
plt.show()

plt.style.available

"""
Explanation: Changing the Look and Feel
You may switch the look and feel of plots by using the plt.style.use command. You may change the size by using the plt.figure(figsize=(4,4)) command, where you give the figure size in inches.
End of explanation
"""


plt.style.use('seaborn-whitegrid')
plt.figure(figsize=(4,3))

plt.plot(x,y)
plt.xlabel("The x-axis")
plt.ylabel("The y-axis")
plt.title("The title")
plt.show()

plt.plot(x,y, label="A sine wave")
plt.plot(x, np.cos(x), label="A cosine wave")
plt.title("A Title!")
plt.legend(loc='lower left')
plt.show()

x = np.arange(10)
p = 0.2
geo_p = (1 - p)**(x-1) * p
plt.plot(x,geo_p,'o') #use circles
plt.show()

x = np.arange(10)
p = 0.2
geo_p = (1 - p)**(x-1) * p
plt.plot(x,geo_p,'yo-') #use yellow circles with dashes
plt.show()

"""
Explanation: Customizing plots
You should read the matplotlib tutorial online, but here's some basic info.
End of explanation
"""


x = np.arange(1, 10)
p = 0.2
geo_p = (1 - p)**(x-1) * p
plt.plot(x, geo_p,color='C0', label='$E_1[x]$')

#plot the mean as vertical line
plt.axvline(x=1 / p, color='C0', linestyle='--', label='$E_1[x]$')

#create a different geometric
p = 0.4
geo_p = (1 - p)**(x-1) * p
plt.plot(x, geo_p,color='C1', label='$P_2(x)$')

#plot the mean as vertical line
plt.axvline(x=1 / p, color='C1', linestyle='--', label='$E_2[x]$')

#add legend, which uses the labels = .. from above
plt.legend()

plt.show()


"""
Explanation: Specifying Color
One of the most common ways of specifying color is the default "color cycler". Matplotlib has a default set of categorical colors that represent different line types. You can specify them by using C0 or C1 or C2 etc. Let's see an example of way you might want to do this:
End of explanation
"""


x = [4,3,24,7]
for element in x:
    print(element)

x = [4,3,24,7]
xsum = 0
for element in x:
    xsum += element # <--- this means xsum = xsum + element
print(xsum)

x = [4,3,24,7]
xsum = 0
for element in x:
    xsum += element
    print(xsum)

"""
Explanation: Notice how I can use consistent colors for related lines in the figure. Categorical colors are for data which has no ordering. Gradient colors are for when a color has order. We will see those later.
For Loops
Now that we have arrays and lists, we need need new flow statements to do something with them
End of explanation
"""


from IPython.display import HTML, display
from IPython.core.magic import register_line_cell_magic
import urllib

@register_line_cell_magic
def tutor(line, cell):
    code = urllib.parse.urlencode({"code": cell})
    display(HTML("""
    <iframe width="800" height="500" frameborder="0"
            src="http://pythontutor.com/iframe-embed.html#{}&py=2">
    </iframe>
    """.format(code)))

%%tutor

prod = 1
for i in range(5):
    prod *= i
    if prod == 0:
        prod = 1
    print('{}! = {}'.format(i, int(prod)))

"""
Explanation: Python Tutor
Python Tutor Allows you to see your code as it executes. Let's look at the last example with it.
End of explanation
"""


#Floating Point
a = 4.4
a

#Converting a floating point to integer
a = int(4.4)
a

"""
Explanation: Python Data Types
Let's put all we know about python data types in one place. A data type is something which can be assigned to a variable. For example, a floating point number or a string.
Here's a list of python data types we'll cover in the class:

floating point numbers
integers
strings
lists
dictionaries
tuples
NumPy arrays

Floating Points and Integers
End of explanation
"""


a = 'A string is a list'
length = len(a)
half_length = length / 2
print(a[half_length:])

half_length = int(length / 2)
a[half_length:]

"""
Explanation: Converting floating points is often needed when you want to slice a list. For example
End of explanation
"""


3 / 2

3 // 2

a[len(a) // 2:]

"""
Explanation: This is such a common occurence though that there is a shortcut to ensure that division results in an integer:
End of explanation
"""


print(int(4.9))

"""
Explanation: What are the mathematical consequences of int?
End of explanation
"""


from math import floor, ceil

print(floor(5.9))

print(ceil(4.0001))

"""
Explanation: Often that's not exactly what we want, so we can use a few functions from math
End of explanation
"""


d = dict()
d['thingie'] = 45
d['other thingie'] = 434343

print(d['thingie'])

print(d)

"""
Explanation: Dictionaries
Dictionaries are very similar to lists. Let's see how they work:
End of explanation
"""


d = {'thing1': 3, 'b': 'a string'}
print(d['b'])

"""
Explanation: Python prints it in the format above, you can also define it using that format
End of explanation
"""


a = [0,1,2,3] # a list
a[3] = 'fdsa'
print(a)

a = (0,1,2,3)
a[3] = 'fdsa'

"""
Explanation: We're learning dictionaries because they are often required for plotting and optimization. We'll see that later in this lecture
Tuples
Tuples are just like lists, except they can't have individual elements modified.
End of explanation
"""


my_tuple = (5,4)
a = np.ones(my_tuple)
print(a)

"""
Explanation: The most common place you'll see tuples is in creating numpy arrays
End of explanation
"""


a = np.ones( (5,4) )
print(a)

"""
Explanation: Notice that's the same as this:
End of explanation
"""


x = np.linspace(0,10, 100)
y = x**2

plt.plot(x, y)
plt.text(2, 40, '$y = x^2$', fontdict={'fontsize': 24})
plt.show()

"""
Explanation: Function Arguments
Functions are a little more complicated than just the paranthesis part. There are three things you should know about functions:

Arguments are separated by ,
Arguments may be named -> foo(1,2,4, example=5)
You may have to pass in lists or dictionaries -> np.ones( (5,4) ) or foo(special_arg={'a':4, 'b':4})

Plotting provides some examples of this. Let's see them in action
End of explanation
"""


from math import log
log(10)

log(10, 10)

"""
Explanation: Most arguments are optional. For example:
End of explanation
"""


