10
$\begingroup$

I'm trying to write a custom function in Mathematica, myFunc[x]. I want to be able to define properties of the function so that the user can call myFunc[x]["Property"] and have it return the value stored in Property.

Basically I'm looking for the same functionality as lmf=LinearModelFit[data,x,x] where you can use lmf["RSquared"] to return the correlation coefficient of the fit, but lmf[5] returns the y value for x=5.

Here's an example of the function that I'm trying to define:

myFunc[data_]:=Module[
   {localProperty, x},
   localProperty = "123abc";
   Return[data+3x];
];

And the output should look like:

(* User enters normal data *)
In[1]: foo = myFunc[5]
Out[1]: 5+3x

(* User enters the name of the property *)
In[2]: foo["localProperty"]
Out[2]: "123abc"

(* User wants to enter a value and evaluate *)
In[3]: foo[3]  (* 5+3*3 *)
Out[3]: 14

Is there a way to do this? I believe it's a built-in capability, as witnessed by the symbolic FittedModel object, but all my searches have so far yielded nothing of use. I've looked at Property, Option, Attributes, and just using If statements to try and catch the cases where the user types in a property name.

I'm using Mathematica 8.

Thanks,

Edit: Solution Found

Thanks to the guys below, I've found my solution and now have a function that will fit a linear model and plot up the data in one go. I can also extract the model or model parameters by entering different options. Below is the code if anyone is interested.

Clear[ListPlotWithTrendline];
ListPlotWithTrendline[data_, addopts___] := Module[
   {lmf, lmfPlot, plot, plots, dr, lmfPlotMinX, lmfPlotMaxX, pos, pr, addopts2, lmfpr, returnValue},
   dr = {Min[data[[All, 1]]], Max[data[[All, 1]]]};

   (* The workaround to get my 'opts' variable to work with Plot *)
   addopts2 = Flatten[{addopts}, 1];
   pos = Quiet[Check[Position[Map[StringSplit[ToString[#]][[1]] &, addopts2], "PlotRange"][[1, 1]], 0]];
   pr = Quiet[Check[ToExpression[addopts2[[pos, 2]]], dr]];
   (* Routine to extract plotrange values for use in the extrapolation *)
   lmfpr = If[
     pos > 0,
     Evaluate[Which[

       (* Both X and Y ranges are given *)
       Dimensions[pr] == {2, 2}, {pr[[1, 1]], pr[[1, 2]]},

       (* The X value is All or Automatic, so we use dataRange *)
       pr[[1]] === All || pr[[1]] === Automatic || pr[[1]] === Full, dr,

       (* The X range is given and Y is Full, All, or Automatic *)
       Length[pr] == 2 && Length[pr[[1]]] == 2, {pr[[1, 1]], pr[[1, 2]]},

       (* Only the Y range is given or there was some error, so use the dataRange *)
       True, dr
       ]],
     dr];
   lmf = LinearModelFit[data, x, x];
   lmfPlot = Plot[lmf[x], {x, lmfpr[[1]], lmfpr[[2]]}, #] &@addopts2;
   plot = ListPlot[data, #] &@addopts2;
   plots = Show[plot, lmfPlot];

   (* Define the return values, based on what property the user is interested in. *)
   returnValue["FittedModel"] = lmf;
   returnValue["Plot"] = plots;
   returnValue["FitPlot"] = lmfPlot;
   returnValue["ScatterPlot"] = plot;
   returnValue["RSquared"] = lmf["RSquared"];
   returnValue["Slope"] = lmf[[1, 2, 2]];
   returnValue["Intercept"] = lmf[[1, 2, 1]];
   Return[returnValue];
   ];

I'm sure it's not the most efficient or clean code around, but it works so I'm happy. :-) Here's an example: Example of ListPlotWithTrendline

$\endgroup$
3
  • $\begingroup$ Oh crap, thanks Szabolcs. I didn't notice that I was at stackoverflow. I'll flag it. $\endgroup$
    – dthor
    Dec 13, 2013 at 20:14
  • $\begingroup$ It's not off topic here, it was just a suggestion that you post on Mathematica.SE as answers come more quickly there. $\endgroup$
    – Szabolcs
    Dec 13, 2013 at 20:16
  • $\begingroup$ Yeah that's where I normally go. But your answer below works, so yay! :-) $\endgroup$
    – dthor
    Dec 13, 2013 at 20:46

2 Answers 2

10
$\begingroup$

Here's an example that should help:

In[1]:= 
makeFoo[] := foo @@ RandomInteger[10, 3]
foo[first_, _, _]["First"] := first
foo[_, second_, _]["Second"] := second
foo[_, _, third_]["Third"] := third
foo[a_, b_, c_][x_] := a x^2 + b x + c

In[6]:= f = makeFoo[]
Out[6]= foo[4, 2, 9]

In[7]:= f["Second"]
Out[7]= 2

In[8]:= f[x]
Out[8]= 9 + 2 x + 4 x^2

For the record:

Definitions of the form f[...] := ... are called DownValues.

Definitions of the form f[...][...] := ... (or with more levels, as in f[...][...]...[...] := ...) are called SubValues (the function doesn't seem to be documented but it works exactly the same way as DownValues).

$\endgroup$
1
  • $\begingroup$ Cool, thanks. It took a little bit of work and I had to add an option for what I wanted to do, but everything's working well thanks to this answer. I now have a function that will plot up a scatterplot and the fitted line of data, and can also return the slope, Rsq, and intercept. $\endgroup$
    – dthor
    Dec 13, 2013 at 20:43
8
$\begingroup$

For your specific problem, the following piece of code will work

In[1]:=  myFunc[data_] := Module[{result},
            result["property"] = "123abc";
            result[v_] := data + v*3;
            Return[result];
         ];

In[2]:=  a=myFunc[10];
         a["property"]
         a[x]

Out[2]=  123abc
         10 + 3x
$\endgroup$
4
  • $\begingroup$ So this appears to just be another way of setting the downvalues that Szabolcs described. Would you agree? $\endgroup$
    – dthor
    Dec 13, 2013 at 20:54
  • 1
    $\begingroup$ Yes, it is! In fact, it is the same with the definition of myFunc as well. The alternative would be to return a list of rules, then you can use result/.Property to retrieve it, similar to what you would find in the return of Solve. You will find it quite powerful and convenient either way. $\endgroup$ Dec 13, 2013 at 21:30
  • 1
    $\begingroup$ You can safely replace Return[result]; by return (note the lack of semicolon). $\endgroup$
    – Szabolcs
    Dec 13, 2013 at 21:55
  • 1
    $\begingroup$ @dthor yes, this solution creates a new unique symbol (using Module) and sets DownValues on it. It's a different solution. My solution uses SubValues and the very same head (foo) every time. So here you'll have "objects" named similar to result$123 and my solution will give you objects such as foo[...]. I think both are good solutions. $\endgroup$
    – Szabolcs
    Dec 13, 2013 at 21:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.