The output of np.c_ differs when its arguments are lists or tuples. Consider the output of the three following lines
np.c_[[1,2]]
np.c_[(1,2)]
np.c_[(1,2),]
With a list argument, np.c_ returns a column array, as expected. When the argument is a tuple instead (second line), it returns a 2D row. Adding a comma after the tuple (third line) returns a column array as for the first call.
Can somebody explain the rationale behind this behavior?
There are 2 common use cases for np.c_:
np.c_ can accept a sequence of 1D array-likes:
In [98]: np.c_[[1,2],[3,4]]
Out[98]:
array([[1, 3],
[2, 4]])
or, np.c_ can accept a sequence of 2D array-likes:
In [96]: np.c_[[[1,2],[3,4]], [[5,6],[7,8]]]
Out[96]:
array([[1, 2, 5, 6],
[3, 4, 7, 8]])
So np.c_ can be passed 1D array-likes or 2D array-likes.
But that raises the question how is np.c_ supposed to recognize if the input is a single 2D array-like (e.g. [[1,2],[3,4]]) or a sequence of 1D array-likes (e.g. [1,2], [3,4])?
The developers made a design decision: If np.c_ is passed a tuple, the argument will be treated as a sequence of separate array-likes. If it is passed a non-tuple (such as a list), then that object will be consider a single array-like.
Thus, np.c_[[1,2], [3,4]] (which is equivalent to np.c_[([1,2], [3,4])]) will treat ([1,2], [3,4]) as two separate 1D arrays.
In [99]: np.c_[[1,2], [3,4]]
Out[99]:
array([[1, 3],
[2, 4]])
In contrast, np.c_[[[1,2], [3,4]]] will treat [[1,2], [3,4]] as a single 2D array.
In [100]: np.c_[[[1,2], [3,4]]]
Out[100]:
array([[1, 2],
[3, 4]])
So, for the examples you posted:
np.c_[[1,2]] treats [1,2] as a single 1D array-like, so it makes [1,2] into a column of a 2D array:
In [101]: np.c_[[1,2]]
Out[101]:
array([[1],
[2]])
np.c_[(1,2)] treats (1,2) as 2 separate array-likes, so it places each value into its own column:
In [102]: np.c_[(1,2)]
Out[102]: array([[1, 2]])
np.c_[(1,2),] treats the tuple (1,2), (which is equivalent to ((1,2),)) as a sequence of one array-like, so that array-like is treated as a column:
In [103]: np.c_[(1,2),]
Out[103]:
array([[1],
[2]])
PS. Perhaps more than most packages, NumPy has a history of treating lists and tuples differently. That link discusses how lists and tuples are treated differenty when passed to np.array.
The first level on handling the argument comes from the Python interpreter, which translates a [...] into a call to __getitem__:
In [442]: class Foo():
...: def __getitem__(self,args):
...: print(args)
...:
In [443]: Foo()['str']
str
In [444]: Foo()[[1,2]]
[1, 2]
In [445]: Foo()[[1,2],]
([1, 2],)
In [446]: Foo()[(1,2)]
(1, 2)
In [447]: Foo()[(1,2),]
((1, 2),)
np.c_ is an instance of np.lib.index_tricks.AxisConcatenator. It's __getitem__
# handle matrix builder syntax
if isinstance(key, str):
....
mymat = matrixlib.bmat(...)
return mymat
if not isinstance(key, tuple):
key = (key,)
....
for k, item in enumerate(key):
....
So except for the np.bmat compatible string, it turns all inputs into a tuple, and then iterates over the elements.
Any of the variations containing [1,2] is the same as ([1,2],), a single element tuple. (1,2) is two elements that will be concatenated. So is ([1,2],[3,4]).
Note that numpy indexing also distinguishes between lists and tuples (though with a few inconsistencies).
In [455]: x=np.arange(24).reshape(2,3,4)
In [456]: x[0,1] # tuple - index for each dim
Out[456]: array([4, 5, 6, 7])
In [457]: x[(0,1)] # same tuple
Out[457]: array([4, 5, 6, 7])
In [458]: x[[0,1]] # list - index for one dim
Out[458]:
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]],
[[12, 13, 14, 15],
[16, 17, 18, 19],
[20, 21, 22, 23]]])
In [459]: x[([0,1],)] # same
....
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With