Skip to main content

NumPy: Arrays, Indexing & Slicing

Introduction​

NumPy (Numerical Python) is the cornerstone library for numerical computation in Python. It provides a fast N-dimensional array object and a large library of mathematical operations, implemented in optimized C, making it orders of magnitude faster than equivalent pure-Python loops.

This section uses a running business scenario: RetailEdge, a fictional mid-size retail chain, whose analytics team needs to process sales numbers efficiently before doing any formal analysis — computing revenue totals, applying discount matrices, flagging outliers. Raw numerical data from point-of-sale systems and inventory tracking arrives as flat number sequences; NumPy is what lets you process that efficiently before it ever reaches a spreadsheet or a Pandas DataFrame (next chapters).

Setup:

pip install numpy
import numpy as np # 'np' is the near-universal alias
print(np.__version__)

Concepts​

Why NumPy instead of Python lists​

Python ListNumPy ndarray
Stores any type (int, str, dict)Stores one fixed type (dtype)
Each element is a Python object with overheadElements are raw values — minimal overhead
Loops are needed for element-wise operationsVectorized ops work element-wise automatically
Memory: scattered, cache-unfriendlyMemory: one contiguous block, cache-friendly
Slow for large numerical arraysTypically 10x–100x faster than lists

Creating arrays​

  • np.array(list) — from an existing Python list (or list of lists, for 2-D).
  • np.zeros(shape), np.ones(shape), np.full(shape, value) — pre-filled arrays, used to initialize matrices before filling them in.
  • np.arange(start, stop, step) — like Python's range, but returns an array.
  • np.linspace(start, stop, n) — n evenly spaced values between two bounds (unlike arange, you specify the count, not the step).
  • np.random.uniform, np.random.normal, np.random.randint — random arrays for simulation and sampling. np.random.seed(n) makes results reproducible.

Every array has a .shape (dimensions), .dtype (element type), and .ndim (number of dimensions).

Indexing and slicing​

  • 1-D indexing/slicing works like Python lists: arr[0], arr[-1], arr[1:4], arr[::-1].
  • 2-D arrays use arr[row, column] — a single comma-separated index, not arr[row][column] chaining (though that also works, it's slower and less idiomatic).
  • Boolean indexing: arr[arr > 50000] selects every element where the condition is True — one of NumPy's most powerful features, since it replaces an explicit filter loop with a single expression.
  • np.where(condition, if_true, if_false) produces a new array choosing between two values element-wise based on a condition.

Examples​

Creating arrays from lists:

# 1-D array — one week of daily units sold at the Mumbai store
daily_units = np.array([120, 95, 142, 88, 210, 175, 133])
print(daily_units.shape) # (7,)
print(daily_units.dtype) # int64
print(daily_units.ndim) # 1

# 2-D array — 4 stores x 7 days of units sold
weekly_units = np.array([
[120, 95, 142, 88, 210, 175, 133], # Mumbai
[98, 112, 89, 104, 188, 160, 120], # Delhi
[75, 80, 95, 70, 155, 130, 110], # Pune
[50, 60, 55, 65, 120, 95, 80], # Jaipur
])
print(weekly_units.shape) # (4, 7)

Factory functions:

discount_matrix = np.zeros((4, 12)) # 4 stores x 12 months, all zero
price_vector = np.ones(10) * 499.0 # 10 products, all priced 499
months = np.arange(1, 13) # [1 2 3 ... 12]
discount_tiers = np.linspace(0, 0.30, 5) # [0. 0.075 0.15 0.225 0.30]
target_units = np.full((3, 3), 100) # 3x3 array, every cell = 100

Random arrays:

np.random.seed(42) # reproducible results

simulated_orders = np.random.uniform(100, 5000, size=1000) # order values
foot_traffic = np.random.normal(loc=300, scale=50, size=365) # daily footfall
txn_units = np.random.randint(1, 21, size=500) # units per transaction, 1-20

1-D and 2-D indexing:

daily_revenue = np.array([45200, 38900, 52100, 41300, 67800, 59400, 48700])

print(daily_revenue[0]) # 45200 (Monday)
print(daily_revenue[1:4]) # [38900 52100 41300]
print(daily_revenue[::-1]) # reversed

# weekly_units shape: (4 stores x 7 days)
print(weekly_units[1, 3]) # 104 — Delhi's Thursday sales
print(weekly_units[2, :]) # all of Pune's weekly sales
print(weekly_units[:, 4]) # all stores' Friday sales

Boolean indexing:

revenue = np.array([45200, 38900, 52100, 41300, 67800, 59400, 48700])

high_rev_mask = revenue > 50000
print(revenue[high_rev_mask]) # [52100 67800 59400]

mid_rev = revenue[(revenue >= 40000) & (revenue <= 60000)]

avg = revenue.mean()
label = np.where(revenue < avg, 'Below Avg', 'Above Avg')

Challenges​

1. Array creation​

  • Create a 1-D array of 12 monthly revenue targets starting at $500,000, increasing by $50,000 each month.
  • Create a 3x4 matrix initialized to zero, representing quarterly targets for 3 product categories.
  • Generate 200 random discount values uniformly distributed between 0.05 and 0.40.
  • Print the shape, dtype, and number of dimensions of each array you created.
💡 Show Answer
import numpy as np

monthly_targets = np.arange(500000, 500000 + 12 * 50000, 50000)
quarterly_targets = np.zeros((3, 4))

np.random.seed(1)
discounts = np.random.uniform(0.05, 0.40, size=200)

for name, arr in [('monthly_targets', monthly_targets),
('quarterly_targets', quarterly_targets),
('discounts', discounts)]:
print(f'{name}: shape={arr.shape}, dtype={arr.dtype}, ndim={arr.ndim}')

2. Indexing and slicing​

Given: monthly_profit = np.array([120000, 95000, 142000, 88000, 210000, 175000, 133000, 98000, 165000, 189000, 220000, 245000])

  • Extract profits for Q3 (July, Aug, Sep — indices 6, 7, 8).
  • Find all months where profit exceeded $150,000.
  • Replace all months where profit < $100,000 with the array mean.
  • What is the index of the month with the highest profit? (np.argmax)
💡 Show Answer
import numpy as np

monthly_profit = np.array([120000, 95000, 142000, 88000, 210000, 175000,
133000, 98000, 165000, 189000, 220000, 245000])

q3 = monthly_profit[6:9]
print('Q3:', q3)

above_150k = monthly_profit[monthly_profit > 150000]
print('Above 150k:', above_150k)

adjusted = monthly_profit.copy()
adjusted[adjusted < 100000] = monthly_profit.mean()
print('Adjusted:', adjusted.round(0))

print('Best month index:', monthly_profit.argmax()) # 11 (December)

Note the .copy() before mutating — indexing into the original array and assigning would silently modify monthly_profit itself, since NumPy indexing returns a view, not a copy, for simple slices.