From m@ech|er @end|ng |rom @t@t@m@th@ethz@ch  Fri Nov  1 09:07:45 2019
From: m@ech|er @end|ng |rom @t@t@m@th@ethz@ch (Martin Maechler)
Date: Fri, 1 Nov 2019 09:07:45 +0100
Subject: [Rd] 
 head.matrix can return 1000s of columns -- limit to n or add
 new argument?
In-Reply-To: <2f877775-521a-7fc1-b68b-d233d4a50bea@fredhutch.org>
References: <CAPRVBcxsAkcgr3mDc4skTe8DTxJaYe2xyhk3QMk3odW1MGcyNA@mail.gmail.com>
 <CAB8pepxS2mRtd+522OwjixnM6ZjYxb+X0dc4vMGn6QxdFYBmVQ@mail.gmail.com>
 <CAD4oTHH=LpeEj6h6xYdOGsreS-GDCGdgJV5wpaUR3Pjuh5_M1w@mail.gmail.com>
 <CAPRVBczYZHO3JaFVfNeNNV0-YMRvX3CUYhUvLCjophDEoP+XgQ@mail.gmail.com>
 <23935.14449.965488.684663@stat.math.ethz.ch>
 <CAPRVBcwMY=GAVfFeCwowQsxP31Y=_B2ducosThN+8V91WESB8Q@mail.gmail.com>
 <20449_1568703158_x8H6qIDG009669_f8285123-371f-5d4b-1281-6cc085dba122@fredhutch.org>
 <22204_1568723392_x8HCSG3F015456_674779E0-B1F6-4DC2-9F1C-D5D48B044B66@mcmaster.ca>
 <AD725242-0FB7-4004-9F16-304E345A363A@mcmaster.ca>
 <23937.1791.389379.103733@stat.math.ethz.ch>
 <CAD4oTHF_e4rtHu0S5bUu38+kQSEHXwun3Sv9-CbK+xN9tSc2hA@mail.gmail.com>
 <CAD4oTHFf7Zp+Xe3jvv7oirLWgZm7ZiSNWZvbrEsqJGfrL6Q2wQ@mail.gmail.com>
 <23993.29737.690600.313456@stat.math.ethz.ch>
 <2f877775-521a-7fc1-b68b-d233d4a50bea@fredhutch.org>
Message-ID: <23995.59345.18924.868610@stat.math.ethz.ch>

>>>>> Pages, Herve 
>>>>>     on Thu, 31 Oct 2019 21:02:07 +0000 writes:

    > On 10/30/19 04:29, Martin Maechler wrote:
    >>>>>>> Gabriel Becker
    >>>>>>> on Tue, 29 Oct 2019 12:43:15 -0700 writes:
    >> 
    >> > Hi all,
    >> > So I've started working on this and I ran into something that I didn't
    >> > know, namely that for x a multi-dimensional (2+) array, head(x) and tail(x)
    >> > ignore dimension completely, treat x as an atomic vector, and return an
    >> > (unclassed) atomic vector:
    >> 
    >> Well, that's  (3+), not "2+" .
    >> 
    >> But I did write (on Sep 17 in this thread!)
    >> 
    >> > The current source for head() and tail() and all their methods
    >> > in utils is just 83 lines of code  {file utils/R/head.R minus
    >> > the initial mostly copyright comments}.
    >> 
    >> and if've ever looked at these few dozen of R code lines, you'll
    >> have seen that we just added two simple utilities with a few
    >> reasonable simple methods.  To treat non-matrix (i.e. non-2d)
    >> arrays as vectors, is typically not unreasonable in R, but
    >> indeed with your proposals (in this thread), such non-2d arrays
    >> should be treated differently either via new  head.array() /
    >> tail.array() methods ((or -- only if it can be done more nicely -- by
    >> the default method)).
    >> 
    >> Note however the following  historical quirk :
    >> 
    >>> sapply(setNames(,1:5), function(K) inherits(array(pi, dim=1:K), "array"))
    >> 1     2     3     4     5
    >> TRUE FALSE  TRUE  TRUE  TRUE
    >> 
    >> (Is this something we should consider changing for R 4.0.0 -- to
    >> have it TRUE also for 2d-arrays aka matrix objects ??)

    > That would be awesome! More generally I wonder how feasible it would be 
    > to fix all these inheritance quirks where inherits(x, "something"), 
    > is(x, "something"), and is.something(x) disagree. They've been such a 
    > nuisance for so many years...

    > Thanks,
    > H.

Thank you Herv?; you are right "in theory", but
no, we don't want to fix _all_ these quirks at the moment
(because we know how much this would break).
Note that ?class does mention S3 and S4, and also you know about
is(.,.)  which is more "rational" than inherits insofar as it
"thinks" the S4 way about inheritance .. but then it has it's
surprises, too; e.g., note the result of  is(NULL) .

I really wanted to address the relatively limited case of
{matrix, array} for now.

{{more on this in the subthread Peter opened}}
Martin

    >> The consequence of that is that
    >> currently, "often"   foo.matrix is just a copy of foo.array  in
    >> the case the latter exists:
    >> "base" examples: foo in {unique, duplicated, anyDuplicated}.
    >> 
    >> So I propose you change current  head.matrix and tail.matrix  to
    >> head.array and tail.array
    >> (and then have   head.matrix <- head.array  etc, at least if the
    >> above quirk must remain, or remains (which I currently guess to
    >> be the case)).
    >> 
    >> 
    >> >> x = array(100, c(4, 5, 5))
    >> 
    >> >> dim(x)
    >> 
    >> > [1] 4 5 5
    >> 
    >> >> head(x, 1)
    >> 
    >> > [1] 100
    >> 
    >> >> class(head(x))
    >> 
    >> > [1] "numeric"
    >> 
    >> 
    >> > (For a 1d array, it does return another 1d array).
    >> 
    >> > When extending head/tail to understand multiple dimensions as discussed in
    >> > this thread, then, should the behavior for 2+d arrays be explicitly
    >> > retained, or should head and tail do the analogous thing (with a head(<2d
    array> ) behaving the same as head(<matrix>), which honestly is what I
    >> > expected to already be happening)?
    >> 
    >> > Are people using/relying on this behavior in their code, and if so, why/for
    >> > what?
    >> 
    >> > Even more generally, one way forward is to have the default methods check
    >> > for dimensions, and use length if it is null:
    >> 
    >> > tail.default <- tail.data.frame <- function(x, n = 6L, ...)
    >> > {
    >> > if(any(n == 0))
    >> > stop("n must be non-zero or unspecified for all dimensions")
    >> > if(!is.null(dim(x)))
    >> > dimsx <- dim(x)
    >> > else
    >> > dimsx <- length(x)
    >> 
    >> > ## this returns a list of vectors of indices in each
    >> > ## dimension, regardless of length of the the n
    >> > ## argument
    >> > sel <- lapply(seq_along(dimsx), function(i) {
    >> > dxi <- dimsx[i]
    >> > ## select all indices (full dim) if not specified
    >> > ni <- if(length(n) >= i) n[i] else dxi
    >> > ## handle negative ns
    >> > ni <- if (ni < 0L) max(dxi + ni, 0L) else min(ni, dxi)
    >> > seq.int(to = dxi, length.out = ni)
    >> > })
    >> > args <- c(list(x), sel, drop = FALSE)
    >> > do.call("[", args)
    >> > }
    >> 
    >> 
    >> > I think this precludes the need for a separate data.frame method at all,
    >> > actually, though (I would think) tail.data.frame would still be defined and
    >> > exported for backwards compatibility. (the matrix method has some extra
    >> > bits so my current conception of it is still separate, though it might not
    >> > NEED to be).
    >> 
    >> > The question then becomes, should head/tail always return something with
    >> > the same dimensionally (number of dims) it got, or should data.frame and
    >> > matrix be special cased in this regard, as they are now?
    >> 
    >> > What are people's thoughts?
    >> > ~G
    >> 
    >> > [[alternative HTML version deleted]]
    >> 
    >> ______________________________________________
    >> R-devel at r-project.org mailing list
    >> https://urldefense.proofpoint.com/v2/url?u=https-3A__stat.ethz.ch_mailman_listinfo_r-2Ddevel&d=DwICAg&c=eRAMFD45gAfqt84VtBcfhQ&r=BK7q3XeAvimeWdGbWY_wJYbW0WYiZvSXAJJKaaPhzWA&m=Xl_11U8w8hVRbuqAPQkz0uSW02kokK9EUPhOopxw0d8&s=vyKU4VkWLb_fGG6KeDPPjVM5_nLhav6UiX7NkzgqsuE&e=
    >> 

    > -- 
    > Herv? Pag?s

    > Program in Computational Biology
    > Division of Public Health Sciences
    > Fred Hutchinson Cancer Research Center
    > 1100 Fairview Ave. N, M1-B514
    > P.O. Box 19024
    > Seattle, WA 98109-1024

    > E-mail: hpages at fredhutch.org
    > Phone:  (206) 667-5791
    > Fax:    (206) 667-1319



[Rd] head.matrix can return 1000s of columns -- limit to n or add new argument?
Martin Maechler