31

As the termplot function in R is containing some weird code that is giving me annoying bugs, I want to override it in my own test code until I find a more permanent solution. Problem is that the changed function is not loaded by the mgcv package. The mgcv package loads termplot from the stats package in its namespace, using importFrom() in the NAMESPACE file.

How can I convince mgcv to use the changed termplot? I tried :

unlockBinding("termplot", as.environment("package:stats"))
assign("termplot", my.termplot, as.environment("package:stats"))
lockBinding("termplot", as.environment("package:stats"))

and when applied to lm-objects, this works and the altered termplot is used. But when using gam-objects made by the mgcv package, this doesn't work. I'm not really going to build the stats package from source if I can avoid it...

To clarify, I also tried with

assignInNamespace("termplot", my.termplot, ns="stats")
assignInNamespace("termplot", my.termplot, ns="mgcv")

in all possible combinations, before attaching mgcv, after attaching mgcv, and I didn't manage to get it working.


EDIT :

I tried all options given here (apart from rebuilding either package), and couldn't get it to work. The easy way out for me is using a wrapper function. That discussion can be found here. Thanks for all the tips.


A reproducible example :

my.termplot <- function (x) print("my new termplot")

  unlockBinding("termplot", as.environment("package:stats"))
  assignInNamespace("termplot", my.termplot, ns="stats", envir=as.environment("package:stats"))
  assign("termplot", my.termplot, as.environment("package:stats"))
  lockBinding("termplot", as.environment("package:stats"))


y <- 1:10
x <- 1:10
xx <- lm(y~x)
termplot(xx)
require(mgcv)
dat <- gamSim(1, n = 400, dist = "normal", scale = 2)
b <- gam(y ~ s(x0) + s(x1) + s(x2) + x3, data = dat)
plot(b,all=TRUE)

plot.gam calls termplot for the non-smooth terms (x3 in this case), but fails to find the new termplot function.


EDIT2 : apparently, my example works. I see now I solved my own question: In the first code, I didn't add both the namespace and the package in assignInNamespace. It is important to remember to change the function both in the namespace and the package before loading the other package. Thx @hadley for pointing me in the right direction, @Marek for testing the code and reporting it works, and the rest for taking the effort to answer.

Joris Meys
  • 106,551
  • 31
  • 221
  • 263
  • Did you try use `assignInNamespace` before stats is loaded? E.g. in .Rprofile? There is note in `?assignInNamespace`: "`assignInNamespace` change the copy in the name space, but not any copies already exported from the name space" – Marek Jun 06 '11 at 16:21
  • @Joris: Please can you give an example of how you are calling `termplot`. The obvious answer is to just call `my.termplot` or your overwritten `stats::termplot`, but I guess this isn't possible for you. – Richie Cotton Jun 06 '11 at 16:40
  • 1
    @Richie @Joris isn't really calling `termplot()`, mgcv is calling `termplot()` and Joris wants a way to get mgcv to see the new version of termplot he has been assigning to the stats namespace, but can't get mgcv to see anything but the original. – Gavin Simpson Jun 06 '11 at 17:11
  • @Marek : will try. I did check though; both `termplot` and `stats:termplot` return the correct function. That's why I use the whole unlockBinding... thing. But somehow mgcv gets it somewhere else. Thx for the tip though – Joris Meys Jun 06 '11 at 18:50
  • This is going to be hard because you need to override the function in the S3 methods table. – hadley Jun 06 '11 at 23:18
  • You really should be using `assignInNamespace` because it also updates the S3 methods table. I know you say you've tried that - but I can't see any other way that could possibly work. A reproducible example would be helpful. (And make sure you're using 2.13 because this behaviour has changed lately) – hadley Jun 07 '11 at 05:44
  • Oh but it's got nothing to do with S3 - ooops. Sorry for the confusion. – hadley Jun 07 '11 at 05:45
  • @hadley : positive on 2.13, and the assignInNamespace didn't really help me much either. It's a funny problem, no clue how it comes about. – Joris Meys Jun 07 '11 at 08:11
  • @hadley : reproducible example added – Joris Meys Jun 07 '11 at 16:15
  • @JorisMeys Strange. Your example works for me as expected (i.e. call new `termplot`). Fresh session, all standard packages, WinXP, R-2.13.0, mgcv_1.7-6. – Marek Jun 09 '11 at 06:56
  • @Marek hmmm... That's very odd. Thx for letting me know. – Joris Meys Jun 09 '11 at 09:04

3 Answers3

13

I'm stumped - I can't figure out how plot.gam is locating termplot - it's not using the ordinary scoping rules as far as I can tell. This seems to need a deeper understanding of namespaces than I currently possess.

my.termplot <- function (x) print("my new termplot")

# where is it defined?
getAnywhere("termplot")
# in package and in namespace

unlockBinding("termplot", as.environment("package:stats"))
assign("termplot", my.termplot, "package:stats")

unlockBinding("termplot", getNamespace("stats"))
assign("termplot", my.termplot, getNamespace("stats"))

getAnywhere("termplot")[1]
getAnywhere("termplot")[2]
# now changed in both places

y <- 1:10
x <- 1:10 + runif(10)
xx <- lm(y ~ x)
termplot(xx) # works

library("mgcv")
b <- gam(y ~ s(x), data = data.frame(x, y))
plot(b) # still calls the old termplot

# I'm mystified - if try and find termplot as
# seen from the environment of plot.gam, it looks 
# like what we want
get("termplot", environment(plot.gam)) 
hadley
  • 102,019
  • 32
  • 183
  • 245
  • I run `plot.gam` in `debug` mode and it seems that it never call `termplot`. `termplot` is depended on `if (n.para > 0) {` condition, which in your case is `FALSE`. And `termplot(b)` works as expected. – Marek Jun 09 '11 at 06:37
  • But on Joris data work like a charm (i.e.: return error about unused parameters). – Marek Jun 09 '11 at 06:47
  • the plot for the smoothers is coded in the function plot.gam(). Termplot is called for non-smoothed terms only if I'm right, so that's why I use the example. Your code works indeed. Apparently, I needed to unlock the termplot also in the namespace. I guess... – Joris Meys Jun 09 '11 at 09:12
  • Oh I thought I tried the original example as well and neither worked. – hadley Jun 09 '11 at 14:16
  • How can you block `mgcv` loading like that? - - I do not understand how `termplot` overrides all other `plot` functions. How can you make a library and its functions persistent? – Léo Léopold Hertz 준영 May 23 '17 at 22:52
  • This seems don't work in cmd check or `devtools::test()`. I have test file and it don't change the original function if I use library("package") in test_something.R you can only access the name using package::fn() it works when used outside of cmd check. – jcubic Dec 10 '19 at 16:39
4

Try overwriting the function that you are calling termplot from. At a guess, this is plot.gam in the mgcv package.

First load the necessary package.

library(mgcv)

Here's your alternate termplot function, added to the stats namespace.

my.termplot <- function (model, ...) 
{
  message("In my.termplot")
}

unlockBinding("termplot", as.environment("package:stats"))
assign("termplot", my.termplot, as.environment("package:stats"))
lockBinding("termplot", as.environment("package:stats"))

Likewise, here's the wrapper function, added to the mgcv namespace.

my.plot.gam <- function (x, ...) 
{
  message("In my.plot.gam")
  my.termplot()
}

unlockBinding("plot.gam", as.environment("package:mgcv"))
assign("plot.gam", my.plot.gam, as.environment("package:mgcv"))
lockBinding("plot.gam", as.environment("package:mgcv"))

Here's an example to test it, taken from ?gam.

dat <- gamSim(1, n = 400, dist = "normal", scale = 2)
b <- gam(y ~ s(x0) + s(x1) + s(x2) + s(x3), data = dat)
plot(b) 
Richie Cotton
  • 118,240
  • 47
  • 247
  • 360
  • I have to overwrite both termplot and plot.gam, which causes new conflicts within the namespace of mgcv. Tried it, and all I got was more errors... – Joris Meys Jun 06 '11 at 18:48
  • With the sample code, it works. When I do this with the real code (which is, in case of plot.gam, exactly the code of the plot.gam function), I get subsequent errors from other conflicts in the namespace. Thanks for the effort though. – Joris Meys Jun 07 '11 at 08:54
2

I think the trace() function does automatically what is attempted above. Do:

trace('termplot', edit='gedit')

Where 'gedit' is the name of a text editor. The editor will open with the original code and you can paste whatever substitution code you desire.

To return to the original version just untrace('termplot')

Caveat: I tried using this when the text editor had many files open and it didn't work. So I use 'gedit', a text editor on my system that I don't use often. This way I am sure that R will open a new instance of 'gedit'.

I'm not positive this will help, but I think it's worth a try. The search sequence when there are namespaces is really confusing.

  • thanks for the tip, but it doesn't help. I get indeed the edited one in the stats package, but mgcv keeps on using the old version. – Joris Meys Jun 07 '11 at 08:55