The idea
Most questions are about part of your data: last week, the Bandung store, March, the first three products. Indexing and slicing are how you point at those parts. If you know Python list slicing, 1-D arrays work the same way; 2-D arrays add one powerful idea: a comma between the row part and the column part.
1-D: like lists
Here are two weeks of daily order counts in one array. Getting a single day, or a stretch of days, works exactly like it does with a Python list:
import numpy as np
daily_orders = np.array([42, 38, 51, 47, 60, 75, 68, 44, 39, 55, 49, 63, 80, 71])
print(daily_orders[0]) # first day
print(daily_orders[-1]) # last day
print(daily_orders[2:5]) # days 3, 4, 5 (index 2 up to, not including, 5)
42
71
[51 47 60]
- Indexes start at 0; negative indexes count from the end.
start:stopincludesstartand excludesstop.- Leave a side out to go to the edge:
print(daily_orders[:7]) # first week
print(daily_orders[7:]) # second week
print(daily_orders[-3:]) # last three days
[42 38 51 47 60 75 68]
[44 39 55 49 63 80 71]
[63 80 71]
A third number is the step:
print(daily_orders[::7]) # every 7th day: each Monday
print(daily_orders[::-1]) # reversed
[42 44]
[71 80 63 49 55 39 44 68 75 60 47 51 38 42]
2-D: rows and columns
With a table, one position isn't enough to point at a value: you need to say which row and which column. Here the rows are four stores and the columns are six months. To get Bandung's units in March, give the row number and the column number, separated by a comma:
stores = ["Jakarta", "Bandung", "Surabaya", "Medan"]
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"]
units = np.array([
[120, 135, 150, 160, 155, 170],
[ 80, 85, 90, 88, 95, 100],
[ 95, 92, 105, 110, 108, 115],
[ 40, 45, 43, 50, 52, 55],
])
print(units[1, 2]) # row 1 (Bandung), column 2 (Mar)
90
units[row, column], with a comma. (You'll also see units[1][2], which works but is slower and doesn't extend to slices, so prefer the comma.)
Each side of the comma can be a slice. A lone : means "all":
print(units[0]) # row 0: Jakarta, all months
print(units[:, 0]) # all rows, column 0: every store in January
print(units[:2, -3:]) # first two stores, last three months
[120 135 150 160 155 170]
[120 80 95 40]
[[160 155 170]
[ 88 95 100]]
Read units[rows, columns] as "which rows, comma, which columns". units[:, 0] is "all rows, column 0", a whole column. This is the same idea pandas uses in df.iloc[rows, columns].
Slicing a grid gives a smaller grid, with its own shape:
q2 = units[:, 3:] # April to June
print(q2.shape)
print(q2.sum())
(4, 3)
1258
Using names to find positions
Arrays don't have labels like pandas. Keep the labels in a list and look up positions with .index():
b = stores.index("Bandung")
m = months.index("May")
print(stores[b], months[m], units[b, m])
Bandung May 95
That's one reason pandas exists: it keeps the labels attached. NumPy is the faster, simpler layer underneath.
Changing values
Assign to an index or a slice to change the array in place:
units[3, 5] = 58 # correct one value
units[2, :2] = [97, 94] # correct two values
units[3, :] = 0 # the Medan store closed: zero the whole row
print(units)
[[120 135 150 160 155 170]
[ 80 85 90 88 95 100]
[ 97 94 105 110 108 115]
[ 0 0 0 0 0 0]]
Assigning a single value to a slice fills every position (= 0 above). Assigning an array needs a matching shape.
A slice is a view
One difference from lists matters a lot. Slicing a list makes a copy; slicing an array makes a view: a window onto the same data. Change the view and the original changes too:
prices = np.array([185, 320, 95, 65, 110])
cheap = prices[2:4]
cheap[0] = 0
print(prices)
[185 320 0 65 110]
Views make slicing instant even on huge arrays (no data is copied), but they surprise people. When you want an independent copy, say so with .copy(): cheap = prices[2:4].copy(). A later module covers views and copies in depth.
Why this matters for clean code
Indexes are "magic numbers" that are easy to get wrong: is column 2 March or April? Clean NumPy code keeps the labels nearby (months.index("Mar")) or gives slices names (q2 = units[:, 3:]), so the intent is readable. And use .copy() whenever you plan to modify a slice but keep the original intact.
Recap
- 1-D:
a[i],a[-1],a[start:stop:step]; stop is excluded. - 2-D:
a[row, col]; each side can be a slice;:means all.a[:, j]is columnj. - Slicing a grid returns a smaller grid with its own
.shape. - Assign to indexes and slices to change values in place.
- Array slices are views of the same data; use
.copy()for an independent copy.