friendly / matlib

Matrix Functions for Teaching and Learning Linear Algebra and Multivariate Statistics
http://friendly.github.io/matlib/
65 stars 16 forks source link

Align symbolic and numeric matrices #66

Open friendly opened 1 month ago

friendly commented 1 month ago

Starting to use latexMatrix() and friends in my book. I ran into a little problem with aligning two equations, one symbolic and the other numeric. My test file is dev/eigen-ex.R

Goal: show the eigen decomposition of a cov matrix, with a numeric example, ie, S = V Lambda V^T

data(workers, package = "matlib")   
head(workers)

vars <- c("Experience", "Income")
# covariance matrix & mean
mu <- colMeans(workers[, vars]) |> print()
S <- cov(workers[, vars]) |> print()

# eigenvalues and eigenvectors
S.eig <- eigen(S)
Lambda <- S.eig$values |> print()
V <- S.eig$vectors |> print()

where,

S <- cov(workers[, vars]) |> print()
           Experience   Income
Experience   135.8333 151.9444
Income       151.9444 233.6111

I tried using \phantom{} to give the symbolic part look like "S = V Lambda V^T" (fiddling with the size of the phantom)

# align V Lambda V' using \phantom{}
options(digits = 4)
rownames(S) <- colnames(S) <- c("Exp", "Inc")
spacer <- "\\phantom{00000000000000}"
Eqn("\\mathbf{S} & = \\mathbf{V}", spacer,
    "\\mathbf{\\Lambda}", spacer,  
    "\\mathbf{V}^\\top", Eqn_newline(),
    latexMatrix(S), "& =", 
    latexMatrix(V), "  ", diag(Lambda), "  ", latexMatrix(V, transpose=TRUE),
    align = TRUE)

which gives:

image

Note too bad, but very fiddly. Is there a better LaTeX way? I tried other alignment (&) tabs, but there's something I don't understand.

# What's wrong here?
spacer <- " & "
Eqn("\\mathbf{S} & = \\mathbf{V}", spacer,
    "\\mathbf{\\Lambda}", spacer,  
    "\\mathbf{V}^\\top", Eqn_newline(),
    latexMatrix(S), "& =", 
    latexMatrix(V), "  ", diag(Lambda), "  ", latexMatrix(V, transpose=TRUE),
    align = TRUE)

Gives:

image

I think this is more of a LaTeX issue, than a problem in the package.

I looked at Eqn_hspace(), but couldn't figure out how to make it work in this context, because the function and docs related to alignment around =.

friendly commented 1 month ago

Some related notes on this:

Also, the default for digits = getOption("digits") - 2 was too large, so I set that option globally, rather than in each call to latexMatrix()

philchalmers commented 1 month ago

Hi Michael,

From my experience aligning symbols above matrices is awkward, at least if you want them centered. If you're okay with right aligning adding a few more '&' can work.

Eqn("\\mathbf{S} & = & \\mathbf{V} & &\\mathbf{\\Lambda} & &\\mathbf{V}^\\top", Eqn_newline(),
    latexMatrix(S), "& = & ", latexMatrix(V), "& &", diag(Lambda), "& &", latexMatrix(V, transpose=TRUE),
    align = TRUE)

To get the symbols middle aligned you'd need to add '&' within the matrices themselves. I can take a stab at this later (right now family responsibilities are calling....).

john-d-fox commented 1 month ago

It's been awhile since I thought about this. Your points:

I think that to get everything you want in an automatic way would require a complete redesign of the how the row/column names are handled. OTOH patching the current approach should be feasible but more complicated to use.

friendly commented 1 month ago

OK, I wasn't proposing anything in the way of re-design, just making some observations. I can try to work around-- e.g., by explicitly using \\text{} when I re-define the colnames from cov()

The main point had to do with alignment.

john-d-fox commented 1 month ago

I was able to quickly implement much of what you want at the expense of additional complexity. For example,

> S <- matrix(1:9, 3, 3)
> rownames(S) <- colnames(S) <- letters[1:3]
> S
  a b c
a 1 4 7
b 2 5 8
c 3 6 9

> latexMatrix(S)
\begin{matrix}
  &  \begin{matrix} \phantom{i} a & b & c
  \end{matrix} \\ 
 \begin{matrix}  
   a\\ 
   b\\ 
   c\\ 
\end{matrix}  & 
\begin{pmatrix}  
1 & 4 & 7 \\ 
2 & 5 & 8 \\ 
3 & 6 & 9 \\ 
\end{pmatrix}
\\ 
\end{matrix} 

> print(latexMatrix(S), display.labels=FALSE)
\begin{pmatrix} 
1 & 4 & 7 \\ 
2 & 5 & 8 \\ 
3 & 6 & 9 \\ 
\end{pmatrix}

> print(latexMatrix(S), text.labels=c(row=TRUE, column=TRUE))
\begin{matrix}
  &  \begin{matrix} \phantom{i} \mathrm{a} & \mathrm{b} & \mathrm{c}
  \end{matrix} \\ 
 \begin{matrix}  
   \mathrm{a}\\ 
   \mathrm{b}\\ 
   \mathrm{c}\\ 
\end{matrix}  & 
\begin{pmatrix}  
1 & 4 & 7 \\ 
2 & 5 & 8 \\ 
3 & 6 & 9 \\ 
\end{pmatrix}
\\ 
\end{matrix} 

There are several new options that control default behaviour; see the code and help file for details.

I didn't implement changing the size of the row/column labels text for the reason I mentioned previously.

The changes are in the GitHub repo for the package. My testing was cursory, so the code should be exercised more.

john-d-fox commented 1 month ago

If you want horizontal alignment to work when mixing matrices some of which have column labels and some not, I think you'll have to define empty column labels (e.g., "" or " ") for those without. I think it would be very hard to automate that in Eqn().

I don't really understand why vertical alignment is so difficult. I'd be tempted simply to align on = and let the RHSs be left-justified.

john-d-fox commented 1 month ago

I screwed around a bit with the size of the row/column labels, and the effect wasn't as extreme as I expected, so I added a mathtext.size argument to print.latexMatrix(), controlled by a corresponding option, and I changed the default for mathtext from mathrm to text to support font-size changes. For example,

> print(latexMatrix(S), text.labels=c(row=TRUE, column=TRUE),
+       mathtext.size="footnotesize")
\begin{matrix}
  &  \begin{matrix} \phantom{i} \text{\footnotesize{a}} & \text{\footnotesize{b}} & \text{\footnotesize{c}}
  \end{matrix} \\ 
 \begin{matrix}  
   \text{\footnotesize{a}}\\ 
   \text{\footnotesize{b}}\\ 
   \text{\footnotesize{c}}\\ 
\end{matrix}  & 
\begin{pmatrix}  
1 & 4 & 7 \\ 
2 & 5 & 8 \\ 
3 & 6 & 9 \\ 
\end{pmatrix}
\\ 
\end{matrix}

So, more flexibility at the expense of a bit more complexity.

friendly commented 1 month ago

Hi John

Your solution looks nice, but it turns out \text{\footnotesize{}} isn't supported by MathJax. I found an online latex editor that allows different rendering engines, https://arachnoid.com/latex/

image

vs. Codecogs, whatever that is

image

So, I'm not sure what to do about this commit, https://github.com/friendly/matlib/commit/30992cfb63e865608fe529f5d9b001793c980c2c

Your example works fine with just \text{}. It is the\footnotesize` that causes the problem, so I'd say let's dispense with this.

john-d-fox commented 1 month ago

Why dispense with it rather than just not using it if the target is an html document to be processed by mathjax? BTW, the text size commands don't work in math mode, so not with, e.g., \mathrm{}.

Another thought: There are now too many (7) options used by print.latexMatrix(). I'd like to consolidate them in a single "print.latexMatrix" option. Any objections?

john-d-fox commented 1 month ago

I went ahead and consolidated the "print.latexMatrix" options.

Feel free to remove the mathtext.size argument if you wish, though as I said I don't see the point now that it's implemented.