0
log10scaleround = function(x) {
    v = log10(c(1, 2, 5, 10))
    lx = log10(x)
    10^(floor(lx) + vapply(lx %% 1, function(r) v[which.min(abs(v - r))], 0))
}
log10scaleround(c(0.2589254, 20.5671765))
[1]  0.2 20.0

The above function can round numbers in log10 scale to numbers like .1, .2, .5, 1, 2, 5, 10, 20, 50, 100.

Given a boundary like 0.2, 20.0, I want to fill in numbers in between like .5, 1, 2, 5, 10.

That is, for input like 0.2589254, 20.5671765, I want the output be c(.2, .5, 1, 2, 5, 10, 20).

I could use a for-loop to solve this problem. But it is not efficient. How to program this efficiently in R?

user1424739
  • 11,937
  • 17
  • 63
  • 152
  • 1
    You will need to make it work for any input number pairs. You should not hard code numbers like this. – user1424739 Apr 29 '22 at 16:29
  • Could you clarify why `0.2589254` should be `0.2` and not `0.3` (same for 20.5671765 being 20, not 21?)? I have a function that may work but need some clarity on these values – jpsmith Apr 29 '22 at 16:42
  • It is defined by function `log10scaleround()`. The numbers to be added in between should be the numbers that could be returned by log10scaleround(). In the example, ..5, 1, 2, 5, 10 can be returned by some input to `log10scaleround()`. But 21 and .3 can not be returned by `log10scaleround()`. – user1424739 Apr 29 '22 at 17:08
  • Just occurred to me that `scales::breaks_log` nearly does what you want so the code from there might be a good starting point – George Savva Apr 29 '22 at 20:07

2 Answers2

0

I think all that was needed was usage of the outer product operator.

log10scaleround <- function(x){
  logrange <- floor(log10(x))
  fill <- c(c(1,2,5,10) %o% 10^(logrange[1]:logrange[2]))
  fill[fill >= x[1] & fill <= x[2]]
}

Which yields:

> log10scaleround(c(0.2589254, 20.5671765))
[1]  0.5  1.0  1.0  2.0  5.0 10.0 10.0 20.0

As previously stated by @jpsmith, the returned vector should not contain 0.2. If your use case does not conform to that, then removing the fill >= x[1] should sort that right out.

Edit 1

I think this may give you what you want? You'll have to let me know.

floor_dec <- function(x, sigDig=1) {
  mostSig = ceiling(log10(abs(x)))
  floor(x*10^(sigDig-mostSig))*10^-(sigDig-mostSig)
}
log10scaleround <- function(x){
  logrange <- floor(log10(x))
  fill <- c(c(1,2,5,10) %o% 10^(logrange[1]:logrange[2]))
  fill[fill >= floor_dec(x[1]) & fill <= x[2]]
}

Which gives:

> log10scaleround(c(0.2589254, 20.5671765))
[1]  0.2  0.5  1.0  1.0  2.0  5.0 10.0 10.0 20.0

Note that floor_dec() came from @Jeff in his answer to this question.

philiptomk
  • 768
  • 3
  • 11
  • This does not work properly for all cases. My original function returns .1 when the input is .12. But your version does not. `R> log10scaleround(c(0.12, 20.5671765)) [1] 0.2 0.5 1.0 1.0 2.0 5.0 10.0 10.0 20.0` – user1424739 Apr 29 '22 at 18:50
  • @GeorgeSavva's version should produce the correct result. Are you able to make it more efficient? – user1424739 Apr 29 '22 at 18:53
  • I have updated the code to include `floor_dec()` which floors a decimal, it should provide a better filter for the numbers included in the output. – philiptomk Apr 30 '22 at 02:44
0

Here's something that works. Maybe it could be more efficient.

logSeq <- function(x){
  lx1 = floor(log10(min(x)))
  lx2 = ceiling(log10(max(x)))
  z=rep(10^(lx1:lx2), each=3)*c(1,2,5)
  z[z>=log10scaleround(min(x))-0.001 & 
      z<=log10scaleround(max(x))+0.001]
}

> logSeq(c(0.2589254, 20.5671765))
[1]  0.2  0.5  1.0  2.0  5.0 10.0 20.0
George Savva
  • 4,152
  • 1
  • 7
  • 21