9

I tried to modify and redefine a function (xcmsRaw) in R package xcms by first defining a function

my.xcmsRaw <- function(filename, profstep = 1, profmethod = "bin",
                    profparam = list(mzcorrf=1),    # PATCH - mzcorrf is the m/z correction factor, e.g. 0.99888 for long-chain hydrocarbons
                    includeMSn = FALSE, mslevel=NULL,
                    scanrange=NULL) { ... }

and then typing

unlockBinding("xcmsRaw", as.environment("package:xcms"))
assign("xcmsRaw", my.xcmsRaw, as.environment("package:xcms"))
lockBinding("xcmsRaw", as.environment("package:xcms"))

However, when I run it it gives me the error

Error in get(as.character(FUN), mode = "function", envir = envir) : 
  object 'profBinM' of mode 'function' was not found

caused by it not finding the profBinM function, which is a C code function defined in file xcms.c of the xcms package.

Any thoughts on how I could resolve this issue? (I am working under Windows 7, using R version 3.0.0)

Tom Wenseleers
  • 7,535
  • 7
  • 63
  • 103
  • Have you tried `assignInNamespace()`, as in, e.g., [this example](http://stackoverflow.com/questions/15505607/diagonal-labels-orientation-on-x-axis-in-heatmaps/15506652#15506652)? In your case, you'd use something like `assignInNamespace(x="xcmsRaw", value="my.xcmsRaw", ns=asNamespace("xcms"))`. – Josh O'Brien May 01 '13 at 12:18
  • Thanks for the suggestion, just tried that but it still gives me the same error unfortunately... – Tom Wenseleers May 01 '13 at 12:21
  • Interesting. Just to be precise/clear, `profBinM()` is an R function wraps a C function. – Josh O'Brien May 01 '13 at 12:30
  • Yes that function is defined in file xcms.c (in windows this would of course already be precompiled) as void ProfBinM(double *xvals, double *yvals, int *numin, int *mindex, int *nummi, double *xstart, double *xend, int *numout, double *out) { ... } – Tom Wenseleers May 01 '13 at 12:32
  • Have you tried doing `trace(xcmsRaw, edit=TRUE)` and making the changes that way? – Josh O'Brien May 01 '13 at 12:42
  • Yes, just tried and that seems to work - can't change the arguments or defaults of the function with that though – Tom Wenseleers May 01 '13 at 12:47
  • And would you know how I can make the change in the code without having to manually enter it in an editor? In my case, I would like to automatically insert the line if ((profparam$mzcorrf!=1)&length(unique(rawdata$mz - trunc(rawdata$mz)))!=1) {rawdata$mz=rawdata$mz*profparam$mzcorrf} else if (profparam$mzcorrf!=1) {print("Exact masses were already rounded to nominal masses");profparam$mzcorrf=1} after line 7 of the original function? – Tom Wenseleers May 01 '13 at 12:54
  • You could use `trace()`'s `tracer` argument, as in [this example](http://stackoverflow.com/questions/14780860/fix-typography-in-axis-labels/14784719#14784719) and [this one](http://stackoverflow.com/questions/14967813/is-there-a-function-or-package-which-will-simulate-predictions-for-an-object-ret/14967981#14967981). For some ways to find the right value for the `at` argument, see the answers -- and Michael Hoffman's in particular -- to [this question](http://stackoverflow.com/questions/11319161/what-is-a-fast-way-to-set-debugging-code-at-a-given-line-in-a-function). – Josh O'Brien May 01 '13 at 13:09
  • Many thanks for this! Got it working now! Would still be nice to get it working via assignInNamespace() too though, as that would allow for more extensive edits/redefinitions... – Tom Wenseleers May 01 '13 at 13:15
  • 1
    In order to get the original approach involving `assignInNamespace()` working, you could try changing the `environment()` of `my.xcmsRaw` before as detailed here: https://stackoverflow.com/a/58238931/3930713 – Quasimodo Jan 27 '23 at 01:04

1 Answers1

5

Thanks Josh - in my case I got it working now via

modifline='if ((profparam$mzcorrf!=1)&length(unique(rawdata$mz - trunc(rawdata$mz)))!=1) {rawdata$mz=rawdata$mz*profparam$mzcorrf} else if (profparam$mzcorrf!=1) {print("Exact masses were already rounded to nominal masses");profparam$mzcorrf=1}'
insertatline=6
trace(xcmsRaw, tracer=modifline,at=c(insertatline))

where I found the correct line to insert my modified code using

as.list(body(xcmsRaw))

To suppress the output of trace I then defined a second function

xcmsRaw2=function(...) {sink("NUL");obj=xcmsRaw(...);sink();return(obj) }

which can the be called and which does not provide any unnecessary tracing output.

Would still be nice to get it working via assignInNamespace() too though, as that would allow for more extensive edits/redefinitions and also for changes in the function arguments (which would be a common reason to redefine functions, that is, to take some extra argument)...

Tom Wenseleers
  • 7,535
  • 7
  • 63
  • 103
  • Great! I agree, this feels like a hack, but its prob. a lot better than nothing until you hit on a more flexible/elegant solution. – Josh O'Brien May 01 '13 at 13:18
  • Oh yes, and is there any way actually to suppress the output of trace? Now each time I call xcmsRaw it will print "Tracing xcmsRaw(files[samplenr], profstep = profst, profmethod = "bin", .... step 6" - but I would like to suppress this output. Is there any way to do this? – Tom Wenseleers May 02 '13 at 11:36
  • My only idea would be to wrap your call to `xcmsRaw()` in `suppressMessages()`. If that works, you *could* write a very thin wrapper function -- maybe call it `xcmsRaw2` -- that passes through all arguments to a `suppressMessages(xcmsRaw(...))`. But remember, that would only work for direct calls to `xcmsRaw`, not calls from other functions in the package. Alternatively, you could just modify the package sources and recompile a version containing your new-and-improved `xcmsRaw` ;) – Josh O'Brien May 02 '13 at 14:28
  • Strangely enough if I define xcmsRaw2=function(...) {suppressMessages(xcmsRaw(...))} I still get Tracing xcmsRaw(...) step 6 as output, so doesn't seem to work... So yes, will have to recompile, but that will be a bit of a mess since I would like to package it together with my package, and then I'll have to figure out what code I will and will not have to copy across... – Tom Wenseleers May 02 '13 at 15:53
  • Ha but xcmsRaw2=function(...) {sink("NUL");obj=xcmsRaw(...);sink();return(obj) } did the trick... Thx for that! – Tom Wenseleers May 02 '13 at 16:07
  • Oh, of course. For `suppressMessages()` to work, you would have to have used `message()` rather than `print()` to output the notification about "Exact masses ...". Come to think of it, that actually might be the cleaner solution. Cheers! – Josh O'Brien May 02 '13 at 16:26
  • One more thought. As for redefining the function in the namespace with different/additional arguments, I *believe* that R's designers have intentionally made that difficult or maybe impossible. My hunch is that that's an invitation to such havoc (for any other pre-existing functions or code that relied on the function) that you're being not-so-gently nudged by R's developers to find other solutions! – Josh O'Brien May 02 '13 at 16:34
  • Yes it does of course seem like a terrible hack... The easy option would have been if the xcms developers would have liked my extension and implemented it, but seems they don't, hehe... :-) – Tom Wenseleers May 02 '13 at 16:43