Está en la página 1de 178

Curso de Computación Científica

Publicación 2016-09-15

Jorge A Pérez Prieto Teodoro Roca Cortés


César Esteban López

septiembre 16, 2016


Índice general

1. La Computación Científica y sus herramientas 3


1.1. ¿En qué consiste? Modelos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2. ¿Cómo procedemos? Algoritmos . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3. Aproximaciones en Computación Científica . . . . . . . . . . . . . . . . . . . . . 5
1.4. Error Absoluto y Error Relativo. Precisión y Exactitud . . . . . . . . . . . . . . . 6
1.5. Aritmética computacional . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.6. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8

2. Introducción a la programación con Python 9


2.1. Empezando con Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.2. Tipos básicos de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.3. Operadores aritméticos y lógicos . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.4. Cadenas de texto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.5. Impresión de texto y de números . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.6. Estructuras de datos: listas, tuplas y diccionarios . . . . . . . . . . . . . . . . . . 19
2.7. Módulos y paquetes de Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.8. Información y ayuda sobre funciones y módulos . . . . . . . . . . . . . . . . . . 25
2.9. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26

3. Análisis de errores 29
3.1. Tipos de errores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.2. Propagación de errores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
3.3. Sensibilidad y Acondicionamiento de un problema . . . . . . . . . . . . . . . . . 32
3.4. Estabilidad y Exactitud de un algoritmo . . . . . . . . . . . . . . . . . . . . . . . 32
3.5. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33

4. Programas ejecutables 35
4.1. Reutilizando el código . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.2. Definiendo funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
4.3. Entrada de datos por pantalla . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
4.4. Definiendo un módulo personal . . . . . . . . . . . . . . . . . . . . . . . . . . . 42

I
4.5. Estructura de un programa o script y normas de escritura . . . . . . . . . . . . . . 43
4.6. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45

5. Control de flujo 47
5.1. El bucle for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
5.2. El bucle while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
5.3. Sentencias condicionadas if-else . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
5.4. Declaraciones break y continue y sentencia else en bucles . . . . . . . . . . . . . 56
5.5. Atrapando los errores. Sentencia try-except . . . . . . . . . . . . . . . . . . . . . 57
5.6. Una aplicación interesante: Raíces de ecuaciones algebraicas cualquiera . . . . . . 59
5.7. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59

6. Probabilidad y números aleatorios 63


6.1. Algunas distribuciones de probabilidad: la Distibución Binomial . . . . . . . . . . 65
6.2. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68

7. Cálculo numérico con Numpy 69


7.1. Listas y arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
7.2. Creando arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
7.3. Indexado de arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
7.4. Algunas propiedades de los arrays . . . . . . . . . . . . . . . . . . . . . . . . . . 72
7.5. Operaciones con arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
7.6. Arrays multidimensionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
7.7. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78

8. Análisis estadístico de datos experimentales 79


8.1. Estadística y parámetros estadísticos . . . . . . . . . . . . . . . . . . . . . . . . . 79
8.2. La distribución de datos subyacente . . . . . . . . . . . . . . . . . . . . . . . . . 79
8.3. Sesgo y robustez . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
8.4. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83

9. Lectura y escritura de ficheros 85


9.1. Creando un fichero sencillo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
9.2. Lectura de ficheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
9.3. Lectura y escritura de ficheros de datos con numpy . . . . . . . . . . . . . . . . . 88
9.4. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89

10. Representación gráfica de funciones y datos 91


10.1. Trabajando con texto dentro del gráfico . . . . . . . . . . . . . . . . . . . . . . . 97
10.2. Representación gráfica de funciones . . . . . . . . . . . . . . . . . . . . . . . . . 99
10.3. Histogramas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
10.4. Figuras diferentes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
10.5. Varios gráficos en una misma figura . . . . . . . . . . . . . . . . . . . . . . . . . 103
10.6. Representando datos experimentales . . . . . . . . . . . . . . . . . . . . . . . . . 104
10.7. Datos experimentales con barras de error . . . . . . . . . . . . . . . . . . . . . . 105
10.8. Representación de datos bidimensionales . . . . . . . . . . . . . . . . . . . . . . 107

II
10.9. Guardando las figuras creadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
10.10. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110

11. Ajuste de datos experimentales: el método de mínimos cuadrados 113


11.1. Formulación general . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
11.2. Aplicación al ajuste de funciones a datos experimentales . . . . . . . . . . . . . . 114
11.3. Ajuste a polinomios con Python . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
11.4. Ajuste de funciones no lineales . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
11.5. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122

12. Otras aplicaciones de Cálculo Numérico 125


12.1. La integración o cuadratura numérica . . . . . . . . . . . . . . . . . . . . . . . . 126
12.2. Álgebra matricial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
12.3. Operaciones básicas con matrices . . . . . . . . . . . . . . . . . . . . . . . . . . 133
12.4. Resolución de sistemas de ecuaciones lineales . . . . . . . . . . . . . . . . . . . 134
12.5. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135

13. Apéndice A: Recursos informáticos para el curso 137


13.1. El sistema operativo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
13.2. El lenguaje de programación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
13.3. Python y módulos científicos para Python . . . . . . . . . . . . . . . . . . . . . . 138
13.4. Editores de texto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
13.5. Más documentación y bibliografía . . . . . . . . . . . . . . . . . . . . . . . . . . 140

14. Apéndice B: El sistema operativo GNU/Linux 143


14.1. Empezando con Linux: el escritorio de trabajo . . . . . . . . . . . . . . . . . . . 143
14.2. Trabajando con la consola de Linux. Directorios y Ficheros. . . . . . . . . . . . . 144
14.3. Copiando, moviendo y renombrando ficheros . . . . . . . . . . . . . . . . . . . . 147
14.4. Caracteres comodín . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
14.5. Trabajando con ficheros de texto . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
14.6. El sistema de usuarios y permisos de Linux . . . . . . . . . . . . . . . . . . . . . 149
14.7. Empaquetando y comprimiendo ficheros . . . . . . . . . . . . . . . . . . . . . . 151
14.8. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152

15. Apéndice C: La distribución Gaussiana 155


15.1. Propiedades, media y varianza . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
15.2. Población, Muestra y Error estándar de la media . . . . . . . . . . . . . . . . . . 156
15.3. La media pesada y su error estándar . . . . . . . . . . . . . . . . . . . . . . . . . 156
15.4. Consistencia interna y externa de un conjunto de medidas . . . . . . . . . . . . . 157
15.5. Bibliografía . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
15.6. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158

16. Apéndice D: Cálculo Simbólico 161


16.1. Introducción a Sympy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
16.2. Operaciones algebraicas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
16.3. Cálculo de límites . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164

III
16.4. Cálculo de derivadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
16.5. Expansión de series . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
16.6. Integración . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
16.7. Ecuaciones algebraicas y álgebra lineal . . . . . . . . . . . . . . . . . . . . . . . 166
16.8. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169

17. Historial de cambios 171


17.1. Revisión 3.2 - Septiembre 2016 . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
17.2. Revisión 3.1 - Septiembre 2015 . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
17.3. Versión 3.0 - Septiembre 2014 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
17.4. Versión 2.5 - Noviembre 2013 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172

IV
Curso de Computación Científica, Publicación 2016-09-15

Curso de Computación del 1º del Grado de Física. Universidad de La Laguna.


Edición 3.2
Fecha septiembre 15, 2016
Documentos PDF - ePUB (experimental)
Contenido

Índice general 1
Curso de Computación Científica, Publicación 2016-09-15

2 Índice general
CAPÍTULO 1

La Computación Científica y sus herramientas

1.1 ¿En qué consiste? Modelos

En pocas palabras, consiste en el diseño y análisis de algoritmos para resolver problemas matemá-
ticos en muchos campos, especialmente en Ciencia e Ingeniería. Los algoritmos son la traducción
y síntesis, en operaciones algebraicas sencillas, de la solución a problemas de cálculo de diversa
complejidad.
La Física como Ciencia Experimental y Observacional. El objetivo de la física es entender los
procesos de la naturaleza. Esta comprensión se alcanza a partir de una modelización matemática
de las observaciones y/o medidas experimentales. El físico reúne información sobre una serie de
magnitudes relevantes en un fenómeno natural, bajo la forma de una o varias medidas u observa-
ciones, expresadas como cantidades de una cierta unidad, acompañadas de sus correspondientes
incertidumbres e intenta producir un modelo que reproduzca el fenómeno natural.
El modelo matemático. Es un símil matemático que pretende reflejar cierta parte del universo y/o
su evolución en el tiempo. Para ser válido, o mejor para ser útil, debe tener dos valores funda-
mentales: el de la representación y el de la predicción. El físico debe ser capaz de condensar la
información obtenida a partir de numerosas observaciones de algún evento en el universo (datos)
en una forma reducida (modelo) y ser capaz de evaluar, a partir de él, los posibles resultados futuros
de otras observaciones a la vez que entender y explicar resultados obtenidos con anterioridad. La
mayor parte de los modelos implican funciones continuas: longitud, tiempo, temperatura, presión,
corriente eléctrica, energía, etc... que no pueden evaluarse matemáticamente de forma exacta.
Los cálculos en los modelos NO pueden resolverse exactamente. Por lo tanto debemos buscar:
1. Un resultado aproximadamente correcto o suficientemente cercano al valor correcto, es de-
cir, encontrar algoritmos que converjan rápidamente hacia la solución verdadera (en el lími-
te).
2. La forma de evaluar la aproximación de los resultados es decir, estimar la exactitud y/o
precisión de las soluciones aproximadas.
La simulación numérica o computacional. La simulación numérica es la representación y emu-

3
Curso de Computación Científica, Publicación 2016-09-15

lación de un sistema o proceso físico utilizando un computador. A los productos obtenidos en la


simulación numérica se les ha venido llamando prototipos virtuales.

1.2 ¿Cómo procedemos? Algoritmos

1.2.1 El proceso de resolver problemas en simulaciones y/o cálculos


numéricos

Generalmente el procedimiento que se sigue para la resolución de problemas incluye los siguientes
pasos:
1. Desarrollar un modelo matemático del problema.
2. Desarrollar un algoritmo para resolver las ecuaciones de dicho modelo.
3. Implementar el algoritmo en un lenguaje de computación creando un programa o script.
4. Hacer funcionar el programa en un computador.
5. Representar los resultados de la computación.
6. Interpretar y evaluar los resultados y sus errores.
En este curso de introducción a la Computación Científica vamos a aprender a desarrollar estos
puntos e insistir especialmente en el 3, 4, 5 y 6.
Desarrollar el primer paso consiste en realizar una modelización matemática. Un problema mate-
mático se dice que está bien planteado si:
la solución existe,
es única y,
depende de forma continua de los datos del problema; es decir, que una pequeña variación
en los datos iniciales no causa grandes cambios en el resultado.
Pero, no siempre es posible obtener problemas bien planteados y entonces se habla de problemas
pobremente (o mal) planteados (” ill-conditioned problems”). Aún en problemas bien planteados
pueden aparecer problemas de perturbaciones en la computación que deben evitarse con algoritmos
estables.

1.2.2 Estrategia general para resolver un problema de computación

Veremos a lo largo del curso que una estrategia básica en la solución de cualquier problema en
Física, en general en Ciencia o en Ingeniería, que requiera de computación es:
Reemplazar un problema difícil o complejo por otro más fácil o simple que tenga la misma solución
o, al menos, una solución ‘muy cercana’ a la del problema original.

4 Capítulo 1. La Computación Científica y sus herramientas


Curso de Computación Científica, Publicación 2016-09-15

En general, lo podemos conseguir, por ejemplo, reemplazando según sea el problema que se tenga
planteado:
Funciones complicadas por otras más simples (polinomios, series de Taylor, etc...).
Matrices complejas en general, por otras más simples (diagonales, trangulares, etc...).
Problemas no lineales por otros lineales aunque sean más largos y tediosos.
Ecuaciones diferenciales por sistemas de muchas ecuaciones algebraicas (de diferencias fi-
nitas).
Sistemas de orden alto por otros de orden bajo.
Procesos infinitos por otros finitos (integrales por sumatorios, derivadas por diferencias fini-
tas, etc...).
Por lo tanto, normalmente debemos: Encontrar un problema alternativo al problema planteado, o
una serie de ellos, que sea más sencillo de solucionar, y que la transformación de uno en otro sea
tal que preserve la solución del problema original, en algún sentido.
Idealmente, la solución al problema alternativo aproximado no siempre coincide con la del ori-
ginal, pero generalmente podremos aproximarla tanto como queramos pagando el precio de más
almacenamiento de datos y/o más trabajo de cálculo. Por lo tanto, debemos estar siempre atentos a
la hora de estimar la exactitud y precisión de la solución aproximada y establecer la convergencia
hacia la solución verdadera del problema original en el límite apropiado.

1.3 Aproximaciones en Computación Científica

Hay muchas fuentes de aproximación que se suelen utilizar en la computación en diferentes fases
del desarrollo de la solución al problema planteado. Por ejemplo, algunos de ellos son:
1. ANTES de empezar el cálculo o computación,
Construyendo el MODELO. Si el problema es muy complejo, siempre podemos olvi-
dar o despreciar algunos detalles (obviamente, de poca importancia) de la física del
problema (fricción, viscosidad, aislamiento, etc...)
Tomando los DATOS. Siempre podemos realizar más y/o mejores MEDIDAS u OB-
SERVACIONES que eviten o minimicen ruido o sesgos, o también completar una
muestra insuficiente
Preparando COMPUTACIONES PREVIAS. Tratar de mejorar la precisión y exacti-
tud en los cálculos previos necesarios para los datos de entrada y que solamente son
aproximadas.
2. DURANTE el proceso de computación, en los procesos de
TRUNCAMIENTO, usando un número finito de términos en una serie en vez de toda
ella,

1.3. Aproximaciones en Computación Científica 5


Curso de Computación Científica, Publicación 2016-09-15

DISCRETIZACIÓN, por ejemplo usando diferencias finitas en los valores de las varia-
bles, en vez de derivadas,
REDONDEO, cuando calculamos (a mano, o usando la calculadora o el computador)
la representación de los números reales y las operaciones aritméticas están limitadas
intrínsecamente por la precisión finita de ellos.

1.4 Error Absoluto y Error Relativo. Precisión y Exactitud

La expresión de un resultado científico siempre debe hacerse utilizando solamente sus cifras sig-
nificativas y/o indicando el error en el resultado. Para ello debemos conocer o estimar el posible
error en tal resultado.
El sentido o significado de un error está relacionado con la magnitud de la cantidad que se mide o
se computa. De esta forma se define:
Error absoluto = VALOR APROXIMADO - VALOR VERDADERO.
Error relativo = ERROR ABSOLUTO / VALOR VERDADERO.
A veces se define como error absoluto el valor absoluto de la definición anterior. Normalmente
no conocemos el valor verdadero, de forma que simplemente acotaremos o estimaremos el error
en vez de computarlo exactamente. Como consecuencia, el error relativo se toma generalmente
respecto al valor aproximado si no conocemos el verdadero. El error relativo multiplicado por 100
nos da el error porcentual (en tanto por ciento) en el resultado.
Una interpretación interesante del error relativo en una medida o cálculo es el siguiente: si un
valor aproximado tiene un error relativo de 1.0 × 10−𝑛 , entonces su representación decimal tiene
𝑛 dígitos.
Los dígitos o cifras significativas del valor numérico de una magnitud (o medida) son aquellos
dígitos que tienen significado, la cantidad de cifras significativas de dicha magnitud se define del
siguiente modo:
1. El primer dígito diferente de cero por la izquierda es el más significativo.
2. Si no hay punto decimal, el dígito más a la derecha distinto de cero es el menos significativo.
3. Si hay un punto decimal, el dígito más a la derecha es el menos significativo aunque sea un
0.
4. Todos los dígitos entre el más y el menos significativo son contados como cifras significati-
vas.
Por ejemplo: 780 y 0.0078 tienen el mismo número de cifras significativas (dos); en cambio 780.0
y 0.7800 tienen cuatro. Otro ejemplo: 430, 430., 430.0 tienen diferentes cifras significativas (dos,
tres y cuatro respectivamente) ya que fueron obtenidas con precisión de una decena, una unidad y
una décima, respectivamente.

6 Capítulo 1. La Computación Científica y sus herramientas


Curso de Computación Científica, Publicación 2016-09-15

Usar la notación científica tiene la ventaja que usa siempre todas las cifras significativas de un
número, y sólo ellas, con un argumento en notación decimal y acompañado con la potencia de 10
apropiada para representarlo. Por ejemplo, 320 lo escribiríamos 3.2 × 102 . Otro ejemplo: 86420 y
0.00008642 (ambos con 4 cifras significativas) los escribimos como 8.642×104 y 8.642×10−5 . En
python y, en general cualquier lenguaje de ordenador, estos numeros se escribirán así: 8.642e04 y
8.642e-05 .
En el caso de resultados experimentales productos de la observación, el número de cifras significa-
tivas que se escriben suele ser una más que la que da la precisión experimental para evitar errores
de redondeo en sucesivos cálculos. Por ejemplo, si medimos una longitud como 1.348 m con una
incertidumbre de 0.011 m, el resultado podríamos expresarlo como: 1.348 ± 0.011; no obstante, si
la incertidumbre es 0.073 (el dígito más significativo es mayor que 5) podríamos expresarlo como:
1.35 ± 0.07. Fijénse que hemos redondeado el dígito menos significativo.
Esto nos lleva a los conceptos de precisión y exactitud. La diferencia entre precisión o exactitud
en una medida o cálculo la podemos establecer aquí como:
PRECISIÓN. Se refiere al número de dígitos o cifras significativas con el que se expresa una
medida. Es una valoración de lo ‘cuidadoso’ que ha sido el cálculo o la medida. Cuantos más
sean mayor es la precisión.
EXACTITUD. Se relaciona mejor con un error absoluto pequeño; cuanto más pequeño más
exacto. Es una valoración de cuán cercano al valor real o verdadero es una medida o cálculo.
Por ejemplo: tengamos el número 3.141345654320976810345 con todas las cifras significativas
como resultado de una medida; se trata de un número muy preciso (22 cifras significativas) pero
muy poco exacto como valor de 𝜋 (su error absoluto es de 0.0003). En cambio, 3.1416 (5 cifras
significativas) es un valor menos preciso pero más aproximado (exacto) a 𝜋 (número irracional que
vale 3.14159265...) que el anterior.

1.5 Aritmética computacional

En cualquier ordenador o calculadora los números se almacenan en un número finito de bits. Los
números enteros no tienen problema y se almacenan exactamente, si disponemos del número su-
ficiente de bits para ello. Sin embargo, en el caso de los números reales esto no ocurre así ya que
muchos de ellos no tienen representación exacta y se procede al redondeo para poder representar-
los; de hecho, se habla de números en coma flotante (floating point). Su representación se toma de
la notación científica y consta de: signo, mantisa y exponente; por ejemplo: −3.9267 × 10−8 , el
signo es negativo, la mantisa es 3.9267 y el exponente es -8. La notación habitual que nos ofrecen
los ordenadores es -3.9267e-8 o también -3.9267E-8.
El almacenamiento de estos números en un ordenador depende de las características de éste y, a
veces, del lenguaje de programación que utilicemos. Python sigue la norma “IEEE standard 754”
que consiste en representar los números en coma flotante en una notación binaria normalizada
(tiene una base 2 y una mantisa menor que 2). Utiliza para ello 64 bits de los cuales reserva uno
para el signo, 52 para la mantisa y 11 para el exponente. Esto determina el intervalo de números

1.5. Aritmética computacional 7


Curso de Computación Científica, Publicación 2016-09-15

que puede almacenar; alrededor de 2.2 × 10−308 y 1.7 × 10308 , es decir, lo más aproximado a 0 e
infinito que tenemos.
Todo ello hace que tengamos errores, normalmente pequeños, al hacer cálculos con números en
coma flotante. Algunas de las características peculiares que todo esto conlleva son las siguientes:
En la pantalla, Python nos ofrece sólo los primeros 17 decimales, el último redondeado.
El redondeo consiste en buscar el número en coma flotante más próximo al que queremos
representar.
No todos los números reales pueden representarse así; por ejemplo, prueben con 0.1. Se
redondea el último.
La precisión depende del propio número. Cuanto más próximos a 0 sean los números me-
jor precisión podremos obtener en las operaciones con ellos; para números grandes vamos
perdiendo precisión; pueden comprobarlo probando a sumar a 1e8 la cantidad de 1e-9.
Volviendo a los números enteros, normalmente en Python se almacenan en 32 bits (el mayor sería
231 ) sin redondeos. No obstante, Python utiliza también los llamados enteros largos que utilizan
el número de bits necesario para almacenar el entero de que se trate. Es muy rápido operar con
enteros pero si el resultado de una operación es un entero que no le cabe en 32 bits, Python se
encarga de alargarlo convenientemente, con el consiguiente mayor gasto en memoria y en rapidez
de cálculo; esto no sucede así en otros lenguajes de programación pero es muy cómodo no tener
que preocuparse de estos problemas.

Nota: Les será muy útil que repasen el Sistema Internacional de Unidades, que pueden encontrar
en cualquiera de estas direcciones en la red, entre otras:
http://es.wikipedia.org/wiki/Sistema_Internacional_de_Unidades
http://en.wikipedia.org/wiki/International_System_of_Units
http://physics.nist.gov/cuu/Units/

En la siguiente dirección podemos encontrar además tablas con múltiplos y submúltiplos decimales
(potencias de 10): http://recursos.citcea.upc.edu/unidades/sicas.html

1.6 Ejercicios

1. Calcular los errores absolutos y relativos si se aproxima el valor de 𝜋 por las siguientes
cantidades: 3 ; 3.14 ; 22/7 .
2. Si a, con error relativo R, es el valor aproximado para una cantidad cuyo valor verdadero es
v, probar a partir de las definiciones que a = v (1 + R).
3. Tenemos que 𝑥 = 0.012, con dos cifras significativas. Si 𝑦 = exp(𝑥), encontrar con cuántas
cifras significativas podemos evaluar 𝑦.

8 Capítulo 1. La Computación Científica y sus herramientas


CAPÍTULO 2

Introducción a la programación con Python

Para crear un algoritmo para resolver un problema matemático, necesitamos conocer un lenguaje de
programación e implementarlo en un ordenador. Un lenguaje de programación, como los lenguajes
naturales, son una serie de órdenes que nos permiten “hablar” con un ordenador pero a diferencia
de los naturales, los lenguajes de programación son estrictos y no permiten ambigüedades. Existen
cientos de lenguajes de programación, cada uno con sus peculiaridades, pero solo con unos pocos
se han programado la mayoría de los programas que usamos diario como navegadores, editores de
texto, aplicaciones en smartphones o muchas páginas web como Wikipedia o Facebook. Entre los
lenguajes más usados están C/C++, Java o Python, aunque en ciencia también son muy populares
FORTRAN y Matlab, entre otros.
En cualquier caso, el objetivo de un lenguaje de programación es ofrecer un idioma relativamente
sencillo con el que dar órdenes a un ordenador; éste, no comprende directamente ningún lenguaje
de programación, únicamente el llamado “lenguaje de máquina”, que es propio de cada tipo de
ordenador y por tanto el programa, hecho en cualquier lenguaje, debe traducirse a este lenguaje de
máquina.
Según cómo se convierte un código o programa a lenguaje de máquina, los lenguajes se clasifican
en dos tipos, los compilados, como C/C++ o FORTRAN, en los cuales mediante una orden se
traduce (compila) completamente el programa una única vez generándose un programa ejecutable
que se puede usar cuantas veces queramos. Por otro lado están los lenguajes interpretados como
Python, Perl o Ruby, que se van traduciendo y ejecutando línea a línea. Por regla general los
programas compilados son más rápidos, pero los interpretados tienen la ventaja de que son mucho
más interactivos y flexibles y es mucho más facil y rápido programar y corregir errores con ellos y
por eso se usan mucho en el análisis de datos.
Python es lo que se denomina un lenguaje interpretado (no compilado), que puede utilizarse como
un programa ejecutable desde una terminal de comandos o también, de manera interactiva mediante
una consola de comandos, donde se dan instrucciones línea a línea. Python incorpora una consola
por defecto, pero también existen otras alternativas con características muy útiles para el análisis
científico de datos. Un ejemplo es ipython 1 que se trata de una consola de Python mejorada,
incluyendo completado de funciones y variables, funcionalidad de los comandos básicos de la
1
Mucha más información en la web oficial: http://ipython.org/

9
Curso de Computación Científica, Publicación 2016-09-15

consola del sistema (cd, ls, pwd, etc.), comandos adicionales (llamados comandos mágicos) y
un largo etcétera. Esta es la consola de Python que usaremos preferentemente durante este curso,
aunque se puede trabajar sin problema con la consola por defecto.

2.1 Empezando con Python

Se puede iniciar Python de manera interactiva desde una terminal de comandos estándar de Linux
o Mac sin más que escribir python. Los usuarios de Windows deben lanzar la consola de Python
(o IPython) desde el menú de programas y se abrirá una ventana nueva. Aparecerá el prompt >>>
(en la consola estándar de Python) y entonces estará listo para empezar. Este símbolo de prompt,
que puede ser distinto en otras consolas Python, indica que el sistema está preparado para recibir
instrucciones del usuario. Veamos un comando sencillo:

>>> print('Hola, esto es Python')


Hola, esto es Python

>>>

Aquí hemos usado la función print() para imprimir una frase en la pantalla. Nota que la frase
está entre comillas, que pueden ser dobles (”) o simples (‘). El resultado se muestra inmediatamente
por pantalla y vuelve a aparecer de nuevo el prompt esperando por nuevos comandos. En el caso de
querer iniciar la consola ipython hay que escribir ipython y tendremos un prompt algo diferente:

In [1]: print('Esto es Python, con la consola ipython')


Esto es Python, con la consola ipython

In [2]:

En este caso cada línea de entrada y salida está numerada y además tenemos otras funcionalidades
muy útiles para el trabajo interactivo que iremos conociendo.

Nota: Cuando iniciamos una sesión de trabajo con ipython, suele ser muy útil guardar todo lo
que vamos haciendo para repasar o estudiar lo que se hizo. Para ello se empieza la sesión con el
comando mágico de ipython 2 %logstart con el objeto de grabarla en un fichero, por ejemplo:

In [1]: %logstart -o clase_25_septiembre_2015.txt

donde el parámetro opcional -o guarda también la salida (respuesta) de python en el fichero


clase_25_septiembre_2015.txt. De esta manera tendremos en un fichero todo lo que
hicimos durante la sesión. Con los comandos %logoff y %logon podemos, respectivamen-
2
Los comandos y funciones mágicos de ipython son una serie de utilidades de esta consola y no de Python en sí.
Para ver más información y la lista completa de comandos mágicos disponibles, escribe %magic en la consola de
ipython. Para salir de %magic teclea q.

10 Capítulo 2. Introducción a la programación con Python


Curso de Computación Científica, Publicación 2016-09-15

te, detener temporalmente y continuar el registro de sesión que hayamos iniciado previamente
con %logstart.
Si queremos guardar todo lo que hemos hecho en un fichero ejecutable o continuar a partir de dónde
lo dejamos, tendríamos que guardarlo con la extensión .py en vez de .txt. Si posteriormente,
queremos recuperar las sesiones hechas y guardadas en un fichero de registro, podemos hacerlo
con el comando run dentro de ipython:
In [1]: run clase_25_septiembre_2015.py

al hacerlo, se ejecuta todo el código de entrada que está en el fichero, repitiendo una a una todas
las operaciones.

2.2 Tipos básicos de datos

En cualquier lenguaje de programación existen distintos tipos de datos, que podemos almacenar y
operar de forma diferente con ellos y que poseen distintas propiedades. Los más comunes son las
llamadas cadenas de texto o string (indicadas siempre entre comillas) y los números ( int o float,
enteros o de “coma flotante” respectivamente):
>>> print("Esta es una linea de texto")
Esta es una linea de texto
>>> print(28) # este es un entero
28

Las variables son componentes fundamentales de un lenguaje de programación y no son más que
un nombre que se refiere a un registro en el computador (una serie de bits de memoria) que contiene
uno o varios datos, que pueden ser de distinto tipo. Veamos unos ejemplos:
>>> frase = "Esta es una linea de texto"
>>> num = 22
>>> num*2
44
>>> frase*2
Esta es una linea de textoEsta es una linea de texto

Aquí, frase es una variable tipo string mientras que num es otra variable numérica entera. Nótese
que mientras se multiplicó num por 2 de forma conocida, la cadena de texto frase fué duplicada
al multiplicarla por 2. En este caso hemos operado con dos tipos de datos distintos, un string y
un int, algo que muchos lenguajes de programación produce un error por no tener sentido, sin
embargo, Python interpreta el producto de un string y un int como la unión o concatenación de la
misma cadena de texto varias veces, y es por eso que vemos frase duplicada.
A lo largo del trabajo podemos acabar definiendo muchas variables de distinto tipo. Con el coman-
do type() podemos saber el tipo de dato que se asigna a una variable si en cualquier momento

2.2. Tipos básicos de datos 11


Curso de Computación Científica, Publicación 2016-09-15

no recordamos como la definimos:

>>> type(frase)
<type 'str'>
>>> type(num)
<type 'int'>

Los nombres de las variables pueden ser cualquier combinación de letras y números (siempre
que no empiece por número) pero no están permitidos carácteres especiales como tildes, puntos,
espacios en blanco, etc. Algunas palabras están además reservadas ya por el propio Python para su
uso como: def, class, return, etc. por lo que tampoco las podremos usar como nombres de
variables.
Algo muy importante es que, como casi todos los lenguajes de programación, hay una distinción
clara y de funcionalidad entre enteros y decimales, es decir, entre int y float; aunque ambos sean
valores numéricos, tienen propiedades distintas que afectan al cálculo. Así, la variable num la
hemos definido de tipo entero estricto y el cálculo entre números enteros siempre da como
resultado otro número entero, redondeándose al más cercano en caso de no ser entero exacto,
por ejemplo:

>>> 113/27
4 # en lugar de 4.1851851851851851

Si queremos usar números decimales, también llamados de coma flotante, debe emplearse directa-
mente un valor decimal (float); en estos casos:

>>> 113.0/27.0
4.1851851851851851

>>> type(113.0/27.0)
<type 'float'>

Es decir, numero = 3 no es lo mismo que numero = 3.0 aunque ambos tengan el mismo
valor. Como dijimos en el tema anterior, Python emplea números de 64 bits (por defecto) en los
float. En cualquier operación es muy importante usar los enteros y float correctamente y tener
cuidado al mezclarlos, de otro modo se obtendrá un resultado no deseado llegando a hacernos
creer que el computador se ha equivocado.
Algunos tipos de datos pueden ser convertidos de unos a otros, empleando str() para convertir
a cadena de texto, int() a entero y float() a coma flotante:

>>> float(3)
3.0

>>> int(3.1416)
3

12 Capítulo 2. Introducción a la programación con Python


Curso de Computación Científica, Publicación 2016-09-15

>>> str(34)
'34'

Para el caso de los float, se pueden redondear con round(), que redondea al entero más próxi-
mo. La funciones ceil() y floor() del módulo math redondean hacia arriba y hacia abajo
respectivamente:

>>> print(round(4.4)) , (round(4.5))


4.0 5.0
>>> # Importamos todas las funciones matemáticas del módulo math
>>> from math import *
>>> print(ceil(4.4)) , (ceil(4.5))
5.0 5.0

>>> print(floor(4.4)) , (floor(4.5))


4.0 4.0

Más adelante veremos qué son los módulos, que ofrecen otras funciones predefinidas muy intere-
santes y aprenderemos cómo importarlos y a utilizarlos.

Nota: La versión de Python que empleamos siempre en este curso es la 2.7, que la más usada
actualmente, aunque la versión más moderna es la serie 3.0, en concreto la versión Python 3.5.
Entonces ¿por qué no usamos Python 3.0? El motivo es porque hay varias diferencias importantes
entre la series Python 2 y Python 3 y aunque el lenguaje es el mismo, hay aún muchos paquetes
que no están completamente soportados en Python 3, por lo que por ahora Python 2.7 es aún con
diferencia la versión de Python más usada, aunque Python 3 lleva existiendo muchos años. Desde
luego, Python 3 es una versión más moderna y mejor, por lo que con el tiempo poco a poco se va
pasando a Python 3. Una peculiaridad de Python 3 es su forma de operar entre enteros. A diferencia
de muchos lenguajes de programación, incluyendo Python 2, la división entre entero devuelve un
float:

Python 3.3.2 (default, Sep 9 2013, 12:40:26)


[GCC 4.7.2 20120921 (Red Hat 4.7.2-2)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 1 / 2
0.5
>>> type(1*2)
<class 'int'>
>>> type(1/2)
<class 'float'>
>>>

2.2. Tipos básicos de datos 13


Curso de Computación Científica, Publicación 2016-09-15

2.3 Operadores aritméticos y lógicos

Con python se pueden hacer las operaciones aritméticas habituales usando los símbolos corres-
pondientes:
Operación Símbolo
Suma +
Resta −
Multiplicación *
División /
Exponenciación **
Residuo o resto %
La prioridad en la ejecución (de mayor a menor, separados por ;) es la siguiente: **; *, /, %; +, - .:

>>> x=3.5
>>> x**2
12.25
>>> x+x**2
15.75
>>> x**(2./3.)
2.3052181460292234
>>> x**(2/3)
1.0
>>> 123%8
3
>>> x+x/2+x*x-x**1.25-x%2
11.212761600464169

Los operadores lógicos son aquellos operadores que permiten comparar valores entre sí. En con-
creto se usan:
Operacion Simbolo
Igual a (comparación) ==
Distinto de (comparación) != o <>
Mayor que, Menor que >, <
Mayor o igual, Menor o igual >=, =<
y, o and, or
cierto, falso True, False
Como resultado de una operación lógica, obtenemos como respuesta un elemento, True o False,
según se verifique o no la operación. Estos elemento lógicos los podemos usar a su vez para otras
operaciones. Veamos algunos ejemplos:

>>> resultado = 8 > 5


>>> print resultado
True

14 Capítulo 2. Introducción a la programación con Python


Curso de Computación Científica, Publicación 2016-09-15

>>> resultado = (4 > 8) or (3 > 2)


>>> print resultado
True
>>> resultado = True and False
>>> print resultado
False
>>> resultado = (4 > 8) and (3 > 2)
>>> print resultado
False

Ahora que conocemos los operadores lógicos, podemos consultar si una variable es de un tipo
concreto:

>>> numero = 10.0


>>> type(numero) == int
>>> False
>>> type(numero) == float
>>> True

En este caso ìnt y float no son cadenas de texto, sino un indicador del tipo de dato. Esto
se puede usar para cualquier tipo de dato más complejos, no únicamente números o cadenas, los
cuales veremos más adelante.

2.4 Cadenas de texto

Las cadenas de texto, como hemos visto, no son mas que texto formado por letras y números de
cualquier longitud y son fácilmente manipulables. Para poder hacerlo, cada carácter de una cadena
de texto tiene asociado un índice que indica su posición en la cadena, siendo 0 el de la izquierda
del todo (primero), 1 el siguiente hacia la derecha y así sucesivamente hasta el último. Veamos
ejemplos:

>>> # Variable "frase" que contiene una cadena de texto


>>> frase = "Si he logrado ver más lejos, ha sido porque he subido
˓→a

hombros de gigantes"
>>> print frase[0] # Primera letra de la cadena
S

>>> print frase[10] # Decimoprimera letra, con índice 10


a
>>> len(frase) # Longitud de la cadena
76

>>> print frase[18:29] # Seccion de la cadena

2.4. Cadenas de texto 15


Curso de Computación Científica, Publicación 2016-09-15

más lejos,

>>> print frase[68:] # Desde el indice 68 hasta el final


gigantes

>>> print frase[:10] # Desde el principio al caracter de


Si he logr # indice 10, sin incluirlo

También se pueden referir con índices contando desde la derecha, usando índices negativos, siendo
-1 el primero por la derecha:

>>> print(frase[-1]) # El último carácter, contando desde la derecha


s

El comando len() nos porporciona el número de caracteres (longitud) de la cadena de texto:

>>> len(frase) # 76 es el número de caracteres de la cadena


76
>>> print(frase[len(frase)-1]) # El último carácter, contando desde la
˓→izquierda

s
>>> print( frase[-1] == frase[len(frase)-1] ) # Compruebo si son
˓→iguales

True

Desde luego es irrelevante qué sistema de índices se use, ya que sólo son dos formas distintas de
referirse a un mismo caracter, pero lo más habitual es usar índices positivos de izquierda a derecha.

Figura 2.1: Ejemplo de indexado de cadenas de texto con Python. Nótese que el último índice del
rango no está incluído en la selección.

Los caracteres en Python son de tipo ascii (7 bits), si queremos que no haya problemas con los
acentos o con la letra ñ tienen que ser definidos como de tipo unicode de 8 bits. Para no te-
ner problemas con estos símbolos en la variable frase, la podemos definir como un string de
caracteres unicode.

16 Capítulo 2. Introducción a la programación con Python


Curso de Computación Científica, Publicación 2016-09-15

>>> frase = u"Si he logrado ver más lejos, ha sido porque he subido a
hombros de gigantes"

Si ahora comprobamos de qué tipo de variable se trata:

>>> type(frase)
<type 'unicode'>

Existen varios métodos o funciones específicas para tratar y manipular cadenas de texto, éstos nos
permiten cambiar de varias maneras las cadenas. Veamos algunos:

>>> frase_mayusculas = frase.upper() # Cambia a mayusculas y lo


˓→guardo en

>>> print(frase_mayusculas) # la variable frase_mayusculas


SI HE LOGRADO VER MAS LEJOS, HA SIDO PORQUE HE SUBIDO A HOMBROS DE
˓→GIGANTES

Probar qué ocurriría si no hubiésemos definido la variable frase como un string de caracteres de
tipo unicode:

>>> frase_minusculas = frase.lower() # Cambia a minúculas y lo


˓→guardo en

# la variable frase_minusculas
>>> print(frase_minusculas)
si he logrado ver más lejos, ha sido porque he subido a hombros de
˓→gigantes

>>> # Reemplaza una cadena de texto por otra


>>> frase.replace("hombros", "la chepa")
u'Si he logrado ver m\xe1s lejos, ha sido porque he
subido a la chepa de gigantes'

Aparece la u delante de la frase y \xe1s en vez de á.


Este último comando devuelve una nueva cadena de texto cambiada, que vemos por pantalla, sin
cambiar la original. Podemos comprobar que la frase no se ha alterado de forma permanente ha-
ciendo un print(frase). Para cambiar la variable frase deberemos volver a definir la cadena
de texto:

>>> # Reemplaza definitivamente una cadena de texto por otra


>>> frase = frase.replace("hombros", "la chepa")
>>> print(frase)
'Si he logrado ver mas lejos, ha sido porque he
subido a la chepa de gigantes'

2.4. Cadenas de texto 17


Curso de Computación Científica, Publicación 2016-09-15

Advertencia: Recordar que el índice en las cadenas de texto y en general cualquier lista de
números, empieza siempre con el 0, por lo que el primer elemento de una cadena de texto o
de una lista es frase[0] y no frase[1]. Al escribir frase[10] estamos tomando el
elemento ordinal 11, no el 10.

2.5 Impresión de texto y de números

La cadenas de texto se pueden concatenar o unir con +:

>>> "Esta es un frase" + " y esta es otra"


'Esta es un frase y esta es otra'

Sin embargo, la concatenación sólo es posible para texto (string), por lo que no se pueden conca-
tenar letras y números. Una posibilidad es convertir los números a string:

>>> a, b = 10, 10**2 # Definimos dos numeros, a=10 y b=10**2


>>> print(str(a) + " elevado al cuadrado es " + str(b))
10 elevado al cuadrado es 100

Una manera más práctica y correcta de hacer esto es imprimiendo los números con el formato que
queramos; veamos como hacerlo:

>>> # Calculamos el logaritmo base 10 de 2**100 e imprimimos


>>> # el resultado con 50 decimales
>>> print("%.50f") % log10(2.0**100)
30.10299956639811824743446777574717998504638671875000
>>> # Otro ejemplo usando texto, enteros y decimales
>>> print("El %s de %d es %f") % ('cubo', 10, 10.**3)
El cubo de 10 es 1000.000000

Aqui se reemplaza cada símbolo %s (para cadenas de texto), %d (para enteros) o %f (para floats)
sucesivamente con los valores después de % que están entre paréntesis. En caso de los floats se
puede utilizar el formato %10.5f, que significa imprimir 10 carácteres en total, incluído el pun-
to, usando 5 decimales. Se puede escribir también floats en formato científico utilizando %e, por
ejemplo:

>>> print("%.5e" % 0.0003567)


3.56700e-04

Los formatos son muy útiles a la hora de expresar el resultado de un cálculo con los dígitos signi-
ficativos solamente o con la indicación del error en el resultado. Así por ejemplo, si el resultado de
un cálculo o de una medida es 3.14158 ± 0.00013 podemos expresarlo como:

18 Capítulo 2. Introducción a la programación con Python


Curso de Computación Científica, Publicación 2016-09-15

>>> # resultado de un cálculo obtenido con las cifras


>>> # decimales que proporciona el ordenador
>>> resultado = 3.1415785439847501
>>> # este es su error con igual número de cifras decimales
>>> error = 0.0001345610900435
>>> # así expresamos de forma correcta el resultado
>>> print("El resultado del experimento es %.5f +/- %.5f" ) %
˓→(resultado, error)

El resultado del experimento es 3.14158 +/- 0.00013

2.6 Estructuras de datos: listas, tuplas y diccionarios

Los datos se pueden almacenar en variables univaluadas como ya hemos visto. No obstante, en
variables estructuradas también pueden almacenarse uno o más datos. Los tipos de variables
estructuradas que ofrece Python son las llamadas listas, tuplas y diccionarios que se definen de la
siguiente forma:

2.6.1 Listas

Se trata de un conjunto de números, cadenas de texto u otras listas (aquí tendríamos listas de listas),
ordenadas de alguna manera:

>>> # Lista de datos string


>>> alumnos = ['Miguel', 'Maria', 'Luisma', 'Fran', 'Luisa', 'Ruyman']

>>> # Lista de enteros


>>> edades = [14, 29, 19, 12, 37, 15, 42]

>>> # lista de datos mixto(entero, string y lista)


>>> datos = [24, "Juan Carlos", [6.7, 3.6, 5.9]]

En el último ejemplo se puede comprobar que es posible mezclar varios tipos de datos, como
enteros, strings y hasta otras listas. Se puede utilizar la función len() para ver el número de
elementos de una lista:

>>> len(alumnos)
6

Es posible utilizar el método split() para separar por medio de los espacios una cadena de texto
cualquiera y colocar los elementos resultantes en una lista:

>>> Definimos la cadena de texto


>>> frase = "Dios no juega a los dados"

2.6. Estructuras de datos: listas, tuplas y diccionarios 19


Curso de Computación Científica, Publicación 2016-09-15

>>> palabras = frase.split() # separo la frase por espacios en


˓→blanco

>>> print(palabras)
['Dios', 'no', 'juega', 'a', 'los', 'dados'] # el resultado es una
˓→lista

>>> palabras2 = frase.split('no') # separo la frase por 'no'


>>> print(palabras2)
['Dios ', ' juega a los dados']

Como se ve en el ejemplo anterior, el método split() separa (por defecto, por espacios en
blanco) los elementos, palabras en este caso, de la cadena de texto y da como resultado una lista
con los elementos. Se puede usar otro separador, como la coma en este ejemplo la cadena ‘no’,
usándolo como parámetro. Nótese que en este caso ‘no’ ya no está en ningún elemento de la lista,
ya que ahora se usa de separador y por eso desaparece.
En este caso hemos llamado método a una función que se aplica a un tipo de dato en concreto, en
este caso un string, usando la variable y el método unidos por un punto, en lugar de un función
normal como es len(alumnos), por ejemplo.
Existen varias formas de añadir nuevos elementos a una lista existente, estas son la formas más
comunes:

>>> alumnos.append('Iballa') # Añade "Iballa" al final de la lista


>>> print(alumnos)
['Miguel', 'Maria', 'Luisma', 'Fran', 'Luisa', 'Ruyman', 'Iballa']

A diferencia de replace, append guarda el cambio realizado.:

>>> alumnos.insert(3, 'Jairo') # Añade "Jairo" en la posición 3


>>> print(alumnos)
['Miguel', 'Maria', 'Luisma', 'Jairo', 'Fran', 'Luisa', 'Ruyman',
˓→'Iballa']

>>> alumnos.index("Jairo")
>>> 3

En la última orden del ejemplo anterior hemos usado index() para encontrar el índice o posi-
ción del primer elemento de la lista que es “Jairo”.
Es posible ordenar alfabéticamente una lista con el método sort():

>>> alumnos.sort()
>>> print(alumnos)
['Fran', 'Iballa', 'Jairo', 'Luisa', 'Luisma', 'Maria', 'Miguel',
˓→'Ruyman']

Para extraer un elemento de la lista podemos usar los métodos pop() y remove():

20 Capítulo 2. Introducción a la programación con Python


Curso de Computación Científica, Publicación 2016-09-15

>>> alumnos.pop(2) # Extraemos el elemento número 2 y lo elimino de


˓→la lista

'Jairo'
>>> print(alumnos)
['Fran', 'Iballa', 'Luisa', 'Luisma', 'Maria', 'Miguel', 'Ruyman']

>>> alumnos.remove('Maria') # Eliminamos el elemento "Maria" (primera


˓→ocurrencia)

>>> print(alumnos)
['Fran', 'Iballa', 'Luisa', 'Luisma', 'Miguel', 'Ruyman']

Las listas se manipulan de manera similar a las cadenas de texto, utilizando índices que indican la
posición de cada elemento siendo 0 el primer elemento de la lista y -1 el último (si se empieza a
numerar por el final):

>>> alumnos[2:6]
['Luisma', 'Fran', 'Luisa', 'Ruyman']

>>> print(alumnos[0], alumnos[-1])


('Fran', 'Ruyman')

Y aquí va un truco. Si queremos invertir el orden de la lista, podemos hacerlo de esta manera:

>>> alumnos_invertida = alumnos[::-1]


>>> print alumnos_invertida
>>> ['Ruyman', 'Miguel', 'Luisma', 'Luisa', 'Iballa', 'Fran']

Una función muy útil es la función range(), que permite crear una lista de números enteros. Por
ejemplo, para crear un lista de 10 elementos, de 0 a 9, e imprimirlos podemos hacer esto:

>>> print( range(10) )


[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Con esta función se puede crear también una lista de números indicando el inicio, final y el inter-
valo entre dos consecutivos. Por ejemplo, para crear una lista con números enteros de 100 a 200 a
intervalos de 20 podemos escribir:

>>> print( range(100, 200, 20) )


[100, 120, 140, 160, 180]

Nótese que el último número, 200, no se incluye la lista. La función range() se emplea para
generar listas de números enteros solamente. Más adelante veremos cómo crear listas similares de
floats.
Con las variables listas podemos operar de forma parecida a las cadenas de texto, por ejemplo:

2.6. Estructuras de datos: listas, tuplas y diccionarios 21


Curso de Computación Científica, Publicación 2016-09-15

>>> a=range(100, 200, 20)


>>> print(a)
[100, 120, 140, 160, 180]
>>> print(a+a)
[100, 120, 140, 160, 180, 100, 120, 140, 160, 180]
>>> print(a*3)
[100, 120, 140, 160, 180, 100, 120, 140, 160, 180, 100, 120, 140, 160,
˓→180]

Como se ve en este ejemplo, se pueden unir dos listas usando + o incluso multiplicarla por un
número entero, que equivale a sumar varias veces la misma lista.
La forma de indexar las listas y los strings de Python, de manera que el segundo índice en un rango
no se incluye en la sublista resultante, puede parecer confuso, pero está bien pensado. Recordemos
esto con un ejemplo
>>> numeros = range(1, 11) # lista de números de 1 a 10 (11 no
˓→incluido)

>>> mitad = len(numeros)/2 # indice de la mitad de la lista


>>> primeros5 = numeros[:mitad] # lista con los primeros cinco
>>> ultimos5 = numeros[mitad:] # lista con los últimos cinco

>>> print primeros5


>>> [1, 2, 3, 4, 5]

>>> print ultimos5


>>> [6, 7, 8, 9, 10]

como vemos, el mismo índice (5) con que termina la primera la lista, empieza la segunda. De esta
manera, si queremos recuperar la lista original sólo tenemos que unir las dos partes:
>>> print primeros5 + ultimos5
>>> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

y así las cuentas y manipulaciones con strings y listas se hacen más fácilmente.

2.6.2 Tuplas: listas inalterables

Las tuplas son listas que no se pueden modificar o alterar y se definen enumerando sus elementos
entre paréntesis en lugar de corchetes, por ejemplo:
>>> lista_alumnos = ('Miguel', 'Maria', 'Luisma', 'Fran', 'Luisa',
˓→'Ruyman')

También podemos definirlas como una variable con varios valores separados por comas; python
interpreta esto como una tupla aunque no esté entre paréntesis:

22 Capítulo 2. Introducción a la programación con Python


Curso de Computación Científica, Publicación 2016-09-15

>>> c = 1, 3 # Defino una variable con dos valores separados por


˓→comas

>>> print(c)
>>> (1, 3)

Podemos comprobarlo viendo el tipo de dato del que se trata:

>>> type(c)
<type 'tuple'>
>>> print(c[0]) # imprimimos el primer elemento de la tupla
1
>>> print(c[1]) # imprimimoa el segundo elemento de la tupla
3

Si se intenta insertar, quitar o reordenar la tupla con los comandos que ya conocemos para las
listas, o en definitiva modificar la tupla, aparecerá un error indicando que no es posible.

2.6.3 Diccionarios

Los diccionarios son listas en las que cada elemento se identifica no con un supuesto índice (nú-
mero de orden), sino con un nombre o clave, por lo que siempre se usan en parejas clave-valor
separadas por ”:”. La clave va primero y siempre entre comillas y luego su valor, que puede ser
en principio cualquier tipo de dato de Python; cada pareja clave-valor se separa por comas y todo
se encierra entre llaves. Por ejemplo, podemos crear un diccionario con los datos básicos de una
persona:

>>> datos = {'Nombre': 'Juan', 'Apellido': 'Martinez', 'Edad': 21,


˓→'Altura': 1.67}

>>> type(datos)
<type 'dict'>

En este caso hemos creado una clave “Nombre” con valor “Juan”, otra clave “Apellido” con valor
“Martínez”, etc. Al crear los datos con esta estructura, podemos acceder a los valores de las claves
fácilmente:

>>> print( datos['Nombre'] )


Juan

Fíjense que ya no podemos acceder a los datos por su índice y obtenemos error:

>>> print(datos[0])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 0

2.6. Estructuras de datos: listas, tuplas y diccionarios 23


Curso de Computación Científica, Publicación 2016-09-15

También podemos conocer todas las claves y los valores de un diccionario usando los métodos
keys() y values() respectivamente:

>>> datos.keys()
['Apellidos', 'Nombre', 'Altura']

>>> datos.values()
['Martinez', 'Juan', 1.6699999999999999]

Como vemos, la respuesta de estos dos métodos es una lista con las claves y valores del diccionario,
que podemos manipular como tales.

2.7 Módulos y paquetes de Python

Python viene con muchos módulos (un conjunto de funciones) que ofrecen funcionalidades adi-
cionales muy interesantes. Uno de ellos es el módulo de funciones matemáticas básicas math, que
ya hemos visto brevemente, otro el paquete de utilidades del sistema sys y muchos más. Se puede
importar un módulo cualquiera haciéndolo implícitamente, o sea importando el módulo completo
o bien nombrando una, varias o todas sus funciones; veamos cómo hacerlo:

>>> import math # importa el módulo math


>>> import math as M # importa el módulo math llamándolo
˓→M

>>> from math import sin, cos, pi # importa funciones sin, cos y pi
˓→de math

>>> from math import * # importa todas las funciones de


˓→math

Podemos ver un listado de las funciones que ofrece un módulo usando la función dir():

>>> import math


>>> dir(math) # Lista todas las funciones y subpaquete del modulo
˓→math

['__doc__', '__name__', '__package__', 'acos', 'acosh', 'asin',


'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'copysign', 'cos', 'cosh',
'degrees', 'e', 'exp', 'fabs', 'factorial', 'floor', 'fmod', 'frexp',
'fsum', 'hypot', 'isinf', 'isnan', 'ldexp', 'log', 'log10', 'log1p',
'modf', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan',
'tanh', 'trunc']

Para conocer otros módulo de la librería estándar pueden consultar el “tutorial” oficial de Python
o la guía oficial de la Librería de Python. En ocasiones nos encontraremos con módulos más gran-
des y complejos, que en realidad son módulos que contienen otros módulos y se les suele llamar
paquetes, aunque se usan de manera similar y a veces se mezclan una y otra definición.

24 Capítulo 2. Introducción a la programación con Python


Curso de Computación Científica, Publicación 2016-09-15

Por nuestra parte iremos introduciendo más adelante otros módulos y paquetes numéricos de Pyt-
hon más avanzados que nos aportan éstas y muchas otras funciones matemáticas, muy útiles para
la computación científica.

2.8 Información y ayuda sobre funciones y módulos

Prácticamente todas las funciones de Python tienen un texto de ayuda que nos explican qué hacen
y cómo funcionan. Para consultar esta ayuda usamos la función help() y entre paréntesis el
nombre de la función que queremos consultar:

>>> help(round)

Help on built-in function round in module __builtin__:

round(...)
round(number[, ndigits]) -> floating point number

Round a number to a given precision in decimal digits (default 0


˓→digits).
This always returns a floating point number. Precision may be
˓→negative.

Si lo que queremos consultar es un método (es decir, una función que se aplica a un tipo de dato
o variable en concreto) y no una función general, podemos ver la ayuda empleando la variable o
elemento con el que queremos usarlo:

>>> frase = "Hombros de gigantes"


>>> help(frase.replace)

de esta manera vemos la ayuda del método replace() de los string. Deberemos teclear q para
salir de la ayuda. Como acabamos de ver, si queremos ver las funciones disponibles en un módulo
podemos hacerlo con dir(modulo). Si usamos ipython, también podemos obtener la ayuda
añadiendo ? a la función, método o módulo que nos interese:

In [1]: round? # da ayuda sobre la función round


In [2]: frase.replace? # ayuda sobre el método replace de los strings

Si escribimos help() sin ningún parámetro, entramos en un entorno de ayuda general de Python,
donde además de la ayuda de funciones y módulos también podemos saber los módulos instalados
en nuestro ordenador entre otras cosas.
Recuerda que la fuente de información más completa y actualizada es la documentación oficial de
Python, donde además de una guía de uso (tutorial), hay una referencia completa de las funciones
y módulos que Python trae por defecto.

2.8. Información y ayuda sobre funciones y módulos 25


Curso de Computación Científica, Publicación 2016-09-15

2.9 Ejercicios

1. Calcular a mano cada una de las expresiones siguientes y comprobar, con la ayuda de Python
en el ordenador, si el resultado es correcto:
16**(1/2)+1/2
16**(1./2)+1/2
3e3/10
15/5e-3+1
2. Evaluar en punto flotante las siguientes funciones para los valores de x=-0.5, 1.1, 2, 3.1:
𝑥4 + 𝑥3 + 2𝑥2 − 𝑥 + 3
√︀
4𝑥 3 | sin 𝑥 + tan 𝑥|
𝑥3 −5𝑥
(𝑥2 + 12 )2

3. Calcular a mano cada una de las expresiones siguientes y comprobar, con la ayuda de Python
en el ordenador, si el resultado es correcto:
int(exp(2*log(3)))
round(4*sin(3*pi/2))
abs(log10(0.01)*sqrt(25))
round(3.21123*log10(1000),3)
4. Calculen a mano cada una de las expresiones siguientes y comprueben, con la ayuda de
Python en el ordenador, si ambas soluciones son iguales:
str(2.1) + str(1.2)
int(str(5) + str(7))
int('5' + '7')
str(5 + 7)
str(int(83.6) + float(7))
5. Dada la frase de S. Hawking: “Dios no solo juega a los dados, a veces los tira donde no se
pueden ver”, ¿qué funciones o comandos de Python utilizarían para responder o ejecutar lo
siguiente?:
¿Cuántos carácteres tiene (incluyendo letras, espacios y signos de puntuación)? ¿y pa-
labras?
Pasa a una variable los 15 primeros carácteres. Pasa a una lista las cinco primeras
palabras.

26 Capítulo 2. Introducción a la programación con Python


Curso de Computación Científica, Publicación 2016-09-15

¿Cuántas letras tiene la última palabra?


Concatenen (unan) el primer tercio de la frase con el último tercio.
6. Crear una variable tipo string con todas las letras del abecedario en minúsculas juntas, sin
incluir la eñe, es decir “abcd...”.
Buscar la posición en la cadena de texto de todas las vocales.
Crea otra variable igual, pero formada por las mayúsculas (es decir “ABCD...”).
Divide la cadena de mayúsculas por la mitad y crea una nueva variable con segunda
mitad primero y después la primera, separadas por un guión.
Usa los índices en cadenas de texto para extraer cada una de las letras necesarias para
escribir “Bohr”, concatenándolas (uniéndolas con +).
7. Crear una lista con el apellido de diez físico/as importantes en la historia. Usando métodos
e índices de listas hacer lo siguiente:
Ordenar la lista alfabéticamente.
Añadir al final de la lista a Curie si no estaba.
Sacar de la lista a Newton (lo habrás puesto, ¿no?).
En su lugar incluir en la lista a Chandrasekhar (¿o ya lo habían puesto?).
Quitar el primer nombre de la lista.
8. Generar una lista de enteros que vaya de 1 a 19, ambos inclusive, de dos en dos. Usando los
índices de listas hacer lo siguiente:
Añadir el 20 a la lista.
Invertir el orden de la lista (usa la ayuda de Python si no sabes cómo).
Imprimir la lista, salvo el primero y el último.
Crear una nueva variable que contenga los cinco primeros elementos de la lista (conca-
tenados, no sumados) y otra con los cinco penúltimos.
Sumar todos los elementos de la lista y divídelo entre su longitud (número de elemen-
tos).
9. Crear una variable del tipo string con el valor del número 𝜋, con al menos seis decimales.
Dividir la cadena de texto con el número 𝜋 en una lista que tenga en el primer elemento la
parte entera y en el segundo elemento la parte decimal. Recordar que sólo las cadenas de
texto se pueden separar en listas, no los números.
10. El valor de 𝜋 se puede obtener del módulo math con pi. Imprimir su valor mostrando sólo
tres decimales.

2.9. Ejercicios 27
Curso de Computación Científica, Publicación 2016-09-15

11. Crear una variable que valga 174 (entero) y calcular su raíz cuadrada que almacenan en otra
variable. Imprimir ahora una frase que diga “La raiz cuadrada de 174 es 13.1909.” usando
las variables creadas junto con el formato de impresión adecuado.
12. Consideremos una lista de python llamada “cosas” que posee distintos tipos de datos:

cosas = ['a=', '3.14', ['perro', 'gato', 'liebre', 'cabra'], 'coche


˓→', 1e4, 4]

Para esta lista, hacer lo siguiente:


Contar el número de elementos de la lista
Calcular el resultado de dividir el segundo elemento por el último.
Extraer la lista de animales y decir cuántos hay.
Contar el número de letras en el tercer elemento de la lista de animales.
Añadir ‘elefante’ a la lista de animales dentro de la lista “cosas”, ordenándola alfabeti-
camente.
13. Crear una lista de Python llamada “planetas” con los nombres de los planetas del Sistema
Solar, hasta Neptuno. Se pide:
Utilizar una función de Python para conocer la longitud de la lista (es decir, saber
cuántos planetas incluye la lista).
Comprobar con algún operador de Python que el último planeta de la lista es Neptuno
(¿es cierto o falso?)
Añadir al final de la lista los planetas enanos: Pluton, Ceres, Eris, Makemake y Haumea.
A partir de esta lista y usando los índices de listas creen tres nuevas listas que incluir
respectivamente: a) Los planetas terrestres (hasta Marte, inclusive), b) los planetas ga-
seosos (el resto, hasta Neptuno), c) los planetas enanos recién añadidos.
Imprimir por pantalla todas las listas creadas hasta ahora diciendo qué son cada una de
ellas.
Eliminar la Tierra de la lista original y colocar en su lugar la Luna.
Añadir la Tierra al principio de la lista original.
Ordenar alfabéticamente la lista completa de planetas.

28 Capítulo 2. Introducción a la programación con Python


CAPÍTULO 3

Análisis de errores

3.1 Tipos de errores

3.1.1 Errores en los Datos y Computacionales

Aunque a veces algunos errores en un cálculo se pueden atribuir a los datos de entrada, en otras
ocasiones se atribuyen al propio cómputo o cálculo. Tal distinción no siempre es clara. veámoslo
con un ejemplo: en el caso del problema típico de evaluar una función 𝑓 (𝑥), donde tenemos que:
𝑥, es valor verdadero de la variable de entrada,
𝑦 = 𝑓 (𝑥), es el valor verdadero deseado de salida,
𝑥𝑖 , es valor inexacto o aproximado de entrada,
𝑓𝑎 , es la aproximación mejor (algoritmo) a la función 𝑓 ,
podemos calcular que el valor del error absoluto total acumulado en el cálculo será:

𝑦𝑎 − 𝑦 = 𝑓𝑎 (𝑥𝑖 ) − 𝑓 (𝑥) = [𝑓𝑎 (𝑥𝑖 ) − 𝑓 (𝑥𝑖 )] + [𝑓 (𝑥𝑖 ) − 𝑓 (𝑥)] ,

que podemos asignar como: [error del cómputo] + [error propagado de los datos].
Fíjense que el algoritmo escogido para realizar el cálculo no influye en el segundo sumando y que,
por lo tanto, sólo es el resultado de la propagación de los errores de los datos. El valor del primer
sumando depende del algoritmo escogido y el segundo depende de cuánto se aproxime el valor
aproximado usado al valor verdadero.
Además, en el error del cómputo, podemos diferenciar los siguientes tipos de error:
1. Error de truncamiento. Diferencia entre el resultado verdadero (dada la entrada real) y el
resultado que se produciría usando un algoritmo determinado utilizando aritmética exacta.
Depende básicamente de la máquina de cómputo empleada. El truncamiento es el número
de dígitos a la derecha del separador decimal con el que cuenta nuestro sistema de cálculo.
Algunas veces proporcionará el mismo resultado que el redondeo, pero el truncamiento su-
pone poner un límite al número de dígitos decimales por lo que, en definitiva, produce un

29
Curso de Computación Científica, Publicación 2016-09-15

redondeo hacia abajo. El error de truncamiento puede ser hasta el doble del error máximo de
redondeo.
2. Error de redondeo. Es la diferencia entre el resultado obtenido con un algoritmo con aritméti-
ca exacta y el resultado producido por el mismo algoritmo utilizando aritmética de precisión
finita.
Por medio de la resolución de un ejemplo práctico podemos entender mejor estos conceptos:
Ejemplo: Calculen una aproximación al 𝑠𝑒𝑛(𝜋/10) y los errores (computacional, de los datos y
el total) que cometen tomando como dato de entrada (𝑥𝑖 ) el valor de 𝜋 ≈ 3 y el del algoritmo
aproximado (𝑓𝑎 ) como 𝑠𝑒𝑛(𝑥) ≈ 𝑥.
Solución: error computacional = 0.00448 , error datos = -0.013497. Pero continuen un poco más
para responder a estas preguntas:
¿Qué pasaría si repetimos los cálculos para 𝑠𝑒𝑛(𝜋/50) o 𝑠𝑒𝑛(𝜋/5)?
¿Cuál es el resultado si utilizamos una calculadora simple en vez del computador?, ¿a todos
les sale igual?, ¿Cómo explican las diferencias si las hay?.
RECUERDEN que: la calidad en el resultado de un cálculo depende primordialmente de la cali-
dad de los datos de entrada, entre otras cosas. Por ejemplo, si nuestros datos de entrada tienen hasta
5 cifras significativas en el resultado del cálculo, independientemente de lo bien que lo hagamos,
no podemos esperar tener dicho resultado con más de 5 cifras significativas.

3.1.2 Error hacia Adelante (EAD) y Error hacia Atrás (EAT)

Debemos enfatizar una vez más que si los errores con que conocemos los datos de entrada son
grandes, por mucho que utilicemos un algoritmo perfecto no vamos a obtener un resultado con una
precisión mejor. Por ello, es importante estudiar como se propagan los errores y cuáles son sus
posibles fuentes durante la computación, con el objeto de minimizarlos o al menos acotarlos.
Supongamos que queremos evaluar una función 𝑦 = 𝑓 (𝑥) para un cierto valor de 𝑥 dado, y sólo
podemos obtener un valor aproximado 𝑦𝑎 , utilizando un algoritmo 𝑓𝑎 ; entonces podemos estimar
el error absoluto que cometemos definiendo ERROR ADELANTE como ∆𝑦 = 𝑦𝑎 − 𝑦.
No obstante, no siempre es posible calcularlo y, a veces, debemos resolver el problema de otra for-
ma: obtener la solución (aproximada) al problema original podría hacerse obteniendo la solución
exacta de un problema ‘’modificado’‘, pero no demasiado. Por lo tanto, podríamos preguntarnos
cuán grande puede ser la modificación al problema propuesto para obtener la solución real. En
otras palabras, definimos la cantidad ∆𝑥 = 𝑥𝑖 − 𝑥, donde 𝑓 (𝑥𝑖 ) = 𝑦𝑎 , como el ERROR ATRÁS;
es decir, cuánto error en los datos de entrada podemos ‘’admitir” para explicar todo el error obteni-
do al final. Obviamente, éste lo deberemos estimar en un análisis hacia atrás del problema. Desde
este punto de vista una solución aproximada para un problema dado es buena si es la solución
exacta a un ‘’problema parecido” al original.
Unas veces será más fácil utilizar uno u otro de los procedimientos y siempre que podamos eligi-
remos el algoritmo o procedimiento que nos de menor error.

30 Capítulo 3. Análisis de errores


Curso de Computación Científica, Publicación 2016-09-15


Ejemplo: Queremos calcular la raíz cuadrada de 2, 𝑦 = 2, con la aproximación 𝑦𝑎 = 1.41.
Calculen los errores hacia adelante y hacia atrás.
Solución: hacia adelante: |∆𝑦|√= |𝑦𝑎 −𝑦| = |1.41−1.4142...| = 0.0042. Para calcular el error hacia
atrás tenemos en cuenta que 1.9881 = 1.41 = 𝑥𝑖 , entonces |∆𝑥| = |𝑥𝑖 − 𝑥| = |1.9881 − 2| =
0.0119.

3.2 Propagación de errores

Como ya hemos visto, a lo largo de la computación de un problema, los errores iniciales en los
datos de entrada, o en cualquier paso del cómputo, se irán transmitiendo a lo largo del proceso
(error computacional) y tendrán influencia en el resultado final. Por lo tanto es importante tratar de
mantener este error que se va propagando lo más pequeño posible y para ello es interesante saber
como se propaga el error a lo largo de un cálculo. Veámoslo con un ejemplo.
Si tenemos dos magnitudes 𝑟 y 𝑠 (cuyos errores relativos son 𝑅𝑟 y 𝑅𝑠 ) y las medimos con errores
absolutos de ∆𝑟 y ∆𝑠 , con ∆𝑟 ≪ 𝑟 y ∆𝑠 ≪ 𝑠, si efectuamos su producto 𝑄 = 𝑟·𝑠, lo obtendremos
con un error:

𝑄 = 𝑟 · 𝑠 = 𝑟𝑣 (1 + 𝑅𝑟 ) · 𝑠𝑣 (1 + 𝑅𝑠 ) = 𝑟𝑣 𝑠𝑣 (1 + 𝑅𝑟 + 𝑅𝑠 + 𝑅𝑟 𝑅𝑠 ) ≈ 𝑟𝑣 𝑠𝑣 (1 + 𝑅𝑟 + 𝑅𝑠 ) = 𝑄𝑣 (1 + 𝑅𝑄 ) ,

con lo que tenemos finalmente RQ = Rr + Rs .


Ustedes mismos pueden comprobar que, de forma similar, se puede mostrar que si:
𝑄 = 𝑟 · 𝑠 · 𝑡 · 𝑢 · 𝑣 ... entonces 𝑅𝑄 = 𝑅𝑟 + 𝑅𝑠 + 𝑅𝑡 + 𝑅𝑢 + 𝑅𝑣 + ... ,
𝑄 = 𝑟𝑛 entonces 𝑅𝑄 = 𝑛𝑅𝑟 , para 𝑛 ≥ 0.
No obstante podrían comprobar igualmente que en el caso de una división, es decir si 𝑄 = 𝑟/𝑠,
entonces 𝑅𝑄 = 𝑅𝑟 − 𝑅𝑠 . Podría parecer pues que en el caso de una división el error relativo puede
ser menor que los de sus miembros pero no es así. Debemos notar que, en general, 𝑅𝑟 y 𝑅𝑠 pueden
ser positivos o negativos (redefiniendo el valor absoluto) con lo que 𝑅𝑄 = 𝑅𝑟 + 𝑅𝑠 puede variar
entre (−|𝑅𝑟| − |𝑅𝑠|) y (|𝑅𝑟| + |𝑅𝑠|).
Esta manera de estimar errores resulta bastante tediosa y a menudo no aporta información relevante
al problema. Normalmente, se suele hablar del error más probable que, para los errores relativos
del producto (o igualmente para el cociente), se define como la raíz cuadrada de la suma de los
cuadrados de los errores de los factores que intervienen en el producto (o cociente). En general,
se puede demostrar sin demasiadas complicaciones, utilizando el cálculo diferencial, el llamado
principio de superposición de errores para una función cualquiera 𝑄(𝑟, 𝑠, 𝑡, ...). En este caso, el
error en 𝑄, ∆𝑄, en función de los errores ∆𝑟, ∆𝑠, ∆𝑡,... vendría dado por:
𝜕𝑄 2 𝜕𝑄 2 𝜕𝑄 2
(∆𝑄)2 = ( ) (∆𝑟)2 + ( ) (∆𝑠)2 + ( ) (∆𝑡)2 + ...
𝜕𝑟 𝜕𝑠 𝜕𝑡
𝜕𝑄
donde 𝜕𝑟
es la derivada parcial de 𝑄 respecto de 𝑟 y análogamente las demás.

3.2. Propagación de errores 31


Curso de Computación Científica, Publicación 2016-09-15

3.3 Sensibilidad y Acondicionamiento de un problema

Una solución inexacta a un problema también puede ser debida al propio problema que queremos
resolver puesto que éste puede ser altamente sensible a pequeñas perturbaciones en los datos de
entrada. Esta cualidad es la sensibilidad, que se define como lo sensible que es la solución de un
problema a pequeñas perturbaciones o a los errores en los datos de entrada. Como vemos, tanto la
sensibilidad como su medida, el acondicionamiento, están relacionados con la propagación de los
errores en los datos de entrada. Así se habla de:
Problema bien acondicionado o insensible, si el cambio relativo en la solución del problema
puede ser menor o igual que en los datos de entrada.
Problema mal acondicionado o sensible, cuando no sucede lo anterior.
Definimos el acondicionamiento de un problema como el cociente entre el Cambio Relativo en la
Solución y el Cambio Relativo en la Entrada, es decir:

| 𝑓 (𝑥𝑖𝑓)−𝑓
(𝑥)
(𝑥)
| | Δ𝑦
𝑦
|
= ,
| (𝑥𝑖𝑥−𝑥) | | Δ𝑥
𝑥
|

o también: Error Relativo Adelante / Error Relativo Atrás , que siempre resulta difícil de obtener.
Así que a menudo se define que:

Error Relativo Adelante ≤ C · Error Relativo Atras ,

Es interesante darse cuenta que 𝐶 se interpreta como un factor de amplificación del cambio en
los datos de entrada. Si es mucho mayor que uno tendremos un problema mal acondicionado o
altamente sensible a los errores en los datos de entrada.
De la definición anteriormente efectuada, y teniendo en cuenta la aproximación 𝑓 (𝑥𝑖 ) = 𝑓 (𝑥) +
𝑓 ′ (𝑥)∆𝑥 + ... podemos calcular 𝐶 de forma aproximada como:
𝑓 ′ (𝑥)Δ𝑥
𝑓 (𝑥) 𝑥𝑓 ′ (𝑥)
𝐶= | Δ𝑥 |=| |
𝑥
𝑓 (𝑥)

3.4 Estabilidad y Exactitud de un algoritmo

El concepto de estabilidad de un algoritmo computacional es parecido al de sensibilidad de un


problema matemático. La diferencia entre ellos es que la estabilidad se refiere al efecto de los
errores de computación mientras que el de sensibilidad (o acondicionamiento) se refiere a los
efectos de los errores de los datos de entrada en la solución del problema.
Así pues un algoritmo es estable si el resultado que produce es relativamente insensible a las
perturbaciones hechas en la computación; desde el punto de vista de análisis de errores, es estable
si el resultado es la solución exacta al problema aproximado. Esta sería la estabilidad en sentido

32 Capítulo 3. Análisis de errores


Curso de Computación Científica, Publicación 2016-09-15

estricto. Podemos ser más permisivos, estable en sentido laxo, si el algoritmo produce la solución
‘casi exacta’ al problema aproximado.
La exactitud, como ya sabemos, se refiere a lo cerca que está la solución computada de la so-
lución verdadera del problema matemático. La estabilidad de un algoritmo no garantiza que la
solución computada sea exacta. La exactitud depende tanto de la sensibilidad (acondicionamiento)
del problema matemático como de la estabilidad del algoritmo utilizado para resolverlo.

3.5 Ejercicios

1. Tengamos un movimiento armónico simple definido por 𝑦 = sin(2𝜔𝑡 + 𝜑), donde 𝜔 =


5.5𝐻𝑧. Encontrar el error relativo en 𝑦 debido a un error relativo en 𝑡 del 0.1 % cuando:
𝜋
𝑡 = 2𝜔 s, y 𝑡 = 𝜔𝜋 s. Finalmente calculen los errores relativos antedichos para el caso de
𝜑 = 𝜋/3.
3 5 7
2. Aproximemos la función sin(𝑥) por su desarrollo en serie sin(𝑥) ≈ 𝑥 − 𝑥3! + 𝑥5! − 𝑥7! + .... Se
pide que calculen los errores hacia adelante y hacia atrás para 𝑥 = 0.1, 0.5, 1.0, en los casos
en que:
Se aproxima la función por el primer término del desarrollo en serie solamente.
Se aproxima la función por los tres primeros términos del desarrollo en serie solamente.
3. Calculemos la densidad media del planeta Tierra contestando a las cuestiones siguientes:
Indiquen las hipótesis más importantes del modelo.
Indiquen el algoritmo que van a usar y busquen los datos de entrada necesarios con sus
errores, si los hay.
Calculen primero el volumen de La Tierra y sus errores absoluto y relativo. Tomen
como valor verdadero 1.08321 × 1012 𝐾𝑚3 .
Calculen finalmente su densidad y expliciten cuantas cifras significativas tiene el resul-
tado final.
4. Cuando un electrón 𝑒− , de masa 𝑚𝑒 , se mueve a velocidad 𝑣 en un acelerador sincrotrón su
masa aumenta según 𝑚 = 𝑚𝑒 [1 − (𝑣/𝑐)2 ]−(1/2) , donde 𝑚𝑒 es su masa en reposo y 𝑐 es la
velocidad de la luz en el vacío. Se pide:
Demostrar que el incremento (error) relativo en 𝑚 es aproximadamente (𝑣/𝑐)2 veces el
incremento (error) relativo en 𝑣, suponiendo que (𝑣/𝑐) ≪ 1.
Calculen la masa del 𝑒− si su velocidad es de 35500. Km/s y en cuánto aumenta si
acelera hasta 36000. Km/s.
5. Supongamos que medimos la aceleración de la gravedad 𝑔, a nivel del mar en un punto de
la Tierra de 0∘ de latitud y 0∘ de longitud, utilizando el método de las oscilaciones de un

3.5. Ejercicios 33
Curso de Computación Científica, Publicación 2016-09-15

péndulo simple (midiendo su longitud ℓ y su periodo 𝜏 ). Del resultado de la experiencia se


obtiene que ℓ = 10.002 ± 0.0001𝑚 y 𝜏 = 6.42559𝑠 ± 1𝜇𝑠. Se pide:
Cuál es el error relativo que tendrá la determinación de 𝑔.
Cuál es la medida final de la aceleración de la gravedad con su error absoluto.
6. Se ha observado el cuásar más lejano conocido midiendo un desplazamiento hacia el rojo de
𝑧 = 8.1 ± 0.3. Utilizando el efecto Doppler relativista nos permite calcular una velocidad
de recesión de 288748 ± 2338𝐾𝑚/𝑠 . Utilicen la Ley de Hubble para calcular la distancia a
la que se encuentra e indiquen la precisión con que se conoce. Nota: La ley de Hubble dice
que 𝑣 = 𝐻 · 𝑑, donde 𝑣 es la velocidad de recesión del objeto en cuestión, 𝑑 es su distancia
y 𝐻 es la llamada constante de Hubble, una constante cuyo valor medido (en 2009 usando
el HST) es de 74.2 ± 3.6𝐾𝑚/𝑠/𝑀 𝑝𝑐. 1𝑀 𝑝𝑐 = 106 𝑝𝑐 ; 1 pc es la distancia desde la cual se
ve la unidad astronómica (UA, distancia media entre la Tierra y el Sol) bajo un ángulo de 1
segundo de arco.

34 Capítulo 3. Análisis de errores


CAPÍTULO 4

Programas ejecutables

4.1 Reutilizando el código

Además de usar python de manera interactiva desde una consola, es posible crear programas o
scripts (guiones) ejecutables; de hecho es la forma más habitual de trabajar. Un programa o script
es un fichero de texto con los comandos o sentencias de python escritos de manera consecutiva y
que se interpretarán línea a línea al ejecutar el script desde una consola de comandos del sistema
operativo. Bastará utilizar un editor de textos cualquiera (kate, gedit, notepad, etc.), crear un fichero
con el nombre que queramos y con extensión .py para que éste lo identifique como un programa
en Python. En él escribiremos las sentencias o comandos uno detrás de otro, en el orden en que
queramos que sean ejecutados.
El siguiente ejemplo es un pequeño programa que llamaremos cubo.py que calcula el cubo de un
número cualquiera dado por el usuario:

#!/usr/bin/python
#-*- coding: utf-8 -*-

# Mensaje de bienvenida
print("Programa de calculo del cubo de un numero.\n")
# La \n antes de las comillas introduce una línea en blanco

# Numero de entrada
x = 23.0

# Calculo el valor del cubo de x


y = x**3

# Imprimo el resultado
print("El cubo de %.2f es %.2f" % (x, y))

El programa se puede ejecutar ahora desde una consola desde el directorio en donde tenemos el
archivo con el programa, escribiendo en ella

35
Curso de Computación Científica, Publicación 2016-09-15

$ python cubo.py

y la respuesta del programa será:

Programa de calculo del cubo de un numero.

El cubo de 23.00 es 12167.00

En la primera línea del programa hemos escrito #!/usr/bin/python (válido para Linux o Mac OS,
no Windows)para indicar la ruta donde tenemos instalado python. La segunda línea, #-*- coding:
utf-8 -*- hemos indicado el tipo de codificación UTF-8, para poder poner caracteres especiales
como tildes y eñes.
Ya que la primera línea indica en qué directorio está el ejecutable de python, el programa tam-
bién se puede ejecutar como un comando o programa del sistema escribiendo, fuera de Python,
únicamente:

$ ./cubo.py

Pero para que funcione esto tenemos que asegurarnos de que el programa tiene permisos de ejecu-
ción. Si no lo tiene se lo damos (ver Apéndice B), que para Linux o Mac se hace como sigue:

$ chmod u+x cubo.py # Para Linux y Mac

La primera línea del programa depende del sistema operativo que está instalado en el ordenador,
que en el ejemplo se supone para Linux (o Mac). En el caso de Windows debemos escribir en la
primera línea de programa algo similar a esto:

#!C:/Python/python.exe -u # o el lugar en donde resida el


# ejecutable python.exe en Windows

pero puede variar según la instalación de python del ordenador y además, en Windows es más
común ejecutar un programa desde el Explorador de Ficheros (File manager) donde funcionará
siempre que esté configurado para que los ficheros .py se ejecuten con Python, por lo que no
tiene tanta utilidad en el el caso de Windows. De cualquier manera, la ejecución de un programa
desde fuera de la consola de python siempre funcionará usando:

$ python cubo.py

Si quieres ejecutar el programa desde dentro de la consola de python, puedes usar lo siguente:

>>> execfile('cubo.py')

de igual manera, si estamos usando ipython podemos usar el comando mágico run:

In [1]: run cubo.py

36 Capítulo 4. Programas ejecutables


Curso de Computación Científica, Publicación 2016-09-15

La ventaja en este último caso con ipython es que una vez ejecutado, las variables definidas en el
programa lo estarán ahora en la sesión de ipython a nuestra disposición, lo que es muy útil para
probar y corregir nuestro programa interactivamente.
In [2]: print x
23.0

es decir, vemos ahora tenemos la variable x está definida en nuestra sesión.


Hay que fijarse que en el programa muy a menudo hay sentencias comentadas, que son las que
empiezan con #. Estas líneas no son ejecutables y son ignoradas por el intérprete de python;
se usan solamente para añadir notas o comentarios. Es muy recomendable incluir comentarios de
este tipo que expliquen lo que el programa va haciendo, porque nos ayudarán a recordar qué hace
o cómo funciona el programa cuando volvamos a leerlo nosotros, u otros usuarios, y queramos
reutilizarlo o modificarlo. Se pueden incluir comentarios de varias líneas, por ejemplo al inicio del
programa para explicar lo que hace, usando comillas triples; todo lo que vaya entre ellas será un
comentario y por tanto ignorado por el intérprete.:
# Este es un comentario de una linea
print("La raíz cuadrada de 2 es %.2f" % sqrt(2.))

"""Este un comentario que


usa varias lineas y está limitado
por comillas triples. Todo lo que esté
entre ellas no se intenta ejecutar
"""
print("El cubo de 2 es %.1f" % 2.0**3)

4.2 Definiendo funciones

En capítulos anteriores ya hemos aprendido a usar funciones; algunas de ellas están predefinidas
(abs(), round(), etc.) mientras que otras deben importarse de módulos antes de poder ser uti-
lizadas (sin(), sqrt() se importan del módulo math). No obstante, además de las funciones
incluidas en python, el usuario puede crear las suyas propias, que permiten reutilizar partes del
código de un programa sin necesidad de repetirlo varias veces. Las funciones para poder ser utiliza-
das o llamadas o invocadas, antes hay que definirlas. Veamos por ejemplo, como podemos escribir
un programa con una sencilla función para calcular el cubo de un número y otra para imprimir un
mensaje:
#!/usr/bin/python
#-*- coding: utf-8 -*-

# ahora definimos una función que calcula el cubo de un número


˓→cualquiera

def cubo(x):

4.2. Definiendo funciones 37


Curso de Computación Científica, Publicación 2016-09-15

y = x**3
return y

# Ahora utilizamos la funcion para calcular el cubo de 4


resultado = cubo(4.0)

# imprimo el resultado
print(resultado)

Definimos la función cubo() que calculará el cubo de un número cualquiera. Nótese que la
definición de la función se hace en líneas identadas (con sangría) y acaba con la sentencia return
para devolver el valor calculado de la función, sin imprimirlo necesariamente. Esto es lo más
habitual cuando se crea un función: hacer todas las operaciones necesarias y luego utilizar return
para devolver un valor. Después de utilizar la función, el resultado puede imprimirse, volcarse a
una variable, o lo que el programador necesite. También podemos definir funciones que dependan
de varios parámetros (separados por comas); lo único que debemos hacer a la hora de utilizarla
después será invocarla con el mismo número de parámetros y los valores que queramos en el mismo
orden que en la definición. Alternativamente, podemos definir una función en la que hacemos
varios cálculos que queremos que nos devuelva; en este caso, los podemos devolver por medio del
return en una lista que contenga dichos resultados. Más adelante veremos un ejemplo.
Vamos a definir a continuación una función con dos variables:
#!/usr/bin/python
#-*- coding: utf-8 -*-

# Definimos una función que calcula el área de un triángulo


def area_triangulo(b,h):
area = b*h/2.
return area

# Utilizamos la funcion para calcular el área de un triángulo de 2.3


˓→de base

y 3 de altura
resultado = area_triangulo(2.3,3.0)

# imprimo el resultado
print(resultado)

No obstante, no todas las funciones devuelven un valor y en este caso los llamamos procedimien-
tos. Veamos un ejemplo interesante, la función que llamamos saludo() en el que no calculamos
nada y por tanto no devolvemos un valor:
#!/usr/bin/python
#-*- coding: utf-8 -*-

# definimos una función que muestra en pantalla un mensaje de saludo

38 Capítulo 4. Programas ejecutables


Curso de Computación Científica, Publicación 2016-09-15

def saludo(nombre):
print("Buenas tardes, %s " % nombre)

# Utilizamos ahora la función para saludar a Juan


saludo('Juan')
# Imprime: Buenas tardes, Juan

En este caso no calculamos nada y por lo tanto, no necesitamos acabar con un return la defi-
nición del procedimiento. Lo que hacemos es ordenar que se imprima una variable y por lo tanto
siempre se imprime una línea cada vez que se llama a la función.

Advertencia: En Python el sangrado (o identación) es obligatorio, porque se emplea para


separar los bloques que indican donde empiezan y terminan las funciones (también como
veremos más adelante los bucles y condicionales). Si el sangrado no es correcto, se obtendrá
un resultado equivocado o nos devolverá un errror. El sangrado habitual es de 4 espacios, si no
es así en nuestro editor, deberemos cambiarlo en las preferencias.

4.2.1 Variables locales y globales

En la definición de las funciones es posible definir y utilizar variables cuya acción y definición
queda circunscrita a la propia función. Son las llamadas variables locales que fuera de la función
no tienen validez. Veamos un ejemplo. Calculemos el volumen, area superficial y la longitud de
cualquier circunferencia máxima en una esfera de radio R definiendo una función que llamamos
esfera():

#!/usr/bin/python
#-*- coding: utf-8 -*-

# importamos todas las funciones del módulo math


from math import *

# ahora definimos una función que calcula los parámetros de la esfera


def esfera(r):
pir = pi*r
longitud = 2.*pir
superficie = 4.*pir*r
volumen = superficie*r/3.
# devolvemos una lista con los resultados
return [longitud,superficie,volumen]

# Ahora utilizamos la función para el caso de una esfera de radio 3.215


l,s,v = esfera(3.215)

4.2. Definiendo funciones 39


Curso de Computación Científica, Publicación 2016-09-15

# imprimimos el resultado
print('La longitud del circulo maximo es: %.3f' % l)
print('La superficie de la esfera es: %.3f' % s)
print('El volumen de la esfera es: %.3f' % v)

Nótese que en la definición de la función usamos la variable pir que es interna o local, es decir, sólo
se usa dentro de la función ya que no la devolvemos al hacer el return. Por lo tanto, si la quisiéramos
usar (o saber el valor que tiene) fuera del cuerpo de la función, en el programa principal, no
podríamos (por ejemplo, no podemos hacer print). Los parámetros de entrada en una función
también son variables locales; en este caso r. Las demás variables definidas son variables llamadas
globales ya que las podemos usar fuera del cuerpo de la función. Es conveniente no mezclarlas,
aunque podemos hacerlo si tenemos cuidado; es decir, una misma variable podemos usarla fuera
y dentro del cuerpo de la función y a pesar de que se llamen igual dentro del cuerpo de la función
tendrá el valor que sea (como local) y fuera de él también lo tendrá, como global.

4.3 Entrada de datos por pantalla

Generalmente el valor de una variable se asigna haciendo por ejemplo pi=3.1416, pero se puede
hacer que pida una entrada por teclado usando la función raw_input() de la forma siguiente
(pueden probarlo en la consola de Python):
>>> entrada_numero = raw_input('Dame un numero: ' )
Dame un numero: # Escribimos 22 con el teclado y pulsamos Enter/
˓→Intro

y esperará hasta que se le de un número o cualquier otro carácter (por ejemplo, le damos 22). Es
muy importante saber que el valor que se asigna a la variable, en este caso entrada_numero, es
siempre un string, aunque se introduzca un número. Por ejemplo, pueden comprobar que en el
ejemplo anterior el valor de la variable entrada_numero es “22”, con comillas, o sea un string. Si
queremos operar aritméticamente con el resultado debemos pasarlo a int o a float, según cómo lo
vayamos a usar, por ejemplo:
entrada_numero = float(entrada_numero)

Ahora, la variable entrada_numero es del tipo float y vale 22.0. Con la ayuda de esta función
podemos crear programas que pidan uno o varios números (u otro tipo de dato) al usuario y hacer
cálculos tan complejos como queramos con ellos, sin tener que cambiar los valores de entrada en
la correspondiente línea del programa cada vez que se utilice. Por ejemplo, podríamos modificar
el programa anterior de manera que pida al usuario el radio de la esfera cuando se ejecute el
programa:
#!/usr/bin/python
#-*- coding: utf-8 -*-
# este programa calcula el volumen, la superficie y la longitud

40 Capítulo 4. Programas ejecutables


Curso de Computación Científica, Publicación 2016-09-15

# del círculo máximo de una esfera de radio r que solicita por pantalla
# teo
# marzo 2012

# importamos todas las funciones del módulo math


from math import *

# ahora definimos una función que calcula los parámetros de la esfera


def esfera(r):
pir = pi*r
longitud = 2.*pir
superficie = 4.*pir*r
volumen = superficie*r/3.
# devolvemos una lista con los resultados
return [longitud,superficie,volumen]

# ahora solicitamos por pantalla el radio de la esfera


radio = float(raw_input('Deme el radio de la esfera: ' ))

# Ahora utilizamos la función, guardando el resultado en tres variables


longitud, superficie, volumen = esfera(radio)

# imprimimos el resultado
print('La longitud del circulo maximo es: %.3f' % longitud)
print('La superficie de la esfera es: %.3f' % superficie)
print('El volumen de la esfera es: %.3f' % volumen)

De esta forma, si queremos reutilizar el programa para el cálculo en el caso de otra esfera de radio
diferente no tenemos que cambiar nada del mismo y podemos volver a ejecutarlo tal cual está sin
problemas, introduciendo el radio que queramos cada vez que la usemos.
También podemos usar a función eval() para introducir valores de una variable. Esta función
evalúa el tipo de dato que introducimos:

# Introducimos dos valores float en la variable datos


>>> datos = eval(raw_input('x, y = '))
x, y = # Introducimos 1.2, 1.3

>>> print datos


(1.2, 1.3)

>>> type (datos)


<type 'tuple'> # Nos devuelve una tupla

Introducimos ahora dos variables separadas x e y:

4.3. Entrada de datos por pantalla 41


Curso de Computación Científica, Publicación 2016-09-15

# Introducimos dos valores float en dos variables diferentes


>>> x, y = eval(raw_input('x, y = '))
x, y = # Introducimos 1.2, 1.3

#Imprimimos una de ellas


>>> print x
1.2

>>> type (x)


<type 'float'> # Nos devuelve un float porque lo introdujimos como
˓→tal

4.4 Definiendo un módulo personal

Ya hemos visto como las funciones ayudan a hacer los programas más legibles y cortos al evitar que
tengamos que escribir una y otra vez los mismos algoritmos en un mismo programa. Sin embargo
a medida que vayamos aprendiendo a programar iremos haciendo más y más programas. Es muy
posible que en varios de estos programas usemos las mismas funciones con lo que acabaremos
teniendo que escribir la misma función en cada programa. Para evitar esta cuestión lo mejor es
definir nuestro propio módulo que incluya las funciones que vamos definiendo.
Un módulo no es más que una colección de funciones que podemos utilizar desde cualquier pro-
grama. Ya hemos visto que la distribución estándar de python ofrece un buen número de módulos
predefinidos que se agrupan normalmente por ámbitos de aplicación: funciones matemáticas en
math; las de numeros aleatorios en random; las que tratan con cadenas en string, etc... Así pues,
¿para qué vamos a re-escribir continuamente en nuestros programas funciones que nosotros ya
hemos escrito antes? Pues para evitarlo, creamos un módulo.
Para ello, creamos un fichero de texto que llamamos por ejemplo: funciones.py y vamos a incluir
en él todas las funciones que vayamos creando. El sufijo o extensión py significa que lo que habrá
dentro del fichero va a ser código python. Incluyamos en él las funciones que hayamos definido
en este capítulo:

from math import *

def cubo(x):
y = x**3
return y

def saludo(nombre):
print("Buenas tardes, %s" % nombre)

def esfera(r):
pir=pi*r

42 Capítulo 4. Programas ejecutables


Curso de Computación Científica, Publicación 2016-09-15

longitud = 2.*pir
superficie = 4.*pir*r
volumen = superficie*r/3.
return [longitud, superficie, volumen]

Listo. Ya podemos utilizar o importar el módulo en cualquier programa sin más que hacer:

>>> # importamos la funcion esfera del módulo funciones


>>> from funciones import esfera

>>> # o bien, importamos todas las funciones del módulo funciones


>>> from funciones import *

Cuando se importa o ejecuta por vez primera funciones.py verás que python crea automáticamen-
te un fichero llamado funciones.pyc; este fichero contiene una versión del módulo más fácil de
cargar en memoria que el original, pero ininteligible para nosotros ya que está codificado en “có-
digo binario”. De esta forma python acelera la carga del módulo en sucesivas ocasiones sin que
tengamos que preocuparnos nosotros de nada. Si se borra el .pyc no afectará al programa original
y simplemente se volverá a crear cuando se importe el módulo otra vez. Igualmente, si se modi-
fica de alguna manera el programa el funciones.py, se vuelve a crear una versión actualizada de
funciones.pyc. Ahora podemos probar nuestro módulo de funciones personales:

>>> saludo("Teo")
>>> Buenas tardes, Teo

Se trata de una manera muy práctica de tener a mano las funciones más usadas en nuestros cálculos
sin tener que copiar el código de un programa a otro.

4.5 Estructura de un programa o script y normas de es-


critura

Al hacer un programa es siempre conveniente guardar una cierta estructura que ayudará a enten-
derlo y corregirlo. No hay reglas definidas para ello pero una buena práctica podría ser la siguiente:
1. Primero poner los comentarios con el tipo de codificación, objeto del programa, nombre del
programador y fecha de la última modificación.
2. Después incluir las sentencias que importan los módulos que nos van a hacer falta para el
desarrollo del algoritmo solución del problema.
3. Después incluimos la definición de las funciones y procedimientos que vayamos a utilizar.
4. A continuación vendría el grueso del algoritmo o programa principal.
5. Finalmente, incluimos las órdenes de salida o de escritura de la solución del problema plan-
teado.

4.5. Estructura de un programa o script y normas de escritura 43


Curso de Computación Científica, Publicación 2016-09-15

Además de una correcta y ordenada estructura general del programa, es conveniente mantener cier-
tas buenas prácticas de codificación. Estas normas no son obligatorias, como la propia sintaxis
del lenguaje, pero conviene seguir las recomendaciones de los desarrolladores de Python. Un ejem-
plo básico para entender a lo que nos referimos es el sangrado, que como hemos visto en Python en
obligatorio, pero mientras la estructura de bloques sea correcta, a Python no le importa el número
de espacios que se usen. Pues bien, aunque a Python le da igual, la recomendación es usar cuatro
espacios (no tabuladores) para la sangrar bloques. Hay otras normas similares muy sencillas que
debemos intentar seguir, como estas:
Cuando sea posible, define variables con nombres que tengan algún sentido o que puedas identificar
fácilmente, no importa que sean más largas. Por ejemplo, en un programa podríamos escribir:

a = 10. # altura
b = 3.5 # base
print("El volumen es %.1f" % (a*b))

pero, ¿qué significan a y b? lo sabemos por el comentario (bien hecho), pero si más adelante nos
encontramos con esas variables, tendremos que recordar cual es cual. Es mejor usar nombres con
significado:

altura = 10.
base = 3.5
print("El volumen es %.1f" % (altura*base))

De hecho podemos usar el nombre para dar más información sobre la variable:

velocidad_metros_segundo = 12.5
angulo_radianes = 1.3

Las líneas de código no deben ser muy largas, como mucho 72 caracteres. Si se tiene una línea
larga, se puede cortar con una barra invertida (\) y continuar en la siguiente línea:

print("Esta es una frase muy larga, se puede cortar con una \


y seguir en la línea inferior.")

Dentro de paréntesis, corchetes o llaves, no dejar espacios inmediatamente dentro de ellos:

Sí: funcion(num[1], {pares: 2})


No: funcion( num[ 1 ], { pares: 2 } )

Justo después de coma, punto y coma y punto, separar con un espacio, para mayor claridad, pero
no antes:

Sí: print x, y; x, y = y, x
No: print x , y ; x , y = y , x

44 Capítulo 4. Programas ejecutables


Curso de Computación Científica, Publicación 2016-09-15

4.6 Ejercicios

1. Escribir una función que calcule la distancia cartesiana entre dos puntos cualesquiera de
coordenadas (𝑥1 , 𝑦1 ) y (𝑥2 , 𝑦2 ).
2. Escribir un programa que calcule la densidad media de cualquier planeta, pidiendo como
entrada su masa y su radio medio.
3. La variación de temperatura de un cuerpo a temperatura inicial 𝑇0 en un ambiente a 𝑇𝑠
cambia de la siguiente manera:

𝑇 = 𝑇𝑠 + (𝑇0 − 𝑇𝑠 ) 𝑒−𝑘𝑡

con 𝑡 en horas y siendo k un parámetro que depende del cuerpo (usemos k=0.45). Una lata
de refresco a 5ºC queda en la guantera del coche a 40ºC. ¿Qué temperatura tendrá 1, 5,
12 y 14 horas? Encuentren las horas que tendríamos que esperar para que el cuerpo esté a
0.5ºC menos que la temperatura ambiente. Definir funciones adecuadas para realizar ambos
cálculos para cualquier tiempo y cualquier diferencia de temperatura respecto al ambiente
respectivamente.
4. Crear una función dentro de un programa que calcule el resto (o residuo) de una división.
Probarlo haciendo un programa que pida dos números al usuario para dividir y devuelva el
resto usando esta función.
5. Para el cálculo de la letra del DNI se calcula el residuo 23 del número, es decir, el resto que
se obtiene de la división entera del número del DNI entre 23. El resultado será siempre un
valor entre 0 y 22 y cada uno de ellos tiene asignado una letra según la siguiente tabla:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
T R W A G M Y F P D X B N J Z S Q V H L C K E
Escribir un programa que solicite el número de DNI al usuario y calcule la letra que le
corresponde.
6. Escribir su propio módulo o librería que contenga todas las funciones que haya hecho para
resolver los problemas anteriores. Escribir un programa principal en el que las utilice y de
esta forma comprueba si funciona correctamente.

4.6. Ejercicios 45
Curso de Computación Científica, Publicación 2016-09-15

46 Capítulo 4. Programas ejecutables


CAPÍTULO 5

Control de flujo

Hemos visto que un programa o script consiste en una serie de sentencias que se ejecutan una
detrás de otra siguiendo así el flujo natural de ejecución. No obstante, puede convenirnos cambiar
esta secuencia de ejecución, de manera que nuestro programa haga una acción u otra según ciertas
condiciones. Para ello se utilizan las sentencias de control de flujo, que nos permiten interactuar
con el programa, tomar decisiones, saltar hacia adelante o hacia atrás y ejecutar sentencias en un
orden diferente al habitual. Son un elemento básico presente en cualquier lenguaje programación
y en este capítulo veremos las más importantes.

5.1 El bucle for

Realiza una misma acción o serie de acciones o sentencias varias veces. Una manera común de
usar el for es para recorrer con una variable cada uno de los elementos de una lista. Esta sería
una manera básica de usarla en un programa de Python:

for isla in ['La Gomera', 'Maui', 'Cuba', 'Sri Lanka']:


print("La isla de %s" % isla) # El sangrado es obligatorio

"""Resultado:
La isla de La Gomera
La isla de Maui
La isla de Cuba
La isla de Sri Lanka
"""

En este caso isla es una variable muda (no definida previamente) que va tomando el valor de
cada uno de los elementos de la lista que queramos de forma sucesiva; el bucle finaliza cuando se
terminen los elementos de la lista. Hay que darse cuenta de la importancia del sangrado del bucle
ya que Python reconoce que el bucle termina cuando el sangrado cambia.
Naturalmente, también es posible hacerlo definiendo antes la lista en una variable:

47
Curso de Computación Científica, Publicación 2016-09-15

islas = ['La Gomera', 'Maui', 'Cuba', 'Sri Lanka']


for isla in islas:
print("La isla de %s" % isla)

Aquí es especialmente útil la función range() que, como ya vimos, crea automáticamente una
lista de números enteros; con ella, podemos usar un bucle for para hacer cálculos con los elementos
de una lista de números; por ejemplo:

for i in range(2,12,2):
print("El cubo de %d es %d" % (i, i**3) )

"""Resultado
El cubo de 2 es 8
El cubo de 4 es 64
El cubo de 6 es 216
El cubo de 8 es 512
El cubo de 10 es 1000
"""

El mismo resultado al que llegamos al principio se puede llegar utilizando el bucle for de otra
forma. Consiste en definir una variable índice para escoger o recorrer los elementos de la
lista; en este caso, la variable índice recorrerá otra lista (con idéntico número de elementos de la
lista que se quiere recorrer) que contiene el valor de los índices; la podemos crear usando len()
dentro de range(). Veamos un ejemplo:

islas = ['La Gomera', 'Maui', 'Cuba', 'Sri Lanka']

for j in range(len(islas)):
print("%d- La isla de %s" % (j+1, islas[j]))

"""Resultado
1- La isla de La Gomera
2- La isla de Maui
3- La isla de Cuba
4- La isla de Sri Lanka
"""

Aquí, con la función range(len(islas)) hemos creado una lista nueva de tantos elemen-
tos (números enteros en este caso) como elementos tiene la lista islas, es decir, desde 0 hasta
len(islas)-1 (la longitud de la lista); después hacemos recorrer al índice j esta lista e iden-
tificamos los elementos de la otra lista con el índice apropiado islas[j]. Esta forma de utilizar
el bucle for nos permite manejar elementos de otras listas que tengan la misma longitud.
Como trabajar de esta manera con elementos de una lista y sus índices al mismo tiempo es muy
común, Python tiene la función enumerate(), que devuelve un elemento y su índice en un
bucle, sin tener que usar range(). Se usa de la siguiente manera:

48 Capítulo 5. Control de flujo


Curso de Computación Científica, Publicación 2016-09-15

for j, isla in enumerate(islas):


print("%d- La isla de %s" % (j+1, isla))

como se ve el resultado es el mismo sin tener que preocuparnos por el índice. Puesto que
enumerate() devuelve en cada ciclo dos elementos, índice y valor, hemos tenido que usar dos
variables, j e isla en lugar de una.

Nota: Además de la función range(), python también tiene un función llamada xrange() que
es prácticamente igual, pero usada en un bucle for, va creando los elementos a medida que los va
necesitando, en lugar de crearlos todos de una vez y guardarlos en memoria ( es decir, no poduce
una lista). Por eso, xrange() es mucho más rápido y eficiente que range(), especialmente
cuando trabajamos con listas de muchos números.

Los bucles for nos permiten crear nuevas listas con resultados de cálculos fácilmente, por ejem-
plo:

# Importo la función log10 del módulo math


from math import log10

b = [2.3, 4.6, 7.5, 10.]

c = [log10(x) for x in b]

print(c)

# Resultado:
# [0.36172783601759284, 0.66275783168157409, 0.87506126339170009, 1.0]

Al hacer esto hemos creado una nueva lista que contiene el logaritmo en base 10 de cada uno de
números en la lista b. Con respecto a la función logaritmo, generalmente se denota el de base 10
como acabamos de ver, mientras que la función log() calcula el logaritmo natural o de base
e. Con la función log() también se puede calcular el logaritmo de cualquier base, indicándola
como un segundo parámetro; por ejemplo, para calcular el logaritmo en base 3 de 5 haríamos
log(5,3).
Otra aplicación muy interesante del bucle ∑︀
for es la suma de series de números. Supongamos que
queremos calcular el sumatorio siguiente 10 1
𝑛=1 𝑛2 . Para ello debemos definir una variable en la
que vamos acumulando los términos del sumatorio. Abrimos un fichero para escribir el programa
que realiza este cálculo:

suma = 0.0 # Variable para ir acumulando los términos del sumatorio

print "n termino n sumatorio hasta n"


print "--------------------------------"

5.1. El bucle for 49


Curso de Computación Científica, Publicación 2016-09-15

for n in range(1,11):
term = 1/n**2.0 # calculamos el término del sumatorio
suma = term + suma # acumulamos el término en la variable suma
print("%3d %10.6f %10.6f" % (n, term, suma)) # imprimimos el
˓→resultado

"""RESULTADO QUE OBTENEMOS EN PANTALLA AL EJECUTARLO


n termino n sumatorio hasta n
--------------------------------
1 1.000000 1.000000
2 0.250000 1.250000
3 0.111111 1.361111
4 0.062500 1.423611
5 0.040000 1.463611
6 0.027778 1.491389
7 0.020408 1.511797
8 0.015625 1.527422
9 0.012346 1.539768
10 0.010000 1.549768
"""

En este ejemplo definimos una variable suma con valor inicial cero para ir acumulando en ella cada
uno de los términos de la suma, que metemos en la variable term y sumándola en cada ciclo del
bucle hasta que termina. El valor final de la suma será el que tenga la variable suma al terminar el
bucle.
Si no conocemos el número de veces que hay que ejecutar las operaciones (es decir, el número de
términos de la serie a sumar) existe otra forma de definir los bucles utilizando la sentencia while.

5.2 El bucle while

En este bucle se ejecutan una o varias operaciones mientras cierta condición que definimos sea
cierta. Por ejemplo para imprimir los números naturales del 0 al 5 podemos hacer (los números
naturales pueden definirse considerando el cero o no según el área de la ciencia):
cuentas = 0

while cuentas < 6:


print(cuentas)
cuentas = cuentas + 1

0
1
2
3

50 Capítulo 5. Control de flujo


Curso de Computación Científica, Publicación 2016-09-15

4
5

En este ejemplo se define inicialmente un valor 0 para la variable cuentas y su valor se va re-
definiendo, aumentado en 1 e imprimiéndolo. Mientras cuentas sea menor que 6 las sentencias
dentro del bucle while seguirán ejecutándose y se detendrá cuando la condición deje de cumplirse,
es decir cuando cuentas valga 6, algo que podemos comprobar después del bucle sin más que
escribir:

print(cuentas)
# Imprime 6

Nótese que, a diferencia del bucle for, en este caso debemos incrementar explícitamente el valor
de la variable que interviene en la condición, de no haber aumentado el valor de cuentas en cada
ciclo del bucle, su valor nunca cambiaría y tendríamos bucle infinito. Si esto nos ocurre podemos
parar el programa con las teclas Ctrl+C (pueda tardar un poco en responder).
Veamos otro ejemplo. Supongamos que tenemos unos ahorros en el banco (unos míseros 100
euros) y queremos saber el tiempo que nos llevará tener cierta cantidad (p.e. 500 euros) gracias
a los intereses que producen. Lo que hacemos es crear un bucle while en el que añadiremos
anualmente los intereses y vamos contando los años. Cuando lleguemos a la cantidad deseada el
bucle se detendrá y tendremos los años que nos llevaría; el código del programa podría ser este:

mis_ahorros = 100 # Partimos de 100 euros


interes = 1.05 # Interés del 5% anual
anhos = 0 # Tiempo de inicio, cuando tenemos 100
˓→euros

# Queremos llegar a tener 500 euros


while mis_ahorros < 500:
# Añado los intereses anuales a los ahorros
mis_ahorros = mis_ahorros * interes

# Añado un año a la cuenta de años ya que


# cada ciclo while equivale a un año
anhos = anhos + 1

print("Me llevará %d anhos ahorrar %d euros." % (anhos, mis_ahorros))

# Resultado que obtendremos al final del programa:


# Me llevará 33 anhos ahorrar 500 euros.

Podemos utilizar el bucle while con una condición negativa, es decir de que no se cumpla,
usando while not de la forma siguiente:

5.2. El bucle while 51


Curso de Computación Científica, Publicación 2016-09-15

Figura 5.1: Diagrama de flujo de una sentencia while.

x = 0
while not x == 5:
x = x + 1
print("x = %d" % x)

""" Resultado que obtenemos del programa:


x = 1
x = 2
x = 3
x = 4
x = 5
"""

Sin embargo, hay que tener cuidado cuando se comparan, mediante una igualdad exacta, números
decimales o de coma flotante floats entre sí. Debido a la precisión finita de los ordenadores, es
posible que una determinada igualdad nunca se cumpla exactamente y por lo tanto la ejecución
del bucle nunca se detendrá. Comprobemos este aspecto con un ejemplo en el que imprimimos los
números que van de 0.0 a 1.0 a intervalos de 0.1,:

x = 0.0
# Mientras x no sea exactamente 1.0, suma 0.1 a la variable *x*
while not x == 1.0:
x = x + 0.1
print("x = %19.17f" % x)

52 Capítulo 5. Control de flujo


Curso de Computación Científica, Publicación 2016-09-15

""" Resultado que obtenemos:


x = 0.10000000000000001
x = 0.20000000000000001
x = 0.30000000000000004
x = 0.40000000000000002
x = 0.50000000000000000
x = 0.59999999999999998
x = 0.69999999999999996
x = 0.79999999999999993
x = 0.89999999999999991
x = 0.99999999999999989 <-- El bucle while debió detenerse aquí, pero
˓→no lo hizo

x = 1.09999999999999987
x = 1.19999999999999996
x = 1.30000000000000004
.
.
. <-- Presionar Ctrl+C para detener el programa
"""

y así sucesivamente; el bucle no se para nunca. El código anterior produce un bucle que no se pa-
ra nunca porque la condición x == 1.0 nunca se da exactamente debido a la precisión limitada
de los ordenadores (el valor más cercano es 0.99999999999999989 pero no 1.0). La conclusión que
podemos extraer de aquí es que es preferible no comparar nunca variables o números de tipo
float exactamente. Una opción para resolver el problema anterior es usar intervalos de precisión,
por ejemplo:

x = 0.0
while abs(x - 1.0) > 1e-8:
x = x + 0.1
print("x = %19.17f" % x)

""" Resultado que obtenemos:


x = 0.10000000000000001
x = 0.20000000000000001
x = 0.30000000000000004
x = 0.40000000000000002
x = 0.50000000000000000
x = 0.59999999999999998
x = 0.69999999999999996
x = 0.79999999999999993
x = 0.89999999999999991
x = 0.99999999999999989
"""

de esta forma cuando x se acerque a 0.1 con una precisión igual o inferior a 1 × 10−8 se detendrá

5.2. El bucle while 53


Curso de Computación Científica, Publicación 2016-09-15

el bucle.

Advertencia: Recuerden que en Python el sangrado es obligatorio, porque se emplea para


separar los bloques que indican donde empiezan y terminan los bucles y otros condicionales.
Si el sangrado no es correcto, se obtendrá un resultado equivocado o nos devolverá un error de
sangrado.
Por otro lado, como regla general para usar el bucle for o el while podemos decir que cuando
sepamos el número de veces que deseamos calcular el bucle es mejor utilizar el for mientras
que en los demás casos es más recomendable usar el while.

5.3 Sentencias condicionadas if-else

Este conjunto de sentencias realizan una o varias operaciones si una determinada condición que
imponemos es cierta. De no cumplirse tal condición, se pueden realizar opcionalmente otras ope-
raciones o detener el proceso de cálculo. Un ejemplo de su utilización es el siguiente:

c = 12

if c>0: # comprueba si es positivo


print("La variable c es positiva")
elif c<0: # si no lo es, comprueba si es negativo
print("La variable c es negativa")
else: # Si nada de lo anterior se cumple, haz lo siguiente
print("La variable c vale 0")

En el ejemplo anterior, primero se define una variable c con valor entero 12 y luego se emplea la
sentencia if para comprobar si c es positiva, luego se usa elif para que en caso de no cumplirse
el if anterior, compruebe si c es negativa. Si no se cumple ninguna de las condiciones anteriores
y sólo en ese caso, se ejecutan las sentencias que vienen después de else. Hay que notar que en
caso de cumplirse la primera condición if, el bucle se interrumpe y el intérprete ya no continúa
comprobando las posibles condiciones elif (pueden haber varias) o else final.

Advertencia: Es muy importante darse cuenta nuevamente los bloques de sangrado o espa-
cios que separan los condicionales if-else en el ejemplo anterior. Al ejecutarse el código
y en contrarse el primer if, el intérprete de Python sabe que todo lo que viene después de
los “:” y sangrado con espacios es lo que debe ejecutarse en caso de cumplirse la condición
y esto termina cuando se encuentra con un bloque de sangrado inferior, que en este caso es la
sentencia elif.

Veamos lo anterior con un ejemplo usando sólo la sentencia if. Tenemos dos números diferentes y
sólo queremos calcular y escribir su diferencia si el primero es mayor que el segundo; el programa

54 Capítulo 5. Control de flujo


Curso de Computación Científica, Publicación 2016-09-15

Figura 5.2: Diagrama de flujo de una sentencia if-else.

podría ser:

a, b = 9.3, 12.0

if a > b:
c = a - b
print ("La variable a es mayor que b")
print ("El valor de c es %f" % c)

En este ejemplo se comprueba si a es mayor que b y en ese caso se calcula su diferencia e imprime
un mensaje. Luego se imprime el valor de c en otra sentencia, pero como esa línea está ya fuera
del bloque de identación del if, el condicional termina justo antes y esa sentencia se intentará
imprimir aunque la condición del if no se cumpla, ya que no está contenido en ella. El código
correcto sería simplemente:

a, b = 9.3, 12.0

if a > b:
c = a - b
print ("La variable a es mayor que b")
print ("El valor de c es %f" % c)

Así, el valor de c sólo imprime si el condicional se cumple.


Hay varias variantes para escribir estas sentencias que se irán viendo y aprendiendo con el tiempo.
Por ejemplo, cuando después de un condicional hay una única sentencia, ésta se puede escribir
en la misma línea sin necesidad de sangrar:

5.3. Sentencias condicionadas if-else 55


Curso de Computación Científica, Publicación 2016-09-15

if a > b: print ("La variable a es mayor que b")


else: print ("La variable a es menor o igual que b")

Evidentemente, los elementos de control de flujo pueden contener a su vez otros elementos de
flujo; por ejemplo, después de un if puede venir otra vez otro if con otras condiciones. También
es posible reproducir el comportamiento de un while combinando if y for, etc., pero como
siempre, hay que ser muy cuidadoso con los bloques de sangrado.

5.4 Declaraciones break y continue y sentencia else en


bucles

La sentencia break se puede usar para interrumpir el bloque más cercano en un bucle while
o for. De manera similar, continue continua con la siguiente iteración dentro del bucle más
próximo. La sentencia else en bucles permite ejecutar una acción cuando el bucle termina una
lista (en bucles for) o cuando la condición es falsa (con while) pero no si el bucle se interrumpe
usando break. Veamos el ejemplo de un programa que calcula los números primos entre 2 y 10:

for n in range(2, 10):


for x in range(2, n):
if n % x == 0:
print("%d es igual a %d *%d." % (n, x, n/x))
break # corto el bucle for
else:
# El bucle termina sin encontrar factor
print("%d es numero primo." % n)

""" Imprime:
2 es numero primo.
3 es numero primo.
4 es igual a 2*2.
5 es numero primo.
6 es igual a 2*3.
7 es numero primo.
8 es igual a 2*4.
9 es igual a 3*3.
"""

En este ejemplo usamos break para cortar el bucle for más interno si el if se cumple (es True)
y así evitar que muestre multiplicaciones equivalentes (e.g.: 3*4 = 4*3); podemos comprobar
lo que ocurre si no pusiésemos break. Es útil para por ejemplo, evitar que un bucle siga corriendo
si ya se consiguió la condición.
Algo muy interesante en este ejemplo es que usamos la sentencia else con for, en lugar del
usarla con if como es habitual. Un else en for se ejecuta cuando la lista en el for llega al

56 Capítulo 5. Control de flujo


Curso de Computación Científica, Publicación 2016-09-15

final, sin cortarse. De esta manera imprimimos un aviso si el bucle termina (el for agota la lista)
sin llegar a usar break, lo que indica que ningún número de la lista es múltiplo suyo y por tanto
es primo.
La declaración continue indica continuar con el siguiente elemento del bucle más interior, inte-
rrumpiendo el ciclo actual. Vámoslo con un ejemplo:

for k in range(8):
if k> 4:
print("%d es mayor que 4." % k)
continue
print("%d es menor o igual que 4." % k)

0 es menor o igual que 4.


1 es menor o igual que 4.
2 es menor o igual que 4.
3 es menor o igual que 4.
4 es menor o igual que 4.
5 es mayor que 4.
6 es mayor que 4.
7 es mayor que 4.

Es este caso, con continue evitamos que se ejecute el último print() si k>4, continuando
el bucle con el siguiente elemento de la lista. Fíjate que pudimos haber hecho un código similar
usando una sentencia if-else.

5.5 Atrapando los errores. Sentencia try-except

Hemos visto que cuando existe algún error en el código, Python detiene la ejecución y nos devuelve
una excepción o mensaje de error indicándonos qué fue lo que ocurrió. Por ejemplo, supongamos
que por algún motivo hacemos una división por cero:

In [4]: a, b = 23, 0

In [5]: a/b
-----------------------------------------------------------------------
˓→----

ZeroDivisionError Traceback (most recent call


˓→last)

/home/japp/<ipython console> in <module>()


ZeroDivisionError: integer division or modulo by zero

al hacer esto, nos avisa del error indicando el tipo en la última línea, ZeroDivisionError,
terminando la ejecución. Que Python nos dé tanta información al ocurrir una excepción es muy

5.5. Atrapando los errores. Sentencia try-except 57


Curso de Computación Científica, Publicación 2016-09-15

útil pero muy a menudo sabemos que estos errores pueden ocurrir y lo ideal es estar preparado
capturando la excepción y actuar en consecuencia en lugar de interrumpir el programa. Para hacer
esto podemos usar la sentencia try-except, que nos permite “probar” (Try) una sentencia y
capturar un eventual error y hacer algo al respecto (except) en lugar de detener el programa. En el
caso anterior podríamos hacer lo siguiente:

a, b = 23, 0
try:
a/b
except:
print("Hay un error en los valores de entrada")

Ahora el código intenta ejecutar a/b y de haber algún tipo de error imprime el mensaje indicado
y sigue adelante en lugar abortar la ejecución del programa. Al hacer esto hemos “capturado” la
excepción o error evitando que el programa se detenga, suponiendo que éste puede continuar a
pesar del error. Nótese que de esta manera no sabemos qué tipo de error ha ocurrido, que antes se
indicaba con la clave ZeroDivisionError, que es uno de los muchos tipos de errores que Pyt-
hon reconoce. Si quisiésemos saber exactamente qué tipo de error ocurrió, debemos especificarlo
en except, por ejemplo:

a, b, c = 23, 0, "A"

try:
a/b
except ZeroDivisionError:
print("Error, division por cero.")
except TypeError:
print("Error en el tipo de dato.")

# Resultado:
# Error, division por cero.

try:
a/c
except ZeroDivisionError:
print("Error, division por cero.")
except TypeError:
print("Error en el tipo de dato.")

# Resultado:
# Error en el tipo de dato.

De esta manera, sabemos exactamente qué tipo de error se cometió en cada caso, una división por
cero o un error en el tipo de dato (que es lo que indica TypeError). Naturalmente, si no ocurriese
ninguno de estos errores específicamente, Python daría un error y terminaría el programa de manera
habitual. Pueden consultar la documentación oficial de Python para tener más información sobre
la captura de excepciones y los tipos de errores reconocidos.

58 Capítulo 5. Control de flujo


Curso de Computación Científica, Publicación 2016-09-15

5.6 Una aplicación interesante: Raíces de ecuaciones al-


gebraicas cualquiera

A menudo en ciencia nos encontramos con que los modelos con los que intentamos describir la
realidad de un fenómeno natural nos lleva a tener que resolver ecuaciones polinómicas o tras-
cendentes (que no tienen solución algebraica) u otras en las que, por su complejidad, la solución
es complicada. La búsqueda de las raices de tales ecuaciones en un intervalo prefijado podemos
hacerlo con el cálculo numérico de forma sencilla utilizando lo que sabemos hasta ahora.
El método más sencillo es el llamado método del bisector que nos permite encontrar una raíz de
una función real cualquiera 𝑓 (𝑥), continua en el intervalo [𝑎, 𝑏] donde 𝑓 (𝑎) y 𝑓 (𝑏) tienen diferente
signo. Recuerden que el Teorema de Bolzano nos dice que esto es posible y lo que hay que hacer
es implementar un programa en Python para resolverlo en el caso de funciones de una sola variable
real.
El método consiste en dividir el intervalo dado [𝑎, 𝑏] por la mitad y llamemos 𝑚 al punto medio. Si
el signo de 𝑓 (𝑚) es diferente al de 𝑓 (𝑎) aplicamos otra vez el método al intervalo [𝑎, 𝑚]; si, por el
contrario, el signo de f(m) es diferente al de f(b) aplicamos otra vez el método al intervalo [𝑚, 𝑏].
El nuevo intervalo en cualquier caso es menor que el anterior y contiene la raiz buscada, por lo
tanto lo único que tenemos que hacer es repetir el método anterior tantas veces como sea necesario
hasta que 𝑓 (𝑚) = 0 o, como ya sabemos, es mejor utilizar la condición 𝑓 (𝑚) < 𝜖, donde 𝜖 es
un número tan pequeño como queramos. Este es un método iterativo, es decir, que se repite tantas
veces como sea necesario hasta que se cumpla la condición que impongamos a la solución.

5.7 Ejercicios

1. Escribir un programa que calcule el factorial de un número n entero y positivo. Es decir,


calculen:
𝑛
∏︁
𝑛! = 𝑘 para 𝑛 ≥ 0
𝑘=1

2. Crear una lista con 10 números enteros con valores distintos y arbitrarios de 0 a 100. Progra-
mar una función que encuentre el mayor de ellos y que dé su posición en la lista.
3. Utilizando la lista anterior, crear una lista nueva que incluya los números que son primos y
otra que incluya sus índices en la lista original.
4. Generar una lista con 100 números enteros aleatorios de -100 a 100, utilizando la fun-
ción randint() (escribir from numpy.random import randint y luego nums
= randint(-100,100,100). Separar en tres listas distintas los números negativos, los
positivos y los mayores de 50, pero de manera que la suma de los números de cada lista no
sea mayor que 200; es decir, vayan rellenando las listas mientras se cumpla la condición.

5.6. Una aplicación interesante: Raíces de ecuaciones algebraicas cualquiera 59


Curso de Computación Científica, Publicación 2016-09-15

5. Obtener un valor de 𝜋 calculando la suma siguiente para n=200:


𝑛
∑︁ (−1)𝑘+1
4
𝑘=1
2𝑘 − 1

6. Modificar el programa anterior para obtener el valor de 𝜋 con una precisión determinada (por
ejemplo 10−6 ) comparado con el valor más aproximado tomado del módulo math.
7. Diseñar un programa que calcule volumen de una esfera, cilindro o un cono. El programa
debe preguntar primero qué es lo que se desea calcular y luego pedir los datos necesarios
según lo que se elija.
8. Dada la lista de notas de los alumnos de una clase, decir quien ha obtenido aprobado (entre
5 y 6.9), notable (entre 7 y 8.9), sobresaliente (más de 9) o ha suspendido.
Alumnado Nota
Carolina 7.1
Enar 4.4
Patricia 9.6
Matias 5.0
Ruyman 6.7
Maria 8.3
Nayra 5.6
Suponiendo que todos los nombres de mujer terminan con “a” (lo que casualmente en este
ejemplo es cierto), decir del alumnado quien es varón o mujer.
9. Escribir un programa que calcule la suma de los elementos necesarios de la serie siguiente:
𝑛
∑︁ 1 3
= ,
𝑖=0
(𝑖 + 1)(𝑖 + 3) 4

para obtenerla con 5 cifras significativas. Como resultado dar el valor de la suma y el número
n de sumandos sumados.
10. La nota final de la asignatura de Computación Científica (p) se calcula añadiendo a la nota
del examen final (z) una ponderación de la evaluación contínua (c) a lo largo del curso de la
forma:
(10 − 0.6𝑐)
𝑝 = 0.6𝑐 + 𝑧
10
El alumno estará aprobado cuando la nota final p sea mayor o igual a cinco, siempre que
z supere un tercio de la nota máxima (z>10/3); en caso contrario, se queda con p=z. Un
grupo de alumnos ha obtenido las siguientes calificaciones en la evaluación contínua y en el
examen final:
c 8.2 0.0 9.0 5.0 8.4 7.2 5.0 9.2 4.9 7.9
z 7.1 5.1 8.8 3.1 4.6 2.0 4.1 7.4 4.4 8.8

60 Capítulo 5. Control de flujo


Curso de Computación Científica, Publicación 2016-09-15

Hacer un programa que calcule sus notas finales indicando además quién ha aprobado y
quién ha suspendido. Calcular también la nota media de la evaluación continua, del examen
final y de la nota final. En todos los resultados debe mostrarse una única cifra decimal.
11. Dada la serie geométrica cuya suma exacta es:

∑︁ 𝑎
𝑎𝑥𝑛 =
𝑛=0
1−𝑥

válida siempre que 0 < x < 1 y siendo a un número real cualquiera, escribir un programa
que calcule esta suma con diez cifras significativas solamente para cualquier valor de x y a,
comprobando que se cumple la condición necesaria para x. Dar como resultado el valor de
la suma y el número de sumandos sumados para obtenerla.
12. Se llama sucesión de Fibonacci a la colección de n números para la que el primer elemento
es cero, el segundo 1 y el resto es la suma de los dos anteriores. Por ejemplo, la sucesión
para n=5 es (0, 1, 1, 2, 3). Crear un programa que calcule la lista de números para cualquier
n.
13. Escribir un programa que proporcione el desglose en el número mínimo de billetes y mone-
das de una cantidad entera cualquiera de euros dada. Recuerden que los billetes y monedas
de uso legal disponibles hasta 1 euro son de: 500, 200, 100, 50, 20, 10, 5, 2 y 1 euros. Para
ello deben solicitar al usuario un número entero, debiendo comprobar que así se lo ofrece y
desglosar tal cantidad en el número mínimo de billetes y monedas que el programa escribirá
finalmente en pantalla.
14. Escribir un programa que pida el valor de dos números enteros n y m cualesquiera y calcule
el sumatorio de todos los números pares comprendidos entre ellos (incluyéndolos en el caso
de que sean pares). Comprobar que efectivamente los números proporcionados son enteros.
15. Crear un programa que resuelva la ecuación de segundo grado 𝑎𝑥2 +𝑏𝑥+𝑐 = 0 para cualquier
valor de a, b y c comprobando el valor del discriminante ∆ = 𝑏2 − 4𝑎𝑐.
16. Una de las formas de medir distancias en el cosmos es utilizar el llamado módulo de dis-
tancias, en el que la diferencia entre las magnitudes aparente 𝑚𝑣 (proporcional al logaritmo
del flujo recibido) y absoluta 𝑀𝑣 (proporcional al logaritmo del flujo(︀ recibido si el astro se
𝑑
)︀
encontrara a 10 parsec de distancia) de un astro es 𝑚𝑣 − 𝑀𝑣 = 5 log 10 + 𝑎𝑑, donde, 𝑑 es
la distancia en parsec y 𝑎 es el coeficiente de absorción interestelar. Lo aplicamos a un caso
concreto. En la Gran Nube de Magallanes, en 1987 se descubrió una supernova la SN1987A
que en su máximo de luminosidad alcanzó una magnitud de 𝑚𝑣 = 3. Sabiendo que este tipo
de supernovas tienen una magnitud absoluta de 𝑀𝑣 = -19.3 y que el coeficiente de absorción
interestelar es de 𝑎 = 0.8 × 10−4 , ¿a qué distancia se encuentra la galaxia?.

5.7. Ejercicios 61
Curso de Computación Científica, Publicación 2016-09-15

62 Capítulo 5. Control de flujo


CAPÍTULO 6

Probabilidad y números aleatorios

Juguemos con el azar. Si lanzamos una moneda, el resultado puede ser cara o cruz; decimos que
la probabilidad de que salga cara es de 1/2 al igual que lo es de que salga cruz. La Probabilidad
es una forma de asignar a cada resultado del experimento un valor entre cero y uno, teniendo en
cuenta que el suceso formado por todos los posibles resultados (en nuestro caso cara o cruz) tiene
una probabilidad de 1. Decimos que el resultado del lanzamiento de una moneda es una variable
aleatoria (discreta), puesto que a pesar de que sabemos que el resultado es cara o cruz, no sabemos
cuál va a ser el resultado del próximo lanzamiento. La teoría de la Probabilidad es una rama de la
matemática que trata del análisis de fenómenos aleatorios, sucesos no deterministas, resultados de
experimentos u observaciones de fenómenos naturales sujetos a “ruido” que parecen evolucionar
con el tiempo en forma aleatoria.
Desde un punto de vista matemático una “probabilidad” es una función ℘ real que actúa sobre los
elementos de un conjunto 𝐶 de tal forma que ℘(𝐶) = 1 y que para cualquier subconjunto 𝑆 ⊂ 𝐶
se tiene que 0 ≤ ℘(𝑆) ≤ 1. Además para cualquier familia 𝑆𝑖 de subconjuntos de 𝐶, disjuntos dos
a dos, se verifica que: ℘(𝑆1 ∪ 𝑆2 ∪ ... ∪ 𝑆𝑛 ) = ℘(𝑆1 ) + ℘(𝑆2 ) + ... + ℘(𝑆𝑛 ). Estas propiedades,
desde un punto de vista físico, son propiedades deseables para una función que debe describir
la probabilidad de obtener un resultado u otro, producto de un experimento u observación. Esta
función ℘ descrita hasta aquí se suele llamar también distribución de probabilidad por muchos
autores.
Sigamos jugando con el azar. Si lanzamos un dado de 6 caras, el resultado puede ser cualquiera de
ellas; decimos que la probabilidad de que salga cualquier cara es de 1/6. Por lo tanto, el resultado
del lanzamiento de un dado es un suceso aleatorio cuya probabilidad de que salga cualquier cara
es la misma. Si realizamos el experimento un número muy grande de veces (𝑛) y vemos cuantas
veces ha salido cada una de las caras obtendremos como distribución subyacente (el número de
veces que ha salido cada una de las caras) del experimento una muy parecida a la llamada dis-
tribución uniforme, en la que todas las caras (de la 1 a la 6) han salido igual número de veces
(𝑛/6). Tal distribución la definimos como un número finito de resultados conocidos con la misma
probabilidad de que suceda uno cualquiera de ellos. Por otro lado, decimos que el resultado del
lanzamiento de un dado es también una variable aleatoria.
Con Python podemos jugar a estos juegos, o más bien podemos simular la realización de este juego
utilizando un módulo que nos proporciona los llamados números aleatorios. Es decir, números

63
Curso de Computación Científica, Publicación 2016-09-15

o secuencias de números que tienen la misma probabilidad de que sucedan como resultado de
cualquier experimento o, dicho de otro modo, secuencias de resultados de un experimento o de
valores de una variable aleatoria.
Aclaremos que una secuencia de números se dice que es aleatoria cuando no se puede recono-
cer cuadros de regularidad en ella; como por ejemplo, los resultados del lanzamiento de un dado
ideal o también los dígitos del número pi. La aleatoriedad estadística no implica necesariamente
aleatoriedad “verdadera”, es decir “impredicibilidad objetiva”. La llamada pseudo-aleatoriedad es
suficiente para muchos usos y consiste en secuencias de números aleatorios que pasan suficientes
tests o pruebas de nula regularidad.
Para ello usamos el módulo random que implementa funciones generadoras de números aleatorios
para varios tipos de distribuciones de probabilidad. En nuestro caso, para una introducción al tema,
nos quedaremos con las más básicas de las que hemos expuesto algunos ejemplos antes.
Casi todas las funciones del módulo se basan en la función básica random(), que se fundamenta
en un algoritmo generador de números aleatorios (del tipo Mersenne-Twister) en coma flotante
en el intervalo [0.0, 1.0). La secuencia presenta regularidad despues de (2**19937-1) numeros
generados lo que le concede una pseudo-aleatoriedad muy cercana a la aleatoriedad objetiva; en
todo caso suficiente para nuestros usos.Para usar el módulo hay que importarlo primero:
>>> import random # importamos el módulo
>>> help(random) # obtenemos ayuda informativa sobre él

En concreto, para obtener un número aleatorio entero, entre 1 y 20 por ejemplo, utilizamos:
>>> random.randint(1,20) # obtenemos un entero aleatorio en el
˓→intervalo [1,20]

Si repetimos el comando obtendremos otro número. No obstante poco podemos hacer con un sólo
número aleatorio. En general, nos interesará simular secuencias más o menos largas de números
aleatorios. Podemos utilizar bucles (for o while) para obtener secuencias de ellos pero podemos
utilizar otras funciones más interesantes. Por ejemplo, para obtener una muestra de 10 números
aleatorios, de una población de 20 (de 0 a 19 obtenida con el conocido range), podemos hacerlo
de la forma siguiente:
>>> random.sample(range(20),10)
[1, 2, 13, 11, 12, 14, 4, 7, 16, 3]

Alternativamente podemos simular una lista de números aleatorios sin más que crear una lista y
escogerlos de ella o mezclarlos de forma aleatoria; veamos cómo:
>>> x=range(15) # obtenemos una lista ordenada con numeros del 0 al 14
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]

>>> random.choice(x) # escogemos aleatoriamente uno de ellos


9

64 Capítulo 6. Probabilidad y números aleatorios


Curso de Computación Científica, Publicación 2016-09-15

>>> random.shuffle(x) # mezclamos aleatoriamente los elementos de la


˓→lista

>>> x
[5, 7, 9, 12, 11, 6, 10, 2, 13, 14, 3, 8, 4, 1]

Obviamente si ustedes prueban estos comandos les saldrán resultados diferentes cada vez que lo
hagan ya que de esto se trata: obtener muestras de números aleatorios en los que todos ellos tienen
la misma probabilidad. Podemos comprobar que se trata de números aleatorios con una distribu-
ción uniforme sin más que hacer un histograma de la secuencia de números aleatorios creada. Un
histograma nos da la frecuencia con que hemos obtenido cada resultado de un experimento dado
y aunque aprenderemos a dibujarlo más adelante podemos entender el resultado con un ejemplo
práctico. Volvamos al juego de tirar un dado ideal; los siguientes comandos de python nos ayudarán
a ello:

>>> import random as r # importamos el módulo random como r


>>> n=60 # definimos el número de tiradas del dado
>>> dado= range(6) # creamos lista donde contamos el resultado de las
˓→tiradas

>>> for i in range(6): dado[i]=0 # ponemos la lista/contador a cero


>>> for i in range(n): # tiramos el dado n veces
res=r.randint(0,5) # obtenemos el resultado de una tirada
dado[res]= dado[res]+1 # la acumulamos y guardamos en el lugar
˓→apropiado

>>> print (dado) # escribimos el resultado de las n tiradas

Si 𝑛 es pequeño (𝑛 = 60) es muy posible que el número de veces que sale cada cara sea diferente
de 10 (distribución uniforme); podemos calcular cual es la desviación respecto a este valor para
cada cara calculando su respectivo error relativo:

>>> for i in range(6):print((dado[i]-n/6.)*100./(n/6.))

No obstante, si repetimos la operación un gran número de veces (digamos 𝑛 = 6000, por ejemplo)
el resultado para cada cara se irá aproximando a 𝑛/6. Pueden comprobarlo repitiendo la serie de
comandos anterior para un número creciente de veces (𝑛) la desviación decrece al aumentar el
número de tiradas.

6.1 Algunas distribuciones de probabilidad: la Distibu-


ción Binomial

Los datos obtenidos por la observación y experimentación en muchos campos tales como la cien-
cia, la ingeniería, la sociología, las finanzas y varios juegos de azar presentan muy diferentes dis-
tribuciones subyacentes. No obstante, la mayoría de ellas son muy similares o se aproximan bien
a alguna de las tres, especialmente importantes, tipos de distribuciones de probabilidad que son:

6.1. Algunas distribuciones de probabilidad: la Distibución Binomial 65


Curso de Computación Científica, Publicación 2016-09-15

la Binomial, la de Poisson y la Gaussiana. Todas ellas pueden derivarse y expresarse matemática-


mente usando la teoría de la probabilidad; aquí introduciremos la primera de ellas puesto que no
podemos abarcar más en este curso.
Retomemos el juego del lanzamiento de una moneda. Si repetimos el lanzamiento un gran número
de veces (digamos 𝑛) el resultado será de 𝑛/2 caras y cruces, aproximadamente. No obstante, si
𝑛 es pequeño (𝑛 = 10) es muy posible que el número de caras sea diferente de 5; la cuestión que
podemos plantearnos entonces es : si lanzamos una moneda 𝑛 veces, >cuál es la probabilidad de
que obtengamos un resultado en el que nos salgan 𝑚 caras? (obviamente 0 ≤ 𝑚 ≤ 𝑛). Puede
demostrarse que si lanzamos la moneda 𝑛 veces la probabilidad de obtener 𝑚 = 0, 1, 2, ..., 𝑛 veces
cara (o cruz) viene dada por los términos sucesivos del binomio (1/2 + 1/2)𝑛 .
Vamos a repasar algunos conceptos de combinatoria que nos ayudarán a justificar esta afirmación.

6.1.1 Combinatoria: Variaciones, Permutaciones y Combinaciones

Variaciones. Si tenemos un conjunto de 𝑛 elementos, nos podemos preguntar cuántos grupos de


𝑚 elementos se pueden formar de modo que en cada uno de ellos no se repita ningún elemento o
estén en orden diferente. La respuesta nos viene dada por las Variaciones de n elementos tomados
de m en m, y se calcula de la forma siguiente:
𝑛!
𝑉 (𝑛, 𝑚) =
(𝑛 − 𝑚)!
Podemos entender este resultado si pensamos que la primera posición de nuestro grupo puede estar
ocupada por uno cualquiera de los 𝑛 elementos, la segunda sólo por (𝑛 − 1) puesto que ya hemos
utilizado uno, la tercera por (𝑛 − 2) ya que hemos utilizado 2 y así hasta la m-ésima en la que
podemos poner uno de los (𝑛 − (𝑚 − 1)) que quedan. Recordemos que 𝑛! es el llamado factorial
de 𝑛 y consiste en multiplicar todos los números naturales desde 1 hasta n.

Nota: Ejemplo. En una carrera de campo a través en la que participan 143 atletas, ¿de cuántas
maneras se puede configurar el podio al final de la carrera?. Está claro que aquí podemos aplicar
las variaciones de 143 atletas tomados de 3 en 3, puesto que son 3 los que caben en el podio
y además el orden en el que están importa mucho. Por lo tanto la respuesta sería: 𝑉 (143, 3) =
143!/140! = 143 × 142 × 141 = 2863146.

Variaciones con Repetición. Nótese que si está permitido repetir los elementos del conjunto a
partir del cual generamos los grupos de 𝑚 elementos y también importa el orden en que estén,
entonces estamos en un caso en que el uso de un elemento en un grupo no lo elimina para estar
en otro y por tanto hablamos de Variaciones con repetición de n elementos tomados m en m, y lo
calcularemos así:

𝑉 𝑅(𝑛, 𝑚) = 𝑛𝑚

66 Capítulo 6. Probabilidad y números aleatorios


Curso de Computación Científica, Publicación 2016-09-15

Nota: Ejemplo. ¿Cuántas columnas diferentes debemos rellenar en una quiniela de fútbol para
asegurarnos un pleno de 14 aciertos?. Como pueden repetirse los resultados en diferentes partidos
se trata de un caso claro de variaciones con repetición de 3 elementos (1,X,2) tomados de 14 en
14, por lo que la solución será: 𝑉 𝑅(𝑛, 𝑚) = 𝑛𝑚 = 314 = 4782969.

Permutaciones. El problema ahora es ligeramente diferente y lo que nos preguntamos es de cuán-


tas formas podemos ordenar los 𝑛 elementos diferentes de un conjunto dado (ahora m = n). La
solución se llama Permutaciones de n elementos, que no es más que las variaciones de 𝑛 elementos
tomadas de 𝑛 en 𝑛 y por lo tanto lo calculamos como (recuerden que 0!=1):

𝑃 (𝑛) = 𝑉 (𝑛, 𝑛) = 𝑛!

Nota: Ejemplo. ¿De cuántas maneras se pueden sentar 6 amigos en sus seis asientos de una sala
de conciertos? Este es un caso claro de permutaciones de 6 elementos. Su solución es: 𝑃 (6) =
6! = 720.

Combinaciones. Supongamos que ahora estamos interesados en un problema similar al de las


variaciones pero en el que dos grupos son diferentes sólo si difieren en alguno de sus elementos.
De esta forma, se dice que las Combinaciones de n elementos tomados de m en m son todos los
subconjuntos de 𝑚 elementos que podemos formar del conjunto de 𝑛 elementos sin que importe el
orden en que elijamos a los elementos.
Un ejemplo típico son los casos en los que repetimos un experimento un numero de veces cual-
quiera y deseamos saber en cuantas de ellas hemos obtenido un resultado concreto. Se calcula
utilizando la fórmula:
𝑛!
𝐶(𝑛, 𝑚) =
𝑚!(𝑛 − 𝑚)!

esta fórmula puede interpretarse como el número de permutaciones de los 𝑛 elementos (𝑛!) divi-
dido por el número de permutaciones de los 𝑚 elementos seleccionados (𝑚!) multiplicadas por el
número de permutaciones de los restantes 𝑛 − 𝑚, ((𝑛 − 𝑚)!).

Nota: Ejemplo. ¿De cuántas formas podemos lanzar una moneda 12 veces y obtener cua-
tro cruces?. Imaginen que tenemos 12 monedas de las cuales cuatro son cruces. Además tene-
mos 12 posiciones donde colocar nuestras monedas. De cuántas formas podemos colocarlas, sa-
biendo que no distinguimos unas monedas de otras más que por el hecho de ser o no ser ca-
ras?. Tenemos 12! formas de colocarlas, pero podemos intercambiar las caras entre ellas de 4!
formas y las cruces de 8! maneras. Así que como nos dice la fórmula de las combinaciones,
12!
𝐶(12, 4) = 4!8! = 12·11·10·9
4·3·2·1
= 495.

6.1. Algunas distribuciones de probabilidad: la Distibución Binomial 67


Curso de Computación Científica, Publicación 2016-09-15

Continuando con la distribución binomial...


De forma más general Bernouilli, a finales del siglo XVII, demostró que si la probabilidad de que
obtengamos un resultado en un experimento cualquiera es 𝑝 y la de que no lo obtengamos es 𝑞 (de
tal forma que 𝑝 + 𝑞 = 1), las probabilidades de que suceda en 0, 1, 2, ..., 𝑛 veces de las 𝑛 veces que
lo intentemos, viene dado por los términos sucesivos del binomio (𝑞 + 𝑝)𝑛 , es decir:
𝑚=𝑛
𝑛(𝑛 − 1) 𝑛−2 2 𝑛(𝑛 − 1)(𝑛 − 2) 𝑛−2 2 ∑︁ 𝑛!
𝑞 𝑛 + 𝑛𝑞 𝑛−1 𝑝 + 𝑞 𝑝 + 𝑞 𝑝 + ... + 𝑝𝑛 = 𝑞 𝑚 𝑝𝑛−𝑚
2! 3! 𝑚=0
𝑚!(𝑛 − 𝑚)!

Veámoslo con un ejemplo práctico. En un experimento lanzamos 12 veces una moneda, las pro-
babilidades de obtener 0, 1, 2, 3, 4,... 12 veces cara nos viene dada por los términos sucesivos del
desarrollo de binomio ( 12 + 21 )12 , es decir:

𝑛!
℘(𝑚, 𝑛) = 𝑝𝑚 𝑞 𝑛−𝑚 𝑚 = 0, ..., 𝑛
𝑚!(𝑛 − 𝑚)!

para n=12 tenemos los diferentes términos:


1 1 12 · 11 1 1
℘𝐵 (0, 12), ℘𝐵 (1, 12), ℘𝐵 (2, 12), ..., ℘𝐵 (12, 12) = 12
, 12 12 , 12
, ..., 12
2 2 2·1 2 2
es decir,
1
= (1, 12, 66, 220, 495, 792, 924, 792, 495, 220, 66, 12, 1)
212
En la práctica para obtener una distribución como ésta es necesario que se intente en un número
muy elevado de veces.

6.2 Ejercicios

1. Verifiquen que los datos de la tabla adjunta, en la que 𝑓 denota la frecuencia con la que un su-
ceso ocurre 𝑛 veces, forma una distribución binomial y encuentren su media y su desviación
estándar.
n 0 1 2 3 4 5
f 1 10 40 80 80 32

2. Una fábrica que produce chips de memoria para portátiles, al final de su cadena de produc-
ción se examina su calidad tomando una muestra de 5 unidades cada hora. Comprobadas
muchas muestras se encuentra que el número de ellas que contienen 0, 1, 2, 3 , 4, 5 me-
morias defectuosas son 58, 32, 7, 2, 1, 0 respectivamente. Mostrar que esta distribución es
aproximadamente binomial y deducir el porcentaje de memorias que salen defectuosas.

68 Capítulo 6. Probabilidad y números aleatorios


CAPÍTULO 7

Cálculo numérico con Numpy

Ya hemos visto el potencial de cálculo que tiene un lenguaje de programación como Python cuan-
do se usan las operaciones y sentencias de programación disponibles con funciones matemáticas
estándar como las que ofrece el módulo math. Si tenemos muchos números con los que calcular,
podemos usar listas y operar entre ellas con bucles. Sin embargo, esto puede ser un trabajo labo-
rioso porque las listas de Python están pensadas para uso general y no precisamente para cálculo
numérico y por eso tienen muchas limitaciones que pronto veremos.
Afortunadamente existen módulos adicionales de Python (es decir, que no vienen con la instalación
de estándar de Python) para el cálculo numérico, que facilitan enormemente el cálculo, ofreciendo
nuevos elementos y funciones. En concreto, el módulo numpy nos ofrece un nuevo tipo de dato
llamado array (traducción del inglés: colección, formación), similar a las listas pero con propie-
dades especiales muy útiles. En este capítulo veremos cómo se trabaja con arrays y las nuevas
funcionalidades que nos ofece numpy.

7.1 Listas y arrays

Los arrays son un tipo nuevo de dato, similar a las listas, pero orientados especialmente al cálculo
numérico. En cierto modo se pueden considerar como vectores o matrices (parecidos a como se
los conoce en álgebra) y son un tipo de dato fundamental para el cálculo científico de cierto nivel
utilizando tipos de datos estructurados (conjuntos de datos).
El inconveniente principal de las listas, que es el tipo básico de dato estructurado en Python, es que
no está pensado para el cálculo matemático o numérico; veámolo con un ejemplo:
In [1]: lista = range(5) # Lista de numeros de 0 a 4

In [2]: print(lista*2)
[0, 1, 2, 3, 4, 0, 1, 2, 3, 4]

In [3]: print(lista*2.5)
-----------------------------------------------------------------------
˓→----

69
Curso de Computación Científica, Publicación 2016-09-15

TypeError Traceback (most recent call


˓→last)

/home/japp/<ipython console> in <module>()

TypeError: can't multiply sequence by non-int of type 'float'

En el ejemplo anterior vemos cómo al multiplicar una lista por un número entero, el resultado es
concatenar la lista original tantas veces como indica el número, en lugar de multiplicar cada uno
de sus elementos por este número. Peor aún, al multiplicarlo por un número no entero da un error,
al no poder crear una fracción de una lista. Si quisiéramos hacer esto, se podría resolver iterando
cada uno de los elementos de la lista con un bucle for, por ejemplo:

In [4]: lista_nueva = [i*2.5 for i in lista]


In [5]: print(lista_nueva)
[0.0, 2.5, 5.0, 7.5, 10.0]

aunque esta técnica es ineficiente y lenta, sobre todo cuando queremos evaluar funciones, polino-
mios o cualquier otra operación matemática que aparece en cualquier problema científico.
Cuando realmente queremos hacer cálculos con listas de números, debemos usar los arrays. El
módulo numpy nos da acceso a los arrays y a una enorme cantidad de métodos y funciones
aplicables a los mismos. Naturalmente, numpy incluye funciones matemáticas básicas similares
al módulo math, las completa con otras más elaboradas y además incluye algunas utilidades de
números aleatorios, ajuste lineal de funciones y otras muchas que iremos viendo más adelante y
que pueden comprobar usando la ayuda como ya sabemos.

7.2 Creando arrays

Primero debemos importar el módulo numpy en sí o bien todas sus funciones:

In [6]: import numpy # Cargar el modulo numpy, o bien


In [7]: import numpy as np # cargar el modulo numpy, llamándolo
˓→np, o bien

In [8]: from numpy import * # cargar todas funciones de numpy

Si cargamos el módulo solamente, accederemos a las funciones como numpy.array() o


np.array(), según cómo importemos el módulo; si en lugar de eso importamos todas las fun-
ciones, accederemos a ellas directamente (e.g. array()). Por comodidad usaremos por ahora esta
última opción, aunque muy a menudo veremos que usa la notación np.array(), especialmente
cuando trabajamos con varios módulos distintos.
Un array se puede crear explícitamente o a partir de una lista de la forma siguiente:

70 Capítulo 7. Cálculo numérico con Numpy


Curso de Computación Científica, Publicación 2016-09-15

In [9]: x = array([2.0, 4.6, 9.3, 1.2]) # Creacion de un array


˓→directamente

In [10]: notas = [ 9.8, 7.8, 9.9, 8.4, 6.7] # Crear un lista


In [11]: notas = array(notas) # y convertir la lista a
˓→array

Existen métodos para crear arrays automáticamente:

In [12]: numeros = arange(10.) # Array de numeros(floats) de 0 a


˓→9

In [13]: print(numeros)
[ 0. 1. 2. 3. 4. 5. 6. 7. 8. 9.]

In [14]: lista_ceros = zeros(10) # Array de 10 ceros


˓→(floats)

In [15]: print(lista_ceros)
[ 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]

In [16]: lista_unos = ones(10) # Array de 10 unos


˓→(floats)

In [17]: print(lista_unos)
[ 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]

In [18]: otra_lista = linspace(0,30,8) # Array de 8 números, de


˓→0 a 30 ambos incluidos

In [19]: print(otra_lista)
[ 0. 4.28571429 8.57142857 12.85714286 17.14285714
21.42857143 25.71428571 30. ]

7.3 Indexado de arrays

Los arrays se indexan prácticamente igual que las listas y las cadenas de texto; veamos algunos
ejemplos:

In [18]: print(numeros[3:8]) # Elementos desde el tercero al


˓→septimo

[ 3. 4. 5. 6. 7.]

In [19]: print(numeros[:4]) # Elementos desde el primero al


˓→cuarto

[ 0. 1. 2. 3.]

In [20]: print(numeros[5:]) # Elementos desde el quinto al


˓→final

7.3. Indexado de arrays 71


Curso de Computación Científica, Publicación 2016-09-15

[ 5. 6. 7. 8. 9.]

In [21]: print(numeros[-3]) # El antepenúltimo elemento


˓→(devuelve un elemento, no un array)

7.

In [24]: print(numeros[:]) # Todo el array, equivalente a


˓→print(numeros)

[ 0. 1. 2. 3. 4. 5. 6. 7. 8. 9.]

In [25]: print(numeros[2:8:2]) # Elementos del segundo al


˓→septimo, pero saltando de dos en dos

[ 2. 4. 6.]

7.4 Algunas propiedades de los arrays

Al igual que las listas, podemos ver el tamaño de un array unidimensional con len(), aunque la
manera correcta de conocer la forma de un array es usando el método shape():
In [28]: print(len(numeros))
10
In [29]: print(numeros.shape)
(10,)

Nótese que el resultado del método shape() es una tupla, en este caso con un solo elemento ya
que el array numeros es unidimensional.
Si creamos un array con arange() usando un número entero, el array que se creará será de
enteros. Es posible cambiar todo el array a otro tipo de dato (como a float) usando el método
astype():
In [31]: enteros = arange(6)

In [32]: print(enteros)
[0 1 2 3 4 5]

In [33]: type(enteros)
Out[33]: <type 'numpy.ndarray'>

In [34]: type(enteros[0])
Out[34]: <type 'numpy.int32'>

In [35]: decimales = enteros.astype('float')

In [36]: type(decimales)

72 Capítulo 7. Cálculo numérico con Numpy


Curso de Computación Científica, Publicación 2016-09-15

Out[36]: <type 'numpy.ndarray'>

In [37]: type(decimales[0])
Out[37]: <type 'numpy.float64'> # En algunos ordenadores puede dar
˓→float32

In [38]: print(decimales)
[ 0. 1. 2. 3. 4. 5.]

In [38]: print(decimales.shape) # Forma o tamaño del array


(6,)

7.5 Operaciones con arrays

Los arrays permiten hacer operaciones aritméticas básicas entre ellos en la forma que uno esperaría
que se hicieran, es decir, haciéndolo elemento a elemento; para ello siempre ambos arrays deben
tener la misma longitud; por ejemplo:
In [39]: x = array([5.6, 7.3, 7.7, 2.3, 4.2, 9.2])

In [40]: print(x+decimales)
[ 5.6 8.3 9.7 5.3 8.2 14.2]

In [41]: print(x*decimales)
[ 0. 7.3 15.4 6.9 16.8 46. ]

In [42]: print(x/decimales)
[ Inf 7.3 3.85 0.76666667 1.05 1.84]

Como podemos apreciar las operaciones se hacen elemento a elemento, por lo que ambas deben
tener la misma forma (shape()). Fíjense que en la división el resultado del primer elemento es
indefinido/infinito (Inf) debido a la división por cero.
Varios arrays se pueden unir con el método concatenate(), que también se puede usar para
añadir elementos nuevos:
In [44]: z = concatenate((x, decimales))

In [45]: print(z)
[ 5.6 7.3 7.7 2.3 4.2 9.2 0. 1. 2. 3. 4. 5. ]

In [46]: z = concatenate((x,[7]))

In [47]: print(z)
[ 5.6 7.3 7.7 2.3 4.2 9.2 7. ]

7.5. Operaciones con arrays 73


Curso de Computación Científica, Publicación 2016-09-15

Es muy importante fijarse que los arrays o listas a unir deben darse como una tupla y de ahí los
elementos entre paréntesis como (x,[7]) o (x,[2,4,7]) o (x,array([2,4,7])).
Para añadir elementos, numpy tiene las funciones insert() y append(), que funcionan de
manera similar a sus equivalentes en listas, pero en este caso son funciones y no métodos que se
aplican a un array, si no que el array en cuestión hay que darlo como parámetro:

# Añadimos el elemento 100 al array z, al final


In [55]: z = append(z, 100)
In [56]: print(z)
[ 5.6 7.3 7.7 2.3 4.2 9.2 7. 100. ]

# Añadimos el elemento 200 al array z, en el tercer puesto (índice 2)


In [57]: z = insert(z, 2, 200)
In [58]: print(z)
[ 5.6 7.3 200. 7.7 2.3 4.2 9.2 7. 100. ]

Como se ve, a diferencia de las listas, el primer parámetro es el array y luego el elemento que se
quiere añadir, en el caso de append() y el array, la posición y luego elemento a añadir en el caso
de append(). Si en lugar de un elemento a insertar se da una lista y otro array, añade todos los
elementos de la lista (a append() habría que dar también una lista de posiciones, como segundo
parámetro). También hay que notar que estas dos funciones no cambian el array original y por eso
en este ejemplo redefinimos z con el array ampliado.
Además de las operaciones aritméticas básicas, los arrays de numpy tienen métodos o funciones
específicas para ellas más avanzadas. Algunas de ellas son las siguientes:

In [5]: z.max() # Valor máximo de los elementos del array


Out[5]: 9.1999999999999993

In [6]: z.min() # Valor mínimo de los elementos del array


Out[6]: 2.2999999999999998

In [7]: z.mean() # Valor medio de los elementos del array


Out[7]: 6.1857142857142851

In [8]: z.std() # Desviación típica de los elementos del array


Out[8]: 2.1603098795103919

In [9]: z.sum() # Suma de todos los elementos del array


Out[9]: 43.299999999999997

In [16]: median(z) # Mediana de los elementos del array


Out[16]: 7.0

Los métodos, que se operan de la forma z.sum() también pueden usarse como funciones de tipo

74 Capítulo 7. Cálculo numérico con Numpy


Curso de Computación Científica, Publicación 2016-09-15

sum(z), etc. Consulten el manual de numpy para conocer otras propiedades y métodos de los
arrays o simplemente acudan y consulten la “ayuda” de las funciones que quieran utilizar.
Una gran utilidad de los arrays es la posibilidad de usarlos con datos booleanos (True o False) y
operar entre ellos o incluso mezclados con arrays con números. Veamos algunos ejemplos:
In [19]: A = array([True, False, True])
In [20]: B = array([False, False, True])

In [22]: A*B
Out[22]: array([False, False, True], dtype=bool)

In [29]: C = array([1, 2, 3])

In [30]: A*C
Out[30]: array([1, 0, 3])

In [31]: B*C
Out[31]: array([0, 0, 3])

En este ejemplo vemos cómo al multiplicar dos arrays booleanos es resultado es otro array boo-
leano con el resultado que corresponda, pero al multiplicar los arrays booleanos con arrays nu-
méricos, el resultado es un array numérico con los mismos elementos, pero con los elementos que
fueron multiplicados por False iguales a cero.
Tambíén es posible usar los arrays como índices de otro array y como índices se pueden usar
arrays numéricos o booleanos. El resultado será en este caso un array con los elementos que se
indique en el array de índices numérico o los elementos correspondientes a True en caso de usar
un array de índices booleano. Veámoslo con un ejemplo:
# Array con enteros de 0 a 9
In [37]: mi_array = arange(0,100,10)

# Array de índices numericos con numeros de 0-9 de 2 en 2


In [38]: indices1 = arange(0,10,2)

# Array de índices booleanos


In [39]: indices2 = array([False, True, True, False, False, True,
˓→False, False, True, True])

In [40]: print(mi_array)
[ 0 10 20 30 40 50 60 70 80 90]

In [43]: print(mi_array[indices1])
[ 0 20 40 60 80]

In [44]: print(mi_array[indices2])
[10 20 50 80 90]

7.5. Operaciones con arrays 75


Curso de Computación Científica, Publicación 2016-09-15

También es muy sencillo crear arrays booleanos usando operadores lógicos y luego usalos como
índices, por ejemplo:
# Creamos un array usando un operador booleano
In [50]: mayores50 = mi_array > 50

In [51]: print(mayores50)
[False False False False False False True True True True]

# Lo utilizamos como índices para seleccionar los que cumplen esa


˓→condición

In [52]: print(mi_array[mayores50])
[60 70 80 90]

7.6 Arrays multidimensionales

Hasta ahora sólo hemos trabajado con arrays con una sola dimensión, pero numpy permite tra-
bajar con arrays de más dimensiones. Un array de dos dimensiones podría ser por ejemplo un
array que tuviera como elementos un sistema de ecuaciones o una imagen. Para crearlos podemos
hacerlo declarándolos directamente o mediante funciones como zero() o ones() dando como
parámetro una tupla con la forma del array final que queramos; o también usando arange() y
crear un array unidimensional y luego cambiar su forma. Veamos algunos ejemplos:
# Array de 3 filas y tres columnas, creado implícitamente
In [56]: arr0 = array([[10,20,30],[9, 99, 999],[0, 2, 3]])
In [57]: print(arr0)
[[ 10 20 30]
[ 9 99 999]
[ 0 2 3]]

# Array de ceros con 2 filas y 3 columnas


In [57]: arr1 = zeros((2,3))
In [59]: print(arr1)
[[ 0. 0. 0.]
[ 0. 0. 0.]]

# Array de unos con 4 filas y una columna


In [62]: arr2 = ones((4,1))
In [63]: print(arr2)
[[ 1.]
[ 1.]
[ 1.]
[ 1.]]

# Array unidimensional de 9 elementos y cambio su forma a 3x3

76 Capítulo 7. Cálculo numérico con Numpy


Curso de Computación Científica, Publicación 2016-09-15

In [64]: arr3 = arange(9).reshape((3,3))


In [65]: print(arr3)
[[0 1 2]
[3 4 5]
[6 7 8]]

In [69]: arr2.shape
Out[69]: (4, 1)

Como vemos en la última línea, la forma o shape() de los arrays se sigue dando como una tupla,
con la dimensión de cada eje separado por comas; en ese caso la primera dimensión son las cuatro
filas y la segunda dimensión o eje es una columna. Es por eso que al usar las funciones zero(),
ones(), reshape(), etc. hay que asegurarse que el parámetro de entrada es una tupla con la
longitud de cada eje. Cuando usamos la función len() en un array bidimensional, el resultado es
la longitud del primer eje o dimensión, es decir, len(arr2) es 4.
El acceso a los elementos es el habitual, pero ahora hay que tener en cuenta el eje al que nos
referimos; además podemos utilizar ”:” como comodín para referirnos a todo el eje. Por ejemplo:

# Primer elemento de la primera fila y primera columna (0,0)


In [86]: arr0[0,0]
Out[86]: 10
# Primera columna
In [87]: arr0[:,0]
Out[87]: array([10, 9, 0])
# Primera fila
In [88]: arr0[0,:]
Out[88]: array([10, 20, 30])
# Elementos 0 y 1 de la primera fila
In [89]: arr0[0,:2]
Out[89]: array([10, 20])

Igualmente podemos manipular un array bidimensional usando sus índices:

# Asigno el primer elemento a 88


In [91]: arr0[0,0] = 88
# Asigno elementos 0 y 1 de la segunda fila
In [92]: arr0[1,:2] = [50,60]
# Multiplico por 10 la última fila
In [93]: arr0[-1,:] = arr0[-1,:]*10

In [94]: print(arr0)
array([[ 88, 20, 30],
[ 50, 60, 999],
[ 0, 20, 30]])

7.6. Arrays multidimensionales 77


Curso de Computación Científica, Publicación 2016-09-15

7.7 Ejercicios

1. Crea un array de 100 números aleatorios con valores de -100 a +100 como hicimos en el
ejercicio 5 del Capítulo 5. Usando arrays de booleanos crea otros arrays que contengan los
positivos, los negativos y los mayores de 50 de ese array.
2. Resuelve el ejercicio 11 del capítulo 5 sobre el cálculo de la nota de la asignatura usando
arrays de numpy e ignorando la condición de z>10/3. ¿Podrías hacerlo también de manera
que sí lo tenga en cuenta? Para ello podrías usar arrays de booleanos.
3. Crea un array bidimensional 5x5 con todos los valores cero. Usando el indexado de arrays,
asigna 1 a todos los elementos de la última fila y 5 a todos los elementos de la primera
columna. Finalmente, asigna el valor 100 a todos los elementos del subarray central 3x3 de
la matriz de 5x5.
4. Para el ejercicio 4 del Apéndice C sobre los diámetros de las esporas del lycopodium, calcu-
lar, usando funciones de numpy, el diámetro medio de las esporas y la desviación estándar
de la muestra. Separar, en arrays independientes, las medidas de los diámetros:
Que tengan valores inferiores a la media menos la desviación estándar, es decir 𝑑 <
𝑑 − 𝜎𝑑
Que tengan valores superiores a la media más la desviación estándar, es decir 𝑑 > 𝑑+𝜎𝑑
Que tengan valores entre 𝑑 − 𝜎𝑑 < 𝑑 < 𝑑 + 𝜎𝑑

78 Capítulo 7. Cálculo numérico con Numpy


CAPÍTULO 8

Análisis estadístico de datos experimentales

8.1 Estadística y parámetros estadísticos

La Estadística es la rama de las Matemáticas que trata del estudio de fenómenos de los que no se
tiene toda la información completa y se diferencia del resto porque usa el concepto de Probabili-
dad, que es una medida de la posibilidad de que algo ocurra o de que algo sea correcto como ya
hemos visto.
En el estudio de la naturaleza, en su observación y medida, nos encontramos con estas situaciones
continuamente. Por ejemplo, ¿cómo describiríamos el comportamiento del Sol?; si nos fijamos
que en el Sol hay alrededor de 𝑁 ≈ 𝑀⊙ /𝑚𝐻 ≈ 1057 partículas, nos damos cuenta de que no
podemos describir el estado de movimiento de cada una de ellas en cada instante de tiempo; serán
conceptos y procedimientos de la Física Estadística los que nos ayudarán a describirlo. Por otro
lado, cualquier observación y/o medida de cualquier magnitud en un experimento de laboratorio
nunca coincide exactamente con otra realizada un instante antes o después debido al “ruido” entre
otros factores; el conjunto de medidas obtenidas forman un ejemplo de aplicación del análisis
estadístico de datos.
Para el análisis de un conjunto de datos desde el punto de vista estadístico, hay que poder compri-
mir o representar la información contenida en ellos en una serie de parámetros o indicadores que
nos den una idea de como se comporta el conjunto. Estos números se llaman parámetros esta-
dísticos y entre los más conocidos están: un parámetro de posición, la media, y otro de “amonto-
namiento”, la dispersión. Por supuesto, estos parámetros estadísticos serán tanto mejores cuanto
mejor representen a todo el conjunto de datos. Hay dos condiciones básicas para que representen
bien al conjunto: la ausencia de “sesgos” por un lado y la “robustez” por otro; al final del capítulo
nos referiremos a ellos.

8.2 La distribución de datos subyacente

Cada vez que realizamos un experimento o una observación de un fenómeno natural, estamos de
algún modo “preguntando” cual es el valor exacto o verdadero de una cierta cantidad. Esta actitud

79
Curso de Computación Científica, Publicación 2016-09-15

presupone la idea que tenemos de que tal valor existe y que además puede ser medido; no es
trivial darse cuenta de que tal cosa no es cierta. No obstante, vamos a suponer que la cantidad que
deseamos medir tiene un valor y que además podemos obtenerlo, aunque sea de modo aproximado,
a través de nuestras medidas. Cuán buena será nuestra aproximación y cuánto trabajo nos costará
obtenerlo y mejorarlo es, de hecho, el objetivo principal de la estadística aplicada a la Física.
Supongamos que hemos realizado un conjunto de 𝑁 medidas de una determinada cantidad 𝑥 y
que las representamos por {𝑥𝑖 } (𝑖 = 1, 2, ..., 𝑁 ). Esperamos que las diferentes medidas realizadas
𝑥𝑖 , se irán acumulando alrededor de un valor que esperamos que coincida con el valor real de la
cantidad medida. Tal distribución de valores que podemos representar mediante un histograma 1
que según añadamos más medidas será cada vez más suave de forma que, en el límite (un número
infinito de medidas), reproducirá una distribución de probabilidad, que llamaremos distribución
subyacente. Nuestro objetivo será aprender lo máximo posible acerca de ella a partir de nuestro
limitado conjunto de datos.

8.2.1 Medidas de posición: media, mediana y moda

El primer parámetro que nos gustaría tener es un valor (aunque sea aproximado) al valor verda-
dero de la cantidad medida, lo que llamamos el valor medio. Comenzaremos por definir la media
aritmética (o simplemente media) como:
∑︀
𝑥𝑖
𝑥= 𝑖
𝑁
Esta definición coincide con la que habitualmente se usa en el lenguaje común para mencionar
el valor medio o promedio de una serie de cantidades. En numpy de Python podemos utilizar
mean() para calcularla.
De modo similar, se define la media geométrica utilizando la multiplicación en vez de la suma, es
decir:

𝑥 = (Π𝑖 𝑥𝑖 )(1/𝑁 ) ,

esta definición resulta útil cuando la incertidumbre en nuestro problema actúa de forma multipli-
cativa en vez de aditiva. Se usa, por ejemplo, cuando queremos promediar porcentajes o casos en
que la variable muestra valores acumulativos. De hecho, son muy pocas las veces en las que esta
medida resulta útil en la práctica. Para calcularla en python hay que usar la función gmean() del
módulo stats de scipy.
Otra medida de posición interesante es la llamada mediana. Se define como el valor de la variable
en la distribución tal que la mitad de los valores presentes en el conjunto son mayores que ella
y la otra mitad restante son menores. La mediana 𝑥𝑚𝑒𝑑 , cumple que 𝑛(𝑥 < 𝑥𝑚𝑒𝑑 ) = 𝑁/2 =
𝑛(𝑥 > 𝑥𝑚𝑒𝑑 ). Su utilidad principal, como podemos comprobar, es que es un indicador más fiable
que la media en el caso de tener algunos valores en la distribución muy alejados de la media o
1
El histograma es una representación gráfica del conjunto de medidas. Se dibuja el número de medidas (eje OY)
que caen en un cierto intervalo de variación dado frente a estos intervalos (eje OX).

80 Capítulo 8. Análisis estadístico de datos experimentales


Curso de Computación Científica, Publicación 2016-09-15

que realmente son erróneos o que no pertenecen a la distribución que nos gustaría encontrar. En
numpy de Python podemos utilizar median() para calcularla.
Finalmente, otra medida de posición es la moda de la distribución que es el valor que más veces se
observa en la misma; su utilidad en ciencia es realmente escasa.

8.2.2 Medidas de dispersión: desviación estándar y varianza

La segunda cuestión que nos interesa conocer es cómo se distribuyen las medidas del conjunto
alrededor de la media, por dos buenas razones:
1. por cuestiones de predicción; por ejemplo, en el caso de que queramos montar otro experi-
mento sabremos el rango de variación de las medidas, o
2. porque nos da una idea de la incertidumbre o del error más probable de nuestras medidas.
Aunque se pueden definir varios indicadores de distancia, o medidas de dispersión, entre cada uno
de los elementos del conjunto de medidas y el valor medio, utilizaremos el más apropiado tanto
matemáticamente como desde el punto de vista físico. Se define la varianza, el cuadrado de la
desviación estándar, como:

(𝑥𝑖 − 𝑥)2
∑︀
2
𝜎 = 𝑖
𝑁
donde tomamos como valor real de la cantidad medida su valor medio. Por cierto, pueden demos-
trar, sin demasiado problema, que también puede calcularse como 𝜎 2 = 𝑥2 − 𝑥2 ; de hecho, esta es
la definición más utilizada para su cálculo. En numpy de Python podemos utilizar las funciones
var() y std() para calcularlas.
El hecho de utilizar como valor real el valor medio de la distribución subyacente, hace que al
utilizar la 𝜎 como medida de dispersión haga que ésta sea mínima. Se puede demostrar (aunque
esta vez no es fácil quedando más allá de este curso hacerlo) que el denominador en la ecuación
anterior debe cambiar de 𝑁 a (𝑁 − 1). Nótese que a medida que 𝑁 aumenta, el error relativo al
utilizar una u otra definición decrece.

8.2.3 Población, muestra y error estándar de la media

En las medidas de fenómenos naturales es muy común encontrarse con que tales medidas siguen
una distribución subyacente muy parecida a la conocida como distribución gaussiana, de tal suer-
te que suele llamársela también distribución normal (ver Apéndice C). Aunque fue obtenida pri-
mero por Demoivre en 1733 cuando estudiaba un problema de lanzamiento de monedas, fue más
tarde obtenida independientemente por Laplace y por Gauss; de ahí lo de distribución gaussiana,
y fue ampliamente aplicada inicialmente a los errores accidentales (o aleatorios) en medidas astro-
nómicas y de otros experimentos científicos. La forma de una distribución gaussiana, es decir su
histograma, es la famosa “Campana de Gauss”.

8.2. La distribución de datos subyacente 81


Curso de Computación Científica, Publicación 2016-09-15

Por otro lado, es bastante usual ver a un conjunto de observaciones o resultados de cualquier ex-
perimento como una muestra finita del conjunto infinito, o mucho más grande que podríamos
obtener si repirtiéramos el experimento indefinidamente, que se conoce como población. Como
esto no es posible, lo más que obtenemos son diferentes conjuntos de medidas de un mismo expe-
rimento que serán muestras finitas. Cada una de ellas las podremos representar por su media y su
desviación estándar que, en general no coincidirán.
No obstante, puede demostrarse matemáticamente que la distribución de medias y la de desviacio-
nes estándar también es una distribución gaussiana. Su media
√ es igual a la media verdadera de toda
la población mientras que su desviación estándar es 𝜎/ 𝑛 donde 𝑛 es el número de datos de cada
muestra y 𝜎 la desviación estándar de toda la población.
Por lo tanto, cuando trabajamos con un conjunto de datos cuya distribución subyacente es una
distribución normal de media 𝑥 y desviación estándar 𝜎,√
es apropiado escribir el resultado final del
experimento u observación como: 𝑥 ± 𝜖, donde 𝜖 = 𝜎/ 𝑛, que es el llamado error estándar de
la media.

8.2.4 Introducción de pesos. Media ponderada

Cuando se realizan medidas repetidas de una misma cantidad es común encontrar que algunas de
ellas, por una razón u otra, son más fiables que otras. Por ejemplo, en Astronomía es corriente
utilizar observaciones realizadas por diferentes observadores, en diferentes instantes de tiempo,
en diferentes observatorios y a menudo con diferentes instrumentos; si queremos utilizarlas todas
es interesante disponer de un método que permita promediarlas, dándoles a cada una de ellas
una influencia proporcional a la medida de su fiabilidad. Ello se consigue introduciendo pesos
asociados a cada medida; es decir, que tenemos un conjunto de medidas {𝑥𝑖 } cada una de ellas con
un peso estadístico asociado, con lo que tendremos finalmente un conjunto de pesos {𝑤𝑖 }. En este
caso, para calcular la media, que se suele llamar media ponderada, hacemos:
∑︀
𝑤𝑖 𝑥𝑖
𝑥 = ∑︀𝑖 (8.1)
𝑖 𝑤𝑖

Con numpy podemos utilizar average() para calcularla y análogamente, siguiendo el apartado
anterior, podemos calcular su varianza usando:

𝑤𝑖 (𝑥𝑖 − 𝑥)2
∑︀
2
𝜎 = 𝑖 ∑︀ (8.2)
𝑖 𝑤𝑖

En el caso de medidas experimentales, una elección habitual para los pesos es utilizar valores in-
versamente proporcionales al cuadrado de los errores de cada medida, cuando éstas son conocidas
o pueden ser estimadas, es decir, 𝑤𝑖 = 1/𝜖2𝑖 . La varianza asociada a este promedio la calculamos
sustituyendo apropiadamente en la relación anterior.

(𝑥𝑖 − 𝑥)2 /𝜖2𝑖


∑︀
2
𝜎 = 𝑖 ∑︀ 2
(8.3)
𝑖 1/𝜖𝑖

82 Capítulo 8. Análisis estadístico de datos experimentales


Curso de Computación Científica, Publicación 2016-09-15

8.3 Sesgo y robustez

En palabras llanas nos gustaría que el conjunto de nuestras medidas tuvieran las cualidades si-
guientes: que den el resultado que “queremos” y que no tuviera valores erróneos.
La primera cualidad exigiría que nuestro conjunto de medidas nos dé un valor medio que se aproxi-
me de forma asintótica al valor de la media de la distribución subyacente al hacer muchas medidas.
Un parámetro que cumple esta condición se dice que no tiene sesgos, es decir, que no contiene erro-
res sistemáticos. Ejemplos de ellos serían: límites en la calibración de instrumentos, condiciones
ambientales en el que se desarrollan los experimentos, errores humanos entre otros.
En cuanto a la segunda cualidad, se exigiría que el parámetro de posición utilizado (media, por
ejemplo) no varíe sensiblemente si quitamos una o unas pocas medidas cualesquiera del conjunto.
Si esto sucede, se dice que la media o el parámetro que sea es robusta. Pueden comprobar por
ejemplo, a partir de las definiciones, que la mediana es un parámetro de posición más robusto que
la media.
En general, obtener medidas robustas y sin sesgos es lo que estamos buscando siempre y es impo-
sible de obtener. No obstante, siempre podemos mejorar las observaciones, los instrumentos y los
experimentos de tal forma que minimicemos los sesgos posibles y dotemos de mayor robustez a la
medida final. La mejora en estos aspectos es el objetivo de cualquier científico y es lo que permite
avanzar a la ciencia.

8.4 Ejercicios

1. Comprobar que para un conjunto de datos de media 𝑥 y varianza 𝜎 2 , ésta última también a
podemos calcular como 𝜎 2 = 𝑥2 − 𝑥2 .
2. El valor medio de un conjunto de 100 observaciones es 2.96 y la desviación estándar es 0.12
. Hacemos otras 50 observaciones cuya media es de 2.93 y su desviación estándar es 0.16.
Encuentren la media y la varianza del conjunto de las 150 observaciones.
3. Comprobar de qué forma podemos calcular también la media y la varianza de una distribu-
ción de datos con sus correspondientes pesos (𝑤𝑖 = 1/𝜎𝑖2 ) en los casos en que: (a) Todos los
errores son iguales, y (b) una sola de las medidas tiene una precisión mucho mejor que las
demás.
4. Escribir un programa que calcule las medias aritmética, aritmética ponderada y geométrica
para la lista de números 34.4, 30.1, 29.8, 33.5, 30.9, 31.1; ponderada con los pesos 0.9, 0.79,
0.84, 0.6, 0.88, 0.78.
5. Crear un programa que calcule la varianza y la desviación estándar de los elementos (núme-
ros) de la misma lista del problema anterior.
6. En observaciones de dispersión de electrones, en el detector se mide la intensidad relativa
del haz a diferentes radios de curvatura obteniendo los datos de la tabla adjunta. Encontrar

8.3. Sesgo y robustez 83


Curso de Computación Científica, Publicación 2016-09-15

el radio de curvatura medio y su desviación estándar.


Radio Curvatura (cm) 5.4 5.5 5.6 5.7 5.8 5.9 6.0
Intensidad Relativa 0 11 36.5 40.5 31 9.5 0

84 Capítulo 8. Análisis estadístico de datos experimentales


CAPÍTULO 9

Lectura y escritura de ficheros

Muy a menudo tenemos datos iniciales para un cálculo o medidas de un experimento en un fichero
(de texto). Para poder manipular estos datos y calcular con ellos debemos aprender a leerlos como
números o arrays. Igualmente, el resultado de un cálculo o un análisis es necesario volcarlo a un
fichero de texto en lugar de mostrarlo por pantalla para conservar el resultado. Esto es especial-
mente necesario cuando los resultados son arrays largos o cuando tenemos que procesar un gran
número de ficheros. Vamos a ver cómo leer y escribir ficheros de texto, es decir letras y números y
signos de puntuación, con Python.

9.1 Creando un fichero sencillo

El primer paso para manipular un fichero, ya sea para crearlo, leerlo o escribir en él es crear lo que
en Python se llama una instancia a ese fichero; una instancia es una llamada o referencia, en este
caso a un fichero, a la que se le asigna un nombre. Esto consiste simplemente en “abrir” el fichero
en modo de lectura, escritura, para añadir o una combinación de estos, según lo que necesitemos:

In [48]: fichero_leer = open('mi_fichero.txt', 'r')


In [48]: fichero_escribir = open('mi_fichero.txt', 'w')
In [48]: fichero_escribir = open('mi_fichero.txt', 'a')
In [48]: fichero_leer = open('mi_fichero.txt', 'rw')

En los ejemplos anteriores se ha abierto un fichero de varias maneras posibles, donde hemos indi-
cado en el primer parámetro el nombre del fichero y el segundo el modo de apertura:

'r' -> Abre el fichero mi_fichero.txt para leer (debe existir


˓→previamente)

'w' -> Abre el fichero mi_fichero.txt para escribir. Si no


existe lo crea y si existe sobrescribirá el contenido que
˓→tenga.

'a' -> Abre el fichero mi_fichero.txt para añadir texto. Si no

85
Curso de Computación Científica, Publicación 2016-09-15

existe lo crea y si existe continua escribiendo al final del


fichero.
˓→

'rw' -> Abre el fichero mi_fichero.txt para leer y escribir

Vamos a crear un fichero y escribir algo en él. Lo primero es abrir el fichero en modo escritura:

In [49]: fs = open('prueba.txt', 'w')


In [50]: type(fs)
Out[50]: <type 'file'>

aquí la variable fs es una instancia o llamada al fichero. A partir de ahora cualquier operación
que se haga en el fichero se hace utilizando esta instancia fs y no el nombre del fichero en sí.
Escribamos ahora algo de contenido, para esto se emplea el método write() a la instancia del
fichero:

In [51]: fs.write('Hola, estoy escribiendo un texto a un fichero')


In [52]: fs.write('Y esta es otra linea')
In [53]: fs.write( str(exp(10)) )
In [54]: fs.close() # Cerramos el fichero

Al teminar de trabajar con el fichero debemos cerrarlo con el método close(), es aquí cuando
realmente se escribe el fichero y no hay que olvidar cerrarlo siempre al terminar de trabajar con él.
Una vez cerrado, se puede abrir con un editor de textos cualquiera como Kate o gEdit. Veremos
que cada orden de escritura se ha hecho consecutivamente y no línea a línea. Si queremos añadir
líneas nuevas debemos ponerlas explícitamente con \n que es el código ASCII para el “intro” o el
“return”, es decir para generar una nueva línea:

In [55]: fs = open('prueba.txt', 'a')


In [56]: fs.write("\n\n") # Dejo dos lineas en blanco
In [57]: fs.write("Esta es una linea nueva\n")
In [58]: fs.write("Y esta es otra linea\n")
In [59]: fs.close()

De igual manera podemos usar un bucle for para escribir una lista de datos:

In [60]: from math import *


In [61]: fsalida = open('datos.txt', 'w')
In [62]: for i in range(100):
....: fsalida.write('%d %10.4f\n' % (i, exp(i)))
In [63]: fsalida.close()

De esta forma creamos un fichero de nombre datos.txt en el que hay escrito, a dos columnas, los
cien primeros números enteros positivos y su exponencial (con 10 caracteres en total y cuatro
decimales). Podemos ver el contenido de este fichero con cualquier editor de texto o desde una
consola de linux usando cat como ya sabemos.

86 Capítulo 9. Lectura y escritura de ficheros


Curso de Computación Científica, Publicación 2016-09-15

9.2 Lectura de ficheros

Ahora podemos leer este fichero de datos u otro similar que ya exista. Una vez abierto el fichero
para lectura, podemos crear una lista vacía para cada columna de datos y luego con un bucle
leerlo línea por línea separando cada una en columnas con el método split(). Veámoslo con
un ejemplo: queremos leer el fichero datos.txt que acabamos de crear antes. Contiene 100 filas
con dos columnas, la primera un número y la segunda su exponencial. Lo podríamos hacer de esta
forma:

In [68]: # Abrimos el fichero "datos.txt" para lectura


In [69]: fdatos = open('datos.txt', 'r')
In [70]: x_datos = [] # Creamos una lista para la
˓→primera columna

In [71]: y_datos = [] # Creamos una lista para la


˓→segunda columna

In [72]: lineas = fdatos.readlines() # Leemos el fichero línea a línea


In [73]: for linea in lineas:
....: x, y = linea.split() # Se separa cada línea en dos
˓→columnas

....: x_datos.append(float(x)) # Añado el elemento x a la lista


˓→x_datos

....: y_datos.append(float(y)) # Añado el elemento y a la lista


˓→y_datos

....:
....:

In [75]: fdatos.close()

En este ejemplos usamos el método readlines() al fichero para que lea línea a linea y devuel-
ve una lista donde cada elemento es una cadena de texto con una línea, por lo que si hacemos
len(lineas) para ver la longitud de la lista nos da el número de líneas del fichero. En realidad
este paso nos lo podemos ahorrar ya que python es capaz de iterar línea a línea una instancia de
fichero, es decir, podemos poner:

for fdatos in lineas:

en lugar de:

for linea in lineas:

pero así es más fácil entender lo que realmente está haciendo.


Es importante recordar que los datos (números, letras, caracteres, etc...) se escriben y leen como
variables string. Por lo tanto, si queremos operar con ellos debemos transformarlos en los tipos de
variables apropiadas (int, float, list, array, ...).

9.2. Lectura de ficheros 87


Curso de Computación Científica, Publicación 2016-09-15

In [77]: type(x_datos)
Out[77]: <type 'list'>

In [78]: len(x_datos)
Out[78]: 100

In [79]: x_datos, y_datos = array(x_datos), array(y_datos)

In [80]: type(x_datos)
Out[80]: <type 'numpy.ndarray'>

In [81]: x_datos.shape
Out[81]: (100,)

Ahora que tenemos todos los datos en arrays los podemos manipular como tales. Recuerda que
split() separa, por defecto, datos en el texto separados por espacios; si queremos separar por
comas u otro carácter debemos incluirlo como parámetro: split(',').

9.3 Lectura y escritura de ficheros de datos con numpy

Una manera alternativa y muchísimo más sencilla de escribir y leer arrays es usando los métodos
savetxt() y loadtxt() de numpy, que escriben (guardan) y leen ficheros de texto pero con
un formato prefijado por defecto.:

# Guardamos en el fichero "datos2.txt" (creándolo) dos columnas


# que contienen los arrays x_datos e y_datos
savetxt("datos2.txt", (x_datos, y_datos))

# Leemos el fichero que acabamos de crear y


# almacenamos los arrays en x e y
x, y = loadtxt("datos2.txt")

Hay que notar que si guardamos varios arrays en un fichero de esta manera, savetxt() guarda
cada uno en una fila, de manera que x_datos ocupa la primera fila del fichero y y_datos
la segunda, en lugar de en columnas y al leerlo con loadtxt(), la variable x contiene toda
la primera fila e y la segunda. No obstante, en el caso de que sólo queramos guardar un array
unidimensional, se escribe en una única columna.
Por otro lado, si leemos el fichero y cargamos los datos en un único array veamos la forma que
tiene:

d = loadtxt("datos2.txt")
d.shape()
(2, 100)

88 Capítulo 9. Lectura y escritura de ficheros


Curso de Computación Científica, Publicación 2016-09-15

es decir, un array bidimensional de 2 x 100, que corresponde primero a las dos filas y luego 100
columnas. Esto quiere decir que si tenemos un fichero de texto con varias columnas (como es
habitual), como datos.txt que contiene:

# tiempo x1 x2
1.0 1.2 1.4
2.0 2.1 2.3
3.0 3.3 3.0
4.0 4.2 4.2
5.0 5.1 5.1

podemos leerlo de esta manera:

d = loadtxt("datos.txt") # d es un array de 6 x 3
t = d[0,:] # columna 1 (tiempo)
x1 = d[1,:] # columna 2 (x1)
x2 = d[2,:] # columna 1 (x2)

pero si queremos pasarlo directamente a arrays individuales debemos usar el parámetro


unpack=True, que lo que hace es intercambiar filas por columnas (traspuesta del array):

t, x1, x2 = loadtxt("datos.txt", unpack=True)

Lo anterior sólo es necesario si el fichero tiene varias columnas, si sólo tiene una no hace falta usar
unpack=True.
Supongamos ahora que estamos interesados en leer los datos de un fichero a partir de la línea 50 y
no desde el principio y que además las columnas están separadas por comas en lugar de espacios;
podemos indicarlo con parámetros adicionales:

x, y = loadtxt("datos.txt", skiprows=49, delimiter=",")

Consulten la ayuda de la función loadtxt() de numpy para conocer otras opciones de lectura,
como seleccionar columnas determinadas, definir otro símbolo de comentario, entre otras opciones.

9.4 Ejercicios

1. En el fichero datos_2col.txt hay dos columnas de datos. Calcular la raíz cuadrada de los
valores de la primera columna y el cubo de la segunda y escribirlos a un nuevo fichero de
dos columnas, pero solo para las entradas cuyos valores de la segunda columna de datos
original sean mayor o igual a 0.5.
2. El fichero datos_4col.txt contiene cuatro columnas de datos. Escribir un fichero de datos con
cuatro columnas que incluya el número de entrada (empezando por 1), el promedio entre
las dos primeras columnas, el promedio entre las dos últimas y la diferencia entre ambas
medias.

9.4. Ejercicios 89
Curso de Computación Científica, Publicación 2016-09-15

3. Usando la función randint() de numpy.random, generen una lista de 30 números en-


teros aleatorios (hasta 100). Calculen la media aritmética y la desviación estándar de grupos
de elementos de 5 en 5 poniendo los resultados en dos arrays diferentes; es decir, obtener un
array con las medias y otro con las desviaciones estándar, que serán de longitud 30/5. Crear
un fichero con tres columnas que contenga el número de línea, la media y la desviación
estándar.
4. Calcule en un array los valores que toma la función seno-cociente: 𝑠𝑒𝑛(𝜃)/𝜃 para valores
de 𝜃 entre -45 y 45 grados a intervalos de 0.1 grados. Escriba el resultado en un fichero que
incluya en una columna el ángulo 𝜃 en grados y en otra el valor del seno-cociente correspon-
diente.
5. A lo largo de la historia desde que O. Roemer en 1676 midiera por vez primera la veloci-
dad de la luz, se han publicado bastantes medidas de la velocidad de la luz por diferentes
métodos. Una colección de ellas hechas en el siglo XX (recopiladas por R.T. Birge, (1941),
Rep. Prog. Phys., 8, 95 con sus errores, y alguna más, están en el fichero velocidad_luz.txt.
Se pide escribir un programa en el que se lea el fichero en cuestión donde, como pueden leer
en la cabecera del fichero, las tres columnas son: el año de la medida, el resultado obtenido
y su error en Km/s y calculen el valor medio ponderado por la inversa del cuadrado de los
errores y su dispersión.
6. En un experimento de laboratorio se tiene un circuito eléctrico al que vamos variando el
voltaje de la fuente de alimentación desde 0.05 V hasta 15.00 V, a intervalos de 0.01 V y se
mide la intensidad de corriente que circula por él con un amperímetro. Se repite el experi-
mento cinco veces, de forma que para cada voltaje V aplicado se obtienen cinco medidas de
I (en amperios) que se almacenan en una tabla que contiene el fichero datos_intensidad.txt.
Dibujar un histograma y calcular el valor medio y la desviación estándar de las cinco series
de medidas de la intensidad para cada voltaje aplicado al circuito.

90 Capítulo 9. Lectura y escritura de ficheros


CAPÍTULO 10

Representación gráfica de funciones y datos

La representación de funciones y/o datos científicos mediante gráficos resulta ser fundamental
para expresar una gran variedad de resultados. Cualquier informe, artículo o resultado a menudo
se expresa de manera mucho más clara mediante gráficos. Python posee varios paquetes gráficos;
nosotros usaremos matplotlib, una potente librería gráfica de alta calidad también para gráficos
bidimensionales y sencilla de manejar. Matplotlib posee el módulo pylab, que es la interfaz para
hacer gráficos bidimensionales. Veamos un ejemplo sencillo:

>>> from pylab import * # importar todas las funciones de pylab


>>> x = arange(10.) # array de floats, de 0.0 a 9.0
>>> plot(x) # generar el gráfico de la función y=x
>>> [<matplotlib.lines.Line2D object at 0x9d0f58c>]
>>> show() # mostrar el gráfico en pantalla

Hemos creado un gráfico que representa diez puntos en un array y luego lo hemos mostrado con
show(); esto es así porque normalmente solemos hacer varios cambios en la gráfica, mostrán-
dolos todos juntos. Sin embargo, cuando trabajamos interactivamente, por ejemplo con la consola
ipython podemos activar el modo interactivo para que cada cambio que se haga en la gráfica

91
Curso de Computación Científica, Publicación 2016-09-15

se muestre en el momento, mediante la función ion(), de esta manera no hace falta poner show()
para mostrar la gráfica cada vez que se haga plot():

In [1]: ion() # Activo el modo interactivo


In [2]: plot(x) # Hago un plot que se muestra sin hacer
˓→show()

Out[2]: [<matplotlib.lines.Line2D object at 0x9ffde8c>]

Recuerden que este modo interactivo sólo está disponible en la consola avanzada ipython pero
no lo está en la consola estándar de Python. Otra posibilidad es iniciar ipython en modo pylab,
haciendo ipython -pylab, de esta manera se carga automáticamente pylab, se activa el mo-
do interactivo, y además se importa el módulo numpy y todas sus funciones.
Fíjense cómo el comando plot() que hemos usado hasta ahora devuelve una lista de instancias
de cada dibujo. Una instancia es una referencia a un elemento que creamos, en este caso la línea en
gráfica. En este caso es una lista con un sólo elemento, una instancia Line2D. Podemos guardar
esta instancia para referirnos a este dibujo (a la línea en concreto) más adelante haciendo:

In [3]: mi_dibujo, = plot(x)

Ahora la variable mi_dibujo es una instancia o “referencia” a la línea del dibujo, que podremos
manipular posteriormente con métodos que se aplican a esa instancia dibujo. Nótese que después
de mi_dibujo hay una coma; esto es para indicar que mi_dibujo debe tomar el valor del
primer (y en este caso el único) elemento de la lista y no la lista en sí, que es lo que habría ocurrido
de haber hecho mi_dibujo = plot(x) (erróneamente). Esto es habitual al trabajar con listas,
veámoslo con un ejemplo:

a = [3, 5]
# Así ``a`` es una lista, que contiene dos valores

a, b = [3, 5]
# Así *desempaquetamos* los elementos de la lista y a=3 y b=5
# Esto funciona porque pusimos tantas variables como elementos en la
˓→lista

Pero si la lista sólo tiene un elemento ¿cómo desempaquetamos ese elemento?. Veamos:

a = [3] # Así, ``a`` es una lista y no el número 3

a, = [3] # Si añadimos una coma indicamos que queremos meter ese único
# elemento en una variable, en lugar de usar la lista

Y esto es justo lo que hicimos con mi_dibujo,= plot(x), para hacer que mi_dibujo con-
tenga una instancia y no una lista de instancias, que es lo que devuelve plot().
La sintaxis básica de plot() es simplemente plot(x,y), pero si no se incluye la lista x, ésta
se reemplaza por el número de elementos o índice de la lista y, por lo que es equivalente a hacer
plot(range(len(y)),y). En la gráfica del ejemplo anterior no se ven diez puntos, sino una

92 Capítulo 10. Representación gráfica de funciones y datos


Curso de Computación Científica, Publicación 2016-09-15

línea contínua uniendo esos puntos, que es como se dibuja por defecto. Si queremos pintar los
puntos debemos hacerlo con un parámetro adicional, por ejemplo:

In [4]: plot(x,'o') # pinta 10 puntos como o


Out[4]: [<matplotlib.lines.Line2D object at 0x8dd3cec>]

In [5]: plot(x,'o-') # igual que antes pero ahora los une con una
˓→linea continua

Out[5]: [<matplotlib.lines.Line2D object at 0x8dd9e0c>]

En este caso el ‘o’ se usa para dibujar puntos gruesos y si se añade ‘-‘ también dibuja la línea
contínua. En realidad, lo que ha sucedido es que se dibujaron dos gráficos uno encima del otro;
si queremos que se cree un nuevo gráfico cada vez que hacemos plot(), debemos añadir el
parámetro hold=False a plot():

mi_dibujo, = plot(x*2,'o', hold=False)

El tercer parámetro de la función plot() (o segundo, si no se incluye la variable x) se usa para


indicar el símbolo y el color del marcador. Admite distintas letras que representan de manera única
el color, el símbolo o la línea que une los puntos; por ejemplo, si hacemos plot(x,'bx-')
pintará los puntos con marcas “x”, de color azul (“b”) y los unirá además con líneas contínuas del
mismo color. A continuación se indican otras opciones posibles:
Colores

93
Curso de Computación Científica, Publicación 2016-09-15

Símbolo Color
‘b’ Azul
‘g’ Verde
‘r’ Rojo
‘c’ Cian
‘m’ Magenta
‘y’ Amarillo
‘k’ Negro
‘w’ Blanco
Marcas y líneas
Símbolo Descripción
‘-‘ Línea continua
‘–‘ Línea a trazos
‘-.’ Línea a puntos y rayas
‘:’ Línea punteada
‘.’ Símbolo punto
‘,’ Símbolo pixel
‘o’ Símbolo círculo relleno
‘v’ Símbolo triángulo hacia abajo
‘^’ Símbolo triángulo hacia arriba
‘<’ Símbolo triángulo hacia la izquierda
‘>’ Símbolo triángulo hacia la derecha
‘s’ Símbolo cuadrado
‘p’ Símbolo pentágono
‘*’ Símbolo estrella
‘+’ Símbolo cruz
‘x’ Símbolo X
‘D’ Símbolo diamante
‘d’ Símbolo diamante delgado
Para borrar toda la figura se puede usar la función clf(), mientras que cla() sólo borra lo que
hay dibujado dentro de los ejes y no los ejes en si.
Se pueden representar varias parejas de datos con sus respectivos símbolos en una misma figura,
aunque para ello siempre es obligatorio incluir el valor del eje x:
In [8]: clf() # Limpiamos toda la figura

In [9]: x2=x**2 # definimos el array x2

In [10]: x3=x**3 # definimos el array x3

In [11]: # dibujamos tres curvas en el mismo gráfico y figura


In [12]: plot(x, x,'b.', x, x2,'rd', x, x3,'g^')
Out[13]:

94 Capítulo 10. Representación gráfica de funciones y datos


Curso de Computación Científica, Publicación 2016-09-15

[<matplotlib.lines.Line2D object at 0x8e959cc>,


<matplotlib.lines.Line2D object at 0x8eb75cc>,
<matplotlib.lines.Line2D object at 0x8eb788c>]

Esta lista de salida de plot() contiene 3 instancias que se refieren a 3 elementos diferentes de la
gráfica.

Es posible cambiar el intervalo mostrado en los ejes con xlim() e ylim() :

In [12]: xlim(-1,11) #nuevos límites para el eje OX


Out[12]: (-1, 11)

In [13]: ylim(-50,850) #nuevos límites para el eje OY


Out[13]: (-50, 850)

Además del marcador y el color indicado de la manera anterior, se pueden cambiar muchas otras
propiedades de la gráfica como parámetros de plot() independientes como los de la tabla adjun-
ta:

95
Curso de Computación Científica, Publicación 2016-09-15

Parámetro Significado y valores


alpha grado de transparencia, float (0.0=transparente a 1.0=opaco)
color o c Color de matplotlib
label Etiqueta con cadena de texto, string
markeredge- Color del borde del símbolo
color o
mec
markeredge- Ancho del borde del símbolo, float (en número de puntos)
width o
mew
markerface- Color del símbolo
color o
mfc
markersize o Tamaño del símbolo, float (en número de puntos)
ms
linestyle o ls Tipo de línea, ‘-‘ ‘–‘ ‘-.’ ‘:’ ‘None’
linewidth o Ancho de la línea, float (en número de puntos)
lw
marker Tipo de símbolo,’+’ ‘*’ ‘,’ ‘.’ ‘1’ ‘2’ ‘3’ ‘4’ ‘<’ ‘>’ ‘D’ ‘H’ ‘^’ ‘_’ ‘d’ ‘h’ ‘o’
‘p’ ‘s’ ‘v’ ‘x’ ‘|’ TICKUP TICKDOWN TICKLEFT TICKRIGHT
Un ejemplo usando más opciones sería este:

In [15]: plot(x, lw=5, c='y', marker='o', ms=10, mfc='red')


Out[15]: [<matplotlib.lines.Line2D object at 0x8f0d14c>]

También es posible cambiar las propiedades de la gráfica una vez creada, para ello debemos captu-
rar las instancias de cada dibujo en una variable y cambiar sus parámetros. En este caso a menudo
hay que usar draw() para actualizar el gráfico,

96 Capítulo 10. Representación gráfica de funciones y datos


Curso de Computación Científica, Publicación 2016-09-15

>>> # Hago tres dibujos, capturando sus instancias


>>> # en las variables p1, p2 y p3
>>> p1, p2, p3 = plot(x, x,'b.',x, x2, 'rd', x, x3, 'g^')

>>> show() # Muestro en dibujo por pantalla


>>> p1.set_marker('o') # Cambio el símbolo de la gráfica 1
>>> p3.set_color('y') # Cambio el color de la gráfica 3
>>> draw() # Hacer los cambios

usando instancias similares poder cambiar prácticamente todas las propiedades de nuestro gráfico
sin tener que rehacerlo. Por tanto es buena costumbre guardar las instancias en variables cuando
trabajemos interactivamente.

10.1 Trabajando con texto dentro del gráfico

Exiten funciones para añadir texto (etiquetas) a los ejes de la gráfica y a la gráfica en sí; éstos son
los más importantes:

In [9]: x = arange(0, 5, 0.05)

In [10]: p, = plot(x,log10(x)*sin(x**2))

In [12]: xlabel('Eje X') # Etiqueta del eje OX


Out[12]: <matplotlib.text.Text object at 0x99112cc>

In [13]: ylabel('Eje Y') # Etiqueta del eje OY


Out[13]: <matplotlib.text.Text object at 0x99303cc>

In [14]: title('Mi grafica') # Título del gráfico


Out[14]: <matplotlib.text.Text object at 0x993802c>

In [15]: text(1, -0.4, 'Nota') # Texto en coodenadas (1, -0.4)

10.1. Trabajando con texto dentro del gráfico 97


Curso de Computación Científica, Publicación 2016-09-15

En este ejemplo, se usó la función text() para añadir un texto arbitrario en la gráfica, cuya
posición se debe dar en las unidades de la gráfica. Cuando se utilizan textos también es posible
usar fórmulas con formato LaTeX. Veamos un ejemplo,:
In [27]: clf() #limpio la figura

In [27]: from math import *

In [27]: from numpy import *

In [29]: x = arange(0.1, 6*pi,0.1)

In [30]: y1 = sin(x)/x

In [31]: y2 = sin(x)*exp(-x)

In [32]: p1, p2 = plot(x, y1, x, y2)

In [33]: texto1 = text(2, 0.6, r'$\frac{\sin(x)}{x}$', fontsize=20)

In [34]: texto2 = text(13, 0.2, r'$\sin(x) \cdot e^{-x}$', fontsize=16)

In [35]: grid() # Añado una malla al gráfico

In [36]: title('Representacion de dos funciones')


Out[36]: <matplotlib.text.Text object at 0x99fb78c>

In [37]: xlabel('Tiempo / s')

98 Capítulo 10. Representación gráfica de funciones y datos


Curso de Computación Científica, Publicación 2016-09-15

Out[37]: <matplotlib.text.Text object at 0x94f172c>

In [38]: ylabel('Amplitud / cm')


Out[38]: <matplotlib.text.Text object at 0x94f20ec>

Aquí hemos usado código LaTeX para escribir fórmulas matemáticas, para lo que siempre hay que
escribir entre r'$ formula $' y he usado un tamaño de letra mayor con el parámetro fontsize.
En la última línea hemos añadido una malla con la función grid().

Nota: LaTeX es un sistema de escritura orientado a contenidos matemáticos muy popular en


ciencia e ingeniería. Puedes ver una buena introducción a LaTeX en esta dirección (pdf): http:
//www.ctan.org/tex-archive/info/lshort/spanish.

10.2 Representación gráfica de funciones

Visto el ejemplo anterior, vemos que en Python es muy fácil representar gráficamente una fun-
ción matemática. Para ello, debemos definir la función y luego generar un array con el intervalo
de valores de la variable independiente que se quiere representar. Definamos algunas funciones
trigonométricas y luego representémoslas gráficamente:

>>> def f1(x):


.....: y = sin(x)
.....: return y

10.2. Representación gráfica de funciones 99


Curso de Computación Científica, Publicación 2016-09-15

.....:

>>> def f2(x):


.....: y = sin(x)+sin(5.0*x)
.....: return y
.....:

>>> def f3(x):


.....: y = sin(x)*exp(-x/10.)
.....: return y
.....:

>>> # array de valores que quiero representar


>>> x = arange(0, 10*pi, 0.1)

>>> p1, p2, p3 = plot(x, f1(x), x, f2(x), x, f3(x))

>>> # Añado leyenda, tamaño de letra 10, en esquina superior derecha


>>> legend(('Funcion 1', 'Funcion 2', 'Funcion 3'),
prop = {'size':10}, loc = 'upper right')
>>> <matplotlib.legend.Legend object at 0xbb4b0ac>

>>> xlabel('Tiempo / s')


>>> <matplotlib.text.Text object at 0xa06764c>

>>> ylabel('Amplitud / cm')


>>> <matplotlib.text.Text object at 0xa0c32cc>

>>> title('Representacion de tres funciones')


>>> <matplotlib.text.Text object at 0xa0c3e8c>

100 Capítulo 10. Representación gráfica de funciones y datos


Curso de Computación Científica, Publicación 2016-09-15

Nótese que hemos añadido una leyenda con la función legend() que admite como entrada una
tupla con strings que corresponden consecutivamente a cada una de las curvas del gráfico.

10.3 Histogramas

Cuando tenemos un conjunto de datos numéricos, por ejemplo como consecuencia de la medida
de una cierta magnitud y queremos representarlos gráficamente para ver la distribución subyacente
de los mismos se suelen usar los gráficos llamados histogramas. Los histogramas representan el
número de veces que los valores del conjunto caen dentro de un intervalo dado, frente a los di-
ferentes intervalos en los que queramos dividir el conjunto de valores. En Python podemos hacer
histogramas muy fácilmente con la función hist() indicando como parámetro un array con los
números del conjunto. Si no se indica nada más, se generará un histograma con 10 intervalos (lla-
mados bins, en inglés) en los que se divide la diferencia entre el máximo y el mínimo valor del
conjunto. Veamos un ejemplo:

>>> # Importo el módulo de numeros aleatorios de scipy


>>> from scipy import random
>>> # utilizo la función randn() del modulo random para generar
>>> # un array de números aleatorios con distribución normal
>>> nums = random.randn(200) # array con 200 números aleatorios
>>> # Genero el histograma
>>> hist(nums)
>>>
(array([ 2, 10, 11, 28, 40, 49, 37, 12, 6, 5]),
array([-2.98768497, -2.41750815, -1.84733134, -1.27715452, -0.70697771,

10.3. Histogramas 101


Curso de Computación Científica, Publicación 2016-09-15

-0.13680089, 0.43337593, 1.00355274, 1.57372956, 2.


14390637,
˓→

2.71408319]),
<a list of 10 Patch objects>)

Si no se le proporciona ningún otro argumento a randn() produce floats alrededor de 0 y con


una varianza = 1.
Vemos que los números del array se dividieron automáticamente en 10 intervalos (o bins) y cada
barra representa para cada una de ellos el número de valores que caen dentro. Si en lugar de usar
sólo 10 divisiones queremos usar 20 por ejemplo, debemos indicarlo como un segundo parámetro:

>>> hist(nums, bins=20)

El la figura de abajo se muestra el resultado de superponer ambos histogramas. Nótese que la


función hist() devuelve una tupla con tres elementos, que son un array con el número elementos
en cada intervalo, un array con el punto del eje OX donde empieza cada intervalo y una lista
con referencias a cada una de las barras para modificar sus propiedades (consulten el manual de
matplotlib para encontrar más información y mayores posibilidades de uso).

10.4 Figuras diferentes

Se pueden hacer cuantas figuras independientes (en ventanas distintas) queramos con la función
figure(n) donde n es el número de la figura. Cuando se crea una figura al hacer plot() se
hace automáticamente figure(1), como aparece en el título de la ventana. Podríamos crear

102 Capítulo 10. Representación gráfica de funciones y datos


Curso de Computación Científica, Publicación 2016-09-15

una nueva figura independiente escribiendo figure(2), en ese momento todos los comandos de
aplican a figura activa, la figura 2. Podemos regresar a la primera escribiendo figure(1) para
trabajar nuevamente en ella, por ejemplo:

>>> p1, = plot(sin(x)) # Crea una figura en una ventana (Figure 1)


>>> figure(2) # Crea una nueva figura vacía en otra ventana
˓→(Figure 2)

>>> p2, = plot(cos(x)) # Dibuja el gráfico en la figura 2


>>> title('Funcion coseno') # Añade un título a la figura 2
>>> figure(1) # Activo la figura 1
>>> title('Funcion seno') # Añade un título a la figura 2

10.5 Varios gráficos en una misma figura

En ocasiones nos interesa mostrar varios gráficos diferentes en una misma figura o ventana. Para
ello podemos usar la función subplot(), indicando entre paréntesis un número con tres dígitos.
El primer dígito indica el número de filas en los que se dividirá la figura, el segundo el número
de columnas y el tercero se refiere al gráfico con el que estamos trabajando en ese momento. Por
ejemplo, si quisiéramos representar las tres funciones anteriores usando tres gráficas en la misma
figura, una al lado de la otra y por lo tanto con una fila y tres columnas, haríamos lo siguiente:

>>> # Figura con una fila y tres columnas, activo primer subgráfico
>>> subplot(131)
>>> p1, = plot(x,f1(x),'r-')
>>> # Etiqueta del eje Y, que es común para todas
>>> ylabel('Amplitud / cm')
>>> title('Funcion 1')

>>> # Figura con una fila y tres columnas, activo segundo subgráfico
>>> subplot(132)
>>> p2, = plot(x,f2(x),'b-')
>>> # Etiqueta del eje X, que es común para todas
>>> xlabel('Tiempo / s')
>>> title('Funcion 2')

>>> # Figura con una fila y tres columnas, activo tercer subgráfico
>>> subplot(133)
>>> p3, = plot(x, f3(x),'g-')
>>> title('Funcion 3')

10.5. Varios gráficos en una misma figura 103


Curso de Computación Científica, Publicación 2016-09-15

Al igual que con varias figuras, para dibujar en un gráfico hay que activarlo. De esta forma, si
acabamos de dibujar el segundo gráfico escribiendo antes subplot(132) y queremos cambiar
algo del primero, debemos activarlo con subplot(131) y en ese momento todas funciones de
gráficas que hagamos se aplicarán a él.

10.6 Representando datos experimentales

Cuando estamos en el laboratorio (u observatorio) haciendo un experimiento y midiendo algún


parámetro físico solemos obtener un conjunto de números que normalmente guardaremos en un
fichero de texto en cualquier ordenador. Representar datos leídos de un fichero en lugar de gene-
rarlos directamente como hemos hecho hasta ahora, es tan sencillo como leer los datos y pasarlos a
arrays de numpy. Una vez hecho, se representan gráficamente como ya hemos visto. Supongamos
que tenemos los resultados de un experimento en una tabla de dos columnas almacenada en el
fichero “datos_2col.txt”, para poder dibujarlos en un gráfico hacemos,:

>>> # Leemos el fichero de dos columnas de datos, pasándolo a un array


>>> datos = loadtxt('datos_2col.txt')
>>> datos.shape
>>> (100, 2)
>>> # es decir, 100 filas, 2 columnas

>>> col2, = plot(datos[:,1], 'b.') # Segunda columna, puntos azules


˓→(b)

>>> col1, = plot(datos[:,0], 'r.') # Primera columna, puntos rojos


˓→(r)

>>> # Trazo una línea horizontal en la coordenada y=4 color verde (g)
>>> axhline(4, color='g')
>>> <matplotlib.lines.Line2D object at 0x169c6d8c>

>>> # Trazo una línea vertical en la coordenada x=20 color verde (g)
>>> axvline(20, color='g')

104 Capítulo 10. Representación gráfica de funciones y datos


Curso de Computación Científica, Publicación 2016-09-15

>>> <matplotlib.lines.Line2D object at 0xac5986c>

>>> # Dibujo una banda horizontal de y=0 a y=2 de color azul


>>> # y 30% de transparencia (alpha=0.3)
>>> axhspan(0, 2, alpha=0.3, color='b')
>>> <matplotlib.patches.Polygon object at 0xac59c4c>

>>> # Dibujo una banda vertical de x=60 a x=80 de color verde


>>> # y 30% de transparencia
>>> axvspan(60, 80, alpha=0.3, color='g')
>>> <matplotlib.patches.Polygon object at 0xac59a0c>

>>> # Etiqueto los ejes


>>> xlabel('Eje X')
>>> <matplotlib.text.Text object at 0xad043ac>
>>> ylabel('Eje Y')
>>> <matplotlib.text.Text object at 0xad0b52c>

En este caso hemos usado además algunas funciones para crear líneas y bandas horizontales y
verticales.

10.7 Datos experimentales con barras de error

Cuando se trabaja con datos de laboratorio es muy habitual tener errores asociados a los datos que
se van tomando; al representarlos gráficamente después es muy conveniente dibujar también las

10.7. Datos experimentales con barras de error 105


Curso de Computación Científica, Publicación 2016-09-15

barras de error de cada dato tomado. En Python esto se puede hacer fácilmente usando la función
errorbar() en lugar de plot() o junto con ella. A la hora de usarla hay que incluir en su
sintaxis los errores como parámetros usando floats si son errores iguales para todos los puntos o
bien un array representando el error de cada punto. Veamos un ejemplo:

>>> # Datos de x e y
>>> x = array([ 0.1, 0.6, 1.1, 1.6, 2.1, 2.6, 3.1, 3.6, 4.1,
˓→4.6])

>>> y = array([ 0.90, 0.54, 0.33, 0.20, 0.12,


0.07, 0.04 , 0.02, 0.01, 0.01])

>>> # Error fijo en x e y


>>> err_x = 0.1
>>> err_y = 0.2

>>> errorbar(x, y, xerr=err_x, yerr=err_y)

>>> # Si los errores de x e y son distintos en cada punto,


>>> # se ponen en un array. Supongamos que sean:
>>> err_y = array([ 0.12, 0.10, 0.14, 0.11, 0.18,
0.18, 0.17, 0.11, 0.18, 0.19])
>>> err_x = array([ 0.07, 0.12, 0.06, 0.11, 0.09,
0.12, 0.06, 0.05, 0.05, 0.14])

>>> # Gráfico con la barra de error en x e y, usando


>>> # línea continua y puntos (fmt='-o')
>>> errorbar(x, y, xerr=err_x, yerr=err_y, fmt='-o')

106 Capítulo 10. Representación gráfica de funciones y datos


Curso de Computación Científica, Publicación 2016-09-15

Hay veces que para algunos tipos de datos, conviene representar alguno de los ejes o ambos en
escala logarítmica para apreciar mejor la evolución de la gráfica. Podemos usar las funciones
semilogx(), semilogy() o loglog() para hacer un gráfico en escala logarítmica en el
eje x, en el eje y o en ambos, respectivamente. Por ejemplo, para representar el gráfico anterior con
el eje y en escala logarítmica, podemos hacer lo siguiente:
>>> # Eje y en escala logarítmica
>>> p1, = semilogy(x, y,'rs-')

>>> grid()
>>> xlabel('Eje X')
>>> ylabel('Eje Y')
>>> title('Representacion en escala logaritmica del eje Y')

10.8 Representación de datos bidimensionales

Los datos bidimensionales como su nombre indica son valores de una magnitud física representada
por una función que tiene dos variables independientes, normalmente x e y; se trata pues de repre-
sentar funciones del tipo z=z(x,y), donde z puede representar flujo luminoso, presión atmosférica,
altura del terreno, etc... Hay varias formas de representar estos datos bidimensionales (como podría
ser una imagen), una de ellas es usar escalas de colores para representar los diferentes valores de z
para pares de valores (x,y). En Python lo hacemos usando la función imshow():
>>> # Creo un array 1D 100x100 de valores de 0.0 a 99999.0
>>> datos1D = arange(10000.) # array unidimensional con 10000
˓→elementos

>>> # Ahora cambio la forma (reshape) del array redistribuyendo sus


˓→elementos,

10.8. Representación de datos bidimensionales 107


Curso de Computación Científica, Publicación 2016-09-15

>>> # de 10000 en 1D a un array de 100 filas y 100 columnas en 2D,


>>> # como si fuese una imagen.
>>> datos2D = datos1D.reshape(100,100)
>>> datos2D.shape
>>> (100, 100)

>>> # Representamos gráficamente el array bidimensional


>>> imshow(datos2D)
>>> # Añadimos una paleta (barra de color) para indicar la equivalencia
>>> # de los colores con los valores de la función
>>> colorbar()
>>> # Cambio a la paleta de colores gray() (por defecto es jet())
>>> gray()

Vamos a representar una imagen en color con formato png, nebulosa.png, que está en nuestro
directorio de trabajo.:

>>> foto = imread("nebulosa.png")


>>> imshow(foto)

108 Capítulo 10. Representación gráfica de funciones y datos


Curso de Computación Científica, Publicación 2016-09-15

Las imágenes en color tienen 3 canales RGB, podemos limitarnos a tomar uno haciendo:

>>> foto1 = foto[:,:,0]

Podemos ahora cambiar la paleta o la escala de colores con la que queremos ver la imagen, para lo
que variamos el argumento cmap. Si queremos mostrarla en blanco y negro usamos gray():

>>> imshow(foto1, cmap = gray())

Otras paletas disponibles son jet(), hot() o spectral().

10.9 Guardando las figuras creadas

Después de crear una figura con cualquiera de los procedimientos descritos hasta ahora podemos
guardarla con la función savefig() poniendo como parámetro el nombre del fichero con su ex-
tensión. El formato de grabado se toma automáticamente de la extensión del nombre. Los formatos
disponibles en Python son los más usuales: png, pdf, ps, eps y svg. Por ejemplo:

>>> savefig("mi_primera_grafica.eps") # Guardo la figura en formato


˓→eps

>>> savefig("mi_primera_grafica.png") # Guardo la figura en formato


˓→png

Si el gráfico se va usar para imprimir, por ejemplo en una publicación científica o en un informe, es
recomendable usar un formato vectorial como Postscript (ps) o Postscript encapsulado (eps), pero

10.9. Guardando las figuras creadas 109


Curso de Computación Científica, Publicación 2016-09-15

si es para mostrar por pantalla o en una web, el más adecuado es un formato de mapa de bits como
png o jpg.
Consulta la web de matplotlib (http://matplotlib.sourceforge.net/) para ver muchas más pro-
piedades y ejemplos de esta librería.

10.10 Ejercicios

1. La curva plana llamada trocoide, una generalización de la cicloide, es la curva descrita por un
punto P situado a una distancia b del centro de una circunferencia de radio a, a medida que
rueda (sin deslizar) por una superficie horizontal. Tiene por coordenadas (x,y) las siguientes:

𝑥 = 𝑎 𝜑 − 𝑏 𝑠𝑒𝑛𝜑 , 𝑦 = 𝑎 − 𝑏 𝑐𝑜𝑠𝜑

Escribir un programa que dibuje tres curvas (contínuas y sin símbolos), en el mismo gráfico
cartesiano (OX,OY), para un intervalo 𝜑 = [0.0, 18.0] (en radianes) y para los valores de
a=5.0 y b=2.0, 5.0 y 8.0 . Rotular apropiadamente los ejes e incluir una leyenda con los tres
valores de que distinguen las tres curvas.
2. Dibujar las diferentes trayectorias de los proyectiles disparados por un cañón situado en un
terreno horizontal para diferentes ángulos de elevación (inclinación respecto de la horizontal)
en un intervalo de tiempo de 0 a 60 s. El cañón proporciona una velocidad inicial de 300 m/s.
Dibujarlas para los ángulos de elevación siguientes: 20, 30, 40, 50, 60 y 70 grados y suponer
que el cañón está situado en el origen de coordenadas. Rotular apropiadamente los ejes e
insertar una leyenda que identifique las diferentes trayectorias. Recordar que el proyectil no
puede penetrar en el suelo de forma que hay que establecer los límites apropiados para el
dibujo.
3. Cuando una fuente de luz coherente atraviesa una rendija delgada, se produce difracción de
la luz, cuyo patrón de intensidad en la aproximación de Fraunhofer está dado por:

𝑠𝑒𝑛𝛽 2 𝜋𝑎𝑠𝑒𝑛𝜃
𝐼(𝜃) = 𝐼0 ( ) 𝛽=
𝛽 𝜆

donde a es el ancho de la rendija, 𝜆 la longitud de onda de la luz, 𝐼0 la intensidad en el


eje y 𝜃 el ángulo de la posición medida con el eje de la rendija (ver dibujo). Representar
gráficamente la intensidad del patrón de difracción para 𝜆 = 400nm, 𝜆 = 650nm y 𝜆 = 800nm
usando 𝐼0 = 1 y a = 0.04mm en el intervalo −𝜋/20 < 𝜃 < +𝜋/20. Comprobar cual es el
efecto del patrón de difracción al duplicar el ancho de la rendija.

110 Capítulo 10. Representación gráfica de funciones y datos


Curso de Computación Científica, Publicación 2016-09-15

1. La variación de temperatura de un objeto a temperatura 𝑇0 en un ambiente a 𝑇𝑠 cambia de la


siguiente manera:

𝑇 = 𝑇𝑠 + (𝑇0 − 𝑇𝑠 )𝑒−𝑘𝑡

con 𝑡 en horas y k un parámetro que depende del objeto. a) Representar gráficamente la


variación de la temperatura con el tiempo, partiendo de una 𝑇0 = 5∘ 𝐶 a lo largo de 24
horas suponiendo k=0.45 y temperatura ambiente de 40ºC. b) Superponer sobre esta curva
las curvas correspondientes a otros objetos con k=0.3 y k=0.6 con distinto color y trazado,
identificándolas con una leyenda.
2. Representen nuevamente la curva del apartado a) del ejercicio anterior superponiendo
además las curvas correspondientes a temperaturas iniciales distintas, de 𝑇0 = −5∘ 𝐶 y
𝑇0 = 15∘ 𝐶. Para 𝑇0 = 5∘ 𝐶 representen en una figura aparte cómo cambian las curvas con
temperaturas ambiente de 20ºC y 50ºC, además de la de 40ºC. Identifiquen cada curva y
etiqueta correctamente los ejes en todas las gráficas.
3. Representar gráficamente las siguientes funciones:
f(x) = a e^{-{frac{(x-x_0)^2}{2c^2}}}
g(x) = frac{b}{(x-x_0)^2+b^2}
usando los valores a=2.0, 𝑥0 = 10.0, c=5.0 y b=0.5, en el intervalo de x [-50,+50]. Com-
prueba cómo afecta a las gráficas distintos valores de los parámetros c y b.
4. Con la serie de Gregory-Leibnitz para el cálculo de 𝜋 usada anteriormente en el problema
5.5:

𝑛
∑︁ (−1)𝑘+1
4
𝑘=1
2𝑘 − 1

el valor obtenido de 𝜋 se acerca lentamenta al verdadero con cada término que se añada.
Calculen todos los valores que va tomando 𝜋 con cada término añadido y representen en una

10.10. Ejercicios 111


Curso de Computación Científica, Publicación 2016-09-15

figura con dos gráficas (usando subplot()) los primeros 300 valores que toma 𝜋 frente al
número de términos usados en una de ellas y el valor absoluto de la diferencia entre el valor
calculado y el real frente al número de elementos en la otra.
5. El fichero medidas_I131.txt (directorio de datos en el aula virtual) contiene medidas de
masa de yodo 131 radioactivo hechas diariamente para medir su coeficiente de desinte-
gración. La primera columna es la masa residual en gramos y la segunda es el error de
la medida. Representen gráficamente las medidas de masa frente al tiempo incluyendo ba-
rras de error usando puntos sin unir con líneas y etiquetando los ejes apropiadamente. Al
leer el fichero saltar las primeras cuatro líneas que no contienen datos. Esto se hace con
input.readlines()[4:].
6. El movimiento de un oscilador amortiguado se puede expresar de la siguiente manera:

𝑥 = 𝐴0 𝑒−𝑘𝜔𝑡 cos (𝜔𝑡 + 𝛿)

Siendo 𝐴0 la amplitud inicial, 𝜔 la fecuencia angular de oscilación y k el factor de amortigua-


miento. Representar gráficamente el movimiento de un oscilador amortiguado de amplitud
inicial de 10 cm y frecuencia de 10 ciclos/s y 𝛿 = 𝜋/8 con factores de amortiguamiento de
0.1, 0.4, 0.9 y 1.1 durante 10 s. Incluya una leyenda identificativa de las curvas dibujadas.
Para el gráfico correspondiente a k=0.1 unir con líneas a trazos los valores máximos por un
lado y los valores mínimos por otro del movimiento oscilatorio. Nótese que corresponden a
las curvas para las que 𝑥 = 𝐴0 𝑒−𝑘𝜔𝑡 y 𝑥 = −𝐴0 𝑒−𝑘𝜔𝑡 .
7. La curva plana llamada epicicloide, tiene por coordenadas cartesianas (x,y) las siguientes:

𝑎 𝑎
𝑥 = (𝑎 + 𝑏) cos 𝜑 − 𝑏 cos( + 1)𝜑 , 𝑦 = (𝑎 + 𝑏) sin 𝜑 − 𝑏 sin( + 1)𝜑
𝑏 𝑏

Escribir un programa en el que se dibuje la curva (contínua y sin símbolo) para un intervalo
𝜑 = [0, 6𝜋] y para los valores de a= 1/3 y b= 1/8. Rotular apropiadamente los ejes e incluir
en el título los valores de a, b y el intervalo de valores de 𝜑 utilizado.
8. En un experimento de laboratorio se tiene un circuito eléctrico al que vamos variando el
voltaje de la fuente de alimentación desde 0.05 V hasta 15.00 V, a intervalos de 0.01 V y se
mide la intensidad de corriente que circula por él con un amperímetro. Se repite el experi-
mento cinco veces, de forma que para cada voltaje V aplicado se obtienen cinco medidas de
I (en amperios) que se almacenan en una tabla que contiene el fichero datos_intensidad.txt.
Dibujar un histograma y calcular el valor medio y la desviación estándar de las cinco series
de medidas de la intensidad para cada voltaje aplicado al circuito.

112 Capítulo 10. Representación gráfica de funciones y datos


CAPÍTULO 11

Ajuste de datos experimentales: el método de mínimos


cuadrados

Un trabajo habitual en el laboratorio es la creación de un modelo matemático de un fenómeno


físico determinado que explique su comportamiento. Obviamente, este modelo deberá predecir
también los datos experimentales que se obtengan de su recreación en el laboratorio. Por ejemplo,
si se toman medidas de la amplitud de las oscilaciones de un péndulo, es posible obtener una
función oscilatoria analítica que describa ese movimiento con la frecuencia y amplitud adecuadas
para cualquier instante de tiempo.

11.1 Formulación general

El principio de mínimos cuadrados fue formulado por primera vez por Legendre de la siguiente
forma: “El valor más probable de una cantidad observada es tal que la suma de los cuadrados
de las desviaciones de las observaciones respecto de este valor es mínimo”; y se utiliza extensiva-
mente en el problema de la optimización de funciones o en el ajuste de funciones a datos obtenidos
de la observación de fenómenos naturales o experimento de laboratorio.
Puede formularse sucintamente así: si tenemos un conjunto de observaciones de una cantidad
cualquiera
∑︀𝑖=𝑛 𝑥𝑖 con 𝑖 = 1, 2, ..., 𝑛, el valor más probable de esta cantidad es 𝑋 si verifica que la
2
𝑖=1 (𝑥𝑖 − 𝑋) es mínima.

Es útil comprobar que en este caso el valor más probable coincide con el valor medio del conjun-
to de observaciones. En efecto, para comprobarlo no tenemos mas que calcular el mínimo de la
función anterior, es decir:
(︃ 𝑖=𝑛 )︃ 𝑖=𝑛 ∑︀
𝑑 ∑︁
2
∑︁ 𝑥𝑖
(𝑥𝑖 − 𝑋) = 0 ⇒ −2 (𝑥𝑖 − 𝑋) = 0 ⇒ 𝑋 = =𝑥
𝑑𝑋 𝑖=1 𝑖=1
𝑛

Nótese que cuando esta función es mínima lo es también la varianza, 𝜎 2 del conjunto de observa-
ciones 𝑥𝑖 .

113
Curso de Computación Científica, Publicación 2016-09-15

11.2 Aplicación al ajuste de funciones a datos experimen-


tales

El problema al que vamos aplicar este principio de mínimos cuadrados es el siguiente:


Supongamos que 𝑦𝑖 son 𝑛 medidas de una cantidad 𝑦 (o una combinación de ellas) que se co-
rresponde con otras 𝑛 medidas 𝑥𝑖 de otra cantidad 𝑥. Podemos pensar que existe una relación
entre ellas, por ejemplo 𝑦 = 𝑓 (𝑥), y queremos determinar precisamente cuál es esta función 𝑓 que
relaciona ambas cantidades.
Aplicando el principio de mínimos cuadrados podemos decir que la función más probable que
relaciona ambas cantidades será aquella que haga que la función:
(︃ 𝑖=𝑛 )︃
∑︁ 2
(𝑓 (𝑥𝑖 ) − 𝑦𝑖 ) sea mínima.
𝑖=1

11.2.1 El caso en que la función es una recta (polinomio de orden 1)

Supongamos que la función que relaciona 𝑥 con 𝑦 es: 𝑦 = 𝑓 (𝑥) = 𝑎𝑥 + 𝑏, por lo tanto la recta
más probable que relaciona el conjunto de observaciones 𝑥𝑖 con el de 𝑦𝑖 será la que minimice la
función:
(︃ 𝑖=𝑛 )︃
∑︁
𝑀= (𝑎𝑥𝑖 + 𝑏 − 𝑦𝑖 )2
𝑖=1

Para calcular los valores de a y b que definen la recta en cuestión, planteamos el problema de
optimización de funciones y obtenemos las ecuaciones que verifican
(︃ 𝑖=𝑛 )︃
𝜕𝑀 ∑︁
=0=2 𝑥𝑖 (𝑎𝑥𝑖 + 𝑏 − 𝑦𝑖 )
𝜕𝑎 𝑖=1

(︃ 𝑖=𝑛 )︃
𝜕𝑀 ∑︁
=0=2 (𝑎𝑥𝑖 + 𝑏 − 𝑦𝑖 )
𝜕𝑏 𝑖=1

es decir,
∑︁ ∑︁ ∑︁
𝑎 𝑥2𝑖 + 𝑏 𝑥𝑖 = 𝑥𝑖 𝑦 𝑖
∑︁ ∑︁
𝑎 𝑥𝑖 + 𝑏𝑛 = 𝑦𝑖

114Capítulo 11. Ajuste de datos experimentales: el método de mínimos cuadrados


Curso de Computación Científica, Publicación 2016-09-15

y resolviendo este sistema lineal de dos ecuaciones con dos incógnitas (a y b) nos queda:
∑︀ ∑︀ ∑︀
𝑛 𝑥𝑖 𝑦𝑖 − 𝑥𝑖 𝑦𝑖
𝑎=
𝑛 𝑥2𝑖 − ( 𝑥𝑖 )2
∑︀ ∑︀

𝑦𝑖 𝑥2𝑖 − 𝑥𝑖 𝑥𝑖 𝑦𝑖
∑︀ ∑︀ ∑︀ ∑︀
𝑏=
𝑛 𝑥2𝑖 − ( 𝑥𝑖 )2
∑︀ ∑︀

con lo que queda resuelto el problema.


Conviene fijarse en algunas características de este problema como las siguientes:
𝑥𝑖 𝑦𝑖 / 𝑥2𝑖 .
∑︀ ∑︀
1. Si 𝑏 = 0 entonces 𝑦 = 𝑎𝑥, donde 𝑎 =
2. Pueden comprobar que la recta obtenida pasa por el punto (𝑥, 𝑦).
3. Se definen los llamados residuos del ajuste como 𝑑𝑖 = 𝑎𝑥𝑖 + 𝑏 − 𝑦𝑖 .
4. Pueden comprobar que la varianza del conjunto de 𝑑𝑖 será 𝛼2 = 𝑑2𝑖 /(𝑛 − 2), donde 𝑛 − 2
∑︀
es el llamado número de grados de libertad del sistema.
5. La varianza es precisamente la cantidad que se minimiza en el método de mínimos cuadra-
dos.
6. Se define el coeficiente de correlación r para evaluar el grado de dependencia de las dos
series de observaciones, que va desde 0 (no correlación) a 1 (correlación perfecta), de la
forma siguiente:
( (𝑥 − 𝑥)(𝑦 − 𝑦))2
∑︀
2
𝑟 = ∑︀ ∑︀
(𝑥 − 𝑥)2 (𝑦 − 𝑦)2

11.2.2 Aplicación al caso de polinomios de cualquier orden

En el caso de ajustes a polinomios de cualquier orden (𝑚), es decir 𝑘=𝑚 𝑘


∑︀
𝑘=0 𝑎𝑘 𝑥 , el método funciona
de igual modo. Lo único que sucede es que, al tener que determinar 𝑚 + 1 parámetros, el sistema
de ecuaciones lineales que aparece es precisamente de orden 𝑚 + 1 complicando algebraicamente
la solución pero no conceptualmente. Veámoslo a título informativo.
Supongamos que tenemos una serie de medidas (𝑥1 , 𝑦1 ), (𝑥2 , 𝑦2 ), (𝑥3 , 𝑦3 ), . . . , (𝑥𝑛 , 𝑦𝑛 ), siendo x
la variable independiente e y la dependiente. Para un modelo 𝑓 (𝑥) de estos datos, hay un error r
para cada medida de 𝑟1 = 𝑦1 − 𝑓 (𝑥1 ), 𝑟2 = 𝑦2 − 𝑓 (𝑥2 ), . . . , 𝑟𝑛 = 𝑦𝑛 − 𝑓 (𝑥𝑛 ). Según el método de
mínimos cuadrados, la mejor función de ajuste f(x) es aquella en la que
𝑛
∑︁ 𝑛
∑︁
𝑅= 𝑑21 + 𝑑22 + ··· + 𝑑2𝑛 = 𝑑2𝑖 = |𝑦𝑖 − 𝑓 (𝑥𝑖 )|2
𝑖=1 𝑖=1

es minimo. La forma canónica de este problema es

𝑋𝑎 = 𝑌

11.2. Aplicación al ajuste de funciones a datos experimentales 115


Curso de Computación Científica, Publicación 2016-09-15

en la que X es una matriz MxN para M medidas experimentales y N grados de libertad y el objetivo
es minimizar |𝐴𝑥−𝑦|. En el caso del ajuste a un polinomio de grado N, el modelo sería de la forma
𝑦(𝑥) = 𝑎𝑁 𝑥𝑁 + · · · + 𝑎2 𝑥2 + 𝑎1 𝑥 + 𝑎0
para el que habría que escojer la combinación de parámetros 𝑎𝑖 que mejor se ajusten a los datos.
Así, la matriz A, llamada matriz de Vandermonde tiene esta forma:
⎡ ⎤
𝑥𝑁
1 · · · 𝑥21 𝑥1 1
⎢ 𝑥𝑁 · · · 𝑥2 𝑥2 1 ⎥
⎢ 2 2 ⎥
⎢ .. ⎥
⎣ . ⎦
𝑁 2
𝑥𝑀 · · · 𝑥𝑀 𝑥𝑀 1
en ella, cada fila corresponde a una medida experimental 𝐴𝑥 = 𝑦𝑖 .

11.3 Ajuste a polinomios con Python

La función polyfit() de numpy permite ajuste de datos experimentales a polinomios de cual-


quier orden. La sintaxis básica es:
parametros = polyfit(x, y, n)

en donde x e y son los datos experimentales y n grado del polinomio a ajustar. El resultado es una
lista con los parámetros del polinomio. Si a polyfit() se le incluye la opción full=True, además
de los parámetros devuelve los residuos y otros datos (ver ayuda de polyfit() para detalles).
Consideremos el siguiente ajuste a una recta de una serie de datos x e y:
# Importo todas las funciones de numpy, si no lo he hecho
from numpy import *

# Datos experimentales
x = array([ 0., 1., 2., 3., 4.])
y = array([ 10.2 , 12.1, 15.5 , 18.3, 20.6 ])

# Ajuste a una recta (polinomio de grado 1)


p = polyfit(x, y, 1)

print(p)
# imprime [ 2.7 9.94]

en este ejemplo polyfit() devuelve la lista de parámetros p de la recta, por lo que el modelo
lineal 𝑓 (𝑥) = 𝑎𝑥 + 𝑏 de nuestros datos será:
𝑦(𝑥) = 𝑝0 𝑥 + 𝑝1 = 2.7𝑥 + 9.94
Ahora podemos dibujar los datos experimentales y la recta ajustada:

116Capítulo 11. Ajuste de datos experimentales: el método de mínimos cuadrados


Curso de Computación Científica, Publicación 2016-09-15

# Valores de y calculados del ajuste


y_ajuste = p[0]*x + p[1]

# Dibujamos los datos experimentales


p_datos, = plot(x, y, 'b.')
# Dibujamos la recta de ajuste
p_ajuste, = plot(x, y_ajuste, 'r-')

title('Ajuste lineal por minimos cuadrados')

xlabel('Eje x')
ylabel('Eje y')

legend(('Datos experimentales', 'Ajuste lineal'), loc="upper left")

Como se ve en este ejemplo, la salida por defecto de polyfit() es un array con los parámetros
del ajuste. Sin embargo, si se pide una salida detalla con el parámetro full=True (por defecto
full=False), el resultado es una tupla con el array de parámetros, el residuo, el rango, los valores
singulares y la condición relativa.
∑︀Nos interesa especialmente el residuo del ajuste, que es la suma
cuadrática de todos los resíduos 𝑛𝑖=1 |𝑦𝑖 −𝑓 (𝑥𝑖 )|2 . Para el ejemplo anterior tendríamos lo siguiente:
# Ajuste a una recta, con salida completa
resultado = polyfit(x, y, 1, full=True)

11.3. Ajuste a polinomios con Python 117


Curso de Computación Científica, Publicación 2016-09-15

print(resultado)
""" Imprime tupla
(array([ 2.7 , 9.94]), # Parámetros del ajuste
array([ 0.472]), # Suma de residuos
2, # Rango de la matriz del
˓→sistema

array([ 2.52697826, 0.69955764]), # Valores singulares


1.1102230246251565e-15) # rcond
"""

Si estamos trabajando con polinomios, puede que nos interese usar las funciones polyval() o
poly1d() de numpy. Se utilizan para evaluar y generar funciones polinómicas respectivamente,
a partir de una lista o array o lista de parámetros. Por ejemplo, si del ejemplo anterior tenemos un
array p con los parámetros del ajuste lineal:

# Evaluo el polinomio en x=5.4


print polyval(p, 5.4)
# Imprime 24.520000000000003

# Creo una funcion polinomica de parametros p


mi_recta = poly1d(p)

# Ahora mi_recta() es una funcion que puedo evaluar

# Evaluo la fucion en x=5.4


print mi_recta(5.4)
# imprime 24.520000000000003

Como es de esperar, polyfit() sólo puede ajustar polinomios a datos experimentales, pero a
veces nos gustaría ajustar otras funciones. En muchos casos, sin embargo, es posible linealizar la
función con un cambio de variable adecuado y ajustar esta última. Por ejemplo la función
𝑏
𝑦=
𝑥+𝑎
se puede linealizar a 𝑦 ′ (𝑥) = 𝑎′ 𝑥 + 𝑏′ haciendo el cambio

1 𝑎′ 1
𝑦′ = 𝑎= 𝑏= .
𝑦 𝑏′ 𝑎′

ahora basta con ajustar la recta 𝑦 ′ (𝑥) = 𝑎′ 𝑥+𝑏′ y recuperar los parámetros a y b de la recta original.
En otros casos en los que no es posible linealizar la función o modelo, se tendrá que emplear un
método de optimización por mínimos cuadrados más general.

118Capítulo 11. Ajuste de datos experimentales: el método de mínimos cuadrados


Curso de Computación Científica, Publicación 2016-09-15

11.4 Ajuste de funciones no lineales

El submódulo optimize de scipy posee varios métodos optimización (búsqueda de máximos


y mínimos) incluyendo una función para ajuste por mínimos cuadrados para una función genérica,
llamada leastsq(). El método se basa en definir una función de error que es la diferencia entre
el modelo y los datos, siendo esta la función que se va a minimizar. En este caso es necesario que
el investigador proporcione unos parámetros iniciales aproximados de la función modelo con los
que empezar. Veamos un ejemplo sencillo para el ajuste de una recta.

from pylab import *


from scipy.optimize import leastsq

# Datos de laboratorio
datos_y = array([ 2.9, 6.1, 10.9, 12.8, 19.2])
datos_x = array([ 1.0, 2.0, 3.0, 4.0, 5.0])

# Función para calcular los residuos, donde


# se calcula (datos - modelo)
def residuos(p, y, x):
error = y - (p[0]*x + p[1])
return error

# Parámetros iniciales estimados


# y = p0[0]*x + p0[0]

p0 = [2.0, 0.0]

# Hacemos el ajuste por minimos cuadrados con leastsq(). El primer


˓→parámetro

# es la funcion de residuos, luego los parámetro iniciales y una tupla


˓→con los

# argumentos de la funcion de residuos, en este caso, datos_y y datos_


˓→x en

# ese orden, porque así se definió la función de error


ajuste = leastsq(residuos, p0, args=(datos_y, datos_x))

# El resultado es una lista, cuyo primer elemento es otra


# lista con los parámetros del ajuste
print ajuste[0]
# array([ 3.93, -1.41])

Naturalmente, para un simple ajuste lineal bien podemos usar polyfit(), de hecho podemos
comprobar que da lo mismo.

params = polyfit(datos_x, datos_y, 1)


print params

11.4. Ajuste de funciones no lineales 119


Curso de Computación Científica, Publicación 2016-09-15

# array([ 3.93, -1.41])

Pero leastq() se hace indispensable con una función no lineal ni linealizable. Veamos otro
ejemplo con una función seno.
from pylab import *
from scipy.optimize import leastsq
from scipy import random

# Generamos unos datos artificiales para hacer el ejemplo


# A datos_y se le añade "ruido" que simula error de
# medida, añadiendole un valor aleatorio
datos_x = arange(0, 0.1, 0.003)
A, k, theta = 10.0, 33.3, pi/5.0
y_real = A*sin(2*pi*k*datos_x + theta)
datos_y = y_real + 2*random.randn(len(datos_x))

# Ahora se trata de ajustar estos datos una función


# modelo tipo senoidal A*sin(2*pi*k*x+theta)

# Defino la funcion de residuos


def residuos(p, y, x):
A, k, theta = p
error = y - A*sin(2*pi*k*x + theta)
return error

# Parámetros iniciales
# y = p[0]*sin(2*pi*p[1]*x + p[2])
# Si estos se alejan mucho del valor real
# la solución no convergerá
p0 = [8.0, 40.0, pi/3]

# hacemos el ajuste por minimos cuadrados


ajuste = leastsq(residuos, p0, args=(datos_y, datos_x))

# El resultado es una lista, cuyo primer elemento es otra


# lista con los parámetros del ajuste.
print ajuste[0]

# Ahora muestro los datos y el ajuste gráficamente

plot(datos_x, datos_y, 'o') # datos

# Defino la funcion modelo, para representarla gráficamente


def funcion(p, x):
return p[0]*sin(2*pi*p[1]*x + p[2])

120Capítulo 11. Ajuste de datos experimentales: el método de mínimos cuadrados


Curso de Computación Científica, Publicación 2016-09-15

# genero datos a partir del modelo para representarlo


x1 = arange(0, datos_x.max(), 0.001) # array con muchos puntos de x
y1 = funcion(ajuste[0], x1) # valor de la funcion modelo en
˓→los x

plot(x1, y1, 'r-')


xlabel('Eje X')
ylabel('Eje Y')
title('Ajuste de funcion seno con leastsq')
legend(('Datos', 'Ajuste lineal'))
show()

Aunque es algo más laborioso, leastsq() nos permite en principio ajustar a cualquier función
ajustable. El módulo optimize posee otros métodos de optimización/minimización y desde la
versión 0.8 de scipy posee la función curve_fit(), que básicamente es una manera simplifi-
cada y más práctica de usar leastsq() para ajuste de curvas.

11.4. Ajuste de funciones no lineales 121


Curso de Computación Científica, Publicación 2016-09-15

11.5 Ejercicios

1. Representar gráficamente los siguientes datos y hacer un ajuste por mínimos cuadrados a un
polinomio de grado tres de los datos representando la curva resultante.
x = 3.1 6.3 9.9 12.6 21.4
y = 50.1 190.2 499.0 720.8 1130.0
Superponer los dibujos de las funciones que resultarían de ajustes a polinomios de orden 1 y
de orden 2.
2. El fichero de texto medidas_PT_H.txt (archivos Tema 7) posee medidas de presión y tempe-
ratura para 10 mol de hidrógeno, que se someten a distintas temperaturas a volumen constan-
te. Este experimento se realiza en tres envases con volúmenes distintos. Suponiendo que el
gas se comporta idealmente y por tanto que se verifica que PV=nRT, representar los datos y
realizar un ajuste lineal P(T) para cada volumen. ¿Cuánto vale la constante de gases ideales
según cada realización del experimento?
3. El fichero de texto medidas_PV_He.txt (archivos Tema 7) posee medidas de presión y vo-
lumen para 0.1 mol de helio, que se comprime sistemáticamente a temperatura constante.
Este experimento se realiza a tres temperaturas distintas. Suponiendo que el gas se compor-
ta idealmente y por tanto que se verifica que PV=nRT, representar los datos y realizar un
ajuste lineal P(V) para cada temperatura. ¿Cuánto vale la constante de gases ideales según el
experimento?
4. Se mide el movimiento unidimensional de una partícula sometida una fuerza a lo lar-
go de 50 segundos; las medidas de posición y tiempo se encuentran en el fichero medi-
das_movimiento_acelerado.txt. Hacer un ajuste a los datos de un movimiento uniforme-
mente acelerado y deducir la aceleración y la velocidad y posición iniciales de la partícula.
Representar gráficamente los datos con puntos y la curva de ajuste, con el etiquetado de ejes
y leyenda.
5. Un isótopo radioactivo sigue una ley de desintegración exponencial de la forma 𝑁 (𝑡) =
𝑁0 𝑒−𝑘𝑡 , donde 𝑁 (𝑡) es la cantidad de material que queda en un tiempo t, 𝑁0 la cantidad origi-
nal (en t=0) y k es la tasa de decaimiento del isótopo. La semivida de un isótopo es el tiempo
que tarda una muestra de ese elemento en disminuir hasta la mitad, es decir 𝑁 (𝑇 ) = 𝑁0 /2.
En un laboratorio se mide cada 12 minutos la masa en gramos de cierto elemento radioactivo.
Estos datos se encuentran en el fichero medidas_sustancia_radioactiva.txt (archivos Tema 7).
Representar gráficamente las medidas con puntos y hacer un ajuste por mínimos cuadrados
del modelo teórico de desintegración radioactiva para conocer la tasa de decaimiento del
isótopo.
6. Para una muestra que contiene 10g de yodo 131 (semivida de 8 días), se hacen diariamente
cinco medidas independientes a lo largo de 60 días. Esas medidas están en el fichero medi-
das_decaimiento_yodo131b.txt (archivos Tema 7), donde cada fila corresponde a cada una
de las 5 medidas realizadas diariamente en gramos de yodo que queda. Representar en un
gráfico con puntos las cinco medidas con colores distintos para cada una y ajustar a cada

122Capítulo 11. Ajuste de datos experimentales: el método de mínimos cuadrados


Curso de Computación Científica, Publicación 2016-09-15

una la curva teórica de decaimiento. Imprimir por pantalla los parámetros de cada uno de los
cinco ajustes.
7. Cualquier metal de longitud 𝐿0 a temperatura inicial 𝑇0 , que es sometido posteriomente a una
temperatura T sufre una dilatación o contracción dada aproximadamente por 𝛿𝐿 = 𝛼∆𝑇 )
donde ∆𝑇 es la diferencia de temperaturas y 𝛼 el coeficiente de dilatación característico del
metal. En un laboratorio se mide la dilatación que experimentan cuatro varillas de metal de
distinto material de longitud inicial 𝐿0 = 10𝑐𝑚 al ir aumentando progresivamente su tempe-
ratura en un grado; estos datos se encuentran en el fichero medidas_dilatacion_metales.txt.
Representar gráficamente las medidas en una única figura con un color distinto para cada
metal y calcular el factor de dilatación a para cada uno ajustando el modelo teórico a los
datos experimentales.

11.5. Ejercicios 123


Curso de Computación Científica, Publicación 2016-09-15

124Capítulo 11. Ajuste de datos experimentales: el método de mínimos cuadrados


CAPÍTULO 12

Otras aplicaciones de Cálculo Numérico

Aunque el paquete numpy ofrece ciertas funcionalidades matemáticas además de la manipulación


básica de arrays, como el paquete linalg para álgebra lineal y random para números aleatorios,
en scipy encontraremos muchas más herramientas matemáticas de análisis científico de las que
podamos necesitar especialmente durante la carrera. scipy es una colección de paquetes de al-
goritmos y herramientas matemáticas para distintas tareas de análisis y cálculo que también utiliza
numpy. scipy posee varios subpaquetes que deben importarse independientemente cuando se
vayan a utilizar; éstos son algunos de ellos:
Subpaquete Descripción
odr Regresión de distancias ortogonales (ODR)
misc Funciones varias (lectura de imagenes, factorial, etc.)
fftpack Algoritmos para transformada de Fourier discreta
io Entrada y salida de datos
stats Funciones estadísticas muy variadas
lib Envoltorios (wrappers) de Python a librerías externas
integrate Integración o cuadratura numérica
ndimage Imagenes n-dimensionales
linalg Álgebra lineal más elaborada que numpy
interpolate Herramientas de interpolación de series numéricas
optimize Herramientas de optimización de funciones
signal Tratamiento de señales o series temporales
Para ver la lista completa de subpaquetes consultar la ayuda de scipy como help(scipy) (ha-
ciendo antes import scipy para tener todos los nombres asociados a scipy) y consultar su
página web para ver la documentación completa (www.scipy.org). Una manera práctica de traba-
jar es importar el espacio de nombres de scipy, es decir el nombre de sus paquetes y funciones
principales y luego importar el o los paquetes que vaya a usar, como en este ejemplo:

>>> from scipy import * # importa el nombre de los subpaquetes


˓→únicamente

>>> import optimize, stats # importa los paquete optimize y stats

125
Curso de Computación Científica, Publicación 2016-09-15

12.1 La integración o cuadratura numérica

12.1.1 El problema que se quiere resolver

En geometría elemental se aprende a calcular el área de figuras simples como rectángulos, triángu-
los, círculos y demás, así como los volúmenes de esferas, cubos, cilindros, conos y otros. Una de
las motivaciones principales para el invento del cálculo integral fue el cálculo de áreas y volúmenes
de regiones del espacio de forma irregular que además constituye un problema central en mecánica
clásica; por ejemplo, para muchos problemas de dinámica de un cuerpo rígido puede considerarse
como una masa puntual localizada en su centroide (centro de gravedad).
Supongamos una función 𝑓 (𝑥) definida en un intervalo [a,b] , la definición moderna de integral:
∫︁ 𝑏
𝐼(𝑓 ) = 𝑓 (𝑥)𝑑𝑥
𝑎

se basa en el concepto de las sumas de Riemann de la forma:


𝑛
∑︁
𝑅𝑛 = (𝑥𝑖+1 − 𝑥𝑖 )𝑓 (𝜉𝑖 )
𝑖=1

donde, 𝑎 = 𝑥1 < 𝑥2 < ... < 𝑥𝑛 < 𝑥𝑛+1 = 𝑏 y 𝜉 ∈ [𝑥𝑖 , 𝑥𝑖+1 ], 𝑖 = 1, ..., 𝑛.
Supongamos que ℎ𝑛 = máx (𝑥𝑖+1 − 𝑥𝑖 ) : 𝑖 = 1, ..., 𝑛 − 1. Si para cualquier 𝑥𝑖 tal que ℎ𝑛 → 0 y
cualquiera que sea 𝜉𝑖 tenemos que el lı́m𝑛→∞ 𝑅𝑛 = 𝑅 finito, entonces se dice que 𝑓 es integrable
en el sentido de Riemann en el intervalo [𝑎, 𝑏] y que el valor de la integral es 𝑅.
Como pueden intuir esta definición enseguida sugiere una forma sencilla de calcular aproximada-
mente la integral. Sólo tenemos que usar una suma de Riemann con 𝑛 suficientemente grande para
alcanzar la precisión requerida en el valor de la integral. Esta idea funciona, pero debemos elegir
los valores de 𝑥𝑖 y 𝜉𝑖 apropiadamente para evaluar el integrando el número de veces que haga falta
sin que sean demasiadas (aumenta el tiempo de cálculo y la necesidad de memoria) o insuficientes
(aumenta la imprecisión en el resultado).
Desgraciadamente, algunas funciones que queremos integrar no tienen primitivas de forma analí-
tica y, por lo tanto, no nos quedará más remedio que utilizar métodos numéricos para calcular las
integrales de forma aproximada. Por otro lado, muchas veces tendremos la función a integrar en
forma de serie numérica y no en forma analítica por lo que deberemos utilizar métodos numéricos
para calcular la integral. A este proceso se le llama cuadratura numérica.
Existen muchas más aplicaciones de la llamada cuadratura numérica, tanto en matemáticas como
en diferentes partes de la Física, de otras ciencias e ingeniería. Aquí solamente introduciremos los
conceptos elementales para el cálculo práctico de las integrales determinadas de funciones de una
variable en un intervalo dado.

126 Capítulo 12. Otras aplicaciones de Cálculo Numérico


Curso de Computación Científica, Publicación 2016-09-15

12.1.2 El método de los coeficientes indeterminados

Para el cálculo aproximado de las integrales tomaremos la idea de la suma de Riemann. De hecho,
aproximaremos la integral por una suma ponderada de los valores del integrando en un número
finito de puntos en el intervalo de integración. De forma más precisa, aproximaremos el valor de
𝐼(𝑓 ) por una regla de cuadratura de 𝑛 puntos que tiene la forma de:
𝑛
∑︁
𝑄𝑛 (𝑓 ) = 𝑤𝑖 𝑓 (𝑥𝑖 ) , 𝑎 ≤ 𝑥1 < 𝑥2 < ... < 𝑥𝑛 ≤ 𝑏.
𝑖=1

A los puntos 𝑥𝑖 se les llama nodos de abcisas o nodos simplemente y a los factores 𝑤𝑖 se les
llama pesos o coeficientes. Una cuadratura se llama abierta si 𝑥 ∈ (𝑎, 𝑏) y cerrada si 𝑥 ∈ [𝑎, 𝑏]. El
principal objetivo consiste en elegir los nodos y los pesos de forma apropiada para que podamos
obtener el nivel deseado de precisión con un coste computacional aceptable, en memoria y/o en
rapidez.
Algunas reglas de cuadratura pueden obtenerse utilizando lo que se conoce como interpolación
polinómica. Se trata de una técnica de interpolación de un conjunto de datos o de una función
por un polinomio. Es decir, dado cierto número de puntos (obtenidos por muestreo o a partir de un
experimento) se pretende encontrar un polinomio que pase por todos los puntos. Los más conocidos
y sencillos los detallamos a continuación.

12.1.3 La cuadratura de Newton-Cotes

La elección más simple de los nodos en una regla de cuadratura es elegirlos igualmente separa-
dos en el intervalo de integración [a,b] lo que constituye la propiedad que define las llamadas
cuadraturas de Newton-Cotes (NC). Una cuadratura NC de n puntos tendrá por nodos:
𝑏−𝑎
𝑥𝑖 = 𝑎 + 𝑖 , 𝑖 = 1, ..., 𝑛.
𝑛+1
Algunas de las reglas más simples de cuadraturas de NC son las siguientes:
Regla del punto medio. Consiste en evaluar la función en el punto medio del intervalo y
sustituir los valores de la función en él por un polinomio de grado 0, es decir una constante;
lo que nos da la regla de NC de un punto:
𝑛−1
∑︁ 𝑥𝑖 + 𝑥𝑖+1
𝑀 (𝑓 ) = ℎ 𝑓( ) ℎ = 𝑥𝑖+1 − 𝑥𝑖 ∀𝑖.
𝑖=1
2
Regla del trapecio: Consiste en evaluar la función en ambos extremos del intervalo y sus-
tituir los valores de la función en él por un polinomio de grado 1, es decir una recta; lo que
nos da la regla de NC de 2 puntos:
𝑛−1
∑︁ 𝑓 (𝑥𝑖 ) + 𝑓 (𝑥𝑖+1 )
𝑇 (𝑓 ) = ℎ ℎ = 𝑥𝑖+1 − 𝑥𝑖 ∀𝑖.
𝑖=1
2

12.1. La integración o cuadratura numérica 127


Curso de Computación Científica, Publicación 2016-09-15

Regla de Simpson: Se evalúa la función en los extremos del intervalo y en el punto medio
del mismo sustituyendo los valores de la función en él por un polinomio de grado 2, una
parábola; lo que nos da la regla de NC de 3 puntos:
𝑛−1 (︂ )︂
ℎ ∑︁ 𝑥𝑖 + 𝑥𝑖+1
𝑆(𝑓 ) = 𝑓 (𝑥𝑖 ) + 4 · 𝑓 ( ) + 𝑓 (𝑥𝑖+1 ) ℎ = 𝑥𝑖+1 − 𝑥𝑖 ∀𝑖.
6 𝑖=1 2

12.1.4 La cuadratura en Scipy de Python

El subpaquete integrate del paquete scipy de Python ofrece varias herramientas de integra-
ción numérica con distintos métodos. Podemos ver todos los disponibles consultando su ayuda:

>>> from scipy import *


>>> help(integrate)

Integration routines
====================

Methods for Integrating Functions given function object.

quad -- General purpose integration.


dblquad -- General purpose double integration.
tplquad -- General purpose triple integration.
fixed_quad -- Integrate func(x) using Gaussian quadrature of order
˓→n.

quadrature -- Integrate with given tolerance using Gaussian


˓→quadrature.

romberg -- Integrate func using Romberg integration.

Methods for Integrating Functions given fixed samples.

trapz -- Use trapezoidal rule to compute integral from samples.


cumtrapz -- Use trapezoidal rule to cumulatively compute integral.
simps -- Use Simpson's rule to compute integral from samples.
romb -- Use Romberg Integration to compute integral from
(2**k + 1) evenly-spaced samples.

See the special module's orthogonal polynomials (special) for Gaussian


quadrature roots and weights for other weighting factors and
˓→regions.

Interface to numerical integrators of ODE systems.

odeint -- General integration of ordinary differential


˓→equations.

ode -- Integrate ODE using VODE and ZVODE routines.

128 Capítulo 12. Otras aplicaciones de Cálculo Numérico


Curso de Computación Científica, Publicación 2016-09-15

Existen por ejemplo implementaciones del método del trapecio o el método de Simpson, am-
bos métodos simples basados en muestras fijas como ya hemos visto. Las funciones trapz()
y simps(), que emplean dichos métodos para al cálculo numérico de integrales, admiten como
entrada un array y de valores a integrar y otro array con la variable independiente x; si no se incluye
un segundo parámetro, el espaciado entre los elementos de y es de 1 por defecto, aunque este valor
se puede asignar con el parámetro opcional dx=1.
Supongamos que queremos integrar numéricamente la función sin(𝑥) de 0 a 𝜋, cuyo valor exacto
es 2. Veamos cómo se puede calcular numéricamente con distinto número de muestras y el grado
de precisión que se alcanza:
>>> from scipy import integrate

>>> # Generamos la función seno con 5 nodos, de 0 a pi


>>> x = linspace(0, pi, 5)
>>> y = sin(x)

>>> # Integramos el array por el método del trapecio


>>> integrate.trapz(y,x)
>>> 1.8961188979370398

Si aumentamos el número de muestras, la integración se acerca más al valor real


>>> # Generamos la función seno con 5 nodos, de 0 a pi
>>> x = linspace(0, pi, 20)
>>> y = sin(x)

>>> # Integramos el array por el método del trapecio


>>> integrate.trapz(y,x)
>>> 1.9954413183201947

>>> # Integramos por el método de Simpson


>>> integrate.simps(y,x)
>>> 1.999977188106568

12.1. La integración o cuadratura numérica 129


Curso de Computación Científica, Publicación 2016-09-15

Como se ve, el método de Simpson nos da un valor más cercano al verdadero (error relativo de
1.14e-05), que el del trapecio (error relativo de 2.28e-03), ya que el primero emplea polinomios de
grado 2 para la integración. Obsérvese que estos métodos de cálculo nos permiten también obtener
las integrales de funciones que conozcamos en forma numérica (como sucesión de nímeros).
Por otro lado, si queremos calcular la integral de una función que tenemos en forma analíti-
ca también lo podemos hacer utilizando la función de Python de uso general más eficiente es
quad(func,a,b), que integra por cuadratura de Clenshaw-Curtis 1 una función de Python
func() entre a y b. Consideremos como ejemplo el cálculo del área una semicircunferencia
de radio unidad calculando la integral bajo la curva. Para ello definimos la función e integramos
entre -1 y 1:

>>> # Definimos la funcion a integrar


>>> def semicirculo(x):
return sqrt(1 - x**2)

# Integramos numéricamente entre -1 y +1


>>> intg, err = integrate.quad(semicirculo, -1.0, 1.0)

# Comprobamos que la precisión en el resultado (el correcto es pi/2)


>>> print((intg-0.5*pi)/(0.5*pi))
6.5872821402755502e-14

La función quad() devuelve por defecto el valor de la integral, que en este caso vale 𝜋2 y una
estimación del error en el proceso de integración numérica. La función quad ofrece como salida,
además del valor de la integral, una estimación del error que comete en el cálculo de la integral;
este error lo calcula internamente y es debido al proceso empleado para el cálculo de la integral
pero no debe confundirse con el cálculo del error relativo que podemos hacer sólo si conocemos el
valor exacto de la integral.
En Python los límites de integración también pueden ser +∞ ∫︀ 2 o −∞ usando
2
los string +Inf o
-Inf; por ejemplo podemos calcular la integral (ver figura) −2 2𝑒𝑥𝑝− 𝑥5 𝑑𝑥,

>>> def func1(x):


return 2.0*exp(-x**2/5.0)

# integración entre -2 y +2
>>> int1, err1 = integrate.quad(func1, -2, +2)
>>> print(int1,err1)
(6.294530963693763, 6.9883332051087914e-14)

Si ahora hacemos la misma integral pero desde −∞ hasta +∞ obtendremos toda el área bajo la
curva definida en el integrando:

# integración entre -infinito y +infinito


>>> int2, err2 = integrate.quad(func1, -Inf, +Inf)

1
Para detalles, ver por ejemplo http://en.wikipedia.org/wiki/Clenshaw-Curtis_quadrature

130 Capítulo 12. Otras aplicaciones de Cálculo Numérico


Curso de Computación Científica, Publicación 2016-09-15

# y el resultado obtenido es:


>>> print(int1,err1)
(7.9266545952120211, 7.5246691415403668e-09)

Nótese la diferencia entre el resultado de ambas integrales así como la diferencia entre el error
estimado al calcularlas.

Es posible incluir varios parámetros a la función empleando el parámetro opcional args como
∫︀una
𝜋
tupla de parámetros (ver la ayuda de quad()). Por ejemplo, calculemos la integral siguiente:
0
(𝑎 + 𝑏𝑠𝑒𝑛(𝑥))𝑑𝑥 para valores de a=0.2 y de b=1:

# Definimos la función a integrar, incluyendo parámetros


def f2(x,a,b):
return a + b*sin(x)

# Definimos unos parámetros de entrada p y q (como a y b)


p=0.2;q=1

# Integramos numéricamente, incluyendo parámetros


integrate.quad(f2, 0, pi, args=(p,q))
(2.6283185307179586, 2.9180197488520396e-14)

De esta forma pueden variarse los parámetros p y q de la función a voluntad.

12.2 Álgebra matricial

Los arrays bidimensionales de numpy pueden interpretarse como matrices, aunque en realidad los
arrays, cuando se opera algebraicamente con ellos, no funcionan como estamos acostumbrados
con matrices. Por ejemplo el producto de dos arrays bidimensionales NxM se realiza elemento a
elemento y no como un producto algebraico de matrices. Veamos unos ejemplos:

12.2. Álgebra matricial 131


Curso de Computación Científica, Publicación 2016-09-15

>>> # Dos arrays de 3 x 3


>>> A = array([[3, 6, 7], [2, 6, 2], [10, 9, 1]])
>>> B = array([[4, 5, 5], [8, 3, 4], [3, 11, 2]])

>>> print(A)
[[ 3 6 7]
[ 2 6 2]
[10 9 1]]

>>> print(B)
[[ 4 5 5]
[ 8 3 4]
[ 3 11 2]]

>>> # Producto entre estos arrays


>>> print(A*B)
[[12 30 35]
[16 18 8]
[30 99 2]]

que, como vemos da como resultado otro array con elementos que son el producto elemento a
elemento de los arrays factores. Sin embargo numpy permite hacer el producto punto entre estos
arrays que es similar (de hecho idéntico) al producto matricial de dos matrices con la función
func:dot:
>>> # Producto entre los arrays como si fueran matrices
>>> print(dot(A,B))
[[ 81 110 53]
[ 62 50 38]
[115 88 88]]

No obstante, si en un problema determinado, se va a operar a menudo con matrices, es conveniente


definirlas como tales. Para ello, se usa el comando mat() de numpy(), que es una abreviatura
de matrix. Un elemento matrix es idéntico a un array y se crea de igual manera o a partir de
arrays, pero se comporta como una matriz:
>>> # Creación elemento matriz (igual que un array)
>>> C = mat([[4, 5, 5], [8, 3, 4], [3, 11, 2]])
>>> type(C)
>>> <class 'numpy.core.defmatrix.matrix'>

También se pueden convertir arrays en matrices de la forma siguiente:


>>> # Conversión de array a matriz
>>> A = mat(A)
>>> type(A)
>>> <class 'numpy.core.defmatrix.matrix'>

132 Capítulo 12. Otras aplicaciones de Cálculo Numérico


Curso de Computación Científica, Publicación 2016-09-15

>>> # Producto matricial


>>> print(A*C)
[[ 81 110 53]
[ 62 50 38]
[115 88 88]]

12.3 Operaciones básicas con matrices

La inversa de una matriz A es una matriz B tal que AB = I donde I es la llamada matriz
identidad que consiste en una matriz en la que los elementos en la diagonal son unos y son ceros
en el resto. Normalmente B se denota como B = A−1 . En scipy, la inversa de una matriz de un
array numpy se puede calcular haciendo linalg.inv(A), o usando el método A.I si A es una
matriz. Por ejemplo, consideremos
⎡ ⎤
1 3 5
A =⎣ 2 5 1 ⎦
2 3 8

entonces:
⎡ ⎤ ⎡ ⎤
−37 9 22 −1.48 0.36 0.88
1 ⎣
A−1 = 14 2 −9 ⎦ = ⎣ 0.56 0.08 −0.36 ⎦ .
25
4 −3 1 0.16 −0.12 0.04

este cálculo lo haríamos con scipy de la siguiente manera:

>>> A = mat([[1, 3, 5], [2, 5, 1], [2, 3, 8]])


>>> A
matrix([[1, 3, 5],
[2, 5, 1],
[2, 3, 8]])
>>> A.I
matrix([[-1.48, 0.36, 0.88],
[ 0.56, 0.08, -0.36],
[ 0.16, -0.12, 0.04]])
>>> from scipy import linalg
>>> linalg.inv(A)
array([[-1.48, 0.36, 0.88],
[ 0.56, 0.08, -0.36],
[ 0.16, -0.12, 0.04]])

12.3. Operaciones básicas con matrices 133


Curso de Computación Científica, Publicación 2016-09-15

12.4 Resolución de sistemas de ecuaciones lineales

Con Scipy es muy fácil resolver un sistema de ecuaciones empleando la función


linalg.solve(). Este comando tiene como parámetros de entrada la matriz y el vector de
términos independientes. Si la matriz es simétrica el proceso de cálculo se puede acelerar si se
indica como parámetro. Supongamos que queremos resolver el siguiente sistema de ecuaciones:
𝑥 + 3𝑦 + 5𝑧 = 10
2𝑥 + 5𝑦 + 𝑧 = 8
2𝑥 + 3𝑦 + 8𝑧 = 3
Podemos encontrar la solución usando la matriz inversa:
⎡ ⎤ ⎡ ⎤−1 ⎡ ⎤ ⎡ ⎤ ⎡ ⎤
𝑥 1 3 5 10 −232 −9.28
⎣ 𝑦 ⎦ = ⎣ 2 5 1 ⎦ ⎣ 8 ⎦ = 1 ⎣ 129 ⎦ = ⎣ 5.16 ⎦ .
25
𝑧 2 3 8 3 19 0.76
Sin embargo, es mejor usar el comando linalg.solve ya que es más rápido y numéricamente
más estable, aunque en este caso el resultado es el mismo:
>>> A = mat('[1 3 5; 2 5 1; 2 3 8]') # Las filas se separan con
";"
˓→

>>> b = mat('[10;8;3]')
>>> A.I*b # Usando la matriz inversa
matrix([[-9.28],
[ 5.16],
[ 0.76]])

>>> # Lo mismo, usando la funcion linalg.solve(A,b)


>>> linalg.solve(A,b)
array([[-9.28],
[ 5.16],
[ 0.76]])

12.4.1 Cálculo del determinante

Supongamos que 𝑎𝑖𝑗 son los elementos de la matriz A y 𝑀𝑖𝑗 = |A𝑖𝑗 | será el determinante de la
matriz que se obtiene elimiando la i-esima fila y la j-esima columna de A. Entonces para cualquier
fila i:
∑︁
|A| = (−1)𝑖+𝑗 𝑎𝑖𝑗 𝑀𝑖𝑗 .
𝑗

Con Scipy el determinante se puede calcular con linalg.det. Por ejemplo, el determinante de la
matriz A
⎡ ⎤
1 3 5
A =⎣ 2 5 1 ⎦
2 3 8

134 Capítulo 12. Otras aplicaciones de Cálculo Numérico


Curso de Computación Científica, Publicación 2016-09-15

es
⃒ ⃒ ⃒ ⃒ ⃒ ⃒
⃒ 5 1 ⃒ ⃒ 2 1 ⃒ ⃒ 2 5 ⃒
|A| = 1 ⃒⃒ ⃒ − 3⃒
⃒ 2 8 ⃒ + 5⃒ 2 3 ⃒
⃒ ⃒ ⃒
3 8 ⃒
= 1 (5 · 8 − 3 · 1) − 3 (2 · 8 − 2 · 1) + 5 (2 · 3 − 2 · 5) = −25.

Con scipy se calcula tan fácilmente como:

>>> A = mat([[1, 3, 5], [2, 5, 1], [2, 3, 8]])


>>> linalg.det(A)
-25.000000000000004

12.5 Ejercicios

1. Calcular numéricamente las siguientes integrales

∫︁ 1 √ ∫︁ 2𝜋
2𝜋 𝑥 3
1 + 9𝑥4 𝑑𝑥 𝑒−𝑥 sin (10𝑥) 𝑑𝑥
0 0

2. Comprobar las siguientes integrales calculándolas numéricamente:

∫︁ 1 ∫︁ 4 ∫︁ 2𝜋
−𝑥2 𝑑𝑥 𝑑𝑥 2𝜋
𝑒 𝑑𝑥 = 0.746 = tan−1 (4) = 1.3258 = √ = 3.627
0 0 1 + 𝑥2 0 2 + cos 𝑥 3

3. Calcular numéricamente el área más pequeña comprendida entre un círculo 𝑥2 + 𝑦 2 = 25 y


la recta x=3.
𝑙𝑛𝑥
4. Escribir un programa en el que dibujen la función 1−𝑥 en el intervalo [0,5]. Calcular el
área bajo de la figura formada por los ejes OX, OY y esta curva en el intervalo [0,1]. Su
resultado exacto es −𝜋 2 /6. Calcular los errores absoluto y relativo con el que se ha obtenido
el resultado, dando sólo las cifras significativas.
5. Crear una función que calcule numéricamente la siguiente integral admitiendo parámetros
de entrada m y n:

1
𝑥𝑚 − 𝑥𝑛
∫︁
𝑚+1
𝜋 𝑑𝑥 = ln
0 ln 𝑥 𝑛+1

6. Las leyes de Kirchhoff de circuitos, indican que la suma de los voltajes en cada malla de
un circuito es igual∑︀a la suma
∑︀ de las resistencias multiplicadas por la corriente que circula
por ellas, es decir 𝑉 = 𝑅𝐼, que no es más que la ley de Ohm generalizada y que las

12.5. Ejercicios 135


Curso de Computación Científica, Publicación 2016-09-15

∑︀ ∑︀
corrientes en los nodos se conserva, es decir, 𝐼𝑒𝑛𝑡𝑟𝑎𝑛 = 𝐼𝑠𝑎𝑙𝑒𝑛 . Utilizando las leyes de
Kirchhoff crear un sistema de ecuaciones para el siguinte circuito y resolverlo numéricamen-
te para conocer cada una de las corrientes que circulan por él.

7. Resolver el sistema AX=B donde:

⎡ ⎤ ⎡ ⎤
1 3 5 7 1
⎢ 2 −1 3 5 ⎥ ⎢ 2 ⎥
A =⎢
⎣ 0
⎥ 𝑦 B =⎢ ⎥
0 2 5 ⎦ ⎣ 3 ⎦
−2 −6 −6 1 4

8. Utilice las leyes de Kirchhoff para conocer las corrientes que circulan por este circuito,
sabiendo que V1 = 7.5 V, V2 = 5.4 V y las resistencias valen 6.7, 2.3, 9.0, 1.0 y 5.6 Ω.

136 Capítulo 12. Otras aplicaciones de Cálculo Numérico


CAPÍTULO 13

Apéndice A: Recursos informáticos para el curso

13.1 El sistema operativo

Cualquier sistema operativo se puede utilizar para el análisis de datos y computación científica en
general, aunque en ambientes científicos y técnicos GNU/Linux es muy empleado y será nuestro
sistema operativo de referencia. En las aulas de informática de la facultad utilizamos Bardinux, la
distribución Linux de la Universidad de la Laguna, que junto con Ubuntu, una de las distribuciones
Linux más populares, son las que recomendamos y se pueden descargar gratuitamente de sus sitios
web.
Si utilizas MS Windows en cualquier versión, puedes crear una partición en tu ordenador para que
éste tenga ambos sistemas operativos, aunque hay que recordar que no es indispensable usar Linux.
Esta opción no es posible para los usuarios de Mac, aunque el MacOS es un sistema operativo
similar a Linux por lo que no aportaría tanto.
Existe la opción de usar una máquina virtual para instalar en Windows o Mac una distribución de
Linux, pero no es recomendable porque se hace muy pesado para el ordenador y la idea es tener
un sistema en que trabajar habitualmente.

13.2 El lenguaje de programación

Existen literalmente cientos de lenguajes de programación, pero sólo algunos son adecuados para el
análisis interactivo de datos. En los últimos años, los más usados son Matlab, IDL o Mathematica,
que son programas con licencia privada (o sea que hay que comprar) pensados para uso científico y
en ingeniería. Por otro lado, Python es un lenguaje interactivo de uso general publicado en código
abierto (open source, compatible con licencia GPL) que es muy usado en ciencia y tecnología,
siendo una alternativa más flexible y completa que los anteriores lenguajes.
Ventajas de Python
Lenguaje de código abierto y gratuito para todos los sistemas operativos.

137
Curso de Computación Científica, Publicación 2016-09-15

Es de uso general, no solo científico.


Fácil de usar y con una enorme comunidad de usuarios.
Desventajas de Python
Módulos (funcionalidades) específicos a instalar por separado.
Existen varios módulos para hacer lo mismo.
La documentación está dispersa.
Python es hoy en día uno de los cinco lenguajes de programación más usados, muy fácil de apren-
der y muy polivalente, ya que tiene amplio uso en prácticamente todos los campos de ciencia,
ingeniería e informática.

13.3 Python y módulos científicos para Python

Ya que Python es un lenguaje de uso general, existen funcionalidades para prácticamente cualquier
proyecto o actividad. Aunque la instalación de Python ya viene con gran cantidad de funcionali-
dades o módulos, para trabajos específicos como el análisis científico de datos, son necesarios
módulos adicionales. Para este curso usaremos los módulos científicos en Python más populares,
que es necesario instalar además del propio Python.
Los módulos básicos que usaremos son los siguientes:
Scipy/Numpy - Paquete científico y numérico básico.
Matplotlib - Librería gráfica para gráficos 2D.
ipython - No es un módulo de Python, sino una consola avanzada, que usaremos en lugar de
la de estándar de Python

13.3.1 Python para Linux

Si usas Linux, Python ya viene instalado y sólo tienes que instalar los módulos adicionales usando
el administrador de paquetes de tu distribución. La manera más fácil instalar software en Linux
es usar la consola de comandos. Para distribuciones basadas en Debian como Ubuntu o Bardinux,
basta ejecutar lo siguiente en una consola:

japp@paquito:~$ sudo apt-get install python-scipy


japp@paquito:~$ sudo apt-get install python-matplotlib
japp@paquito:~$ sudo apt-get install ipython

El comando sudo te permite ejecutar un comando como superusuario o administrador del orde-
nador (necesario para instalar), por lo que te pedirá la contraseña de administrador e instalará los

138 Capítulo 13. Apéndice A: Recursos informáticos para el curso


Curso de Computación Científica, Publicación 2016-09-15

paquetes de Python, scipy (y numpy, porque lo necesita) y matplotlib y la consola avanzada


ipython.
Otra opción para instalar en Ubuntu o Bardinux es usar el instalardor gráfico, que sueler estar en
la opción Sistema del menú de inicio. Ahí basta con buscar los paquetes necesarios e instalarlos.

13.3.2 Python en Windows o Mac

Tanto en Windows como en Mac es posible instalar Python y luego instalar los módulos científicos
necesarios, aunque hay varios proyectos que hacen esto por nosotros, es decir, instalar Python y
todos los módulos científicos en un paso. El proyecto más completo y recomendado es Anaconda ,
un software que reúne Python y gran cantidad de módulos. Basta descargar la versión de Anaconda
- Python 2.7 para el sistema operativo que usemos y seguir las instrucciones.
Veremos que tenemos la opción de usar dos versiones distintas, Python 2.7 o Python 3.5, y aunque
podemos emplear cualquiera y no hay gran diferencia entre ambas, es recomendable usar por ahora
Python 2.7 ya que es el que viene por defecto en Linux y Mac y de esta manera todos usaremos la
misma versión.

13.4 Editores de texto

Además del uso de Python interactivamente por terminal de comandos, también necesitaremos
hacer programas ejecutables, por lo que necesitaremos un editor de texto/código adecuado. Existen
muchas opciones para todos los sistemas operativos y suelen todos venir con alguno útil, aunque a
nosotros nos interesa un editor sencillo que de destaque con colores el código y ayude a la edición.

13.4.1 Editores para Linux

Gedit: Editor por defecto para escritorios Gnome (como Ubuntu). Simple pero muy funcio-
nal.
Kedit: Editor por defecto para escritorios KDE (como Bardinux). Muy similar a Gedit.
Kate: Pensado para programadores, muy completo y con muchas herramientas para la edi-
ción de código.
Spyder: Entorno de desarrollo y análisis de datos. Ideal para manipular datos de manera
interactiva y pensado para científicos, aunque algo complejo para nuestro curso.
Si usas Linux, probablemente ya tengas instalado alguno de estos editores, si no, utiliza el admi-
nistrador de paquetes de tu distribución. También puedes instalar programas usando la terminal de
comandos de Linux:

13.4. Editores de texto 139


Curso de Computación Científica, Publicación 2016-09-15

sudo apt-get install kate # Si usas Ubuntu, Kubuntu o Bardinux


˓→(te pregunta contraseña de administrador)

yum install kate # Si usas Fedora. Debes hacerlo como


˓→root.

13.4.2 Editores para Mac

En Mac existen varias buenas opciones también. Mac OSX incluye por defecto un editor de texto,
llamado TextEdit.app que se puede utilizar para editar código, aunque es muy limitado. Reco-
mendamos alguna de las siguientes opciones:
Spyder. Entorno de desarrollo y análisis de datos. Ideal para manipular datos de manera
interactiva y pensado para científicos. Está incluído en Python-Anaconda.
TextWrangler. Editor de texto sencillo de caracter general.

13.4.3 Editores para Windows

Windows incluye por defecto dos editores de texto, el Notepad y Wordpad o similares. El primero
es demasiado sencillo y el segundo está más orientado a escribir documentos en lugar de progra-
mas, por lo que no recomendamos usar ninguno de los dos. En su lugar existen buenas opciones:
Spyder.: Entorno de desarrollo y análisis de datos con Python. Ideal para manipular datos de
manera interactiva y pensado para científicos. Está incluído en Python-Anaconda.
Notepad++. Editor muy completo y para muchos lenguajes de programación.

13.5 Más documentación y bibliografía

Python es un lenguaje de programación muy completo y con extensa documentación, pero convie-
ne tener a mano algunas guías:
Python Scientific Lecture Notes. Completo curso de Python científico; Incluye algunos temas
avanzados. En inglés.
Introducción a la programación con Python, Andrés Marzal, Isabel Gracia y Pedro García
Sevilla, Universitat Jaume I (2014). Guía de programación muy completa y detallada, aunque
orientada a estudiantes de informática.
Python para todos, Raúl González Duque. Buen libro de introducción de Python, aunque no
incluye paquetes científicos.

140 Capítulo 13. Apéndice A: Recursos informáticos para el curso


Curso de Computación Científica, Publicación 2016-09-15

Think Python - How to Think Like a Computer Scientist, Allen Downey – Otra buena guía
de introducción a Python. En inglés.
Documentación oficial (sólo de consulta)
Tutorial oficial de Python - http://docs.python.org/tutorial/
Documentación de Scipy y Numpy - http://docs.scipy.org/doc/
Documentación de Matplotlib - http://matplotlib.sourceforge.net/contents.html
Documentación de Ipython - http://ipython.scipy.org/doc/stable/html/

13.5. Más documentación y bibliografía 141


Curso de Computación Científica, Publicación 2016-09-15

142 Capítulo 13. Apéndice A: Recursos informáticos para el curso


CAPÍTULO 14

Apéndice B: El sistema operativo GNU/Linux

El sistema operativo es el programa principal de un ordenador y se emplea para ofrecer al usuario


funcionalidades básicas para interactuar con él. Además de controlar los dispositivos de entrada
y salida como teclado, monitor, etc. en el sistema operativo, se ejecutan otros programas para
tareas más específicas como procesadores de texto, hojas de cálculo o navegadores para Internet.
Aunque el mercado de ordenadores personales está dominado principalmente por el sistema ope-
rativo Windows, existen otros que se emplean en campos específicos. En ciencia e ingeniería, se
emplea tradicionalmente el sistema UNIX y existe mucho software científico y técnico para esta
plataforma y sus variantes. El los últimos años, ha ganado popularidad el sistema GNU/Linux (o
simplemente Linux), que se originó a finales de los años ochenta y principios de los noventa como
un sistema operativo libre y de código abierto inspirado en UNIX y muy similar a éste. Actual-
mente, Linux [#linux_] se usa ampliamente en ambientes académicos y técnicos, y su licencia de
código abierto facilita su uso libre para estudiantes e investigadores.
GNU/Linux es un proyecto en el que participan voluntarios y empresas de todo el mundo y como
cualquiera puede participar en él, es muy amplio y configurable. En realidad, lo que habitualmen-
te se conoce como Linux es su kernel o núcleo con funcionalidades principales y junto con él
funcionan gran variedad de programas complementarios como interfases gráficos, utilidades pa-
ra administrar el sistema y programas en general. Esta variedad de opciones hace que no exista
un único Linux, sino varias distribuciones con distintas características, generalmente similares.
Aunque existen decenas de distribuciones, hay algunas especialmente populares, como Ubuntu,
Fedora o SuSE. Para este curso usaremos la distribución de Linux de la Universidad de La Laguna,
Bardinux, que se basa a su vez en la distribución Kubuntu.

14.1 Empezando con Linux: el escritorio de trabajo

Linux es un sistema multiusuario, por lo que requiere una cuenta de usuario y su contraseña para
entrar en el sistema y en él cada usuario posee una zona de trabajo reservada con una configuración
propia única. Para empezar, entra en el sistema escribiendo tu nombre de usuario y contraseña. Una
vez dentro, verás un escritorio de trabajo similar al de la Figura 1, en el que se reconocen varias
zonas.

143
Curso de Computación Científica, Publicación 2016-09-15

Figura 14.1: Escritorio de Bardinux 3.2.

La barra horizontal inferior es la barra de tareas y aplicaciones, desde donde se accede a los pro-
gramas presionando el botón de la esquina inferior izquierda (menú de aplicaciones). Desde el
menú de aplicaciones podemos ver los programas disponibles en el ordenador, organizados por
categorías. Para este curso, utilizaremos básicamente el navegador Firefox, que se encuentra en la
categoría Internet y también un editor de texto (no procesador de textos gráfico) como Kate. En
la pestaña “Máquina” al lado de Aplicaciones hay un icono de acceso al directorio personal de
nuestro usuario. En general, lo más práctico es buscar el programa deseado en el buscador, en la
parte superior del menú.

14.2 Trabajando con la consola de Linux. Directorios y


Ficheros.

Aunque gracias al interfaz gráfico de Linux (el que usa Bardinux se llama KDE) podemos hacer
prácticamente cualquier operación con el ordenador de manera gráfica como con Windows o Mac,
para muchos trabajos conviene saber cómo hacerlos manualmente en lugar de usar la interfaz
gráfico. Para ello usaremos la terminal de comandos, que no es más que un programa en el que
escribimos órdenes o comandos al ordenador. Aunque esto puede parecer anticuado, en cuanto
se conoce un poco cómo funciona a menudo se trabaja de forma más rápida y directa con ella
y además es indispensable para ciertos trabajos. Para iniciar una terminal, hay que ir al menú de
aplicaciones en la sección Sistema, donde está la aplicación Konsole (o buscarlo en el buscador);
al ejecutarlo aparecerá una ventana como la de la Figura 3.

144 Capítulo 14. Apéndice B: El sistema operativo GNU/Linux


Curso de Computación Científica, Publicación 2016-09-15

Figura 14.2: Pantalla de la teminal de comandos Konsole.

Una vez lanzada la terminal de comandos, ésta está lista para recibir instrucciones. En ella veremos
una pantalla en negro (u otro color, según la configuración). La única línea que aparece en ella es
la línea de comandos o “Prompt” en inglés, que indica el nombre del usuario y el de la máquina,
separados por una arroba. En el ejemplo anterior vemos:

japp@paquito:~$

lo que el usuario es “japp” trabajando en el ordenador paquito. Al iniciar la terminal, el lugar de


trabajo por defecto es nuestro directorio personal, normalmente llamado home, que es lo que indica
el símbolo “~” a la derecha del indicador, antes del signo de dólar ($). Podemos comprobar que
estamos ahí ejecutando el comando pwd, que nos indica nuestro lugar de trabajo:

japp@paquito:~$ pwd
/home/japp
japp@paquito:~$

Vemos que nos devuelve una línea con nuestro directorio de trabajo actual /home/japp, y regresa
al prompt. Podemos ver el contenido de nuestro directorio y cualquier otro usado el comando ls:

Figura 14.3: Listado del contenido de un directorio en la terminal de comandos usando ls.

14.2. Trabajando con la consola de Linux. Directorios y Ficheros. 145


Curso de Computación Científica, Publicación 2016-09-15

Generalmente los comandos de Linux tienen varias opciones que cambian su funcionalidad; por
ejemplo, para ver un listado más completo del directorio, usamos la opción -l del comando ls:

Figura 14.4: Listado detallado del contenido de un directorio en la terminal de comandos usando
ls -l.

Ahora vemos un fichero o directorio por línea, cuyo nombre aparece a la derecha de todo. A la
izquierda aparecen unos caracteres del tipo “drwxr-xr-x” que se refieren a los permisos de acceso
al fichero, que veremos más adelante. Después se muestra el usuario y grupo al que pertenecen los
ficheros, que en el ejemplo de arriba es “japp” para ambos. La siguiente columna indica el tamaño
en bytes del fichero y finalmente se muestra la fecha y hora de su creación.
Desde la terminal podemos crear un fichero o ejecutar cualquier programa. Podemos, por ejemplo,
lanzar el editor de texto kwrite o cualquier otro (kate, p.e.) y crear un fichero de texto. :
japp@paquito:~$ kate prueba.txt &

Vemos que ahora aparece el nuevo fichero de texto, con un tamaño y fecha de creación distintos.
Nótese que el primer carácter de la tabla de permisos de cada fichero es “d”, excepto para prue-
ba.txt. La “d” indica que es un directorio y por tanto todos los documentos en el listado anterior
son directorios, excepto el fichero de texto que acabamos de crear.
Podemos crear nuestro propio directorio usando el comando mkdir:
japp@paquito:~$ mkdir practicas_16-septiembre-2011

y entramos en él cambiando de directorio con el comando cd:


japp@paquito:~$ cd practicas_16-septiembre-2011
japp@paquito:~/practicas_16-septiembre-2011$ pwd
/home/japp/practicas_16-septiembre-2011
japp@paquito:~$

Fíjate que el prompt ha cambiado y ahora muestra el nuevo directorio; comprobamos que esta-
mos en él usado pwd nuevamente. Para regresar al directorio anterior, podemos hacerlo con cd
/home/japp o bien bajando un nivel de directorio con cd ... Aquí ”..” (dos puntos seguidos)
es una abreviatura que se refiere al directorio anterior. Igualmente, como el símbolo “~” es una
abreviatura de nuestro home, podemos ir a él estemos donde estemos escribiendo cd ~ o incluso
cd solamente.

146 Capítulo 14. Apéndice B: El sistema operativo GNU/Linux


Curso de Computación Científica, Publicación 2016-09-15

Linux tiene una estructura de directorio cuyo raíz o directorio base es */* y en él están otros di-
rectorios del sistema, como el home. Podemos ver su contenido haciendo ls -l / en la terminal.
Verás que el propietario de todos los ficheros del directorio raíz o del sistema es el usuario root.
El usuario root es el administrador del sistema y tiene privilegios especiales en el ordenador y sus
usuarios; un usuario normal no puede por tanto alterar estos ficheros ni instalar nuevos programas,
sólo el usuario root puede hacerlo.

14.3 Copiando, moviendo y renombrando ficheros

Cualquier fichero o directorio se puede copiar con el comando cp, indicando las rutas de los fiche-
ros de origen y destino:

japp@paquito:~$ cp prueba.txt prueba2.txt


japp@paquito:~$ cp /home/japp/prueba.txt /home/japp/Documentos/prueba.
˓→txt

En el primer ejemplo se hace una copia del mismo fichero en el mismo directorio con otro nombre
y en el segundo se copia el fichero a otro directorio con el mismo nombre. Como es habitual, se
puede indicar la ruta (o path, en inglés) como en el segundo ejemplo o usar direcciones relativas
como en el primero. En caso de querer cambiar el nombre de un fichero o su localización, se
emplea el comando mv, cuyo uso es idéntico a cp, sólo que mv reemplaza un fichero o directorio
por otro en lugar de copiarlo:

japp@paquito:~$ mv prueba.txt prueba2.txt


japp@paquito:~$ mv /home/japp/prueba.txt /home/japp/Documentos/prueba.
˓→txt

en el primer ejemplo se cambia el nombre del fichero prueba.txt por prueba2.txt, en el segun-
do se mueve el prueba.txt del home al directorio Documentos/, conservando el mismo nombre.
Fíjate que “cambiar de nombre” y “mover” son exactamente lo mismo. En realidad, para Linux
el nombre de un fichero es su ruta completa. Por ejemplo, nuestro fichero prueba.txt, para Li-
nux en realidad se llama /home/japp/prueba.txt y al “moverlo” realmente lo renombramos a /ho-
me/japp/Documentos/prueba.txt y de ahí la equivalencia entre mover y renombrar.
Para eliminar un fichero, puedes hacerlo con rm, pero debes usarlo con precaución, porque no hay
vuelta atrás y el borrado es permanente. Para borrar un directorio vacío, se puede hacer con rmdir,
pero daría un error si éste no está vacío. Si queremos borrar un directorio no vacío, podemos usar
el comando rm de manera recursiva con la opción -r:
Si no recuerdas cómo se usa un comando o las opciones que tiene, recuerda que para cualquier
comando de Linux tienes acceso a ayuda escribiendo comando correspondiente con la opción
--help. Para una descripción más detallada, se puede acceder a su manual escribiendo man
<comando>:

14.3. Copiando, moviendo y renombrando ficheros 147


Curso de Computación Científica, Publicación 2016-09-15

japp@paquito:~$ cp --help # Ayuda breve del comando cp


japp@paquito:~$ man cp # Manual del comando cp

14.4 Caracteres comodín

Para muchos comandos de la terminal que implican tratar con ficheros de nombre similar, podemos
utilizar caracteres comodín “*”, ”?” y “[ ]”. El * reemplaza a cualquier cadena de caracteres, por
ejemplo:

japp@paquito:~$ ls pr*

listará todos los ficheros que empiezan por “pr”. El ”?” reemplaza cualquier carácter, pero única-
mente uno, por ejemplo:

japp@paquito:~$ ls prueba?.txt

listaría prueba1.txt, prueba2.txt, pruebaA.txt de haberlos, pero no prueba1.mp3 ni prueba.txt. Fi-


nalmente se pueden poner caracteres en corchetes para cubrir un rango de caracteres:

japp@paquito:~$ ls prueba[2-4].txt

que listaría ficheros prueba2.txt, prueba3.txt y prueba4.txt si existiesen.

14.5 Trabajando con ficheros de texto

La terminal de Linux tiene muchos comandos y utilidades para trabajar con ficheros de texto nor-
males. Por ejemplo, aunque habitualmente el resultado de un comando cualquiera se muestra por
pantalla, como un listado con ls, podemos pasar este resultado a un fichero de texto en lugar de la
pantalla poniendo “> nombre_del_fichero.txt” después del comando:

japp@paquito:~$ ls /usr/* > listado_usr.txt

el comando anterior lista todos los ficheros que hay en el directorio del sistema /usr, pero en lugar
de mostrar el listado por pantalla lo copia a un fichero llamado “listado_usr.txt”. La extensión
.txt que indica que es un fichero de texto no es obligatoria, pero sí muy recomendable. El fichero
resultante es un fichero de texto normal que podemos abrir por ejemplo con kwrite o kate, que son
editores de texto. Podemos echar un vistazo rápido a un fichero de texto como este con el comando
cat que nos muestra contenido del fichero en la pantalla. Como vemos que se trata de un fichero
bastante largo y difícil manipular, podemos usar el comando more que nos muestra su contenido
pasando pantallas con la barra espaciadora y del que podemos salir con la letra “q”. Un comando
más completo es less que nos muestra el contenido como more pero permitiéndonos avanzar y
retroceder en el fichero usando las flechas de dirección del teclado:

148 Capítulo 14. Apéndice B: El sistema operativo GNU/Linux


Curso de Computación Científica, Publicación 2016-09-15

recuerda que puedes ver más opciones de los comando con la opción --help.
Como curiosidad útil, podemos encadenar varios comandos con una “tubería” o “pipeline” usan-
do el carácter “|” entre comandos. Por ejemplo, si queremos ver las diez primeras líneas del direc-
torio /usr/ sin grabarlo a un fichero, basta con hacer:

japp@paquito:~$ ls /usr/* | head

aquí lo primero que hace la terminal es crear el listado como antes, pero en lugar de mostrarlo por
pantalla, envía el resultado al comando head, que lo procesa y nos muestra el resultado. Podemos
usar el comando wc para contar las líneas, palabras y caracteres de un fichero:

japp@paquito:~$ wc listado_usr.txt
5987 5973 77062 listado_usr.txt

los números que aparecen como resultado indican el número de líneas (5987), palabras (5973) y
caracteres (77062) del fichero. Si queremos buscar un fichero en un directorio o en todo el disco,
usamos find con la notación siguiente:

find /home/japp -name "*.txt"


---------- ----------
| |
| |
V V
ruta patrón de búsqueda

este ejemplo busca todos los ficheros de extensión “txt” en /home/japp. Para buscar en todos los
directorios:

japp@paquito:~$ find * -name "*.txt"

Al igual que ficheros sueltos, podemos buscar dentro de ficheros de texto. Eso lo podemos hacer
con “grep <cadena de búsqueda> <fichero>”. Por ejemplo, el comando:

japp@paquito:~$ grep ark listado_usr.txt

busca dentro de listado_usr.txt todas las cadenas “ark” que hay e imprime por pantalla la línea en
la que está.

14.6 El sistema de usuarios y permisos de Linux

Puesto que Linux es un sistema multiusuario con usuarios independientes, los ficheros que crea en
su home son suyos y únicamente suyos. El usuario puede administrar el acceso a sus documentos
cambiando su tabla de permisos, que se muestra cuando se hace un listado largo. Por ejemplo, para
el fichero de texto que creamos:

14.6. El sistema de usuarios y permisos de Linux 149


Curso de Computación Científica, Publicación 2016-09-15

japp@paquito:~$ ls -l prueba.txt
japp@paquito:~$ -rw-r--r-- 1 japp japp 0 2009-09-14 19:47 prueba.txt

como dijimos, el primer carácter es “d” si es un directorio y “-” si no lo es, el resto se agrupan en
tres grupos de caracteres “rwx” que se refieren al dueño del fichero, su grupo y a todos los demás y
las letras rwx indican permisos de lectura (r), escritura (w) y ejecución (x). Esto se entiende mejor
con unos ejemplos:

-rw-r--r-- El dueño tiene permisos para leer y escribir el


fichero, el resto solo para ver.
-rw-rw-r-- El dueño y su grupo tienen permisos para leer y
escribir, el resto solo para ver.
-r-------- El dueño sólo puede leer el fichero, el resto
no pueden verlo ni modificarlo.

Por defecto, los ficheros se crean con permisos -rw-r–r–, y estos se pueden modificar con el co-
mando chmod. Su modo de empleo es chmod <XXX> <nombre del fichero> en el que <XXX>
son tres números de 0 a 7 que son los permisos para el usuario, grupo y el resto. Cada valor de X
es la suma cada permiso: X=Lectura+Escritura+Ejecución; los valores de los permisos se asignan
de la siguiente manera:

0 ningún permiso
1 permiso de ejecución
2 permiso de escritura
3 permisos de escritura y ejecución
4 permiso de lectura
5 permisos de lectura y de ejecución
6 permisos de lectura y de escritura
7 todos los permisos

mejor vemos esto con unos ejemplos:

# Permiso de lectura y escritura para el usuario y de lectura para los


˓→demás

japp@paquito:~$ chmod 644 prueba.txt


# Todos los permisos para el usuario, ninguno para el resto
japp@paquito:~$ chmod 700 prueba.txt
# El usuario y su grupo pueden leerlo, el resto no tiene ningún permiso
japp@paquito:~$ chmod 440 prueba.txt

Una manera alternativa de cambiar permisos es asignarlos por tipo de categoría de usuario (usuario,
grupo o resto) usando esta notación:

chmod [ugoa][+-][rwx] <fichero>

donde [ugoa] se refiere a al usuario (u), grupo (g), otros (o) o todos (a), el [+-] para dar (+) o quitar
(-) el permiso y [rwx] y indica qué permiso cambiar. Aquí van unos ejemplos:

150 Capítulo 14. Apéndice B: El sistema operativo GNU/Linux


Curso de Computación Científica, Publicación 2016-09-15

# Da al usuario permiso de escritura


japp@paquito:~$ chmod u+w prueba.txt
# Da a todos permisos de lectura
japp@paquito:~$ chmod a+r prueba.txt
# Quita al resto de usuarios permisos de ejecución
japp@paquito:~$ chmod o-x prueba.txt

Si se desea cambiar los permisos a un directorio completo, debe incluirse la opción -R (recursivo)
después del comando. Por ejemplo, el comando:

japp@paquito:~$ chmod -R a+r Documentos/

da permisos de lectura al directorio Documentos y a todo su contenido. Estas combinaciones son


más que suficientes para desenvolverse con permisos de archivos.

14.7 Empaquetando y comprimiendo ficheros

A menudo tenemos que guardar y transportar ficheros de un ordenador a otro o como copias de
seguridad y en ocasiones los ficheros pueden ser muchos o muy grandes. Se puede comprimir un
fichero con el comando gzip y descomprimirlo con gunzip:

japp@paquito:~$ gzip listado_usr.txt # Comprime listado_usr.txt a


˓→listado_usr.txt.gz

japp@paquito:~$ gunzip listado_usr.txt # Descomprime listado_usr.txt.


˓→gz

Fíjate que gzip sólo comprime ficheros sueltos. Si queremos comprimir varios ficheros o un direc-
torio, debemos empaquetar los ficheros antes con el comando tar:

# Empaqueta el directorio Documentos/ y su contenido al fichero


˓→Documentos.tar

japp@paquito:~$ tar -cf Documentos.tar Documentos/

# Comprime el fichero Documentos.tar a Documentos.tar.gz


japp@paquito:~$ gzip Documentos.tar

En realidad la operación anterior se puede hacer en una sola línea con tar y las opciones adecua-
das:

# Empaqueta y comprime el directorio Documentos/ al fichero Documentos.


˓→tar.gz

japp@paquito:~$ tar -czf Documentos.tar.gz Documentos/


# Descomprime y extrae Documentos.tar.gz
japp@paquito:~$ tar -xzf Documentos.tar.gz

14.7. Empaquetando y comprimiendo ficheros 151


Curso de Computación Científica, Publicación 2016-09-15

Hay que notar que aquí usamos -c para empaquetar y -x para desempaquetar; la z se refiere a
que el tar debe comprimirse con gzip y la f indica que después viene el nombre del fichero con
el que empaquetará.
Otro comando similar que empaqueta y comprime al mismo tiempo es zip:

japp@paquito:~$ zip -r Documentos/ Documentos.zip


japp@paquito:~$ unzip Documentos.zip

en donde hemos comprimido el directorio Documentos con todo su contenido (opción -r) en un
archivo llamado Documentos.zip. Para recuperarlo usamos el comando unzip.

14.8 Ejercicios

1. Genere un listado de todos los archivos que se encuentren en el directorio /usr/bin que em-
piecen con “a” y que en algún lugar tengan una “o”.
2. ¿Qué comando usaría para borrar todos los ficheros que empiecen por un número y que
tengan extensión mp3 que estén en el directorio Música/ del home?
3. Lista el contenido del home ordenado por fecha de creación (averigua la opción adecuada).
4. ¿Cómo listaría el contenido de /usr/* que se muestre página a página como hace el comando
more?
5. En el directorio /bin del sistema hay programas ejecutables ¿cómo contaría cuántos hay?
6. En el home hay un fichero oculto llamado konqueror, que utiliza el administrador gráfico de
archivos. Encuentre la ruta o path de ese fichero. Los ficheros ocultos tienen un ”.” delante
del nombre.
7. ¿Cual es la diferencia entre intentar borrar un fichero 700 y otro 400, siendo ambos del
propietario? Comprobarlo en la terminal.
8. Cambie los permisos a prueba.txt de manera que absolutamente todos puedan leerlo y mo-
dificarlo.
9. El fichero anterior, prueba.txt, tiene ahora permisos de lectura y escritura para todos. Ha
cambiado de opinión y ahora quiere que el resto (el grupo y otros) sólo pueda leerlo sin
modificarlo, ¿cómo les quitaría esos permisos?
10. El fichero del sistema /proc/meminfo, es un fichero de texto que contiene información sobre
la memoria del ordenador. La memoria total está indicada dentro del fichero con la pala-
bra MemTotal (fíjese en las mayúsculas). ¿Cómo buscarías esa cadena de texto o palabra?
¿Cuánta memoria tiene el ordenador en kB?
11. El comando “ps -ux” muestra una lista larga de procesos que se están ejecutando en el orde-
nador. Muestra sólo los 10 últimos.

152 Capítulo 14. Apéndice B: El sistema operativo GNU/Linux


Curso de Computación Científica, Publicación 2016-09-15

12. El comando “history” muestra por pantalla todos los comandos ejecutados en la consola; si
pones “history 25”, muestra los últimos 25, etc. En lugar de mostrarlos por pantalla, pásalos
a un fichero llamado comandos_[tu_nombre].txt en el home.
13. Empaqueta y comprime todos los ficheros de texto en tu directorio de trabajo (con extensión
txt), incluyendo el fichero de textos con la lista de comandos, en un fichero tar.gz.

14.8. Ejercicios 153


Curso de Computación Científica, Publicación 2016-09-15

154 Capítulo 14. Apéndice B: El sistema operativo GNU/Linux


CAPÍTULO 15

Apéndice C: La distribución Gaussiana

La distribución gaussiana puede considerarse como el límite de una distribución binomial cuando el
número posible de resultados es infinitamente grande, tan grande que podemos hablar del continuo
(números reales). Es decir, la variable en una distribución gaussiana puede tomar valores entre −∞
y +∞, contrariamente a lo que sucedía con las distribuciones anteriores. No obstante, en la práctica
el número de realizaciones de cualquier suceso es siempre limitado y es importante saber si este
conjunto finito de realizaciones (la distribución subyacente) se puede aproximar a (o representar
por) una distribución gaussiana. En estos casos podemos utilizar sus propiedades para analizar la
distribución subyacente de nuestro conjunto limitado de datos
Tiene multitud de aplicaciones en todos los campos puesto que parece que sea la preferida por la
naturaleza como modelo para los experimentos y observaciones, de tal suerte que suele llamár-
sela también distribución normal. Aunque fue obtenida primero por Demoivre en 1733 cuando
estudiaba un problema de lanzamiento de monedas, fue más tarde obtenida independientemente
por Laplace y por Gauss; de ahí lo de distribución gaussiana, que fue aplicada inicialmente a los
errores accidentales (o aleatorios) en medidas astronómicas y de otros experimentos científicos.
La forma de una distribución gaussiana (la famosa “Campana de Gauss”) es la siguiente:

1 (𝑥 − 𝑥)2
℘𝐺 (𝑥, 𝑥, 𝜎) = √ exp[− ]
2𝜋𝜎 2𝜎 2

y al ser una función continua consideramos esta función como una densidad de probabilidad; es
decir que la probabilidad de que una observación se encuentre entre 𝑥 y 𝑥 + 𝑑𝑥 será ℘𝐺 (𝑥, 𝑥, 𝜎)𝑑𝑥.
Nótese que 𝑥 es la media y 𝜎 es la desviación estándar de la distribución.

15.1 Propiedades, media y varianza

En el caso de la distribución normal la media y la desviación estándar entran directamente en su


definición. Podemos fijarnos y comprobar que entre las propiedades de la distribución normal están
las siguientes:

155
Curso de Computación Científica, Publicación 2016-09-15

∫︀ ∞
−∞
℘𝐺 (𝑥, 𝑥, 𝜎)𝑑𝑥 = 1; esto correspondería a la probabilidad de obtener una observación
cualquiera,

∫︀ 𝑥2 de que una observación esté comprendida en el intervalo [𝑥1 , 𝑥2 ] se calcularía


la probabilidad
haciendo 𝑥1 ℘𝐺 (𝑥, 𝑥, 𝜎) 𝑑𝑥,
la función es simétrica respecto de 𝑥, la media,
𝑒−1/2
℘𝐺 (𝑥 ± 𝜎) = √
2𝜋𝜎
, donde 𝜎 es la desviación estándar,
la probabilidad de que una observación caiga dentro del rango 𝑥 ± 𝜎 es de 0.6827 y dentro
de 𝑥 ± 3𝜎 es de 0.9973.

15.2 Población, Muestra y Error estándar de la media

Es bastante usual ver a un conjunto de observaciones o resultados de experimentos como una


muestra finita del conjunto infinito, o mucho más grande que podríamos obtener, que se conoce
como población. La cuestión es si esta muestra finita, de (𝑛) datos, se puede representar por una
distribución normal. Si es así, deberíamos ser capaces de encontrar los parámetros de la distribu-
ción normal (media y distribución estándar de la muestra) que ajustan mejor al conjunto de datos.
Estos parámetros encontrados mediante en la muestra o conjunto, ¿serán la media y la desviación
estándar de toda la población?. La respuesta es que, en general, no coincidirán exactamente, aun-
que si 𝑛 es lo suficientemente grande se aproximarán mucho. Visto desde otro ángulo: si tenemos
otro conjunto de datos (otra muestra) del mismo experimento u observación con el mismo número
de elementos (𝑛), su media y desviación estándar, ¿serán iguales a la primera?. En principio no,
aunque se parecerán cada vez más a medida que 𝑛 crezca.
Puede demostrarse matemáticamente que la distribución de medias y la de desviaciones estándar
que obtendríamos al repetir el experimento muchas veces (con lo que tendríamos muchas muestras)
es que también es una distribución gaussiana. Su media√ es igual a la media verdadera de toda la
población mientras que su desviación estándar es 𝜎/ 𝑛 donde 𝑛 es el número de datos de cada
muestra y 𝜎 la desviación estándar de toda la población.
Por lo tanto, cuando trabajamos con un conjunto de datos cuya distribución subyacente √ es una
distribución normal de media 𝑥 es apropiado escribir la media encontrada como: 𝑥 ± 𝜎/ 𝑛, donde
lo escrito detrás del símbolo ± es el llamado √ error estándar de la media. De forma similar, el
error estándar de la desviación
√ estándar es 𝜎/ 2𝑛, de forma que la desviación estándar la podemos
escribir como: 𝜎(1 ± 1/ 2𝑛).

15.3 La media pesada y su error estándar

Supongamos que tenemos una muestra de 𝑛 medidas obtenidas de un experimento u observación de


cualquier fenómeno físico {𝑥𝑖 } cuya distribución subyacente es una distribución normal. Además,
supongamos que algunas medidas se han obtenido con mejores precisiones {𝜎𝑖 } que otras. Puede

156 Capítulo 15. Apéndice C: La distribución Gaussiana


Curso de Computación Científica, Publicación 2016-09-15

mostrarse (Gauss fue quien primero lo demostró) que la media ponderada con los pesos 𝑤𝑖 = 1/𝜎𝑖2
es el valor más probable de la medida que buscamos (de la distribución subyacente).
Es decir, como se dice en [6.1]:

𝑥𝑖 /𝜎𝑖2
∑︀
𝑥 = ∑︀𝑖 2
(15.1)
𝑖 1/𝜎𝑖

y de [6.2] la desviación estándar de la distribución alrededor de esta media será:

(𝑥𝑖 − 𝑥)2 /𝜎𝑖2


∑︀
𝜎 2 = 𝑖 ∑︀ 2
(15.2)
𝑖 1/𝜎𝑖

y de la sección anterior podemos concluir que el error asociado al valor medio será:
√︃ ∑︀
𝑖 (𝑥𝑖 − 𝑥)2 /𝜎𝑖2
𝜖= (15.3)
(𝑛 − 1) 𝑖 1/𝜎𝑖2
∑︀

Démonos cuenta que si todas las medidas están tomadas con igual precisión esta definición se
reduce a:
√︃ ∑︀
2
𝑖 (𝑥𝑖 − 𝑥) (15.4)
𝜖=
𝑛(𝑛 − 1)

15.4 Consistencia interna y externa de un conjunto de


medidas

Acabamos de ver que si el peso 𝑤𝑖 asignado a una medida 𝑥𝑖 es igual (o proporcional) a 1/𝜖2𝑖 ,
donde 𝜖𝑖 es el error estándar de 𝑥𝑖 , entonces el error estándar de la media pesada 𝑥 podemos
calcularlo como en la ecuación (15.3). A esta forma de obtener el error estándar le llamaremos el
error externo, para distinguirlo de otra forma de calcularlo, el error interno que obtendremos a
continuación.
En efecto, el error estándar de la media pesada también podemos obtenerla de otra forma. En el
capítulo II de esta serie hemos definido como calcular la propagación de errores en una función
cualquiera:
[︃ (︂ )︂2 ]︃
∑︁ 𝜕𝑥
𝜎𝑥2 = 𝜎𝑖2 (15.5)
𝑖
𝜕𝑥 𝑖

En este caso si lo aplicamos a la definición de 𝑥 podemos llegar a que el resultado final es:
1
𝜎𝑥2 = ∑︀ 2 (15.6)
𝑖 (1/𝜎𝑖 )

15.4. Consistencia interna y externa de un conjunto de medidas 157


Curso de Computación Científica, Publicación 2016-09-15

Las expresiones (15.3) (error externo, 𝜖𝑒𝑥𝑡 ) y (15.6) (error interno, , 𝜖𝑖𝑛𝑡 ) de la media son muy dife-
rentes aparentemente, pero ambas deberian coincidir. Fijémonos que mientras la segunda expresión
((15.6)) depende tan sólo de los errores estándar de cada una de las observaciones o medidas, la
primera expresión ((15.3)) depende también de las diferencias entre ellas y el valor medio.
Su cociente será:
]︂1/2
− 𝑥)2 /𝜖2𝑖
[︂∑︀
𝜖𝑒𝑥𝑡 𝑖 (𝑥𝑖 (15.7)
𝑍= =
𝜖𝑖𝑛𝑡 (𝑛 − 1)
y debería ser igual a 1. Para muestras de medidas tomadas de una población normal infinita puede
demostrarse√︀que 𝜖𝑖𝑛𝑡 y 𝜖𝑒𝑥𝑡 deben coincidir o, mejor dicho, siendo estrictos 𝑍 debe ser 1 con un error
estándar 1/ 2(𝑛 − 1). Si esto es así podemos decir que las observaciones pueden considerarse
consistentes. En este caso, lo mejor es considerar el mayor de los dos errores como el error estándar
de la media pesada y el resultado de las observaciones podemos tomarlo como 𝑥 ± 𝑚𝑎𝑥(𝜖𝑖𝑛𝑡 , 𝜖𝑒𝑥𝑡 ).
En la práctica, generalmente encontrarán que esto no es así y que 𝑍 no coincidirá con la unidad.
Pero esto no debería sorprenderles ya que, por un lado depende fuertemente de los valores de 𝜖𝑖 ;
algunos de ellos pueden ser considerablemente imprecisos o inexactos. Por otro lado, 𝑍 también
depende de lo que se separan las medidas de su valor medio ( 𝑖 (𝑥𝑖 − 𝑥)2 ) y por lo tanto pueden
∑︀
estar afectados de errores sistemáticos. Por tanto, el conjunto de observaciones no es consistente.
Si el resultado difiere significativamente de la unidad podemos concluir que tenemos errores siste-
máticos presentes en el conjunto de observaciones. Si 𝑍 ≫ 1, podemos tener errores sistemáticos
entre las observaciones o podemos haber subestimado los errores en cada observación, o ambos;
deberemos repasar el experimento viendo las posibles fuentes de error sistemático que han podido
pasar por alto y/o deberemos asignar nuevos errores a cada una de las observaciones. Esto último
será más bien arbitrario dependiendo inevitablemente del juicio y conocimiento del científico de
las condiciones experimentales. El caso contrario, el de 𝑍 ≪ 1 es mucho menos frecuente y se
debería a una sobreestimación de los errores de cada una de las observaciones.

15.5 Bibliografía

Estas notas se han redactado con ayuda de otros trabajos que merecen ser citados:
1. Topping, J. (1972, 4th ed.). Errors of Observation and their Treatment, Science paperbacks,
Chapman & Hall Ed.
2. Fernández Soto, A. (2005). Técnicas experimentales y Análisis de Datos I. Proyecto Docen-
te. OA - UVEG

15.6 Ejercicios

1. El control de calidad de una fábrica que produce bombillas eléctricas comprueba 1000, ele-
gidas al azar, cada mes. Encuentran que el número de horas de encendido de las bombillas

158 Capítulo 15. Apéndice C: La distribución Gaussiana


Curso de Computación Científica, Publicación 2016-09-15

sigue una distribución normal con una vida media de 950 horas de encendido y una des-
viación estándar de 150 horas. Se pide que calculen el número de bombillas que podemos
esperar que tengan: a) menos de 650 horas de encendido, b) entre 800 y 1100 horas y c) entre
1100 y 1250 horas.
2. Los diámetros de las esporas del lycopodium pueden medirse por métodos interferométricos.
Los resultados de uno de estos experimentos son los siguientes:
k * diametro (cm) 14 15 16 17 18 19 20 21 22 23
numero de esporas 1 1 8 24 48 58 35 16 8 1
donde k=5880. Se pide:
Encuentren el diámetro medio de las esporas y su error estándar y
representen los resultados por un histograma y dibujen encima la correspondiente distribu-
ción normal.
1. Los ficheros dssn2002.txt y dssn2007.txt contienen las observaciones del índice 𝐼𝑚 que es
proporcional al número de manchas solares en un determinado día del año 2002 o 2007. Las
columnas de la tabla contienen: 1) el día correspondiente, 2) el día en fracción de año, 3) el
𝐼𝑚 en toda la superficie visible solar, 4) el 𝐼𝑚 solo en el hemisferio norte visible y 5) el 𝐼𝑚
solo en el hemisferio sur visible. Para uno cualquiera de los dos ficheros, se pide que:
hagan un histograma de estas observaciones (rango de 0 a 180),
calculen el promedio y la desviación éstandar anual, para cada mes, para cada uno de los
hemisferios y el global,
ofrezcan, como salida del programa, un fichero con una tabla en la que las columnas sean: 1)
mes, 2) promedio, 3) error, 4) desviación éstandar; de la 5) a la 7) lo mismo que las 2), 3) y
4) pero sólo para el hemisferio norte; de la 8) a la 10) igual que antes pero para el hemisferio
sur. En la últimafila obtengan los valores para todo el año.
1. El experimento MarkI, situado en el Observatorio de El Teide viene observando, desde 1975,
la velocidad de la superficie solar respecto del observatorio por un método de dispersión
resonante usando el efecto Doppler. Una de sus medidas consiste en la velocidad hacia el
rojo debido a la diferencia entre el campo gravitatorio en el Sol y en el Observatorio. En los
ficheros file2008.txt y similares, hay tablas en las que se muestran los resultados obtenidos
en diferentes días del año. Se pide que:
calculen el promedio de la velocidad y su error así como la varianza, utilizando como pesos
estadísticos la inversa del cuadrado del error en cada una de las medidas,
calculen los mismos parámetros pero con igual peso estadístico para todas las medidas,
repitan los cálculos anteriores eliminando del conjunto de medidas aquéllas que difieran de
la media en más de tres veces la desviación estándar.

15.6. Ejercicios 159


Curso de Computación Científica, Publicación 2016-09-15

160 Capítulo 15. Apéndice C: La distribución Gaussiana


CAPÍTULO 16

Apéndice D: Cálculo Simbólico

En el capítulo anterior vimos cómo es posible usar técnicas numéricas para resolver problemas ma-
temáticos complejos, como integrales, sistemas de ecuaciones, etc. En Física e ingeniería es prác-
ticamente más habitual resolver ciertos problemas numéricamente debido a la dificultad o incluso
imposibilidad de hacerlo analíticamente. Sin embargo, a menudo necesitamos resolver problemas
metemáticos de manera analítica necesariamente, en ocasiones pueden ser problemas complejos.
Por ejemplo, es posible que para obtener la primitiva cierta integral recorramos a técnicas de subs-
titución, integración por partes, etc. o bien tengremos que consultar un libro de tablas de integrales.
Afortunadamente, los ordenadores también nos pueden ayudar al cálculo analítico y Python posee
para ello módulo Sympy. Sympy es una librería de cálculo simbólico que permite resolver analí-
ticamente múltiples problemas matemáticos, entre ellos derivadas, integrales, series numéricas o
límites. Veamos algunas posibilidades de esta librería.

16.1 Introducción a Sympy

Al igual que en Python existen varios tipos de datos numéricos como enteros (int), decimales (float)
o booleanos (bool: True, False, etc.), Sympy posee tres tipos de datos propios: Real, Rational e In-
teger, es decir, números reales, racionales y enteros. Esto quiere decir que Rational(1,2) representa
1/2, Rational(5,2) a 5/2, etc. y no 0.5 o 2.5.
>>> from sympy import *
>>> a = Rational(1,2)

>>> a
1/2

>>> a*2
1

>>> Rational(2)**50/Rational(10)**50
1/88817841970012523233890533447265625

161
Curso de Computación Científica, Publicación 2016-09-15

También existen algunas constantes especiales, como el número e o 𝜋, si embargo éstos se tratan
con símbolos y no tienen un valor númerico determinado. Eso quiere decir que no se puede obtener
un valor numérico de una operación usando el pi de Sympy, como (1+pi), como lo haríamos con
el de Numpy, que es numérico:

>>> pi**2
pi**2

>>> pi.evalf()
3.14159265358979

>>> (pi+exp(1)).evalf()
5.85987448204884

como se ve, sin embargo, se puede usar el método evalf() para evaluar una expresión para tener un
valor en punto flotante (float). Es posible incluso representar matemáticamente el símbolo infinito,
mediante oo:

>>> oo > 99999


True
>>> oo + 1
oo

Para hacer operaciones simbólicas hay que definir explícitamente los símbolos que vamos a usar,
que serán en general las variables y otros elementos de nuestras ecuaciones:

>>> x = Symbol('x')
>>> y = Symbol('y')

Y ahora ya podemos manipularlos como queramos:

>>> x+y+x-y
2*x

>>> (x+y)**2
(x + y)**2

>>> ((x+y)**2).expand()
2*x*y + x**2 + y**2

Es posible hacer una substitución de variables usando subs(viejo,nuevo):

>>> ((x+y)**2).subs(x, 1)
(1 + y)**2

162 Capítulo 16. Apéndice D: Cálculo Simbólico


Curso de Computación Científica, Publicación 2016-09-15

>>> ((x+y)**2).subs(x, y)
4*y**2

16.2 Operaciones algebraicas

Podemos usar apart(expr,x) para hacer una descomposición parcial de fracciones:

>>> 1/( (x+2)*(x+1) )


1
---------------
(2 + x)*(1 + x)

>>> apart(1/( (x+2)*(x+1) ), x)


1 1
------- - --------
1 + x 2 + x

>>> (x+1)/(x-1)
-(1 + x)
---------
1 - x

>>> apart((x+1)/(x-1), x)
2
1 - ------
1 - x

Para unirlas, podemos usar together(expr,x):

>>> together(1/x + 1/y + 1/z)


x*y + x*z + y*z
---------------
x*y*z

>>> together(apart((x+1)/(x-1), x), x)


-1 - x
-------
1 - x

>>> together(apart(1/( (x+2)*(x+1) ), x), x)


1
---------------
(2 + x)*(1 + x)

16.2. Operaciones algebraicas 163


Curso de Computación Científica, Publicación 2016-09-15

16.3 Cálculo de límites

Sympy puede calcular límites usando la función limit() con la siguientesintaxis: limit(función,
variable, punto), lo que calcularía el limite de f(x) cuando variable -> punto::

>>> x = Symbol("x")
>>> limit(sin(x)/x, x, 0)
1

es posible incluso usar límites infinitos:

>>> limit(x, x, oo)


oo

>>> limit(1/x, x, oo)


0

>>> limit(x**x, x, 0)
1

16.4 Cálculo de derivadas

La función de Sympy para calcular la derivada de cualquier función es diff(func,var). Vea-


mos algunos ejemplos:

>>> x = Symbol('x')
>>> diff(sin(x), x)
cos(x)
>>> diff(sin(2*x), x)
2*cos(2*x)

>>> diff(tan(x), x)
1 + tan(x)**2

Puedes comprobar que es correcto calculando el límite:

>>> limit((tan(x+y)-tan(x))/y, y, 0)
1 + tan(x)**2

También se pueden calcular derivadas de orden superior indicando el orden de la derivada como
un tercer parámetro opcional de la función diff():

>>> diff(sin(2*x), x, 1) # Derivada de orden 1


2*cos(2*x)

164 Capítulo 16. Apéndice D: Cálculo Simbólico


Curso de Computación Científica, Publicación 2016-09-15

>>> diff(sin(2*x), x, 2) # Derivada de orden 2


-4*sin(2*x)

>>> diff(sin(2*x), x, 3) # Derivada de orden 3


-8*cos(2*x)

16.5 Expansión de series

Para la expansión de series se aplica el método .series(var,punto,orden) a la serie que


se desea expandir:

>>> cos(x).series(x, 0, 10)


1 - x**2/2 + x**4/24 - x**6/720 + x**8/40320 + O(x**10)
>>> (1/cos(x)).series(x, 0, 10)
1 + x**2/2 + 5*x**4/24 + 61*x**6/720 + 277*x**8/8064 + O(x**10)

e = 1/(x + y)
s = e.series(x, 0, 5)

pprint(s)

La función pprint de Sympy imprime el resultado de manera más legible:

4 3 2
1 x x x x
- + -- - -- + -- - -- + O(x**5)
y 5 4 3 2
y y y y

16.6 Integración

La integración definida e indefinida de funciones es una de las funcionalidades más interesantes de


Sympy. Veamos algunos ejemplos:

>>> integrate(6*x**5, x)
x**6
>>> integrate(sin(x), x)
-cos(x)
>>> integrate(log(x), x)
-x + x*log(x)

16.5. Expansión de series 165


Curso de Computación Científica, Publicación 2016-09-15

>>> integrate(2*x + sinh(x), x)


cosh(x) + x**2

También con funciones especiales:

>>> integrate(exp(-x**2)*erf(x), x)
pi**(1/2)*erf(x)**2/4

También es posible calcular integrales definidas:

>>> integrate(x**3, (x, -1, 1))


0
>>> integrate(sin(x), (x, 0, pi/2))
1
>>> integrate(cos(x), (x, -pi/2, pi/2))
2

Y también integrales impropias:

>>> integrate(exp(-x), (x, 0, oo))


1
>>> integrate(log(x), (x, 0, 1))
-1

Algunas integrales definidas complejas es necesario definirlas como objeto Integral() y luego
evaluarlas con el método evalf():

>>> integ = Integral(sin(x)**2/x**2, (x, 0, oo))


>>> integ.evalf()
>>> 1.6

16.7 Ecuaciones algebraicas y álgebra lineal

Otra sorprendente utilidad de Sympy es su capacidad para resolver sistemas de ecuaciones fácil-
mente:

>>> # Una ecuación, resolver x


>>> solve(x**4 - 1, x)
[I, 1, -1, -I]

# Sistema de dos ecuaciones. Resuelve x e y


>>> solve([x + 5*y - 2, -3*x + 6*y - 15], [x, y])
{y: 1, x: -3}

166 Capítulo 16. Apéndice D: Cálculo Simbólico


Curso de Computación Científica, Publicación 2016-09-15

Sympy tiene su propio tipo de dato Matriz, independiente del de Numpy/Scipy. Con él se pueden
definir matrices numéricas o simbólicas y operar con ellas:

>>> # Matriz identidad 2x2


>>> Matrix([[1,0], [0,1]])
[1, 0]
[0, 1]

>>> x = Symbol('x')
>>> y = Symbol('y')
>>> A = Matrix([[1,x], [y,1]])
>>> A
[1, x]
[y, 1]

>>> A**2
[1 + x*y, 2*x]
[ 2*y, 1 + x*y]

Existen otros constructores similares a arrays de Numpy pero para matrices:

>>> # Matrices de unos


>>> ones((5,5))
[1, 1, 1, 1, 1]
[1, 1, 1, 1, 1]
[1, 1, 1, 1, 1]
[1, 1, 1, 1, 1]
[1, 1, 1, 1, 1]

>>> # Matriz identidad


>>> eye(3)
[1, 0, 0]
[0, 1, 0]
[0, 0, 1]

Hay que fijarse en que muchas de las funciones anteriores ya existen en Numpy con el mismo
nombre (ones(), eye(), etc.), por lo que si queremos usar ambas debemos importar los paquetes
con otro nombre, Por ejemplo:

>>> import numpy as np


>>> from sympy import *

>>> # Funcion eye de Sympy (matriz)


>>> eye(3)
[1, 0, 0]
[0, 1, 0]
[0, 0, 1]

16.7. Ecuaciones algebraicas y álgebra lineal 167


Curso de Computación Científica, Publicación 2016-09-15

>>> # Funcion eye de Numpy (array)


>>> np.eye(3)
array([[ 1., 0., 0.],
[ 0., 1., 0.],
[ 0., 0., 1.]])

>>> # Matriz de Numpy


>>> np.matrix(np.eye(3))
matrix([[ 1., 0., 0.],
[ 0., 1., 0.],
[ 0., 0., 1.]])

Es posible operar entre ellas, salvo que las matrices de Numpy no pueden operar con símbolos,
algo que se puede hacer con Sympy. La selección de elementos de matrices de Sympy se hace de
manera idéntica a los arrays o matrices de Numpy:
>>> # Multiplico la matriz identidad por x
>>> x = Symbol('x')
>>> M = eye(3) * x
>>> M
[x, 0, 0]
[0, x, 0]
[0, 0, x]

>>> # Substituyo x por 4 en la matriz


>>> M.subs(x, 4)
[4, 0, 0]
[0, 4, 0]
[0, 0, 4]

>>> # Substituyo la variable x por y en la matriz


>>> y = Symbol('y')
>>> M.subs(x, y)
[y, 0, 0]
[0, y, 0]
[0, 0, y]

>>> def f(x): return 1.5*x**2


....:

>>> eye(3).applyfunc(f)
[1.5, 0, 0]
[ 0, 1.5, 0]
[ 0, 0, 1.5]

168 Capítulo 16. Apéndice D: Cálculo Simbólico


Curso de Computación Científica, Publicación 2016-09-15

>>> M = Matrix(( [1, 2, 3], [3, 6, 2], [2, 0, 1] ))

>>> # Determinante de la matriz


>>> M.det()
-28
# Matriz inversa
>>> M.inv()
[-3/14, 1/14, 1/2]
[-1/28, 5/28, -1/4]
[ 3/7, -1/7, 0]

>>> # Substituyo algunos elementos de la matriz


>>> M[1,2] = x
>>> M[2,1] = 2*x
>>> M[1,1] = sqrt(x)
>>> M
[1, 2, 3]
[3, x**(1/2), x]
[2, 2*x, 1]

Podemos resolver un sistema de ecuaciones por el método LU:

>>> # Matriz 3x3


>>> A = Matrix([ [2, 3, 5], [3, 6, 2], [8, 3, 6] ])
>>> # Matriz 1x3
>>> x = Matrix(3,1,[3,7,5])
>>> b = A*x
>>> # Resuelvo el sistema por LU
>>> soln = A.LUsolve(b)
>>> soln
[3]
[7]
[5]

16.8 Ejercicios
(︀ 2 )︀
1. Calcular la derivada de 𝑥3 arccos 𝑥
.
2. Calcular la derivada segunda de 𝑒−𝑥 log (𝑥).
3. Calcular los diez primeros elementos de la serie:
∑︁ 1
.
𝑛=0
(1 + 𝑛)5/2

16.8. Ejercicios 169


Curso de Computación Científica, Publicación 2016-09-15

4. Calcular la siguiente integral:


∫︁
cos (𝑥)
𝑦(𝑥) = 𝑑𝑥.
sin (𝑥)

Evaluar el resultado para x=0.5.


5. Calcular la integral siguiente y evaluar su valor en x=1.5:
∫︁
1
𝑦(𝑥) = 𝑑𝑥
1 + 𝑒𝑥

6. Comprobar las siguientes integrales definidas:


∫︁ 1

∫︁ 1 ∫︁ ∞
1 1 1
√︁ (︀ )︀ 𝑑𝑥 = 𝜋 𝑥 log (1 + 𝑥) 𝑑𝑥 = 𝑒−𝑥 sin (𝑥) 𝑑𝑥 =
0 log 𝑥1 0 4 0 2

170 Capítulo 16. Apéndice D: Cálculo Simbólico


CAPÍTULO 17

Historial de cambios

17.1 Revisión 3.2 - Septiembre 2016

Revisión general de erratas y tipográficos.


Actualización de Apéndice sobre Recursos Informáticos
Capítulo sobre cálculo simbólico movido a apéndice

17.2 Revisión 3.1 - Septiembre 2015

Error en ejercicio control de flujo. Suma = 3/4 en lugar de 1/2

17.3 Versión 3.0 - Septiembre 2014

Revisión general de erratas y tipográficos.


Explicado iterador enumerate() en control de flujo.
Ampliado el apartado Estructura de un programa o script y normas de escritura incluyendo
normas de escritura de código en Python en “Programas ejecutables”
Dibujo sobre indices en Python
Añadidas y explicadas funciones insert() y append() de numpy.
Reescrito párrafo de introducción a numpy.
Corrección de errores debido a cambios de versión de Python (help de print) e IPython (run
en lugar de -lp).
Actualización del apéndice sobre recursos para el curso, sobre cómo instalar Linux y Python.

171
Curso de Computación Científica, Publicación 2016-09-15

César Esteban en la lista de autores

17.4 Versión 2.5 - Noviembre 2013

Ampliación de lectura de ficheros con numpy


Revisión capítulo de gráficos
Cambio de orden de ejercicios de capitulo de graficos
Introduccion de capitulo sobre probabilidad y aleatorios
Cambio de orden de numpy y estadística
Capítulo de Distribución normal, como apendice C

172 Capítulo 17. Historial de cambios

También podría gustarte