Reputation: 2534
I am using cut
to divide my data into bins, which gives the resulting bin as something like (x1,x2]
. Can anyone tell me how I might make a new column that expresses these bins as the midpoint of the bin? For example, with the following dataframe:
structure(list(x = c(1L, 4L, 6L, 7L, 8L, 9L, 12L, 18L, 19L),
y = 1:9), .Names = c("x", "y"), class = "data.frame", row.names = c(NA,
-9L))
I can use
test$xRange <- cut(test$x, breaks=seq(0, 20, 5))
to give
x y xRange
1 1 1 (0,5]
2 4 2 (0,5]
3 6 3 (5,10]
4 7 4 (5,10]
5 8 5 (5,10]
6 9 6 (5,10]
7 12 7 (10,15]
8 18 8 (15,20]
9 19 9 (15,20]
But the result I need should instead look like:
x y xRange xMidpoint
1 1 1 (0,5] 2.5
2 4 2 (0,5] 2.5
3 6 3 (5,10] 7.5
4 7 4 (5,10] 7.5
5 8 5 (5,10] 7.5
6 9 6 (5,10] 7.5
7 12 7 (10,15] 12.5
8 18 8 (15,20] 17.5
9 19 9 (15,20] 17.5
I've done some searching, and came upon a similar question at divide a range of values in bins of equal length: cut vs cut2, which gives a solution as
cut2 <- function(x, breaks) {
r <- range(x)
b <- seq(r[1], r[2], length=2*breaks+1)
brk <- b[0:breaks*2+1]
mid <- b[1:breaks*2]
brk[1] <- brk[1]-0.01
k <- cut(x, breaks=brk, labels=FALSE)
mid[k]
}
But when I try this on my case, using
test$xMidpoint <- cut2(test$x, 5)
it does not return the correct midpoint. Perhaps I am entering the breaks incorrectly in cut2
? Can anyone tell me what I'm doing incorrectly?
Upvotes: 14
Views: 6229
Reputation: 994
An alternative way of calculating midpoints regardless of how you specify the breaks in "cut" function (i.e. regardless of wether you supply a vector of breakpoints or a number of bins) is using the label text that the cut function supplies.
get_midpoint <- function(cut_label) {
mean(as.numeric(unlist(strsplit(gsub("\\(|\\)|\\[|\\]", "", as.character(cut_label)), ","))))
}
test$xMidpoint <- sapply(test$xRange, get_midpoint)
Note that this requires the "labels" argument in the cut function to be set to TRUE.
Upvotes: 4
Reputation: 860
I know this is a really old question, but this may help future googlers. I wrote a function that I called midcut that cuts the data and provides me with the midpoint of the bin.
midcut<-function(x,from,to,by){
## cut the data into bins...
x=cut(x,seq(from,to,by),include.lowest=T)
## make a named vector of the midpoints, names=binnames
vec=seq(from+by/2,to-by/2,by)
names(vec)=levels(x)
## use the vector to map the names of the bins to the midpoint values
unname(vec[x])
}
example
test$midpoint=midcut(test$x,0,20,5)
> test
x y xRange midpoint
1 1 1 (0,5] 2.5
2 4 2 (0,5] 2.5
3 6 3 (5,10] 7.5
4 7 4 (5,10] 7.5
5 8 5 (5,10] 7.5
6 9 6 (5,10] 7.5
7 12 7 (10,15] 12.5
8 18 8 (15,20] 17.5
9 19 9 (15,20] 17.5
Upvotes: 4
Reputation: 13122
Unless I miss something, something like this looks valid:
brks = seq(0, 20, 5)
ints = findInterval(test$x, brks, all.inside = T)
#mapply(function(x, y) (x + y) / 2, brks[ints], brks[ints + 1]) #which is ridiculous
#[1] 2.5 2.5 7.5 7.5 7.5 7.5 12.5 17.5 17.5
(brks[ints] + brks[ints + 1]) / 2 #as sgibb noted
#[1] 2.5 2.5 7.5 7.5 7.5 7.5 12.5 17.5 17.5
(head(brks, -1) + diff(brks) / 2)[ints] #or using thelatemail's idea from the comments
#[1] 2.5 2.5 7.5 7.5 7.5 7.5 12.5 17.5 17.5
Upvotes: 8