Simulación

Uso básico de Python

Esta sección sirve como repaso o introducción, por lo cual participantes que ya tienen familiaridad en Python y lo han utilizado recientemente no necesitan revisar esta parte sino pueden proceder directamente a la sección de paralelismo.

Instalación

Python es una herramienta muy útil y versátil para el cómputo científico. Funciona en Windows, Linux & Mac OS. Está disponible en línea de forma gratuita en www.python.org/downloads.

El material de estudio en esta página incluye las instrucciones requeridas para diferentes operaciones necesarias. Conviene hacer esta ventana de navegador delgada para que ocupe la mitad de la pantalla por máximo y colocar a su lado otra ventana que tenga Python ejecutándose.

Cada código de Python (que se muestra en esta página en letra azul) conviene copiarlo al intérprete interactivo de Python y probarlo uno mismo, modificando y explorando hasta que quede claro qué y por qué está sucediendo en la herramienta.

Para salir de Python usa la instrucción quit().

La asignación de valores a variables en Python (interactivo) y la consulta de ellas se realiza de la siguiente forma:

>>> X = 3
>>> X
3
>>> X = 4
>>> X
4

El texto de arriba incluye la salida de Python, por lo cual incluye los símbolos ">>>" que marcan los inicios de instrucciones en Python interactivo en consola. No se teclean esos símbolos. El usuario escribe lo de X = 3 mientras Python ya produjo el ">>>" para indicar que está listo para recibir instrucciones. Solamente se ocupa escribir la parte en letra azul.

La creación de una lista en Python y la consulta de ello se realiza de la siguiente forma:

>>> datos = [4, 2, 4, 5]
>>> n = len(datos)
>>> datos
[4, 2, 4, 5]
>>> n
4
>>> min(datos)
2
>>> max(datos)
5
>>> sum(datos)
15
>>> ord = sorted(datos)
>>> ord[0]
2
>>> ord[-1]
5

Otras rutinas útiles para analizar listas son las siguientes y requieren los paquetes numpy y scipy (véase instalación de paquetes):

>>> data = [1, 4, 2, 4, 2, 5, 6, 7, 4, 76, 3, 2, 5, 6, 7]
>>> import numpy as np
>>> np.mean(data)
8.933333
>>> np.median(data)
4
>>> from scipy.stats import describe
>>> describe(data)
DescribeResult(nobs=15, minmax=(1, 76), mean=8.933333333333334, variance=347.78095238095233, skewness=3.4131591099307736, kurtosis=9.810409945242618)
>>> np.quantile(data, 0.9)
7.0
>>> np.unique(data, return_counts = True)
(array([ 1, 2, 3, 4, 5, 6, 7, 76]), array([1, 3, 1, 3, 2, 2, 2, 1]))
>>> np.var(data, ddof=1)
347.78095238095233
>>> np.std(data, ddof=1)
18.648886089548412
>>> otra = [1, 5, 3, 5, 3, 65, 4, 6, 3, 6, 3, 56, 4]
>>> len(otra)
13
>>> len(data)
15
>>> otra += [2, 4]
>>> np.corrcoef(data, [1, 5, 3, 5, 3, 65, 4, 6, 3, 6, 3, 56, 4, 2, 4])[0,1]
-0.08119100801154079

Para crear una matríz con puros ceros, se ocupa la rutina zeros. Toma como argumentos la cantidad de filas y columnas. Por ejemplo, M = np.zeros((3, 3)) produce una matriz tres por tres con puros ceros:

>>> M = np.zeros((3, 3))
>>> M
array([[0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.]])

Para comparaciones entre listas, las subrutinas all y any sirven para determinar si todos o algunos de los elementos son iguales:

>>> a = [1, 2, 3]
>>> b = [1, 2, 4]
>>> c = [x == y for (x, y) in zip(a, b)]
>>> c
[True, True, False]
>>> all(c)
False
>>> any(c)
True

La aritmética funciona en grandes rasgos como uno esperaría, con el detalle que las operaciones vectoriales y matriciales de álgebra lineal no se hacen con listas sino arreglos de numpy:

>>> a = 3
>>> b = 4
>>> c = np.array([5, 6, 7])
>>> d = np.array([8, 10, 12])
>>> a + b
7
>>> a - b
-1
>>> a * b
12
>>> a / b
0.75
>>> a**b
81
>>> b**a
64
>>> a + c
array([ 8, 9, 10])
>>> c + d
array([13, 16, 19])
>>> c * d
array([40, 60, 84])
>>> np.inner(c, d)
184
>>> e = np.matrix([[1, 3], [2, 4]])
>>> e
matrix([[1, 3],
        [2, 4]])
>>> f = np.matrix([[2, 6], [4, 8]])
>>> f
matrix([[2, 6],
        [4, 8]])
>>> e + f
matrix([[3,  9],
        [6, 12]])
>>> e - f
matrix([[-1, -3],
        [-2, -4]])
>>> np.multiply(e, f)
matrix([[ 2, 18],
        [ 8, 32]])
>>> e * f
matrix([[14, 30],
        [20, 44]])

El redondeo de enteros a decimales se hace con las tres reglas: hacia abajo con la función piso floor, hacia arriba con la función techo ceil y al entero más cercano con round — los primeros dos provienen de la librería estándar math. La división entera se logra con /, la división entera con // y el residuo (también llamado modulo con % que en Python funciona hasta con decimales:

>>> d = 7.3
>>> from math import floor, ceil
>>> floor(d)
7
>>> ceil(d)
8
>>> round(d)
7
>>> d / 2
3.65
>>> d // 2
3.0
>>> d % 2
1.2999999999999998
>>> round(d) % 2
1

Las funciones matemáticas tienen sus nombres típicos y están disponibles en la librería math:

>>> from math import sqrt, exp, sin, cos, tan, tanh, log
>>> x = 123.4
>>> sqrt(x)
11.108555261599053
>>> exp(x)
3.9078606320089135e+53
>>> sin(x)
-0.7693905459455221
>>> cos(x)
-0.6387786688749486
>>> tan(x)
1.2044712565318034
>>> tanh(2.5)
0.9866142981514303
>>> log(x)
4.8154311114712876
>>> log(x, 10)
2.091315159697223
>>> log(x, 2)
6.947198584262056

Para permutaciones y combinaciones, existen subrutinas para el factorial y la coeficiente binomial:

>>> from math import factorial
>>> factorial(5)
120
>>> from scipy.special import binom
>>> binom(9, 3)
84.0
>>> factorial(9) / (factorial(6) * factorial(3))
84.0

Para manipular secuencias, existen los siguientes trucos prácticos:

>>> s = np.arange(4, 100, 2)
>>> s
array([ 4,  6,  8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36,
       38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70,
       72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98])
>>> r = [13] * 20
>>> r
[13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13]
>>> i = [j for j in range(2, 13)]
>>> i
[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
>>> c = np.histogram(s, bins = 4)
>>> c
(array([12, 12, 12, 12]), array([ 4. , 27.5, 51. , 74.5, 98. ]))
>>> s[-5:]
array([90, 92, 94, 96, 98])
>>> s[-10:]
array([80, 82, 84, 86, 88, 90, 92, 94, 96, 98])
>>> s[:5]
array([ 4, 6, 8, 10, 12])
>>> s[:8]
array([ 4, 6, 8, 10, 12, 14, 16, 18])

Para expresiones condicionales, repeticiones, y lógica, sirve lo siguiente:

>>> x = 3
>>> y = None
>>> y = x * 3 if x > 5 else 6 - x
>>> y
3
>>> z = None
>>> z = 2 if x % 2 == 1 and y % 2 == 1 else 3
>>> z
2
>>> if z != 1:
...     print('no es uno')
...
no es uno
>>> print('hay un dos' if x == 2 or y == 2 or z == 2 else 'no hay un dos')
hay un dos
>>> for i in range(1, 6):
...     print(2**i)
...
2
4
8
16
32
>>> while x > 0:
...     print('quito uno')
...      x -= 1
...
quito uno
quito uno
quito uno
>>> x
0

Subrutinas

Cuando algún cálculo ocupa ser reutilizado en múltiples ocasiones, posiblemente con algunas variaciones, conviene encapsularlo en una subrutina como una función:

def nombre(p1, p2, ... ):
     ...
     return resultado

Conviene probar la creación y llamadas a subrutinas propias.

>>> def potdos(x):
...     return 2**x
...
>>> potdos(3)
8
>>> from math import sqrt
>>> def eucl(x1, y1, x2, y2):
...     dx = x1 - x2
...     dy = y1 - y2
...     return sqrt(dx**2 + dy**2)
....
>>> eucl(1, 2, 3, 4)
2.8284271247461903

Números pseudoaleatorios

Generación pseudoaleatoria refiere a la creación de datos que parecen ser al azar. Verdaderamente no lo son si se generan de forma mecánica/aritmética. El sitio web random.org proporciona datos que están basados en bits generados por ruido atmosférico que son "más aleatorios" que cualquier cosa que se genere de forma mecánica.

Para números pseudoaleatorios uniformemente distribuidos, se usa runif, con parámetros opciones en el caso que no se quiera que sean entre cero y uno sino de otro rango de valores. Para crear valores de verdad o falso, 50–50, por ejemplo, se puede condicionar al valor uniforme. La subrutina sample sirve para muestreo y permutaciones.

>>> from random import random
>>> random()
0.4542473300983174
>>> from numpy.random import rand
>>> rand(5)
array([0.56381609, 0.74918022, 0.57375601, 0.12773787, 0.41589151])
>>> from numpy.random import uniform
>>> uniform(4, 10, size = 2)
array([7.27280135, 7.5751913 ])
>>> random() < 0.5
True
>>> random() < 0.5
False
>>> random() < 0.5
True
>>> rand(5) < 0.5
array([False, False, True, False, False])
>>> from random import sample
>>> sample([i for i in range(1, 11)], 3)
[2, 6, 9]
>>> sample([i for i in range(1, 11)], 3)
[7, 1, 4]
>>> from random import shuffle
>>> a = [i for i in range(1, 11)]
>>> a
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> shuffle(a)
>>> a
[3, 5, 2, 10, 4, 9, 6, 8, 1, 7]
>>> from numpy.random import choice
>>> choice([2, 4, 6], size = 10, replace = True, p = [0.1, 0.2, 0.7])
array([2, 6, 6, 6, 6, 6, 6, 6, 6, 2])

Python es capaz de producir números pseudoaleatorios en una varias distributiones (c.f. documentación), además de contar con las funciones de densidad y cumulativas de las distribuciones (c.f. documentación). Ejemplos útiles incluyen las distribución normal (gaussiana) y exponencial.

>>> from numpy.random import normal, exponential
>>> normal(size = 10) # media cero, desv. est. 1
array([-3.62250207, -0.85582587, -0.77536386,  0.07319879, -0.7018473 ,
       -0.17808432, -0.19535574,  0.80870695,  0.44887121,  1.58750338])
>>> normal(loc = 5, scale = 1.3, size = 10)
array([5.08377992, 4.63262477, 4.29651358, 3.85644244, 4.41892421,
       5.42060481, 4.02797161, 6.83631189, 4.73899607, 6.94469645])
>>> exponential(size = 2)
array([0.42913609, 4.47809099])
>>> exponential(scale = 1 / 3, size = 2)
array([0.00398857, 0.12845763])

Conjuntos de datos

Para traer datos a Python, lo más conveniente es utilizar archivos de texto, o importar desde hojas de cálculo en el formato CSV (comma separated values), aunque en Python se puede utilizar cualquier separador. Se ocupa instalar y cargar el paquete pandas.

Para probar, colocamos los siguientes datos en un archivo llamado ejemplo.csv con un editor de texto básico tipo notepad o emacs:

Nombre Estatura Gatos Perros
Elisa 184 2 0
Gonzalo 177 0 1
Jorge 180 1 1
Mariana 158 0 0
Carlos 164 1 0

>>> import pandas as pd
>>> datos = pd.read_csv('ejemplo.csv', sep=' ')
>>> datos
    Nombre  Estatura  Gatos  Perros
0    Elisa       184      2       0
1  Gonzalo       177      0       1
2    Jorge       180      1       1
3  Mariana       158      0       0
4   Carlos       164      1       0
>>> datos.Estatura
0    184
1    177
2    180
3    158
4    164
Name: Estatura, dtype: int64
>>> sum(datos.Gatos)
4
>>> sum(datos.Gatos) > sum(datos.Perros)
True
>>> datos.Gatos == datos.Perros
0    False
1    False
2     True
3     True
4    False
dtype: bool
>>> datos.loc[datos['Nombre'] == 'Elisa']
  Nombre  Estatura  Gatos  Perros
0  Elisa       184      2       0
>>> prom = np.mean(datos.Estatura)
>>> datos.loc[datos['Estatura'] > prom]
    Nombre  Estatura  Gatos  Perros
0    Elisa       184      2       0
1  Gonzalo       177      0       1
2    Jorge       180      1       1
>>> datos[datos$Estatura > prom,]$Gatos
2 0 1
>>> datos = datos.append({'Nombre': 'Tania', 'Estatura': 175, 'Gatos': 0, 'Perros': 2}, ignore_index = True)
>>> datos
    Nombre  Estatura  Gatos  Perros
0    Elisa       184      2       0
1  Gonzalo       177      0       1
2    Jorge       180      1       1
3  Mariana       158      0       0
4   Carlos       164      1       0
5    Tania       175      0       2
>>> datos['Estudiante'] = [False, True, True, True, False, False]
>>> datos
    Nombre  Estatura  Gatos  Perros  Estudiante
0    Elisa       184      2       0       False
1  Gonzalo       177      0       1        True
2    Jorge       180      1       1        True
3  Mariana       158      0       0        True
4   Carlos       164      1       0       False
5    Tania       175      0       2       False
>>> datos.loc[2,]
Nombre        Jorge
Estatura        180
Gatos             1
Perros            1
Estudiante     True
Name: 2, dtype: object
>>> datos.loc[1:3,]
    Nombre  Estatura  Gatos  Perros  Estudiante
1  Gonzalo       177      0       1        True
2    Jorge       180      1       1        True
3  Mariana       158      0       0        True
>>> datos.loc[1:3].Gatos
1    0
2    1
3    0
Name: Gatos, dtype: int64

Gráficas sencillas

Python contiene bastantes funcionalidades para graficar información en el paquete matplotlib (hay que instalarla una vez y cargarla antes de cada uso). La subrutina plot crea una gráfica (será de puntos si no se solicita que sea de líneas con -). Se le pueden agregar cosas en ese mismo dibujo si no se cierra con close(). El título se coloca con la rutina title():

>>> x = np.arange(1, 21)
>>> y = np.sin(x)
>>> import matplotlib.pyplot as plt
>>> plt.plot(x, y)
>>> plt.show()
>>> plt.close()
>>> plt.plot(x, y, 'o')
>>> plt.show()
>>> plt.close()
>>> plt.plot(x, y, '-o')
>>> plt.plot(x, np.cos(x), color ='red')
>>> plt.axhline(y = 0, color = 'lime')
>>> plt.show()
>>> plt.close()
>>> plt.plot(x, y)
>>> plt.plot(x + 1, np.cos(x + 1), marker = 's', color = 'green')
>>> plt.show()
>>> plt.close()
>>> plt.plot(x, y)
>>> plt.title('Texto arriba')
>>> plt.xlabel('Etiqueta')
>>> plt.ylabel('Otra etiqueta')
>>> plt.show()
>>> plt.close()

Se puede guardar gráficas a archivos, por ejemplo para compartirlos o cuando uno no está trabajando en un ambiente gráfico que pueda abrir ventanas:

>>> plt.plot(x, y, marker = '^')
>>> plt.savefig('salida.png')
>>> plt.close()

Diagramas caja-bigote

>>> d1 = [10, 32, 34, 24, 49, 42, 89]
>>> d2 = [30, 23, 45, 24, 75, 34, 12, 56, 33]
>>> plt.boxplot(d1)
>>> plt.show()
>>> plt.close()
>>> plt.boxplot(d2)
>>> plt.show()
>>> plt.close()
>>> plt.boxplot([d1, d2])
>>> plt.show()
>>> plt.close()

Histogramas

Existe una rutina en matplotlib que se llama hist para dibujar histogramas. Se le puede indicar a la rutina a cuántas cubetas realizar la división: hist(datos, bins = 5), lo que produce cinco barras.

>>> datos = [1.6, 4.6, 2.6, 3.6, 5.6, 6.6, 3.5, 2.2, 4.4, 5.2, 5.4, 7.6, 5.8, 4.4, 6.4]
>>> plt.hist(datos)
>>> plt.show()
>>> plt.close()
>>> plt.hist(datos, bins = 2)
>>> plt.show()
>>> plt.close()
>>> plt.hist(datos, bins = 4, normed = True)
>>> plt.show()
>>> plt.close()
>>> histograma = np.histogramhisto(datos)
>>> histograma
(array([1, 2, 0, 2, 2, 1, 4, 0, 2, 1]), array([1.6, 2.2, 2.8, 3.4, 4. , 4.6, 5.2, 5.8, 6.4, 7. , 7.6]))
>>> densidad = histograma[0] / len(datos)
>>> densidad
array([0.06666667, 0.13333333, 0.        , 0.13333333, 0.13333333,
       0.06666667, 0.26666667, 0.        , 0.13333333, 0.06666667])
>>> sum(densidad)
1.0

Instalación de paquetes adicionales

Se utiliza la herramienta pip3 en la línea de instrucciones del sistema operativo (no dentro del mismo Python) para incorporar una nueva funcionalidad a Python. Ya viene incluido en la instalación básica. Abriendo la línea de instrucciones, si Python no está en la variable ambiental PATH, primero se navega a la carpeta de instalación y de ahí se ejecuta.

Actualizado el 16 de noviembre del 2022.
https://satuelisa.github.io/simulation/tutorial.html