Two 1-D arrays, three ways of putting them together. Only one of them is a table with a column each.
import numpy as np a = np.array([1, 2]) b = np.array([3, 4]) np.concatenate([a, b]) # -> [1, 2, 3, 4] still 1-D, end to end np.stack([a, b]) # -> [[1, 2], [3, 4]] a ROW each np.stack([a, b], axis=1) # -> [[1, 3], [2, 4]] a COLUMN each # stack's new dimension goes where axis says. axis=0 puts each array on # its own row; axis=1 puts each array down its own column. np.stack([a, np.array([5, 6, 7])]) # ValueError: all input arrays must have the same shape
- concatenate joins; stack creates
concatenate needs the arrays to already have the dimension it is joining along, so two 1-D arrays give a longer 1-D array. stack adds a dimension that did not exist, so two 1-D arrays give a 2-D result. If the number of dimensions in your answer should be larger than in your inputs, you want stack.
- axis says where the new dimension goes
With axis=0 the arrays become rows, with axis=1 they become columns — the same data, transposed. Which you want depends entirely on the convention of whatever consumes it, and for feature matrices that is one row per sample and one column per feature, so axis=1.
- stack refuses mismatched lengths, and that is a feature
Two features of different lengths mean rows have gone missing somewhere upstream, and silently truncating to the shorter would produce a table where each row mixes measurements from different samples. numpy raises rather than aligning — unlike zip, which truncates without a word.
One call does the work — the question is which of the two joining functions, and which axis. Guard the no-arguments case first.
np.stack creates the new dimension you need; concatenate would give you one long flat array. axis=1 makes each input a column rather than a row.
Refuse an empty call before stacking, because numpy’s message talks about a sequence rather than about your arguments. Let the length mismatch through — numpy’s error there already names the shapes, and it is a ValueError, which is what the contract promises.
import numpy as np def as_columns(*arrays): if not ____: raise ____('no arrays to combine') return np.____(arrays, axis=____)
import numpy as np
def as_columns(*arrays):
if not arrays:
raise ValueError("no arrays to combine into columns")
# stack, not concatenate: the result needs a dimension the inputs do
# not have. axis=1 puts each input down a column rather than across
# a row — one row per sample, one column per feature.
return np.stack(arrays, axis=1)
np.stack raises ValueError naming both shapes, which is exactly the information a caller needs, and it is already the right exception type. Writing our own length check would duplicate that with a worse message — the empty-arguments guard exists only because numpy’s message there talks about sequences rather than about features.
np.stack defaults to axis=0, which gives one row per input — the transpose of what a feature matrix wants. Both are correct arrays and only one is the right shape for sklearn, which expects rows to be samples. np.column_stack is the same thing under a name that says which convention it follows, and is worth preferring for that reason alone.
The signature accepts any number of features, including none — so the guard is not hypothetical, it is the natural consequence of the flexibility. A caller with a list of features passes as_columns(*features), and a list that happens to be empty arrives here as no arguments at all.
np.column_stack does exactly this and says so in its name, and np.row_stack is its counterpart — prefer them over stack with an axis when the axis is only there to pick a convention. If the inputs are already 2-D, concatenate along axis=1 is what joins them side by side and stack would add a third dimension you did not want. And if the columns have names and types that differ, an array is the wrong container: that is a DataFrame, and forcing mixed types into one array silently promotes everything to a common dtype — usually object, which throws away the performance you came to numpy for.
- Swap to np.column_stack and confirm nothing else changes, then look at what it does differently when handed a 2-D array.
- Try np.concatenate on the same inputs and check the shape you get — then work out how many steps later a model would notice.
- Stack two arrays of different dtypes, one int and one float, and see what the result’s dtype becomes and why.