Getting NA/NaN error message in R but no missing values

Viewed 220

I am trying to write a function for calculating surprisal for each new value in a list. Here is a dummy example:

x <- c('A','A','C','D','A','A','D','A','B','A','A')

surprise <- function(x){
  sur <-c()
  for (i in x){
    num <- sum(x[1:i] == x[i])
    denom <- length(x[1:i])
    sur[i] <- -log2((num)/(denom-1+4))
  }}

My for loop yields this error, even though if I manually insert values for i, everything seems to work:

Error in 1:i : NA/NaN argument In addition: Warning message: In 1:i : NAs introduced by coercion

I also know there is probably a way to write this without for loops - any suggestions welcome!

1 Answers

Fixing your Loop

As @stefan pointed out, the error is caused by looping over the elements of x, and not their indices. Your code can be rephrased to the following in order to fix this:

x <- c('A','A','C','D','A','A','D','A','B','A','A')

surprise <- function(x){
  sur <-c()
    for (i in 1:length(x)){
      num <- sum(x[1:i] == x[i])
      denom <- length(x[1:i])
      sur[i] <- -log2((num)/(denom-1+4))
    }
  return(sur)
}

surprise(x)

Output:

 [1] 2.000000 1.321928 2.584963 2.807355 1.415037 1.169925 2.321928 1.137504 3.584963 1.115477 1.000000

Alternatives to a Loop

As you mention in your question, you can also write this in a few ways without a loop. Here are a couple of suggestions using map_dbl from {purrr}.

Loop-less Option 1

You could rephrase your function to calculate the relative position of each element in x one at a time, rather than all at once. Do this by making i an argument, as below. You can then apply this function to each i of x using map_dbl.

library(purrr)

surprise <- function(x, i) {
  num <- sum(x[1:i] == x[i])
  denom <- length(x[1:i])
  return(-log2(num/(denom-1+4)))
}

map_dbl(1:length(x), ~ surprise(x, .))

Loop-less Option 2

Alternatively, you could just scrap the user defined function and do your entire calculation inside map_dbl. This is a little less readable, but gives the same output as your loop function in one line.

map_dbl(1:length(x), ~ -log2(sum(x[1:.] == x[.]) / (length(x[1:.]) - 1 + 4)))
Related