Pure functions applied to a two dimensional list

Look at how Map and MapThread are applied to a two-dimensional list.

Map[f, list]

{f[{a, b, c}], f[{aa, bb, cc}], f[{x, y, z}], f[{xx, yy, zz}]}

and MapThread[f, list]

{f[a, aa, x, xx], f[b, bb, y, yy], f[c, cc, z, zz]}

Total[#]/Length[#] & /@ list

{1/3 (a + b + c), 1/3 (aa + bb + cc), 1/3 (x + y + z), { 1/3 (xx + yy + zz)}

Similarly,

MapThread[Total[List[##]]/Length[List[##]] &, list]

{1/4 (a + aa + x + xx), 1/4 (b + bb + y + yy), 1/4 (c + cc + z + zz)}

Total[List[##]]/Length[List[##]] & @@ list

{1/4 (a + aa + x + xx), 1/4 (b + bb + y + yy), 1/4 (c + cc + z + zz)}

An efficient way to apply a function over a column of a list would be

Total[##]/Length[##] &@list[[All, {1, 3}]]

{1/4 (a + aa + x + xx), 1/4 (c + cc + z + zz)}


I'm sure that how the functions Map, MapThread, MapAt and Apply work are explained in the documentation and on site, at least in the course of answering some other question; nonetheless, I'll try to give an elementary explanation of how they work. It seems to me that the emphasis of the OP on "pure functions" is a red herring. There is no real difference in applying any f as a function to list, except for the values that the function computes. And since f2 is not used in the question at all, it seems probable that the OP is not asking about the difference between Slot[1] (#) and SlotSequence[1] (##).

Animated illustrations of some applications of these functions can be found at the following webpage (which I found in Where can I find examples of good Mathematica programming practice?):

  • https://reference.wolfram.com/legacy/flash/

A difference between mathematical matrices and "matrices" in Mathematica is that in Mathematica they are expressions with a structure. Unlike mathematics, the structure is not symmetric with respect to rows and columns (hence the highly popular Elegant operations on matrix rows and columns). The functions in question operate on the structure of expressions. Two important concepts in the structure of expressions are the notions of Levels and Parts. Here are the FullForm and the levels of the OP's list:

FullForm@list
(*
  List[
   List[a,  b,  c ],
   List[aa, bb, cc],
   List[x,  y,  z ],
   List[xx, yy, zz]
   ]
*)

Mathematica graphics

Map, MapThread, and Apply all have a Level specification argument; MapAt has a Part specification argument. When we try to work with the columns of a matrix through these functions, we tend not to get a List (or vector) of elements to work with; we tend to get a Sequence of elements to work with.

It's easy to get rows by using parts: list[[2]] yields the second row; list[[{3, 1}]] yields a list of rows, the third and first in that order. It's easy to get a single column as a List by using parts: list[[All, 1]] yields the column vector {a, aa, x, xx}. However, to get many columns is less convenient (if we don't use Transpose):

list[[All, {3, 2}]]               (* parts 3 and 2 of All rows *)
(*  {{c, b}, {cc, bb}, {z, y}, {zz, yy}}  *)

list[[All, {3, 2}]] // Transpose  (* list of columns 3 and 2 *)
(*  {{c, cc, z, zz}, {b, bb, y, yy}}  *)

Now let's go back to our functions. For Apply, applying f @@ list replaces level 0 (a single List) with f and f @@@ list replaces level 1 (four List heads) with f:

f @@ list
(*
  f[
   {a,  b,  c },
   {aa, bb, cc},
   {x,  y,  z },
   {xx, yy, zz}
   ]
*)

f @@@ list
(*
  List[
   f[a,  b,  c ],
   f[aa, bb, cc],
   f[x,  y,  z ],
   f[xx, yy, zz]
 ]
*)

In this instance MapThread might be viewed as performing a sort of transposed f @@@ list:

MapThread[f, list]
(*
  List[
   f[a, aa, x, xx],
   f[b, bb, y, yy],
   f[c, cc, z, zz]
   ]
*)

MapThread takes the parts of each list list[[1]],..., list[[4]] and uses the corresponding parts as a sequence of arguments to f:

      list[[1]]  list[[2]]  list[[3]]  list[[4]]
{ f[      a,        aa,        x,        xx     ],
  f[      b,        bb,        y,        yy     ],
  f[      c,        cc,        z,        zz     ]  }

As for MapAt, MapAt[f, list, {2}] operates on parts at level 1 (because the length of {2} is 1); likewise MapAt[f, list, {All, 2}] operates on parts at level 2 (because the length of {All, 2} is 2). In the first case, we get f of the second row, which is part {2} of list; in the second case, we get f of the second part of all the rows, which are parts {1, 2}, {2, 2}, {3, 2}, and {4, 2}:

MapAt[f, list, {2}]
(*
  List[
      {a,  b,  c},
   f[ {aa, bb, cc} ],
      {x, y, z},
      {xx, yy, zz}
   ]
*)

MapAt[f, list, {All, 2}]
(*
  List[
   {a,  f[ b  ], c },
   {aa, f[ bb ], cc},
   {x,  f[ y  ], z },
   {xx, f[ yy ], zz}
 ] 
*)

Side note: As for the efficiency of the computation intended by the OP's f1, I don't think anything can arguably beat this:

Mean[list]
(*  {1/4 (a + aa + x + xx), 1/4 (b + bb + y + yy), 1/4 (c + cc + z + zz)}  *)

It's equivalent in speed to Total[list]/Length[list] on packed arrays, much faster on unpacked arrays, and much easier to understand.


One of the most efficient ways (for machine precision data in a packed array) would be

Total[list, {1}]/Length[list]

{1/4 (a + aa + x + xx), 1/4 (b + bb + y + yy), 1/4 (c + cc + z + zz)}

What is special here is that Total has a two-argument version that allows you to choose the dimenension of a tensor to sum over.