-
-
Notifications
You must be signed in to change notification settings - Fork 12.2k
Clean up and unify r_,c_,hstack,vstack etc (Trac #201) #799
Description
Original ticket http://projects.scipy.org/numpy/ticket/201 on 2006-07-23 by @baxissimo, assigned to unknown.
== SUMMARY ==
- make r_ behave like "vstack plus range literals"
- make column_stack only transpose its 1d inputs.
- rename r_,c_ to v_,h_ (or something else) to make their connection with vstack and hstack clearer. Maybe vs_ and hs_ would be better?
- make a new vertsion of 'c_' that acts like column_stack and make r_ an alias for v_ (just as row_stack is an alias for vstack) so that theres a nice parallel v_<=>vstack, h_<=>hstack, c_<=>column_stack, r_<=>row_stack
== DISCUSSION ==
Here's essentially what these different methods do:
vstack(tup):
concatenate( map(atleast_2d,tup), axis=0 )
hstack(tup):
concatenate( map(atleast_1d,tup),axis=1 )
column_stack(tup):
arrays = map( transpose,map(atleast_2d,tup) )
concatenate(arrays,1)
(note that column_stack transposes everything not just 1-d inputs. The current docstring only claims that it works with 1d inputs, so making it do something reasonable with 2d inputs will hopefully not break much code.)
The above 3 are pretty much exactly the code used by numpy. That's
all there is to those 3 functions.
For r_ and c_ I'm summarizing, but effectively they seem to be doing
something like:
r_[args]:
concatenate( map(atleast_1d,args),axis=0 )
c_[args]:
concatenate( map(atleast_1d,args),axis=-1 )
c_ behaves almost exactly like hstack -- with the addition of range
literals being allowed.
r_ is mostly like vstack, but a little different since it effectively
uses atleast_1d, instead of atleast_2d. So you have
>>> numpy.vstack((1,2,3,4))
array([[1],
[2],
[3],
[4]])
but
>>> numpy.r_[1,2,3,4]
array([1, 2, 3, 4])
However for cases like that with just 0-d or 1-d inputs, c_ behaves
identically to r_, so if you wanted to get a 1-d output you could have
just used c_.
The current situation is confusing because r_, and c_ act similar to the existing hstack/vstack methods but different enough to make the distinction hard to remember.
Ideally these changes will make it so that all the user needs to do to change from one of the *stack methods to the equivalent that supports slice literals, or back again, is a simple syntactical transformation:
hstack( (args) ) <==> h_[ args ]
vstack( (args) ) <==> v_[ args ]
column_stack( (args) ) <==> c_[ args ]
row_stack( (args) ) <==> r_[ args ]
== Code ==
The following is code that implements the above suggestions in a file called altstack.py. Mostly the code is copied from numpy/lib/shape_base.py and numpy/lib/index_tricks.py. I'd be happy to provide a patch against numpy svn with these changes if there's interest.
#!python
import sys
import types
import numpy
import numpy.core.numeric as _nx
from numpy.core.numeric import asarray, ScalarType
import numpy.lib.function_base
import numpy.core.defmatrix as matrix
makemat = matrix.matrix
## ascol helper function to convert only 1d inputs to columns.
def ascol(v):
"""Turns 1-d inputs into a column vectors.
For all other inputs acts like atleast_2d.
(Q: Should this also transpose any (1xN)'s to be columns? The
current thinking is if you have a 1-d then you haven't really
decided whether it's a row or col, and this method is asserting
that it should be a column. But if it's already a 2-d row, then
it's probably a row for a reason, and you should transpose it
explicitly if you want a column.)
"""
arr = numpy.array(v,copy=False,subok=True)
if arr.ndim<2:
return numpy.transpose(numpy.atleast_2d(arr))
else:
return arr
## Modified column_stack that leaves >2d data alone
def column_stack(tup):
""" Stack 1D arrays as columns into a 2D array
Description:
Take a sequence of 1D and 2D arrays and stack them as columns
to make a single 2D array. All arrays in the sequence
must have the same first dimension. 2D arrays are stacked as-is,
just like with hstack, 1D arrays are turned into 2D columns first.
Arguments:
tup -- sequence of 1D and 2D arrays. All arrays must have the same
first dimension.
Examples:
>>> import numpy
>>> a = array((1,2,3))
>>> b = array((2,3,4))
>>> numpy.column_stack((a,b))
array([[1, 2],
[2, 3],
[3, 4]])
"""
arrays = map(ascol,tup)
return _nx.concatenate(arrays,1)
# Slightly modified version of numpy.lib.index_tricks.concatenator
# Only difference is the addition of 'mapper' parameter and
# self.mapper attribute. This allows for running the data through a
# given function (e.g. atleast_2d) before calling concatenate().
# Makes it so concatenator can generate results like vstack and
# column_stack
class concatenator(object):
"""Translates slice objects to concatenation along an axis.
"""
def _retval(self, res):
if self.matrix:
oldndim = res.ndim
res = makemat(res)
if oldndim == 1 and self.col:
res = res.T
self.axis = self._axis
self.mapping = self._mapping
self.matrix = self._matrix
self.col = 0
return res
def __init__(self, axis=0, mapping=None, matrix=False):
self._axis = axis
self._mapping = mapping
self._matrix = matrix
self.axis = axis
self.mapping = mapping
self.matrix = matrix
self.col = 0
def __getitem__(self,key):
if isinstance(key, str):
frame = sys._getframe().f_back
mymat = matrix.bmat(key,frame.f_globals,frame.f_locals)
return mymat
if type(key) is not tuple:
key = (key,)
objs = []
scalars = []
final_dtypedescr = None
for k in range(len(key)):
scalar = False
if type(key[k]) is slice:
step = key[k].step
start = key[k].start
stop = key[k].stop
if start is None: start = 0
if step is None:
step = 1
if type(step) is type(1j):
size = int(abs(step))
#newobj = function_base.linspace(start, stop, num=size)
newobj = numpy.linspace(start, stop, num=size)
else:
newobj = _nx.arange(start, stop, step)
elif type(key[k]) is str:
if (key[k] in 'rc'):
self.matrix = True
self.col = (key[k] == 'c')
continue
try:
self.axis = int(key[k])
continue
except (ValueError, TypeError):
raise ValueError, "unknown special directive"
elif type(key[k]) in ScalarType:
newobj = asarray([key[k]])
scalars.append(k)
scalar = True
else:
newobj = key[k]
objs.append(newobj)
if isinstance(newobj, _nx.ndarray) and not scalar:
if final_dtypedescr is None:
final_dtypedescr = newobj.dtype
elif newobj.dtype > final_dtypedescr:
final_dtypedescr = newobj.dtype
if final_dtypedescr is not None:
for k in scalars:
objs[k] = objs[k].astype(final_dtypedescr)
if self.mapping is not None:
objs = map(self.mapping, objs)
res = _nx.concatenate(tuple(objs),axis=self.axis)
return self._retval(res)
def __getslice__(self,i,j):
res = _nx.arange(i,j)
return self._retval(res)
def __len__(self):
return 0
# separate classes are used here instead of just making r_ = concatentor(0),
# etc. because otherwise we couldn't get the doc string to come out right
# in help(r_)
## The concatenator objects are now:
# v_ -- like vstack
# r_ -- like row_stack (but row_stack==vstack, so r_==v_)
# h_ -- like hstack
# c_ -- like column_stack (the modified version above)
# Note that r_ was used both for 'making a row' and for 'stacking rows' previously,
# but the old c_ could also be used for this purpose.
# Now if you want to make a row, h_ is the way to do it.
class v_class(concatenator):
"""Translates slice objects to vertical (row-wise) concatenation.
Similar to vstack with the additional ability to use slices.
For example:
>>> v_[[1,2,3], 3:0:-1, [4,5,6]]
array([[1, 2, 3],
[3, 2, 1],
[4, 5, 6]])
"""
def __init__(self):
concatenator.__init__(self, 0, numpy.atleast_2d)
v_ = v_class()
r_ = v_
class h_class(concatenator):
"""Translates slice objects to horizontal concatenation.
Similar to hstack with the additional ability to use slices.
For example:
>>> h_[ 1, 10:7:-1, [2,3,4] ]
array([ 1, 10, 9, 8, 2, 3, 4])
>>> h_[[[1],[2],[3]], [[4],[5],[6]]]
array([[1, 4],
[2, 5],
[3, 6]])
"""
def __init__(self):
concatenator.__init__(self, 1)
h_ = h_class()
class c_class(concatenator):
"""Translates slice objects to column-wise concatenation.
Similar to column_stack with the additional ability to use slices.
For example:
>>> c_[[1,2,3], [4,5,6]]
array([[1, 4],
[2, 5],
[3, 6]])
>>> c_[ [1,2], [4,5], [[7,8],[9,10]] ]
array([[ 1, 4, 7, 8],
[ 2, 5, 9, 10]])
"""
def __init__(self):
concatenator.__init__(self, 1, ascol)
c_ = c_class()