1

from this array

s = np.array([[35788, 41715, ... 34964], 
          [5047, 23529, ... 5165], 
          [12104, 33899, ... 11914], 
          [3646, 21031, ... 3814], 
          [8704, 7906, ... 8705]])

I have a loop like this

end =[]
for i in range(len(s)):
    for j in range(i, len(s)):
        out = mahalanobis(s[i], s[j], invcov)       
        end.append(out)
print end

and i take output :

[0.0, 12.99, 5.85, 10.22, 3.95, 0.0, 5.12, 3.45, 4.10, 0.0, 5.05, 8.10, 0.0, 15.45, 0.0]

but I want the output like this :

[[0.0, 12.99, 5.85, 10.22, 3.95], 
[12.99, 0.0, 5.12, 3.45, 4.10], 
[5.85, 5.12, 0.0, 5.05, 8.10], 
[10.22, 3.45, 5.05, 0.0, 15.45], 
[3.95, 4.10, 8.10, 15.45, 0.0]]
2
  • 1
    To clarify, you want a list containing lists of exactly 5 items each? Or will the size of the inside lists vary? Commented Apr 1, 2015 at 1:17
  • I want a list containing lists of exactly 5 items each, like a distance matrix Commented Apr 1, 2015 at 1:20

5 Answers 5

6

You need to loop differently in at least two ways:

end =[]
for s1 in s:
    end.append([mahalanobis(s1, s2, invcov) for s2 in s])

The most important thing is that the inner loop needs to be on the whole s again, else you will never get a square but 1 + 2 + ... + len(s) items (15 in this case as len(s) is 5).

Next, the inner loop must be enclosed in a list, since you want a list of lists.

Less important but nice: I've changed the inner loop to a list comprehension; and I've changed both loops to be directly on s since there's really no reason to go over the indirection of looping over indices then using those indices to get the s items you care about.

So I made four changes in all, but the first two are what you really need to get the result you desire, the other two are just nice improvements:-).

Sign up to request clarification or add additional context in comments.

2 Comments

nice and compact, but how would you assure there that you have 5 elements in the inner array? when i run your code i get different array length depending on input, and i thought Majesty wanted 5 fix?
You'll have just as many items in each sub-list as you have sub-lists -- "like a distance matrix" (the distance matrix among N points is of course always a square matrix, N by N).
3

Given the list,

end = [0.0, 12.99, 5.85, 10.22, 3.95, 0.0, 5.12, 3.45, 4.10, 0.0, 5.05, 8.10, 0.0, 15.45, 0.0]

you could build the desired 2-dimensional array using

import numpy as np
result = np.zeros((s.shape[0],)*2)               # 1
result[np.triu_indices(s.shape[0], 0)] = end     # 2
result += result.T                               # 3
print(result)

which yields

[[  0.    12.99   5.85  10.22   3.95]
 [ 12.99   0.     5.12   3.45   4.1 ]
 [  5.85   5.12   0.     5.05   8.1 ]
 [ 10.22   3.45   5.05   0.    15.45]
 [  3.95   4.1    8.1   15.45   0.  ]]
  1. make an array filled with zeros
  2. np.triu_indices(s.shape[0], 0) returns the indices for the upper-triangle of an array of shape (s.shape[0], s.shape[0]).

    In [95]: np.triu_indices(5, 0)
    Out[95]: 
    (array([0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 3, 3, 4]),
     array([0, 1, 2, 3, 4, 1, 2, 3, 4, 2, 3, 4, 3, 4, 4]))
    

    result[...] = end fills the upper-triangle with the values from end.

  3. Take the transpose of result and add that to result, thus making result symmetric.

This allows you to obtain the result without calling both mahalanobis(s[i], s[j]) and mahalanobis(s[j], s[i]) which is unnecessary since mahalanbis distance is symmetric.


Note that the diagonal is always zero since mahalanobis(x,x) equals zero for any x. So for a little added efficiency, you could exclude the diagonal:

end =[]
for i in range(len(s)):
    for j in range(i+1, len(s)):              # <-- note i+1
        out = mahalanobis(s[i], s[j], invcov)       
        end.append(out)

and then build result with the same code as before except that now we can use

result[np.triu_indices(s.shape[0], 1)] = end     

instead of

result[np.triu_indices(s.shape[0], 0)] = end     

The second argument to np.triu_indices controls the diagonal offset. When the offset is 1, the indices corresponding the the main diagonal are omitted.

In [96]: np.triu_indices(5, 1)
Out[96]: (array([0, 0, 0, 0, 1, 1, 1, 2, 2, 3]), array([1, 2, 3, 4, 2, 3, 4, 3, 4, 4]))

Comments

0

your loop is fine, if you just add a counter and a second array, then you can group results in groups of 5 elements in one array (if this is the desired result)

end =[]
tmp =[]
for i in range(len(s)):
    for j in range(i, len(s)):
        out = mahalanobis(s[i], s[j], invcov)
        if k % 5 == 0 and k != 0:
            end.append(tmp)
            tmp =[]       
        k += 1
        tmp.append(out)
if len(tmp) > 0:
    end.append(tmp)
print end

No matter what your input then, your output will be an array with n arrays each with 5 members [[1,2,3,4,5],[...],[...], ... ]

2 Comments

You missed the most important error in that range(i, len(s)) -- generating fewer and fewer items each time through. With len(s) at 5 this makes a three by five matrix, not the 5 by 5 declared as the desired output in the body of the question.
@AlexMartelli yupp, i just concentrated on the 5 array business, as Majesty had not posted the sample data until i had posted my answer... i prefer yours anyhow from style and if the result is what was desired, then perfect!
0

Here's a straight forward iteration that allocates the elements of out to a symmetric matrix:

x=np.zeros((5,5))
cnt=0
for i in range(5):
    for j in range(i,5):
        x[j,i] =x[i,j] = out[cnt]
        cnt += 1

producing

array([[  0.  ,  12.99,   5.85,  10.22,   3.95],
       [ 12.99,   0.  ,   5.12,   3.45,   4.1 ],
       [  5.85,   5.12,   0.  ,   5.05,   8.1 ],
       [ 10.22,   3.45,   5.05,   0.  ,  15.45],
       [  3.95,   4.1 ,   8.1 ,  15.45,   0.  ]])

Since this iteration parallels your original one, you could construct the matrix right away:

n = s.shape[0]
x = np.zeros((n,n))
for i in range(n):
    for j in range(i+1, n):
        x[i,j] = x[j,i] = mahalanobis(s[i], s[j], invcov)       
print x

I use i+1 in inner loop since, apparently, mahalanobis is 0 for i==j.

Comments

0

Another way is to use your code, and underneath it:

end2 = []
for repeat in range(len(end)/5-1):
    end2.append(end[0:4])
    end = end[5:]

1 Comment

end as generated by the Q's posted code is only 15 items long (not 25) so this will have plenty of empties at the tail of end2.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.