views:

96

answers:

3

I would like to apply some function on each row of a dataframe in R.

The function can return a single-row dataframe or nothing (I guess 'return ()' return nothing?).

I would like to apply this function on each of the rows of a given dataframe, and get the resulting dataframe (which is possibly shorter, i.e. has less rows, than the original one).

For example, if the original dataframe is something like:

id size name
1  100  dave
2  200  sarah
3  50   ben

And the function I'm using gets a row n the dataframe (i.e. a single-row dataframe), returns it as-is if the name rhymes with "brave", otherwise returns null, then the result should be:

id size name
1  100  dave

This example actually refers to filtering a dataframe, and I would love to get both an answer specific to this kind of task but also to a more general case when even the result of the helper function (the one that operates on a single row) may be an arbitrary data frame with a single row. Please note than even in the case of filtering, I would like to use some sophisticated logic (not something simple like $size>100, but a more complex condition that is checked by a function, let's say boo(single_row_df).

P.s. What I have done so far in these cases is to use apply(df, MARGIN=1) then do.call(rbind ...) but I think it give me some trouble when my dataframe only has a single row (I get Error in do.call(rbind, filterd) : second argument must be a list)

UPDATE

Following Stephen reply I did the following:

ranges.filter <- function(ranges,boo) {
    subset(x=ranges,subset=!any(boo[start:end]))
}

I then call ranges.filter with some ranges dataframe that looks like this:

start end
100   200
250   400
698   1520
1988  2147
...

and some boolean vector

(TRUE,FALSE,TRUE,TRUE,TRUE,...)

I want to filter out any ranges that contain a TRUE value from the boolean vector. For example, the first range 100 .. 200 will be left in the data frame iff the boolean vector is FALSE in positions 100 .. 200.

This seems to do the work, but I get a warning saying numerical expression has 53 elements: only the first used.

A: 

It sounds like you want to use subset:

subset(orig.df,grepl("ave",name))

The second argument evaluates to a logical expression that determines which rows are kept. You can make this expression use values from as many columns as you want, eg grepl("ave",name) & size>50

James
+1  A: 

You may have to use lapply instead of apply to force the result to be a list.

> rhymesWithBrave <- function(x) substring(x,nchar(x)-2) =="ave"
> do.call(rbind,lapply(1:nrow(dfr),function(i,dfr)
+                      if(rhymesWithBrave(dfr[i,"name"])) dfr[i,] else NULL,
+                      dfr))
  id size name
1  1  100 dave

But in this case, subset would be more appropriate:

> subset(dfr,rhymesWithBrave(name))
  id size name
1  1  100 dave

If you want to perform additional transformations before returning the result, you can go back to the lapply approach above:

> add100tosize <- function(x) within(x,size <- size+100)
> do.call(rbind,lapply(1:nrow(dfr),function(i,dfr)
+                      if(rhymesWithBrave(dfr[i,"name"])) add100tosize(dfr[i,])
+                      else NULL,dfr))
  id size name
1  1  200 dave

Or, in this simple case, apply the function to the output of subset.

> add100tosize(subset(dfr,rhymesWithBrave(name)))
  id size name
1  1  200 dave

UPDATE:

To select rows that do not fall between start and end, you might construct a different function (note: when summing result of boolean/logical vectors, TRUE values are converted to 1s and FALSE values are converted to 0s)

test <- function(x)
  rowSums(mapply(function(start,end,x) x >= start & x <= end,
                 start=c(100,250,698,1988),
                 end=c(200,400,1520,2147))) == 0

subset(dfr,test(size))
Stephen
+1 Thanks, please see update.
David B
+1  A: 

For the more general case of processing a dataframe, get the plyr package from CRAN and look at the ddply function, for example.

install.packages(plyr)
library(plyr)
help(ddply)

Does what you want without masses of fiddling.

For example...

> d
    x          y           z xx
1   1 0.68434946 0.643786918  8
2   2 0.64429292 0.231382912  5
3   3 0.15106083 0.307459540  3
4   4 0.65725669 0.553340712  5
5   5 0.02981373 0.736611949  4
6   6 0.83895251 0.845043443  4
7   7 0.22788855 0.606439470  4
8   8 0.88663285 0.048965094  9
9   9 0.44768780 0.009275935  9
10 10 0.23954606 0.356021488  4

We want to compute the mean and sd of x within groups defined by "xx":

> ddply(d,"xx",function(r){data.frame(mean=mean(r$x),sd=sd(r$x))})
  xx mean        sd
1  3  3.0        NA
2  4  7.0 2.1602469
3  5  3.0 1.4142136
4  8  1.0        NA
5  9  8.5 0.7071068

And it gracefully handles all the nasty edge cases that sometimes catch you out.

Spacedman
could you explain how to use it? AFAICT, it works on columns, not rows.
David B
There's lots of documentation for plyr available from the help in the package itself or elsewhere. The ddply function takes a dataframe, a grouping variable, and a function; it splits the dataframe by the grouping variable and calls the function with each split. The result is then made back into a data frame.
Spacedman
the help is actually very short. how can I split the dataframe into rows? do I have to add a dummy column with unique id?
David B