Nociones básicas

Esta práctica es una introducción para recordar algunas nociones de Python que nos serán útiles este curso.

Contenidos

Python

En este curso, trabajaremos con Python de tres formas distintas:

  • Spyder: es un entorno interactivo para escribir código Python que facilita la edición y el debugging de una forma sencilla. Lo utilizaremos para escribir el código de los ejercicios.

  • Consola: es la interación básica con Python. Lo usaremos en unos pocos ejercicios donde no es posible hacerlo con Spyder.

  • Ipython notebook: es un entorno interactivo en el que se combinan ejecución de código, texto enriquecido, fórmulas matemáticas, gráficos y "rich media". Con él han sido realizadas las prácticas de laboratorio.

Ejecutar el código: asumiendo que los paths son correctos,

  • Spyder: Puede abrirse o bien desde un lanzador de Spyder en el escritorio o desde el menú de Inicio. En cualquier caso, siempre se puede abrir spyder desde la línea de comandos tecleando spyder.

  • Consola: Simplemente abrir la consola y ejecutar python. Así entraríamos en python en línea de comandos. También se puede usar la consola para ejecutar un programa python tecleando python programa_python.py.

  • Ipython notebook: abrir la consola e ir a la carpeta donde hemos guardado el código (o queremos guardar el código) y ejecutar ipython notebook. Se abrirá una ventana nueva del navegador por defecto, donde podemos leer los ficheros existentes con sufijo ipynb o crear ficheros nuevos.

Módulos y paquetes

Python es un lenguaje muy versatil que puede realizar tareas muy distintas. En este curso nos centraremos en algoritmos numéricos que se pueden implementar con Python.

Python es modular. Un módulo es un programa que contiene, entre otras cosas, las definiciones de las variables y las funciones. A menudo, los módulos se agrupan en paquetes para una determinada aplicación. Por ejemplo, utilizaremos mayormente los paquetes NumPy (numerical python) y Matplotlib (python plotting).

La organización en módulos permite espacios de variables locales. No hay relación entre los nombres en los diferentes espacios de variables. Por ejemplo, si los usuarios tienen la precaución de usar como prefijo de la función el del módulo correspondiente, se podría definir la función maximiza con dos módulos diferentes sin que se confundieran.

Un programa típico python empieza importando módulos de los paquetes. Por ejemplo

In [1]:
import numpy as np

Aquí hemos importado todos los módulos de numpy y nos referiremos a él con el prefijo np:

In [2]:
print np.pi
3.14159265359

Hay variaciones de la orden import. Por ejemplo, si sólo queremos cargar la definición de $\pi$ usaremos

In [3]:
from numpy import pi as PI
print PI
3.14159265359

Algunas reglas de estilo y convenciones

La sintaxis de Python tiene algunas reglas y convenciones que intentan mejorar la legibilidad del código y la consistencia a lo largo de un amplio espectro de código Python. Por ejemplo:

  • Indentación. En Python, la indentación sustituye las aperturas-cierres de otros lenguajes. Usar cuatro espacios por nivel de indentación. En los editores Python, como Spyder, esto lo realiza el editor.
In [4]:
for i in range(0,3):
    print 'i = ', i
for j in ['a','b']:
    print 'j = ', j
i =  0
i =  1
i =  2
j =  a
j =  b
  • Longitud máxima de línea. El límite es de 79 caracteres por línea.

  • Import. Import va siempre en la parte de arriba del fichero, en líneas separadas, después de los comentarios del módulo. Y debe agruparse en el orden siguiente: librería estandar, módulos de terceros, aplicaciones locales/librerías específicas. Importaciones del tipo from <module> import * deben evitarse porque entonces no está claro el módulo del que proceden las aplicaciones y confundir tanto a lectores como a herramientas automáticas.

  • Convenciones para los nombres

    • Los nombres de los módulos deben ser cortos y con minúsculas. Se pueden usar guiones bajos.
    • Los nombres de las clases deben seguir la convención CapWords.
    • Los nombres de las funciones deben usar minúsculas, con las palabras separadas por guiones bajos, si es necesario para mejorar la legibilidad.
    • Los nombres de constantes deben estar escritos con todo mayúsculas con guiones bajos para separar palabras.
  • Espacios en blanco en expresiones. Evitar espacios en blanco en las siguientes situaciones:

    • Pegados a los paréntesis, llaves o corchetes:
      • Si: spam(pan[1], {queso: 2})
      • No: spam( pan[ 1 ], { queso: 2 } )
    • Inmediatamente antes de una coma, punto y coma o dos puntos:
      • Si: if x == 4: print x, y; x, y = y, x
      • No: if x == 4 : print x , y ; x , y = y , x
    • No obstante, cuando los dos puntos funciones como un operador binario, debe tener el mismo espacio a ambos lados:
      • Si: pan[1:9], pan[1:9:3], pan[:9:3]
      • No: pan[1: 9], pan[1:9 :3], pan[ :9:3]
    • Inmediatamente antes de abrir un paréntesis que empieza la lista de argumentos de la llamada a una función, o del corchete en una indexación:
      • Si: spam(1), lista[indice]
      • No: spam (1), lista [indice]
    • Alrededor de los operadores binarios, poner un espacio a cada lado. Si se usan operadores con distintas prioridades, añadir un espacio en blanco alrededor de los operadores con menor prioridad:
      • Si: i = i + 1, hypot2 = x*x + y*y, c = (a+b) * (a-b)
      • No: i=i+1, hypot2 = x * x + y * y, c = (a + b) * (a - b)

Para mayor información, mirar Style Guide for Python Code

NumPy

El objeto principal de NumPy es el array multidimensional homogéneo. Es una tabla de elementos (habitualmente números), todos del mismo tipo, indexados por una tupla de enteros positivos. En NumPy las dimensiones se llaman ejes. El número de ejes es el rango.

Hay muchas formas de crear un array NumPy. Por ejemplo,

In [5]:
a = np.array([1, 2, 3, 4])
b = np.array([(1.5, 2, 3), (4, 5, 6)])
c = np.zeros( (3, 4) )
d = np.arange( 1, 10, 2 )          
print a 
print b
print c
print d
[1 2 3 4]
[[ 1.5  2.   3. ]
 [ 4.   5.   6. ]]
[[ 0.  0.  0.  0.]
 [ 0.  0.  0.  0.]
 [ 0.  0.  0.  0.]]
[1 3 5 7 9]

Los índices comienzan en cero. El índice -1 da el último elemento del array.

In [6]:
print a[0], b[0,0], b[0][0]
print d[-1]
1 1.5 1.5
9

ndim: número de dimensiones del array

shape: número de elementos en cada dimensión

In [7]:
print 'dim = ', b.ndim, '; shape = ', b.shape
dim =  2 ; shape =  (2, 3)

Usaremos principalmente los tipos estandar int* y float* (además de otros tipos estandar como boolean):

In [8]:
print a.dtype
print b.dtype
int64
float64

Estos tipos pueden cambiar dentro del programa

In [9]:
a32 = a.astype('int32')
print a32.dtype
int32

Operaciones básicas

Los operadores aritméticos se aplican en los arrays elemento a elemento. ¡En las listas Python funcionan de forma diferente!

In [10]:
a = np.array([1, 2, 3, 4])
b = np.array([(1.5, 2, 3, PI)])
print a+b
a1 = [1, 2, 3, 4]
b1 = [1.5, 2, 3, PI]
print a1+b1
[[ 2.5         4.          6.          7.14159265]]
[1, 2, 3, 4, 1.5, 2, 3, 3.141592653589793]

A diferencia de otros lenguajes, el operador * se aplica elemento a elemento en los arrays NumPy. El producto de matrices se realiza utilizando la función dot:

In [11]:
A = np.array( [[1, 1], [0, 1]] )
B = np.array( [[2, 3], [1, 4]] )
print A*B
print A.dot(B)
print np.dot(A,B)
[[2 3]
 [0 4]]
[[3 7]
 [1 4]]
[[3 7]
 [1 4]]

Muchas operaciones, como calcular la suma de todos los elementos de un array, se implementan como métodos de las clase ndarray:

In [12]:
print B.max()
print B.max(axis=0)
print B.max(axis=1)
print A.sum()
4
[2 4]
[3 4]
3

Indexado

Es parecido al utilizado en Python. Por ejemplo

In [13]:
a = np.arange(10)**3
print a
print a[2:5]
print a[2:5:2]
b = np.array([0, 1, 5, -1])
print a[b]
[  0   1   8  27  64 125 216 343 512 729]
[ 8 27 64]
[ 8 64]
[  0   1 125 729]

Análogamente para arrays multidimensionales:

In [14]:
a = np.array([[1, 2, 4], [3, -1, 2]])
print a[0]
print a[1,2]
print a[:2, :2]
[1 2 4]
2
[[ 1  2]
 [ 3 -1]]

Copias

Cuando operamos y manipulamos arrays, algunas veces, sus datos se copian en un nuevo array, mientras que otras no. Esto crea confusión en los principiantes. Veamos tres ejemplos:

Sin copia: Una asignación sencilla no crea copia de los arrays o sus datos.

In [15]:
a = np.arange(12)
b = a
print a[0], b[0]
b[0] = 10
print a[0], b[0]
0 0
10 10

Con copia: el método copy realiza una copia completa del array y de sus datos.

In [16]:
b = a.copy()
print a[0], b[0]
b[0] = 0
print a[0], b[0]
10 10
10 0

Funciones elementales

NumPy contiene las funciones matemáticas elementales con sus nombres habituales. Por ejemplo

In [17]:
print np.sin(PI/2)
print np.exp(-1)
print np.arctan(np.inf)
print np.sqrt(4)
1.0
0.367879441171
1.57079632679
2.0

Definición de funciones

Podemos definir funciones de dos formas:

  • Usando una función lambda
  • Usando def
In [18]:
f1 = lambda x: x ** 3
f2 = lambda x,y: x+y
print f1(2)
print f2(1,1)
8
2
In [19]:
def f3(x):
    if (x>2):
        return 0
    else:
        return 1
In [20]:
print f3(-1)
print f3(3)
1
0

Pero si definimos la función de esta manera no funciona con arrays:

In [21]:
print a
print f3(a)
[10  1  2  3  4  5  6  7  8  9 10 11]
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-21-665f2a9f1fd2> in <module>()
      1 print a
----> 2 print f3(a)

<ipython-input-19-55709386c8a1> in f3(x)
      1 def f3(x):
----> 2     if (x>2):
      3         return 0
      4     else:
      5         return 1

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

Podemos modificar la función de la manera siguiente

In [22]:
def f3(x):
    return np.where(x>2, 0, 1)
In [23]:
print f3(a)
[0 1 1 0 0 0 0 0 0 0 0 0]

Para más información, ver Numpy Quickstart tutorial

Matplotlib

Usamos el paquete Matplotlib para hacer plots sencillos. La segunda línea del código siguiente se usa sólo en Notebooks pero no en línea de comando o en Spyder.

In [24]:
import matplotlib.pyplot as plt
%matplotlib inline

Plot en una dimensión

In [25]:
x=np.linspace(-1,2)                # definimos la malla
f = lambda x : x**3 - 2*x**2 + 1   # definimos la función 
OX=0*f(x)
In [26]:
plt.figure(1)
plt.plot(x,f(x))                   # dibujar la función
plt.plot(x,OX,'k-')                # dibujar el eje X
plt.xlabel('x')
plt.ylabel('y')
plt.show()
In [27]:
plt.figure(2)
plt.plot(x,np.sin(2*x),x,np.cos(3*x))                                   
plt.xlabel('x')
plt.ylabel('y')
plt.title('El seno y el coseno')
plt.show()

Plot en dos dimensiones

In [28]:
hW, hH = 600, 300
hFreq = 10.5

# Malla en el cuadrado [-1,1]x[-1,1]
x = np.linspace( -1, 1, 30)     # columnas (Ancho)
y = np.linspace( -1, 1, 30)     # filas (alto)

[X,Y] = np.meshgrid(x,y)
A = np.exp(-(X**2+Y**2)/10)

plt.imshow(A);
In [29]:
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter

Z = np.sin(X*2*np.pi)*np.cos(Y*2*np.pi)

fig = plt.figure()
ax = fig.gca(projection='3d')
surf = ax.plot_surface(X, Y, Z, cmap=cm.coolwarm,)
plt.show()

Referencias