# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

# + [markdown] id="view-in-github" colab_type="text"
# <a href="https://colab.research.google.com/github/brianharnish/cs419/blob/master/labs/numpy.ipynb" target="_parent"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab"/></a>

# + [markdown] id="YvO58iCw4TPP" colab_type="text"
# # Task 1: Getting started with Numpy
#
# Let's spend a few minutes just learning some of the fundamentals of Numpy. (pronounced as num-pie **not num-pee**) 
#
# ### what is numpy
# Numpy is a Python library that support large, multi-dimensional arrays and matrices. 
#
# Let's look at an example. Suppose we start with a little table:
#
# | a  | b | c  |  d | e |
# | :---: | :---: | :---: | :---: | :---: |
# | 0 | 1 | 2 | 3 | 4 |
# |10| 11| 12 | 13 | 14|
# |20| 21 | 22 | 23 | 24 |
# |30 | 31 | 32 | 33 | 34 |
# |40 |41 | 42 | 43 | 44 |
#
# and I simply want to add 10 to each cell:
#
# | a  | b | c  |  d | e |
# | :---: | :---: | :---: | :---: | :---: |
# | 10 | 11 | 12 | 13 | 14 |
# |20| 21| 22 | 23 | 24|
# |30| 31 | 32 | 33 | 34 |
# |40 | 41 | 42 | 43 | 44 |
# |50 |51 | 52 | 53 | 54 |
#
# To make things interesting, instead of a a 5 x5 array, let's make it 1,000x1,000 -- so 1 million cells!
#
# First, let's construct it in generic Python

# + id="bOUNkVx_4TPS" colab_type="code" colab={}
a = [[x + y * 1000 for x in range(1000)] for y in range(1000)]


# + [markdown] id="2amye7sC4TPc" colab_type="text"
# Instead of glossing over the first code example in the course, take your time, go back, and parse it out so you understand it. Test it out and see what it looks like. For example, how would you change the example to make a 10x10 array called `a2`? execute the code here:

# + id="OtiM1QDa4TPh" colab_type="code" colab={}
# TO DO
a2 = [[x+y*10 for x in range(10)] for y in range(10)]

# + [markdown] id="ZeolqblL4TPo" colab_type="text"
# Now let's take a look at the value of a2:

# + id="VEqna78i4TPp" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 191} outputId="8e69a4d7-bc34-46c8-9c50-d2dfcb0c0f99"
a2


# + [markdown] id="RhHHAQfX4TPy" colab_type="text"
# Now that we understand that line of code let's go on and write a function that will add 10 to each cell in our original 1000x1000 matrix.
#

# + id="5gpmU08i4TPz" colab_type="code" colab={}
def addToArr(sizeof):
    for i in range(sizeof):
        for j in range(sizeof):
            a[i][j] = a[i][j] + 10



# + [markdown] id="elSN3EHX4TP4" colab_type="text"
# As you can see, we iterate over the array with nested for loops. 
#
# Let's take a look at how much time it takes to run that function:

# + id="y3r-2u6s4TP6" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 52} outputId="eb9243d4-f6f6-4efe-8a22-c315717fb7c0"
# %time addToArr(1000)

# + [markdown] id="twm-NSBX4TQB" colab_type="text"
# My results were:
#
#     CPU times: user 145 ms, sys: 0 ns, total: 145 ms
#     Wall time: 143 ms
#
# So about 1/7 of a second. 
#
# ### Doing in using Numpy
# Now do the same using Numpy.
#
#
# We can construct the array using
#     
#     arr = np.arange(1000000).reshape((1000,1000))
#
# Not sure what that line does? Numpy has great online documentation. [Documentation for np.arange](https://numpy.org/doc/stable/reference/generated/numpy.arange.html) says it "Return evenly spaced values within a given interval." Let's try it out:

# + id="Krgo71yk4TQD" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 34} outputId="430c0ce4-86c9-4ac7-8d29-f41ad853da39"
import numpy as np
np.arange(16)

# + [markdown] id="_Lcf7q9E4TQH" colab_type="text"
# So `np.arange(10)` creates a matrix of 16 sequential integers. [The documentation for reshape](https://numpy.org/doc/1.18/reference/generated/numpy.reshape.html) says, as the name suggests, "Gives a new shape to an array without changing its data."  Suppose we want to reshape our 1 dimensional matrix of 16 integers to a 4x4 one. we can do:

# + id="SQ32mDwJ4TQI" colab_type="code" colab={} outputId="bf566205-409d-49ff-a66d-a1e2cc527f75"
np.arange(16).reshape((4,4))

# + [markdown] id="C0f7qK3h4TQM" colab_type="text"
# As you can see it is pretty easy to find documentation on Numpy.
#
# Back to our example of creating a 1000x1000 matrix, we now can time how long it takes to add 10 to each cell.
#
#     # %time arr = arr + 10
#     
# Let's put this all together:

# + id="84avQwMW4TQO" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 52} outputId="e49985dc-6b7f-4383-a649-8aa83c750c4e"
import numpy as np
arr = np.arange(1000000).reshape((1000,1000))
# %time arr = arr + 10

# + [markdown] id="Ra0pa57n4TQS" colab_type="text"
# My results were
#
#     CPU times: user 1.26 ms, sys: 408 µs, total: 1.67 ms
#     Wall time: 1.68 ms
#
# So, depending on your computer, somewhere around 25 to 100 times faster. **That is phenomenally faster!**
#
# And Numpy is even fast in creating arrays:
#
# #### the generic Python way

# + id="TOELtDP84TQT" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 52} outputId="6514125f-b6ff-41d3-c687-7926591266ba"
a = [[x + y * 1000 for x in range(1000)] for y in range(1000)]


# + [markdown] id="DMl0Y4HM4TQY" colab_type="text"
# My results were
#
#     CPU times: user 92.1 ms, sys: 11.5 ms, total: 104 ms
#     Wall time: 102 ms
#
# #### the Numpy way

# + id="4-iE5DlZ4TQZ" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 52} outputId="c49cefdc-9f9d-4f6b-fb3d-221c2dd347ec"
# %time arr = np.arange(1000000).reshape((1000,1000))

# + [markdown] id="XDxa7Z6X4TQd" colab_type="text"
# What are your results?
#
# Numpy:
#
# CPU times: user 3.71 ms, sys: 3.91 ms, total: 7.61 ms
#
# Wall time: 10 ms
#
# Generic Python: 
#
# CPU times: user 126 ms, sys: 15 ms, total: 141 ms
# Wall time: 142 ms
#
# <h3 style="color:red">Q1. Speed</h3>
# <span style="color:red">Suppose I want to create an array with 10,000 by 10,000 cells. Then I want to add 1 to each cell. How much time does this take using generic Python arrays and using Numpy arrays?</span>
#
# Generic:
#
# CPU times: user 130 ms, sys: 38 µs, total: 130 ms
# Wall time: 130 ms
#
# Numpy: 
#
# CPU times: user 113 ms, sys: 145 ms, total: 258 ms
# Wall time: 258 ms
#
# #### in Python
# (be patient -- this may take a number of seconds)

# + id="C_D7y0x74TQf" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 52} outputId="91eaea20-1a87-419c-b6bc-3d945a9f1330"
myarr = [[x + y * 10000 for x in range(10000)] for y in range(10000)]
def add1():
  for i in range(1000):
    for j in range(1000):
      myarr[i][j] = myarr[i][j] + 1
# %time add1()


# + [markdown] id="W1jZKJUL4TQk" colab_type="text"
# #### in Numpy

# + id="iSU6YNlp4TQl" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 278} outputId="6a6dea22-51c7-4418-d3a6-ded8e97a2277"
import numpy as np
mynumarr = np.arange(100000000).reshape(10000, 10000)
# %time mynumarr + 1

# + [markdown] id="GjCE-F5y4TQo" colab_type="text"
# ### built in functions
# In addition to being faster, numpy has a wide range of built in functions. So, for example, instead of you writing code to calculate the mean or sum or standard deviation of a multidimensional array you can just use numpy:

# + id="fUzv0jTh4TQq" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 34} outputId="b2fe6882-a315-42af-899c-84fc5406af38"
arr.mean()

# + id="r6-nikNe4TQv" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 34} outputId="ab813f9d-5783-4f65-88a6-832d4f059a80"
arr.sum()

# + id="ulfgOIgg4TQz" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 34} outputId="98db7fe6-850b-4bbe-fff9-ace443696b37"
 arr.std()

# + [markdown] id="0WlMOYYk4TQ2" colab_type="text"
# So not only is it faster, but it minimizes the code you have to write. A win, win.
#
# Let's continue with some basics.

# + [markdown] id="jZnlcChn4TQ2" colab_type="text"
# ## numpy examined 
# So Numpy is a library containing a super-fast n-dimensional array object and a load of functions that can operate on those arrays. To use numpy, we must first load the library into our code and we do that with the statement:
#

# + id="fW2RTY1W4TQ3" colab_type="code" colab={}
 import numpy as np

# + [markdown] id="53eEfdFZ4TQ8" colab_type="text"
# Perhaps most of you are saying "fine, fine, I know this already", but let me catch others up to speed. This is just one of several ways we can load a library into Python. We could just say:

# + id="W8sO13A34TQ8" colab_type="code" colab={}
 import numpy

# + [markdown] id="pu8CdMNM4TRB" colab_type="text"
# and everytime we need to use one of the functions built in
# to numpy we would need to preface that function with `numpy` . So for example, we could create an array with
#

# + id="MbLgi6YH4TRC" colab_type="code" colab={}
arr = numpy.array([1, 2, 3, 4, 5])

# + [markdown] id="KaBdiuoW4TRF" colab_type="text"
# If we got tired of writing `numpy` in front of every function, instead of typing

# + id="pGIi5emZ4TRG" colab_type="code" colab={}
import numpy

# + [markdown] id="Ncjnn2Ao4TRJ" colab_type="text"
# we could write:

# + id="IALtX00-4TRK" colab_type="code" colab={}
from numpy import *

# + [markdown] id="bwu36h8i4TRN" colab_type="text"
# (where that * means 'everything' and the whole expression means import everything from the numpy library).  Now we can use any numpy function without putting numpy in front of it:

# + id="S83oF-1-4TRP" colab_type="code" colab={}
arr = array([1, 2, 3, 4, 5])

# + [markdown] id="2_bpFnDE4TRT" colab_type="text"
# This may at first seem like a good idea, but it is considered bad form by Python developers. 
#
# The solution is to use what we initially introduced:

# + id="FjTtg3O_4TRX" colab_type="code" colab={}
 import numpy as np

# + [markdown] id="CmjMNqb14TRa" colab_type="text"
# this makes `np` an alias for numpy. so now we would put *np* in front of numpy functions.

# + id="-jNFkV-w4TRa" colab_type="code" colab={}
 arr = np.array([1, 2, 3, 4, 5])

# + [markdown] id="NtdEAUyL4TRe" colab_type="text"
# Of course we could use anything as an alias for numpy:

# + id="bF5v-uqM4TRe" colab_type="code" colab={}
import numpy as myCoolSneakers
arr = myCoolSneakers.array([1, 2, 3, 4, 5])


# + [markdown] id="YYedz6yd4TRk" colab_type="text"
# But it is convention among data scientists, machine learning experts, and the cool kids to use np.  One big benefit of this convention is that it makes the code you write more understandable to others and vice versa (I don't need to be scouring your code to find out what `myCoolSneakers.array` does)
#
# ## creating arrays
#
# An Array in Numpy is called an `ndarray` for n-dimensional array.  As we will see, they share some similarities with Python lists. We have already seen how to create one:

# + id="f5nIOXRf4TRk" colab_type="code" colab={}
arr = np.array([1, 2, 3, 4, 5])

# + [markdown] id="4LkPC3b04TRn" colab_type="text"
# and to display what `arr` equals

# + id="InCZXCnc4TRo" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 34} outputId="a97fa890-ac0c-41f4-f6df-53804bc48153"
arr

# + [markdown] id="myMAwh-W4TRq" colab_type="text"
# This is a one dimensional array. The position of an element in the array is called the index. The first element of the array is at index 0, the next at index 1 and so on. We can get the item at a particular index by using the syntax:

# + id="KLqDsLhc4TRr" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 34} outputId="e582eefd-2e18-46ff-c1e0-e0c56ec147dc"
 arr[0]

# + id="-WUzfMUY4TRv" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 34} outputId="62f78088-35d6-4253-d0fa-86d8aa3b4263"
arr[3]

# + [markdown] id="Pqa7Yo0T4TRy" colab_type="text"
# We can create a 2 dimensional array that looks like
#
#       10  20  30
#       40  50  60
#  
# by:
#

# + id="sHK2VoXj4TR0" colab_type="code" colab={}
 arr = np.array([[10, 20, 30], [40, 50, 60]])

# + [markdown] id="It-VOMHo4TR4" colab_type="text"
# and we can show the contents of that array just be using the name of the array, `arr`
#

# + id="W3UXV2x84TR5" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 52} outputId="3b00e098-156a-484b-a5aa-c0e0b2e8dd42"
arr

# + [markdown] id="UGJ8fTqs4TR8" colab_type="text"
# We don't need to name arrays `arr`, we can name them anything we want. 

# + id="moKih7Bt4TR8" colab_type="code" colab={}
ratings = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

# + id="ME_P6Ybn4TR-" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 69} outputId="961ae54f-ef97-44bb-e396-9b6e6c972406"
ratings

# + [markdown] id="fIxNUHtk4TSB" colab_type="text"
# So far, we've been creating numpy arrays by using Python lists. We can make that more explicit by first creating the Python list and then using it to create the ndarray:

# + id="CYITcR2K4TSB" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 52} outputId="5a4cbafd-dcd2-4451-f283-f0f4bc350c86"
pythonArray = [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]
sweet = np.array(pythonArray)
sweet

# + [markdown] id="0239ShCk4TSE" colab_type="text"
# We can also create an array of all zeros or all ones directly:

# + id="ja4ltbWN4TSE" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 34} outputId="af0159ed-cc53-4aab-9a00-d79847d4ec39"
np.zeros(10)

# + id="gdyXwLXV4TSH" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 104} outputId="1e53d695-5a5c-4f3b-a959-cd2b5cc5edea"
np.ones((5, 2))

# + [markdown] id="X6ENzXd-4TSK" colab_type="text"
# ### indexing
# Indexing elements in ndarrays works pretty much the same as it does in Python. We have already seen one example, here is another example with a one dimensional array:
#

# + id="pWhOVXjZ4TSK" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 34} outputId="c7482e9a-33c7-4cff-dd35-4bf522e1adcd"
temperatures = np.array([48, 44, 37, 35, 32, 29, 33, 36, 42])
temperatures[0]

# + id="JiQrzh344TSN" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 34} outputId="8eb41b88-b504-4527-ad01-1adc1f054e44"
temperatures[3]

# + [markdown] id="bnG5l3DD4TSP" colab_type="text"
# and a two dimensional one:

# + id="cBfCA4Q54TSQ" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 34} outputId="0a22d3e3-eed7-4fbd-a9e7-d31e64befcd9"
sample = np.array([[10, 20, 30], [40, 50, 60]])
sample[0][1]

# + [markdown] id="cTA5rlsL4TST" colab_type="text"
# For numpy ndarrays we can also use a comma to separate the indices of multi-dimensional arrays:

# + id="YeJmDRvp4TSU" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 34} outputId="f9b42ecb-ff7e-45e6-944e-83a0c049b2d9"
sample[1,2]

# + [markdown] id="oCo-L8Vm4TSW" colab_type="text"
# And, like Python you can also get a slice of an array. First, here is the basic Python example:

# + id="KWM1MFGP4TSW" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 34} outputId="c5576acc-2557-42b2-da1c-77229b770a23"
a = [10, 20, 30, 40, 50, 60]
b = a[1:4]
b

# + [markdown] id="FbUhm5HP4TSZ" colab_type="text"
# and the similar numpy example:

# + id="OqgPubtu4TSZ" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 34} outputId="c9c741e7-6a95-4dc6-f0f9-d1fa8a044599"
aarr = np.array(a)
barr = aarr[1:4]
barr

# + [markdown] id="zB0cQ7YK4TSc" colab_type="text"
# ### Something  wacky to remember
# But there is a difference between Python arrays and numpy ndarrays. If I alter the array `b` in Python the orginal `a` array is not altered:

# + id="zxGHoP304TSc" colab_type="code" colab={}
b[1] = b[1] + 5

# + id="8M3L-iVm4TSf" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 34} outputId="bd69ef92-d086-4421-a866-f4ad86e476a9"
b

# + id="7iKhgYtY4TSj" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 34} outputId="fa7fd562-71ce-46ac-e168-5c364b4206bc"
a

# + [markdown] id="u8J1v5Kc4TSm" colab_type="text"
# but if we do the same in numpy:

# + id="8pcO_LMO4TSn" colab_type="code" colab={}
barr[1] = barr[1] + 5

# + id="-Dr09sFD4TSq" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 34} outputId="ea20d981-d681-4068-9f24-5126e8c3acc4"
barr

# + id="G6NaVQ374TS2" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 34} outputId="9805cf81-6a57-428a-ba58-5f04f636c116"
aarr

# + [markdown] id="J3W6C-BX4TS5" colab_type="text"
# we see that the original array is altered since we modified the slice. This may seem wacky to you, or maybe it doesn't. In any case, it is something you will get used to. For now, just be aware of this. 
#
# ## functions on arrays
#
# Numpy has a wide range of array functons. Here is just a sample.
#
# ### Unary functions
#
# #### absolute value

# + id="ZTy8SO7E4TS5" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 34} outputId="0762b2b4-f8f8-408a-c72b-c9998cdc32f7"
arr = np.array([-2, 12, -25, 0])
arr2 = np.abs(arr)
arr2

# + id="NQhPY0IC4TS9" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 52} outputId="280d189c-0b2c-4eb8-e14b-03fee079dfb9"
arr = np.array([[-2, 12], [-25, 0]])
arr2 = np.abs(arr)
arr2               

# + [markdown] id="cwRGxNtR4TTA" colab_type="text"
# #### square

# + id="BNKQ7oqL4TTA" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 34} outputId="e5a8f52f-7659-4291-c165-649df93b45f7"
arr = np.array([-1, 2, -3, 4])
arr2 = np.square(arr)
arr2

# + [markdown] id="NL9ze1dX4TTD" colab_type="text"
# #### squareroot

# + id="6Dy5i6tH4TTE" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 52} outputId="ad6706a8-0ed2-4e23-ffea-b8f2021208ef"
arr = np.array([[4, 9], [16, 25]])
arr2 = np.sqrt(arr)
arr2

# + [markdown] id="jocFvG4H4TTH" colab_type="text"
# ## Binary functions
#
# #### add /subtract / multiply / divide
#

# + id="yg1S-eRY4TTH" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 52} outputId="e5823686-e18f-4bf5-bc86-2b10edf4459f"
arr1 = np.array([[10, 20], [30, 40]])
arr2 = np.array([[1, 2], [3, 4]])
np.add(arr1, arr2)

# + id="mF8WsRV34TTJ" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 52} outputId="53288484-8154-45a9-a6e0-2e7edd9043e6"
np.subtract(arr1, arr2)

# + id="moQ9RA-M4TTL" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 52} outputId="63a8005a-37ca-4568-ae57-381cce486cd2"
np.multiply(arr1, arr2)

# + id="CT42qcyI4TTN" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 52} outputId="483fe5f0-4bcb-4486-ace0-33872d3c690b"
np.divide(arr1, arr2)

# + [markdown] id="7YaWtsLn4TTP" colab_type="text"
# #### maximum / minimum
#

# + id="8PmZRIZ24TTQ" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 52} outputId="83c59ddb-309b-419e-fb7d-8f43a0e2047e"
arr1 = np.array([[10, 2], [3, 40]])
arr2 = np.array([[1, 20], [30, 4]])
np.maximum(arr1, arr2)

# + [markdown] id="HZxz8Gwg4TTR" colab_type="text"
# #### these are just examples. There are more unary and binary functions

# + [markdown] id="DqNkxaE64TTS" colab_type="text"
# ## Numpy Uber
# lets say I have Uber drivers at various intersections around Austin. I will represent that as a set of x,y coordinates.
#
#  | Driver |xPos | yPos |
#  | :---: | :---: | :---: |
#  | Ann | 4 | 5 |
#  | Clara | 6 | 6 |
#  | Dora | 3 | 1 |
#  | Erica | 9 | 5 |
#  
#  
#  Now I would like to find the closest driver to a customer who is at 6, 3.
#  And to further define *closest* I am going to use what is called **Manhattan Distance**. Roughly put, Manhattan distance is distance if you followed streets. Ann, for example, is two blocks West of our customer and two blocks north. So the Manhattan distance from Ann to our customer is `2+2` or `4`. 
#  
#  First, to make things easy (and because the data in a numpy array must be of the same type), I will represent the x and y positions in one numpy array and the driver names in another:

# + id="bOwnt_ez4TTS" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 86} outputId="be7235ea-7086-4dc9-e257-18b94430440a"
locations = np.array([[4, 5], [6, 6], [3, 1], [9,5]])
locations

# + id="OPUxIJM74TTV" colab_type="code" colab={}
drivers = np.array(["Ann", "Clara", "Dora", "Erica"])

# + [markdown] id="ud-9mlPa4TTY" colab_type="text"
# Our customer is at

# + id="KuWfgrji4TTY" colab_type="code" colab={}
cust = np.array([6, 3])

# + [markdown] id="90d5ltWQ4TTa" colab_type="text"
# now we are going to figure out the distance between each of our drivers and the customer

# + id="D21R_EQw4TTa" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 86} outputId="396fe7b2-ea91-44a3-acc2-f3105af947a0"
xydiff = locations - cust
xydiff

# + [markdown] id="f6V695L94TTg" colab_type="text"
# NOTE: displaying the results with `xydiff` isn't a necessary step. I just like seeing intermediate results.
#
# Ok. now I am goint to sum the absolute values:

# + id="YbR8vtU24TTg" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 34} outputId="728b4065-2614-4099-d57f-21a06b3505f7"
distances =np.abs(xydiff).sum(axis = 1)
distances

# + [markdown] id="Vm25fsDK4TTi" colab_type="text"
# So the output is the array `[4, 3, 5, 5]` which shows that Ann is 4 away from our customer; Clara is 3 away and so on.
#
# Now I am going to sort these using `argsort`:

# + id="-al5gmO04TTi" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 34} outputId="6187e2bf-1ab2-43f6-f268-f92795fbcc0c"
sorted = np.argsort(distances)
sorted

# + [markdown] id="DNDc7Ig94TTk" colab_type="text"
# `argsort` returns an array of sorted indices. So the element at position 1 is the smallest followed by the element at position 0 and so on.
#
# Next, I am going to get the first element of that array (in this case 1) and find the name of the driver at that position in the `drivers` array

# + id="z4jseLfS4TTk" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 35} outputId="d7987f80-5e41-468d-93b6-b3ce6c88d57e"
drivers[sorted[0]]


# + [markdown] id="bjfRcs3W4TTn" colab_type="text"
# <h3 style="color:red">Q2. You Try</h3>
# <span style="color:red">Can you put all the above in a function. that takes 3 arguments, the location array, the array containing the names of the drivers, and the array containing the location of the customer. It should return the name of the closest driver.</span>
#

# + id="REW2RFiT4TTn" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 34} outputId="4f233a5b-b91a-47e0-b7e0-8fab268e40bb"
def findDriver(distanceArr, driversArr, customerArr):
   result = ''
   ### put your code here
   locdiff = distanceArr - customerArr
   abs = np.abs(locdiff).sum(axis=1)
   sort= np.argsort(abs)
   result = drivers[sorted[0]]
   return result
print(findDriver(locations, drivers, cust)) # this should return Clara

# + [markdown] id="ksRjVA4w4TTp" colab_type="text"
# ### CONGRATULATIONS
#
# Even though this is just an intro to Numpy, I am going to throw some math at you. So far we have been looking at a two dimensional example, x and y (or North-South and East-West) and our distance formula for the distance, Dist between Ann, A and Customer C is
#
# $$ DIST_{AC} = |A_x - C_x | + |A_y - C_y | $$
#
# Now I am going to warp this a bit. In this example, each driver is represented by an array (as is the customer) So, Ann is represented by `[1,2]` and the customer by `[3,4]`. So Ann's 0th element is 1 and the customer's 0th element is 3. And, sorry, computer science people start counting at 0 but math people (and all other normal people) start at 1 so we  can rewrite the above formula as:
#
# $$ DIST_{AC} = |A_1 - C_1 | + |A_2 - C_2 | $$
#
# That's the distance formula for Ann and the Customer. We can make the formula by saying the distance between and two people, let's call them *x* and *y* is
#
#
# $$ DIST_{xy} = |x_1 - y_1 | + |x_2 - y_2 | $$
#
# That is the formula for  2 dimensional Manhattan Distance. We can imagine a three dimensional case.  
#
# $$ DIST_{xy} = |x_1 - y_1 | + |x_2 - y_2 | + |x_3 - y_3 | $$
#
# and we can generalize the formula to the n-dimensional case.
#  
# $$ DIST_{xy}=\sum_{i=1}^n |x_i - y_i| $$
#
# Just in time for a five dimensional example:
#
#
# # The Amazing 5D Music example
#
# Guests went into a listening booth and rated the following tunes:
#
# * [<NAME>](https://www.youtube.com/watch?v=pwnefUaKCbc)
# * [Major Lazer - Cold Water](https://www.youtube.com/watch?v=nBtDsQ4fhXY)
# * [<NAME> - Humble & Kind](https://www.youtube.com/watch?v=awzNHuGqoMc)
# * [<NAME> - My Church](https://www.youtube.com/watch?v=ouWQ25O-Mcg)
# * [<NAME>eld - Starving](https://www.youtube.com/watch?v=xwjwCFZpdns)
#
#
# Here are the results:
#
# | Guest  | <NAME>  | Major Lazer  | <NAME>  |  <NAME> | <NAME>| 
# |---|---|---|---|---|---|
# |  Ann | 4  |  5 | 2  |  1 | 3 |
# | Ben  |  3 |  1 |  5 | 4  | 2|
# | Jordyn  | 5  |  5 | 2  | 2  | 3|
# |  Sam | 4 | 1 | 4 | 4 | 1|
# | Hyunseo | 1 | 1 | 5 | 4 | 1 |
# | Ahmed | 4 | 5 | 3 |  3 | 1 |
#
# So Ann, for example, really liked <NAME> and <NAME> but didn't care much for <NAME>.
#
# Let's set up a few numpy arrays.
#

# + id="5Yu4FNGe4TTp" colab_type="code" colab={}
customers = np.array([[4, 5, 2, 1, 3],
                      [3, 1, 5, 4, 2],
                      [5, 5, 2, 2, 3],
                      [4, 1, 4, 4, 1], 
                      [1, 1, 5, 4, 1],
                      [4, 5, 3, 3, 1]])

customerNames = np.array(["Ann", "Ben", 'Jordyn', "Sam", "Hyunseo", "Ahmed"])



# + [markdown] id="zlFYIoux4TTs" colab_type="text"
# Now let's set up a few new customers:

# + id="qFUQO1pI4TTs" colab_type="code" colab={}
mikaela = np.array([3, 2, 4, 5, 4])
brandon = np.array([4, 5, 1, 2, 3])

# + [markdown] id="sWySn6uw4TTu" colab_type="text"
# Now we would like to determine  which of our current customers is closest to Mikaela and which to Brandon.
# <h3 style="color:red">Q3. You Try</h3>
# <span style="color:red">Can you write a function findClosest that takes 3 arguments: customers, customerNames, and an array representing one customer's ratings and returns the name of the closest customer?</span>
#
# Let's break this down a bit.
#
# 1. Which line in the Numpy Uber section above will create a new array which is the result of subtracting the Mikaela array from each row of the customers array resulting in
#
# ```
# array([[ 1,  3, -2, -4, -1],
#        [ 0, -1,  1, -1, -2],
#        [ 2,  3, -2, -3, -1],
#        [ 1, -1,  0, -1, -3],
#        [-2, -1,  1, -1, -3],
#        [ 1,  3, -1, -2, -3]])
#        ```
#

# + id="lxtqsElA4TTu" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 121} outputId="c3ff3803-56d3-4f35-cbed-df778d9653bf"
# TODO
newarr = customers - mikaela
newarr


# + [markdown] id="HSOlrN-44TTw" colab_type="text"
# 2. Which line above will take the array you created and generate a single integer distance for each row representing how far away that row is from Mikaela?  The results will look like:
#
# ```
#     array([11,  5, 11,  6,  8, 10])
# ```

# + id="CAZi6_uk4TTw" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 34} outputId="5b9dcabb-0245-4b4e-a583-b8dca1f6044c"
# TO DO 
newarr = np.abs(newarr)
newarr = np.sum(newarr, axis=1)
newarr

# + [markdown] id="nTbgFrTv4TTx" colab_type="text"
# Finally, we want a sorted array of indices, the zeroth element of that array will be the closest row to Mikaela, the next element will be the next closest and so on. The result should be
#
# ```
# array([1, 3, 4, 5, 0, 2])
# ```
#

# + id="X67C7RuR4TTy" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 34} outputId="3ef180d4-858d-4d6a-ad83-a8c85fd73453"
# TO DO
sort = np.argsort(newarr)
sort


# + [markdown] id="GOm6y4md4TT0" colab_type="text"
# Finally we need the name of the person that is the closest. 

# + id="8-0TbyJA4TT0" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 35} outputId="89808845-916d-4889-9ce8-ded295703c39"
# TO DO
result = customerNames[sort[0]]
result


# + [markdown] id="9a5Hx8mE4TT3" colab_type="text"
# Okay, time to put it all together. Can you combine all the code you wrote above to finish the following function? So x is the new person and we want to find the closest customer to x.

# + id="DDeeSYhh4TT3" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 52} outputId="193aa8d1-30a8-4f47-ac65-d69504d0027d"
def findClosest(customers, customerNames, x):
   # TO DO
   newarr = customers - x
   abs = np.abs(newarr)
   sum = np.sum(abs, axis=1)
   sort = np.argsort(sum)
   result = customerNames[sort[0]]
   return result


print(findClosest(customers, customerNames, mikaela)) # Should print Ben
print(findClosest(customers, customerNames, brandon)) # Should print Ann

# + [markdown] id="psSxCWmM4TT5" colab_type="text"
# ## Numpy Amazon
#
# We are going to start with the same array we did way up above:
#
#  
#  | Drone |xPos | yPos |
#  | :---: | :---: | :---: |
#  | wing_1a | 4 | 5 |
#  | wing_2a | 6 | 6 |
#  | wing_3a | 3 | 1 |
#  | wing_4a | 9 | 5 |
#  
#  But this time, instead of Uber drivers, think of these as positions of [Alphabet's Wing delivery drones](https://wing.com/). 
#  Now we would like to find the closest drone to a customer who is at 7, 1.
#  
# With the previous example we used Manhattan Distance.  With drones, we can compute the distance as the crow flies -- or Euclidean Distance.  We probably learned how to do this way back in 7th grade when we learned the Pythagorean Theorem which states:
#
# $$c^2 = a^2 + b^2$$
#
# Where *c* is the hypotenuse and *a* and *b* are the two other sides. So, if we want to find *c*:
#
# $$c = \sqrt{a^2 + b^2}$$
#
#
# If we want to find the distance between the drone and a customer, *x* and *y* in the formula becomes
#
# $$Dist_{xy} = \sqrt{(x_1-y_1)^2 + (x_2-y_2)^2}$$
#
# and for `wing_1a` who is at `[4,5]` and our customer who is at `[7,1]` then the formula becomes:
#
# $$Dist_{xy} = \sqrt{(x_1-y_1)^2 + (x_2-y_2)^2} = \sqrt{(4-7)^2 + (5-1)^2} =\sqrt{-3^2 + 4^2}  = \sqrt{9 + 16} = \sqrt{25} = 5$$
#
# Sweet!  And to generalize this distance formula:
#
# $$Dist_{xy} = \sqrt{(x_1-y_1)^2 + (x_2-y_2)^2}$$
#
# to n-dimensions:
#
# $$Dist_{xy} = \sum_{i=1}^n{\sqrt{(x_i-y_i)^2}}$$
#
#
#
#
# <h4 style="color:red">Q4. You Try</h3>
# <span style="color:red">Can you write a function euclidean that takes 3 arguments: droneLocation, droneNames, and an array representing one customer's position and returns the name of the closest drone?</span>
#
# First, a helpful hint:
#

# + id="y3fc10pZ4TT5" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 34} outputId="f9f84e56-b86b-4795-f6e2-93576d2449f7"
arr = np.array([-1, 2, -3, 4])
arr2 = np.square(arr)
arr2

# + id="nEj8LchD4TT7" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 35} outputId="47a95a15-e01a-40df-f55a-f62e23bbd502"
locations = np.array([[4, 5], [6, 6], [3, 1], [9,5]])
drivers = np.array(["wing_1a", "wing_2a", "wing_3a", "wing_4a"])
cust = np.array([6, 3])

def euclidean(droneLocation, droneNames, x):
   result = ''
   ### your code here   
   diff = droneLocation - x
   square = np.square(diff)
   sum = np.sum(square, axis=1)
   sqrt = np.sqrt(sum)
   
   sort = np.argsort(sqrt)
   result = droneNames[sort[0]]
   return result
euclidean(locations, drivers, cust) 

# + [markdown] id="cQwC3Iie4TT9" colab_type="text"
# <h4 style="color:red">Q5. You Try</h3>
# <span style="color:red">try your code on the "Amazing 5D Music example. Does it return the same person or a different one?"</span>

# + id="bPVLKKqJ4TT-" colab_type="code" colab={"base_uri": "https://localhost:8080/", "height": 52} outputId="f5911324-8e94-4f7e-890d-5324fb162ca7"
#TBD
customers = np.array([[4, 5, 2, 1, 3],
                      [3, 1, 5, 4, 2],
                      [5, 5, 2, 2, 3],
                      [4, 1, 4, 4, 1], 
                      [1, 1, 5, 4, 1],
                      [4, 5, 3, 3, 1]])
 
customerNames = np.array(["Ann", "Ben", 'Jordyn', "Sam", "Hyunseo", "Ahmed"])
mikaela = np.array([3, 2, 4, 5, 4])
brandon = np.array([4, 5, 1, 2, 3])
print(euclidean(customers, customerNames, mikaela))
print(euclidean(customers, customerNames, brandon))
#The same results

StarcoderdataJupyterScriptsDedupFiltered