9
$\begingroup$

A problem that arises more often than I care for is that I want to modify specific elements nested lists.

Examples may be wanting to add 1 to the second value, get the Sqrt of the third or wanting to add/change the Unit of the penultimate one. My current workaround is using Table to do so, which is not only untidy, but probably pretty inefficient, too. (I just downloaded a list that's 190k lines long, with 18 values a line. I'm not even gonna try that)

test = Table[{x, x + 1, x + 2, x + 3, x + 4, x + 5}, {x, 0, 10}];
Table[{test[[i, 1]], test[[i, 2]] + 1, Sqrt[test[[i, 3]]], Quantity[test[[i, 4]], "Meters"], test[[i, 5]], test[[i,6]]}, {i, 1, Length[test]}]

what I'd very much prefer is using Replace all /.

rule=**???**
test /. rule

I as I don't want to change every third element to a "static" value or something, I assume that I'd need to use Slots (#), but anything I can come up with like

rule = #[[2]] -> #[[2]] + 1
rule = #2 -> #2 + 1

will throw me an error. I'm sure there's a solution, and I'm sure more experienced people will look at this and see my obvious mistake (and lack of understanding of Slots), but I don't know, and would appreciate any suggestions.

Edit: I didn't intentionally drop the last element, and fixed it.

$\endgroup$
2
  • 5
    $\begingroup$ Try something like that: ReplacePart[#, {2 -> #[[2]] + 1, 3 -> Sqrt[#[[3]]], 4 -> Quantity[#[[4]], "Meters"]}] & /@ test $\endgroup$
    – Acus
    Commented Aug 26, 2020 at 9:04
  • $\begingroup$ Thank you, this works perfectly! I think I even understand how the logic behind it. $\endgroup$
    – Jonas
    Commented Aug 26, 2020 at 9:23

7 Answers 7

16
$\begingroup$

The easiest way to apply different functions to different columns is with Query. This has the added advantage that columns you don't want to do anything with do not have to be specified explicitly. For example, to apply functions to the 1st and 3rd columns:

Query[All, {1 -> f, 3 -> g}] @ RandomInteger[10, {5, 4}] // TableForm

enter image description here

This also works very well with data in the form of a list of associations.

$\endgroup$
1
  • 2
    $\begingroup$ Query seems very powerful and is especially way faster. Thank you, I wasn't aware of this, and I'll try to learn to use it. $\endgroup$
    – Jonas
    Commented Aug 26, 2020 at 10:29
5
$\begingroup$

You can define a pure function func1 with the desired transformation of various Parts of an input n-tuple and use it with Map:

func1 = {#[[1]], #[[2]] + 1, Sqrt @ #[[3]],  Quantity[#[[4]], "Meters"], #[[5]], #[[6]]} &;

Map[func] @ test

or define your function using Slots and use it with Apply:

func2 = {#, #2 + 1, Sqrt @ #3, Quantity[#4, "Meters"], #5, #6} &;

func2 @@@ test

to get

enter image description here

$\endgroup$
4
  • $\begingroup$ Thank you for the explanation! I wasn't aware of Map and Apply. I just noticed a mistake I made above, that your solution also includes: The last element is dropped, because [[6]] is left out. user18792 's solution elegantly circumvents that problem. I will use your answer as a starting point to learn more about pure functions, Map and Apply $\endgroup$
    – Jonas
    Commented Aug 26, 2020 at 9:27
  • $\begingroup$ @Jonas, I thought you wanted to drop the last column. $\endgroup$
    – kglr
    Commented Aug 26, 2020 at 9:28
  • $\begingroup$ No, I just made a mistake. I'll mark and edit it, if I find out how to. $\endgroup$
    – Jonas
    Commented Aug 26, 2020 at 9:30
  • 2
    $\begingroup$ @Jonas, welcome to mma.se. Thank you for the accept; but, if this were my question, I would have accepted Sjoerd's answer. I suggest you consider unaccepting this answer and accepting Sjoerd's. $\endgroup$
    – kglr
    Commented Aug 26, 2020 at 9:57
4
$\begingroup$

One way of applying a function to a specific column of a matrix is to use Inner (which may be thought of as a generalized form of Dot). (see also here)

Inner[Times,test,ConstantArray[1,Length@test[[1]]],{#1,f@#2,##3}&]//TeXForm

$$ \left( \begin{array}{cccccc} 0 & f[1] & 2 & 3 & 4 & 5 \\ 1 & f[2] & 3 & 4 & 5 & 6 \\ 2 & f[3] & 4 & 5 & 6 & 7 \\ 3 & f[4] & 5 & 6 & 7 & 8 \\ 4 & f[5] & 6 & 7 & 8 & 9 \\ 5 & f[6] & 7 & 8 & 9 & 10 \\ 6 & f[7] & 8 & 9 & 10 & 11 \\ 7 & f[8] & 9 & 10 & 11 & 12 \\ 8 & f[9] & 10 & 11 & 12 & 13 \\ 9 & f[10] & 11 & 12 & 13 & 14 \\ 10 & f[11] & 12 & 13 & 14 & 15 \\ \end{array} \right) $$

For the requested modifications:

Inner[Times,test,ConstantArray[1,Length@test[[1]]],
       {#1,#2+1, Sqrt@#3, Quantity[#4, "meters"],##5}&
     ]//TeXForm

$$ \left( \begin{array}{cccccc} 0 & 2 & \sqrt{2} & 3\text{m} & 4 & 5 \\ 1 & 3 & \sqrt{3} & 4\text{m} & 5 & 6 \\ 2 & 4 & 2 & 5\text{m} & 6 & 7 \\ 3 & 5 & \sqrt{5} & 6\text{m} & 7 & 8 \\ 4 & 6 & \sqrt{6} & 7\text{m} & 8 & 9 \\ 5 & 7 & \sqrt{7} & 8\text{m} & 9 & 10 \\ 6 & 8 & 2 \sqrt{2} & 9\text{m} & 10 & 11 \\ 7 & 9 & 3 & 10\text{m} & 11 & 12 \\ 8 & 10 & \sqrt{10} & 11\text{m} & 12 & 13 \\ 9 & 11 & \sqrt{11} & 12\text{m} & 13 & 14 \\ 10 & 12 & 2 \sqrt{3} & 13\text{m} & 14 & 15 \\ \end{array} \right) $$


If all that is required is to multiply each value in a column by a factor, then Dot is sufficient (and very fast).

For example, to multiply all values in column-2 by 100:

test.DiagonalMatrix[{1,100,1,1,1,1}]//TeXForm

$$\left( \begin{array}{cccccc} 0 & 100 & 2 & 3 & 4 & 5 \\ 1 & 200 & 3 & 4 & 5 & 6 \\ 2 & 300 & 4 & 5 & 6 & 7 \\ 3 & 400 & 5 & 6 & 7 & 8 \\ 4 & 500 & 6 & 7 & 8 & 9 \\ 5 & 600 & 7 & 8 & 9 & 10 \\ 6 & 700 & 8 & 9 & 10 & 11 \\ 7 & 800 & 9 & 10 & 11 & 12 \\ 8 & 900 & 10 & 11 & 12 & 13 \\ 9 & 1000 & 11 & 12 & 13 & 14 \\ 10 & 1100 & 12 & 13 & 14 & 15 \\ \end{array} \right) $$


test = Table[{x, x + 1, x + 2, x + 3, x + 4, x + 5}, {x, 0, 10}];

Comparison with the very neat method given by Sjoerd Smit

(Query[All, {2 -> (#+1&),3->Sqrt,4 ->(Quantity[#, "meters"]&)}]@test)===
Inner[Times,test,{1,1,1,1,1,1},{#1,#2+1,Sqrt@#3, Quantity[#4, "meters"],##5}&]

True

$\endgroup$
6
  • $\begingroup$ > Inner[Times,test,ConstantArray[1,Length@test[[1]]], {#1,#2+1, Sqrt@#3, Quantity[#4, "meters"],#5}& ]//TeXForm Why do I need to multiply test with the ConstantArray First? Is it only because Inner needs two matrices, and that Array is a neutral element of Times? $\endgroup$
    – Jonas
    Commented Aug 26, 2020 at 11:01
  • $\begingroup$ As I understand it, and from the docs, Inner[f,list1,list2,g] "is a generalization of Dot in which f plays the role of multiplication and g of addition. For example, to multiply the second column of test by 100 using Inner: Inner[Times, test,{1,100,1,1,1,1},List]. And, say, to multiply column-1 by 100 and do your requested modifications (one of several ways): Inner[Times,test,{100,1,1,1,1,1},{#1,#2+1,Sqrt@#3, Quantity[#4, "meters"],#5}&]//MatrixForm $\endgroup$
    – user1066
    Commented Aug 26, 2020 at 11:47
  • 1
    $\begingroup$ I didn't, but that hasn't cropped up yet, and now I do :). And the "requested modifications" are more examples that would be the same form of things that came up in the past. What I need currently is UnixTime, but if Sqrt and Quantity work, then most of what I can think of, will, too. - The reason I asked about the ConstantArray is, that to me it seems like it doesn't "do" anything, because it's the neutral element. From the docs and my meddling I figured, that I need that, but it seems like a roundabout way. A placeholder. Please correct me if I'm wrong. $\endgroup$
    – Jonas
    Commented Aug 26, 2020 at 12:16
  • 1
    $\begingroup$ IMO Query , as shown by Sjoerd Smit, is 'the way to go' $\endgroup$
    – user1066
    Commented Aug 26, 2020 at 16:31
  • 1
    $\begingroup$ The second list (or 'ConstantArray' list) in Inner may be thought of as being equivalent to the diagonal matrix in Dot. If v={1,100,1,1,1,1};, all of the following multiply the second row of test by 100 (but Dot is much faster than Inner): test.DiagonalMatrix[v] == Inner[Times, test, DiagonalMatrix[v]] == Inner[Times, test, v, List]== Inner[Times, test, {1,1,1,1,1,1}, {#1,100 #2,##3}&]==Inner[Times, test, {1,4,1,1,1,1}, {#1,25 #2,##3}&] $\endgroup$
    – user1066
    Commented Aug 27, 2020 at 11:41
3
$\begingroup$
SeedRandom[0];

list = RandomInteger[10, {3, 4}];

One function applied to two columns

MapAt[f, {{All, 2}, {All, 4}}] @ list // MatrixForm

enter image description here

Two functions applied to two columns

MapAt[g, {All, 4}] @ MapAt[f, {All, 2}] @ list // MatrixForm

enter image description here

$\endgroup$
3
$\begingroup$

Using Fold:

SeedRandom[0];
list = RandomInteger[10, {3, 4}];
Fold[MapAt[First@#2, #1, {All, Last@#2}] &, 
  list, {{f, 2}, {g, 4}}] // MatrixForm

or

Fold[SubsetMap[Map[First@#2], #1, {All, Last@#2}] &, list, {{f, 
   2}, {g, 4}}] // MatrixForm

enter image description here

$\endgroup$
2
$\begingroup$
SeedRandom[0];

list = RandomInteger[10, {3, 4}];

Grabbing the @eldo's list and playing with ReplacePart, we can apply one function to two columns as follows:

p1 = Array[{#, 2} &, {Length@list}];
p2 = Array[{#, 4} &, {Length@list}];

r1 = Thread[p1 -> f /@ Extract[#, p1]] &;
r2 = Thread[p2 -> f /@ Extract[#, p2]] &;

ReplacePart[#, Union @@ {r1@#, r2@#}] &@list // MatrixForm

enter image description here

Two functions applied to two columns:

r1 = Thread[p1 -> f /@ Extract[#, p1]] &;
r2 = Thread[p2 -> g /@ Extract[#, p2]] &;

ReplacePart[#, Union @@ {r1@#, r2@#}] &@list // MatrixForm

enter image description here

$\endgroup$
1
$\begingroup$
SeedRandom[0];

m = RandomInteger[9, {3, 4}];

Using ColumnMap by Michael Sollami

com = ResourceFunction["ColumnMap"];

One function, one column

com[f, m, 3] // MatrixForm

enter image description here

One function, severals columns

com[f, m, {2, 4}] // MatrixForm

enter image description here

Two functions, two columns

com[{f, g}, m, {1, -1}] // MatrixForm

enter image description here

$\endgroup$

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.