Meaning / equivalent of numpy.einsum expression - python

I'm desperately trying to find the python built-in equivalent of the following numpy.einsum expression:
>>> a = np.array((((1, 2), (3, 4)), ((5, 6), (7, 8))))
>>> a
array([[[1, 2],
[3, 4]],
[[5, 6],
[7, 8]]])
>>> b = np.array((((9, 10), (11, 12)), ((13, 14), (15, 16))))
>>> b
array([[[ 9, 10],
[11, 12]],
[[13, 14],
[15, 16]]])
>>> np.einsum("abc,abd->dc", a, b)
array([[212, 260],
[228, 280]])

As #AlexRiley comments the direct translation is something like:
(a[...,None,:]*b[...,None]).sum((0,1))
Let's parse the spec string 'abc,abd->dc' and let's rename the terms to x and y so they do not clash with the indices:
This is read as resultdc = ∑ab xabc yabd
As you can see the indices are taken verbatim from the spec string. Indices that do not occur in result spec are summed over. And that's it.
Side note: We can do better than that: Merging the first two axes the expression can be read as a matrix product for which numpy uses a highly optimized code path:
b.reshape(-1,b.shape[-1]).T#a.reshape(-1,a.shape[-1])
This is more than twice as fast as the direct translation and also a bit faster than the original einsum.

Related

Extract columns from list of tables in python

I have a list of tables, where each table is a list of lists. I'd like to extract the columns from each table to get a lists of columns. An example should clarify:
input=[[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]],[[13,14,15],[16,17,18]]]
output=[[[1,4],[7,10],[13,16]],[[2,5],[8,11],[14,17]],[[3,6],[9,12],[15,18]]]
I know I can probably use several for loops, but it feels like there should be a nice one liner to do this.
I intend to assign the results to variables:
a,b,_=output
With a single table, I can extract the columns with:
>>>input=[[1,2,3],[4,5,6]]
>>>list(zip(*input))
[(1, 4), (2, 5), (3, 6)]
or for assignment:
>>>a,b,_=zip(*input)
>>>a
(1, 4)
>>>b
(2, 5)
But for a list of tables, I haven't been able to do it. Here are some of the things I've tried unsuccessfully:
>>>list(zip(*zip(*input)))
[([1, 2, 3], [4, 5, 6]), ([7, 8, 9], [10, 11, 12]), ([13, 14, 15], [16, 17, 18])]
>>>[list(zip(*inp)) for inp in input]
[[(1, 4), (2, 5), (3, 6)], [(7, 10), (8, 11), (9, 12)], [(13, 16), (14, 17), (15, 18)]]
>>>[[x,y,z] for x,y,z in [zip(*inp) for inp in input]]
#same as above
>>> [[x,y] for inp in input for x,y in zip(*inp)]
[[1, 4], [2, 5], [3, 6], [7, 10], [8, 11], [9, 12], [13, 16], [14, 17], [15, 18]]
And nested/unpacked assignment didn't work for me either:
>>>[[a,b,_]]=[zip(*inp) for inp in input]
ValueError: too many values to unpack (expected 1)
>>>[*a,*b,*_]=[[x,y] for x,y in [zip(*inp) for inp in input]]
SyntaxError: two starred expressions in assignment
Is there a one-liner to do what I'm trying to do?
Edit: Note that while the example is specifically 3 tables with 2 rows and 3 columns each, my actual use case has unknown numbers of tables and rows.
I ended up using this line in my code:
list(zip(*[zip(*inp) for inp in input]))
You got close with that last attempt. You need one more, well-chosen nesting level. EDIT: I added the final zip step to get the desired ordering. I also used that "star" notation to help show how to extend the concept.
given = [[[1,2,3],[4,5,6]],
[[7,8,9],[10,11,12]],
[[13,14,15],[16,17,18]]]
f1 = [[[a, b] for a, b in zip(list1, list2)] for list1, list2 in given]
print(f1)
f2 = list(zip(*f1))
print(f2)
Output (edited for readability)
[[[1, 4], [2, 5], [3, 6]],
[[7, 10], [8, 11], [9, 12]],
[[13, 16], [14, 17], [15, 18]]]
[([1, 4], [7, 10], [13, 16]),
([2, 5], [8, 11], [14, 17]),
([3, 6], [9, 12], [15, 18])]
The second one has tuples instead of lists at the middle level; is that okay? If not, can you fix it? (left as an exercise for the student ... )

How can I turn two lists of equal lengths into a list of nested lists that represent ordered pairs?

For example, if these are my two lists:
a=[1,2,3,4,5]
b=[6,7,8,9,10]
Then what I'm trying to do is to figure out a way to get:
c=[[1,6],[2,7],[3,8],[4,9],[5,10]]
Sorry for the probably basic question. These are numpy arrays if that makes a difference.
If you want a numpy array as a result, you can build it using array.T:
In [15]: a=np.array([1,2,3,4,5])
In [16]: b=np.array([6,7,8,9,10])
In [17]: np.array([a,b]).T
Out[17]:
array([[ 1, 6],
[ 2, 7],
[ 3, 8],
[ 4, 9],
[ 5, 10]])
Reference: What is the equivalent of "zip()" in Python's numpy?
One approach is using list comprehension and zip:
>>> [[i, j] for i, j in zip(a,b)]
[[1, 6], [2, 7], [3, 8], [4, 9], [5, 10]]
I don't use numpy, but maybe by using zip:
>>> a=[1,2,3,4,5]
>>> b=[6,7,8,9,10]
>>> list(zip(a,b))
[(1, 6), (2, 7), (3, 8), (4, 9), (5, 10)]
It returns a list of tuples though.
Just use np.transpose:
>>> np.transpose([a, b])
array([[ 1, 6],
[ 2, 7],
[ 3, 8],
[ 4, 9],
[ 5, 10]])
If you want the result as list just call tolist() afterwards:
>>> np.transpose([a, b]).tolist()
[[1, 6], [2, 7], [3, 8], [4, 9], [5, 10]]
d = []
for i in range(0, 5):
d.append([a[i], b[i])
Is a simple way to create a new 2D list with element pairs. Using the zip() function as others have pointed out is also viable.
I'm pretty sure there is an easier or more pythonic way than this.
c = [list(x) for x in zip(a,b)]
This outputs a list of lists, instead of just doing list(zip(a,b) that outputs a list of tuples. This combines list comprehension and zip
Also avoids the tuple unpacking of [[i,j] for i,j in zip(a,b)]
not sure whats more efficient though
zip(*iterables) Make an iterator that aggregates elements from each of the iterables.
https://docs.python.org/3/library/functions.html#zip
There are multiple ways to do this.
a = [1, 2, 3, 4, 5]
b = [6, 7, 8, 9, 10]
If you just need to access the elements and not modify them, you can use the zip function:
zip(a, b)
>[(1, 6), (2, 7), (3, 8), (4, 9), (5, 10)]
If you actually need a list of lists, then you can use a list comprehension:
[[a[i], b[i]] for i in range(len(a))]
>[[1, 6], [2, 7], [3, 8], [4, 9], [5, 10]]
And finally, if you need a numpy array as a result, use the Transpose function:
import numpy as np
np.concatenate([[a], [b]]).T
>array([[1, 6],
[2, 7],
[3, 8],
[4, 9],
[5, 10]])
This will give you what you want.
a = [1,2,3,4,5]
b = [6,7,8,9,10]
c = [list(x) for x in zip(a, b)]
with no libraries, you could use:
a = [1,2,3,4,5]
b = [6,7,8,9,10]
c = [[a[i],b[i]] for i in range(len(a))]

Creating a nested list referencing specific ranges

I was challenged by a friend to make a simple program that asks a user to input a maximum value, and then a sample size (n). It then just uses randint to create a histogram in shell using ascii characters.
I can establish the class width and boundaries very easily. Where I'm having trouble is in understanding and implementing some sort of algorithm that will append all numbers that fall within a specific class to the histogram list to be printed. For example, if I have:
sample = [5, 1, 3, 9, 7, 13, 12, 5]
class_boundaries = [(1, 4), (4, 7), (7, 10), (10, 14)]
histogram = []
I just need to make a function that appends the sample values in the position that they would belong to in reference to the class boundaries. So for example, histogram[0] should return [1, 3]. I've been doing my best to try different solutions and understand how for-loop algorithms or list comprehensions function, but a practical explanation to my problem would be really helpful in my quest to better understand how to program. Thank you in advance!
sample = [5, 1, 3, 9, 7, 13, 12, 5]
class_boundaries = [(1, 4), (4, 7), (7, 10), (10, 14)]
classified = [[X for X in sample if LO <= X <= HI] for LO,HI in class_boundaries]
counts = [sum(LO <= X <= HI for X in sample) for LO,HI in class_boundaries]
Result: classified = [[1, 3], [5, 7, 5], [9, 7], [13, 12]], counts = [2, 3, 2, 2]
The computation of the counts doesn't need classified, so if thats all you need, skip the classified step.

Pythonic way of manipulating nested list into nested chunked list

I have a list whose nested list's size may vary with the multiple of 2. Currently, in this example, the nested list's length is 4.
a_list = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
According to length, I am trying to break the list to get the following result in the best possible pythonic way:
a = [[1,2], [5,6], [9,10]]
b = [[3,4], [7,8], [11,12]]
and if nested list's length is 6, then
c = [[..], [..], [..]]
Its kind of a transpose of a nested list but with sets of 2 values in a single row not to be transposed.
Using list comprehension:
>>> a_list = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
>>> a = [x[:2] for x in a_list]
>>> b = [x[2:] for x in a_list]
>>> a
[[1, 2], [5, 6], [9, 10]]
>>> b
[[3, 4], [7, 8], [11, 12]]
More general solution:
>>> [[x[i:i+2] for x in a_list] for i in range(0, len(a_list[0]), 2)]
[[[1, 2], [5, 6], [9, 10]],
[[3, 4], [7, 8], [11, 12]]]
I'd hesitate to call this "pythonic", since it's pretty much illegible, but:
>>> a, b = zip(*(zip(*[iter(s)]*2) for s in a_list))
>>> a
((1, 2), (5, 6), (9, 10))
>>> b
((3, 4), (7, 8), (11, 12))
Also works for 6-item lists:
>>> a_list = [[1,2,3,4,100,102],[5,6,7,8,103,104],[9,10,11,12,105,106]]
>>> a, b, c = zip(*(zip(*[iter(s)]*2) for s in a_list))
>>> a
((1, 2), (5, 6), (9, 10))
>>> b
((3, 4), (7, 8), (11, 12))
>>> c
((100, 102), (103, 104), (105, 106))
Almost the same as falsetru's answer, but first the nested lists are split into chunks of size 2 and then all of them are zipped together.
>>> a_list = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
>>> zip(*([j[i*2: i*2 + 2] for i in range(len(j) / 2)] for j in a_list))
[([1, 2], [5, 6], [9, 10]), ([3, 4], [7, 8], [11, 12])]
>>> a_list = [[1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11, 12]]
>>> zip(*([j[i*2: i*2 + 2] for i in range(len(j) / 2)] for j in a_list))
[([1, 2], [7, 8]), ([3, 4], [9, 10]), ([5, 6], [11, 12])]
>>> a_list = [[1,2,3,4,100,102],[5,6,7,8,103,104],[9,10,11,12,105,106]]
>>> zip(*([j[i*2: i*2 + 2] for i in range(len(j) / 2)] for j in a_list))
[([1, 2], [5, 6], [9, 10]), ([3, 4], [7, 8], [11, 12]), ([100, 102], [103, 104], [105, 106])]
A Fast way is using numpy.hsplit :
>>> import numpy
>>> numpy.hsplit(numpy.array(a_list),2)
[array([[ 1, 2],[ 5, 6],[ 9, 10]]),array([[ 3, 4],[ 7, 8],[11, 12]])]
Since readability is pythonic, here's a simpler iterator-based solution (without the neat tricks that #Zero used to turn it into a one-liner):
First, an iterator that turns a list [1,2,3,4,5,6] into [(1, 2), (3, 4), (5, 6)].
def pairs(lst):
it=iter(lst)
return list(zip(it, it)) # Return a list of pairs drawn from the same iterator
The list a_list can be transformed into a list of such pair lists as follows:
a_list = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
pair_list = [ pairs(row) for row in a_list ]
Finally, we need to effectively transpose this list, making a list of the first pair/element from each sublist, another list of the second one, etc. A nice idiom for transposing a list is zip(*some_list). Let's use it to make the transformation requested by the OP:
a, b = zip(*pair_list)
or to collect any number of generated lists in one list:
results = list( zip(*pair_list) )
Feel free to pack these into a one-liner (though I wouldn't):
results = list(zip( *(pairs(row) for row in a_list) ))

unexpected result sorting list with mixed items tuples and lists

tmp = [
(1, 2, 3),
(4, 5, 6),
[7, 8, 9],
[10, 11, 12],
]
print tmp
tmp.sort()
print tmp
results in:
[(1, 2, 3), (4, 5, 6), [7, 8, 9], [10, 11, 12]]
[[7, 8, 9], [10, 11, 12], (1, 2, 3), (4, 5, 6)]
Apparently lists get precedence over tuples.
Is this correct?
In Python 2,
In the documentation https://docs.python.org/2/reference/expressions.html#not-in,
Most other objects of built-in types compare unequal unless they are the same object; the choice whether one object is considered smaller or larger than another one is made arbitrarily but consistently within one execution of a program.
But I believe it's implementation independent:
Objects of different types except numbers are ordered by their type
names; objects of the same types that don’t support proper comparison
are ordered by their address.
In Python 3, this is fixed, so that comparing tuples and lists gives
TypeError: unorderable types: tuple() > list().

Categories

Resources