Reputation: 10506
I have a data frame of x and y coordinates, and also a classification (A or B) What is the best way to apply an operation on all the consecutive rows where the classification type has been repeated?
Here is an example:
set.seed(1)
n = 9
x = 1:n
y = runif(n)
df = data.frame(x,y,type=sample(c("A","B"),n,replace=TRUE))
Which produces the following:
+---+---+-----------+------+ | | x | y | type | +---+---+-----------+------+ | 1 | 1 | 0.2655087 | A | | 2 | 2 | 0.3721239 | A | | 3 | 3 | 0.5728534 | A | | 4 | 4 | 0.9082078 | B | | 5 | 5 | 0.2016819 | A | | 6 | 6 | 0.8983897 | B | | 7 | 7 | 0.9446753 | A | | 8 | 8 | 0.6607978 | B | | 9 | 9 | 0.6291140 | B | +---+---+-----------+------+
So I want to to a ddply(...)
type orperation, to get the average x and y coordinate when the 'type' classification is repeated across consecutive rows, in the above, rows 1:3 should collapse to 1 row, rows 4:7 would be unaffected and rows 8:9, also collapsing to 1 row, the result should return 6 rows.
Upvotes: 2
Views: 504
Reputation: 8333
A few methods I can think of using Base, dplyr
and data.table
## put into numerical groups
df$grp <- match(df$type, LETTERS)
## use rle to find consecutive groups
nGroups <- length(rle(df$grp)[[1]]) ## returns number of groups
grp <- rep(seq(1,nGroups,1), rle(df$grp)$length)
## put rle groups onto data
df$rle_grp <- grp
## perform calculation
Base R
aggregate(x=df[,c("x","y")], by=list(df$rle_grp), FUN=mean)
# Group.1 x y
#1 1 2.0 0.4034953
#2 2 4.0 0.9082078
#3 3 5.0 0.2016819
#4 4 6.0 0.8983897
#5 5 7.0 0.9446753
#6 6 8.5 0.6449559
dplyr
## using dplyr (you asked for ddply, but I don't use plyr anymore)
library(dplyr)
df %>%
group_by(rle_grp) %>%
summarise(avgX = mean(x),
avgY = mean(y)) %>%
ungroup
# rle_grp avgX avgY
# (dbl) (dbl) (dbl)
#1 1 2.0 0.4034953
#2 2 4.0 0.9082078
#3 3 5.0 0.2016819
#4 4 6.0 0.8983897
#5 5 7.0 0.9446753
#6 6 8.5 0.6449559
data.table
## or using data.table which is my package of choice
library(data.table)
setDT(df)
df[, .(avgX = mean(x), avgY = mean(y)) , by=.(rle_grp)]
# rle_grp avgX avgY
#1: 1 2.0 0.4034953
#2: 2 4.0 0.9082078
#3: 3 5.0 0.2016819
#4: 4 6.0 0.8983897
#5: 5 7.0 0.9446753
#6: 6 8.5 0.6449559
Upvotes: 1
Reputation: 1956
To achieve it with base R only:
changed <- which(c(TRUE, diff(as.integer(df$type)) != 0))
class <- rep(changed, diff(c(changed, nrow(df) + 1)))
df1 <- data.frame(meanX=tapply(df$x, class, mean),
meanY=tapply(df$y, class, mean))
df1
meanX meanY
1 2.0 0.4034953
4 4.0 0.9082078
5 5.0 0.2016819
6 6.0 0.8983897
7 7.0 0.9446753
8 8.5 0.6449559
Upvotes: 1