Preallocation mallocs all the memory you need in one call, while resizing the array (through calls to append,insert,concatenate or resize) may require copying the array to a larger block of memory. So you are correct, preallocation is preferred over (and should be faster than) resizing.
There are a number of “preferred” ways to preallocate numpy arrays depending on what you want to create. There is np.zeros, np.ones, np.empty, np.zeros_like, np.ones_like, and np.empty_like, and many others that create useful arrays such as np.linspace, and np.arange.
So
ar0 = np.linspace(10, 20, 16).reshape(4, 4)
is just fine if this comes closest to the ar0 you desire.
However, to make the last column all 1’s, I think the preferred way would be to just say
ar0[:,-1]=1
Since the shape of ar0[:,-1] is (4,), the 1 is broadcasted to match this shape.