You’re going to use the same data set that you used for the first peer code review session last week:
load("/shared/groups/jrole001/pals0047/data/EMA-EMG_data2.Rda")
It last week’s tutorial, we created an EMA plotting function that represented time as a color gradient:
t1 <- round(data$segments$start[1]*data$SR$EMA)
t2 <- round(data$segments$end[4]*data$SR$EMA)
gradientEMA <- function(x,y,from=c(1,0,0),to=c(0,0,1),pch=1) {
N <- length(x)
mycols <- cbind(seq(from[1],to[1],length.out=N),
seq(from[2],to[2],length.out=N),
seq(from[3],to[3],length.out=N))
plot(x,y,col=rgb(mycols),pch=pch)
}
gradientEMA(data$EMA$TT_x[t1:t2], data$EMA$TT_z[t1:t2], from=c(1,0,1), to=c(0,1,0), pch=19)
N <- length(data$EMA$TT_x[t1:t2])
mycols <- cbind(seq(1,0,length.out=N),
seq(0,1,length.out=N),
seq(1,0,length.out=N))
Although this was a really nice way to represent 3 dimensions on a single 2-dimensional plot, the trajectory of the tongue tip movement is a bit difficult to see here because the dots aren’t connected to each other. What if we want to use a single line of gradient colors instead of separate dots?
We can plot a connected line using the base R plot
function:
plot(data$EMA$TT_x[t1:t2], data$EMA$TT_z[t1:t2], type='l')
However, if we try to add our color gradient vector, the first color is used for all segments of the line:
plot(data$EMA$TT_x[t1:t2], data$EMA$TT_z[t1:t2], type='l', col=rgb(mycols))
R does not have a built-in function to be able to do this… so you are going to create one!
To make your function, you will be using the segments()
function, which plots a line segment from one set of coordinates to
another; run ? segments
to read the description. The key
arguments you will need to understand are x0
and
y0
, which are the starting coordinates, and x1
and y1
, which are the ending coordinates. Also, just like
the functions points()
and lines()
, the
function segments()
can only plot onto an existing
figure.
The basic order of operations should be similar to the following:
Create a blank plot, which the segments will be added onto.
(hint: use the argument type='n'
to set the axes limits to
the data, but without plotting anything)
Create a matrix of color gradients (hint: you did this last week!)
Loop through the data points (hint: the iteration range here is a bit tricky!)
At each iteration of the loop, use segments()
to
plot from one data point to the next, using the corresponding color from
the matrix you created
Before you begin, there is one issue you need to be aware of, which
appears in this portion of the help documentation for the function
rgb()
:
“The colors may be specified by passing a matrix or data frame as argument red, and leaving blue and green missing. In this case the first three columns of red are taken to be the red, green and blue values.”
What this means is that, although you can use an entire matrix of RGB values (like you did last week), if you try to use a single vector of RGB values from the matrix you will get the following error:
rgb(mycols[1,])
## Error in rgb(mycols[1, ]): argument "green" is missing, with no default
Why? Because this vector is not a matrix or a data frame:
class(mycols[1,])
## [1] "numeric"
You can convert the vector to a 3-element matrix, but then you will get a new error:
rgb(as.matrix(mycols[1,]))
## Error in rgb(as.matrix(mycols[1, ])): at least 3 columns needed
So what’s the problem now? The issue is that the
as.matrix()
function creates a 3x1 matrix from the
3-element vector, but the rgb()
function requires a 1x3
matrix.
as.matrix(mycols[1,])
## [,1]
## [1,] 1
## [2,] 0
## [3,] 1
The solution is to transpose the matrix using the
t()
function:
t(as.matrix(mycols[1,]))
## [,1] [,2] [,3]
## [1,] 1 0 1
Transposing the data into a 1x3 matrix will now allow you to generate
a hex color code with the rgb()
function:
rgb(t(as.matrix(mycols[1,])))
## [1] "#FF00FF"
In the end, your function should be able to produce the following plot:
plot_linecols <- function(xx,yy,from=c(1,0,1), to=c(0,1,0)) {
plot(xx,yy,type="n")
N <- length(xx)
mycols <- cbind(seq(from[1],to[1],length.out=N),
seq(from[2],to[2],length.out=N),
seq(from[3],to[3],length.out=N))
for (obs in 1:(N-1)) {
segments(xx[obs],yy[obs],xx[obs+1],yy[obs+1],
col=rgb(t(as.matrix(mycols[obs,]))))
}
}
t1 <- round(data$segments$start[1]*data$SR$EMA)
t2 <- round(data$segments$end[4]*data$SR$EMA)
plot_linecols(data$EMA$TT_x[t1:t2],
data$EMA$TT_z[t1:t2],
from=c(1,0,1),
to=c(0,1,0)
)
The advantage of the line plot you just made is that you can now easily follow the trajectory of the tongue tip movement. However, an advantage of the previous dot plot was that you could easily see how quickly the tongue tip was moving at any given point in time based on how far apart one dot was from the next. Ideally, we want a plot that will allow us to do both.
For this second exercise, your goal is to add two optional arguments and a return condition:
Add an argument for the lwd
plotting parameter, with
a default value of 1
. If the function is run with any other
value given for this argument, the value should be passed to the
segments()
function.
Add an argument called dots
that has a default
NULL
value. If a number in the range 0-25 is given, the
value should be passed as the pch
argument (see
examples here) to a points()
function to change the
plotting shape. This points()
function should only executed
if a value is provided for the dots
argument.
Break the function and don’t plot anything if the value for
dots
falls outside the range 0-25
plot_linecols <- function(xx, yy, from=c(1,0,1), to=c(0,1,0), lwd=1, dots=NULL) {
if(!(is.null(dots))){
if (dots < 0 | dots > 25){
return("The value for 'dots' must be in the range 0-25!")
}
}
plot(xx,yy,type="n")
N <- length(xx)
mycols <- cbind(seq(from[1],to[1],length.out=N),
seq(from[2],to[2],length.out=N),
seq(from[3],to[3],length.out=N))
if(!(is.null(dots))){
points(xx,yy,col=rgb(mycols),pch=dots)
}
for (obs in 1:(N-1)) {
segments(xx[obs],yy[obs],xx[obs+1],yy[obs+1],col=rgb(t(as.matrix(mycols[obs,]))),lwd=lwd)
}
}
In the end, your new function should produce the following plot if it
is run without specifying values for the arguments lwd
and
dots
(hint: this should be the same plot
from Exercise #1):
plot_linecols(data$EMA$TT_x[t1:t2],
data$EMA$TT_z[t1:t2],
from=c(1,0,1),
to=c(0,1,0))
But it should also produce the following plot if it is run with, for
example, a value of 3
for lwd
and
19
for dots
:
plot_linecols(data$EMA$TT_x[t1:t2],
data$EMA$TT_z[t1:t2],
from=c(1,0,1),
to=c(0,1,0),
lwd=3,
dots=19)
And if the function is run with a dots
value that is
outside of the range 0-25, nothing should be plotted and the following
message should be printed instead:
plot_linecols(data$EMA$TT_x[t1:t2],
data$EMA$TT_z[t1:t2],
from=c(1,0,1),
to=c(0,1,0),
lwd=3,
dots=30)
## [1] "The value for 'dots' must be in the range 0-25!"