Accessing neighbour indices in a particular element of an array (python) - python

I have a two dimensional list like :
data = [[0,0,0,0,0,1,0,0,0,0], [0,1,0,0,0,0,0,0,0,0]]
How can I access the index of the neighbours, where the value equals 1?
Expected output:
[[4, 5, 6], [0, 1, 2]]
For example, the indices of an array data in first row at value 1 is 5, so I need to access its left and right side neighbour indices like 4 and 6. Same way for row 2.

If I understand description well (please clarify) , maybe you can try this one. Additionally, you can check edge case where there is no 1, or no left or right .
import numpy as np
a = np.array([
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0]])
if __name__ == "__main__":
indices = np.where(a == 1)[1]
indices = indices.reshape(-1,1)
indices = np.concatenate([indices-1,indices,indices+1],-1)
print(indices)

One efficient solution is using FOR loops:
for i in range(2):
for j in range(10):
if a[i][j]==1:
print(str(i)+' '+str(j))

If using lists, here is a one approach which identifies the indexes of the neighbours of 1. As a caveat, this will fail with a index out of range, if the 1 value is the first of last element in the list.
Input:
data = [[0,0,0,0,0,1,0,0,0,0], [0,1,0,0,0,0,0,0,0,0]]
Example:
[[i-1, i, i+1] for sub in data for i, j in enumerate(sub) if j == 1]
Output:
[[4, 5, 6], [0, 1, 2]]

Related

Just start to learn 2D array in Python. Why both codes generate different outputs just by changing place of col=[ ]

Code that works perfectly:
rows, cols = (4,2)
arr=[]
for i in range(rows):
col = []
for j in range(cols):
col.append(0)
arr.append(col)
print(arr)
Output:
[[0, 0], [0, 0], [0, 0], [0, 0]]
Alternative implementation:
rows, cols = (4,2)
arr=[]
col = []
for i in range(rows):
for j in range(cols):
col.append(0)
arr.append(col)
print(arr)
Output:
[[0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0]]
Why both codes generate different outputs just by changing place of col where an empty list is assigned to it. I have tried to get concept with trace table but got confused.
This is one of the classic Python blunders. Your entire second program contains EXACTLY two lists. There's one bound to col, and one bound to arr, and the one bound to arr contains multiple references to the first list. It does not contain 4 different lists, it contains 4 links to the SAME list.
To do what you want, you need to create a NEW empty list in each iteration, as you have done in the first example.

Replacing the values of a numpy array of zeros using a array of indexes

I'm working with numpy and I got a problem with index, I have a numpy array of zeros, and a 2D array of indexes, what I need is to use this indexes to change the values of the array of zeros by the value of 1, I tried something, but it's not working, here is what I tried.
import numpy as np
idx = np.array([0, 3, 4],
[1, 3, 5],
[0, 4, 5]]) #Array of index
zeros = np.zeros(6) #Array of zeros [0, 0, 0, 0, 0, 0]
repeat = np.tile(zeros, (idx.shape[0], 1)) #This repeats the array of zeros to match the number of rows of the index array
res = []
for i, j in zip(repeat, idx):
res.append(i[j] = 1) #Here I try to replace the matching index by the value of 1
output = np.array(res)
but I get the syntax error
expression cannot contain assignment, perhaps you meant "=="?
my desired output should be
output = [[1, 0, 0, 1, 1, 0],
[0, 1, 0, 1, 0, 1],
[1, 0, 0, 0, 1, 1]]
This is just an example, the idx array can be bigger, I think the problem is the indexing, and I believe there is a much simple way of doing this without repeating the array of zeros and using the zip function, but I can't figure it out, any help would be aprecciated, thank you!
EDIT: When I change the = by == I get a boolean array which I don't need, so I don't know what's happening there either.
You can use np.put_along_axis to assign values into the array repeat based on indices in idx. This is more efficient than a loop (and easier).
import numpy as np
idx = np.array([[0, 3, 4],
[1, 3, 5],
[0, 4, 5]]) #Array of index
zeros = np.zeros(6).astype(int) #Array of zeros [0, 0, 0, 0, 0, 0]
repeat = np.tile(zeros, (idx.shape[0], 1))
np.put_along_axis(repeat, idx, 1, 1)
repeat will then be:
array([[1, 0, 0, 1, 1, 0],
[0, 1, 0, 1, 0, 1],
[1, 0, 0, 0, 1, 1]])
FWIW, you can also make the array of zeros directly by passing in the shape:
np.zeros([idx.shape[0], 6])

Changing items in numpy array

I want to change all items in array A (in axis=1) into 0, according to the following criteria (toy code):
import numpy as np
A = np.array([[1,3], [2,5], [6,2]] )
B = np.array([[1,1,0,0,0],[1,0,0,2,0],[0,0,2,2,2],[0,0,0,2,0],[6,6,0,0,0]])
for i in A:
if i[1]<=2:
B[B==i[0]]=0
# result
>>> B
array([[1, 1, 0, 0, 0],
[1, 0, 0, 2, 0],
[0, 0, 2, 2, 2],
[0, 0, 0, 2, 0],
[0, 0, 0, 0, 0]])
But, in numpy way, that is NO 'for' loops :) Thanks!
You can use a conditional list comprehension to create a list of the first value in a tuple pair where the second value is less than or equal to two (in the example for A, it is the last item which gives a value of 6).
Then use slicing with np.isin to find the elements in B what are contained within the values from the previous condition, and then set those values to zero.
target_val = 2
B[np.isin(B, [a[0] for a in A if a[1] <= target_val])] = 0
>>> B
array([[1, 1, 0, 0, 0],
[1, 0, 0, 2, 0],
[0, 0, 2, 2, 2],
[0, 0, 0, 2, 0],
[0, 0, 0, 0, 0]])
Alternatively, you could also use np.where instead of slicing.
np.where(np.isin(B, [a[0] for a in A if a[1] <= target_val]), 0, B)
In one line: B[np.isin(B, A[A[:, 1] <= 2][:, 0])] = 0
Explanation:
c = A[:, 1] <= 2 # broadcast the original `if i[1]<=2:` check along axis=1
# i.e., mask A according to where the second values of the pairs are <= 2
d = c[:, 0] # index with the mask, and select the old `i[0]` values, here just `6`
e = np.isin(B, d) # mask B according to where the values are in the above
B[e] = 0 # and zero out those positions, i.e. where the old B value is 6

How to fill in empty array - invalid syntax

I have created an empty array which I want to fill.
The array is 10 by 10. I want the first row and column to display text names, which I have in a list of 9. I want the inner 9 by 9 cells to contain another matrix, which I already have filled in with the values.
Here is how I made the matrix and tried to fill in the names so far:
rows, cols = (10, 10)
array = [[0 for i in range (cols)] for j in range (rows)]
array [0][1:9] = photographs
array [1:9][0] = photographs
where photographs is my list of 9 words.
This gives me an array where the first row is as desired, but the first column is still all displaying 0.
This is what my array looks like:
[[0, 'DSC001 \n', 'DSC4587 \n', 'DSC3948 \n', 'DSC98798 \n', 'DSC44 \n', 'DSC098098d \n', 'DSC098734a-796876 \n', 'DSC8976 \n', 'DSC098707-a-b \n', 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
I tried to make the cell in the first row and first column display - or just a space, but got this error back:
array [0][0] = -
^
SyntaxError: invalid syntax
I have also tried to fill in my array with the values from my 9 by 9 matrix like this:
array [1:9][1:9] = matrix
But this did not work at all.
Filling in the first column should be
array[0][1:10] = photographs
In python, list slices go from the starting number to one less than the ending number, just like range
You can't use array[1:9][0] to refer to the first column.
array[1:9] is a list containing rows indexed 1 to 8 (so 2nd row to 9th row) so array[1:9][0] is just the second row. You could use a for loop to insert the column names instead like:
for row in array[1:10]:
row[0] = photographs[i]
Also, to insert a value into the first cell you want:
array[0][0] = '-'
just like how you would assign a variable.
nrows = 4
ncols = 4
# Initialize an empty list of lists.
# NB this is a list of lists, not an array. Think of the outer list as a list of rows. Each row is an inner list of 1 element per column.
array = [[0] * ncols for _ in range(ncols)]
# Note that array[n] gets the nth row. array[n][m] gets the element at (n, m).
# But to get the mth column, you need to do [array[row][m] for row in range(nrows)].
# This is reason enough to start thinking about numpy or pandas for an application list this.
headers = ["A", "B", "C"]
# Add the row headers to your 'array'
array[0][1:] = headers
# remember that array[0] gets the first row. It is a list. You can get all the elements except the first by slicing it with [1:]
# Add the column headers to your 'array'
for row_number, row in enumerate(array[1:]):
row[0] = headers[row_number]
# in this case we need a loop as we want to change the first element of each of the inner lists. A loop over array gives us a row at each iteration. row[0] is then the first column of that row.
# put - in the corner
array[0][0] = "-"
# fill the array with another list
data = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
# because both data and array are lists of rows, we do this row by row, skipping the first row
for data_row_number, array_row in enumerate(array[1:]):
array_row[1:] = data[data_row_number]
gives the output for array of
[['-', 'A', 'B', 'C'], ['A', 1, 2, 3], ['B', 4, 5, 6], ['C', 7, 8, 9]]

Efficient way to subset and combine arrays of different lengths

Given a 3 dimensional boolean data:
np.random.seed(13)
bool_data = np.random.randint(2, size=(2,3,6))
>> bool_data
array([[[0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 1]],
[[1, 0, 1, 1, 0, 0],
[0, 1, 1, 1, 1, 0],
[1, 1, 1, 0, 0, 0]]])
I wish to count the number of consecutive 1's bounded by two 0's in each row (along axis=1) and return a single array with the tally. For bool_data, this would give array([1, 1, 2, 4]).
Due to the 3D structure of bool_data and the variable tallies for each row, I had to clumsily convert the tallies into nested lists, flatten them using itertools.chain, then back-convert the list into an array:
# count consecutive 1's bounded by two 0's
def count_consect_ones(input):
return np.diff(np.where(input==0)[0])-1
# run tallies across all rows in bool_data
consect_ones = []
for i in range(len(bool_data)):
for j in range(len(bool_data[i])):
res = count_consect_ones(bool_data[i, j])
consect_ones.append(list(res[res!=0]))
>> consect_ones
[[], [1, 1], [], [2], [4], []]
# combines nested lists
from itertools import chain
consect_ones_output = np.array(list(chain.from_iterable(consect_ones)))
>> consect_ones_output
array([1, 1, 2, 4])
Is there a more efficient or clever way for doing this?
consect_ones.append(list(res[res!=0]))
If you use .extend instead, the content of the sequence is appended directly. That saves the step to combine the nested lists afterwards:
consect_ones.extend(res[res!=0])
Furthermore, you could skip the indexing, and iterate over the dimensions directly:
consect_ones = []
for i in bool_data:
for j in i:
res = count_consect_ones(j)
consect_ones.extend(res[res!=0])
We could use a trick to pad the columns with zeros and then look for ramp-up and ramp-down indices on a flattened version and finally filter out the indices corresponding to the border ones to give ourselves a vectorized solution, like so -
# Input 3D array : a
b = np.pad(a, ((0,0),(0,0),(1,1)), 'constant', constant_values=(0,0))
# Get ramp-up and ramp-down indices/ start-end indices of 1s islands
s0 = np.flatnonzero(b[...,1:]>b[...,:-1])
s1 = np.flatnonzero(b[...,1:]<b[...,:-1])
# Filter only valid ones that are not at borders
n = b.shape[2]
valid_mask = (s0%(n-1)!=0) & (s1%(n-1)!=a.shape[2])
out = (s1-s0)[valid_mask]
Explanation -
The idea with padding zeros at either ends of each row as "sentients" is that when we get one-off sliced array versions and compare, we could detect the ramp-up and ramp-down places with b[...,1:]>b[...,:-1] and b[...,1:]<b[...,:-1] respectively. Thus, we get s0 and s1 as the start and end indices for each of the islands of 1s. Now, we don't want the border ones, so we need to get their column indices traced back to the original un-padded input array, hence that bit : s0%(n-1) and s1%(n-1). We need to remove all cases where the start of each island of 1s are at the left border and end of each island of 1s at the right side border. The starts and ends are s0 and s1. So, we use those to check if s0 is 0 and s1 is a.shape[2]. These give us the valid ones. The island lengths are obtained with s1-s0, so mask it with valid-mask to get our desired output.
Sample input, output -
In [151]: a
Out[151]:
array([[[0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 1]],
[[1, 0, 1, 1, 0, 0],
[0, 1, 1, 1, 1, 0],
[1, 1, 1, 0, 0, 0]]])
In [152]: out
Out[152]: array([1, 1, 2, 4])

Categories

Resources