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