Behavior of np.c_ with list and tuple arguments
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']strIn [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 dimOut[456]: array([4, 5, 6, 7])In [457]: x[(0,1)] # same tupleOut[457]: array([4, 5, 6, 7])In [458]: x[[0,1]] # list - index for one dimOut[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 ....