NOTE: Run all the following code to create the functions prior to creating the figures.

Packages

Install and import the package “rEDM” from the CRAN Repository. CRAN - Package rEDM: Empirical Dynamic Modeling (‘EDM’)

# install this specific version using devtools
require(devtools)
install_version("rEDM", version="1.2.3", repos="http://cran.us.r-project.org")
library(rEDM)

Embedding

Generate an embedding matrix of data based on E, tau, and tp.

# create a function to make an embedding matrix
make_embed <- function(data, E, tau, tp){
  
  # data rows are observations and columns are variables
  data = as.matrix(data)
  
  # calculate sizes
  numSamples = dim(data)[1]
  inputDim = dim(data)[2]
  finalDim = inputDim*(E+1)
  output = array(NA, dim=c(numSamples,finalDim))
  
  # insert the values to be predicted in the first "inputDim" columns
  output[1:numSamples,1:inputDim] <- data[1:numSamples,1:inputDim]
  
  for (i in c(0:(E-1))){
    # shift and insert the input data to create the lags
    output[((i*tau)+1+tp):numSamples,((i+1)*inputDim+1):((i+2)*inputDim)] <-
      data[1:(numSamples-(i*tau)-tp),1:inputDim]
  }
  return(output)
}

Predictions

Use the “s-map” method in the rEDM package to make predictions on the data.

# create a function to make predictions
make_pred <- function(E, matrix, theta, lib, pred){
  
  # create empty vectors for observations/predictions output
  observations_total = {}
  predictions_total = {}
  
  # loop through the embedding matrix and generate predictions using block_lnlp
  for(target in c(1:5)){
    # the optimal exclusion radius is 10
    # tp is 0 because it is already accounted for in the embedding
    output <- block_lnlp(matrix, target_column=target, columns=c(6:((E+1)*5)),
                         exclusion_radius=10, stats_only=FALSE, method="s-map",
                         tp=0, theta=theta, lib=lib, pred=pred)
    
    outputs <- output$model_output[[1]]
    predictions <- outputs$pred
    observations <- outputs$obs
    
    # add observations/predictions to the vectors created above
    observations_total = cbind(observations_total, observations)
    predictions_total = cbind(predictions_total, predictions)
  }
  # combine and return observations and predictions
  new_list = list("obs"=observations_total, "pred"=predictions_total)
  return(new_list)
}

EDM Predictions Error (Whole Worm)

Calculate RMS error based on the observations and the predictions.

# create a function to calculate error
edm_error <- function(pred, observations_total, ew, predictions_total){
  
  # define the length of the predictions and an empty array for error
  range_loop = c(1:(pred[2]-pred[1]+1))
  errors = array(NA, dim=c(length(range_loop),1))
  
  for(t in range_loop){
    # a linear combination of eigenworms will generate body angles
    curr = observations_total[t,]
    pos_obs = array(0, dim=c(100,1))
    for(i in c(1:5)){
      # multiply each coefficient by its corresponding eigenworm
      pos_obs = pos_obs + ew[,i]*as.numeric(curr[i])
    }
    
    curr = predictions_total[t,]
    pos_pred = array(0, dim=c(100,1))
    for(i in c(1:5)){
      # multiply each coefficient by its corresponding eigenworm
      pos_pred = pos_pred + ew[,i]*as.numeric(curr[i])
    }
    
    # calculate the RMS error
    errors[t] = (mean((pos_obs - pos_pred)^2))^(1/2)
  }
  return(errors)
}

EDM Predictions Error (Worm Segments)

Calculate RMS error based on the observations and the predictions for each section of the worm.

# create a function to calculate error
edm_error_section <- function(pred, observations_total, ew, predictions_total){
  
  # define the length of the predictions and an empty array for error
  range_loop = c(1:(pred[2] - pred[1] + 1))
  errors = array(NA, dim=c(length(range_loop),5))
  
  # break the worm body angles into five equal segments
  angles = c(1,20,21,40,41,60,61,80,81,100)
  for(ang in 1:5){
    for(t in range_loop){
      # a linear combination of eigenworms will generate body angles
      curr = observations_total[t,]
      pos_obs = array(0, dim=c(100,1))
      for(i in c(1:5)){
        # multiply each coefficient by its corresponding eigenworm
        pos_obs = pos_obs + ew[,i]*as.numeric(curr[i])
      }
      pos_obs = pos_obs[angles[(ang*2-1)]:angles[(ang*2)]]
      
      curr = predictions_total[t,]
      pos_pred = array(0, dim=c(100,1))
      for(i in c(1:5)){
        # multiply each coefficient by its corresponding eigenworm
        pos_pred = pos_pred + ew[,i]*as.numeric(curr[i])
      }
      pos_pred = pos_pred[angles[(ang*2-1)]:angles[(ang*2)]]
      
      # calculate the RMS error
      errors[t,ang] = (mean((pos_obs - pos_pred)^2))^(1/2)
    }
  }
  return(errors)
}

Constant Predictor Error

Calculate constant predictor error based on one time step earlier.

# create a function to calculate error
cp_error <- function(pred, data, ew){
  
  # define the length of the predictions and an empty array for error
  range_loop = c(1:(pred[2] - pred[1]))
  cp_errors = array(NA, dim=c(length(range_loop),1))
  
  for(t in range_loop){
    # a linear combination of eigenworms will generate body angles
    curr = data[t + pred[1],]
    pos_obs = array(0, dim=c(100,1))
    for(i in c(1:5)){
      # multiply each coefficient by its corresponding eigenworm
      pos_obs = pos_obs + ew[,i]*as.numeric(curr[i])
    }
    
    curr = data[t + pred[1] - 1,]
    pos_pred = array(0, dim=c(100,1))
    for(i in c(1:5)){
      # multiply each coefficient by its corresponding eigenworm
      pos_pred = pos_pred + ew[,i]*as.numeric(curr[i])
    }
    
    # calculate the RMS error
    cp_errors[t] = (mean((pos_obs - pos_pred)^2))^(1/2)
  }
  
  # find the average value of the constant predictor error time series
  cp_mean = mean(cp_errors, na.rm=TRUE)
  
  return(cp_mean)
}

Classifying Forwards/Backwards/Turns

Use thresholds to filter data and classify sections of the time series.

# create a function to classify data
data_class <- function(data, pred){
  
  # thresholds
  delta_thresh = 3
  epsilon_thresh = 0.01
  turns_thresh = 10
  
  # define eigenworms a1, a2, and a3
  a1_before = data[(pred[1]:(pred[2]-2)),1]
  a1 = data[((pred[1]+1):(pred[2]-1)),1]
  a1_after = data[((pred[1]+2):pred[2]),1]
  a2_before = data[(pred[1]:(pred[2]-2)),2]
  a2 = data[((pred[1]+1):(pred[2]-1)),2]
  a2_after = data[((pred[1]+2):pred[2]),2]
  a3 = data[((pred[1]+1):(pred[2]-1)),3]
  
  # estimate phase velocity in the a1,a2 plane
  ang_change = atan(a2_after/a1_after) - atan(a2_before/a1_before)
  change = pmin(abs(ang_change), (pi - abs(ang_change)))
  thresh = which(((a1^2 + a2^2)^(1/2)) > delta_thresh)
  
  # if phase velocity is smaller than pi/2
  place_a = which(abs(ang_change) < (pi - abs(ang_change)))
  new_a = c()
  for (i in 1:length(place_a)){
    # only use these values if they exceed the threshold
    if (place_a[i] %in% thresh){new_a = append(new_a, place_a[i])}
  }
  change[new_a] = change[new_a]*sign(ang_change)[new_a]  # return the same sign
  
  # if phase velocity is larger than pi/2
  place_b = which(abs(ang_change) > (pi - abs(ang_change)))
  new_b = c()
  for (i in 1:length(place_b)){
    # only use these values if they exceed the threshold
    if (place_b[i] %in% thresh){new_b = append(new_b, place_b[i])}
  }
  change[new_b] = change[new_b]*-sign(ang_change)[new_b]  # reverse the sign
  
  # find values that do not meet thresholds
  not_c = c()
  for (i in 1:length(change)){
    if (!(i %in% append(new_a, new_b))){not_c = append(not_c, i)}
  }
  change[not_c] = NA  # set them to NA
  
  # classify forwards, backwards, and turns
  forward = which(change < -epsilon_thresh)
  backward = which(change > epsilon_thresh)
  turns = which(abs(a3) > turns_thresh)
  
  change[turns] = NA  # remove phase velocity at turns
  
  # combine and return forward, backward, turns, and change
  new_list = list("forw"=forward, "back"=backward, "turn"=turns, "chan"=change)
  return(new_list)
}