select the best couple of values from list to obtain a target mean

--Hi,

i'm looking for a method to select the best couple of numeric values from a list to obtain a target mean.
For example if i have a serie like that: c(12,6,8,75,6,2,69,42,13)
How to find the best 2 values to obtain a mean closest to 20.

thank you --

1 Like

Here is some code that I think does what you want. There's probably a faster way to do it, but for a moderate length list this should be fine. (The time is order n^2/2, where n is the length of the list.)

x <- c(12,6,8,75,6,2,69,42,13)
target <- 20
n <- length(x)
oldMiss <- Inf
besti <- NA
bestj <- NA
for (i in 1:(n-1))
  for(j in (i+1):n){
    newMiss <- abs(mean(c(x[i], x[j])) - target)
    if (newMiss < oldMiss){
      besti <- i
      bestj <- j
      oldMiss <- newMiss
    }
  }
cat("Best pair is",x[besti],x[bestj],"\n")

Here is another solution, similar to that of @startz . It has the same complexity, but swaps explicit loops for the outer function, so it might be a wee bit faster.

x <- c(12,6,8,75,6,2,69,42,13)
target <- 20
a <- outer(x, x, FUN = function(i, j) {abs(i + j - 2*target)})
b <- which(a == min(a), arr.ind = TRUE)
besti <- b[1, 1]
bestj <- b[1, 2]
cat("Best pair is",x[besti],x[bestj],"\n")
1 Like

@prubin's code is likely quite a bit faster, since outer() is vectorized. Also, avoiding the if() should help.

1 Like

A small tweak would be to subtract the target from the original array instead of doing it in the function.

1 Like

yes, @prubin's code is fine and elegant. I don't know how the outer function works and what

abs(i + j - 2*target)

do.

i and j are going to be the values of x[i] and x[j]. The mean will be (x[i]+x[j])/2.
So abs((i + j)/2 - target) will be the distance between the mean and the target. Then @prubin has multiplied through by 2 inside the absolute value. That doubles all the distances, but doesn't change which is the smallest distance.

ok i understand now.
but @prudin's code failed with this example:

x <- c(2.5,2.6,2.3)
 target <- 2
 a <- outer(x, x, FUN = function(i, j) {abs(i + j - 2*target)})
 b <- which(a == min(a), arr.ind = TRUE)
besti <- b[1, 1]
bestj <- b[1, 2]
 cat("Best pair is",x[besti],x[bestj],"\n")
Best pair is 2.3 2.3 

whereas your code works fine.

That's a good catch about "edge" conditions. @prubin's code includes all possible pairs of x. In this case, that includes 2.3 as both the i and j value--which isn't what you want. Note that my code made sure i and j were different.

1 Like

yes, you can't use the same number twice.
Actually, that condition is missing from his code.

1 Like

My bad for missing that. The fix is to insert the following line after the assignment to a:

diag(a) <- Inf

That will prevent the subsequent code from using the same observation twice.
The tweak suggested by @Ajackson, if I'm reading it correctly, won't work due to the absolute value function.
The expression abs(i + j - 2*target) creates a score that is the absolute difference between the sum of a pair of entries and double the target. That's exactly twice the difference between the mean of the two entries and the target, so the winner is the same. If you prefer, you can use abs((i + j)/2 - target).
The outer() function takes two arguments (usually vectors, maybe arrays) and applies the function specified in the third argument to every pair of entries from the first two arguments.

1 Like

Alternatively, the following works and still follows @prubin's logic and speed:

x <- c(2.5,2.6,2.3)
target <- 2
firstX <- x[1:(length(x)-1)]
secondX <- x[2:length(x)]
a <- outer(firstX, secondX, FUN = function(i, j) {abs(i + j - 2*target)})
b <- which(a == min(a), arr.ind = TRUE)
besti <- b[1, 1]
bestj <- b[1, 2]

cat("Best pair is",firstX[besti],secondX[bestj],"\n")

1 Like

Yes, it's a combination of his code and yours and it works fine.

Have you tried this version with x <- c(2.5,2.3,2.6)?

Well I checked now. And no it doesn't work :roll_eyes:

My original code works and @prubin's modified code works (and is faster if that matters.)

1 Like