How to map a states table to life table in numpy?

Viewed 87

The problem and what I expect.

I have a table h which indicates the H-bond states, where 1 for existing H-bond, 0 for no H-bond exist. Colums are for different object, rows are for different time step.

0, 0, 1, 1, 0, 0
0, 0, 1, 1, 0, 0
1, 0, 1, 1, 0, 0
1, 1, 1, 1, 0, 1
0, 1, 0, 0, 1, 1
0, 1, 0, 0, 1, 1
1, 0, 1, 1, 0, 1

I want to calculate the life (how long the H-bond exists) for each object. So the expected life table L should be like this.

0, 0, 4, 4, 0, 0
0, 0, 4, 4, 0, 0 
2, 0, 4, 4, 0, 0 
2, 3, 4, 4, 0, 4
0, 3, 0, 0, 2, 4
0, 3, 0, 0, 2, 4
1, 0, 1, 1, 0, 4

I don't know how to apply this map.

What I've done.

I've tried to map h to a new table generations indicating the generations of H-bond using the rising edge detecting. And the generations table G obtained:

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

And then I tried to map h to a new ages table, indicating the ages of H-bond. I got the following table A.

 [[0 0 1 1 0 0]
 [0 0 2 2 0 0]
 [1 0 3 3 0 0]
 [2 1 4 4 0 1]
 [0 2 0 0 1 2]
 [0 3 0 0 2 3]
 [1 0 1 1 0 4]]

I was tried to map h to life table L by using the logic: The life of a H-bond is the maximum age of all ages with the same generation. However, I've stuck on this mapping.

Some code

Some codes I used to calculate G and A.

    def getQbGenerations(self, QbStates):
        QbGenerations = np.zeros_like(QbStates, dtype=np.int64)
        Generation = np.zeros(QbStates.shape[-1])
        Generation[QbStates[0] == 1] = 1
        QbGenerations[0] = Generation
        for i in range(1, QbStates.shape[0]):
            # Rising Edge
            RiseMask = np.logical_and(QbStates[i-1]==0, QbStates[i]==1)
            Generation[RiseMask] += 1
            QbGenerations[i] = Generation
        return QbGenerations

    def getQbAges(self, QbStates):
        QbAges = np.zeros_like(QbStates, dtype=np.int64)
        Age = np.zeros(QbStates.shape[-1])
        Age[QbStates[0] == 1] = 1
        QbAges[0] = Age
        for i in range(1, QbStates.shape[0]):
            BondMask = QbStates[i] == 1
            Age[BondMask] += 1
            Age[~BondMask] = 0
            QbAges[i] = Age
        return QbAges
3 Answers

Thanks to the find_runs.py.

Another answer is:

import numpy as np


def find_runs(x):
    """Find runs of consecutive items in an array."""
    # ensure array
    x = np.asanyarray(x)
    if x.ndim != 1:
        raise ValueError('only 1D array supported')
    n = x.shape[0]

    # handle empty array
    if n == 0:
        return np.array([]), np.array([]), np.array([])

    else:
        # find run starts
        loc_run_start = np.empty(n, dtype=bool)
        loc_run_start[0] = True
        np.not_equal(x[:-1], x[1:], out=loc_run_start[1:])
        run_starts = np.nonzero(loc_run_start)[0]

        # find run values
        run_values = x[loc_run_start]

        # find run lengths
        run_lengths = np.diff(np.append(run_starts, n))

        return run_values, run_starts, run_lengths


if __name__ == "__main__":
    QbStates = np.array([
                    [0, 0, 1, 1, 0, 0],
                    [0, 0, 1, 1, 0, 0],
                    [1, 0, 1, 1, 0, 0],
                    [1, 1, 1, 1, 0, 1],
                    [0, 1, 0, 0, 1, 1],
                    [0, 1, 0, 0, 1, 1],
                    [1, 0, 1, 1, 0, 1]])

    print('QbStates: \n{}'.format(QbStates))
    lifes = np.array([], dtype=np.int64).reshape(0,QbStates.shape[0])
    for row in QbStates.T:
        values, positions, times = find_runs(row)
        life = np.array([],dtype=np.int64)
        for v, t in zip(values, times):
            life = np.append(life, np.repeat(v*t,t))
        lifes = np.vstack([lifes, life])
    
    # Transpose to get the answer
    print('LifeStates:\n {}'.format(lifes.T))

The result obtained.

QbStates: 
[[0 0 1 1 0 0]
 [0 0 1 1 0 0]
 [1 0 1 1 0 0]
 [1 1 1 1 0 1]
 [0 1 0 0 1 1]
 [0 1 0 0 1 1]
 [1 0 1 1 0 1]]
LifeStates:
 [[0 0 4 4 0 0]
 [0 0 4 4 0 0]
 [2 0 4 4 0 0]
 [2 3 4 4 0 4]
 [0 3 0 0 2 4]
 [0 3 0 0 2 4]
 [1 0 1 1 0 4]]

I would do something much simpler, albeit not the most 'pythonic' solution. I'd use the following algorithm:

  1. Take each column

  2. Loop through the column until a non-zero entry is reached

  3. Record how long the 'streak' of non-zero entries is

  4. Add this to a list

  5. Reloop through the column

  6. Identify the first streak

  7. Change the values in the first streak to the first entry in the list above.

Note that you have obtain the array A, we can continue from A. If use an algorithm based on gradient along the frame-axis, then we can obtain the expected array L.

# The array: Age
A= np.array([[0,0,1,1,0,0],
 [0,0,2,2,0,0],
 [1,0,3,3,0,0],
 [2,1,4,4,0,1],
 [0,2,0,0,1,2],
 [0,3,0,0,2,3],
 [1,0,1,1,0,4]])

# Define two parameters
age = A.T
n = age.shape[0]
m = age.shape[1]

# Generate the gradient along frame-axis for the array A
# output: a new array grad_age
grad_age = np.zeros((n, m))
for i in range(n):
    for j in range(m):
        if j < m-1:
            grad_age[i][j] = age[i][j+1] - age[i][j]
        else: # In the old version, this condition was neglected.
            grad_age[i][m-1] = 0 - age[i][m-1]
            
print("The array grad_age:")            
print(grad_age)

# Find out the locations of elements in the array 'age' 
# which are of negative gradient and modify related 
# elements in the array 'age'

for i in range(n):
    for j in reversed(range(m)):       
        t = grad_age[i][j]
        if t < 0:     #find out these index where gradient < 0
            print(t) # for checking
            for x in range(abs(int(t))):
                age[i][j-x] += x
print("The array L:")                
print(age.T)

The result is:

The array grad_age:
[[ 0.  1.  1. -2.  0.  1. -1.]
 [ 0.  0.  1.  1.  1. -3.  0.]
 [ 1.  1.  1. -4.  0.  1. -1.]
 [ 1.  1.  1. -4.  0.  1. -1.]
 [ 0.  0.  0.  1.  1. -2.  0.]
 [ 0.  0.  1.  1.  1.  1. -4.]]
-1.0
-2.0
-3.0
-1.0
-4.0
-1.0
-4.0
-2.0
-4.0
The array L:
[[0 0 4 4 0 0]
 [0 0 4 4 0 0]
 [2 0 4 4 0 0]
 [2 3 4 4 0 4]
 [0 3 0 0 2 4]
 [0 3 0 0 2 4]
 [1 0 1 1 0 4]]
Related