You can perform some numeric computations with operators (covered in “Numeric Operations”) and built-in functions (covered in “Built-in Functions”). Python also provides modules that support additional numeric computations, covered in this chapter: math
and cmath
in “The math and cmath Modules”, operator
in “The operator Module”, random
in “The random Module”, fractions
in “The fractions Module”, and decimal
in “The decimal Module”. “The gmpy2 Module” also mentions the third-party module gmpy2
, which further extends Python’s numeric computation abilities. Numeric processing often requires, more specifically, the processing of arrays of numbers, covered in “Array Processing”, focusing on the standard library module array
and popular third-party extension NumPy.
The math
module supplies mathematical functions on floating-point numbers; the cmath
module supplies equivalent functions on complex numbers. For example, math.sqrt(-1)
raises an exception, but cmath.sqrt(-1)
returns 1j
.
Just like for any other module, the cleanest, most readable way to use these is to have, for example, import math
at the top of your code, and explicitly call, say, math.sqrt
afterward. However, if your code includes many calls to the modules’ well-known mathematical functions, it’s permissible, as an exception to the general guideline, to use at the top of your code from math import *
, and afterward just call sqrt
.
Each module exposes two float
attributes bound to the values of fundamental mathematical constants, e
and pi
, and a variety of functions, including those shown in Table 15-1.
acos, asin, atan, cos, sin, tan |
Returns the arccosine, arcsine, arctangent, cosine, sine, or tangent of |
math and cmath |
acosh, asinh, atanh, cosh, sinh, tanh |
Returns the arc hyperbolic cosine, arc hyperbolic sine, arc hyperbolic tangent, hyperbolic cosine, hyperbolic sine, or hyperbolic tangent of |
math and cmath |
atan2 |
Like
When |
math only |
ceil |
Returns |
math only |
e |
The mathematical constant e (2.718281828459045). |
math and cmath |
exp |
Returns |
math and cmath |
erf |
Returns the error function of |
math only |
fabs |
Returns the absolute value of |
math only |
factorial |
Returns the factorial of |
math only |
floor |
Returns |
math only |
fmod |
Returns the float |
math only |
fsum |
Returns the floating-point sum of the values in |
math only |
frexp |
Returns a pair |
math only |
gcd |
Returns the greatest common divisor of |
math only v3 only |
hypot |
Returns |
math only |
inf |
A floating-point positive infinity, like |
math only |
isclose |
Returns
Don’t use == between floating-point numbersGiven the approximate nature of floating-point arithmetic, it rarely makes sense to check whether two |
math and cmath v3 only |
isfinite |
Returns |
math and cmath v3 only |
isinf |
Returns |
math and cmath |
isnan |
Returns |
math and cmath |
ldexp |
Returns |
math only |
log |
Returns the natural logarithm of |
math and cmath |
log10 |
Returns the base-10 logarithm of |
math and cmath |
modf |
Returns a pair |
math only |
nan |
A floating-point “Not a Number” ( |
math only |
pi |
The mathematical constant π, 3.141592653589793. |
math and cmath |
phase |
Returns the phase of |
cmath only |
polar |
Returns the polar coordinate representation of |
cmath only |
pow |
Returns |
math only |
sqrt |
Returns the square root of |
math and cmath |
trunc |
Returns |
math only |
Always keep in mind that float
s are not entirely precise, due to their internal representation in the computer. The following example shows this, and also shows why the new function isclose
may be useful:
>>>
f
=
1.1
+
2.2
-
3.3
# f is intuitively equal to 0
>>>
f
==
0
False
>>>
f
4.440892098500626e-16
>>>
math
.
isclose
(
0
,
f
,
abs_tol
=
1e-15
)
# abs_tol for near-0 comparison
True
>>>
g
=
f
-
1
>>>
g
-0.9999999999999996 # almost -1 but not quite
>>>
math
.
isclose
(
-
1
,
g
)
# default is fine for this comparison
True
>>>
isclose
(
-
1
,
g
,
rel_tol
=
1e-15
)
# but you can set the tolerances
True
>>>
isclose
(
-
1
,
g
,
rel_tol
=
1e-16
)
# including higher precision
False
The operator
module supplies functions that are equivalent to Python’s operators. These functions are handy in cases where callables must be stored, passed as arguments, or returned as function results. The functions in operator
have the same names as the corresponding special methods (covered in “Special Methods”). Each function is available with two names, with and without “dunder” (leading and trailing double underscores): for example, both operator.add(
a
,
b
)
and operator.__add__(
a
,
b
)
return a
+
b
. Matrix multiplication support has been added for the infix operator @
, in v3,1 but you must (as of this writing) implement it by defining your own __matmul__()
, __rmatmul__()
, and/or __imatmul__()
; NumPy, however, does support
(but not yet @
@=
) for matrix multiplication.
Table 15-2 lists some of the functions supplied by the operator
module.
Method | Signature | Behaves like |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The operator
module also supplies two higher-order functions whose results are functions suitable for passing as named argument key
=
to the sort
method of lists, the sorted
built-in function, itertools.groupby()
, and other built-in functions such as min
and max
.
The random
module of the standard library generates pseudorandom numbers with various distributions. The underlying uniform pseudorandom generator uses the Mersenne Twister algorithm, with a period of length 2**19937-1
.
Pseudorandom numbers provided by the random
module, while very good, are not of cryptographic quality. If you want higher-quality random numbers, you can call os.urandom
(from the module os
, not random
), or instantiate the class SystemRandom
from random
(which calls os.urandom
for you).
urandom |
Returns |
An alternative source of physically random numbers: http://www.fourmilab.ch/hotbits.
All functions of the random
module are methods of one hidden global instance of the class random.Random
. You can instantiate Random
explicitly to get multiple generators that do not share state. Explicit instantiation is advisable if you require random numbers in multiple threads (threads are covered in Chapter 14). Alternatively, instantiate SystemRandom
if you require higher-quality random numbers. (See “Physically Random and Cryptographically Strong Random Numbers”.) This section documents the most frequently used functions exposed by module random
:
choice |
Returns a random item from nonempty sequence |
getrandbits |
Returns an |
getstate |
Returns a hashable and pickleable object |
jumpahead |
Advances the generator state as if |
randint |
Returns a random |
random |
Returns a random |
randrange |
Like |
sample |
Returns a new list whose |
seed |
Initializes the generator state. |
setstate |
Restores the generator state. |
shuffle |
Shuffles, in place, mutable sequence |
uniform |
Returns a random floating-point number |
The random
module also supplies several other functions that generate pseudorandom floating-point numbers from other probability distributions (Beta, Gamma, exponential, Gauss, Pareto, etc.) by internally calling random.random
as their source of randomness.
The fractions
module supplies a rational number class called Fraction
whose instances can be constructed from a pair of integers, another rational number, or a string. You can pass a pair of (optionally signed) integers: the numerator and denominator. When the denominator is 0, a ZeroDivisionError
is raised. A string can be of the form '3.14'
, or can include an optionally signed numerator, a slash (/
) , and a denominator, such as '-22/7'
. Fraction
also supports construction from decimal.Decimal
instances, and from float
s (although the latter may not provide the result you’d expect, given float
s’ bounded precision). Fraction
class instances have the properties numerator
and denominator
.
Fraction
reduces the fraction to the lowest terms—for example, f = Fraction(226, 452)
builds an instance f
equal to one built by Fraction(1, 2)
. The numerator and denominator originally passed to Fraction
are not recoverable from the built instance.
from fractions import Fraction
>>>
Fraction
(
1
,
10
)
Fraction(1, 10)
>>>
Fraction
(
Decimal
(
'
0.1
'
)
)
Fraction(1, 10)
>>>
Fraction
(
'
0.1
'
)
Fraction(1, 10)
>>>
Fraction
(
'
1/10
'
)
Fraction(1, 10)
>>>
Fraction
(
0.1
)
Fraction(3602879701896397, 36028797018963968)
>>>
Fraction
(
-
1
,
10
)
Fraction(-1, 10)
>>>
Fraction
(
-
1
,
-
10
)
Fraction(1, 10)
Fraction
also supplies several methods, including limit_denominator
, which allows you to create a rational approximation of a float
—for example, Fraction(0.0999).limit_denominator(10)
returns Fraction(1, 10)
. Fraction
instances are immutable and can be keys in dictionaries and members of sets, as well as being used in arithmetic operations with other numbers. See the fractions
docs for more complete coverage.
The fractions
module, in both v2 and v3, also supplies a function called gcd
that works just like math.gcd
(which exists in v3 only), covered in Table 15-1.
A Python float
is a binary floating-point number, normally in accordance with the standard known as IEEE 754 and implemented in hardware in modern computers. A concise, practical introduction to floating-point arithmetic and its issues can be found in David Goldberg’s essay What Every Computer Scientist Should Know about Floating-Point Arithmetic. A Python-focused essay on the same issues is part of the online tutorial; another excellent summary is also online.
Often, particularly for money-related computations, you may prefer to use decimal floating-point numbers; Python supplies an implementation of the standard known as IEEE 854, for base 10, in the standard library module decimal
. The module has excellent documentation for both v2 and v3: there you can find complete reference documentation, pointers to the applicable standards, a tutorial, and advocacy for decimal
. Here, we cover only a small subset of decimal
’s functionality that corresponds to the most frequently used parts of the module.
The decimal
module supplies a Decimal
class (whose immutable instances are decimal numbers), exception classes, and classes and functions to deal with the arithmetic context, which specifies such things as precision, rounding, and which computational anomalies (such as division by zero, overflow, underflow, and so on) raise exceptions when they occur. In the default context, precision is 28 decimal digits, rounding is “half-even” (round results to the closest representable decimal number; when a result is exactly halfway between two such numbers, round to the one whose last digit is even), and the anomalies that raise exceptions are: invalid operation, division by zero, and overflow.
To build a decimal number, call Decimal
with one argument: an integer, float, string, or tuple. If you start with a float
, it is converted losslessly to the exact decimal equivalent (which may require 53 digits or more of precision):
from
decimal
import
Decimal
df
=
Decimal
(
0.1
)
df
Decimal
(
'
0.1000000000000000055511151231257827021181583404541015625
'
)
If this is not the behavior you want, you can pass the float as a string; for example:
ds
=
Decimal
(
str
(
0.1
)
)
# or, directly, Decimal('0.1')
ds
Decimal
(
'
0.1
'
)
If you wish, you can easily write a factory function for ease of experimentation, particularly interactive experimentation, with decimal
:
def
dfs
(
x
):
return
Decimal
(
str
(
x
))
Now dfs
(0.1)
is just the same thing as Decimal(str(0.1))
, or Decimal('0.1')
, but more concise and handier to write.
Alternatively, you may use the quantize
method of Decimal
to construct a new decimal by rounding a float to the number of significant digits you specify:
dq
=
Decimal
(
0.1
)
.
quantize
(
Decimal
(
'
.00
'
)
)
dq
Decimal
(
'
0.10
'
)
If you start with a tuple, you need to provide three arguments: the sign (0 for positive, 1 for negative), a tuple of digits, and the integer exponent:
pidigits
=
(
3
,
1
,
4
,
1
,
5
)
Decimal
(
(
1
,
pidigits
,
-
4
)
)
Decimal
(
'
-3.1415
'
)
Once you have instances of Decimal
, you can compare them, including comparison with float
s (use math.isclose
for this); pickle and unpickle them; and use them as keys in dictionaries and as members of sets. You may also perform arithmetic among them, and with integers, but not with float
s (to avoid unexpected loss of precision in the results), as demonstrated here:
>>>
a
=
1.1
>>>
d
=
Decimal
(
'
1.1
'
)
>>>
a
==
d
False
>>>
math
.
isclose
(
a
,
d
)
True
>>>
a
+
d
Traceback (most recent call last):
File
"<stdin>"
, line
1
, in
<module>
TypeError
:
unsupported operand type(s) for +:
'decimal.Decimal' and
'float'
>>>
d
+
Decimal
(
a
)
# new decimal constructed from
a
Decimal('2.200000000000000088817841970') # whoops
>>>
d
+
Decimal
(
str
(
a
)
)
# convert
a
to decimal with str(
a
)
Decimal('2.20')
The online docs include useful recipes for monetary formatting, some trigonometric functions, and a list of Frequently Asked Questions (FAQ).
The gmpy2
module is a C-coded extension that supports the GMP, MPFR, and MPC libraries, to extend and accelerate Python’s abilities for multiple-precision arithmetic (arithmetic in which the precision of the numbers involved is bounded only by the amount of memory available). The main development branch of gmpy2
supports thread-safe contexts. You can download and install gmpy2
from PyPI.
You can represent arrays with lists (covered in “Lists”), as well as with the array
standard library module (covered in “The array Module”). You can manipulate arrays with loops; indexing and slicing; list comprehensions; iterators; generators; genexps (all covered in Chapter 3); built-ins such as map
, reduce
, and filter
(all covered in “Built-in Functions”); and standard library modules such as itertools
(covered in “The itertools Module”). If you only need a lightweight, one-dimensional array, stick with array
. However, to process large arrays of numbers, such functions may be slower and less convenient than third-party extensions such as NumPy and SciPy (covered in “Extensions for Numeric Array Computation”). When you’re doing data analysis and modeling, pandas, which is built on top of NumPy, might be most suitable.
The array
module supplies a type, also called array
, whose instances are mutable sequences, like lists. An array
a
is a one-dimensional sequence whose items can be only characters, or only numbers of one specific numeric type, fixed when you create a
.
array.array
’s advantage is that, compared to a list, it can save memory to hold objects all of the same (numeric or character) type. An array
object a
has a one-character, read-only attribute a
.typecode
, set on creation: the type code of a
’s items. Table 15-3 shows the possible type codes for array
.
typecode | C type | Python type | Minimum size |
---|---|---|---|
|
|
|
1 byte (v2 only) |
|
|
|
1 byte |
|
|
|
1 byte |
|
|
|
2 bytes (4 if this Python is a “wide build”) |
|
|
|
2 bytes |
|
|
|
2 bytes |
|
|
|
2 bytes |
|
|
|
2 bytes |
|
|
|
4 bytes |
|
|
|
4 bytes |
|
|
|
8 bytes (v3 only) |
|
|
|
8 bytes (v3 only) |
|
|
|
4 bytes |
|
|
|
8 bytes |
Note: 'c'
is v2 only. 'u'
is in both v2 and v3, with an item size of 2
if this Python is a “narrow build,” and 4
if a “wide build.” q
and Q
(v3 only) are available only if the platform supports C’s long long
(or, on Windows, __int64
) type.
The size in bytes of each item may be larger than the minimum, depending on the machine’s architecture, and is available as the read-only attribute a
.itemsize
. The module array
supplies just the type object called array
:
array |
Creates and returns an Array objects expose all methods and operations of mutable sequences (as covered in “Sequence Operations”), except |
In addition to the methods of mutable sequences, an array object a
exposes the following methods.2
As you’ve seen, Python has great support for numeric processing. However, third-party library SciPy and packages such as NumPy, Matplotlib, Sympy, IPython/Jupyter, and pandas provide even more tools. We introduce NumPy here, then provide a brief description of SciPy and other packages (see “SciPy”), with pointers to their documentation.
If you need a lightweight one-dimensional array of numbers, the standard library’s array
module may often suffice. If you are doing scientific computing, advanced image handling, multidimensional arrays, linear algebra, or other applications involving large amounts of data, the popular third-party NumPy package meets your needs. Extensive documentation is available online; a free PDF of Travis Oliphant’s Guide to NumPy book is also available.
The docs variously refer to the package as NumPy or Numpy; however, in coding, the package is called numpy
and you usually import it with import numpy as np
. In this section, we use all of these monikers.
NumPy
provides class ndarray
, which you can subclass to add functionality for your particular needs. An ndarray
object has n
dimensions of homogenous items (items can include containers of heterogenous types). An ndarray
object a
has a number of dimensions (AKA axes) known as its rank. A scalar (i.e., a single number) has rank 0
, a vector has rank 1
, a matrix has rank 2
, and so forth. An ndarray
object also has a shape, which can be accessed as property shape
. For example, for a matrix m
with 2 columns and 3 rows, m.shape
is (3,2)
.
NumPy
supports a wider range of numeric types (instances of dtype
) than Python; however, the default numerical types are: bool_
, one byte; int_
, either int64 or int32 (depending on your platform); float_
, short for float64; and complex_
, short for complex128.
There are several ways to create an array in NumPy
; among the most common are:
with the factory function np.array
, from a sequence (often a nested one), with type inference or by explicitly specifying dtype
with factory functions zeros
, ones
, empty
, which default to dtype
float64
, and indices
, which defaults to int64
with factory function arange
(with the usual start, stop, stride), or with factory function linspace
(start, stop, quantity) for better floating-point behavior
reading data from files with other np
functions (e.g., CSV with genfromtxt
)
Here are examples of creating an array, as just listed:
import numpy as np
np.array([1, 2, 3, 4]) # from a Python list
array([1, 2, 3, 4])
np.array(5, 6, 7) # a common error: passing items separately (they
# must be passed as a sequence, e.g. a list)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: only 2 non-keyword arguments accepted
s = 'alph', 'abet' # a tuple of two strings
np.array(s)
array(['alph', 'abet'], dtype='<U4')
t = [(1,2), (3,4), (0,1)] # a list of tuples
np.array(t, dtype='float64') # explicit type designation
array([[ 1., 2.],
[ 3., 4.],
[ 0., 1.]]
x = np.array(1.2, dtype=np.float16) # a scalar
x.shape
()
x.max()
1.2002
np.zeros(3) # shape defaults to a vector
array([ 0., 0., 0.])
np.ones((2,2)) # with shape specified
array([[ 1., 1.],
[ 1., 1.]])
np.empty(9) # arbitrary float64
s
array([ 4.94065646e-324, 9.88131292e-324, 1.48219694e-323,
1.97626258e-323, 2.47032823e-323, 2.96439388e-323,
3.45845952e-323, 3.95252517e-323, 4.44659081e-323])
np.indices((3,3))
array([[[0, 0, 0],
[1, 1, 1],
[2, 2, 2]],
[[0, 1, 2],
[0, 1, 2],
[0, 1, 2]]])
np.arange(0, 10, 2) # upper bound excluded
array([0, 2, 4, 6, 8])
np.linspace(0, 1, 5) # default: endpoint included
array([ 0. , 0.25, 0.5 , 0.75, 1. ])
np.linspace(0, 1, 5, endpoint=False) # endpoint not included
array([ 0. , 0.2, 0.4, 0.6, 0.8])
import io
np.genfromtxt(io.BytesIO(b'1 2 3
4 5 6')) # using a pseudo-file
array([[ 1., 2., 3.],
[ 4., 5., 6.]])
with io.open('x.csv', 'wb') as f:
f.write(b'2,4,6
1,3,5')
np.genfromtxt('x.csv', delimiter=',') # using an actual CSV file
array([[ 2., 4., 6.],
[ 1., 3., 5.]])
Each ndarray
object a
has an attribute a
.shape
, which is a tuple of int
s. len(
a
.shape)
is a
’s rank; for example, a one-dimensional array of numbers (also known as a vector) has rank 1
, and a
.shape
has just one item. More generally, each item of a
.shape
is the length of the corresponding dimension of a
. a
’s number of elements, known as its size, is the product of all items of a
.shape
(also available as property a.size
). Each dimension of a
is also known as an axis. Axis indices are from 0
and up, as usual in Python. Negative axis indices are allowed and count from the right, so -1
is the last (rightmost) axis.
Each array a
(except a scalar, meaning an array of rank-0
) is a Python sequence. Each item a
[
i
]
of a
is a subarray of a
, meaning it is an array with a rank one less than a
’s: a
[
i
].shape==
a
.shape[1:]
. For example, if a
is a two-dimensional matrix (a
is of rank 2
), a
[
i
]
, for any valid index i
, is a one-dimensional subarray of a
that corresponds to a row of the matrix. When a
’s rank is 1
or 0
, a
’s items are a
’s elements (just one element, for rank-0
arrays). Since a
is a sequence, you can index a
with normal indexing syntax to access or change a
’s items. Note that a
’s items are a
’s subarrays; only for an array of rank 1
or 0
are the array’s items the same thing as the array’s elements.
As for any other sequence, you can also slice a
: after b=a[i:j]
, b
has the same rank as a
, and b.shape
equals a.shape
except that b.shape[0]
is the length of the slice i:j
(j-i
when a.shape[0]>
j>=i>=0
, and so on).
Once you have an array a
, you can call a.reshape
(or, equivalently, np.reshape
with a
as the first argument). The resulting shape must match a.size
: when a.size
is 12
, you can call a.reshape(3,4)
or a.reshape(2,6)
, but a.reshape(2,5)
raises ValueError
. Note that reshape
does not work in place: you must explicitly bind or rebind the array—that is, a = a.reshape(i,j)
or b = a.reshape(i,j)
.
You can also loop on (nonscalar) a
in a for
, just as you can with any other sequence. For example:
for
x
in
a
:
process
(
x
)
means the same thing as:
for
_
in
range
(
len
(
a
)
)
:
x
=
a
[
_
]
process
(
x
)
In these examples, each item x
of a
in the for
loop is a subarray of a
. For example, if a
is a two-dimensional matrix, each x
in either of these loops is a one-dimensional subarray of a
that corresponds to a row of the matrix.
You can also index or slice a
by a tuple. For example, when a
’s rank is >=2
, you can write a
[
i
][
j
]
as a
[
i,j
]
, for any valid i
and j
, for rebinding as well as for access; tuple indexing is faster and more convenient. Do not put parentheses inside the brackets to indicate that you are indexing a
by a tuple: just write the indices one after the other, separated by commas. a
[
i,j
]
means the same thing as a
[(
i,j
)]
, but the form without parentheses is more readable.
An indexing is a slicing when one or more of the tuple’s items are slices, or (at most once per slicing) the special form ...
(also available, in v3 only, as Python built-in Ellipsis
). ...
expands into as many all-axis slices (:
) as needed to “fill” the rank of the array you’re slicing. For example, a[1,...,2]
is like a[1,:,:,2]
when a
’s rank is 4, but like a[1,:,:,:,:,2]
when a
’s rank is 6.
The following snippets show looping, indexing, and slicing:
a
=
np
.
arange
(
8
)
a
array
(
[
0
,
1
,
2
,
3
,
4
,
5
,
6
,
7
]
)
a
=
a
.
reshape
(
2
,
4
)
a
array
(
[
[
0
,
1
,
2
,
3
]
,
[
4
,
5
,
6
,
7
]
]
)
(
a
[
1
,
2
]
)
6
a
[
:
,
:
2
]
array
(
[
[
0
,
1
]
,
[
4
,
5
]
]
)
for
row
in
a
:
(
row
)
[
0
1
2
3
]
[
4
5
6
7
]
for
row
in
a
:
for
col
in
row
[
:
2
]
:
# first two items in each row
(
col
)
0
1
4
5
As mentioned in “The operator Module”, NumPy
implements the new3 operator @
for matrix multiplication of arrays. a1 @ a2
is like np.matmul(a1,a2)
. When both matrices are two-dimensional, they’re treated as conventional matrices. When one argument is a vector, you promote it to a two-dimensional array, by temporarily appending or prepending a 1, as needed, to its shape. Do not use @
with a scalar; use *
instead (see the following example). Matrices also allow addition (using +
) with a scalar (see example), as well as with vectors and other matrices (shapes must be compatible). Dot product is also available for matrices, using np.dot(a1, a2)
. A few simple examples of these operators follow:
a
=
np
.
arange
(
6
)
.
reshape
(
2
,
3
)
# a 2-d matrix
b
=
np
.
arange
(
3
)
# a vector
a
array
(
[
[
0
,
1
,
2
]
,
[
3
,
4
,
5
]
]
)
a
+
1
# adding a scalar
array
(
[
[
1
,
2
,
3
]
,
[
4
,
5
,
6
]
]
)
a
+
b
# adding a vector
array
(
[
[
0
,
2
,
4
]
,
[
3
,
5
,
7
]
]
)
a
*
2
# multiplying by a scalar
array
(
[
[
0
,
2
,
4
]
,
[
6
,
8
,
10
]
]
)
a
*
b
# multiplying by a vector
array
(
[
[
0
,
1
,
4
]
,
[
0
,
4
,
10
]
]
)
a
@
b
# matrix-multiplying by vector
array
(
[
5
,
14
]
)
c
=
(
a
*
2
)
.
reshape
(
3
,
2
)
# using scalar multiplication to create
c
# another matrix
array
(
[
[
0
,
2
]
,
[
4
,
6
]
,
[
8
,
10
]
]
)
a
@
c
# matrix multiplying two 2-d matrices
array
(
[
[
20
,
26
]
,
[
56
,
80
]
]
)
NumPy is rich enough to warrant books of its own; we have only touched on a few details. See the NumPy documentation for extensive coverage of its many features.
NumPy contains classes and methods for handling arrays; the SciPy library supports more advanced numeric computation. For example, while NumPy provides a few linear algebra methods, SciPy provides many more functions, including advanced decomposition methods, and also more advanced functions, such as allowing a second matrix argument for solving generalized eigenvalue problems. In general, when you are doing advanced numerical computation, it’s a good idea to install both SciPy and NumPy.
SciPy.org also hosts the documentation for a number of other packages, which are integrated with SciPy and NumPy: Matplotlib, which provides 2D plotting support; Sympy, which supports symbolic mathematics; IPython, a powerful interactive console shell and web-application kernel (the latter now blossoming as the Jupyter project); and pandas, which supports data analysis and modeling (you can find the pandas tutorials here, and many books and other materials here). Finally, if you’re interested in Deep Learning, consider using the open source TensorFlow, which has a Python API.