The library numpy allows to use vector and arrays in python in a faster and more efficient way for numerical computation with respect to "lists" and "list of lists".
Another advantage of using numpy arrays with respect to ordinary python lists is that it allows to import data from text or binary files written by C o Fortran, which are still the most used language for numerical computation.
The base construct is the ndarray, that can have any dimension and types corresponding to the classic ones of C or Fortran.
Moreover, one strenght of numpy is the possibility to work with vectors and arrays by using the vectorization capabilities of the processor, which allows quite efficient and fast computations with respect to data stored in ordinary python lists.
To start using the numpy library, as it happens for any python library, one has to import the corresponding module. To use a quicker notation when invoking the functions belonging to the library, we can assign a shortcut to the library name, like this:
import numpy as np
ndarray_name = np.ndarray( dimensions, dtype = type )where dimensions is a tuple containing the dimensions of the object, and type is its type. type may be one of:
Data type | Description |
---|---|
bool | Boolean (True or False) stored as a byte |
int | Platform integer (normally either int32 or int64) |
int8 | Byte (-128 to 127) |
int16 | Integer (-32768 to 32767) |
int32 | Integer (-2147483648 to 2147483647) |
int64 | Integer (9223372036854775808 to 9223372036854775807) |
uint8 | Unsigned integer (0 to 255) |
uint16 | Unsigned integer (0 to 65535) |
uint32 | Unsigned integer (0 to 4294967295) |
uint64 | Unsigned integer (0 to 18446744073709551615) |
float | Shorthand for float64. |
float16 | Half precision float: sign bit, 5 bits exponent, 10 bits mantissa |
float32 | Single precision float: sign bit, 8 bits exponent, 23 bits mantissa |
float64 | Double precision float: sign bit, 11 bits exponent, 52 bits mantissa |
complex | Shorthand for complex128. |
complex64 | Complex number, represented by two 32-bit floats (real and imaginary components) |
complex128 | Complex number, represented by two 64-bit floats (real and imaginary components) |
ndarray_name = np.array( values, dtype=...)
where values are initialization values for the array, that is, for instance, a list, list of lists, tuple, etc.
Example:
# Please, notice as the values may NOT be initialized!
aa=np.ndarray((10,10),dtype=float)
print(aa)
# Here the values are initialized to a list!
a=np.array([10,20,30,40,50], dtype=int)
print(a)
The specific types (int32, float64, etc.) can be used, in turn, to initialize an ndarray object with, for instance a list or a tuple (BUT, be aware that if you use standard types produce an error! This is because they are used for type conversion...)
Example:
a=np.float64((0,1,2,3,4,5))
print(a)
b=np.float([0,1,2,3,4,5])
print(b)
# Note: np.pi is pi=3.1415926...
x=np.zeros((10,2))
y=np.arange(0.0,2.0*np.pi,0.1)
z=np.linspace(0.0,2.0*np.pi,11)
print(x)
print(y)
print(z)
Examples:
u2=np.zeros((4,4),dtype=np.float32)
print(u2)
print(u2.ndim)
print(u2.shape)
print(u2.size)
print(u2.itemsize)
print(u2.dtype)
print(u2.astype(np.int32))
Examples:
x=np.linspace(-np.pi, np.pi, 101)
print('x=', x)
print('min. val.=', x.min())
print('max. val.=', x.max())
print('mean=', x.mean())
print('sum=', x.sum())
print('sum (mean*N)=', x.mean() * x.size)
print('standard deviation=', x.std())
As we have already seen, the constructor array(list) produces a numpy vector (or array) initialized with the values of list. The method tolist(ndarray) does the converse, namely transform an ndarray to a python list!
Examples:
x=np.array([1,2,3,4,5])
print(x)
type(x)
xx=x.tolist()
print(xx)
type(xx)
The true power of numpy is the hability to perform vector operations on data when using, for instance mathematical functions on numpy ndarrays. For instance:
# Produces a vector of equally spaced 101 float values between 0 and 2pi
x=np.linspace(0.0, 2.0*np.pi, 11)
print("x=", x)
# Defines y=sin(x) by taking x as a vector
y=np.sin(x)
print("y=",y)
Both numpy vectors and arrays (that is, ndarray objects) can be "sliced" like ordinary python lists, and then we can apply the numerical operators to "slices" too:
# Produces a 10x10 array with elements between 0 and 1
x=np.arange(0.0,1.0,0.1)
y=np.arange(0.0,1.0,0.1)
xx=np.zeros((10,10))
for i in range(10):
for j in range(10):
xx[i,j] = x[i] * y[j]
print("xx=", xx)
# Computes and prints 10 times the square root of x * y
yy=10*np.sqrt(xx)
print("yy=",yy)
Example:
# Creates a nx2 array (2 vectors!)
xx=np.ndarray((11,2),dtype=float)
# First column: 10 equally spaced values between 0 and 2*pi
xx[:,0]=np.linspace(0.0,2.0*np.pi,11)
# Second column: sin(x)!
xx[:,1]=np.sin(xx[:,0])
print("xx = ", xx)
xx.tofile("data.dat", sep='\t')
yy=np.fromfile("data.dat", sep='\t')
print("yy = ", yy) # Note as yy is now a SINGLE vector instead of an array! NOT GOOD!
# The same values are written and read by using savetxt() and loadtxt(), instead!
print("xx = ", xx)
np.savetxt("new_data.dat", xx)
yy=np.loadtxt("new_data.dat")
print("yy = ", yy) # Much better!
x,y=np.loadtxt("file_name",unpack=True)which allows to read two (or more) columns of data directly in different vectors, for instance! Default is False (why???!!!)
Example:
np.savetxt("other_data.dat", xx, header="Written columns: x=[0,2pi] and sin(x)", footer="Here ends the file...")
yy=np.loadtxt("other_data.dat")
print("yy: ", yy)
# Alternative way to read data into two vectors with "unpack=True"
x, y = np.loadtxt("other_data.dat", unpack=True)
print("x=", x)
print("y=", y)
plot()and
show()
Example:
import matplotlib.pyplot as plt
import numpy as np
plt.plot([0.0, 1.0, 4.0, 9.0, 16.0, 25.0])
plt.show()
A single list or ndarray is interpreted as the vector describing the ordinates of the function to plot (\(y\) data), BUT if we pass two lists (or ndarrays) the first one is interpreted as the vector of abscissae (\(x\) data). Finally, a third parameter, if present, is interpreted a a descriptor for the color, stile, etc. of the line.
Example: plots data from two lists with a solid line and red dots...
plt.plot([0.0, 1.0, 2.0, 3.0, 4.0, 5.0],[0.0, 1.0, 4.0, 9.0, 16.0, 25.0],'or-')
plt.show()
Of course, it is better to define three ndarrays, one for the abscissae and the other two for the ordinates of the plot. In the following example we overlap the two functions, one with blue dots (\(\sin(x)\)), another one with green triangles (\(\cos(x)\)):
x=np.linspace(0.0,2.0*np.pi,101)
y=np.sin(x)
z=np.cos(x)
plt.plot(x,y,'o-')
plt.plot(x,z,'^-')
plt.show()
Of course, one can use just a single command for the two functions (note: we do not use symbols here, but we explicitly set the color before the symbol of line!):
plt.plot(x,y,'g-',x,z,'b-')
plt.show()
However, one can also use just the name of the color (ex.: green), an RGB hexadecimal sequence (ex: #FF0000 is the red!), an RGB tuple, or a greyscale through a string containing a number between 0.0 and 1.0, as well.
Example: (sinus in green, cosinus in red)...
plt.plot(x,y,'g',x,z,'#A0A0A0')
plt.show()
... or sinus in yellow, cosinus in grey:
plt.plot(x,y,'yellow',x,z,'0.5')
plt.show()
Examples:
plt.plot(x,y,'g-',x,z,'b--')
plt.show()
Collecting all the plots together is of course simple, however, by separating them, one can have higher flexibility in fine-tuning the style of the plot, by using suitable keywords to indicate additional features of the plot.
Example:
plt.plot(x,y,color='c',marker='x',linestyle=':')
plt.plot(x,z,color='#0000FF',marker='+',linestyle='--')
plt.show()
Example:
plt.plot(x,y,linestyle='-',linewidth=2.0,color='b',marker="x")
plt.plot(x,z,ls='-',lw=1.0,marker='x',markersize=10.0,color='y',markevery=(10))
plt.show()
Another way to specify the characteristics of a line is to get an object "line" from the function plot and to modify its properties through the function setp().
myline = plt.plot(x,y)
plt.setp(myline,linestyle="-.",color="green")
plt.show()
legend( list_of_labels, pos=position, fontsize=font_size, ...)where:
Location string | Integer value |
---|---|
'best' | 0 |
'upper right' | 1 |
'upper left' | 2 |
'lower left' | 3 |
'lower right' | 4 |
'right' | 5 |
'center left' | 6 |
'center right' | 7 |
'lower center' | 8 |
'upper center' | 9 |
'center' | 10 |
Example:
plt.plot(x,y,linestyle='-',linewidth=2.0,color='b',marker="x")
plt.plot(x,z,ls='-',lw=1.0,marker='x',markersize=10.0,color='y',markevery=(10))
plt.legend([r"sin(x)", r"cos(x)"], loc=3)
plt.show()
It is possible to improve the quality of the plot by adding a title, labels on the axes, a grid, text in arbitrary positions, etc.
def damped_exponential(x):
y=np.exp(-0.5*x)*np.cos(2.0*x)
return y
pi=np.pi
x=np.linspace(0.0,2.0*np.pi,101)
y=damped_exponential(x)
plt.plot(x,y,'ko-')
plt.title("A damped exponential")
plt.xlabel("x")
plt.ylabel("y")
plt.grid(True)
plt.text(pi,np.exp(-0.5*pi),"Max 1")
plt.text(0.5*pi,-np.exp(-0.25*pi),"Min 1")
plt.axis([0.0,2.0*pi,-1.2,1.2]) # Notice that min and max of the axes are specified through a list!
plt.show()
Note:
One of very useful characteristics of matplotlib is the possibility to use the \(\TeX\) or \(\LaTeX\) rendering of strings, by putting an "r" (standing for "raw") in front of the string, to indicate to python to NOT interpret the \(\TeX\) or \(\LaTeX\) symbols as escape sequences!
The previous example, a little bit nicer:
plt.plot(x,y,'ko-')
plt.title(r"A damped exponential: $e^{-\frac{x}{2}}\cos(2x)$")
plt.xlabel(r"$x$")
plt.ylabel(r"$y$")
plt.grid(True)
plt.text(pi,1.4*np.exp(-0.5*pi),"Max 1",horizontalalignment="center",fontsize=18)
plt.text(0.5*pi,-1.1*np.exp(-0.25*pi),"Min 1",horizontalalignment="right",verticalalignment="top")
plt.xlim(0.0,2.0*pi) # Note as here we us xlim() and ylim() to set the min and max of axes, instead of axis()!
plt.ylim(-0.7,1.2)
plt.show()
It is possible to annotate the plot with a text and an arrow through the function "annotate()":
plt.plot(x,y,'ko-')
plt.title(r"A damped exponential: $e^{-\frac{x}{2}}\cos(2x)$")
plt.xlabel(r"$x$")
plt.ylabel(r"$y$")
plt.grid(False) # Note that we removed the grid here!
xpos=pi
ypos=np.exp(-0.5*pi)
# Note the arrow parameters: shrink move away a little bit the tip and the base of the arrow from the point!
plt.annotate("Max 1", xy=(xpos,ypos), xytext=(xpos+1.0,ypos+0.5), arrowprops={"facecolor":"blue","shrink":0.05})
plt.xlim(0.0,2.0*pi)
plt.ylim(-0.7,1.2)
plt.show()
subplot(num_rows,num_cols,position)
Example:
plt.subplot(211)
plt.plot(x,y,'ko-')
plt.title("A damped exponential")
plt.xlabel(r"$x$")
plt.ylabel(r"$y$")
plt.subplot(212)
plt.plot(x,np.abs(y),'ko-')
plt.title("Absolute value of a damped exponential")
plt.xlabel(r"$x$")
plt.ylabel(r"$|y|$")
plt.show()
xscale("log|linear") yscale("log|linear")
Example:
plt.subplot(211)
plt.plot(x,y,'ko-')
plt.title(r"A damped exponential in linear $y$ scale")
plt.xlabel(r"$x$")
plt.ylabel(r"$y$")
plt.subplot(212)
plt.yscale("log")
plt.plot(x,np.abs(y),'ko-')
plt.title(r"Absolute value of a damped exponential in $y$-log scale")
plt.xlabel(r"$x$")
plt.ylabel(r"$\log(|y|)$")
plt.show()
def power_law(x,n):
y=np.power(x,n)
return y
x=np.linspace(1.0,101.0,101)
y=power_law(x,-5.0/3.0)
plt.subplot(211)
plt.plot(x,y,'ko-')
plt.title(r"A power-law in linear $x-y$ scales")
plt.xlabel(r"$x$")
plt.ylabel(r"$y$")
plt.subplot(212)
plt.xscale("log")
plt.yscale("log")
plt.plot(x,y,'ko-')
plt.title(r"A power-law in log $x-y$ scale")
plt.xlabel(r"$\log(x)$")
plt.ylabel(r"$\log(y)$")
plt.show()
It is possible to produce polar plots, pie plots, histograms, etc., through several functions.
Example: polar coordinates...
r = np.linspace(0.0,5.0,101)
theta = 2.0 * np.pi * r
plt.polar(r, theta)
plt.show()
... pie plot...
dims=[10.0,30.0,40.0,10.0]
mylabels=["data1","data2","data3","data4"]
mycolors=['b','r','y','magenta']
plt.pie(dims,labels=mylabels,colors=mycolors)
plt.show()
... and, finally, contour plots.
Example:
x=np.zeros((101,51),dtype=float)
y=np.zeros((101,51),dtype=float)
xx=np.linspace(-2.0,2.0,101)
yy=np.linspace(-2.0,2.0,51)
for j in range(51):
x[:,j]=xx
for i in range(101):
y[i,:]=yy
z=np.exp(-(x*x + y*y))
plt.contour(x,y,z)
plt.show()