From |kry|ov @end|ng |rom d|@root@org  Sat Sep 21 20:51:56 2024
From: |kry|ov @end|ng |rom d|@root@org (Ivan Krylov)
Date: Sat, 21 Sep 2024 21:51:56 +0300
Subject: [Rd] model.matrix() may be misleading for "lme" models
In-Reply-To: <b60df077-eab0-451c-a8c5-67e5e333b2da@mcmaster.ca>
References: <a7d44ccb-10b4-4fbb-b34c-154aaa687ef7@mcmaster.ca>
 <b60df077-eab0-451c-a8c5-67e5e333b2da@mcmaster.ca>
Message-ID: <20240921215156.1c65b61a@trisector>

Dear Prof. John Fox,

? Sat, 21 Sep 2024 12:47:49 -0400
John Fox <jfox at mcmaster.ca> ?????:

>      NextMethod(formula(object),  data=eval(object$call$data),
>                 contrasts.arg=object$contrasts)

The use of NextMethod worries me a bit. It will work as intended as
long as everyone gives fully-named arguments to the generic, without
relying on positional or partial matching, but may give unexpected
results otherwise:

foo <- \(x, ...) UseMethod('foo')
foo.default <- \(x, foo = 'default', baz = 'baz', ...)
 list(foo = foo, baz = baz, '...' = list(...))
# try to override the argument to the default method
foo.bar <- \(x, ...) NextMethod(x, foo = 'override')
x <- structure(list(), class = 'bar')
foo(x) # works, gives the right argument to foo.default
# $foo
# [1] "override"
# 
# $baz
# [1] "baz"
# 
# $...
# list()

# this used to work with foo.default, but now doesn't:
foo(x, fo = 'bar') # not matched to foo=
# $foo
# [1] "override"
# 
# $baz
# [1] "baz"
# 
# $...
# $...$fo
# [1] "bar"

foo(x, 'bar') # not matched to foo=, given to baz=
# $foo
# [1] "override"
# 
# $baz
# [1] "bar"
# 
# $...
# list()

This happens because NextMethod() overwrites named arguments already
present in the call, but any other arguments just get appended, without
any regard to whether they had already matched an argument before the
call was modified. In fact, I'm not seeing a way to safely override
some of the arguments for the next S3 method. The "attempt 4" described
by Henrik Bengtsson at [1] seems to work only if an argument is given
as part of the call:

foo.bar <- \(x, foo, ...) { foo <- 'override'; NextMethod() }
foo(x) # doesn't work
# $foo
# [1] "default"
# 
# $baz
# [1] "baz"
# 
# $...
# list()

foo(x, 1) # does work
# $foo
# [1] "override"
# 
# $baz
# [1] "baz"
#
# $...
# list()

Evaluating object$call$data in the environment of the suggested
nlme:::model.matrix.lme function may also not work right. Without an
explicit copy of the data, the best environment to evaluate it in would
be parent.frame().

-- 
Best regards,
Ivan

[1] https://github.com/HenrikBengtsson/Wishlist-for-R/issues/44



[Rd] model.matrix() may be misleading for "lme" models
Ivan Krylov