ms609 / Ternary

Create ternary plots in R

Home Page:https://ms609.github.io/Ternary/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Bug: heatmaps and contour plots fail for reasonable function

bienvenu opened this issue · comments

Hi, I think I have found a bug.

Let dp denote the Lp distance, that is,

dp(x, y) = (|x1 - y1|p + |x2 - y2|p + |x3 - y3|p)1/p

and, for p = ∞, d(x, y) = max(|x1 - y1|, |x2 - y2|, |x3 - y3|)

I want to make a contour plot showing the corresponding balls centered on c = (1/3, 1/3, 1/3), as a function of p. So, first, I define a function giving the distance to c:

dc <- function (p) {
  function (a, b, c) {
    (abs(a - 1/3)^p + abs(b - 1/3)^p + abs(c - 1/3)^p)^(1/p)
  }
}

Then, I use the code from the doc:

p = 1
TernaryPlot()
values <- TernaryPointValues(dc(p), resolution = 36L)
ColourTernary(values)
TernaryContour(dc(p), resolution = 36L)

For p = 1, 2, ..., the plots look about right:

Capture d’écran du 2021-11-08 21-02-40

Capture d’écran du 2021-11-08 21-06-21

and, for p = 100, we get something that looks exactly like what we are supposed to see for p = ∞:

Capture d’écran du 2021-11-08 21-02-40

However, for p = ∞ this is not working. Let us start by defining the relevant distance function:

dcmax <- function (a, b, c) {
  max(abs(a - 1/3), abs(b - 1/3), abs(c - 1/3))
}

The function seems to work fine. In particular, it is not constant:

> dcmax(1/3, 1/3, 1/3)
[1] 0
> dcmax(1/2, 1/4, 1/4)
[1] 0.1666667
> dcmax(0.6, 0.1, 0.3)
[1] 0.2666667

However, trying to plot the contours fails and raises an error:

> TernaryPlot()
> values <- TernaryPointValues(dcmax, resolution = 36L)
> ColourTernary(values)
> TernaryContour(dcmax, resolution = 36L)
Message d'avis :
Dans contour.default(x, y, z, add = TRUE, ...) :
  toutes les valeurs en z sont égales

The last message (in French) says "All z values are equal". And, indeed,

values
...
        [,1278]    [,1279]    [,1280]     [,1281]     [,1282]     [,1283]
x    0.02777778 0.04166667 0.05555556 -0.04166667 -0.02777778 -0.01388889
y    0.75376285 0.76178161 0.75376285  0.77781911  0.78583787  0.77781911
z    0.64814815 0.64814815 0.64814815  0.64814815  0.64814815  0.64814815
down 0.00000000 1.00000000 0.00000000  0.00000000  1.00000000  0.00000000
       [,1284]    [,1285]    [,1286]    [,1287]     [,1288]     [,1289]
x    0.0000000 0.01388889 0.02777778 0.04166667 -0.02777778 -0.01388889
y    0.7858379 0.77781911 0.78583787 0.77781911  0.80187537  0.80989413
z    0.6481481 0.64814815 0.64814815 0.64814815  0.64814815  0.64814815
down 1.0000000 0.00000000 1.00000000 0.00000000  0.00000000  1.00000000
       [,1290]    [,1291]    [,1292]     [,1293]   [,1294]    [,1295]   [,1296]
x    0.0000000 0.01388889 0.02777778 -0.01388889 0.0000000 0.01388889 0.0000000
y    0.8018754 0.80989413 0.80187537  0.82593164 0.8339504 0.82593164 0.8499879
z    0.6481481 0.64814815 0.64814815  0.64814815 0.6481481 0.64814815 0.6481481
down 0.0000000 1.00000000 0.00000000  0.00000000 1.0000000 0.00000000 0.0000000

dcmax is a continuous, non-constant function so there is no reason why we should not be able to get a contour plot.

In case that can be useful:

> version
               _                           
platform       x86_64-pc-linux-gnu         
arch           x86_64                      
os             linux-gnu                   
system         x86_64, linux-gnu           
status                                     
major          4                           
minor          1.1                         
year           2021                        
month          08                          
day            10                          
svn rev        80725                       
language       R                           
version.string R version 4.1.1 (2021-08-10)
nickname       Kick Things                 
> sessionInfo()
R version 4.1.1 (2021-08-10)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Debian GNU/Linux 10 (buster)

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.8.0
LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.8.0

locale:
 [1] LC_CTYPE=fr_FR.UTF-8       LC_NUMERIC=C              
 [3] LC_TIME=fr_FR.UTF-8        LC_COLLATE=fr_FR.UTF-8    
 [5] LC_MONETARY=fr_FR.UTF-8    LC_MESSAGES=fr_FR.UTF-8   
 [7] LC_PAPER=fr_FR.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=fr_FR.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] Ternary_1.2.3

loaded via a namespace (and not attached):
 [1] Rcpp_1.0.7        viridisLite_0.4.0 digest_0.6.28     later_1.3.0      
 [5] mime_0.12         R6_2.5.1          lifecycle_1.0.1   xtable_1.8-4     
 [9] magrittr_2.0.1    rlang_0.4.12      promises_1.2.0.1  ellipsis_0.3.2   
[13] tools_4.1.1       shiny_1.7.1       httpuv_1.6.3      fastmap_1.1.0    
[17] compiler_4.1.1    htmltools_0.5.2

Hi, thanks for the detailed bug report.

In your function dcmax, try replacing max() with pmax().

So this happens because the functions TernaryPointValues and TernaryContour only work with vectorized functions. This might be worth adding to the doc: I do not think this is stated anywhere. For instance, in the documentation of these functions, the argument is described as

    Func: Function taking the parameters ‘a’, ‘b’ and ‘c’, which
          evaluates to a numeric whose value should be depicted.

Moreover, there is never a type error that could give the user a clue as to what is going on.

That's a good suggestion, thanks. Could you suggest a suitable wording that I could use in the docs?

I'm not sure how I'd set up a type error – could you talk me through how you'd imagine the error detection process working?

Thanks!

I do not think that that my description of the problem being that "the functions TernaryPointValues and TernaryContour only work with vectorized functions" was accurate. I think that in R the term "vectorized" is used to refer to two different things:

  1. In the loose sense, functions f such that for a vector x = (xi), f(x) return y = (f(xi))
  2. In the strict sense, functions f that are weakly vectorized and where the iteration on the entries of the (atomized) input vector is done outside of R.

Here, I think that you only need the argument to be vectorized in the loose sense (although I haven't tried to confirm it).

(also, I think that R's function max is vectorized in the strict sense, but not in the loose one)

Could you suggest a suitable wording that I could use in the docs?

I'm not sure, I'm not a R user so I do not know what the standard practices are. I thought that saying

    Func: Function taking the parameters ‘a’, ‘b’ and ‘c’, which
          evaluates to a numeric whose value should be depicted.
          Must be a vectorized function.

would be concise and clear enough, but based on what I said above I'm afraid that is not a very precise wording. And I don't know what the R jargon is to refer to "vectorized functions" in the loose sense.

I'm not sure how I'd set up a type error – could you talk me through how you'd imagine the error detection process working?

Unfortunately, I have no idea how to do that. Based on this, It seems that there is no way to check if a function is "vectorized". Depending on how your functions are implemented, simply checking that f(a, b, c) is a vector and not a scalar. For instance,

> dcmax(c(1/2, 1/3), c(1/4, 1/3), c(1/4, 1/3))
[1] 0.1666667

whereas I'm assuming that your code is expecting this to be a vector (whether the entry-wise operation are performed in R or vectorized in the strict sense being irrelevant). In that case, you might just check this explicitly and raise an error with a detailed error message if this is not the case.

Hope that helps. I'm sorry but, not being a R user, I'm not really going to be able to be any useful on this.

Thanks for these pointers – I've made appropriate improvements on this basis. Hopefully this will make life easier for future users!

Hope you can now get the package to do everything you need. Do let me know if you run into any other snags!

Now incorporated in v1.2.4 on CRAN.