6

In reading R for programmers I saw this function

oddcount <- function(x) {
  k <- 0
  for (n in x) {
    if (n %% 2 == 1) k <- k+1
  }
  return(k)
}

I would prefer to write it in a simpler style (i.e in lisp)

(defn odd-count [xs]
  (count (filter odd? xs)))

I see the function length is equivalent to count and I can write odd? so are there built-in map/filter/remove type functions?

ChrisR
  • 1,227
  • 1
  • 10
  • 17

3 Answers3

12

In R, when you are working with vectors, people often prefer to work on the entire vector at once instead of looping through it (see, for example, this discussion).

In a sense, R does have "built in" filter and reduce functions: the way in which you can select subsets of a vector. They are very handy in R, and there are a few ways to go about it - I'll show you a couple, but you'll pick up more if you read about R and look at other people's code on a site like this. I would also consider looking at ?which and ?'[', which has more examples than I do here.

The first way is simply to select which elements you want. You can use this if you know the indices of the elements you want:

x <- letters[1:10]
> x
 [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j"

If we only want the first five letters, we can write:

x[1:5]
x[c(1,2,3,4,5)] # a more explicit version of the above

You can also select which elements you don't want by using a minus sign, for example:

 x[-(6:10)]

Another way to select elements is by using a boolean vector:

x <- 1:5
selection <- c(FALSE, TRUE, FALSE, TRUE, FALSE)
x[selection]   # only the second and fourth elements will remain

This is important because we can create such a vector by putting a vector in a comparison function:

selection <- (x > 3)
> selection
 [1] FALSE FALSE FALSE  TRUE  TRUE

x[selection]   # select all elements of x greater than 3
x[x > 3]       # a shorthand version of the above

Once again, we can select the opposite of the comparison we use (note that since it is boolean, we use ! and not -):

x[!(x > 3)]    # select all elements less than or equal to 3

If you want to do vector comparisons, you should consider the %in% function. For example:

x <- letters[1:10]
> x %in% c("d", "p", "e", "f", "y")
 [1] FALSE FALSE FALSE  TRUE  TRUE  TRUE FALSE FALSE FALSE FALSE

# Select all elements of x that are also "d", "p", "e", "f", or "y"
x[x %in% c("d", "p", "e", "f", "y")]  
# And to select everything not in that vector:
x[!(x %in% c("d", "p", "e", "f", "y"))]  

The above are only a few examples; I would definitely recommend the documentation. I know this is a long post after you have already accepted an answer, but this sort of thing is very important and understanding it is going to save you a lot of time and pain in the future if you are new to R, so I thought I'd share a couple of ways of doing it with you.

Community
  • 1
  • 1
Edward
  • 5,367
  • 1
  • 20
  • 17
11

A more R way to doing this would be to avoid the for loop, and use vectorization:

oddcount <- function(x) {
  sum(x %% 2)
}

The comparison between x and 2 outputs a vector as x itself is a vector. Sum than calculates the sum of the vector, where TRUE equals 1 and FALSE equals zero. In this way the function calculates the number of odd numbers in the vector.

This already leads to more simple syntax, although for non-vectorization-oriented people the for loop tends to be easier to read. I greatly prefer the vectorized syntax as it is much shorter. I would prefer to use a more descriptive name for x though, e.g. number_vector.

Paul Hiemstra
  • 59,984
  • 12
  • 142
  • 149
  • 1
    In this case it could be even shorter, you don't realy need the `==1` since `%%` will only be returning 0's and 1's. Use `sum(x %% 2)`. This might also be a little bit quicker since you are not generating logicals and ther converting them back to numeric. Some may find the longer version more readable ( and if you were to expand this to looking for numbers that are or are not multiples of something other than 2 then you would need the longer version. – Greg Snow Jul 31 '12 at 15:41
5

You should take a look at the funprog library, which includes map, filter, reduce etc.

Chris Taylor
  • 46,912
  • 15
  • 110
  • 154
  • Thanks, length(Filter(odd_p, xs)) seems to do the job, it seems I was asking the wrong question based on the last answer however :) – ChrisR Jul 31 '12 at 11:44
  • There is nothing wrong with use `funprog`, but using standard R you can get the same shortness in code. – Paul Hiemstra Jul 31 '12 at 11:46
  • That's a good point, @Paul. Trying to force a language that uses one paradigm (vectorized) into another paradigm (functional) often results in needlessly verbose code. – Chris Taylor Jul 31 '12 at 11:49