matlib icon indicating copy to clipboard operation
matlib copied to clipboard

plotEqn() - can't supply labels for lines

Open friendly opened this issue 1 year ago • 9 comments

In dev/plotEqn-test.R, I've tried to supply my own labels for lines, b/c I want to use x and y for the variables and simplify the equations to the form y = a + b * x. I have a test version in dev/plotEqn.R, where I'd like to also add options to control the solution points, allowing the argument solution = list(pch =, cex=, col=)

(This is for an example where I'd like to show the duality of points and lines in data / beta space.)

A <- matrix(c( 1, 2, 0,
               -1, 2, 1), 3, 2) |> print()

b <- c(2, 1, 1)
# works OK
plotEqn(A, b, vars = c("x", "y"))
        
# try to change the labels: doesn't work
plotEqn(A, b, vars = c("x", "y"),
        labels = c("y = x - 2",
                   "y = 1/2 - x",
                   "y = 1"))

The lines appear, but not the labels

image

I can't see what is wrong with the relevant code in the function (around line 132)

    if (!is.null(labels)) {
      xl <- if(A[i, 2] == 0) b[i] else x[1]
      label <- parse(text=sub("=", "==", labels[i]))
      text(xl, y[1], label, col=col[i], pos=4)
    }

Related to this is the fact that the simplify argument doesn't simplify as much as I'd like:

> showEqn(A, b, vars = c("x", "y"), simplify = TRUE)
  x - 1*y  =  2 
2*x + 2*y  =  1 
0*x   + y  =  1 

It would be nicer if this gave (aligned)

  x -       y  =  2 
2*x + 2*y  =  1 
             y  =  1 

friendly avatar Oct 20 '24 21:10 friendly

I took a look at this a couple of days ago and have thought about it a bit since. I think that the simplify option could use a complete rewrite, perhaps separating the signs, coefficients, and variables. I don't know when I'd have a chance to attempt that.

john-d-fox avatar Oct 23 '24 17:10 john-d-fox

Just to be clear: the figure I'm trying to create is the left panel of that below, illustrating the duality between lines in data space and points in $\beta$ space.

dual-points-lines

I did this from scratch, starting from

# Equations as intercepts & slopes in data space
x <- c(-2, .5, 1)
y <- c( 1, -1, 0)

# plot lines in data space
plot(0,0, type ="n",
     xlab = "x",
     ylab = "y",
     xlim = c(-1, 4), ylim = c(-3, 2),
     cex.lab = 2, asp = 1,
     main = "Data space")
abline(h = 0, v = 0, col = "gray")
for (i in seq_along(x)) {
  abline(x[i], y[i], col = col[i], lwd = 2)
}
  ...

then adding annotations of the simplified y = ... form of the equations, calculating the intersections of lines, etc., all because I couldn't do this with plotEqn().

Not a big hurry to fix the simplify problem, now that I've done this graph, but it would be nice to fix at least the bug with labels.

friendly avatar Oct 23 '24 17:10 friendly

Yes, the two problems were clear to me: (1) You can't get user-supplied labels with plotEqn(); (2) the automatic labels are poorly simplified. I was addressing (2), which I think I could improve (but don't know when). I didn't see the source of (1).

john-d-fox avatar Oct 23 '24 17:10 john-d-fox

Here's a first attempt at simplifying 2-variable equations, which could be used in showEqn() and plotEqn():

simplifyEqn <- function(A, b, 
                        digits=min(3, getOption("digits") - 3),
                        vars = c("x1", "x2"),
                        align=TRUE){
  signs <- ifelse(A[, 2] < 0, " - ", 
                  ifelse(A[, 2] == 0, "   ", " + "))
  signs[A[, 1] == 0 & A[, 2] > 0] <- "    "
  A[, 2] <- abs(A[, 2])
  vars1 <- rep(vars[1], nrow(A))
  vars2 <- rep(vars[2], nrow(A))
  vars1[A[, 1] == 0] <- paste(rep(" ", nchar(vars[1])), collapse="")
  vars2[A[, 2] == 0] <- paste(rep(" ", nchar(vars[2])), collapse="")
  AA <- format(signif(A, digit=digits))
  AA[A[, 1] == 0, 1] <- paste(rep(" ", nchar(A[1, 1])), collapse="")
  AA[A[, 2] == 0, 2] <- paste(rep(" ", nchar(A[2, 1])), collapse="")
  AA[A[, 1] == 1, 1] <- paste(rep(" ", nchar(A[1, 1])), collapse="")
  AA[A[, 2] == 1, 2] <- paste(rep(" ", nchar(A[2, 1])), collapse="")
  bb <- format(signif(b, digits==digits))
  eqns <- paste0(AA[, 1], " ", vars1,  signs, AA[, 2],
                 " ", vars2, " = ", bb)
  if (align) eqns else gsub(" +", " ", eqns)
}

For example,

>   A <- matrix(c(0, 1, -2, 0, -3, -2, 10, -20, 5, 5), 5, 2, byrow=TRUE)
>   b <- c(-1, 0, 2, 4, -10)

>   simplifyEqn(A, b)
[1] "           x2 =  -1" "-2 x1         =   0"
[3] "-3 x1 -  2 x2 =   2" "10 x1 - 20 x2 =   4"
[5] " 5 x1 +  5 x2 = -10"

>   simplifyEqn(A, b, align=FALSE)
[1] " x2 = -1"           "-2 x1 = 0"          "-3 x1 - 2 x2 = 2"  
[4] "10 x1 - 20 x2 = 4"  " 5 x1 + 5 x2 = -10"

I don't see how you can make something like this work with expression(), which can't simply take a character string as an argument (and produce a proper graphed equation) and must follow proper R syntax (which, I suspect, is why the current version doesn't work right).

john-d-fox avatar Oct 23 '24 20:10 john-d-fox

This looks nice, and would help certainly in showEqn().

It would also help for my use case, but I think I see where the expression() problem comes from:

    if (!is.null(labels)) {
      xl <- if(A[i, 2] == 0) b[i] else x[1]
      label <- parse(text=sub("=", "==", labels[i]))
      text(xl, y[1], label, col=col[i], pos=4)
    }

This assumes that the variables have been mapped from x1 to expressions, x[1], which is desirable in the case that (a) no vars= has been given, so the variables become expressions, if (missing(vars)) vars <- c(expression(x[1]), expression(x[2])) (b) no labels= has been given, so the equation labels become the result of showEqn(A, b, vars, simplify=TRUE)

> A<- matrix(c(1,2,3, -1, 2, 1),3,2)
> b <- c(2,1,3)
> showEqn(A, b)
1*x1 - 1*x2  =  2 
2*x1 + 2*x2  =  1 
3*x1 + 1*x2  =  3 
> plotEqn(A,b)
  x[1] - 1*x[2]  =  2 
2*x[1] + 2*x[2]  =  1 
3*x[1]   + x[2]  =  3 

Unless @philchalmers objects, I suggest to add your simplifyEqn(), then consider how to fix plotEqn() in the case that triggered this issue, namely when equation labels are specified.

In this case, the function could just treat them as given; it would be up to the user to decide whether to use expressions in them.

Does that sound workable?

friendly avatar Oct 23 '24 21:10 friendly

I don't have an objection to improving the simply logic as I agree there's room for improvement, though I'm wondering about changing the output focus of showEqn() now that TeX rendering is available and has become a major focus. For instance, a more natural rendering approach at this point is to use

A <- matrix(c( 1, 2, 0,
               -1, 2, 1), 3, 2)
b <- c(2, 1, 1)
showEqn(A,b,latex=TRUE) |> Eqn()

image

which looks fine (though I'd prefer if Eqn() were called within showEqn() when latex=TRUE to avoid the pipe), but then the simplify argument makes the output awful.

showEqn(A,b,latex=TRUE,simplify=TRUE) |> Eqn()

image

Obviously this needs to be fixed too, though given the new focus on TeX instead of ASCII outputs should showEqn() be modified to be TeX driven instead? The ASCII and TeX approaches are not particularly complimentary.... just trying to get ahead of future issues.

philchalmers avatar Oct 23 '24 23:10 philchalmers

Maybe I'm missing something, but isn't the fundamental problem that although character strings can occur within an expression, character strings aren't expressions? Try, e.g.,

  plot(0:1, 0:1, type="n")
  text(0.1, 0.7, paste("0 ==", expression(x[2])), adj=0)
  text(0.1, 0.3, expression("0" == x[2]), adj=0)

It is possible to render LaTeX in plots.

john-d-fox avatar Oct 24 '24 00:10 john-d-fox

I agree with @philchalmers that Eqn() makes the rendering of showEqn() nicer, but that's not the point here.

The issue is being able to use equations as line labels in plotEqn(), particularly when they are supplied as input to the labels arg

friendly avatar Oct 24 '24 00:10 friendly

To elaborate my previous examples slightly, compare

  plot(0:1, 0:1, type="n")
  text(0.1, 0.1, paste("0 ==", expression(x[2])), adj=0)
  text(0.1, 0.3, expression("0" == x[2]), adj=0)
  text(0.1, 0.5, eval(parse(text="expression(0 == x[2])")), adj=0)
  text(0.1, 0.7, latex2exp::TeX("$0 = x_2$"), adj=0)

john-d-fox avatar Oct 24 '24 17:10 john-d-fox