x = 1
y = 2
x + y

x

"""
Explanation: You are currently looking at version 1.1 of this notebook. To download notebooks and datafiles, as well as get help on Jupyter notebooks in the Coursera platform, visit the Jupyter Notebook FAQ course resource.

The Python Programming Language: Functions
End of explanation
"""


def add_numbers(x, y):
    return x + y

add_numbers(1, 2)

"""
Explanation: <br>
add_numbers is a function that takes two numbers and adds them together.
End of explanation
"""


def add_numbers(x,y,z=None):
    if (z==None):
        return x+y
    else:
        return x+y+z

print(add_numbers(1, 2))
print(add_numbers(1, 2, 3))

"""
Explanation: <br>
add_numbers updated to take an optional 3rd parameter. Using print allows printing of multiple expressions within a single cell.
End of explanation
"""


def add_numbers(x, y, z=None, flag=False):
    if (flag):
        print('Flag is true!')
    if (z==None):
        return x + y
    else:
        return x + y + z
    
print(add_numbers(1, 2, flag=True))

"""
Explanation: <br>
add_numbers updated to take an optional flag parameter.
End of explanation
"""


def add_numbers(x,y):
    return x+y

a = add_numbers
a(1,2)

"""
Explanation: <br>
Assign function add_numbers to variable a.
End of explanation
"""


type('This is a string')

type(None)

type(1)

type(1.0)

type(add_numbers)

"""
Explanation: <br>
The Python Programming Language: Types and Sequences
<br>
Use type to return the object's type.
End of explanation
"""


x = (1, 'a', 2, 'b')
type(x)

"""
Explanation: <br>
Tuples are an immutable data structure (cannot be altered).
End of explanation
"""


x = [1, 'a', 2, 'b']
type(x)

"""
Explanation: <br>
Lists are a mutable data structure.
End of explanation
"""


x.append(3.3)
print(x)

"""
Explanation: <br>
Use append to append an object to a list.
End of explanation
"""


for item in x:
    print(item)

"""
Explanation: <br>
This is an example of how to loop through each item in the list.
End of explanation
"""


i=0
while( i != len(x) ):
    print(x[i])
    i = i + 1

"""
Explanation: <br>
Or using the indexing operator:
End of explanation
"""


[1,2] + [3,4]

"""
Explanation: <br>
Use + to concatenate lists.
End of explanation
"""


[1]*3

"""
Explanation: <br>
Use * to repeat lists.
End of explanation
"""


1 in [1, 2, 3]

"""
Explanation: <br>
Use the in operator to check if something is inside a list.
End of explanation
"""


x = 'This is a string'
print(x[0]) #first character
print(x[0:1]) #first character, but we have explicitly set the end character
print(x[0:2]) #first two characters


"""
Explanation: <br>
Now let's look at strings. Use bracket notation to slice a string.
End of explanation
"""


x[-1]

"""
Explanation: <br>
This will return the last element of the string.
End of explanation
"""


x[-4:-2]

"""
Explanation: <br>
This will return the slice starting from the 4th element from the end and stopping before the 2nd element from the end.
End of explanation
"""


x[:3]

"""
Explanation: <br>
This is a slice from the beginning of the string and stopping before the 3rd element.
End of explanation
"""


x[3:]

firstname = 'Christopher'
lastname = 'Brooks'

print(firstname + ' ' + lastname)
print(firstname*3)
print('Chris' in firstname)


"""
Explanation: <br>
And this is a slice starting from the 3rd element of the string and going all the way to the end.
End of explanation
"""


firstname = 'Christopher Arthur Hansen Brooks'.split(' ')[0] # [0] selects the first element of the list
lastname = 'Christopher Arthur Hansen Brooks'.split(' ')[-1] # [-1] selects the last element of the list
print(firstname)
print(lastname)

"""
Explanation: <br>
split returns a list of all the words in a string, or a list split on a specific character.
End of explanation
"""


'Chris' + 2

'Chris' + str(2)

"""
Explanation: <br>
Make sure you convert objects to strings before concatenating.
End of explanation
"""


x = {'Christopher Brooks': 'brooksch@umich.edu', 'Bill Gates': 'billg@microsoft.com'}
x['Christopher Brooks'] # Retrieve a value by using the indexing operator


x['Kevyn Collins-Thompson'] = None
x['Kevyn Collins-Thompson']

"""
Explanation: <br>
Dictionaries associate keys with values.
End of explanation
"""


for name in x:
    print(x[name])

"""
Explanation: <br>
Iterate over all of the keys:
End of explanation
"""


for email in x.values():
    print(email)

"""
Explanation: <br>
Iterate over all of the values:
End of explanation
"""


for name, email in x.items():
    print(name)
    print(email)

"""
Explanation: <br>
Iterate over all of the items in the list:
End of explanation
"""


x = ('Christopher', 'Brooks', 'brooksch@umich.edu')
fname, lname, email = x

fname

lname

"""
Explanation: <br>
You can unpack a sequence into different variables:
End of explanation
"""


x = ('Christopher', 'Brooks', 'brooksch@umich.edu', 'Ann Arbor')
fname, lname, email = x

"""
Explanation: <br>
Make sure the number of values you are unpacking matches the number of variables being assigned.
End of explanation
"""


print('Chris' + 2)

print('Chris' + str(2))

"""
Explanation: <br>
The Python Programming Language: More on Strings
End of explanation
"""


sales_record = {
'price': 3.24,
'num_items': 4,
'person': 'Chris'}

sales_statement = '{} bought {} item(s) at a price of {} each for a total of {}'

print(sales_statement.format(sales_record['person'],
                             sales_record['num_items'],
                             sales_record['price'],
                             sales_record['num_items']*sales_record['price']))


"""
Explanation: <br>
Python has a built in method for convenient string formatting.
End of explanation
"""


import csv

%precision 2

with open('mpg.csv') as csvfile:
    mpg = list(csv.DictReader(csvfile))
    
mpg[:3] # The first three dictionaries in our list.

"""
Explanation: <br>
Reading and Writing CSV files
<br>
Let's import our datafile mpg.csv, which contains fuel economy data for 234 cars.

mpg : miles per gallon
class : car classification
cty : city mpg
cyl : # of cylinders
displ : engine displacement in liters
drv : f = front-wheel drive, r = rear wheel drive, 4 = 4wd
fl : fuel (e = ethanol E85, d = diesel, r = regular, p = premium, c = CNG)
hwy : highway mpg
manufacturer : automobile manufacturer
model : model of car
trans : type of transmission
year : model year
End of explanation
"""


len(mpg)

"""
Explanation: <br>
csv.Dictreader has read in each row of our csv file as a dictionary. len shows that our list is comprised of 234 dictionaries.
End of explanation
"""


mpg[0].keys()

"""
Explanation: <br>
keys gives us the column names of our csv.
End of explanation
"""


sum(float(d['cty']) for d in mpg) / len(mpg)

"""
Explanation: <br>
This is how to find the average cty fuel economy across all cars. All values in the dictionaries are strings, so we need to convert to float.
End of explanation
"""


sum(float(d['hwy']) for d in mpg) / len(mpg)

"""
Explanation: <br>
Similarly this is how to find the average hwy fuel economy across all cars.
End of explanation
"""


cylinders = set(d['cyl'] for d in mpg)
cylinders

"""
Explanation: <br>
Use set to return the unique values for the number of cylinders the cars in our dataset have.
End of explanation
"""


CtyMpgByCyl = []

for c in cylinders: # iterate over all the cylinder levels
    summpg = 0
    cyltypecount = 0
    for d in mpg: # iterate over all dictionaries
        if d['cyl'] == c: # if the cylinder level type matches,
            summpg += float(d['cty']) # add the cty mpg
            cyltypecount += 1 # increment the count
    CtyMpgByCyl.append((c, summpg / cyltypecount)) # append the tuple ('cylinder', 'avg mpg')

CtyMpgByCyl.sort(key=lambda x: x[0])
CtyMpgByCyl

"""
Explanation: <br>
Here's a more complex example where we are grouping the cars by number of cylinder, and finding the average cty mpg for each group.
End of explanation
"""


vehicleclass = set(d['class'] for d in mpg) # what are the class types
vehicleclass

"""
Explanation: <br>
Use set to return the unique values for the class types in our dataset.
End of explanation
"""


HwyMpgByClass = []

for t in vehicleclass: # iterate over all the vehicle classes
    summpg = 0
    vclasscount = 0
    for d in mpg: # iterate over all dictionaries
        if d['class'] == t: # if the cylinder amount type matches,
            summpg += float(d['hwy']) # add the hwy mpg
            vclasscount += 1 # increment the count
    HwyMpgByClass.append((t, summpg / vclasscount)) # append the tuple ('class', 'avg mpg')

HwyMpgByClass.sort(key=lambda x: x[1])
HwyMpgByClass

"""
Explanation: <br>
And here's an example of how to find the average hwy mpg for each class of vehicle in our dataset.
End of explanation
"""


import datetime as dt
import time as tm

"""
Explanation: <br>
The Python Programming Language: Dates and Times
End of explanation
"""


tm.time()

"""
Explanation: <br>
time returns the current time in seconds since the Epoch. (January 1st, 1970)
End of explanation
"""


dtnow = dt.datetime.fromtimestamp(tm.time())
dtnow

"""
Explanation: <br>
Convert the timestamp to datetime.
End of explanation
"""


dtnow.year, dtnow.month, dtnow.day, dtnow.hour, dtnow.minute, dtnow.second # get year, month, day, etc.from a datetime

"""
Explanation: <br>
Handy datetime attributes:
End of explanation
"""


delta = dt.timedelta(days = 100) # create a timedelta of 100 days
delta

"""
Explanation: <br>
timedelta is a duration expressing the difference between two dates.
End of explanation
"""


today = dt.date.today()

today - delta # the date 100 days ago

today > today-delta # compare dates

"""
Explanation: <br>
date.today returns the current local date.
End of explanation
"""


class Person:
    department = 'School of Information' #a class variable

    def set_name(self, new_name): #a method
        self.name = new_name
    def set_location(self, new_location):
        self.location = new_location

person = Person()
person.set_name('Christopher Brooks')
person.set_location('Ann Arbor, MI, USA')
print('{} live in {} and works in the department {}'.format(person.name, person.location, person.department))

"""
Explanation: <br>
The Python Programming Language: Objects and map()
<br>
An example of a class in python:
End of explanation
"""


store1 = [10.00, 11.00, 12.34, 2.34]
store2 = [9.00, 11.10, 12.34, 2.01]
cheapest = map(min, store1, store2)
cheapest

"""
Explanation: <br>
Here's an example of mapping the min function between two lists.
End of explanation
"""


for item in cheapest:
    print(item)

"""
Explanation: <br>
Now let's iterate through the map object to see the values.
End of explanation
"""


my_function = lambda a, b, c : a + b

my_function(1, 2, 3)

"""
Explanation: <br>
The Python Programming Language: Lambda and List Comprehensions
<br>
Here's an example of lambda that takes in three parameters and adds the first two.
End of explanation
"""


my_list = []
for number in range(0, 1000):
    if number % 2 == 0:
        my_list.append(number)
my_list

"""
Explanation: <br>
Let's iterate from 0 to 999 and return the even numbers.
End of explanation
"""


my_list = [number for number in range(0,1000) if number % 2 == 0]
my_list

"""
Explanation: <br>
Now the same thing but with list comprehension.
End of explanation
"""


import numpy as np

"""
Explanation: <br>
The Python Programming Language: Numerical Python (NumPy)
End of explanation
"""


mylist = [1, 2, 3]
x = np.array(mylist)
x

"""
Explanation: <br>
Creating Arrays
Create a list and convert it to a numpy array
End of explanation
"""


y = np.array([4, 5, 6])
y

"""
Explanation: <br>
Or just pass in a list directly
End of explanation
"""


m = np.array([[7, 8, 9], [10, 11, 12]])
m

"""
Explanation: <br>
Pass in a list of lists to create a multidimensional array.
End of explanation
"""


m.shape

"""
Explanation: <br>
Use the shape method to find the dimensions of the array. (rows, columns)
End of explanation
"""


n = np.arange(0, 30, 2) # start at 0 count up by 2, stop before 30
n

"""
Explanation: <br>
arange returns evenly spaced values within a given interval.
End of explanation
"""


n = n.reshape(3, 5) # reshape array to be 3x5
n

"""
Explanation: <br>
reshape returns an array with the same data with a new shape.
End of explanation
"""


o = np.linspace(0, 4, 9) # return 9 evenly spaced values from 0 to 4
o

"""
Explanation: <br>
linspace returns evenly spaced numbers over a specified interval.
End of explanation
"""


o.resize(3, 3)
o

"""
Explanation: <br>
resize changes the shape and size of array in-place.
End of explanation
"""


np.ones((3, 2))

"""
Explanation: <br>
ones returns a new array of given shape and type, filled with ones.
End of explanation
"""


np.zeros((2, 3))

"""
Explanation: <br>
zeros returns a new array of given shape and type, filled with zeros.
End of explanation
"""


np.eye(3)

"""
Explanation: <br>
eye returns a 2-D array with ones on the diagonal and zeros elsewhere.
End of explanation
"""


np.diag(y)

"""
Explanation: <br>
diag extracts a diagonal or constructs a diagonal array.
End of explanation
"""


np.array([1, 2, 3] * 3)

"""
Explanation: <br>
Create an array using repeating list (or see np.tile)
End of explanation
"""


np.repeat([1, 2, 3], 3)

"""
Explanation: <br>
Repeat elements of an array using repeat.
End of explanation
"""


p = np.ones([2, 3], int)
p

"""
Explanation: <br>
Combining Arrays
End of explanation
"""


np.vstack([p, 2*p])

"""
Explanation: <br>
Use vstack to stack arrays in sequence vertically (row wise).
End of explanation
"""


np.hstack([p, 2*p])

"""
Explanation: <br>
Use hstack to stack arrays in sequence horizontally (column wise).
End of explanation
"""


print(x + y) # elementwise addition     [1 2 3] + [4 5 6] = [5  7  9]
print(x - y) # elementwise subtraction  [1 2 3] - [4 5 6] = [-3 -3 -3]

print(x * y) # elementwise multiplication  [1 2 3] * [4 5 6] = [4  10  18]
print(x / y) # elementwise divison         [1 2 3] / [4 5 6] = [0.25  0.4  0.5]

print(x**2) # elementwise power  [1 2 3] ^2 =  [1 4 9]

"""
Explanation: <br>
Operations
Use +, -, *, / and ** to perform element wise addition, subtraction, multiplication, division and power.
End of explanation
"""


x.dot(y) # dot product  1*4 + 2*5 + 3*6

z = np.array([y, y**2])
print(len(z)) # number of rows of array

"""
Explanation: <br>
Dot Product:  
$ \begin{bmatrix}x_1 \ x_2 \ x_3\end{bmatrix}
\cdot
\begin{bmatrix}y_1 \ y_2 \ y_3\end{bmatrix}
= x_1 y_1 + x_2 y_2 + x_3 y_3$
End of explanation
"""


z = np.array([y, y**2])
z

"""
Explanation: <br>
Let's look at transposing arrays. Transposing permutes the dimensions of the array.
End of explanation
"""


z.shape

"""
Explanation: <br>
The shape of array z is (2,3) before transposing.
End of explanation
"""


z.T

"""
Explanation: <br>
Use .T to get the transpose.
End of explanation
"""


z.T.shape

"""
Explanation: <br>
The number of rows has swapped with the number of columns.
End of explanation
"""


z.dtype

"""
Explanation: <br>
Use .dtype to see the data type of the elements in the array.
End of explanation
"""


z = z.astype('f')
z.dtype

"""
Explanation: <br>
Use .astype to cast to a specific type.
End of explanation
"""


a = np.array([-4, -2, 1, 3, 5])

a.sum()

a.max()

a.min()

a.mean()

a.std()

"""
Explanation: <br>
Math Functions
Numpy has many built in math functions that can be performed on arrays.
End of explanation
"""


a.argmax()

a.argmin()

"""
Explanation: <br>
argmax and argmin return the index of the maximum and minimum values in the array.
End of explanation
"""


s = np.arange(13)**2
s

"""
Explanation: <br>
Indexing / Slicing
End of explanation
"""


s[0], s[4], s[-1]

"""
Explanation: <br>
Use bracket notation to get the value at a specific index. Remember that indexing starts at 0.
End of explanation
"""


s[1:5]

"""
Explanation: <br>
Use : to indicate a range. array[start:stop]
Leaving start or stop empty will default to the beginning/end of the array.
End of explanation
"""


s[-4:]

"""
Explanation: <br>
Use negatives to count from the back.
End of explanation
"""


s[-5::-2]

"""
Explanation: <br>
A second : can be used to indicate step-size. array[start:stop:stepsize]
Here we are starting 5th element from the end, and counting backwards by 2 until the beginning of the array is reached.
End of explanation
"""


r = np.arange(36)
r.resize((6, 6))
r

"""
Explanation: <br>
Let's look at a multidimensional array.
End of explanation
"""


r[2, 2]

"""
Explanation: <br>
Use bracket notation to slice: array[row, column]
End of explanation
"""


r[3, 3:6]

"""
Explanation: <br>
And use : to select a range of rows or columns
End of explanation
"""


r[:2, :-1]

"""
Explanation: <br>
Here we are selecting all the rows up to (and not including) row 2, and all the columns up to (and not including) the last column.
End of explanation
"""


r[-1, ::2]

"""
Explanation: <br>
This is a slice of the last row, and only every other element.
End of explanation
"""


r[r > 30]

"""
Explanation: <br>
We can also perform conditional indexing. Here we are selecting values from the array that are greater than 30. (Also see np.where)
End of explanation
"""


r[r > 30] = 30
r

"""
Explanation: <br>
Here we are assigning all values in the array that are greater than 30 to the value of 30.
End of explanation
"""


r2 = r[:3,:3]
r2

"""
Explanation: <br>
Copying Data
Be careful with copying and modifying arrays in NumPy!
r2 is a slice of r
End of explanation
"""


r2[:] = 0
r2

"""
Explanation: <br>
Set this slice's values to zero ([:] selects the entire array)
End of explanation
"""


r

"""
Explanation: <br>
r has also been changed!
End of explanation
"""


r_copy = r.copy()
r_copy

"""
Explanation: <br>
To avoid this, use r.copy to create a copy that will not affect the original array
End of explanation
"""


r_copy[:] = 10
print(r_copy, '\n')
print(r)

"""
Explanation: <br>
Now when r_copy is modified, r will not be changed.
End of explanation
"""


test = np.random.randint(0, 10, (4,3))
test

"""
Explanation: <br>
Iterating Over Arrays
Let's create a new 4 by 3 array of random numbers 0-9.
End of explanation
"""


for row in test:
    print(row)

"""
Explanation: <br>
Iterate by row:
End of explanation
"""


for i in range(len(test)):
    print(test[i])

"""
Explanation: <br>
Iterate by index:
End of explanation
"""


for i, row in enumerate(test):
    print('row', i, 'is', row)

"""
Explanation: <br>
Iterate by row and index:
End of explanation
"""


test2 = test**2
test2

for i, j in zip(test, test2):
    print(i,'+',j,'=',i+j)

"""
Explanation: <br>
Use zip to iterate over multiple iterables.
End of explanation
"""


