One of the most useful applications for linear algebra in data science is image manipulation. We often need to compress, expand, warp, skew, etc. images. To do so, we left multiply a transformation matrix by each of the point vectors.
For this assignment, build the first letters for both your first and last name using point plots in R.
Then, write R code that will left multiply (%>%) a square matrix (x) against each of the vectors of points (y). Initially, that square matrix will be the Identity matrix.
Use a loop that changes the transformation matrix incrementally to demonstrate 1) shear, 2) scaling, 3) rotation , and 4) projection in animated fashion.
The gifski
library allows us to turn a series of plots
into a GIF. A note about implementation, we’ve added the following code
to the top of our code blocks that we wanted to see animated as a GIF:
{r, animation.hook = 'gifski'}
library("gifski")
Here we build the letters, ‘P’ & ‘K’.
Every line needs an x and a y entry. Think of each line in terms of a
box on the x-y plane where you specify the beginning and ending value of
X in the box for the x entry and the beginning and ending value of Y in
the box for the y entry. You also have to specify the length which is
the number of dots (actually small circles) drawn between those two
points. The number of dots must be consistent between each x and y
entry. Vertical lines only specify the one x-coordinate value using the
rep
function. Horizontal lines only specify the one
y-coordinate using the rep
function.
I had the benefit of reviewing Diana Plunkett’s work to learn how to make these lines. rpubs.com/dplunkett/996546
lo <- 20
x=c(rep(0,lo),
seq(0,.5,length.out=lo/2),
seq(0,.5,length.out=lo/2),
rep(.5,lo/2),
rep(.8,lo),
seq(.8,1.3,length.out=lo),
seq(1,1.3,length.out=lo/2))
y=c(seq(0,2,length.out=lo),
rep(1,lo/2),
rep(2,lo/2),
seq(1,2,length.out=lo/2),
seq(0,2,length.out=lo),
seq(.7,2,length.out=lo),
seq(1,0,length.out=lo/2))
z=rbind(x,y)
plot(y~x, xlim=c(-2,4), ylim=c(-3,5))
In all of the image translations below, we are multiplying the x, y-coordinates of a dot in an image by a matrix so that it has new x, y-coordinates.
We intentionally used a low number of vector dots to represent the lines so that the individual
I wasn’t able to independently originate these concepts. I tried for hours, cooked myself a steak and garlic mushroom lunch to fend off despair, used right-nostril ayurvedic breathing to stimulate my left-brain. But nothing. I ended up borrowing generously from Catherine Cho’s work here: rpubs.com/catcho1632/996294.
To compensate for the generous borrowing, I’ve doubled the examples for each transformation type to demonstrate an ability to use the concepts and tried to format and explain in a way that would teach someone if they referred to this.
This is a transformation of all points where for each one, one coordinate stays fixed and the other is shifted in portion to how far away it is.
With x-axis shear we are magnifying each dot’s x-coordinate, the y-coordinate remains unchanged, based on the magnitude of the dot’s y-coordinate. Notice how the x-coordinate is changing in the GIF below for each dot but the y-coordinate stays the same.
Note that this matrix is the identity matrix with a 1 in the bottom-left element, corresponding to the x-coordinate. In the animation below, that 1 is replaced with incrementally larger numbers.
\[ XShear:\ \left[ {\begin{array}{cc} 1 & 0 \\ 1 & 1 \\ \end{array} } \right] \]
for (i in seq(0,1.5,length.out=10)) {
z_xshear <- apply(z,2,function(x) x %*% matrix(c(1,i,0,1),ncol=2))
plot(z_xshear[2,]~z_xshear[1,], xlim=c(-2,4), ylim=c(-3,5), col='black', xlab="x-axis", ylab="y-axis", main="X-Axis Shear")
}
With y-axis shear we are magnifying each dot’s y-coordinate, the x-coordinate remains unchanged, based on the magnitude of the dot’s x-coordinate. Notice how the y-coordinate is changing in the GIF below for each dot but the x-coordinate stays the same.
Note that this matrix is the identity matrix with a 1 in the top-right element, corresponding to the y-coordinate. In the animation below, that 1 is replaced with incrementally larger numbers.
\[ YShear:\ \left[ {\begin{array}{cc} 1 & 1 \\ 0 & 1 \\ \end{array} } \right] \]
for (i in seq(0,2,length.out=10)) {
z_yshear <- apply(z,2,function(x) x %*% matrix(c(1,0,i,1),ncol=2))
plot(z_yshear[2,]~z_yshear[1,], xlim=c(-2,4), ylim=c(-3,5), col='black', xlab="x-axis", ylab="y-axis", main="Y-Axis Shear")
}
This is a transformation of all points where both coordinates are magnified in the same proportion so that the image stays fixed, it just gets bigger or smaller.
Note that if a
in the matrix below were 1, this would be
the identity matrix. In the animation below, a is incrementally larger
numbers starting with 1.
\[ Scale:\ \left[ {\begin{array}{cc} a & 0 \\ 0 & a \\ \end{array} } \right] \]
for(i in seq(1,1.5,length.out = 10)){
z_scale_up<-apply(z,2,function(x) x %*% matrix(c(2*i,0,0,2*i),ncol=2))
plot(z_scale_up[2,]~z_scale_up[1,],xlim=c(-2,4),ylim=c(-3,5),col='black', xlab="x-axis", ylab="y-axis", main="Increase Scaling")
}
Here a is being replaced with incrementally smaller fractions so we see the letters shrink in the GIF.
\[ Scale:\ \left[ {\begin{array}{cc} a & 0 \\ 0 & a \\ \end{array} } \right] \]
for(i in seq(1,2,length.out = 10)){
z_scale_down<-apply(z,2,function(x) x %*% matrix(c(1/i,0,0,1/i),ncol=2))
plot(z_scale_down[2,]~z_scale_down[1,],xlim=c(-2,4),ylim=c(-3,5),col='black', xlab="x-axis", ylab="y-axis", main="Decrease Scaling")
}
If you think of each dot in our letters as a vector from (0,0) then the rotational matrix rotates all of the vectors by the same angle as measured from (0,0).
While the rotations are true rotations, we’ve had to adjust the size of these two plots so that we have a more accurate one-to-one correspondence between the x and y-axis unit length when represented on the screen so that the true rotation is evident.
Here is the matrix for Counterclockwise Rotation. If you imagine a unit-circle you can work out how the sines and cosines work together to rotate the vectors.
\[ Counterclockwise \ Rotation:\ \left[ {\begin{array}{cc} cos(a) & sin(a) \\ -sin(a) & cos(a) \\ \end{array} } \right] \]
for(i in seq(0,2*pi,length.out = 16)){
z_rotate_counter <- apply(z,2,function(x) x %*% matrix(c(cos(i),-sin(i),sin(i),cos(i)),ncol=2))
plot(z_rotate_counter[2,]~z_rotate_counter[1,],xlim=c(-5,5),ylim=c(-3,3),col='black', xlab="x-axis", ylab="y-axis", main="Counterclockwise Rotation")
}
Here is the matrix for Clockwise Rotation. If you imagine a unit-circle you can work out how the sines and cosines work together to rotate the vectors.
\[ Clockwise \ Rotation:\ \left[ {\begin{array}{cc} cos(a) & -sin(a) \\ sin(a) & cos(a) \\ \end{array} } \right] \]
for(i in seq(0,2*pi,length.out = 16)){
z_rotate_clockwise <- apply(z,2,function(x) x %*% matrix(c(cos(i),sin(i),-sin(i),cos(i)),nrow=2,ncol=2))
plot(z_rotate_clockwise[2,]~z_rotate_clockwise[1,],xlim=c(-5,5),ylim=c(-3,3),col='black', xlab="x-axis", ylab="y-axis", main="Clockwise Rotation")
}
When you project a vector, it always reduces the dimension of the vector. In this case we are projecting the vectors from two dimensions onto one.
Projection onto the x-axis occurs when we replace the y-coordinate with zero which can be accomplished with the following matrix. As such our animation is only two animations: start and finish. Incremental steps would be a form of single-axis scaling.
\[ XAxis \ Projection:\ \left[ {\begin{array}{cc} 1 & 0 \\ 0 & 0 \\ \end{array} } \right] \]
for (i in 0:1) {
z_project_x <- apply(z,2,function(x) x %*% matrix(c(1,0,0,(1-i)),nrow=2,ncol=2))
plot(z_project_x[2,]~z_project_x[1,], xlim=c(-2,4), ylim=c(-3,5), col='black', xlab="x-axis", ylab="y-axis", main="X-Axis Projection")
}
Projection onto the y-axis occurs when we replace the x-coordinate with zero which can be accomplished with the following matrix. As such our animation is only two animations: start and finish. Incremental steps would be a form of single-axis scaling.
\[ YAxis \ Projection:\ \left[ {\begin{array}{cc} 0 & 0 \\ 0 & 1 \\ \end{array} } \right] \]
for (i in 0:1) {
z_project_y <- apply(z,2,function(x) x %*% matrix(c((1-i),0,0,1),nrow=2,ncol=2))
plot(z_project_y[2,]~z_project_y[1,], xlim=c(-2,4), ylim=c(-3,5), col='black', xlab="x-axis", ylab="y-axis", main="Y-Axis Projection")
}