pythonforbio.
[WASM idle]
NumPy01/18

Array Creation

Starting sandbox…
Beginnerlesson3 tests

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.arange with a float step. Use linspace.
  • Growing arrays in a loop. Preallocate or collect-then-concatenate.
  • np.empty left 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) interprets 4 as the dtype and errors confusingly.)
  • Forgetting the default dtype is float64. np.zeros(5)[0] = 1 stores 1.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

#viainputexpected stdout
1stdin5[0.0, 0.25, 0.5, 0.75, 1.0]
2stdin3[0.0, 0.5, 1.0]
3stdin11[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.

starter

No output yet — run the code to populate this drawer.

start of pathAxes and Reductions