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 List | NumPy ndarray |
|---|---|
| Stores any type (int, str, dict) | Stores one fixed type (dtype) |
| Each element is a Python object with overhead | Elements are raw values — minimal overhead |
| Loops are needed for element-wise operations | Vectorized ops work element-wise automatically |
| Memory: scattered, cache-unfriendly | Memory: one contiguous block, cache-friendly |
| Slow for large numerical arrays | Typically 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'srange, but returns an array.np.linspace(start, stop, n)—nevenly spaced values between two bounds (unlikearange, 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, notarr[row][column]chaining (though that also works, it's slower and less idiomatic). - Boolean indexing:
arr[arr > 50000]selects every element where the condition isTrue— 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.