18
$\begingroup$

Given a matrix, we want to subtract the mean of each column, from all entries in that column. So given this matrix:

 (mat = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}) // MatrixForm

Mathematica graphics

the mean of each column is m = Mean[mat].

Mathematica graphics

So the result should be

Mathematica graphics

This operation is called centering of observations in data science.

The best I could find using Mathematica, is as follows:

mat = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
m = Mean[mat];
(mat[[All, #]] - m[[#]]) & /@ Range@Length@m // Transpose

But I am not too happy with it. I think it is too complicated. Is there a simpler way to do it? I tried Map and MapThread, but I had hard time getting the syntax to work.

In MATLAB, there is a nice function called bsxfun which is sort of like MapThread. Here is how it is done in MATLAB:

A=[1 2 3 4;5 6 7 8;9 10 11 12];
bsxfun(@minus,A,mean(A))

    -4    -4    -4    -4
     0     0     0     0
     4     4     4     4

It maps the function minus, taking one column from A and one element from mean(A). I think it is more clear than what I have in Mathematica. One should be able to do this in Mathematica using one of the map functions more easily and clearly than what I have above.

The question is: Can the Mathematica solution above be improved?

$\endgroup$

11 Answers 11

22
$\begingroup$
mat - ConstantArray[Mean[mat], 3]

or more generally:

mat - ConstantArray[Mean[mat], Length[mat]]
$\endgroup$
29
$\begingroup$

It is there:

Standardize[mat, Mean, 1 &]
$\endgroup$
  • $\begingroup$ Here comes the winner! $\endgroup$ – xzczd Jan 7 '16 at 10:57
  • $\begingroup$ @xzczd Thanks. It may be not the fastest though, it rescales by 1 anyway. $\endgroup$ – Kuba Jan 7 '16 at 11:39
  • $\begingroup$ Can use Identity' instead of 1&. $\endgroup$ – TheDoctor Jan 13 '16 at 13:19
  • $\begingroup$ Re "rescales by 1": see my answer to this Q&A. $\endgroup$ – Michael E2 Jul 3 '17 at 15:10
  • $\begingroup$ @MichaelE2 thanks for the investigation! $\endgroup$ – Kuba Jul 4 '17 at 21:48
23
$\begingroup$

Well, transposing, subtracting, transposing...

Transpose[Transpose[mat] - Mean[mat]]
$\endgroup$
12
$\begingroup$
# - Mean@mat & /@ mat // MatrixForm

enter image description here

$\endgroup$
  • $\begingroup$ This computes the same Mean for each row - could be inefficient. $\endgroup$ – shrx Jan 8 '16 at 9:29
11
$\begingroup$

If you don't mind the type of mat changes:

CircleMinus = Compile[{{a, _Real, 1}, {b, _Real, 1}}, a - b, RuntimeAttributes -> Listable]
mat⊖Mean@mat

$\left( \begin{array}{cccc} -4. & -4. & -4. & -4. \\ 0. & 0. & 0. & 0. \\ 4. & 4. & 4. & 4. \\ \end{array} \right)$

$\endgroup$
7
$\begingroup$

I was asked to post an answer based on a recently uncovered (at least, for me) undocumented function, Statistics`Library`MatrixRowTranslate, available in V10 and later. My feeling is that, at heart, it is essentially the same as Kuba's answer, which I will use this answer to explain. I will also shed some further light on Kuba's comment.

First, the function Statistics`Library`MatrixRowTranslate modifies the matrix in place, which is uncharacteristic of Mathematica. Here is a typical usage applied to the OP's problem:

mat = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
Module[{res = mat},
  Statistics`Library`MatrixRowTranslate[res, -Mean[res]];
  res] // MatrixForm

Mathematica graphics

In V11 (but not in V10, though the functionality exists), Kuba's Standardize[mat, Mean, 1 &] uses this call to shift mat by the mean, but it also rescales by the second function, which seems inefficient for the problem at hand. However, it turns out that rescaling by constant & is a special case that is implemented by dividing the translated matrix by constant. In case you didn't know, multiplying by 1 (not 1.) is also a special case, which the comparison below will show. Dividing by 1 in normal Mathematica means multiplying by the reciprocal Power[1, -1] and takes roughly twice as long as multiplication, unless you use Divide.

SeedRandom[0];
data = RandomReal[{0, 50}, {1*^7, 2}];
1*data; // RepeatedTiming
data/1; // RepeatedTiming
Divide[data, 1]; // RepeatedTiming
1.*data; // RepeatedTiming
data/2; // RepeatedTiming
(*
  {3.01*10^-7, Null}
  {5.2*10^-7, Null}
  {3.8*10^-7, Null}
  {0.063, Null}
  {0.062, Null}
*)    

So we should expect that the rescaling by 1 & in Kuba's answer to add a negligible amount of time to the overall time of the operation in V11:

mrt = Module[{res = data},
    Statistics`Library`MatrixRowTranslate[res, -Mean[res]];
    res]; // RepeatedTiming
(*  {0.139, Null}  *)

st = Standardize[data, Mean, 1 &]; // RepeatedTiming
(*  {0.139, Null}  *)

mrt == st     (* check *)
(*  True  *)
$\endgroup$
  • $\begingroup$ Thanks, good addition to this Q&A, especially for v10 users like me. $\endgroup$ – Mr.Wizard Jul 3 '17 at 15:16
  • $\begingroup$ @Mr.Wizard Even though I noticed the difference in V10, I was really slow to recognize its significance. Thanks for prodding me. $\endgroup$ – Michael E2 Jul 3 '17 at 15:18
  • $\begingroup$ @Mr.Wizard sorry for pinging you here, but I was not sure I could reach you in chat. I wanted to suggest changing the title of the question to "How to subtract the column means from each row of a matrix?". I suppose it could be argued that the accepted answer kind of implements bsxfun (see also my answer), but I feel for example the Standardize answer should also be captured by the title, especially considering that another question was closed as a duplicate of this one. $\endgroup$ – Jacob Akkerboom Jul 4 '17 at 15:30
  • $\begingroup$ @Jacob I think that would be a good change. "bsxfun" will still appear in the body should anyone explicitly search for that. For other (non-MATLAB) users your title seems far more descriptive. $\endgroup$ – Mr.Wizard Jul 4 '17 at 16:35
4
$\begingroup$
mat = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
Transpose[Map[# - Mean[#] &, Transpose[mat]]]

which gives you

{{-4, -4, -4, -4}, {0, 0, 0, 0}, {4, 4, 4, 4}}
$\endgroup$
4
$\begingroup$

You might think of transposing your data. MATLAB naturally has the column as the basic subunit of the matrix, while Mathematica has the row.

Defining

(mat = Transpose[{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 
 12}}]) // MatrixForm

the (transpose) of the desired result is then

mat - Mean /@ mat // MatrixForm
$\endgroup$
3
$\begingroup$

Here is something a bit closer to the Matlab syntax

mat1=Partition[Range@12,4];
mat2 = ConstantArray[Mean[mat1], Length[mat1]];
binaryFunction = #1 - #2 &;

MapThread[binaryFunction, {mat1,mat2}]
$\endgroup$
2
$\begingroup$
TranslationTransform[-Mean @ #][#] & @ mat

{{-4, -4, -4, -4}, {0, 0, 0, 0}, {4, 4, 4, 4}}

Roughly comparable to Standardize in speed.

$\endgroup$
1
$\begingroup$

Mike's answer (the accepted one), almost implements bsxfun, taking listability for granted, like this

bsxfunListable[listableFu_, mat_, vec_] :=
 listableFu[mat, ConstantArray[vec, Length@mat]];

bsxfun[Subtract, mat, Mean@mat]

We can generalise the function like this

bsxfun[fu_, mat_, vec_] :=
 Thread @@ {Unevaluated @@ 
    fu @@@ Hold@Evaluate@{mat, ConstantArray[vec, Length@mat]}}

bsxfun[Subtract, mat, Mean@mat]
$\endgroup$

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.