Efficient numpy array manipulation to convert an identity matrix to a permutation matrix
Are there more efficient libraries than numpy for this?


Since permutation matrices are rather sparse, the scipy.sparse library is helpful.
Using its coo_matrix method we can build a matrix which contains given values at given indices. 

From here it's just a matter of building the right lists of indices.

from itertools import chain
from scipy.sparse import coo_matrix

def permutation_matrix(n):
    # row and column indices - first even, then odd numbers in the latter
    I, J = range(n), list(chain(range(0, n, 2), range(1, n, 2)))
    # the following also works, if you are so inclined. 
    # J = [m*2 + d for d, m in map(lambda k: divmod(k, n//2 + n % 2), range(n))]

    return coo_matrix(([1]*n, (I, J)))


If needed, we can use the .A property (short for .toarray()) to build a full matrix from this: e.g.
permutation_matrix(10).A.



Why bother with sparse matrices?

Multiplication with sparse matrices will be much faster: e.g. matrix-vector products can be computed in O(n) time instead of O(n^2).
Similarly, the memory requirements for storing these matrices in sparse format is O(n) instead of O(n^2).




  Is there an approach where I can shift the 1s without needing to
  create a separate zeros array in memory?


Sure, numpy.roll does circular shifting:

numpy.roll([1,0,0], 4) == numpy.array([0, 1, 0])



  Is there an Pythonic way to implement two different functions on two
  halves of the same array without splitting them up?


Not sure, but you can always cook something up.

from collections import deque
from itertools import chain

def apply_to_parts(part_selector, funs):

    def wrapper(vals):
        results = [deque() for _ in funs]

        for ix, val in enumerate(vals):
            part_ix = part_selector(ix, val)
            results[part_ix].append(funs[part_ix](val))

        return list(chain(*results))

    return wrapper

# multiply elements at even indices by 2
# divide   elements at odd  indices by 4    
# return a list of elements in part 1 followed by elements in part 2
example = apply_to_parts(
    lambda ix, val: ix % 2,
    [lambda x:2*x, lambda y: y/4]
)

# should return [2, 6, 0.5, 1.0]
example([1,2,3,4])