Array Creation
In one line: how the buffer gets allocated and filled — and why you should almost always preallocate rather than grow.
From existing data
np.array([[1, 2], [3, 4]]) # copies by default
np.asarray(x) # no copy if x is already an ndarray
np.array(x, copy=False) # NumPy 2: raises if a copy is needed
np.frombuffer(bytes_obj, dtype=np.uint8)
np.asarray is the right choice inside a function that accepts "array-like" input — it accepts lists and arrays without paying for a copy when it does not need to.
Filled with a constant
np.zeros((3, 4)) # float64 zeros
np.ones((3, 4), dtype=np.int32)
np.full((3, 4), np.nan) # the idiomatic "empty result matrix"
np.empty((3, 4)) # uninitialised — contains whatever was in RAM
np.eye(4); np.identity(4) # identity matrix
np.diag([1, 2, 3]) # diagonal matrix, or extract a diagonal
np.empty is genuinely faster because it skips the fill, but only use it when you will overwrite every element. Otherwise np.full(shape, np.nan) is safer: unfilled cells show up as NaN instead of masquerading as plausible numbers.
The _like family
Matches shape and dtype of an existing array:
np.zeros_like(counts)
np.ones_like(counts)
np.full_like(counts, np.nan, dtype=np.float64) # override dtype if needed
np.empty_like(counts)
Use these when building a result array in a pipeline — they keep dtype consistent without you restating it.
Ranges
np.arange(10) # 0..9
np.arange(0, 1, 0.1) # ← float step: length is not guaranteed!
np.linspace(0, 1, 11) # 11 points inclusive of both ends — use this for floats
np.logspace(0, 3, 4) # 1, 10, 100, 1000
np.geomspace(1, 1000, 4) # same, specified by endpoints
arange with a float step is a trap. Floating-point accumulation means the number of elements is not exactly predictable — np.arange(0, 0.3, 0.1) may give 3 or 4 elements depending on rounding. Use linspace whenever the step is not an integer.
Grids and coordinates
np.meshgrid(x, y) # coordinate matrices for surface plots
np.mgrid[0:5, 0:5] # same idea, slice syntax
np.indices((3, 4)) # index arrays
np.ix_(rows, cols) # open mesh for cross-product indexing
np.ix_ is the clean way to pull a submatrix by row and column label lists:
sub = expr[np.ix_(gene_idx, sample_idx)] # shape (len(gene_idx), len(sample_idx))
Without ix_, expr[gene_idx, sample_idx] would pair them elementwise instead — a classic Fancy Indexing mistake.
Random data
Always use the modern Generator API, never the legacy np.random.seed + np.random.rand globals. See NumPy Random Generator.
rng = np.random.default_rng(42)
rng.normal(0, 1, size=(3, 4))
rng.integers(0, 100, size=10)
rng.choice(genes, size=100, replace=False)
From files
np.loadtxt("matrix.tsv", delimiter="\t", skiprows=1)
np.genfromtxt("matrix.tsv", delimiter="\t", names=True, missing_values="NA")
np.load("array.npy")
For anything with mixed types, headers, or missing values, use pd.read_csv and then .to_numpy() — it is faster and far more forgiving than genfromtxt. See NumPy File IO and Reading and Writing Data.
The preallocation pattern
This is the single most valuable habit in this note.
# BAD — quadratic time, reallocates the whole buffer every iteration
result = np.array([])
for chunk in chunks:
result = np.append(result, process(chunk))
# GOOD — preallocate when you know the size
result = np.empty(n_total, dtype=np.float32)
i = 0
for chunk in chunks:
out = process(chunk)
result[i:i + len(out)] = out
i += len(out)
# ALSO GOOD — collect and convert once when you do not
pieces = [process(chunk) for chunk in chunks]
result = np.concatenate(pieces)
Arrays are fixed-size (ndarray); "appending" means allocating a new buffer and copying everything. On 10,000 iterations that is 10,000 copies of a growing array.
Bioinformatics examples
# a genotype matrix placeholder: samples × variants, -1 = missing
gt = np.full((n_samples, n_variants), -1, dtype=np.int8)
# per-base coverage vector for a chromosome
cov = np.zeros(chrom_length, dtype=np.int32)
# a position weight matrix scaffold: 4 bases × motif length
pwm = np.zeros((4, motif_len), dtype=np.float64)
# one-hot encoding of a sequence
BASES = np.array(list("ACGT"))
onehot = (np.array(list(seq))[:, None] == BASES[None, :]).astype(np.int8)
That last one is a nice Broadcasting exercise: an (L, 1) array of sequence letters compared against a (1, 4) array of bases gives an (L, 4) boolean matrix.
Common mistakes
np.arangewith a float step. Uselinspace.- Growing arrays in a loop. Preallocate or collect-then-concatenate.
np.emptyleft partially filled. Garbage that looks like data.np.zeros(3, 4)— passing dimensions as separate arguments. The shape must be a tuple:np.zeros((3, 4)). (np.zeros(3, 4)interprets4as the dtype and errors confusingly.)- Forgetting the default dtype is
float64.np.zeros(5)[0] = 1stores1.0. If you wanted an integer index array, say so.
See also
ndarray · NumPy dtypes · NumPy Random Generator · NumPy File IO · Fancy Indexing · Reshaping and Stacking
Test cases · 3
| # | via | input | expected stdout |
|---|---|---|---|
| 1 | stdin | 5 | [0.0, 0.25, 0.5, 0.75, 1.0] |
| 2 | stdin | 3 | [0.0, 0.5, 1.0] |
| 3 | stdin | 11 | [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0] |
Hints · 2
01Hint
np.linspace(start, stop, num) includes both endpoints.
02Hint
np.round(arr, 2) rounds element-wise; .tolist() gives plain floats.