Fast canonical indexing of elements in array
First, why return a vector of characters when the output data is clearly about integers? Instead of initializing

output &lt;- input


i.e., a vector of the same class as input (character), you should have used:

output &lt;- integer(length(input))


to pre-allocate a vector of integers. Integers use less memory; you will also save time avoiding unnecessary conversions from integer to character.

Second, grep is for regular expression matching. When you do grep("a", x), you are checking if x contains an "a" which is not the same as asking if x is exactly "a". In your case, you want exact equality so having used == in output[input == labels[i]] &lt;- i would have been more appropriate. Even better, there is the match function. It is vectorized so you can avoid the for loop and just do:

canonical2 &lt;- function(input){
   labels &lt;- unique(input)
   match(input, labels)
}


Last, you could look into factors. Your code is equivalent to doing:

canonical3 &lt;- function(input) {
   labels &lt;- unique(input)
   as.integer(factor(input, levels = labels))
}


Comparing speeds:

library(microbenchmark)
input &lt;- sample(letters, 1e4, replace = TRUE)

identical(as.integer(canonical(input)), canonical2(input))
# [1] TRUE
identical(canonical2(input), canonical3(input))
# [1] TRUE

microbenchmark(canonical(input), canonical2(input), canonical3(input))

# Unit: microseconds
#               expr       min         lq     median         uq       max neval
 #  canonical(input) 47669.384 49870.0695 51615.3475 53604.0700 65261.986   100
#  canonical2(input)   492.921   529.9210   589.2805   635.7925   966.853   100
#  canonical3(input)   526.412   582.8405   638.5455   718.9310  3821.143   100