[Rd] matplot.Date & matplot.POSIXct

Martin Maechler m@ech|er @end|ng |rom @t@t@m@th@ethz@ch
Tue Jan 28 12:13:03 CET 2020


>>>>> Spencer Graves 
>>>>>     on Mon, 27 Jan 2020 23:02:28 -0600 writes:

    >       Thanks for the reply.
    > On 2020-01-27 19:56, Abby Spurdle wrote:
    >> Maybe I'm missing something really obvious here, but I was unable to
    >> create a matrix out of POSIXct object(s).
    >> Perhaps that deserves a separate discussion...?

yes, very much a separate discussion.
Let's come back to Spencer's topic of matplot() :

    [....]

    >       The standard matplot application that concerns me is with 
    > matplot(x, y, ...) where x has class Date or POSIXct and y is a matrix.  

    > The "fda" package on CRAN includes a "matplot" help page with examples 
    > that worked when I tested them recently.

Indeed.  That's how I understood you well.
matplot() has been one of my favorite plotting functions in S
and Splus in the 1990s ... and that's why the R source code of
matplot(), matpoints(), and matlines()  still has a comment
mentioning that I wrote (the first version of) it for R on June 27, 1997.

By design (from S), matplot() {etc} has always been thought as a convenience
wrapper to calling
	plot() and lines(),  lines(),  lines()
or    	plot() and points(), points(), points()	

{plot() : for setting up coord.system, draw axes, titles, ...},
-- notably also for adhering to the DRY (instead of WET)
   programming principle (-> "Don't Repeat Yourself")  --

*and* -- be assured -- more than 99% of its use has been for the
special case where x has been a numeric vector and y a matrix
with several columns and indeed, the following part from the
beginning of  matplot()'s source code has basically already been in the
version from 1997 and is all about having it work for
vectors treated as 1-column matrices :

------------------------------------------------------------------------

    if(missing(x)) {
	if(missing(y)) stop("must specify at least one of 'x' and 'y'")
	else x <- seq_len(NROW(y))
    } else if(missing(y)) {
	y <- x;	 ylabel <- xlabel
	x <- seq_len(NROW(y)); xlabel <- ""
    }
    kx <- ncol(x <- as.matrix(x))
    ky <- ncol(y <- as.matrix(y))
    n <- nrow(x)
    if(n != nrow(y)) stop("'x' and 'y' must have same number of rows")

    if(kx > 1L && ky > 1L && kx != ky)
	stop("'x' and 'y' must have only 1 or the same number of columns")
    if(kx == 1L) x <- matrix(x, nrow = n, ncol = ky)
    if(ky == 1L) y <- matrix(y, nrow = n, ncol = kx)
    k <- max(kx, ky) ## k == kx == ky

------------------------------------------------------------------------





    >       If you have an example that you think should work but doesn't I'd 
    > like to know.  Maybe it should be added to the examples in 
    > fda::matplot.Rd file, then the code should be modified until it works.
    >> 
    >> Regarding your other comments/questions:
    >> (1) You should *NOT* mask functions from the graphics package (or
    >> base, stats, etc), except possibly for personal use.
    >> (2) The xlab and ylab are fine.


    >       In most situations, I agree with your comment that, "You should 
    > *NOT* mask functions from the graphics package (or base, stats, etc)".


    >       However, when the behavior of the function in graphics, base, or 
    > stats seems patently inappropriate and not adequately considered, then I 
    > think that someone should mask the function in the core distribution 
    > with one whose behavior seems more consistent with what most users would 
    > most likely want.


    >       Ten or twelve years ago, I concluded that the behavior of 
    > graphics::matplot(x, y, ...) was inappropriate when x is either of class 
    > Date or POSIXct.  Specifically, it labeled the horizontal axis the same 
    > as graphics::matplot(as.numeric(x), y, ...).  I think it should instead 
    > be labeled the same as graphics::plot(x, y[,1], ...) in such cases.  To 
    > fix this problem, I made fda::matplot generic; graphics::matplot is not 
    > generic.  And a coded methods for x of class numeric, matrix, Date and 
    > POSIXct plus a default.  Each calls either graphics::matplot or matlines 
    > as appropriate after first setting up the horizontal axis properly if x 
    > is of class Date or POSIXct.

I pretty much agree with your judgement here, Spencer.
What you say (and I assume your fda::matplot() does) is still
implementing the original basic idea of being a convenience
wrapper for a call to plot() and typically several calls
to lines() or points()...

and indeed in R {graphics}, plot(), lines() and points()
all have been S3 generics for a long time and so it seems
naturaly that a wrapper to these functions should also dispatch
correctly ...

possibly *not* by becoming S3 generic itself, but just, conceptually,
by ensuring *not* to change the S3 class of 'x' before calling
plot(), points() and lines() ....


    >       For specific examples, consider the following taken from 
    > fda::matplot.Rd:


    > invasion1 <- as.Date('1775-09-04')
    > invasion2 <- as.Date('1812-07-12')
    > earlyUS.Canada <- c(invasion1, invasion2)
    > Y <- matrix(1:4, 2, 2)
    > graphics::matplot(earlyUS.Canada, Y)
    > # horizontal axis labeled per as.numeric(earlyUS.Canada),
    > # NOT as Dates
    > fda::matplot(earlyUS.Canada, Y)
    > # problem fixed.


    > # POSIXct
    > AmRev.ct <- as.POSIXct1970(c('1776-07-04', '1789-04-30'))
    > graphics::matplot(AmRev.ct, Y)
    > # horizontal axis labeled per as.numeric(AmRev.ct),
    > # NOT as POSIXct
    > fda::matplot(AmRev.ct, Y)
    > # problem fixed.

    >       Comments?

first parts: see above; from there I hope it's already
clear that I am sympathetic to your proposal,
... but (there's alway a "but", ..)

Still, as Abby mentioned,  turning a simple function into the
default method of an S3 generic is easy to do, but comes with a
bit of cost, not just S3 dispatch which typically is negligable in
graphics, but a bit of maintenance cost and mostly in this case
the cost of breaking back compatibility by the improvement.
How many plots will change where people have already relied on
the current   as.numeric(x)   behavior?
If we'd change this in R's graphics, it will be
- me and/or the CRAN team who have to contact CRAN package
  maintainer about problems
  (maybe none, as the change may not break any checks)

- Users of matplot() {& matlines() & matpoints()}  who may have to
  adopt their calls to these functions {I'm pretty sure all
  three would have to change for consistency}.

----- and then, there are quite a few other changes,  bug
      assignments to which I have committed which should be
      dealt with rather before this.

If you'd turn this into a proper "wishlist"  "bug" report
on R's bugzilla, *and* you or another volunteer provided a patch
to the R sources (including changes to man/*.Rd, NAMESPACE, ..)
which then can be tested to pass 'make check-all',
then I'd definitely commit to this
(possibly too late for R 4.0.0;  teaching starts here soon, etc).

Best,
Martin

    >       Thanks again for the reply.
    >       Spencer Graves
    >> 
    >> B.



More information about the R-devel mailing list