7
$\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$ – user18792 Aug 26 '20 at 9:04
  • $\begingroup$ Thank you, this works perfectly! I think I even understand how the logic behind it. $\endgroup$ – Jonas Aug 26 '20 at 9:23
11
$\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
  • 1
    $\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 Aug 26 '20 at 10:29
4
$\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 Aug 26 '20 at 9:27
  • $\begingroup$ @Jonas, I thought you wanted to drop the last column. $\endgroup$ – kglr Aug 26 '20 at 9:28
  • $\begingroup$ No, I just made a mistake. I'll mark and edit it, if I find out how to. $\endgroup$ – Jonas Aug 26 '20 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 Aug 26 '20 at 9:57
3
$\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 Aug 26 '20 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 Aug 26 '20 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 Aug 26 '20 at 12:16
  • 1
    $\begingroup$ IMO Query , as shown by Sjoerd Smit, is 'the way to go' $\endgroup$ – user1066 Aug 26 '20 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 Aug 27 '20 at 11:41

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.