#!/usr/bin/env python
# coding: utf-8
# ## numpy broadcasting is like "sharing" a smaller array for the bigger array
# - An array sharing a number
# - Regression paramters/weights sharing 1 intercept
# - Image data classification 2-D array of weights sharing 1-D array of intercept weights
#
#
# ### Remember:
#
#
# 1. shape (3,) is treated as (1,3)
#
# 2. shape column sizes must match in order to broadcast
#
# 3. any ufunc can be broadcasted, not just +-
# In[1]:
import numpy as np
#
#
# ## [Broadcasting 1 number](#Broadcasting1Number)
#
# ## [Broadcasting 1D array](#Broadcasting1D)
#
# ## [Broadcast 2 ways](#two-way)
#
# ## [Examples](#examples)
#
# ***
#
# ## Broadcasting 1 number
# In[2]:
a = np.arange(10)
a
# In[3]:
a +10
# In[4]:
a = np.arange(15).reshape(3,5)
# In[5]:
a + 1000
#
# ## Broadcasting 1D array
# In[6]:
a = np.arange(3)
a
# In[7]:
b = np.eye(3)
b
# In[8]:
a + b
# In[9]:
b = np.ones((2,3), int)
# In[10]:
a + b
#
# Won't work: when column sizes don't match unless one of them has only 1 column! Column sizes must match! (3,) is treated as (1,3) when broadcasting. (6,) is treated as (1,6)
#
# In[11]:
b = np.arange(10)
b
# In[12]:
a + b
# In[ ]:
# even though 6 is divisable by 3, a refuses to be shared :(
b = np.arange(6)
a + b
# In[ ]:
# as soon as we reshape b to have the same column size as a, it works.
a + b.reshape(2,3)
#
# ## Broadcast two ways
# In[ ]:
a = np.arange(3)
a.shape
# In[ ]:
a
# In[ ]:
b = np.arange(3)[:, np.newaxis]
b.shape
# In[ ]:
b
# In[ ]:
# it is working because b has 1 column
a + b
#
# ## Examples
# ### Ex. 1 long - wide
# - long duplicates itself horizontally to match wide's width
# - wide duplicates itself vertically to match long's width
# In[ ]:
X = np.ones((2,1), dtype=int)
X
# In[ ]:
Y = np.ones((1,2), dtype=int)
Y
# In[ ]:
X - Y
# ### Ex. 2 long - wide
# - long duplicates itself horizontally to match wide's width
# - wide duplicates itself vertically to match long's width
# In[ ]:
X = np.full((2,1),2)
X
# In[ ]:
Y = np.full((1,2),1)
Y
# In[ ]:
X - Y
# ### Ex. 3 long - wide
# - long duplicates itself horizontally to match wide's width
# - wide duplicates itself vertically to match long's width
# In[ ]:
X = np. array([[1],[2]])
X
# In[ ]:
Y = np.array([[2, 1]])
Y
# In[ ]:
X -Y
# ## Tricking it into doing something with evey row
# If we think of each row is a point on 2-D space (like a sheet of paper), if we want to get its distance from all other points, including itself,which we called X here,
#
# then we reshape a copy of it into 3-D space, which we call Y. So when we take the difference between them, X will be duplicated along the 3rd dimension.
#
# The trick is that we do not reshape Y in (2,2,1). Rather, we reshape Y in (2,1,2).
#
# In the first 2D space, X is (2,2) whereas Y is (2,1). So Y has to duplicate itself to become (2,2).
#
# In the last dimension, X has to duplicate itself for Y.
# In[ ]:
X = np.array([[1,0],
[2,1]])
X
# In[ ]:
Y = X.reshape(2,1,2)
Y
# In[ ]:
#[[0,0] ,[-1,-1]] = [[1, 0]] - [[1, 0],[2, 1]]
#[[ 1, 1],[ 0, 0]]] = [[2, 1]] - [[1, 0],[2, 1]]
Y-X
# #### Let's check to see if we can replicate what numpy did
# In[ ]:
np.array([[1, 0]]) - np.array([[1, 0],[2, 1]])
# In[ ]:
np.array([[2, 1]]) - np.array([[1, 0],[2, 1]])
# In[ ]:
np.hstack((np.array([[1, 0]]) - np.array([[1, 0],[2, 1]]), np.array([[2, 1]])) - np.array([[1, 0],[2, 1]]) )