I wanted to produce a list of statistics labelled with the statistic name, for exchange with a database.

I defined

defaultStatsHeadings = {"Mean", "Variance", "Skewness", "Kurtosis", "excessKurtosis", "GeometricMean", "Median"};

(where excessKurtosis is user-defined)

and then a function that would apply these as function names to a set of data

(* Most functions are happy, but GeometricMean complains that what is passed is not 
non-empty (i.e. its empty because it's on Hold)... but calculates when released -> Just 
quieten that message *)
statsByHeadings[data_, headings_:defaultStatsHeadings]:= 
                    ToExpression[ToString[statsHeadings[[i]]]<>"["<>ToString[Hold[data]]<>"]"], {i,1,Length[headings]}

(where the Re[] is only really for GeometricMean if the result is complex)

This works, but on application to a large dataset (~1 million values) it is almost two orders of magnitude slower than the direct application of the individual functions.

I think what is happening is that where I wanted to refer to the data by symbol I am actually referring to it by values - and the ToString is converting 1 million values for each call.

So, this was clearly a naive and bad way to achieve my objective.

Question how should/could I achieve the application of functions by name strings as described and how does the right approach work, i.e. I'd like to understand better the use of Hold etc. in this context


I don't see that any of the Hold tricks are necessary here at all. Isn't the following already doing what you need?

statsByHeadings[data_, headings_: defaultStatsHeadings] :=

Are you aware of the fact that it is completely supported to collect your "headings" as a list of symbols (or if you like functions) instead of their names as strings? That would make the code even more straightforward:

defaultStatsFunctions= {Mean, Variance, Skewness, Kurtosis, excessKurtosis, GeometricMean, Median};

statsByHeadings[data_, functions_: defaultStatsFunctions] := Re[Through[functions[data]]];

I would also suggest to think about returning an association which will make it easier to identify which number was for which function. And if you need Re only in one case, I would only apply it for that case, which could be done like this:

statsByHeadings[data_, statsFunctions_: defaultStatsFunctions] := 
      (statsFunctions /. GeometricMean -> (Re[GeometricMean[#]] &))[data]

You should also note that this definition will make it possible to use pure functions or function compositions (@* is the shortcut for Composition) in the list of functions, e.g. you could do something like:

 defaultStatsFunctions = {Mean, Variance, Skewness, Kurtosis, 
    excessKurtosis, Re@*GeometricMean, Median};

 statsByHeadings[data_, statsFunctions_: defaultStatsFunctions] := 
    AssociationThread[statsFunctions, Through[statsFunctions[data]]]
| improve this answer | |
  • $\begingroup$ I was thinking strings->functions; clearly better to think functions first then get their names as strings by SymbolName. As for the rest..."Through" yet another function I hadn't encountered; exactly the sort of revelatory simplification I was looking for - thank you; nonetheless, I'll wait the customary 24hrs before accepting, just in case... $\endgroup$ – Julian Moore Nov 3 '18 at 13:11
  • $\begingroup$ Thanks also for the Association idea. Also introduced me to "@*", which can''t be searched for in the documentation, but, for other's benefit = Composition. $\endgroup$ – Julian Moore Nov 4 '18 at 7:28
  • $\begingroup$ @JulianMoore: good point, I added that information about Composition to the answer. Thanks for the accept $\endgroup$ – Albert Retey Nov 4 '18 at 19:40

Define the function like this:

defaultStatsHeadings = {Mean, Variance, Skewness, Kurtosis, excessKurtosis, GeometricMean, Median};

Now the Quiet is unnecessary as it will be evaluated instantly. There is no need for the Hold Then:

statsByHeadings[data_, headings_:defaultStatsHeadings]:= 

This will give the correct results.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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