Please help - I'm really struggling to understand why this evaluates with errors.

data = RandomReal[10, {4, 1000}];
metric[data_, perc_] := (x = Quantile[#, perc] & /@ data; Accumulate[x]/Total[x]);
Plot[Evaluate@metric[data, p], {p, 0.01, 0.99}, AxesLabel -> {"Percentile", "Proportion"},  PlotLegends -> {"Series A", "Series A+B", "Series A+B+C", "Series A+B+C+D"}]

Basically I need to see the results from the function as 4 separate curves in 4 colours. Placing the "metric" function inside the Plot means that it plots everything as one function, which doesn't help (although it evaluates error free). In line with other answers on here (here and others) I recognise I have to either Evaluate it or Apply the metric to the Plot pure function, but then I get a Quantile error (and critical/"fall over" errors with some of the other more complex metrics I'm using). I've tried using default values, Holds, matching patterns on when perc doesn't exist etc etc but to no avail. I'm losing years off my life trying to get this to work so any help would be really appreciated...


2 Answers 2


Would you find this acceptable?

data = RandomReal[10, {4, 1000}];
metric[d_, perc_] := Accumulate[#]/Total[#] &[Quantile[#, perc] & /@ d]
ListLinePlot[Transpose[Table[{p, #} & /@ metric[data, p], {p, 0.01, 0.99, .0001}]], 
   AxesLabel -> {"Percentile", "Proportion"}, 
   PlotLegends -> {"Series A", "Series A+B", "Series A+B+C", "Series A+B+C+D"}]

This should have a sufficiently small step size, and your legends display correctly. I also modified your metric[] function so that it doesn't reuse the already defined variable data, and it doesn't create an extra variable x.

  • $\begingroup$ Thanks. Yes, it runs faster but at the expense of losing all the granularity when the function changes rapidly (which it does in the early part of this specific metric). I guess if there is no way to solve the unknown problem (of which the quantile error is a symptom) then I'll have to do this but it feels pretty unsatisfactory (and means the plot may miss some features). And thanks re the metric redefinition, I'll use that. $\endgroup$
    – Adrian
    Jan 8, 2014 at 11:58
  • $\begingroup$ If you want less granularity at the beginning, then use a smaller step size. It still runs faster. I can use a step value of 0.0001 and get about the same performance as you were getting with Plot[]. That is 100 times more fine than the example I gave you, with 9800 data points. However, I should caution that the x-axis labeling is incorrect. It shouldn't be hard to fix. $\endgroup$
    – heropup
    Jan 8, 2014 at 12:03
  • $\begingroup$ I have edited the code per your feedback. $\endgroup$
    – heropup
    Jan 8, 2014 at 12:10

Post-generation styling

If I understand correctly Plot works correctly in your application (without Evaluate) with the exception of the line styling. If that is true you merely need to add the styling afterward. See Is it possible to change the color of plot in Show?. A variation of my restylePlot function from my answer there:

stylePlot[plot_Graphics, styles_List, op : OptionsPattern[Graphics]] :=
  Module[{x = styles, sfun},
    sfun = {Directive @ Last[x = RotateLeft @ x], #} &;
    Show[MapAt[# /. {__, ln__Line} :> sfun /@ {ln} &, plot, 1], op]

Example of use:

pl = Plot[metric[data, p], {p, 0.01, 0.99}, AxesLabel -> {"Percentile", "Proportion"}];

stylePlot[pl, {Red, Green, Blue, Purple}]

enter image description here

Intermediary function

As described in Plot draws list of curves in same color when not using Evaluate Plot styles based on the explicit structure of its first argument, before evaluation. If it is not possible to evaluate your plot function without error, and you do not find post-generation styling acceptable, you will need to use some intermediary function to present your data, before evaluation, in a form that Plot will recognize. Here is a basic example of the form I am describing. (By the way, I am rewriting your metric function to properly localize x.)

data = RandomReal[10, {4, 1000}];
metric[data_, perc_] := With[{x = Quantile[#, perc] & /@ data}, Accumulate[x]/Total[x]];

f[x_] := f[x] = metric[data, x];

 {p, 0.01, 0.99}, 
 AxesLabel -> {"Percentile", "Proportion"}

enter image description here

Here f exists merely to memoize metric, which prevents quadruple evaluation of every p value. The key is that there are four expressions in a List before evaluation, and each expression will evaluate to that part of the data as the Plot progresses. Any method that achieves this will work. Here is a different formulation with the memoizing made part of metric, and the function f serves to simplify programmatically generating the list of expressions for the first argument of Plot:

ClearAll[metric, f]

mem : metric[data_, perc_] := 
  mem = With[{x = Quantile[#, perc] & /@ data}, Accumulate[x]/Total[x]];

f[x_?NumericQ, n_] := metric[data, x][[n]]; 

Plot[Array[f[p, #] &, 4], {p, 0.01, 0.99}, AxesLabel -> {"Percentile", "Proportion"}, 
  Evaluated -> True]

I use Evaluated -> True because it localizes the Plot variable p, which Evaluate does not.

  • $\begingroup$ Thanks. The issues are that all lines are taken to be one function, so the 4 legends don't display (only the first does), and I can't do the normal styling (such as filling between two lines etc). From your solution I'd work around the colour issues, but I would guess legends etc would be wrong. I'd hoped that there was a simple change (either to the evaluation or to the metric function) which wouldn't imply a fudge, as many other functions work fine it's just some of these that don't. Maybe elegance is a casualty here and I'll have to go with a compromised approach. $\endgroup$
    – Adrian
    Jan 8, 2014 at 16:08
  • $\begingroup$ @Adrian I am not aware of any elegant solution; because of the mechanics of Plot I do not think there is one. Nevertheless I added another method to my post that may be closer to what you desire, and hopefully it also illustrates the cause of the problem in the first place. $\endgroup$
    – Mr.Wizard
    Jan 9, 2014 at 10:47

Your Answer

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

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