29

I'd like to get the Min, Max, Median, Mean, etc. for the same list. For now I'm doing the following:

y = {1, 2, 3, 4, 5, 6, 7};
Map[{Max[#] , Min[#] , Median[#], Mean[#]} &, y, {0}]

It seems like there should be a better way, not that this is awful. Is there a cleaner way to do this?

J. M.'s missing motivation
  • 124,525
  • 11
  • 401
  • 574
Mitchell Kaplan
  • 3,696
  • 22
  • 34

9 Answers9

42

Also,

y = {1, 2, 3, 4, 5, 6, 7};

#[y] & /@ {Max, Min, Median, Mean}

(*  {7, 1, 4, 4}  *)

EDIT: comparing the timings:

n = 100000;

Do[Through[{Max, Min, Median, Mean}[y]], n] // AbsoluteTiming

(*  {0.548089, Null}  *)

Do[#[y] & /@ {Max, Min, Median, Mean}, n] // AbsoluteTiming

(*  {0.709574, Null}  *)

Through is more efficient, at least in this case.

Bob Hanlon
  • 157,611
  • 7
  • 77
  • 198
  • 4
    I use this more often than Through. – Mr.Wizard Feb 19 '16 at 06:42
  • 1
    @Mr.Wizard any reasons, or just personal preference? – LLlAMnYP Feb 19 '16 at 09:33
  • It never occurred to me that I could do that with map. I never put the # in place of a function. In retrospect I don't know why. – Mitchell Kaplan Feb 19 '16 at 12:32
  • @LLlAMnYP I use Map for so much else that it is very familiar, whereas Through still takes a moment of thought. More importantly this works with held arguments, e.g. #[2 + 2] & /@ {Hold, HoldForm, Defer, MakeBoxes}. And as nearly everyone knows I like terse coding and this is a few keystrokes shorter. – Mr.Wizard Feb 19 '16 at 17:54
  • @Mr.W, ahh, how right you are, & has the attribute HoldAll. Convenient. Sometimes, of course, the opposite behavior may be desired. – LLlAMnYP Feb 19 '16 at 17:57
  • @LLlAMnYP That's also a good point, e.g. #[y] & /@ {Max, Min, Mean, Hold} returns Hold[y] which might not be intended. If evaluation is needed however it is easy, e.g. # @@ {y} & /@ {Max, Min, Mean, Hold}, and that is faster to change than rewriting to use Through would be, at least for me. – Mr.Wizard Feb 19 '16 at 18:19
  • @Mr.Wizard was, hä? Your last two examples return exactly the same output. I was referring to the difference of Through[{Hold}[2 + 2]] -> {Hold[4]} and #[2 + 2] & /@ {Hold} -> {Hold[2+2]} – LLlAMnYP Feb 19 '16 at 18:27
  • @LLlAMnYP I meant using the definition of y in Bob's answer. More explicitly: #[2 + 2] & /@ {Hold} versus # @@ {2 + 2} & /@ {Hold} – Mr.Wizard Feb 19 '16 at 18:30
  • @Mr.Wizard Oh, right, didn't realize that. Yes, we're talking about the same thing. But when optimizing code, I become really pedantic about an Apply here, and a Through there and so on, there it starts to count. Much like a debate you had once with Leonid about the most efficient form of the vanishing function. – LLlAMnYP Feb 19 '16 at 18:33
34

You can use Through.

Through[{Max, Min, Median, Mean}[y]]

(* {7, 1, 4, 4} *)

Hope this helps.

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
Edmund
  • 42,267
  • 3
  • 51
  • 143
  • 4
    I've always had trouble getting the syntax of Through correct, since to me it seems more natural if it were instead Through[{Max, Min, Median, Mean}][y] – murray Feb 19 '16 at 01:49
  • 2
    @murray You can use Prefix if it feels more natural: Through@{Max, Min, Median, Mean}[y] – Edmund Feb 19 '16 at 03:44
  • Thanks! - I had tried through, but I used it wrong. I tried to map or apply it. – Mitchell Kaplan Feb 19 '16 at 12:30
21

Query offers a reasonable syntax for this case:

y = {1, 2, 3, 4, 5, 6, 7};

y // Query[{Max, Min, Median, Mean}]
(* {7, 1, 4, 4} *)

Query has the nice feature that we can apply such lists of functions at deeper levels without too much additional thought:

ys = {{1, 2, 6}, {4, 5, 9}, {10, 20, 60}};

ys // Query[All, {Max, Min, Median, Mean}]
(* {{6, 1, 2, 3}, {9, 4, 5, 6}, {60, 10, 20, 30}} *)
WReach
  • 68,832
  • 4
  • 164
  • 269
  • 1
    I did not realize Query could be used like that! +1 of course. However it is not a general replacement for Through as the output is different, e.g. "x" // Query[{Max, Min, Median, Mean}] returns {"x", "x", Missing["Indeterminate"], Missing["Indeterminate"]}, and it is much slower than Through or Map, undoubtely related to (56609) – Mr.Wizard Feb 19 '16 at 21:05
  • 2
    @Mr.Wizard Your points are absolutely valid, but as usual you and I have different pain thresholds when labelling something as "much slower" :D Using Normal @ Query[...] will eliminate both the semantic differences and the performance differences since the query will then be compiled down to a wafer-thin wrapper over Through (the canonical answer). But I find myself using Query as-is with increasing frequency these days because its one-stop-shopping syntactic convenience usually overshadows the other considerations (for me, YMMV). – WReach Feb 19 '16 at 23:57
15

I prefer:

{Max[#], Min[#], Median[#], Mean[#]} & @ y

Clean, simple, and elegant.

David G. Stork
  • 41,180
  • 3
  • 34
  • 96
13

murray wrote:

I've always had trouble getting the syntax of Through correct, since to me it seems more natural if it were instead Through[{Max, Min, Median, Mean}][y]

each[x : _[__]][arg__] := Through[ x @ arg ]

foo // each[bar[a, b, c]]

   (* out:   bar[a[foo], b[foo], c[foo]]   *)

Sequence[foo, bar] // each[{a, b, c}]

   (* out:   {a[foo, bar], b[foo, bar], c[foo, bar]}   *)

I rather like that idea. Thanks, murray.

Comments below Bob Hanlon's answer remind me one thing this lacks as written is the ability to work with held arguments, which #[y] & /@ {f1, f2, . . .} has by nature. If I am going to actually use this abstraction I will need to address that. One possibility:

ClearAll[each]

each[x : _[__]] := Function[, Through @ Unevaluated @ x[##], HoldAll]

Now:

2 + 2 // each[{Hold, HoldForm, Defer, MakeBoxes}]

   (* out:   {Hold[2 + 2], 2 + 2, 2 + 2, RowBox[{2, +, 2}]}  *)

Update: also notably this case which is a bit harder to get with Map:

2 + 2 // each[ Hold[foo, bar, baz] ]

   (* out:   Hold[foo[2 + 2], bar[2 + 2], baz[2 + 2]]   *)
Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
4

And (so far), no one has suggested the right solution.

If you have a bundle of operations that you want to reuse, define a function.

stats[x_List]:= {Max[#] , Min[#] , Median[#], Mean[#]}& [x]
...

y = {1, 2, 3, 4, 5, 6, 7};
stats[y]
(* {7, 1, 4, 4} *)
Eric Towers
  • 3,040
  • 12
  • 14
  • Why not stats[x_List] := #[y] & /@ {Max, Min, Median, Mean}, especially if you want to extend the list of stats? – garej Feb 21 '16 at 07:12
  • @garej: Habit of mind: morphisms go to the left of the objects they act on. (If I were an adherent to certain European algebraist schools, I'd feel the other way about it.) – Eric Towers Feb 21 '16 at 19:19
  • Why not just stats[x_List] := {Max[x], Min[x], Median[x], Mean[x]} ? – PaulCommentary Jul 11 '20 at 22:01
  • @PaulCommentary : Habit of mind: If expression x has side effects or if expression x takes significant resources to compute, repeatedly evaluating it is foolish. To make it very clear what is guarded against, contrast Clear[stats]; stats[x_] := {Max[#], Min[#], Median[#], Mean[#]} &[Activate[x]]; stats[y = 3; Inactive[Table[y++, {3}]]] with Clear[stats]; stats[x_] := {Max[Activate[x]], Min[Activate[x]], Median[Activate[x]], Mean[Activate[x]]}; stats[y = 3; Inactive[Table[y++, {3}]]]. – Eric Towers Jul 12 '20 at 16:02
  • Good answer @Er – PaulCommentary Jul 12 '20 at 19:15
4

As of 2022 we have at our disposal the resource function called ThroughOperator that can do that. This is a development thanks to @Sjoerd Smit.

It was first suggested here. In the comment section under the answer @Sjoerd Smit gives motivation for its development and subsequent use for those interested. It was further used in this thread.

The way it works for the example at hand is the following:

ResourceFunction["ThroughOperator"][{Max, Min, Median, Mean}]@{1, 2, 
  3, 4, 5, 6, 7}

l

bmf
  • 15,157
  • 2
  • 26
  • 63
2

As of version 14.0, we can also use Comap:

y = {1, 2, 3, 4, 5, 6, 7};

Comap[{Max, Min, Median, Mean}, y] (* {7, 1, 4, 4} *)

Comap[{Max, Min, Median, Mean}]@y (* {7, 1, 4, 4} *)

enter image description here

xzczd
  • 65,995
  • 9
  • 163
  • 468
  • 1
    This new function is so slow, e.g. funlist=Array[f,10^5]; Comap[funlist,x];//RepeatedTiming Comap[funlist]@x;//RepeatedTiming Through[funlist[x]];//RepeatedTiming Query[funlist]@x;//RepeatedTiming Map[x,funlist];//RepeatedTiming – Lacia Jan 15 '24 at 05:35
1

Another way using Table and Operate:

y = {1, 2, 3, 4, 5, 6, 7};
funcs = {Max, Min, Median, Mean};

Table[Operate[funcs[[i]] &, {y}], {i, Length@funcs}]

({7, 1, 4, 4})

E. Chan-López
  • 23,117
  • 3
  • 21
  • 44