How to initialize locations of numpy array using dictionary keys and values?

Question:

I have the following numpy array which is basically a 3 channel image:

arr = np.zeros((6, 4, 3), dtype=np.float32)

# dictionary of values, key is array location
values_of_channel_0 = {
        (0, 2) : 1,  
        (1, 0) : 1,  
        (1, 3) : 5,  
        (2, 1) : 2,  
        (2, 2) : 3,  
        (2, 3) : 1,  
        (3, 0) : 1,  
        (3, 2) : 2,  
        (4, 0) : 2,  
        (4, 2) : 20,  
        (5, 0) : 1,  
        (5, 2) : 10, 
        (5, 3) : 1 
}

I am trying to find the most elegant way to set all the values of the 3rd channel according to the dictionary. Here is what I tried:

locations = list(values_of_channel_0.keys())
values = list(values_of_channel_0.values())
arr[lc,0] = values # trying to set the 3rd channel

But this fails.

Is there a way in which this can be done without looping over keys and values?

Asked By: Elad Maimoni

||

Answers:

What’s wrong with a simple loop? Something will have to iterate over the key/value-pairs you provide in your dictionary in any case?

import numpy as np

arr = np.zeros((6, 4, 3), dtype=np.float32)

# dictionary of values, key is array location
values_of_channel_0 = {
        (0, 2) : 1,
        (1, 0) : 1,
        (1, 3) : 5,
        (2, 1) : 2,
        (2, 2) : 3,
        (2, 3) : 1,
        (3, 0) : 1,
        (3, 2) : 2,
        (4, 0) : 2,
        (4, 2) : 20,
        (5, 0) : 1,
        (5, 2) : 10,
        (5, 3) : 1
}

for (a, b), v in values_of_channel_0.items():
    arr[a, b, 0] = v

print(arr)

Result:

[[[ 0.  0.  0.]
  [ 0.  0.  0.]
  [ 1.  0.  0.]
  [ 0.  0.  0.]]

 [[ 1.  0.  0.]
  [ 0.  0.  0.]
  [ 0.  0.  0.]
  [ 5.  0.  0.]]

 [[ 0.  0.  0.]
  [ 2.  0.  0.]
  [ 3.  0.  0.]
  [ 1.  0.  0.]]

 [[ 1.  0.  0.]
  [ 0.  0.  0.]
  [ 2.  0.  0.]
  [ 0.  0.  0.]]

 [[ 2.  0.  0.]
  [ 0.  0.  0.]
  [20.  0.  0.]
  [ 0.  0.  0.]]

 [[ 1.  0.  0.]
  [ 0.  0.  0.]
  [10.  0.  0.]
  [ 1.  0.  0.]]]

If you insist on not looping for the assignment, you can construct a data structure that can be assigned at once:

channel_0 = [[values_of_channel_0[b, a] if (b, a) in values_of_channel_0 else 0 for a in range(4)] for b in range(6)]

arr[..., 0] = channel_0

But this is clearly rather pointless and not even more efficient. If you have some control over how values_of_channel_0 is constructed, you could consider constructing it as a nested list or array of the right dimensions immediately, to allow for this type of assignment.

Users @mechanicpig and @michaelszczesny offer a very clean alternative (which will be more efficient since it relies on the efficient implementation of zip()):

arr[(*zip(*values_of_channel_0), 0)] = list(values_of_channel_0.values())

Edit: you asked for an explanation of the lefthand side.

This hinges on the unpacking operator *. *values_of_channel_0 spreads all the keys of the dictionary values_of_channel_0 into a call to zip(). Since these keys are all 2-tuples of int, zip will yield two tuples, one with all the first coordinates (0, 1, 1, ...) and the second with the second coordinates (2, 0, 3, ...).

Since the call to zip() is also preceded by *, these two values will be spread to index arr[], together with a final coordinate 0. So this:

arr[(*zip(*values_of_channel_0), 0)] = ...

Is essentially the same as:

arr[((0, 1, 1, ...), (2, 0, 3, ...), 0)] = ...

That’s a slice of arr with exactly the same number of elements as the dictionary, including all the elements with the needed coordinates. And so assigning list(values_of_channel_0.values()) to it works and has the desired effect of assigning the matching values to the correct coordinates.

Answered By: Grismar
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.