5

I have written the following function:

asteriks = function(pvalue){
  if(pvalue > 0.05){
    output = "NS"
  }else if (pvalue <=0.05 & pvalue >0.01){
    output = "*"
  }else if (pvalue <=0.01 & pvalue >0.001){
    output = "**"
  }else if (pvalue <=0.001 & pvalue >0.0001){
    output = "***"
  }else if (pvalue <=0.0001){
    output = "****"
  }
  return(output)
}

It works fine when I provide an argument of length 1, but I would like the function to take a vector of length >1 as an input and return a vector of the same length.

example of what I would like to do:

vector_pvals = c(0.1, 0.05, 0.001, 0.0001)
asteriks(vector_pvals)

The output should be a character vector like this:

[1] "NS" "*" "***" "****"

I can achieved this by using the function in a for-loop of course, but I actually want to use it within a dplyr pipe, so it would be nice to be able to just feed it a whole vector. Is the answer to use a for-loop within the function to work one each element one at a time, or is there an easier way?

Annick
  • 51
  • 3

4 Answers4

7

Here you don't need to write your own function. cut does exactly what you're looking for (this is by far the simplest method)

pvalues <- seq(0, 0.1, by = 0.0001)
cut(pvalues,
    breaks = c(-Inf, 0.0001, 0.001, 0.01, 0.05, Inf), 
    include.lowest = TRUE, 
    right = FALSE, 
    labels = c('****', '***', '**', '*', 'NS'))

If you want to make this an exercise in "vectorizing" you can convert your function in several ways, both using ifelse as suggested by another answer, creating an index matching each group or using multiple indexes. ifelse is the simplest of the methods.

Oliver
  • 8,169
  • 3
  • 15
  • 37
3

Use ifelse instead of if. It is designed to be vectorized. Your function could be written as

asteriks = function(pvalue){
  ifelse(pvalue > 0.05, "NS",
  ifelse(pvalue > 0.01, "*",
  ifelse(pvalue > 0.001, "**",
  ifelse(pvalue >0.0001, "***", "****"))))
}

For other functions with more complicated computations where this isn't possible, you can use the Vectorize() function to convert a function to vectorized form (by running a loop internally). For example,

asteriksV <- Vectorize(asteriks)

Now asteriksV would work even with your original definition of asteriks.

user2554330
  • 37,248
  • 4
  • 43
  • 90
  • Thank you, both of these solutions worked perfectly and the `vectorize()` option is great as a general solution, too. I guess the series of nested `ifelse` statements might get confusing if there are many more of them, in which case the solution using `cut` suggested above could be easier. – Annick Jan 06 '22 at 14:42
3

With breaks at powers of 10 strrep:

asteriks <- function(pvalue) {
  ifelse(pvalue > 0.05, "NS", strrep("*", pmin(-log10(pvalue), 4)))
}

asteriks(c(0.1, 0.05, 0.001, 0.0001))
#> [1] "NS"   "*"    "***"  "****"
jblood94
  • 10,340
  • 1
  • 10
  • 15
-1

Just to add up on the exercise of different methods.

asteriks <- function(p) {
  v <- c("*" = 0.05, "**" = 0.01, "***" = 0.001, "****" = 0.0001)
  ifelse(p > max(v), "NS", names(v[v <= p][1]))
}

vector_pvals = c(0.1, 0.05, 0.001, 0.0001)

unlist(lapply(vector_pvals, asteriks))

[1] "NS"   "*"    "***"  "****"
Merijn van Tilborg
  • 5,452
  • 1
  • 7
  • 22