Best way to go towards an index in numpy, with wrap - python

Lets say I have a 2D array below:
[[ 0 0 0 0 0 0 ]
[ 0 0 0 0 0 0 ]
[ 0 0 0 0 0 0 ]
[ 0 0 0 0 0 2 ]
[ 0 1 0 0 0 0 ]
[ 0 0 0 0 0 0 ]]
I would like to get the direction from where '1' (index 4,1) is to '2' (index 3,5). Assuming directions are only up, down, left, right. Thus no diagonal movement.
One way to get the directions:
"right" if destination.x > start.x else "left" if target.x < start.x else None
"down" if destination.y > start.y else "up" if destination.y < start.y else None
So for this example, we can go to '2' or the destination by either going "up" or "right". That of course is just one step, once you moved, can perform the same logic to move closer to the destination.
The problem with this logic is that it doesnt take the wrapping into account. With this logic it will take 5 steps to get to the destination. There is a shorter way by actually going left or up that can get to the destination in just 3 steps, because of the wrap.
Was thinking of generating another array where the start will be the middle of the array and perform the same logic. The problem is if the array is even (like this is 6x6, need to pad to get a middle. For example:
[[ 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0]
[ 0 2 0 0 0 0 0]
[ 0 0 0 1 0 0 0]
[ 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0]]
Here the array is now 7x7. I believe there is a simplier way to get the answer without this extra step, but cant think of it.

Can you consider using this method?
import numpy as np
# build the array
a = np.zeros( (6,6), dtype=int )
a[4][1] = 1
a[3][5] = 2
# extract required informations
i,j = np.where(a == 1)
h,k =np.where(a == 2)
print (i-h) => [1]
print (j-k) => [-4]

Well, there is a quite simple formula for computing the distance in case of periodic boundary conditions. Below I consider only periodic b.c. on x-axis:
import numpy as np
# periodic boundary condition for the x-axis only
def steps(start, dest, L_x):
x_start = start[1]
y_start = start[0]
x_dest = dest[1]
y_dest = dest[0]
dx = x_dest - x_start
if np.abs(dx) <= L_x/2:
steps_x = x_dest - x_start
else:
if dx > 0:
steps_x = (x_dest - L_x) - x_start
else:
steps_x = (x_dest + L_x) - x_start
steps_y = y_dest - y_start
return steps_x, steps_y
Example:
grid = np.array([[0, 0, 0, 0, 0, 0 ],
[0, 0, 0, 0, 0, 0 ],
[0, 0, 0, 0, 0, 0 ],
[0, 0, 0, 0, 0, 2 ],
[0, 1, 0, 0, 0, 0 ],
[0, 0, 0, 0, 0, 0 ]])
L_x = grid.shape[1]
start = (4, 1) # (y, x) or (i, j)
dest = (3, 5)
steps_x, steps_y = steps(start, dest, grid)
dir_x = 'left' if steps_x < 0 else 'right'
dir_y = 'up' if steps_y < 0 else 'down'
print(abs(steps_x), dir_x, ',', abs(steps_y), dir_y)
Out: 2 left , 1 up

I try an other way:
On an horizontal axis of length size, to go from a to b, let delta = ((b-a)%size*2-1)//size.
if delta=-1, a=b : you don't move.
if delta=0 : you have to go right.
if delta=1 : you have to go left.
So this code seems works
size=10
vertical=['down','up',None]
horizontal=['right','left',None]
def side(a,b):
return ((b-a)%size*2-1)//size
def step(M1,M2):
x1,y1=M1
x2,y2=M2
return (vertical[side(x1,x2)],horizontal[side(y1,y2)])
For example :
In [6]: step((2,1),(2,8))
Out[6]: (None, 'left')

Related

finding neighbors in np.array at predefined start points

in an array i have 2 randomly defind start points for 2 agents that will fill the grid with "1" & "2" (that will get data from a pandas df)
#starting points:
start_point = []
cellMAP_0 = np.full((11,7), 0)
for i in range(2):
start_points = random.randint(0,cellMAP_0.size)
start_point.append(start_points)
further i assign each number to one agent:
for i in range(len(df[0:77])):
if df.loc[i]['side'] == 'MOVE' and df.loc[i]['symbol'] == 'FIGHTER_1':
cellMAP_0[np.unravel_index(random.choices(list(start_point)), cellMAP_0.shape)] = 1
elif df.loc[i]['side'] == 'MOVE' and df.loc[i]['symbol'] == 'FIGHTER_2':
cellMAP_0[np.unravel_index(random.choices(list(start_point)), cellMAP_0.shape)] = 2
this is my outcome:
[[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 2 0 0 0]
[0 0 0 0 0 0 0]
[0 0 0 0 0 0 0]
[0 0 0 0 0 0 1]
[0 0 0 0 0 0 0]
[0 0 0 0 0 0 0]]
so far so good. now i want to find horizontal & vertical neighbors for "1" and "2"
therefore i found this code, but somehow i don't get it to work with both instances (1&2) at once in the same array:
edit: sorry was a bit on the run here the code that i edited:
def find_horizontal_and_vertical_neighbours(cellMAP_0, start_point):
"""
This method takes 2d array and return list of all elements
with all horizontal and vertical neighbours
:param arr: 2d array
:return: list of array elements with neighbours
"""
neighbors = []
for start_point in range(len(cellMAP_0)):
for j, value in enumerate(cellMAP_0[start_point]):
if start_point == 0 or start_point == len(cellMAP_0) - 1 or j == 0 or j == len(cellMAP_0[start_point]) - 1:
# corners
new_neighbors = []
if start_point != 0:
new_neighbors.append(cellMAP_0[start_point - 1][j]) # top neighbor
if j != len(cellMAP_0[start_point]) - 1:
new_neighbors.append(cellMAP_0[start_point][j + 1]) # right neighbor
if start_point != len(cellMAP_0) - 1:
new_neighbors.append(cellMAP_0[start_point + 1][j]) # bottom neighbor
if j != 0:
new_neighbors.append(cellMAP_0[start_point][j - 1]) # left neighbor
else:
# add neighbors
new_neighbors = [
cellMAP_0[start_point - 1][j], # top neighbor
cellMAP_0[start_point][j + 1], # right neighbor
cellMAP_0[start_point + 1][j], # bottom neighbor
cellMAP_0[start_point][j - 1] # left neighbor
]
neighbors.append({
"index": start_point * len(cellMAP_0[start_point]) + j,
"value": value,
"neighbors": new_neighbors})
return neighbors
find_horizontal_and_vertical_neighbours(cellMAP_0,start_point)
this gives me inedex values and neighbors
[{'index': 0, 'value': 0, 'neighbors': [0, 0]},
{'index': 1, 'value': 0, 'neighbors': [0, 0, 0]},
{'index': 2, 'value': 0, 'neighbors': [0, 0, 0]},
{'index': 3, 'value': 0, 'neighbors': [0, 0, 0]},
{'index': 4, 'value': 0, 'neighbors': [0, 0, 0]},
but what i actually seek is a list of the cells next to the starting points (here the fat numbers in the array):
[[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 **2** 0 0 0]
[0 0 **2** 2 **2** 0 0]
[0 0 0 **2** 0 0 0]
[0 0 0 0 0 0 **1**]
[0 0 0 0 0 **1** 1]
[0 0 0 0 0 0 **1**]
afterwards i want that the fighter can fill one field of the neighbours in each round, as well as that each new acquired field can act as a new starting field. the first seems kind of obvious for me to pick random positions from the list of neighbours i would get from the outcome above.
hope this is now a bit more clear :) thank you very much!
Since you want only the cells next to the starting points, you can use np.where() to find the indices of the starting points and then apply a function that finds all cells next to them:
oneIndex = np.where(cellMAP_0 == 1) #in your example it will return [8][6]
twoIndex = np.where(cellMAP_0 == 2) #in your example it will return [5][4]
def allInRange(num1,num2):
inRange = []
if num1< cellMAP_0.shape[0]-1:
inRange.append((num1+1,num2))
if num1>0:
inRange.append((num1-1,num2))
if num2< cellMAP_0.shape[1]-1:
inRange.append((num1,num2+1))
if num2>0:
inRange.append((num1,num2-1))
return inRange
firstNeighbours = allInRange(oneIndex[0],oneIndex[1])
secondNeighbours = allInRange(twoIndex[0],twoIndex[1])

How to create an NxM matrix with each column value in range(x,y)?

Problem:
I want to create a 5 dimensional numpy matrix, each column's value restricted to a range. I can't find any solution online for this problem.
I'm trying to generate a list of rules in the form
Rule: (wordIndex, row, col, dh, dv)
with each column having values in range ( (0-7), (0,11), (0,11), (-1,1), (-1,1) ). I want to generate all possible combinations.
I could easily make the matrix using five loops, one inside another
m, n = 12, 12
rules =[]
for wordIndex in range(0, 15):
for row in range(0,m):
for col in range(0,n):
for dh in range(-1,2):
for dv in range(-1,2):
rules.append([wordIndex, row, col, dh, dv])
But this approach takes an exponentially large time to do this and I wonder if there's a better, vectorized approach to solve this problem using numpy.
I've tried the following but none seem to work:
rules = np.mgrid[words[0]:words[-1], 0:11, 0:11, -1:1, -1:1]
rules = np.rollaxis(words,0,4)
rules = rules.reshape((len(words)*11*11*3*3, 5))
Another approach that fails:
values = list(itertools.product(len(wordsGiven()), range(11), range(11), range(-1,1), range(-1,1)))
I also tried np.arange() but can't seem to figure out how to use if for a multidimensional array.
I think there should be a better way for it. But just in case if you cannot find it, here is a hacky array based way for it:
shape = (8-0, 12-0, 12-0, 2-(-1), 2-(-1))
a = np.zeros(shape)
#create array of indices
a = np.argwhere(a==0).reshape(*shape, len(shape))
#correct the ranges that does not start from 0, here 4th and 5th elements (dh and dv) reduced by -1 (starting range).
#You can adjust this for any other ranges and elements easily.
a[:,:,:,:,:,3:5] -= 1
First few elements of a:
[[[[[[ 0 0 0 -1 -1]
[ 0 0 0 -1 0]
[ 0 0 0 -1 1]]
[[ 0 0 0 0 -1]
[ 0 0 0 0 0]
[ 0 0 0 0 1]]
[[ 0 0 0 1 -1]
[ 0 0 0 1 0]
[ 0 0 0 1 1]]]
[[[ 0 0 1 -1 -1]
[ 0 0 1 -1 0]
[ 0 0 1 -1 1]]
[[ 0 0 1 0 -1]
[ 0 0 1 0 0]
[ 0 0 1 0 1]]
[[ 0 0 1 1 -1]
[ 0 0 1 1 0]
[ 0 0 1 1 1]]]
[[[ 0 0 2 -1 -1]
[ 0 0 2 -1 0]
[ 0 0 2 -1 1]]
[[ 0 0 2 0 -1]
[ 0 0 2 0 0]
[ 0 0 2 0 1]]
[[ 0 0 2 1 -1]
[ 0 0 2 1 0]
[ 0 0 2 1 1]]]
...

Numpy - How to shift values at indexes where change happened

So I would like to shift my values in a 1D numpy arrays, where change happened. The sample of shifting shall be configured.
input = np.array([0,0,0,0,1,0,0,0,0,0,1,1,1,0,0,1,0,0,0,0])
shiftSize = 2
out = np.magic(input, shiftSize)
print out
np.array([0,0,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,0,0])
For example the first switch happened and index 4, so index 2,3 becomes '1'.
The next happened at 5, so 6 and 7 becomes '1'.
EDIT: Also it would be important to be without for cycle because, that might be slow (it is needed for large data sets)
EDIT2: indexes and variable name
I tried with np.diff, so i get where the changes happened and then np.put, but with multiple index ranges it seems impossible.
Thank you for the help in advance!
What you want is called "binary dilation" and is contained in scipy.ndimage:
import numpy as np
import scipy.ndimage
input = np.array([0,0,0,0,1,0,0,0,0,0,1,1,1,0,0,1,0,0,0,0], dtype=bool)
out = scipy.ndimage.morphology.binary_dilation(input, iterations=2).astype(int)
# array([0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0])
Nils' answer seems good. Here is an alternative using NumPy only:
import numpy as np
def dilate(ar, amount):
# Convolve with a kernel as big as the dilation scope
dil = np.convolve(np.abs(ar), np.ones(2 * amount + 1), mode='same')
# Crop in case the convolution kernel was bigger than array
dil = dil[-len(ar):]
# Take non-zero and convert to input type
return (dil != 0).astype(ar.dtype)
# Test
inp = np.array([0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0])
print(inp)
print(dilate(inp, 2))
Output:
[0 0 0 0 1 0 0 0 0 0 1 1 1 0 0 1 0 0 0 0]
[0 0 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 0 0]
Another numpy solution :
def dilatation(seed,shift):
out=seed.copy()
for sh in range(1,shift+1):
out[sh:] |= seed[:-sh]
for sh in range(-shift,0):
out[:sh] |= seed[-sh:]
return out
Example (shift = 2) :
in : [0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0]
out: [0 0 0 0 0 0 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0 1 1 1 1]

Drawing a checkerboard out of 1s and 0s with a nested for loop

I'm using just normal python to make a checkerboard grids out of alternating 1s and 0s. I know that I can use a nested for loop with the modulus operator but I don't know exactly what to do with the modulus inside the for loop.
def print_board(board):
for i in range(len(board)):
print " ".join([str(x) for x in board[i]])
my_grid = []
for i in range(8):
my_grid.append([0] * 8)
for j in range(8):
#This is where I'm stuck.
print_board(my_grid)
Here's my solution, using nested for loops. Note that whether i+j is even or odd is a good way to determine where it should be 1 and where it should be 0, as it always alternates between adjacent 'cells'.
def checkerboard(n):
board = []
for i in range(n):
board.append([])
for j in range(n):
board[i].append((i+j) % 2)
return board
for row in checkerboard(8):
print(row)
Prints
[1, 0, 1, 0, 1, 0, 1, 0]
[0, 1, 0, 1, 0, 1, 0, 1]
[1, 0, 1, 0, 1, 0, 1, 0]
[0, 1, 0, 1, 0, 1, 0, 1]
[1, 0, 1, 0, 1, 0, 1, 0]
[0, 1, 0, 1, 0, 1, 0, 1]
[1, 0, 1, 0, 1, 0, 1, 0]
[0, 1, 0, 1, 0, 1, 0, 1]
Perhaps we should first aim to solve a different problem: how to generate a list with checkboard patterns.
Such list thus has interleaved a [0,1,0,...] row, and an [1,0,1,...] row.
Let us first construct the first row with length n. We can do this like:
[i%2 for i in range(n)]
Now the next row should be:
[(i+1)%2 for i in range(n)]
the next one can be:
[(i+2)%2 for i in range(n)]
Do you see a pattern emerge? We can construct such a pattern like:
[[(i+j)%2 for i in range(n)] for j in range(m)]
Now the only thing that is left is producing it as a string. We can do this by converting the data in the list to strings, join them together (and optionally use generators instead of list comprehension). So:
'\n'.join(''.join(str((i+j)%2) for i in range(n)) for j in range(m))
So we can construct an m×n grid like:
def print_board(m,n):
print('\n'.join(''.join(str((i+j)%2) for i in range(n)) for j in range(m)))
A 10x15 board then is:
>>> print_board(10,15)
010101010101010
101010101010101
010101010101010
101010101010101
010101010101010
101010101010101
010101010101010
101010101010101
010101010101010
101010101010101
N.B.: we can make the code a bit more efficient, by using &1 instead of %2:
def print_board(m,n):
print('\n'.join(''.join(str((i+j)&1) for i in range(n)) for j in range(m)))
A simple approach
# Function to draw checkerboard
def drawBoard(length):
for row in xrange(0, length):
for col in xrange(0, length):
# Even rows will start with a 0 (No offset)
# Odd rows will start with a 1 (1 offset)
offset = 0
if row % 2 == 0:
offset = 1
# alterate each column in a row by 1 and 0
if (col + offset) % 2 == 0:
print '1',
else:
print '0',
# print new line at the end of a row
print ""
drawBoard(8)
For even widths, you could avoid loops all together and just multiply some strings:
def print_board(width):
print ('0 1 ' * (width // 2) + '\n' + '1 0 ' * (width // 2) + '\n') * (width // 2)
print_board(10)
Giving:
0 1 0 1 0 1 0 1 0 1
1 0 1 0 1 0 1 0 1 0
0 1 0 1 0 1 0 1 0 1
1 0 1 0 1 0 1 0 1 0
0 1 0 1 0 1 0 1 0 1
1 0 1 0 1 0 1 0 1 0
0 1 0 1 0 1 0 1 0 1
1 0 1 0 1 0 1 0 1 0
0 1 0 1 0 1 0 1 0 1
1 0 1 0 1 0 1 0 1 0
This works as follows for a 10 x 10 grid:
Take the string
1 0
Multiply the string by 5 giving
1 0 1 0 1 0 1 0 1 0
Do the same with 0 1 giving:
0 1 0 1 0 1 0 1 0 1
Add a newline to the end of each and join them together:
1 0 1 0 1 0 1 0 1 0 \n0 1 0 1 0 1 0 1 0 1 \n
Now multiply this whole string by 5 to get the grid.
You can use list comprehension and a modulo:
new_board = [[0 if b%2 == 0 else 1 for b in range(8)] if i%2 == 0 else [1 if b%2 == 0 else 0 for b in range(8)] for i in range(8)]
for row in new_board:
print(row)
Output:
[0, 1, 0, 1, 0, 1, 0, 1]
[1, 0, 1, 0, 1, 0, 1, 0]
[0, 1, 0, 1, 0, 1, 0, 1]
[1, 0, 1, 0, 1, 0, 1, 0]
[0, 1, 0, 1, 0, 1, 0, 1]
[1, 0, 1, 0, 1, 0, 1, 0]
[0, 1, 0, 1, 0, 1, 0, 1]
[1, 0, 1, 0, 1, 0, 1, 0]
For a more custom finish:
for row in new_board:
print(' '.join(map(str, row)))
Output:
0 1 0 1 0 1 0 1
1 0 1 0 1 0 1 0
0 1 0 1 0 1 0 1
1 0 1 0 1 0 1 0
0 1 0 1 0 1 0 1
1 0 1 0 1 0 1 0
0 1 0 1 0 1 0 1
1 0 1 0 1 0 1 0

Pandas: flag consecutive values

I have a pandas series of the form [0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0 , 0 , 1].
0: indicates economic increase.
1: indicates economic decline.
A recession is signaled by two consecutive declines (1).
The end of the recession is signaled by two consecutive increase (0).
In the above dataset I have two recessions, begin at index 3, end at index 5 and begin at index 8 end at index 11.
I am at a lost for how to approach this with pandas. I would like to identify the index for the start and end of the recession. Any assistance would be appreciated.
Here is my python attempt at a soln.
np_decline = np.array([0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0 , 0 , 1])
recession_start_flag = 0
recession_end_flag = 0
recession_start = []
recession_end = []
for i in range(len(np_decline) - 1):
if recession_start_flag == 0 and np_decline[i] == 1 and np_decline[i + 1] == 1:
recession_start.append(i)
recession_start_flag = 1
if recession_start_flag == 1 and np_decline[i] == 0 and np_decline[i + 1] == 0:
recession_end.append(i - 1)
recession_start_flag = 0
print(recession_start)
print(recession_end)
Is the a more pandas centric approach?
Leon
The start of a run of 1's satisfies the condition
x_prev = x.shift(1)
x_next = x.shift(-1)
((x_prev != 1) & (x == 1) & (x_next == 1))
That is to say, the value at the start of a run is 1 and the previous value is not 1 and the next value is 1. Similarly, the end of a run satisfies the condition
((x == 1) & (x_next == 0) & (x_next2 == 0))
since the value at the end of a run is 1 and the next two values value are 0.
We can find indices where these conditions are true using np.flatnonzero:
import numpy as np
import pandas as pd
x = pd.Series([0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0 , 0 , 1])
x_prev = x.shift(1)
x_next = x.shift(-1)
x_next2 = x.shift(-2)
df = pd.DataFrame(
dict(start = np.flatnonzero((x_prev != 1) & (x == 1) & (x_next == 1)),
end = np.flatnonzero((x == 1) & (x_next == 0) & (x_next2 == 0))))
print(df[['start', 'end']])
yields
start end
0 3 5
1 8 11
You can use shift:
df = pd.DataFrame([0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0 , 0 , 1], columns=['signal'])
df_prev = df.shift(1)['signal']
df_next = df.shift(-1)['signal']
df_next2 = df.shift(-2)['signal']
df.loc[(df_prev != 1) & (df['signal'] == 1) & (df_next == 1), 'start'] = 1
df.loc[(df['signal'] != 0) & (df_next == 0) & (df_next2 == 0), 'end'] = 1
df.fillna(0, inplace=True)
df = df.astype(int)
signal start end
0 0 0 0
1 1 0 0
2 0 0 0
3 1 1 0
4 1 0 0
5 1 0 1
6 0 0 0
7 0 0 0
8 1 1 0
9 1 0 0
10 0 0 0
11 1 0 1
12 0 0 0
13 0 0 0
14 1 0 0
Similar idea using shift, but writing the result as a single Boolean column:
# Boolean indexers for recession start and stops.
rec_start = (df['signal'] == 1) & (df['signal'].shift(-1) == 1)
rec_end = (df['signal'] == 0) & (df['signal'].shift(-1) == 0)
# Mark the recession start/stops as True/False.
df.loc[rec_start, 'recession'] = True
df.loc[rec_end, 'recession'] = False
# Forward fill the recession column with the last known Boolean.
# Fill any NaN's as False (i.e. locations before the first start/stop).
df['recession'] = df['recession'].ffill().fillna(False)
The resulting output:
signal recession
0 0 False
1 1 False
2 0 False
3 1 True
4 1 True
5 1 True
6 0 False
7 0 False
8 1 True
9 1 True
10 0 True
11 1 True
12 0 False
13 0 False
14 1 False
use rolling(2)
s = pd.Series([0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0 , 0 , 1])
I subtract .5 so the rolling sum is 1 when a recession starts and -1 when it stops.
s2 = s.sub(.5).rolling(2).sum()
since both 1 and -1 evaluate to True I can mask the rolling signal to just start and stops and ffill. Get truth values of when they are positive or negative with gt(0).
pd.concat([s, s2.mask(~s2.astype(bool)).ffill().gt(0)], axis=1, keys=['signal', 'isRec'])
You can use scipy.signal.find_peaks for this problem.
from scipy.signal import find_peaks
np_decline = np.array([0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0 , 0 , 1])
peaks = find_peaks(np_decline,width=2)
recession_start_loc = peaks[1]['left_bases'][0]

Categories