Ha! I knew it had to be possible! Thanks Derek. So for and N = 2 (now on my laptop):

In [70]: M = 1200
In [69]: N = 2
In [71]: a = np.random.randint(0, 255, (M**2)).reshape(M,-1)

In [76]: timeit np.rollaxis(np.tile(a, N**2).reshape(M,N,-1), 2, 1).reshape(M*N,-1)
10 loops, best of 3: 99.1 ms per loop

In [78]: timeit a.repeat(2, axis=0).repeat(2, axis=1)
10 loops, best of 3: 85.6 ms per loop

In [79]: timeit np.kron(a, np.ones((2,2), 'uint8'))
1 loops, best of 3: 521 ms per loop

It turns out np.kron and repeat are pretty straightforward for multi-dimensional data too - scaling or stretching a stacked array representing pixel data over time, for example. Nothing changes for np.kron - it handles the additional dimensionality by itself. With repeat you just tell it to operate on the last two dimensions.

So to sum up:

1) np.kron is cool for the simplicity of the code and simple scaling to N dimensions. It's also handy if you want to scale the array elements themselves too.
2) repeat() along the last N axes is a bit more intuitive (i.e. less magical) to me and has a better performance profile. 
3) Derek's reshape/rolling solution is almost as fast but it gives me a headache trying to visualize what it's actually doing. I don't want to think about adding another dimension ...

Thanks for the help folks. Here's scaling of a hypothetical time series (i.e. 3 axes), where each sub-array represents a month.


In [26]: print a
[[[1 2]
  [3 4]]

 [[1 2]
  [3 4]]

 [[1 2]
  [3 4]]]

In [27]: np.kron(a, np.ones((2,2), dtype='uint8'))
Out[27]: 
array([[[1, 1, 2, 2],
        [1, 1, 2, 2],
        [3, 3, 4, 4],
        [3, 3, 4, 4]],

       [[1, 1, 2, 2],
        [1, 1, 2, 2],
        [3, 3, 4, 4],
        [3, 3, 4, 4]],

       [[1, 1, 2, 2],
        [1, 1, 2, 2],
        [3, 3, 4, 4],
        [3, 3, 4, 4]]])

In [64]: a.repeat(2, axis=1).repeat(2, axis=2)
Out[64]: 
array([[[1, 1, 2, 2],
        [1, 1, 2, 2],
        [3, 3, 4, 4],
        [3, 3, 4, 4]],

       [[1, 1, 2, 2],
        [1, 1, 2, 2],
        [3, 3, 4, 4],
        [3, 3, 4, 4]],

       [[1, 1, 2, 2],
        [1, 1, 2, 2],
        [3, 3, 4, 4],
        [3, 3, 4, 4]]])

On Dec. 3, 2011, at 12:50PM, Derek Homeier wrote:

On 03.12.2011, at 6:22PM, Robin Kraft wrote:

> That does repeat the elements, but doesn't get them into the desired order.
> In [4]: print a
> [[1 2]
>  [3 4]]
> In [7]: np.tile(a, 4)
> Out[7]: 
> array([[1, 2, 1, 2, 1, 2, 1, 2],
>        [3, 4, 3, 4, 3, 4, 3, 4]])
> In [8]: np.tile(a, 4).reshape(4,4)
> Out[8]: 
> array([[1, 2, 1, 2],
>        [1, 2, 1, 2],
>        [3, 4, 3, 4],
>        [3, 4, 3, 4]])
> It's close, but I want to repeat the elements along the two axes, effectively stretching it by the lower right corner:
> array([[1, 1, 2, 2],
>        [1, 1, 2, 2],
>        [3, 3, 4, 4],
>        [3, 3, 4, 4]])
> It would take some more reshaping/axis rolling to get there, but it seems doable.
> Anyone know what combination of manipulations would work with the result of np.tile?
Rolling was the keyword:

np.rollaxis(np.tile(a, 4).reshape(2,2,-1), 2, 1).reshape(4,4))
[[1 1 2 2]
 [1 1 2 2]
 [3 3 4 4]
 [3 3 4 4]]

I leave the generalisation and timing up to you, but it seems for 
a = np.arange(M**2).reshape(M,-1)

np.rollaxis(np.tile(a, N**2).reshape(M,N,-1), 2, 1).reshape(M*N,-1) 

should do the trick.

Cheers,
Derek