5
$\begingroup$

Lets say I have a two dimensional list:

list = {{a, b, c}, {aa, bb, cc}, {x, y, z}, {xx, yy, zz}};

and two pure functions:

f1 = Total[#]/Length[#] &;
f2 = Total[##]/Length[##] &;

I want to learn how to apply these functions over rows and columns of a list using Map, MapThread, MapAt and Apply. Here:

f1 /@ list 

produces, as expected,

enter image description here.

But, why MapThread does not work as it supposed to work in MapThread[f1, list]?

Similarly, f1 @@ list works as expected but f1 @@@ list does not work, why?

MapAt[f1, list, 2] works fine but MapAt[f1, list, {All, 2}] does not work, why?

What is the most efficient way to apply these pure functions to columns of the list without Transpose? My goal is to learn how to apply pure functions column wise most efficiently. I would appreciate general answer.

$\endgroup$
8
  • $\begingroup$ Should be MapThread[f1, {list}]. Length @@@ list will shed also some light on the issue. $\endgroup$ Commented Jul 12, 2019 at 23:12
  • $\begingroup$ To apply to a column, for example f1 /@ {list[[All, 2]]} yields {1/4 (b + bb + y + yy)} $\endgroup$
    – MelaGo
    Commented Jul 12, 2019 at 23:15
  • $\begingroup$ Both MapThread[f1, list] and MapAt[f1, list, {All, 2}] work as I expect them to. -- What did you expect? [Somewhere in the world there are animations that show how some of these functions work. I found out about the on this site. I cannot find them now, though. They might be helpful, if someone can locate them.] $\endgroup$
    – Michael E2
    Commented Jul 13, 2019 at 0:35
  • $\begingroup$ Perhaps try MapThread[f, list] and MapAt[f, list, {All, 2}] with an undefined function f to see where the arguments come from. $\endgroup$
    – Michael E2
    Commented Jul 13, 2019 at 0:48
  • $\begingroup$ @ Michael, thank you for your feedback. MapThread[f, list]={f[a, aa, x, xx], f[b, bb, y, yy], f[c, cc, z, zz]} and MapThread[f, {{a, b, c}, {x, y, z}}]={f[a, x], f[b, y], f[c, z]}. I would expect the function be applied to each column. I might be missing something here. $\endgroup$
    – DLT
    Commented Jul 13, 2019 at 3:02

3 Answers 3

6
$\begingroup$

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)}

$\endgroup$
1
  • $\begingroup$ @ ramesh, thank you. $\endgroup$
    – DLT
    Commented Jul 13, 2019 at 15:01
5
$\begingroup$

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?):

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.

$\endgroup$
3
  • $\begingroup$ @ Michael, I cannot imagine how thankful I am. I really appreciate it for your time. $\endgroup$
    – DLT
    Commented Jul 14, 2019 at 23:28
  • $\begingroup$ @DLT You're welcome. $\endgroup$
    – Michael E2
    Commented Jul 15, 2019 at 0:00
  • $\begingroup$ @DLT It just occurred to me that MapAt[f, list, {2, All}] is the corresponding row version of the column operation MapAt[f, list, {All, 2}]. In that case, I think the symmetry of the code is reflected in the symmetry of the operations, which may be the sort of correspondence you were hoping to find in the other operations. $\endgroup$
    – Michael E2
    Commented Jul 15, 2019 at 0:20
4
$\begingroup$

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.

$\endgroup$
1
  • $\begingroup$ @ Henrik, I am not sure how this is different from Total[list]/Length[list]. Any help on MapThread and @@@ for the pure function would be appreciated. $\endgroup$
    – DLT
    Commented Jul 13, 2019 at 0:24

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.