0% found this document useful (0 votes)
15 views23 pages

Unit Iii Using Numpy

Uploaded by

Mohan
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
15 views23 pages

Unit Iii Using Numpy

Uploaded by

Mohan
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 23

UNIT III USING NUMPY

Basics of NumPy – Computation on NumPy – Aggregations – Computation on Arrays – Comparisons –


Masks and Boolean Arrays – Fancy Indexing – Sorting Arrays – Structured Data: NumPy’s Structured
Array.

NumPy stands for Numerical Python. It is a Python library used for working with an array. In Python, we use the list
for the array but it’s slow to process. NumPy array is a powerful N-dimensional array object and is used in linear
algebra, Fourier transform, and random number capabilities. It provides an array object much faster than traditional
Python lists.
Types of Array:
1. One Dimensional Array
2. Multi-Dimensional Array
One Dimensional Array:
A one-dimensional array is a type of linear array.

One Dimensional Array


Example:
# importing numpy module
import numpy as np

# creating list
list = [1, 2, 3, 4]

# creating numpy array


sample_array = np.array(list)

print("List in python : ", list)

print("Numpy Array in python :",sample_array)

Output:
List in python : [1, 2, 3, 4]
Numpy Array in python : [1 2 3 4]

Check data type for list and array:

print(type(list_1))
print(type(sample_array))

Output:
<class 'list'>
<class 'numpy.ndarray'>
Multi-Dimensional Array:
Data in multidimensional arrays are stored in tabular form.

Two Dimensional Array

Example:
# importing numpy module
import numpy as np

# creating list
list_1 = [1, 2, 3, 4]
list_2 = [5, 6, 7, 8]
list_3 = [9, 10, 11, 12]

# creating numpy array


sample_array = np.array([list_1,list_2,list_3])
print("Numpy multi dimensional array in python\n",sample_array)

Output:
Numpy multi dimensional array in python
[[ 1 2 3 4]
[ 5 6 7 8]
[ 9 10 11 12]]

Note: use [ ] operators inside numpy.array() for multi-dimensional


Anatomy of an array :
1. Axis: The Axis of an array describes the order of the indexing into the array.
Axis 0 = one dimensional
Axis 1 = Two dimensional
Axis 2 = Three dimensional
2. Shape: The number of elements along with each axis. It is from a tuple.
3. Rank: The rank of an array is simply the number of axes (or dimensions) it has.

The one-dimensional array has rank 1.

Rank 1

The two-dimensional array has rank 2.

Rank 2
4. Data type objects (dtype): Data type objects (dtype) is an instance of numpy.dtype class. It describes how the
bytes in the fixed-size block of memory corresponding to an array item should be interpreted.

Example:
# Import module
import numpy as np
# Creating the array
sample_array_1 = np.array([[0, 4, 2]])
sample_array_2 = np.array([0.2, 0.4, 2.4])
# display data type
print("Data type of the array 1 :",sample_array_1.dtype)
print("Data type of array 2 :",sample_array_2.dtype)

Output:
Data type of the array 1 : int32
Data type of array 2 : float64

Some different way of creating Numpy Array :


1. numpy.array(): The Numpy array object in Numpy is called ndarray. We can create ndarray
using numpy.array() function.

Syntax: numpy.array(parameter)

Example:
# import module
import numpy as np
#creating a array
arr = np.array([3,4,5,5])
print("Array :",arr)

Output:
Array : [3 4 5 5]

2. numpy.fromiter(): The fromiter() function create a new one-dimensional array from an iterable object.

Syntax: numpy.fromiter(iterable, dtype, count=-1)

Example:
#Import numpy module
import numpy as np
# iterable
iterable = (a*a for a in range(8))
arr = np.fromiter(iterable, float)
print("fromiter() array :",arr)

Output:
fromiter() array : [ 0. 1. 4. 9. 16. 25. 36. 49.]

3. numpy.arange(): This is an inbuilt NumPy function that returns evenly spaced values within a given interval.

Syntax: numpy.arange([start, ]stop, [step, ]dtype=None)


Example:
import numpy as np
np.arange(1, 20 , 2, dtype = np.float32)
Output:
array([ 1., 3., 5., 7., 9., 11., 13., 15., 17., 19.], dtype=float32)

4. numpy.linspace(): This function returns evenly spaced numbers over a specified between two limits.

Syntax: numpy.linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis=0)

Example:
import numpy as np
np.linspace(3.5, 10, 3)

Output:
array([ 3.5 , 6.75, 10. ])

5. numpy.empty(): This function create a new array of given shape and type, without initializing value.

Syntax: numpy.empty(shape, dtype=float, order=’C’)

Example:
import numpy as np
np.empty([4, 3], dtype = np.int32, order = 'f')

Output:
array([[ 1, 5, 9],
[ 2, 6, 10],
[ 3, 7, 11],
[ 4, 8, 12]])

6. numpy.ones(): This function is used to get a new array of given shape and type, filled with ones(1).

Syntax: numpy.ones(shape, dtype=None, order=’C’)

Example:

import numpy as np
np.ones([4, 3], dtype = np.int32, order = 'f')

Output:
array([[1, 1, 1],
[1, 1, 1],
[1, 1, 1],
[1, 1, 1]])

7. numpy.zeros(): This function is used to get a new array of given shape and type, filled with zeros(0).

Syntax: numpy.ones(shape, dtype=None)

Example:
import numpy as np
np.zeros([4, 3], dtype = np.int32, order = 'f')
Output:
array([[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]])
8. Create a Full Numpy Array
To create a full NumPy array, you can use the numpy.full() function. The full() function in NumPy creates an array of
a given shape and fills it with a specified value. A full NumPy array is an array where all the elements have the same
predefined value. This is useful when you want to initialize an array with a specific value.

Syntax: numpy.full(shape, fill_value, dtype = None, order = ‘C’)

Example:
import numpy as np
full_array_2d = np.full((3, 4), 5)
print(full_array_2d)

NumPy Applications - Uses of NumPy:


The NumPy API in Python is used primarily for numerical computing. It provides support for a wide range of
mathematical functions to operate on data efficiently. The following are some common application areas where
NumPy is extensively used:
1. Data Analysis: NumPy offers rapid and effective array operations, rendering it well-suited for
tasks like data cleansing, filtering, and transformation. It is predominantly used in the analysis and
scientific handling of data, particularly when working with extensive, large datasets.
2. Machine Learning and Artificial Intelligence: Different machine learning and deep learning
frameworks in Python, such as TensorFlow, and PyTorch, rely on NumPy arrays for handling input
data, model parameters, and outputs.
3. Scientific Computing: NumPy is widely used in scientific computing applications such as physics,
chemistry, biology, and astronomy for data manipulation, numerical simulations, and analysis.
NumPy is often used in numerical simulations and computational modelling for solving differential
equations, optimisation problems, and other mathematical problems.
4. Array manipulation: NumPy provides an assortment of methods for manipulating arrays, such as
resizing, slicing, indexing, stacking, splitting, and concatenating arrays. These techniques are
essential for preparing and manipulating data in diverse scientific computing jobs.
5. Finance and Economics: The NumP API is also widely used in financial data analysis and
economics to do portfolio optimisation, risk analysis, time series analysis, and statistical modelling.
6. Engineering and Robotics: NumPy is used in engineering disciplines such as mechanical, civil,
and electrical engineering for tasks like finite element analysis, control system design, and robotics
simulations.
7. Image and Signal Processing: NumPy is extensively used in processing and analysing images and
signals.
8. Data Visualisation: NumPy doesn't provide data visualisation but supports Matplotlib and Seaborn
libraries to generate plots and visualisations from numerical data.
Overall, NumPy's versatility and efficiency make it an essential Python package across a wide range of application
areas in scientific computing, data analysis, and beyond.

N-Dimensional array(ndarray) in Numpy


Array in Numpy is a table of elements (usually numbers), all of the same type, indexed by a tuple of positive
integers. In Numpy, number of dimensions of the array is called rank of the array.A tuple of integers giving the size
of the array along each dimension is known as shape of the array. An array class in Numpy is called as ndarray.
Elements in Numpy arrays are accessed by using square brackets and can be initialized by using nested Python Lists.
Example :
[[ 1, 2, 3],
[ 4, 2, 5]]

Here, rank = 2 (as it is 2-dimensional or it has 2 axes)


First dimension(axis) length = 2, second dimension has length = 3
overall shape can be expressed as: (2, 3)

# Python program to demonstrate


# basic array characteristics
import numpy as np
# Creating array object
arr = np.array( [[ 1, 2, 3], [ 4, 2, 5]] )
# Printing type of arr object
print("Array is of type: ", type(arr))
# Printing array dimensions (axes)
print("No. of dimensions: ", arr.ndim)
# Printing shape of array
print("Shape of array: ", arr.shape)
# Printing size (total number of elements) of array
print("Size of array: ", arr.size)
# Printing type of elements in array
print("Array stores elements of type: ", arr.dtype)

Output :
Array is of type: <class 'numpy.ndarray'>
No. of dimensions: 2
Shape of array: (2, 3)
Size of array: 6
Array stores elements of type: int64

Array Creation
There are various ways to create arrays in NumPy.
 For example, you can create an array from a regular Python list or tuple using the array function.
The type of the resulting array is deduced from the type of the elements in the sequences.
 Often, the elements of an array are originally unknown, but its size is known. Hence, NumPy offers
several functions to create arrays with initial placeholder content. These minimize the necessity
of growing arrays, an expensive operation.
For example: np.zeros, np.ones, np.full, np.empty, etc.
 To create sequences of numbers, NumPy provides a function analogous to range that returns arrays
instead of lists.
 arange: returns evenly spaced values within a given interval. step size is specified.
 linspace: returns evenly spaced values within a given interval. num no. of elements are returned.
 Reshaping array: We can use reshape method to reshape an array. Consider an array with shape
(a1, a2, a3, …, aN). We can reshape and convert it into another array with shape (b1, b2, b3, …,
bM). The only required condition is:
a1 x a2 x a3 … x aN = b1 x b2 x b3 … x bM . (i.e original size of array remains unchanged.)
 Flatten array: We can use flatten method to get a copy of array collapsed into one dimension. It
accepts order argument. Default value is ‘C’ (for row-major order). Use ‘F’ for column major
order.

Example:
# Python program to demonstrate
# array creation techniques
import numpy as np

# Creating array from list with type float


a = np.array([[1, 2, 4], [5, 8, 7]], dtype = 'float')
print ("Array created using passed list:\n", a)

# Creating array from tuple


b = np.array((1 , 3, 2))
print ("\nArray created using passed tuple:\n", b)
# Creating a 3X4 array with all zeros
c = np.zeros((3, 4))
print ("\nAn array initialized with all zeros:\n", c)

# Create a constant value array of complex type


d = np.full((3, 3), 6, dtype = 'complex')
print ("\nAn array initialized with all 6s."
"Array type is complex:\n", d)

Run on IDE
Output :
Array created using passed list:
[[ 1. 2. 4.]
[ 5. 8. 7.]]

Array created using passed tuple:


[1 3 2]

An array initialized with all zeros:


[[ 0. 0. 0. 0.]
[ 0. 0. 0. 0.]
[ 0. 0. 0. 0.]]

An array initialized with all 6s. Array type is complex:


[[ 6.+0.j 6.+0.j 6.+0.j]
[ 6.+0.j 6.+0.j 6.+0.j]
[ 6.+0.j 6.+0.j 6.+0.j]]

Array Indexing
Knowing the basics of array indexing is important for analysing and manipulating the array object. NumPy offers
many ways to do array indexing.
 Slicing: Just like lists in python, NumPy arrays can be sliced. As arrays can be multidimensional,
you need to specify a slice for each dimension of the array.
 Integer array indexing: In this method, lists are passed for indexing for each dimension. One to
one mapping of corresponding elements is done to construct a new arbitrary array.
 Boolean array indexing: This method is used when we want to pick elements from array which
satisfy some condition.

Example:
# Python program to demonstrate
# indexing in numpy
import numpy as np
# An exemplar array
arr = np.array([[-1, 2, 0, 4],
[4, -0.5, 6, 0],
[2.6, 0, 7, 8],
[3, -7, 4, 2.0]])
# Slicing array
temp = arr[:2, ::2]
print ("Array with first 2 rows and alternate" "columns(0 and 2):\n", temp)
# Integer array indexing example
temp = arr[[0, 1, 2, 3], [3, 2, 1, 0]]
print ("\nElements at indices (0, 3), (1, 2), (2, 1)," "(3, 0):\n", temp)
# boolean array indexing example
cond = arr > 0 # cond is a boolean array
temp = arr[cond]
print ("\nElements greater than 0:\n", temp)

Output :
Array with first 2 rows and alternatecolumns(0 and 2):
[[-1. 0.]
[ 4. 6.]]

Elements at indices (0, 3), (1, 2), (2, 1),(3, 0):


[ 4. 6. 0. 3.]

Elements greater than 0:


[ 2. 4. 4. 6. 2.6 7. 8. 3. 4. 2. ]
Basic operations
Plethora of built-in arithmetic functions are provided in NumPy.
 Operations on single array: We can use overloaded arithmetic operators to do element-wise
operation on array to create a new array. In case of +=, -=, *= operators, the exsisting array is
modified.
# Python program to demonstrate
# basic operations on single array
numpy as np

np.array([1, 2, 5, 3])

# add 1 to every element


print ("Adding 1 to every element:", a+1)

# subtract 3 from each element


print ("Subtracting 3 from each element:", a-3)

# multiply each element by 10


print ("Multiplying each element by 10:", a*10)

# square each element


print ("Squaring each element:", a**2)

# modify existing array

print ("Doubled each element of original array:", a)

# transpose of array
np.array([[1, 2, 3], [3, 4, 5], [9, 6, 0]])

print ("\nOriginal array:\n", a)


print ("Transpose of array:\n", a.T)
 Run on IDE
 Output :
 Adding 1 to every element: [2 3 6 4]
 Subtracting 3 from each element: [-2 -1 2 0]
 Multiplying each element by 10: [10 20 50 30]
 Squaring each element: [ 1 4 25 9]
 Doubled each element of original array: [ 2 4 10 6]

 Original array:
 [[1 2 3]
 [3 4 5]
 [9 6 0]]
 Transpose of array:
 [[1 3 9]
 [2 4 6]
 [3 5 0]]
 Unary operators: Many unary operations are provided as a method of ndarray class. This
includes sum, min, max, etc. These functions can also be applied row-wise or column-wise by
setting an axis parameter.
# Python program to demonstrate
# unary operators in numpy
numpy as np

np.array([[1, 5, 6],
[4, 7, 2],
[3, 1, 9]])

# maximum element of array


print ("Largest element is:", arr.max())
print ("Row-wise maximum elements:",
arr.max(axis = 1))

# minimum element of array


print ("Column-wise minimum elements:",
arr.min(axis = 0))

# sum of array elements


print ("Sum of all array elements:",
arr.sum())

# cumulative sum along each row


print ("Cumulative sum along each row:\n",
arr.cumsum(axis = 1))
 Run on IDE
 Output :
 Largest element is: 9
 Row-wise maximum elements: [6 7 9]
 Column-wise minimum elements: [1 1 2]
 Sum of all array elements: 38
 Cumulative sum along each row:
 [[ 1 6 12]
 [ 4 11 13]
 [ 3 4 13]]
 Binary operators: These operations apply on array elementwise and a new array is created. You
can use all basic arithmetic operators like +, -, /, , etc. In case of +=, -=, = operators, the exsisting
array is modified.
# Python program to demonstrate
# binary operators in Numpy
numpy as np

np.array([[1, 2],
[3, 4]])
np.array([[4, 3],
[2, 1]])
# add arrays
print ("Array sum:\n", a + b)

# multiply arrays (elementwise multiplication)


print ("Array multiplication:\n", a*b)

# matrix multiplication
print ("Matrix multiplication:\n", a.dot(b))
 Run on IDE
 Output:
 Array sum:
 [[5 5]
 [5 5]]
 Array multiplication:
 [[4 6]
 [6 4]]
 Matrix multiplication:
 [[ 8 5]
 [20 13]]
 Universal functions (ufunc): NumPy provides familiar mathematical functions such as sin, cos,
exp, etc. These functions also operate elementwise on an array, producing an array as output.
Note: All the operations we did above using overloaded operators can be done using ufuncs like np.add, np.subtract,
np.multiply, np.divide, np.sum, etc.
# Python program to demonstrate
# universal functions in numpy
numpy as np

# create an array of sine values


np.array([0, np.pi/2, np.pi])
print ("Sine values of array elements:", np.sin(a))

# exponential values
np.array([0, 1, 2, 3])
print ("Exponent of array elements:", np.exp(a))

# square root of array values


print ("Square root of array elements:", np.sqrt(a))
Run on IDE
Output:
Sine values of array elements: [ 0.00000000e+00 1.00000000e+00 1.22464680e-16]
Exponent of array elements: [ 1. 2.71828183 7.3890561 20.08553692]
Square root of array elements: [ 0. 1. 1.41421356 1.73205081]

Data Type

Every ndarray has an associated data type (dtype) object. This data type object (dtype) informs us about the layout of
the array. This means it gives us information about :
 Type of the data (integer, float, Python object etc.)
 Size of the data (number of bytes)
 Byte order of the data (little-endian or big-endian)
 If the data type is a sub-array, what is its shape and data type.
The values of a ndarray are stored in a buffer which can be thought of as a contiguous block of memory bytes. So
how these bytes will be interpreted is given by the dtype object.
Every Numpy array is a table of elements (usually numbers), all of the same type, indexed by a tuple of positive
integers. Every ndarray has an associated data type (dtype) object.
This data type object (dtype) provides information about the layout of the array. The vaues of an ndarray are stored in
a buffer which can be thought of as a contiguous block of memory bytes which can be interpreted by the dtype
object. Numpy provides a large set of numeric datatypes that can be used to construct arrays.
At the time of Array creation, Numpy tries to guess a datatype, but functions that construct arrays usually also
include an optional argument to explicitly specify the datatype.

# Python Program to create a data type object


numpy as np

# np.int16 is converted into a data type object.


print(np.dtype(np.int16))
Run on IDE
Output:
int16
# Python Program to create a data type object
# containing a 32 bit big-endian integer
numpy as np

# i4 represents integer of size 4 byte


# > represents big-endian byte ordering and
# < represents little-endian encoding.
# dt is a dtype object
np.dtype('>i4')

print("Byte order is:",dt.byteorder)

print("Size is:",dt.itemsize)

print("Data type is:",dt.name)


Run on IDE
Output:
Byte order is: >
Size is: 4
Name of data type is: int32

Comparison Operators as ufuncs


In Computation on NumPy Arrays: Universal Functions we introduced ufuncs, and focused in
particular on arithmetic operators. We saw that using +, -, *, /, and others on arrays leads to element-
wise operations. NumPy also implements comparison operators such as < (less than) and > (greater
than) as element-wise ufuncs. The result of these comparison operators is always an array with a
Boolean data type. All six of the standard comparison operations are available:

x = np.array([1, 2, 3, 4, 5])
x < 3 # less than
array([ True, True, False, False, False], dtype=bool)
x > 3 # greater than
array([False, False, False, True, True], dtype=bool)
x <= 3 # less than or equal
array([ True, True, True, False, False], dtype=bool)
x >= 3 # greater than or equal
array([False, False, True, True, True], dtype=bool)
x != 3 # not equal
array([ True, True, False, True, True], dtype=bool)
x == 3 # equal
array([False, False, True, False, False], dtype=bool)

It is also possible to do an element-wise comparison of two arrays, and to include compound


expressions:
(2 * x) == (x ** 2)
array([False, True, False, False, False], dtype=bool)
As in the case of arithmetic operators, the comparison operators are implemented as ufuncs in
NumPy; for example, when you write x < 3, internally NumPy uses np.less(x, 3). A summary of the
comparison operators and their equivalent ufunc is shown here:

Operator Equivalent ufunc Operator Equivalent ufunc


== np.equal != np.not_equal
< np.less <= np.less_equal
> np.greater >= np.greater_equal
Just as in the case of arithmetic ufuncs, these will work on arrays of any size and shape. Here is a two-
dimensional example:

rng = np.random.RandomState(0)
x = rng.randint(10, size=(3, 4))
x
array([[5, 0, 3, 3],
[7, 9, 3, 5],
[2, 4, 7, 6]])
x<6
array([[ True, True, True, True],
[False, False, True, True],
[ True, True, False, False]], dtype=bool)
In each case, the result is a Boolean array, and NumPy provides a number of straightforward patterns
for working with these Boolean results.

Working with Boolean Arrays


Given a Boolean array, there are a host of useful operations you can do. We'll work with x, the two-
dimensional array we created earlier.
print(x)
[[5 0 3 3]
[7 9 3 5]
[2 4 7 6]]
Counting entries
To count the number of True entries in a Boolean array, np.count_nonzero is useful:
# how many values less than 6?
np.count_nonzero(x < 6)
8
We see that there are eight array entries that are less than 6. Another way to get at this information is
to use np.sum; in this case, False is interpreted as 0, and True is interpreted as 1:
np.sum(x < 6)
8
The benefit of sum() is that like with other NumPy aggregation functions, this summation can be done
along rows or columns as well:

# how many values less than 6 in each row?


np.sum(x < 6, axis=1)
array([4, 2, 2])
This counts the number of values less than 6 in each row of the matrix.

If we're interested in quickly checking whether any or all the values are true, we can use (you guessed
it) np.any or np.all:

# are there any values greater than 8?


np.any(x > 8)
True
# are there any values less than zero?
np.any(x < 0)
False
# are all values less than 10?
np.all(x < 10)
True
# are all values equal to 6?
np.all(x == 6)
False
np.all and np.any can be used along particular axes as well. For example:

# are all values in each row less than 8?


np.all(x < 8, axis=1)
array([ True, False, True], dtype=bool)
Here all the elements in the first and third rows are less than 8, while this is not the case for the second
row.
Finally, a quick warning: as mentioned in Aggregations: Min, Max, and Everything In Between,
Python has built-in sum(), any(), and all() functions. These have a different syntax than the NumPy
versions, and in particular will fail or produce unintended results when used on multidimensional
arrays. Be sure that you are using np.sum(), np.any(), and np.all() for these examples!

Boolean operators
We've already seen how we might count, say, all days with rain less than four inches, or all days with
rain greater than two inches. But what if we want to know about all days with rain less than four
inches and greater than one inch? This is accomplished through Python's bitwise logic operators, &, |,
^, and ~. Like with the standard arithmetic operators, NumPy overloads these as ufuncs which work
element-wise on (usually Boolean) arrays.

For example, we can address this sort of compound question as follows:


np.sum((inches > 0.5) & (inches < 1))
29
So we see that there are 29 days with rainfall between 0.5 and 1.0 inches.
Note that the parentheses here are important–because of operator precedence rules, with parentheses
removed this expression would be evaluated as follows, which results in an error:
inches > (0.5 & inches) < 1
Using the equivalence of A AND B and NOT (NOT A OR NOT B) (which you may remember if
you've taken an introductory logic course), we can compute the same result in a different manner:
np.sum(~( (inches <= 0.5) | (inches >= 1) ))
29
Combining comparison operators and Boolean operators on arrays can lead to a wide range of
efficient logical operations.

The following table summarizes the bitwise Boolean operators and their equivalent ufuncs:

Operator Equivalent ufunc Operator Equivalent ufunc


& np.bitwise_and | np.bitwise_or
^ np.bitwise_xor ~ np.bitwise_not
Using these tools, we might start to answer the types of questions we have about our weather data.
Here are some examples of results we can compute when combining masking with aggregations:

print("Number days without rain: ", np.sum(inches == 0))


print("Number days with rain: ", np.sum(inches != 0))
print("Days with more than 0.5 inches:", np.sum(inches > 0.5))
print("Rainy days with < 0.2 inches :", np.sum((inches > 0) & (inches < 0.2)))
Number days without rain: 215
Number days with rain: 150
Days with more than 0.5 inches: 37
Rainy days with < 0.2 inches : 75

Boolean Arrays as Masks


In the preceding section we looked at aggregates computed directly on Boolean arrays. A more
powerful pattern is to use Boolean arrays as masks, to select particular subsets of the data themselves.
Returning to our x array from before, suppose we want an array of all values in the array that are less
than, say, 5:

x
array([[5, 0, 3, 3],
[7, 9, 3, 5],
[2, 4, 7, 6]])
We can obtain a Boolean array for this condition easily, as we've already seen:
x<5
array([[False, True, True, True],
[False, False, True, False],
[ True, True, False, False]], dtype=bool)
Now to select these values from the array, we can simply index on this Boolean array; this is known
as a masking operation:
x[x < 5]
array([0, 3, 3, 3, 2, 4])
What is returned is a one-dimensional array filled with all the values that meet this condition; in other
words, all the values in positions at which the mask array is True.
We are then free to operate on these values as we wish. For example, we can compute some relevant
statistics on our Seattle rain data:
# construct a mask of all rainy days
rainy = (inches > 0)
# construct a mask of all summer days (June 21st is the 172nd day)
days = np.arange(365)
summer = (days > 172) & (days < 262)
print("Median precip on rainy days in 2014 (inches): ",
np.median(inches[rainy]))
print("Median precip on summer days in 2014 (inches): ",
np.median(inches[summer]))
print("Maximum precip on summer days in 2014 (inches): ",
np.max(inches[summer]))
print("Median precip on non-summer rainy days (inches):",
np.median(inches[rainy & ~summer]))
Median precip on rainy days in 2014 (inches): 0.194881889764
Median precip on summer days in 2014 (inches): 0.0
Maximum precip on summer days in 2014 (inches): 0.850393700787
Median precip on non-summer rainy days (inches): 0.200787401575
By combining Boolean operations, masking operations, and aggregates, we can very quickly answer
these sorts of questions for our dataset.

Aside: Using the Keywords and/or Versus the Operators &/|


One common point of confusion is the difference between the keywords and and or on one hand, and
the operators & and | on the other hand. When would you use one versus the other?
The difference is this: and and or gauge the truth or falsehood of entire object, while & and | refer to
bits within each object.

When you use and or or, it's equivalent to asking Python to treat the object as a single Boolean entity.
In Python, all nonzero integers will evaluate as True. Thus:

bool(42), bool(0)
(True, False)
bool(42 and 0)
False
bool(42 or 0)
True
When you use & and | on integers, the expression operates on the bits of the element, applying the and
or the or to the individual bits making up the number:

bin(42)
'0b101010'
bin(59)
'0b111011'
bin(42 & 59)
'0b101010'
bin(42 | 59)
'0b111011'
Notice that the corresponding bits of the binary representation are compared in order to yield the
result.

When you have an array of Boolean values in NumPy, this can be thought of as a string of bits where
1 = True and 0 = False, and the result of & and | operates similarly to above:

A = np.array([1, 0, 1, 0, 1, 0], dtype=bool)


B = np.array([1, 1, 1, 0, 1, 1], dtype=bool)
A|B
array([ True, True, True, False, True, True], dtype=bool)
Using or on these arrays will try to evaluate the truth or falsehood of the entire array object, which is
not a well-defined value:

A or B
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-38-5d8e4f2e21c0> in <module>()
----> 1 A or B

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or
a.all()
Similarly, when doing a Boolean expression on a given array, you should use | or & rather than or or
and:

x = np.arange(10)
(x > 4) & (x < 8)
array([False, False, False, False, False, True, True, True, False, False], dtype=bool)
Trying to evaluate the truth or falsehood of the entire array will give the same ValueError we saw
previously:

(x > 4) and (x < 8)


---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-40-3d24f1ffd63d> in <module>()
----> 1 (x > 4) and (x < 8)

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or
a.all()
So remember this: and and or perform a single Boolean evaluation on an entire object, while & and |
perform multiple Boolean evaluations on the content (the individual bits or bytes) of an object. For
Boolean NumPy arrays, the latter is nearly always the desired operation.

Fancy Indexing
Fancy indexing is conceptually simple: it means passing an array of indices to access multiple array
elements at once. For example, consider the following array:
In [1]:
import numpy as np
rand = np.random.RandomState(42)
x = rand.randint(100, size=10)
print(x)
[51 92 14 71 60 20 82 86 74 74]
Suppose we want to access three different elements. We could do it like this:
[x[3], x[7], x[2]]
Out[2]:
[71, 86, 14]
Alternatively, we can pass a single list or array of indices to obtain the same result:
In [3]:
ind = [3, 7, 4]
x[ind]
Out[3]:
array([71, 86, 60])
When using fancy indexing, the shape of the result reflects the shape of the index arrays rather than
the shape of the array being indexed:
In [4]:
ind = np.array([[3, 7],
[4, 5]])
x[ind]
Out[4]:
array([[71, 86],
[60, 20]])
Fancy indexing also works in multiple dimensions. Consider the following array:
In [5]:
X = np.arange(12).reshape((3, 4))
X
Out[5]:
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
Like with standard indexing, the first index refers to the row, and the second to the column:
In [6]:
row = np.array([0, 1, 2])
col = np.array([2, 1, 3])
X[row, col]
Out[6]:
array([ 2, 5, 11])
Notice that the first value in the result is X[0, 2], the second is X[1, 1], and the third is X[2, 3]. The
pairing of indices in fancy indexing follows all the broadcasting rules that were mentioned
in Computation on Arrays: Broadcasting. So, for example, if we combine a column vector and a row
vector within the indices, we get a two-dimensional result:
In [7]:
X[row[:, np.newaxis], col]
Out[7]:
array([[ 2, 1, 3],
[ 6, 5, 7],
[10, 9, 11]])
Here, each row value is matched with each column vector, exactly as we saw in broadcasting of
arithmetic operations. For example:
In [8]:
row[:, np.newaxis] * col
Out[8]:
array([[0, 0, 0],
[2, 1, 3],
[4, 2, 6]])
It is always important to remember with fancy indexing that the return value reflects the broadcasted
shape of the indices, rather than the shape of the array being indexed.

Combined Indexing
For even more powerful operations, fancy indexing can be combined with the other indexing schemes
we've seen:
In [9]:
print(X)
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
We can combine fancy and simple indices:
In [10]:
X[2, [2, 0, 1]]
Out[10]:
array([10, 8, 9])
We can also combine fancy indexing with slicing:
In [11]:
X[1:, [2, 0, 1]]
Out[11]:
array([[ 6, 4, 5],
[10, 8, 9]])
And we can combine fancy indexing with masking:
In [12]:
mask = np.array([1, 0, 1, 0], dtype=bool)
X[row[:, np.newaxis], mask]
Out[12]:
array([[ 0, 2],
[ 4, 6],
[ 8, 10]])
All of these indexing options combined lead to a very flexible set of operations for accessing and
modifying array values.
Example: Selecting Random Points
One common use of fancy indexing is the selection of subsets of rows from a matrix.
In [13]:
mean = [0, 0]
cov = [[1, 2],
[2, 5]]
X = rand.multivariate_normal(mean, cov, 100)
X.shape
Out[13]:
(100, 2)

Modifying Values with Fancy Indexing


Just as fancy indexing can be used to access parts of an array, it can also be used to modify parts of an
array. For example, imagine we have an array of indices and we'd like to set the corresponding items
in an array to some value:
In [18]:
x = np.arange(10)
i = np.array([2, 1, 8, 4])
x[i] = 99
print(x)
[ 0 99 99 3 99 5 6 7 99 9]
We can use any assignment-type operator for this. For example:
In [19]:
x[i] -= 10
print(x)
[ 0 89 89 3 89 5 6 7 89 9]
Notice, though, that repeated indices with these operations can cause some potentially unexpected
results. Consider the following:
In [20]:
x = np.zeros(10)
x[[0, 0]] = [4, 6]
print(x)
[ 6. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
Where did the 4 go? The result of this operation is to first assign x[0] = 4, followed by x[0] = 6. The
result, of course, is that x[0] contains the value 6.
Fair enough, but consider this operation:
In [21]:
i = [2, 3, 3, 4, 4, 4]
x[i] += 1
x
Out[21]:
array([ 6., 0., 1., 1., 1., 0., 0., 0., 0., 0.])
You might expect that x[3] would contain the value 2, and x[4] would contain the value 3, as this is
how many times each index is repeated. Why is this not the case? Conceptually, this is because x[i]
+= 1 is meant as a shorthand of x[i] = x[i] + 1. x[i] + 1 is evaluated, and then the result is assigned to
the indices in x. With this in mind, it is not the augmentation that happens multiple times, but the
assignment, which leads to the rather nonintuitive results.
So what if you want the other behavior where the operation is repeated? For this, you can use
the at() method of ufuncs (available since NumPy 1.8), and do the following:
In [22]:
x = np.zeros(10)
np.add.at(x, i, 1)
print(x)
[ 0. 0. 1. 2. 3. 0. 0. 0. 0. 0.]
The at() method does an in-place application of the given operator at the specified indices (here, i)
with the specified value (here, 1). Another method that is similar in spirit is the reduceat() method of
ufuncs, which you can read about in the NumPy documentation.

You might also like